Line data Source code
1 : #include "setup_wizard.h"
2 : #include "platform/terminal.h"
3 : #include <stdio.h>
4 : #include <stdlib.h>
5 : #include <string.h>
6 : #include <unistd.h>
7 :
8 20 : static char* get_input(const char *prompt, int hide, FILE *stream) {
9 20 : int is_tty = isatty(fileno(stream));
10 :
11 20 : if (hide && stream == stdin) {
12 : /* Delegate password reading (with echo suppression) to the platform layer.
13 : * terminal_read_password() reads from stdin and handles TTY detection. */
14 1 : char buf[512];
15 1 : int n = terminal_read_password(prompt, buf, sizeof(buf));
16 1 : if (n < 0) return NULL;
17 1 : return strdup(buf);
18 : }
19 :
20 19 : if (stream == stdin && is_tty) {
21 0 : printf("%s: ", prompt);
22 0 : fflush(stdout);
23 : }
24 :
25 19 : char *line = NULL;
26 19 : size_t len = 0;
27 19 : if (getline(&line, &len, stream) == -1) {
28 5 : free(line);
29 5 : return NULL;
30 : }
31 :
32 : /* Remove newline */
33 14 : line[strcspn(line, "\r\n")] = 0;
34 14 : return line;
35 : }
36 :
37 : /**
38 : * @brief Internal wizard implementation that can take any input stream.
39 : */
40 5 : Config* setup_wizard_run_internal(FILE *stream) {
41 5 : int is_tty = isatty(fileno(stream));
42 5 : if (stream == stdin && is_tty) {
43 0 : printf("\n--- email-cli Configuration Wizard ---\n");
44 0 : printf("Please enter your email server details.\n\n");
45 : }
46 :
47 5 : Config *cfg = calloc(1, sizeof(Config));
48 5 : if (!cfg) return NULL;
49 :
50 5 : cfg->host = get_input("IMAP Host — include protocol (e.g., imaps://imap.example.com)", 0, stream);
51 5 : if (!cfg->host) { config_free(cfg); return NULL; }
52 :
53 5 : cfg->user = get_input("Email Username", 0, stream);
54 5 : if (!cfg->user) { config_free(cfg); return NULL; }
55 :
56 4 : cfg->pass = get_input("Email Password", 1, stream);
57 4 : if (!cfg->pass) { config_free(cfg); return NULL; }
58 :
59 3 : cfg->folder = get_input("Default Folder [INBOX]", 0, stream);
60 3 : if (!cfg->folder || strlen(cfg->folder) == 0) {
61 1 : if (cfg->folder) free(cfg->folder);
62 1 : cfg->folder = strdup("INBOX");
63 : }
64 :
65 : /* SMTP configuration (optional) */
66 3 : if (stream == stdin && is_tty)
67 0 : printf("\n--- SMTP (outgoing mail) — press Enter to skip ---\n");
68 :
69 3 : char *smtp_host = get_input("SMTP Host (e.g. smtp://smtp.example.com) [Enter=skip]", 0, stream);
70 3 : if (smtp_host && smtp_host[0] != '\0') {
71 0 : cfg->smtp_host = smtp_host;
72 :
73 0 : char *port_str = get_input("SMTP Port [587]", 0, stream);
74 0 : if (port_str && port_str[0] != '\0')
75 0 : cfg->smtp_port = atoi(port_str);
76 : else
77 0 : cfg->smtp_port = 587;
78 0 : free(port_str);
79 :
80 0 : char *su = get_input("SMTP Username [Enter=same as IMAP]", 0, stream);
81 0 : if (su && su[0] != '\0')
82 0 : cfg->smtp_user = su;
83 : else
84 0 : free(su);
85 :
86 0 : char *sp = get_input("SMTP Password [Enter=same as IMAP]", 1, stream);
87 0 : if (sp && sp[0] != '\0')
88 0 : cfg->smtp_pass = sp;
89 : else
90 0 : free(sp);
91 : } else {
92 3 : free(smtp_host);
93 : }
94 :
95 3 : if (stream == stdin && is_tty)
96 0 : printf("\nConfiguration collected. Checking connection...\n");
97 :
98 3 : return cfg;
99 : }
100 :
101 1 : Config* setup_wizard_run(void) {
102 1 : return setup_wizard_run_internal(stdin);
103 : }
104 :
105 : /* ── SMTP sub-wizard ─────────────────────────────────────────────────── */
106 :
107 : /**
108 : * Derive a plausible default SMTP URL from the IMAP URL in cfg->host.
109 : * Writes into buf (size bytes). buf[0] == 0 if derivation is not possible.
110 : */
111 0 : static void derive_smtp_url(const Config *cfg, char *buf, size_t size) {
112 0 : buf[0] = '\0';
113 0 : if (!cfg->host) return;
114 0 : if (strncmp(cfg->host, "imaps://", 8) == 0)
115 0 : snprintf(buf, size, "smtps://%s", cfg->host + 8);
116 : /* imap:// is no longer accepted; no derivation for it */
117 : }
118 :
119 0 : int setup_wizard_smtp(Config *cfg) {
120 0 : printf("\n--- SMTP (outgoing mail) configuration ---\n");
121 0 : printf("Press Enter on any field to keep the shown default.\n\n");
122 :
123 : /* ── SMTP Host ────────────────────────────────────────────────── */
124 0 : char derived[512];
125 0 : derive_smtp_url(cfg, derived, sizeof(derived));
126 :
127 0 : char host_prompt[1024];
128 0 : if (cfg->smtp_host)
129 0 : snprintf(host_prompt, sizeof(host_prompt),
130 : "SMTP Host [current: %s]", cfg->smtp_host);
131 0 : else if (derived[0])
132 0 : snprintf(host_prompt, sizeof(host_prompt),
133 : "SMTP Host [Enter = %s]", derived);
134 : else
135 0 : snprintf(host_prompt, sizeof(host_prompt),
136 : "SMTP Host (e.g. smtps://smtp.example.com)");
137 :
138 0 : char *host = get_input(host_prompt, 0, stdin);
139 0 : if (!host) return -1; /* EOF / Ctrl-D → abort */
140 0 : if (host[0]) {
141 0 : free(cfg->smtp_host);
142 0 : cfg->smtp_host = host;
143 : } else {
144 0 : free(host);
145 0 : if (!cfg->smtp_host && derived[0])
146 0 : cfg->smtp_host = strdup(derived);
147 : /* else keep whatever was there */
148 : }
149 :
150 : /* ── SMTP Port ────────────────────────────────────────────────── */
151 0 : int cur_port = cfg->smtp_port ? cfg->smtp_port : 587;
152 0 : char port_prompt[64];
153 0 : snprintf(port_prompt, sizeof(port_prompt), "SMTP Port [%d]", cur_port);
154 0 : char *port_str = get_input(port_prompt, 0, stdin);
155 0 : if (port_str && port_str[0])
156 0 : cfg->smtp_port = atoi(port_str);
157 : else
158 0 : cfg->smtp_port = cur_port;
159 0 : free(port_str);
160 :
161 : /* ── SMTP Username ────────────────────────────────────────────── */
162 0 : char user_prompt[768];
163 0 : if (cfg->smtp_user)
164 0 : snprintf(user_prompt, sizeof(user_prompt),
165 : "SMTP Username [current: %s]", cfg->smtp_user);
166 : else
167 0 : snprintf(user_prompt, sizeof(user_prompt),
168 : "SMTP Username [Enter = same as IMAP (%s)]",
169 0 : cfg->user ? cfg->user : "");
170 0 : char *su = get_input(user_prompt, 0, stdin);
171 0 : if (su && su[0]) {
172 0 : free(cfg->smtp_user);
173 0 : cfg->smtp_user = su;
174 : } else {
175 0 : free(su);
176 : /* NULL means "use IMAP username" — keep as-is */
177 : }
178 :
179 : /* ── SMTP Password ────────────────────────────────────────────── */
180 0 : char pass_prompt[64];
181 0 : snprintf(pass_prompt, sizeof(pass_prompt), "%s",
182 0 : cfg->smtp_pass ? "SMTP Password [Enter = keep current]"
183 : : "SMTP Password [Enter = same as IMAP]");
184 0 : char *sp = get_input(pass_prompt, 1, stdin);
185 0 : if (sp && sp[0]) {
186 0 : free(cfg->smtp_pass);
187 0 : cfg->smtp_pass = sp;
188 : } else {
189 0 : free(sp);
190 : /* NULL means "use IMAP password" — keep as-is */
191 : }
192 :
193 0 : printf("\nSMTP configuration updated.\n");
194 0 : return 0;
195 : }
196 :
197 : /* ── IMAP sub-wizard ─────────────────────────────────────────────────── */
198 :
199 0 : int setup_wizard_imap(Config *cfg) {
200 0 : printf("\n--- IMAP (incoming mail) configuration ---\n");
201 0 : printf("Press Enter on any field to keep the shown default.\n\n");
202 :
203 : /* ── IMAP Host ────────────────────────────────────────────────── */
204 0 : for (;;) {
205 0 : char host_prompt[1024];
206 0 : if (cfg->host)
207 0 : snprintf(host_prompt, sizeof(host_prompt),
208 : "IMAP Host [current: %s]", cfg->host);
209 : else
210 0 : snprintf(host_prompt, sizeof(host_prompt),
211 : "IMAP Host (e.g. imaps://imap.example.com)");
212 :
213 0 : char *host = get_input(host_prompt, 0, stdin);
214 0 : if (!host) return -1; /* EOF / Ctrl-D → abort */
215 0 : if (host[0] == '\0') {
216 0 : free(host);
217 0 : break; /* keep current */
218 : }
219 0 : if (strncmp(host, "imaps://", 8) != 0) {
220 0 : fprintf(stderr,
221 : "Error: IMAP host must start with 'imaps://' (got '%s').\n",
222 : host);
223 0 : free(host);
224 0 : continue; /* re-prompt */
225 : }
226 0 : free(cfg->host);
227 0 : cfg->host = host;
228 0 : break;
229 : }
230 :
231 : /* ── IMAP Username ────────────────────────────────────────────── */
232 0 : char user_prompt[768];
233 0 : if (cfg->user)
234 0 : snprintf(user_prompt, sizeof(user_prompt),
235 : "IMAP Username [current: %s]", cfg->user);
236 : else
237 0 : snprintf(user_prompt, sizeof(user_prompt), "IMAP Username");
238 :
239 0 : char *user = get_input(user_prompt, 0, stdin);
240 0 : if (user && user[0]) {
241 0 : free(cfg->user);
242 0 : cfg->user = user;
243 : } else {
244 0 : free(user);
245 : }
246 :
247 : /* ── IMAP Password ────────────────────────────────────────────── */
248 0 : const char *pass_prompt = cfg->pass
249 : ? "IMAP Password [Enter=keep current]"
250 0 : : "IMAP Password";
251 0 : char *pass = get_input(pass_prompt, 1, stdin);
252 0 : if (pass && pass[0]) {
253 0 : free(cfg->pass);
254 0 : cfg->pass = pass;
255 : } else {
256 0 : free(pass);
257 : }
258 :
259 : /* ── Default Folder ───────────────────────────────────────────── */
260 0 : char folder_prompt[768];
261 0 : const char *cur_folder = cfg->folder ? cfg->folder : "INBOX";
262 0 : snprintf(folder_prompt, sizeof(folder_prompt),
263 : "Default Folder [current: %s]", cur_folder);
264 :
265 0 : char *folder = get_input(folder_prompt, 0, stdin);
266 0 : if (folder && folder[0]) {
267 0 : free(cfg->folder);
268 0 : cfg->folder = folder;
269 : } else {
270 0 : free(folder);
271 0 : if (!cfg->folder)
272 0 : cfg->folder = strdup("INBOX");
273 : }
274 :
275 0 : printf("\nIMAP configuration updated.\n");
276 0 : return 0;
277 : }
|