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 : }
|