LCOV - code coverage report
Current view: top level - src - main.c (source / functions) Coverage Total Hit
Test: coverage.info Lines: 37.4 % 340 127
Test Date: 2026-04-17 09:17:10 Functions: 50.0 % 12 6

            Line data    Source code
       1              : #include <stdio.h>
       2              : #include <stdlib.h>
       3              : #include <string.h>
       4              : #include <unistd.h>
       5              : #include <locale.h>
       6              : #include "config_store.h"
       7              : #include "setup_wizard.h"
       8              : #include "email_service.h"
       9              : #include "platform/terminal.h"
      10              : #include "platform/path.h"
      11              : #include "raii.h"
      12              : #include "logger.h"
      13              : #include "local_store.h"
      14              : #include "fs_util.h"
      15              : #include "smtp_adapter.h"
      16              : #include "compose_service.h"
      17              : 
      18              : /* Number of non-data lines printed around the message table */
      19              : #define LIST_HEADER_LINES 6
      20              : /* Default limit when stdout is not a terminal or --batch is given */
      21              : #define BATCH_DEFAULT_LIMIT 100
      22              : 
      23           55 : static int detect_page_size(int batch) {
      24           55 :     if (batch || !terminal_is_tty(STDOUT_FILENO))
      25           55 :         return BATCH_DEFAULT_LIMIT;
      26            0 :     int rows = terminal_rows();
      27            0 :     if (rows > LIST_HEADER_LINES + 2)
      28            0 :         return rows - LIST_HEADER_LINES;
      29            0 :     return 20; /* safe fallback */
      30              : }
      31              : 
      32              : /* ── Help pages ──────────────────────────────────────────────────────── */
      33              : 
      34            1 : static void help_general(void) {
      35            1 :     printf(
      36              :         "Usage: email-cli [--batch] <command> [options]\n"
      37              :         "\n"
      38              :         "Global options:\n"
      39              :         "  --batch           Disable interactive pager; use fixed page size (%d).\n"
      40              :         "                    Implied when stdout is redirected to a pipe or file.\n"
      41              :         "\n"
      42              :         "Commands:\n"
      43              :         "  list                    List messages in the configured mailbox\n"
      44              :         "  show <uid>              Display the full content of a message by its UID\n"
      45              :         "  folders                 List available IMAP folders\n"
      46              :         "  attachments <uid>       List attachments in a message\n"
      47              :         "  save-attachment <uid>   Save a named attachment to disk\n"
      48              :         "  send                    Send a message non-interactively\n"
      49              :         "  config                  View or update configuration (incl. SMTP)\n"
      50              :         "  help [command]          Show this help, or detailed help for a command\n"
      51              :         "\n"
      52              :         "Run 'email-cli help <command>' for more information.\n"
      53              :         "For background sync and cron management use email-sync.\n",
      54              :         BATCH_DEFAULT_LIMIT
      55              :     );
      56            1 : }
      57              : 
      58            1 : static void help_list(void) {
      59            1 :     printf(
      60              :         "Usage: email-cli list [options]\n"
      61              :         "\n"
      62              :         "Lists messages in the configured mailbox folder.\n"
      63              :         "\n"
      64              :         "Options:\n"
      65              :         "  --all              All messages are always shown; this flag has no\n"
      66              :         "                     effect and is kept for backwards compatibility.\n"
      67              :         "                     Unread messages are marked with 'N' and listed first.\n"
      68              :         "  --folder <name>    Use <name> instead of the configured folder.\n"
      69              :         "  --limit <n>        Show at most <n> messages per page.\n"
      70              :         "                     Defaults to terminal height when output is a\n"
      71              :         "                     terminal, or %d when piped / --batch is used.\n"
      72              :         "  --offset <n>       Start listing from the <n>-th message (1-based).\n"
      73              :         "  --batch            Disable terminal detection; use limit=%d.\n"
      74              :         "\n"
      75              :         "Examples:\n"
      76              :         "  email-cli list\n"
      77              :         "  email-cli list --all\n"
      78              :         "  email-cli list --all --offset 21\n"
      79              :         "  email-cli list --folder INBOX.Sent --limit 50\n"
      80              :         "  email-cli list --all --batch\n",
      81              :         BATCH_DEFAULT_LIMIT, BATCH_DEFAULT_LIMIT
      82              :     );
      83            1 : }
      84              : 
      85            1 : static void help_show(void) {
      86            1 :     printf(
      87              :         "Usage: email-cli show <uid>\n"
      88              :         "\n"
      89              :         "Displays the full content of the message identified by <uid>.\n"
      90              :         "\n"
      91              :         "  <uid>   Numeric IMAP UID shown by 'email-cli list'\n"
      92              :         "\n"
      93              :         "The message is fetched from the server on first access and stored\n"
      94              :         "locally at ~/.local/share/email-cli/messages/<folder>/<uid>.eml.\n"
      95              :         "Subsequent reads are served from the local store.\n"
      96              :         "\n"
      97              :         "Long messages are paginated automatically when output is a terminal.\n"
      98              :         "Use --batch or pipe to a file to disable the interactive pager.\n"
      99              :     );
     100            1 : }
     101              : 
     102            0 : static void help_folders(void) {
     103            0 :     printf(
     104              :         "Usage: email-cli folders [options]\n"
     105              :         "\n"
     106              :         "Lists all available IMAP folders on the server.\n"
     107              :         "\n"
     108              :         "Options:\n"
     109              :         "  --tree    Render the folder hierarchy as a tree.\n"
     110              :         "\n"
     111              :         "Examples:\n"
     112              :         "  email-cli folders\n"
     113              :         "  email-cli folders --tree\n"
     114              :     );
     115            0 : }
     116              : 
     117            0 : static void help_send(void) {
     118            0 :     printf(
     119              :         "Usage: email-cli send --to <addr> --subject <text> --body <text>\n"
     120              :         "\n"
     121              :         "Sends a message non-interactively (scriptable / batch mode).\n"
     122              :         "\n"
     123              :         "Options:\n"
     124              :         "  --to <addr>       Recipient email address (required)\n"
     125              :         "  --subject <text>  Subject line (required)\n"
     126              :         "  --body <text>     Message body text (required)\n"
     127              :         "\n"
     128              :         "SMTP settings must be configured (run 'email-cli config smtp').\n"
     129              :         "\n"
     130              :         "Examples:\n"
     131              :         "  email-cli send --to friend@example.com --subject \"Hello\" --body \"Hi there!\"\n"
     132              :     );
     133            0 : }
     134              : 
     135            0 : static void help_config(void) {
     136            0 :     printf(
     137              :         "Usage: email-cli [--account <email>] config <subcommand>\n"
     138              :         "\n"
     139              :         "View or update configuration settings.\n"
     140              :         "\n"
     141              :         "Global options:\n"
     142              :         "  --account <email>  Select a specific account by email address.\n"
     143              :         "                     Required when multiple accounts are configured.\n"
     144              :         "\n"
     145              :         "Subcommands:\n"
     146              :         "  show    Print current configuration (passwords masked)\n"
     147              :         "  imap    Interactively configure IMAP (incoming mail) settings\n"
     148              :         "  smtp    Interactively configure SMTP (outgoing mail) settings\n"
     149              :         "\n"
     150              :         "SMTP settings are used by email-tui for composing and sending mail.\n"
     151              :         "Configuring them here writes to the shared config file.\n"
     152              :         "\n"
     153              :         "Examples:\n"
     154              :         "  email-cli config show\n"
     155              :         "  email-cli config imap\n"
     156              :         "  email-cli config smtp\n"
     157              :         "  email-cli --account user@example.com config show\n"
     158              :     );
     159            0 : }
     160              : 
     161            0 : static void help_attachments(void) {
     162            0 :     printf(
     163              :         "Usage: email-cli attachments <uid>\n"
     164              :         "\n"
     165              :         "Lists all attachments in the message identified by <uid>.\n"
     166              :         "Prints one line per attachment: filename and decoded size.\n"
     167              :         "\n"
     168              :         "  <uid>   Numeric IMAP UID shown by 'email-cli list'\n"
     169              :         "\n"
     170              :         "Examples:\n"
     171              :         "  email-cli attachments 42\n"
     172              :     );
     173            0 : }
     174              : 
     175            0 : static void help_save_attachment(void) {
     176            0 :     printf(
     177              :         "Usage: email-cli save-attachment <uid> <filename> [dir]\n"
     178              :         "\n"
     179              :         "Saves the named attachment from message <uid> to disk.\n"
     180              :         "\n"
     181              :         "  <uid>       Numeric IMAP UID shown by 'email-cli list'\n"
     182              :         "  <filename>  Exact attachment filename shown by 'email-cli attachments'\n"
     183              :         "  [dir]       Destination directory (default: ~/Downloads or ~)\n"
     184              :         "\n"
     185              :         "Examples:\n"
     186              :         "  email-cli save-attachment 42 report.pdf\n"
     187              :         "  email-cli save-attachment 42 report.pdf /tmp\n"
     188              :     );
     189            0 : }
     190              : 
     191              : /* ── Helpers ─────────────────────────────────────────────────────────── */
     192              : 
     193            4 : static int parse_uid(const char *s, char uid_out[17]) {
     194            4 :     if (!s || !*s) return -1;
     195            4 :     char *end;
     196            4 :     unsigned long v = strtoul(s, &end, 10);
     197            4 :     if (*end != '\0' || v == 0 || v > 4294967295UL) return -1;
     198            4 :     snprintf(uid_out, 17, "%016lu", v);
     199            4 :     return 0;
     200              : }
     201              : 
     202            0 : static void unknown_option(const char *cmd, const char *opt) {
     203            0 :     fprintf(stderr, "Unknown option '%s' for '%s'.\n", opt, cmd);
     204            0 :     fprintf(stderr, "Run 'email-cli help %s' for usage.\n", cmd);
     205            0 : }
     206              : 
     207              : /* ── Entry point ─────────────────────────────────────────────────────── */
     208              : 
     209              : #ifndef EMAIL_CLI_VERSION
     210              : #define EMAIL_CLI_VERSION "unknown"
     211              : #endif
     212              : 
     213           55 : int main(int argc, char *argv[]) {
     214              :     /* 0. Set locale so wcwidth() and mbsrtowcs() work correctly for
     215              :      *    multi-byte UTF-8 characters (needed for column-width calculations). */
     216           55 :     setlocale(LC_ALL, "");
     217              : 
     218           55 :     if (argc >= 2 && (strcmp(argv[1], "--version") == 0 || strcmp(argv[1], "-V") == 0)) {
     219            0 :         printf("email-cli %s\n", EMAIL_CLI_VERSION);
     220            0 :         return EXIT_SUCCESS;
     221              :     }
     222              : 
     223              :     /* 1. Determine cache directory for logs */
     224           55 :     const char *cache_base = platform_cache_dir();
     225           55 :     if (!cache_base) {
     226            0 :         fprintf(stderr, "Fatal: Could not determine cache directory.\n");
     227            0 :         return EXIT_FAILURE;
     228              :     }
     229              : 
     230          110 :     RAII_STRING char *log_dir  = NULL;
     231          110 :     RAII_STRING char *log_file = NULL;
     232          110 :     if (asprintf(&log_dir,  "%s/email-cli/logs", cache_base) == -1 ||
     233           55 :         asprintf(&log_file, "%s/session.log", log_dir)        == -1) {
     234            0 :         fprintf(stderr, "Fatal: Memory allocation failed.\n");
     235            0 :         return EXIT_FAILURE;
     236              :     }
     237              : 
     238              :     /* 2. Global flags: scan all args for --batch and --account */
     239           55 :     int batch = 0;
     240           55 :     const char *account_arg = NULL;
     241          188 :     for (int i = 1; i < argc; i++) {
     242          133 :         if (strcmp(argv[i], "--batch") == 0) { batch = 1; continue; }
     243           87 :         if (strcmp(argv[i], "--account") == 0 && i + 1 < argc) {
     244            0 :             account_arg = argv[++i]; continue;
     245              :         }
     246              :     }
     247              : 
     248              :     /* Command: first non-global-flag arg */
     249           55 :     const char *cmd = NULL;
     250           55 :     int cmd_idx = 0;
     251          101 :     for (int i = 1; i < argc; i++) {
     252          100 :         if (strcmp(argv[i], "--batch") == 0) continue;
     253           54 :         if (strcmp(argv[i], "--help") == 0) continue;
     254           54 :         if (strcmp(argv[i], "--account") == 0) { i++; continue; }
     255           54 :         cmd = argv[i]; cmd_idx = i; break;
     256              :     }
     257              : 
     258              :     /* --help anywhere in the args: treat as "help <cmd>" */
     259          188 :     for (int i = 1; i < argc; i++) {
     260          133 :         if (strcmp(argv[i], "--account") == 0) { i++; continue; }
     261          133 :         if (strcmp(argv[i], "--help") == 0) {
     262            0 :             if (cmd && strcmp(cmd, "--help") != 0) {
     263              :                 /* e.g. email-cli list --help */
     264            0 :                 if (strcmp(cmd, "list")            == 0) { help_list();            return EXIT_SUCCESS; }
     265            0 :                 if (strcmp(cmd, "show")            == 0) { help_show();            return EXIT_SUCCESS; }
     266            0 :                 if (strcmp(cmd, "folders")         == 0) { help_folders();         return EXIT_SUCCESS; }
     267            0 :                 if (strcmp(cmd, "attachments")     == 0) { help_attachments();     return EXIT_SUCCESS; }
     268            0 :                 if (strcmp(cmd, "save-attachment") == 0) { help_save_attachment(); return EXIT_SUCCESS; }
     269            0 :                 if (strcmp(cmd, "send")            == 0) { help_send();            return EXIT_SUCCESS; }
     270            0 :                 if (strcmp(cmd, "config")          == 0) { help_config();          return EXIT_SUCCESS; }
     271              :             }
     272              :             /* email-cli --help  or  email-cli help --help */
     273            0 :             help_general();
     274            0 :             return EXIT_SUCCESS;
     275              :         }
     276              :     }
     277              : 
     278              :     /* Page size / pager capability (used by list and show) */
     279           55 :     int pager     = !batch && terminal_is_tty(STDOUT_FILENO);
     280           55 :     int page_size = detect_page_size(batch);
     281              : 
     282           55 :     if (cmd && strcmp(cmd, "help") == 0) {
     283              :         /* First arg after the command is the topic */
     284            2 :         const char *topic = NULL;
     285            2 :         for (int i = cmd_idx + 1; i < argc; i++) {
     286            2 :             if (strcmp(argv[i], "--batch") == 0) continue;
     287            2 :             topic = argv[i]; break;
     288              :         }
     289            2 :         if (topic) {
     290            2 :             if (strcmp(topic, "list")            == 0) { help_list();            return EXIT_SUCCESS; }
     291            1 :             if (strcmp(topic, "show")            == 0) { help_show();            return EXIT_SUCCESS; }
     292            0 :             if (strcmp(topic, "folders")         == 0) { help_folders();         return EXIT_SUCCESS; }
     293            0 :             if (strcmp(topic, "attachments")     == 0) { help_attachments();     return EXIT_SUCCESS; }
     294            0 :             if (strcmp(topic, "save-attachment") == 0) { help_save_attachment(); return EXIT_SUCCESS; }
     295            0 :             if (strcmp(topic, "send")            == 0) { help_send();            return EXIT_SUCCESS; }
     296            0 :             if (strcmp(topic, "config")          == 0) { help_config();          return EXIT_SUCCESS; }
     297            0 :             fprintf(stderr, "Unknown command '%s'.\n", topic);
     298            0 :             fprintf(stderr, "Run 'email-cli help' for available commands.\n");
     299            0 :             return EXIT_FAILURE;
     300              :         }
     301            0 :         help_general();
     302            0 :         return EXIT_SUCCESS;
     303              :     }
     304              : 
     305              :     /* No command in batch/non-tty mode: show help and exit. */
     306           53 :     if (!cmd && !pager) {
     307            1 :         help_general();
     308            1 :         return EXIT_SUCCESS;
     309              :     }
     310              : 
     311              :     /* 3. Initialize logger */
     312           52 :     if (fs_mkdir_p(log_dir, 0700) != 0)
     313            0 :         fprintf(stderr, "Warning: Could not create log directory %s\n", log_dir);
     314           52 :     if (logger_init(log_file, LOG_DEBUG) != 0)
     315            0 :         fprintf(stderr, "Warning: Logging system failed to initialize.\n");
     316           52 :     logger_log(LOG_INFO, "--- email-cli starting (cmd: %s) ---", cmd);
     317              : 
     318              :     /* 4. Load configuration */
     319           52 :     Config *cfg = config_load_from_store();
     320           52 :     if (!cfg) {
     321            0 :         logger_log(LOG_INFO, "No configuration found. Starting setup wizard.");
     322            0 :         cfg = setup_wizard_run();
     323            0 :         if (cfg) {
     324            0 :             if (config_save_to_store(cfg) != 0) {
     325            0 :                 logger_log(LOG_ERROR, "Failed to save configuration.");
     326            0 :                 fprintf(stderr, "Error: Failed to save configuration to disk.\n");
     327              :             } else {
     328            0 :                 printf("Configuration saved. Run 'email-cli sync' to download your mail.\n");
     329              :             }
     330              :         } else {
     331            0 :             logger_log(LOG_ERROR, "Configuration aborted by user.");
     332            0 :             fprintf(stderr, "Configuration aborted. Exiting.\n");
     333            0 :             logger_close();
     334            0 :             return EXIT_FAILURE;
     335              :         }
     336              :     }
     337              : 
     338              :     /* 5. Initialize local store */
     339           52 :     if (local_store_init(cfg->host, cfg->user) != 0)
     340            0 :         logger_log(LOG_WARN, "Failed to initialize local store for %s", cfg->host);
     341              : 
     342              :     /* 6. Dispatch */
     343           52 :     int result = -1;
     344              : 
     345           52 :     if (!cmd) {
     346              :         /* Interactive TUI: start with unread messages in the configured folder. */
     347            0 :         char *tui_folder = strdup(cfg->folder ? cfg->folder : "INBOX");
     348            0 :         if (!tui_folder) {
     349            0 :             result = -1;
     350              :         } else {
     351            0 :             for (;;) {
     352            0 :                 EmailListOpts opts = {0, tui_folder, page_size, 0, 1, {0}};
     353            0 :                 int ret = email_service_list(cfg, &opts);
     354            0 :                 if (ret != 1) { result = (ret >= 0) ? 0 : -1; break; }
     355              :                 /* User pressed Backspace → show folder browser */
     356            0 :                 char *sel = email_service_list_folders_interactive(cfg, tui_folder, NULL);
     357            0 :                 free(tui_folder);
     358            0 :                 tui_folder = sel;
     359            0 :                 if (!tui_folder) { result = 0; break; }
     360              :             }
     361            0 :             free(tui_folder);
     362              :         }
     363              : 
     364           52 :     } else if (strcmp(cmd, "list") == 0) {
     365           45 :         EmailListOpts opts = {0, NULL, 0, 0, pager, {0}};
     366           45 :         int ok = 1, explicit_limit = -1;
     367           55 :         for (int i = cmd_idx + 1; i < argc && ok; i++) {
     368           10 :             if (strcmp(argv[i], "--batch") == 0) {
     369            0 :                 continue; /* already handled globally */
     370           10 :             } else if (strcmp(argv[i], "--all") == 0) {
     371            0 :                 opts.all = 1;
     372           10 :             } else if (strcmp(argv[i], "--folder") == 0) {
     373           10 :                 if (i + 1 >= argc) {
     374            0 :                     fprintf(stderr, "Error: --folder requires a folder name.\n");
     375            0 :                     ok = 0;
     376              :                 } else {
     377           10 :                     opts.folder = argv[++i];
     378              :                 }
     379            0 :             } else if (strcmp(argv[i], "--limit") == 0) {
     380            0 :                 if (i + 1 >= argc) {
     381            0 :                     fprintf(stderr, "Error: --limit requires a number.\n");
     382            0 :                     ok = 0;
     383              :                 } else {
     384            0 :                     char *end;
     385            0 :                     long v = strtol(argv[++i], &end, 10);
     386            0 :                     if (*end != '\0' || v <= 0) {
     387            0 :                         fprintf(stderr, "Error: --limit must be a positive integer.\n");
     388            0 :                         ok = 0;
     389              :                     } else {
     390            0 :                         explicit_limit = (int)v;
     391              :                     }
     392              :                 }
     393            0 :             } else if (strcmp(argv[i], "--offset") == 0) {
     394            0 :                 if (i + 1 >= argc) {
     395            0 :                     fprintf(stderr, "Error: --offset requires a number.\n");
     396            0 :                     ok = 0;
     397              :                 } else {
     398            0 :                     char *end;
     399            0 :                     long v = strtol(argv[++i], &end, 10);
     400            0 :                     if (*end != '\0' || v < 1) {
     401            0 :                         fprintf(stderr, "Error: --offset must be a positive integer.\n");
     402            0 :                         ok = 0;
     403              :                     } else {
     404            0 :                         opts.offset = (int)v;
     405              :                     }
     406              :                 }
     407              :             } else {
     408            0 :                 unknown_option("list", argv[i]);
     409            0 :                 ok = 0;
     410              :             }
     411              :         }
     412           45 :         if (ok) {
     413           45 :             opts.limit = (explicit_limit >= 0) ? explicit_limit : page_size;
     414           45 :             result = email_service_list(cfg, &opts);
     415              :         }
     416              : 
     417            7 :     } else if (strcmp(cmd, "show") == 0) {
     418              :         /* UID is the first non --batch arg after "show" */
     419            4 :         const char *uid_str = NULL;
     420            4 :         for (int i = cmd_idx + 1; i < argc; i++) {
     421            4 :             if (strcmp(argv[i], "--batch") == 0) continue;
     422            4 :             uid_str = argv[i]; break;
     423              :         }
     424            4 :         if (!uid_str) {
     425            0 :             fprintf(stderr, "Error: 'show' requires a UID argument.\n");
     426            0 :             help_show();
     427              :         } else {
     428            4 :             char uid[17];
     429            4 :             if (parse_uid(uid_str, uid) != 0)
     430            0 :                 fprintf(stderr,
     431              :                         "Error: UID must be a positive integer (got '%s').\n",
     432              :                         uid_str);
     433              :             else
     434            4 :                 result = email_service_read(cfg, uid, pager, page_size);
     435              :         }
     436              : 
     437            3 :     } else if (strcmp(cmd, "folders") == 0) {
     438            2 :         int tree = 0, ok = 1;
     439            3 :         for (int i = cmd_idx + 1; i < argc && ok; i++) {
     440            1 :             if (strcmp(argv[i], "--batch") == 0) continue;
     441            1 :             if (strcmp(argv[i], "--tree") == 0)
     442            1 :                 tree = 1;
     443            0 :             else { unknown_option("folders", argv[i]); ok = 0; }
     444              :         }
     445            2 :         if (ok) result = email_service_list_folders(cfg, tree);
     446              : 
     447            1 :     } else if (strcmp(cmd, "attachments") == 0) {
     448            0 :         const char *uid_str = NULL;
     449            0 :         for (int i = cmd_idx + 1; i < argc; i++) {
     450            0 :             if (strcmp(argv[i], "--batch") == 0) continue;
     451            0 :             uid_str = argv[i]; break;
     452              :         }
     453            0 :         if (!uid_str) {
     454            0 :             fprintf(stderr, "Error: 'attachments' requires a UID argument.\n");
     455            0 :             help_attachments();
     456              :         } else {
     457            0 :             char uid[17];
     458            0 :             if (parse_uid(uid_str, uid) != 0)
     459            0 :                 fprintf(stderr,
     460              :                         "Error: UID must be a positive integer (got '%s').\n",
     461              :                         uid_str);
     462              :             else
     463            0 :                 result = email_service_list_attachments(cfg, uid);
     464              :         }
     465              : 
     466            1 :     } else if (strcmp(cmd, "save-attachment") == 0) {
     467            0 :         const char *uid_str  = NULL;
     468            0 :         const char *filename = NULL;
     469            0 :         const char *outdir   = NULL;
     470            0 :         int argn = 0;
     471            0 :         for (int i = cmd_idx + 1; i < argc; i++) {
     472            0 :             if (strcmp(argv[i], "--batch") == 0) continue;
     473            0 :             if (argn == 0)      { uid_str  = argv[i]; argn++; }
     474            0 :             else if (argn == 1) { filename = argv[i]; argn++; }
     475            0 :             else if (argn == 2) { outdir   = argv[i]; argn++; }
     476              :         }
     477            0 :         if (!uid_str || !filename) {
     478            0 :             fprintf(stderr,
     479              :                     "Error: 'save-attachment' requires a UID and a filename.\n");
     480            0 :             help_save_attachment();
     481              :         } else {
     482            0 :             char uid[17];
     483            0 :             if (parse_uid(uid_str, uid) != 0)
     484            0 :                 fprintf(stderr,
     485              :                         "Error: UID must be a positive integer (got '%s').\n",
     486              :                         uid_str);
     487              :             else
     488            0 :                 result = email_service_save_attachment(cfg, uid, filename, outdir);
     489              :         }
     490              : 
     491            1 :     } else if (strcmp(cmd, "config") == 0) {
     492              :         /* Determine which account to operate on.
     493              :          * --account <email> selects a specific named account.
     494              :          * If omitted and exactly one account exists, use it (already loaded as cfg).
     495              :          * If omitted and multiple accounts exist, list them and exit. */
     496            0 :         if (account_arg) {
     497              :             /* Load the requested account, replacing the default cfg */
     498            0 :             int acc_count = 0;
     499            0 :             AccountEntry *accounts = config_list_accounts(&acc_count);
     500            0 :             Config *acc_cfg = NULL;
     501            0 :             for (int i = 0; i < acc_count; i++) {
     502            0 :                 if (accounts[i].name && strcmp(accounts[i].name, account_arg) == 0) {
     503            0 :                     acc_cfg = accounts[i].cfg;
     504            0 :                     accounts[i].cfg = NULL; /* transfer ownership */
     505            0 :                     break;
     506              :                 }
     507              :                 /* also match by user field */
     508            0 :                 if (accounts[i].cfg && accounts[i].cfg->user &&
     509            0 :                     strcmp(accounts[i].cfg->user, account_arg) == 0) {
     510            0 :                     acc_cfg = accounts[i].cfg;
     511            0 :                     accounts[i].cfg = NULL;
     512            0 :                     break;
     513              :                 }
     514              :             }
     515            0 :             config_free_account_list(accounts, acc_count);
     516            0 :             if (!acc_cfg) {
     517            0 :                 fprintf(stderr,
     518              :                         "Error: Account '%s' not found.\n"
     519              :                         "Run 'email-cli config show' to list available accounts.\n",
     520              :                         account_arg);
     521            0 :                 config_free(cfg);
     522            0 :                 logger_close();
     523            0 :                 return EXIT_FAILURE;
     524              :             }
     525            0 :             config_free(cfg);
     526            0 :             cfg = acc_cfg;
     527              :         } else {
     528              :             /* No --account: check if multiple accounts exist */
     529            0 :             int acc_count = 0;
     530            0 :             AccountEntry *accounts = config_list_accounts(&acc_count);
     531            0 :             if (acc_count > 1) {
     532            0 :                 fprintf(stderr,
     533              :                         "Multiple accounts configured. "
     534              :                         "Re-run with --account <email>:\n");
     535            0 :                 for (int i = 0; i < acc_count; i++)
     536            0 :                     fprintf(stderr, "  %s\n", accounts[i].name ? accounts[i].name
     537              :                                                                : "(unknown)");
     538            0 :                 config_free_account_list(accounts, acc_count);
     539            0 :                 config_free(cfg);
     540            0 :                 logger_close();
     541            0 :                 return EXIT_FAILURE;
     542              :             }
     543            0 :             config_free_account_list(accounts, acc_count);
     544              :         }
     545              : 
     546            0 :         const char *subcmd = (argc > cmd_idx + 1) ? argv[cmd_idx + 1] : "";
     547              : 
     548            0 :         if (strcmp(subcmd, "show") == 0) {
     549            0 :             printf("\nemail-cli configuration");
     550            0 :             if (cfg->user) printf(" (%s)", cfg->user);
     551            0 :             printf(":\n\n");
     552            0 :             printf("  IMAP:\n");
     553            0 :             printf("    Host:     %s\n", cfg->host   ? cfg->host   : "(not set)");
     554            0 :             printf("    User:     %s\n", cfg->user   ? cfg->user   : "(not set)");
     555            0 :             printf("    Password: %s\n", cfg->pass   ? "****"      : "(not set)");
     556            0 :             printf("    Folder:   %s\n", cfg->folder ? cfg->folder : "INBOX");
     557            0 :             printf("\n  SMTP:\n");
     558            0 :             if (cfg->smtp_host) {
     559            0 :                 printf("    Host:     %s\n", cfg->smtp_host);
     560            0 :                 printf("    Port:     %d\n", cfg->smtp_port ? cfg->smtp_port : 587);
     561            0 :                 printf("    User:     %s\n", cfg->smtp_user ? cfg->smtp_user : "(same as IMAP)");
     562            0 :                 printf("    Password: %s\n", cfg->smtp_pass ? "****"         : "(same as IMAP)");
     563              :             } else {
     564            0 :                 printf("    (not configured — will be derived from IMAP host)\n");
     565              :             }
     566            0 :             printf("\n");
     567            0 :             result = 0;
     568              : 
     569            0 :         } else if (strcmp(subcmd, "imap") == 0) {
     570            0 :             if (setup_wizard_imap(cfg) == 0) {
     571            0 :                 if (config_save_to_store(cfg) == 0) {
     572            0 :                     printf("IMAP configuration saved.\n");
     573            0 :                     result = 0;
     574              :                 } else {
     575            0 :                     fprintf(stderr, "Error: Could not save configuration.\n");
     576              :                 }
     577              :             }
     578              : 
     579            0 :         } else if (strcmp(subcmd, "smtp") == 0) {
     580            0 :             if (setup_wizard_smtp(cfg) == 0) {
     581            0 :                 if (config_save_to_store(cfg) == 0) {
     582            0 :                     printf("SMTP configuration saved.\n");
     583            0 :                     result = 0;
     584              :                 } else {
     585            0 :                     fprintf(stderr, "Error: Could not save configuration.\n");
     586              :                 }
     587              :             }
     588              : 
     589              :         } else {
     590            0 :             if (subcmd[0])
     591            0 :                 fprintf(stderr, "Unknown config subcommand '%s'.\n", subcmd);
     592            0 :             help_config();
     593            0 :             result = subcmd[0] ? -1 : 0;
     594              :         }
     595              : 
     596            1 :     } else if (strcmp(cmd, "send") == 0) {
     597            1 :         const char *to = NULL, *subject = NULL, *body = NULL;
     598            1 :         int ok = 1;
     599            4 :         for (int i = cmd_idx + 1; i < argc && ok; i++) {
     600            3 :             if (strcmp(argv[i], "--batch") == 0) continue;
     601            3 :             if (strcmp(argv[i], "--to") == 0) {
     602            1 :                 if (i + 1 >= argc) {
     603            0 :                     fprintf(stderr, "Error: --to requires an address.\n"); ok = 0;
     604            1 :                 } else to = argv[++i];
     605            2 :             } else if (strcmp(argv[i], "--subject") == 0) {
     606            1 :                 if (i + 1 >= argc) {
     607            0 :                     fprintf(stderr, "Error: --subject requires text.\n"); ok = 0;
     608            1 :                 } else subject = argv[++i];
     609            1 :             } else if (strcmp(argv[i], "--body") == 0) {
     610            1 :                 if (i + 1 >= argc) {
     611            0 :                     fprintf(stderr, "Error: --body requires text.\n"); ok = 0;
     612            1 :                 } else body = argv[++i];
     613              :             } else {
     614            0 :                 unknown_option("send", argv[i]); ok = 0;
     615              :             }
     616              :         }
     617            1 :         if (ok) {
     618            1 :             if (!to || !to[0] || !subject || !body) {
     619            0 :                 fprintf(stderr, "Error: --to, --subject, and --body are all required.\n");
     620            0 :                 help_send();
     621              :             } else {
     622            1 :                 const char *from = cfg->smtp_user ? cfg->smtp_user : cfg->user;
     623            1 :                 ComposeParams p = {from, to, subject, body, NULL};
     624            1 :                 char *msg = NULL;
     625            1 :                 size_t msg_len = 0;
     626            1 :                 if (compose_build_message(&p, &msg, &msg_len) != 0) {
     627            0 :                     fprintf(stderr, "Error: Failed to build message.\n");
     628              :                 } else {
     629            1 :                     result = smtp_send(cfg, from, to, msg, msg_len);
     630            1 :                     if (result == 0) {
     631            1 :                         printf("Message sent.\n");
     632            1 :                         if (email_service_save_sent(cfg, msg, msg_len) == 0)
     633            1 :                             printf("Saved.\n");
     634              :                         else
     635            0 :                             fprintf(stderr, "(Could not save to Sent folder — "
     636              :                                     "check EMAIL_SENT_FOLDER in config.)\n");
     637              :                     }
     638            1 :                     free(msg);
     639              :                 }
     640              :             }
     641              :         }
     642              : 
     643              :     } else {
     644            0 :         fprintf(stderr, "Unknown command '%s'.\n", cmd);
     645            0 :         fprintf(stderr, "Run 'email-cli help' for available commands.\n");
     646              :     }
     647              : 
     648              :     /* 7. Cleanup */
     649           52 :     config_free(cfg);
     650           52 :     logger_log(LOG_INFO, "--- email-cli session finished ---");
     651           52 :     logger_close();
     652              : 
     653           52 :     if (result >= 0)
     654           52 :         return EXIT_SUCCESS;
     655            0 :     fprintf(stderr, "\nFailed. Check logs in %s\n", log_file);
     656            0 :     return EXIT_FAILURE;
     657              : }
        

Generated by: LCOV version 2.0-1