* Set up the IMAP ID at the moment of loging in to the server, rather than
[alpine.git] / alpine / imap.c
blobb4e044e7375713de64722f8c42a4889ec9837c47
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: imap.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2020 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
20 imap.c
21 The call back routines for the c-client/imap
22 - handles error messages and other notification
23 - handles prelimirary notification of new mail and expunged mail
24 - prompting for imap server login and password
26 ====*/
28 #include "headers.h"
29 #include "alpine.h"
30 #include "imap.h"
31 #include "status.h"
32 #include "mailview.h"
33 #include "mailcmd.h"
34 #include "radio.h"
35 #include "keymenu.h"
36 #include "signal.h"
37 #include "mailpart.h"
38 #include "mailindx.h"
39 #include "arg.h"
40 #include "busy.h"
41 #include "titlebar.h"
42 #include "xoauth2.h"
43 #include "xoauth2conf.h"
44 #include "confscroll.h"
45 #include "init.h"
46 #include "../pith/state.h"
47 #include "../pith/conf.h"
48 #include "../pith/msgno.h"
49 #include "../pith/filter.h"
50 #include "../pith/news.h"
51 #include "../pith/util.h"
52 #include "../pith/list.h"
53 #include "../pith/margin.h"
54 #ifdef SMIME
55 #include "../pith/smime.h"
56 #endif /* SMIME */
58 #if (WINCRED > 0)
59 #include <wincred.h>
60 #define TNAME "UWash_Alpine_"
61 #define TNAMESTAR "UWash_Alpine_*"
64 * WinCred Function prototypes
66 typedef BOOL (WINAPI CREDWRITEW) ( __in PCREDENTIALW Credential, __in DWORD Flags );
67 typedef BOOL (WINAPI CREDENUMERATEW) ( __in LPCWSTR Filter, __reserved DWORD Flags,
68 __out DWORD *Count, __deref_out_ecount(*Count) PCREDENTIALW **Credential );
69 typedef BOOL (WINAPI CREDDELETEW) ( __in LPCWSTR TargetName, __in DWORD Type,
70 __reserved DWORD Flags );
71 typedef VOID (WINAPI CREDFREE) ( __in PVOID Buffer );
74 * WinCred functions
76 int g_CredInited = 0; /* 1 for loaded successfully,
77 * -1 for not available.
78 * 0 for not initialized yet.
80 CREDWRITEW *g_CredWriteW;
81 CREDENUMERATEW *g_CredEnumerateW;
82 CREDDELETEW *g_CredDeleteW;
83 CREDFREE *g_CredFree;
85 #endif /* WINCRED */
87 #ifdef APPLEKEYCHAIN
88 #include <Security/SecKeychain.h>
89 #include <Security/SecKeychainItem.h>
90 #include <Security/SecKeychainSearch.h>
91 #define TNAME "UWash_Alpine"
92 #define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
94 int macos_store_pass_prompt(void);
95 void macos_set_store_pass_prompt(int);
97 static int storepassprompt = -1;
98 #endif /* APPLEKEYCHAIN */
102 * Internal prototypes
104 void mm_login_alt_cue(NETMBX *);
105 long pine_tcptimeout_noscreen(long, long, char *);
106 int answer_cert_failure(int, MSGNO_S *, SCROLL_S *);
107 int oauth2_auth_answer(int, MSGNO_S *, SCROLL_S *);
108 OAUTH2_S *oauth2_select_flow(char *);
109 int xoauth2_flow_tool(struct pine *, int, CONF_S **, unsigned int);
111 #ifdef LOCAL_PASSWD_CACHE
112 int read_passfile(char *, MMLOGIN_S **);
113 void write_passfile(char *, MMLOGIN_S *);
114 int preserve_prompt(char *);
115 int preserve_prompt_auth(char *, char *authtype);
116 void update_passfile_hostlist(char *, char *, STRLIST_S *, int);
117 void update_passfile_hostlist_auth(char *, char *, STRLIST_S *, int, char *);
118 void free_passfile_cache_work(MMLOGIN_S **);
120 static MMLOGIN_S *passfile_cache = NULL;
121 static int using_passfile = -1;
122 int save_password = 1;
123 #endif /* LOCAL_PASSWD_CACHE */
125 #ifdef PASSFILE
126 char xlate_in(int);
127 char xlate_out(char);
128 int line_get(char *, size_t, char **);
129 #endif /* PASSFILE */
131 #if (WINCRED > 0)
132 void ask_erase_credentials(void);
133 int init_wincred_funcs(void);
134 #endif /* WINCRED */
137 static char *details_cert, *details_host, *details_reason;
139 extern XOAUTH2_INFO_S xoauth_default[];
142 * This is the private information of the client, which is passed to
143 * c-client for processing. Every c-client application must have its
144 * own.
146 OAUTH2_S alpine_oauth2_list[] =
148 {GMAIL_NAME,
149 {"imap.gmail.com", "smtp.gmail.com", NULL, NULL},
150 {{"client_id", NULL},
151 {"client_secret", NULL},
152 {"tenant", NULL}, /* not used */
153 {"code", NULL}, /* access code from the authorization process */
154 {"refresh_token", NULL},
155 {"scope", "https://mail.google.com/"},
156 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
157 {"grant_type", "authorization_code"},
158 {"grant_type", "refresh_token"},
159 {"response_type", "code"},
160 {"state", NULL},
161 {"device_code", NULL} /* not used */
163 {{"GET", "https://accounts.google.com/o/oauth2/auth", /* authorization address, get access code */
164 {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End}},
165 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* Device Info information, not used */
166 {"POST", "https://accounts.google.com/o/oauth2/token", /* Address to get refresh token from access code */
167 {OA2_Id, OA2_Secret, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Code, OA2_End, OA2_End}},
168 {"POST", "https://accounts.google.com/o/oauth2/token", /* access token from refresh token */
169 {OA2_Id, OA2_Secret, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}
171 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information */
172 NULL, /* access token */
173 NULL, /* special IMAP ID */
174 0, /* do not hide */
175 0, /* expiration time */
176 0, /* first time indicator */
177 1, /* client secret required */
178 0 /* Cancel refresh token */
180 {OUTLOOK_NAME,
181 {"outlook.office365.com", "smtp.office365.com", NULL, NULL},
182 {{"client_id", NULL},
183 {"client_secret", NULL}, /* not used, but needed */
184 {"tenant", NULL}, /* used */
185 {"code", NULL}, /* not used, not needed */
186 {"refresh_token", NULL},
187 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
188 {"grant_type", "urn:ietf:params:oauth:grant-type:device_code"},
189 {"scope", NULL}, /* not used */
190 {"grant_type", "refresh_token"},
191 {"response_type", "code"}, /* not used */
192 {"state", NULL}, /* not used */
193 {"device_code", NULL} /* only used for frst time set up */
195 {{NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* Get Access Code, Not used */
196 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/devicecode", /* first time use and get device code information */
197 {OA2_Id, OA2_Scope, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}},
198 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
199 {OA2_Id, OA2_Redirect, OA2_DeviceCode, OA2_End, OA2_End, OA2_End, OA2_End}},
200 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
201 {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}
203 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information */
204 NULL, /* access token */
205 NULL, /* special IMAP ID */
206 0, /* do not hide */
207 0, /* expiration time */
208 0, /* first time indicator */
209 0, /* client secret required */
210 0 /* Cancel refresh token */
212 {OUTLOOK_NAME,
213 {"outlook.office365.com", "smtp.office365.com", NULL, NULL},
214 {{"client_id", NULL},
215 {"client_secret", NULL}, /* not used, but needed */
216 {"tenant", NULL}, /* used */
217 {"code", NULL}, /* used during authorization */
218 {"refresh_token", NULL},
219 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
220 {"redirect_uri", "http://localhost"},
221 {"grant_type", "authorization_code"},
222 {"grant_type", "refresh_token"},
223 {"response_type", "code"},
224 {"state", NULL}, /* not used */
225 {"device_code", NULL} /* not used */
227 {{"GET", "https://login.microsoftonline.com/\001/oauth2/v2.0/authorize", /* Get Access Code */
228 {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End}},
229 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
230 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
231 {OA2_Id, OA2_Redirect, OA2_Scope, OA2_GrantTypeforAccessToken, OA2_Secret, OA2_Code, OA2_End}},
232 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
233 {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_Secret, OA2_End, OA2_End}}
235 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
236 NULL, /* access token */
237 NULL, /* special IMAP ID */
238 0, /* do not hide */
239 0, /* expiration time */
240 0, /* first time indicator */
241 1, /* client secret required */
242 0 /* Cancel refresh token */
244 {YANDEX_NAME,
245 {"imap.yandex.com", "smtp.yandex.com", NULL, NULL},
246 {{"client_id", NULL},
247 {"client_secret", NULL}, /* not used, but needed */
248 {"tenant", NULL}, /* not used */
249 {"code", NULL}, /* used during authorization */
250 {"refresh_token", NULL},
251 {"scope", NULL}, /* not needed, so not used */
252 {"redirect_uri", "https://oauth.yandex.ru/verification_code"},
253 {"grant_type", "authorization_code"},
254 {"grant_type", "refresh_token"},
255 {"response_type", "code"},
256 {"state", NULL}, /* not used */
257 {"device_code", NULL} /* not used */
259 {{"GET", "https://oauth.yandex.com/authorize", /* Get Access Code */
260 {OA2_Id, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End, OA2_End}},
261 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
262 {"POST", "https://oauth.yandex.com/token", /* Get first Refresh Token and Access token */
263 {OA2_Id, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Secret, OA2_Code, OA2_End, OA2_End}},
264 {"POST", "https://oauth.yandex.com/token", /* Get access token from refresh token */
265 {OA2_Id, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_Secret, OA2_End, OA2_End, OA2_End}}
267 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
268 NULL, /* access token */
269 NULL, /* special IMAP ID */
270 0, /* do not hide */
271 0, /* expiration time */
272 0, /* first time indicator */
273 1, /* client secret required */
274 0 /* Cancel refresh token */
276 { NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0},
280 xoauth2_flow_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
282 int rv = 0;
284 switch(cmd){
285 case MC_CHOICE:
286 *((*cl)->d.xf.selected) = (*cl)->d.xf.pat;
287 rv = simple_exit_cmd(flags);
289 case MC_EXIT:
290 rv = simple_exit_cmd(flags);
291 break;
293 default:
294 rv = -1;
297 if(rv > 0)
298 ps->mangled_body = 1;
300 return rv;
303 OAUTH2_S *
304 oauth2_select_flow(char *host)
306 OAUTH2_S *oa2list, *oa2;
307 int i, rv;
308 char *method;
310 if(ps_global->ttyo){
311 CONF_S *ctmp = NULL, *first_line = NULL;
312 OAUTH2_S *x_sel = NULL;
313 OPT_SCREEN_S screen;
314 char tmp[1024];
316 dprint((9, "xoauth2 select flow"));
317 ps_global->next_screen = SCREEN_FUN_NULL;
319 memset(&screen, 0, sizeof(screen));
321 for(i = 0; i < sizeof(tmp) && i < ps_global->ttyo->screen_cols; i++)
322 tmp[i] = '-';
323 tmp[i] = '\0';
325 new_confline(&ctmp);
326 ctmp->flags |= CF_NOSELECT;
327 ctmp->value = cpystr(tmp);
329 new_confline(&ctmp);
330 ctmp->flags |= CF_NOSELECT;
331 ctmp->value = cpystr(_("Please select below the authorization flow you would like to follow:"));
333 new_confline(&ctmp);
334 ctmp->flags |= CF_NOSELECT;
335 ctmp->value = cpystr(tmp);
337 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
338 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
339 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
340 new_confline(&ctmp);
341 if(!first_line)
342 first_line = ctmp;
343 method = oa2list->server_mthd[0].name ? "Authorize"
344 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
345 sprintf(tmp, "%s (%s)", oa2list->name, method);
346 ctmp->value = cpystr(tmp);
347 ctmp->d.xf.selected = &x_sel;
348 ctmp->d.xf.pat = oa2list;
349 ctmp->keymenu = &xoauth2_id_select_km;
350 ctmp->help = NO_HELP;
351 ctmp->help_title = NULL;
352 ctmp->tool = xoauth2_flow_tool;
353 ctmp->flags = CF_STARTITEM;
354 ctmp->valoffset = 4;
357 (void)conf_scroll_screen(ps_global, &screen, first_line, _("SELECT AUTHORIZATION FLOW"),
358 _("xoauth2"), 0, NULL);
359 oa2 = x_sel;
361 else{
362 char *s;
363 char prompt[1024];
364 char reply[1024];
365 int sel, n = 0, j;
367 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++)
368 n += strlen(oa2list->name); + 5; /* number, parenthesis, space */
369 n += 1024; /* large enough to display to lines of 80 characters in UTF-8 */
370 s = fs_get(n*sizeof(char));
371 strcpy(s, _("Please select below the authorization flow you would like to follow:"));
372 sprintf(s + strlen(s), _("Please select the client-id to use from the following list.\n\n"));
373 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
374 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
375 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i])
376 sprintf(s + strlen(s), " %d) %.70s\n", j++, oa2list->name);
378 display_init_err(s, 0);
380 strncpy(prompt, _("Enter your selection number: "), sizeof(prompt));
381 prompt[sizeof(prompt)-1] = '\0';
383 rv = optionally_enter(reply, 0, 0, sizeof(reply), prompt, NULL, NO_HELP, 0);
384 sel = atoi(reply);
385 rv = (sel >= 0 && sel < i) ? 0 : -1;
386 } while (rv != 0);
388 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
389 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
390 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
391 if(j == sel) break;
392 else j++;
395 oa2 = oa2list;
397 return oa2;
400 typedef struct auth_code_s {
401 char *code;
402 int answer;
403 } AUTH_CODE_S;
406 oauth2device_decode_reply(void *datap, void *replyp)
408 OAUTH2_DEVICEPROC_S *av = (OAUTH2_DEVICEPROC_S *)datap;
409 int reply = *(int *) replyp;
411 return reply < 0 ? av->code_failure : (reply == 0 ? av->code_success : av->code_wait);
415 oauth2_elapsed_done(void *aux_valuep)
417 OAUTH2_S *oauth2 = aux_valuep ? ((OAUTH2_DEVICEPROC_S *) aux_valuep)->xoauth2 : NULL;
418 static time_t savedt = 0, now;
419 int rv = 0;
421 if(aux_valuep == NULL) savedt = 0; /* reset last time we approved */
422 else{
423 now = time(0);
424 if(oauth2->devicecode.interval + now >= savedt)
425 savedt = now;
426 else
427 rv = -1;
429 return rv;
432 void
433 oauth2_set_device_info(OAUTH2_S *oa2, char *method)
435 char tmp[MAILTMPLEN];
436 char *code;
437 char *name = oa2->name;
438 int aux_rv_value;
439 OAUTH2_DEVICECODE_S *deviceinfo = &oa2->devicecode;
440 OAUTH2_DEVICEPROC_S aux_value;
442 if(ps_global->ttyo){
443 SCROLL_S sargs;
444 STORE_S *in_store, *out_store;
445 gf_io_t pc, gc;
446 HANDLE_S *handles = NULL;
447 AUTH_CODE_S user_input;
449 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
450 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
451 goto try_wantto;
453 aux_value.xoauth2 = oa2;
454 aux_value.code_success = 'e';
455 aux_value.code_failure = 'e';
456 aux_value.code_wait = NO_OP_COMMAND;
458 so_puts(in_store, "<HTML><P>");
459 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name);
460 so_puts(in_store, tmp);
461 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name, method),
462 so_puts(in_store, tmp);
464 if(deviceinfo->verification_uri && deviceinfo->user_code){
465 sprintf(tmp,
466 _("</P><P>To sign in, use a web browser to open the page <A HREF=\"%s\">%s</A> and enter the code \"%s\" without the quotes."),
467 deviceinfo->verification_uri, deviceinfo->verification_uri, deviceinfo->user_code);
468 so_puts(in_store, tmp);
470 else{
471 so_puts(in_store, "</P><P>");
472 so_puts(in_store, deviceinfo->message);
474 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
475 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), name);
476 so_puts(in_store, tmp);
477 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
479 so_puts(in_store, _("</P><P> After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
480 so_puts(in_store, _("to grant access to Alpine to your data. "));
481 so_puts(in_store, _("</P><P> Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. If you do "));
482 so_puts(in_store, _("not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection to the server will still be "));
483 so_puts(in_store, _("alive at the end of this process, and your connection will proceed from there."));
484 so_puts(in_store, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit"));
485 so_puts(in_store, _("</P></HTML>"));
487 so_seek(in_store, 0L, 0);
488 init_handles(&handles);
489 gf_filter_init();
490 gf_link_filter(gf_html2plain,
491 gf_html2plain_opt(NULL,
492 ps_global->ttyo->screen_cols, non_messageview_margin(),
493 &handles, NULL, GFHP_LOCAL_HANDLES));
494 gf_set_so_readc(&gc, in_store);
495 gf_set_so_writec(&pc, out_store);
496 gf_pipe(gc, pc);
497 gf_clear_so_writec(out_store);
498 gf_clear_so_readc(in_store);
500 memset(&sargs, 0, sizeof(SCROLL_S));
501 sargs.text.handles = handles;
502 sargs.text.text = so_text(out_store);
503 sargs.text.src = CharStar;
504 sargs.text.desc = _("help text");
505 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
506 sargs.proc.tool = oauth2_auth_answer;
507 sargs.proc.data.p = (void *)&user_input;
508 sargs.keys.menu = &oauth2_device_auth_keymenu;
509 /* don't want to re-enter c-client */
510 sargs.quell_newmail = 1;
511 setbitmap(sargs.keys.bitmap);
512 sargs.help.text = h_oauth2_start;
513 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
514 sargs.aux_function = oauth2deviceinfo_get_accesscode;
515 sargs.aux_value = (void *) &aux_value;
516 sargs.aux_condition = oauth2_elapsed_done;
517 sargs.decode_aux_rv_value = oauth2device_decode_reply;
518 sargs.aux_rv_value = (void *) &aux_rv_value;
520 do {
521 scrolltool(&sargs);
522 ps_global->mangled_screen = 1;
523 ps_global->painted_body_on_startup = 0;
524 ps_global->painted_footer_on_startup = 0;
525 } while (user_input.answer != 'e');
527 so_give(&in_store);
528 so_give(&out_store);
529 free_handles(&handles);
530 oauth2_elapsed_done(NULL);
532 else{
533 int flags, rc, q_line;
534 /* TRANSLATORS: user needs to input an access code from the server */
535 char prompt[MAILTMPLEN], token[MAILTMPLEN];
537 * If screen hasn't been initialized yet, use want_to.
539 try_wantto:
541 tmp_20k_buf[0] = '\0';
542 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
543 _("Authorizing Alpine Access to %s Email Services\n\n"), name);
544 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
546 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
547 _("Alpine is attempting to log you into your %s account, using the %s method. "), name, method),
548 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
550 if(deviceinfo->verification_uri && deviceinfo->user_code){
551 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
552 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
553 deviceinfo->verification_uri, deviceinfo->user_code);
554 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
556 else{
557 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
558 "%s\n\n", deviceinfo->message);
559 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
562 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
563 _("Copy and paste the previous URL into a web browser that supports javascript, to take you to %s's servers to complete this process.\n\n"), name);
564 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
566 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
567 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
568 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
570 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
571 "%s", _("to grant access to Alpine to your data.\n\n"));
572 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
574 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
575 "%s", _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
576 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
578 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
579 "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
580 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
582 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
583 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
584 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
586 display_init_err(tmp_20k_buf, 0);
587 memset((void *)tmp, 0, sizeof(tmp));
588 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
589 tmp[sizeof(tmp)-1] = '\0';
591 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
592 int rv;
593 UCS ch;
594 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
595 flags = OE_APPEND_CURRENT;
597 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
598 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
599 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
601 aux_value.xoauth2 = oa2;
602 aux_value.code_success = 'y';
603 aux_value.code_failure = 'n';
604 aux_value.code_wait = 'w';
606 strncpy(tmp, _("Continue waiting"), sizeof(tmp));
607 tmp[sizeof(tmp)-1] = '\0';
608 do {
609 if(oauth2_elapsed_done((void *) &aux_value) == 0)
610 oauth2deviceinfo_get_accesscode((void *) &aux_value, (void *) &rv);
611 ch = oauth2device_decode_reply((void *) &aux_value, (void *) &rv);
612 } while (ch == 'w' || want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y');
613 oauth2_elapsed_done(NULL);
618 char *
619 oauth2_get_access_code(unsigned char *url, char *method, OAUTH2_S *oauth2, int *tryanother)
621 char tmp[MAILTMPLEN];
622 char *code;
624 if(ps_global->ttyo){
625 SCROLL_S sargs;
626 STORE_S *in_store, *out_store;
627 gf_io_t pc, gc;
628 HANDLE_S *handles = NULL;
629 AUTH_CODE_S user_input;
631 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
632 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
633 goto try_wantto;
635 so_puts(in_store, "<HTML><BODY><P>");
636 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2->name);
637 so_puts(in_store, tmp);
638 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
639 so_puts(in_store, tmp);
641 if(strucmp(oauth2->name, GMAIL_NAME) == 0){
642 so_puts(in_store, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
643 so_puts(in_store, _("</P><P> First you must register Alpine with Google and create a client-id and client-secret. If you already did that, then you can skip to the authorization step, and continue with the process outlined below."));
644 so_puts(in_store, _("<UL> "));
645 so_puts(in_store, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
646 so_puts(in_store, _("and create a project. The name of the project is not important."));
647 so_puts(in_store, _("<LI> Go to the Consent Screen and make your app INTERNAL, if your account is a G-Suite account, or EXTERNAL if it is a personal gmail.com account."));
648 so_puts(in_store, _("<LI> Create OAUTH Credentials."));
649 so_puts(in_store, _("</UL> "));
650 so_puts(in_store, _("<P> As a result of this process, you will get a client-id and a client-secret."));
651 so_puts(in_store, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
652 so_puts(in_store, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
653 so_puts(in_store, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
654 so_puts(in_store, _("</P><P> If you completed these steps successfully, you are ready to move to the second part, where you will authorize Gmail to give access to Alpine to access your email."));
657 so_puts(in_store, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
658 so_puts(in_store,"</P><P>");
659 sprintf(tmp_20k_buf, _("<A HREF=\"%s\">%s</A>"), url, url);
660 so_puts(in_store, tmp_20k_buf);
662 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
663 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2->name);
664 so_puts(in_store, tmp);
665 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
667 so_puts(in_store, _("</P><P> After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine. "));
668 so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page."));
669 so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
670 so_puts(in_store, _(" If you do not see a code, copy the url of the page you were redirected to and again press 'C' and copy and paste it into the prompt. "));
671 so_puts(in_store, _(" Once you have completed this process, Alpine will proceed with authentication."));
672 so_puts(in_store, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
673 so_puts(in_store, _("</P></BODY></HTML>"));
675 so_seek(in_store, 0L, 0);
676 init_handles(&handles);
677 gf_filter_init();
678 gf_link_filter(gf_html2plain,
679 gf_html2plain_opt(NULL,
680 ps_global->ttyo->screen_cols, non_messageview_margin(),
681 &handles, NULL, GFHP_LOCAL_HANDLES));
682 gf_set_so_readc(&gc, in_store);
683 gf_set_so_writec(&pc, out_store);
684 gf_pipe(gc, pc);
685 gf_clear_so_writec(out_store);
686 gf_clear_so_readc(in_store);
688 memset(&sargs, 0, sizeof(SCROLL_S));
689 sargs.text.handles = handles;
690 sargs.text.text = so_text(out_store);
691 sargs.text.src = CharStar;
692 sargs.text.desc = _("help text");
693 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
694 sargs.proc.tool = oauth2_auth_answer;
695 sargs.proc.data.p = (void *)&user_input;
696 sargs.keys.menu = &oauth2_auth_keymenu;
697 /* don't want to re-enter c-client */
698 sargs.quell_newmail = 1;
699 setbitmap(sargs.keys.bitmap);
700 sargs.help.text = h_oauth2_start;
701 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
703 do {
704 scrolltool(&sargs);
705 ps_global->mangled_screen = 1;
706 ps_global->painted_body_on_startup = 0;
707 ps_global->painted_footer_on_startup = 0;
708 } while (user_input.answer != 'e');
710 if(!struncmp(user_input.code, "http://", 7)
711 || !struncmp(user_input.code, "https://", 8)){
712 char *s, *t;
713 s = strstr(user_input.code, "code=");
714 if(s != NULL){
715 t = strchr(s, '&');
716 if(t) *t = '\0';
717 code = cpystr(s+5);
718 if(t) *t = '&';
721 else code = user_input.code ? cpystr(user_input.code) : NULL;
722 if(user_input.code) fs_give((void **) &user_input.code);
724 if(code == NULL) *tryanother = 1;
726 so_give(&in_store);
727 so_give(&out_store);
728 free_handles(&handles);
730 else{
731 int flags, rc, q_line;
732 /* TRANSLATORS: user needs to input an access code from the server */
733 char *accesscodelabel = _("Copy and Paste Access Code");
734 char prompt[MAILTMPLEN], token[MAILTMPLEN];
736 * If screen hasn't been initialized yet, use want_to.
738 try_wantto:
739 tmp_20k_buf[0] = '\0';
740 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
741 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2->name);
742 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
744 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
745 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
746 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
748 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
749 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
750 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
752 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
753 "%s\n\n", url);
754 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
756 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
757 _("Copy and paste the previous URL into a web browser that supports javascript, to take you to %s's servers to complete this process.\n\n"), oauth2->name);
758 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
760 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
761 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
762 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
764 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
765 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
766 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
768 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
769 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
770 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
772 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
773 "%s", _(" If you do not see a code, copy the url of the page you were redirected to and again press 'C' and copy and paste it into the prompt. "));
774 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
776 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
777 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
778 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
780 display_init_err(tmp_20k_buf, 0);
781 memset((void *)tmp, 0, sizeof(tmp));
782 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
783 tmp[sizeof(tmp)-1] = '\0';
785 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
786 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
787 flags = OE_APPEND_CURRENT;
788 sprintf(prompt, "%s: ", accesscodelabel);
789 do {
790 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
791 prompt, NULL, NO_HELP, &flags);
792 } while (rc != 0 && rc != 1);
793 if(!struncmp(token, "http://", 7)
794 || !struncmp(token, "https://", 8)){
795 char *s, *t;
796 s = strstr(token, "code=");
797 if(s != NULL){
798 t = strchr(s, '&');
799 if(t) *t = '\0';
800 code = cpystr(s+5);
801 if(t) *t = '&';
804 else code = token[0] ? cpystr(token) : NULL;
808 return code;
811 void mm_login_oauth2(NETMBX *, char *, char *, OAUTH2_S *, long int, char *, char *);
813 /* The purpose of this function is to report to c-client the values of the
814 * different tokens and codes so that c-client can try to log in the user
815 * to the server. This function DOES NOT attempt to get these values for
816 * the user. That is attempted in the c-client side (as best as it can be
817 * done given our circumstances: no http support, no javascript support,
818 * etc.). This is the best we can do:
820 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
821 * as best as we can. Unloaded means that there is no server known to
822 * connect, no access token, etc. Pretty much nothing is known about
823 * how to get access code, access token, etc. We ask the user to fill
824 * it up for us, if they can. If the user fills it up we save those
825 * values.
827 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
828 * save the information that c-client got for us.
830 * 3. When saving this information we use the password caching facilities,
831 * but we must do it in a different format so that old information and
832 * new information are not mixed. In order to accommodate this for new
833 * authentication methods, we save the information in the same fields,
834 * but this time we modify it slightly, so that old functions fail to
835 * understand the new information and so not modify it nor use it. The
836 * modification is simple: Every piece of information that was saved
837 * before is prepended XOAUTH2\001 to indicate the authentication
838 * method for which it works. The character \001 is a separator. New
839 * Alpine will know how to deal with this, but old versions, will not
840 * strip this prefix from the information and fail to get the
841 * information or modify it when needed. Only new versions of Alpine will
842 * know how to process this information.
843 * new_value = authenticator_method separator old_value
844 * authenticator_method = "XOAUTH2_S"
845 * separator = "U+1"
846 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
847 * authenticator is "XOAUTH2\001imap.gmail.com".
848 * In addition, the password field is not used to encode the password
849 * anymore, it is used to save login information needed in a format that
850 * the caller function chooses, but that must be preceded by the
851 * "authenticator_method separator" code as above.
853 void
854 mm_login_oauth2(NETMBX *mb, char *user, char *method,
855 OAUTH2_S *login, long int trial,
856 char *usethisprompt, char *altuserforcache)
858 char *token, tmp[MAILTMPLEN];
859 char prompt[4*MAILTMPLEN], value[4*MAILTMPLEN], *last;
860 char defuser[NETMAXUSER];
861 char hostleadin[80], hostname[200], defubuf[200];
862 char logleadin[80], pwleadin[50];
863 char *url_oauth2;
864 char *tool = NULL;
865 char *OldRefreshToken, *OldAccessToken;
866 char *NewRefreshToken, *NewAccessToken;
867 char *SaveRefreshToken, *SaveAccessToken;
868 /* TRANSLATORS: A label for the hostname that the user is logging in on */
869 char *hostlabel = _("HOST");
870 /* TRANSLATORS: user is logging in as a particular user (a particular
871 login name), this is just labelling that user name. */
872 char *userlabel = _("USER");
873 STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1];
874 HelpType help ;
875 int len, rc, q_line, flags, i, j;
876 int oespace, avail, need, save_dont_use;
877 int save_in_init;
878 int registered;
879 int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
880 OAUTH2_S *oa2list, *oa2;
881 XOAUTH2_INFO_S *x;
883 unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
884 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
885 int preserve_password = -1;
886 #endif
888 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
889 trial, mb->user ? mb->user : "(null)",
890 mb->service ? mb->service : "(null)",
891 mb->port ? " port=" : "",
892 mb->port ? comatose(mb->port) : "",
893 altuserforcache ? " altuserforcache =" : "",
894 altuserforcache ? altuserforcache : ""));
896 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
898 save_in_init = ps_global->in_init_seq;
899 ps_global->in_init_seq = 0;
900 ps_global->no_newmail_check_from_optionally_enter = 1;
902 /* make sure errors are seen */
903 if(ps_global->ttyo && !ps_global->noshow_error)
904 flush_status_messages(0);
906 token = NULL; /* start from scratch */
908 hostlist[0].name = mb->host;
909 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
910 hostlist[0].next = &hostlist[1];
911 hostlist[1].name = mb->orighost;
912 hostlist[1].next = NULL;
914 else
915 hostlist[0].next = NULL;
917 if(hostlist[0].name){
918 dprint((9, "mm_login_oauth2: host=%s\n",
919 hostlist[0].name ? hostlist[0].name : "?"));
920 if(hostlist[0].next && hostlist[1].name){
921 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name));
925 if(trial == 0L && !altuserforcache){
926 if(*mb->user != '\0')
927 strncpy(user, mb->user, NETMAXUSER);
928 else{
929 flags = OE_APPEND_CURRENT;
930 sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
931 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
932 prompt, NULL, NO_HELP, &flags);
934 user[NETMAXUSER-1] = '\0';
938 * We check to see if the server we are going to log in to is already
939 * registered. This gives us a list of servers with the same
940 * credentials, so we use the same credentials for all of them.
943 for(registered = 0, oa2list = alpine_oauth2_list;
944 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
945 oa2list++){
946 for(i = 0; i < OAUTH2_TOT_EQUIV
947 && oa2list->host[i] != NULL
948 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
949 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
950 registered++;
951 break;
955 if(registered){
956 x = oauth2_get_client_info(oa2list->name, user);
957 if(x && x->flow){
958 for(oa2list = alpine_oauth2_list;
959 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
960 oa2list++){
961 for(i = 0; i < OAUTH2_TOT_EQUIV
962 && oa2list->host[i] != NULL
963 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
964 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
965 char *flow = oa2list->server_mthd[0].name ? "Authorize"
966 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
967 if(!strucmp(x->flow, flow)) break; /* found it */
971 /* else use the one we found earlier, the user has to configure this better */
974 if(registered){
975 hostlist2[i = 0].name = mb->host;
976 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
977 hostlist2[++i].name = mb->orighost;
979 for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){
980 int k;
981 for(k = 0; k <= i && hostlist2[k].name
982 && strcmp(hostlist2[k].name, oa2list->host[j]); k++);
983 if(k == i + 1)
984 hostlist2[++i].name = oa2list->host[j];
986 hostlist2[i+1].name = NULL;
987 hostlist2[i+1].next = NULL;
988 for(j = i; j >= 0; j--)
989 hostlist2[j].next = &hostlist2[j+1];
992 if(registered){ /* redo the app_id, no questions asked */
993 free_id(&ps_global->id);
994 ps_global->id = set_alpine_id(oa2list->app_id ? oa2list->app_id : PACKAGE_NAME, PACKAGE_VERSION);
995 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
999 * We check if we have a refresh token saved somewhere, if so
1000 * we use it to get a new access token, otherwise we need to
1001 * get an access code so we can get (and save) a refresh token
1002 * and use the access token.
1004 if(trial == 0L && !altuserforcache){
1005 /* Search for a refresh token that is already loaded ... */
1006 if(imap_get_passwd_auth(mm_login_list, &token, user,
1007 registered ? hostlist2 : hostlist,
1008 (mb->sslflag||mb->tlsflag), OA2NAME)){
1009 dprint((9, "mm_login_oauth2: found a refresh token\n"));
1010 ps_global->no_newmail_check_from_optionally_enter = 0;
1011 ps_global->in_init_seq = save_in_init;
1013 #ifdef LOCAL_PASSWD_CACHE
1014 /* or see if we have saved one in the local password cache and load it */
1015 else if(get_passfile_passwd_auth(ps_global->pinerc, &token,
1016 user, registered ? hostlist2 : hostlist,
1017 (mb->sslflag||mb->tlsflag), OA2NAME)){
1018 imap_set_passwd_auth(&mm_login_list, token, user,
1019 hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1020 update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist,
1021 (mb->sslflag||mb->tlsflag), OA2NAME);
1022 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
1023 ps_global->no_newmail_check_from_optionally_enter = 0;
1024 ps_global->in_init_seq = save_in_init;
1026 if(token && *token) preserve_password = 1; /* resave it, no need to ask */
1027 #endif /* LOCAL_PASSWD_CACHE */
1029 user[NETMAXUSER-1] = '\0';
1031 /* The Old* variables is what c_client knows */
1032 OldRefreshToken = login->cancel_refresh_token ? NULL : login->param[OA2_RefreshToken].value;
1033 OldAccessToken = login->access_token;
1034 OldExpirationTime = login->expiration;
1036 /* The New* variables is what Alpine knows */
1037 NewRefreshToken = NewAccessToken = NULL;
1038 NewExpirationTime = 0L;
1039 ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0;
1041 if(token && *token && !login->cancel_refresh_token){
1042 char *s, *t;
1044 s = token;
1045 t = strchr(s, PWDAUTHSEP);
1046 if(t == NULL)
1047 NewRefreshToken = cpystr(s);
1048 else {
1049 *t++ = '\0';
1050 NewRefreshToken = cpystr(s);
1051 s = t;
1052 t = strchr(s, PWDAUTHSEP);
1053 if(t == NULL)
1054 NewAccessToken = cpystr(s);
1055 else {
1056 *t++ = '\0';
1057 NewAccessToken = cpystr(s);
1058 s = t;
1059 NewExpirationTime = strtol(s, &s, 10);
1060 if(NewExpirationTime <= 0 || NewExpirationTime <= time(0))
1061 NewExpirationTime = 0L;
1064 /* check we got good information, and send good information below */
1065 if(NewRefreshToken && !*NewRefreshToken)
1066 fs_give((void **) &NewRefreshToken);
1067 if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken))
1068 fs_give((void **) &NewAccessToken);
1071 if(NewRefreshToken == NULL)
1072 login->first_time++;
1074 if(login->first_time){ /* count how many authorization methods we support */
1075 int nmethods, i, j;
1077 for(nmethods = 0, oa2 = alpine_oauth2_list; oa2 && oa2->name ; oa2++){
1078 for(j = 0; j < OAUTH2_TOT_EQUIV
1079 && oa2
1080 && oa2->host[j] != NULL
1081 && strucmp(oa2->host[j], mb->orighost) != 0; j++);
1082 if(oa2 && oa2->host && j < OAUTH2_TOT_EQUIV && oa2->host[j])
1083 nmethods++;
1086 if(nmethods > 1)
1087 oa2list = oauth2_select_flow(mb->orighost);
1089 if(!oa2list) registered = 0;
1092 /* Default to saving what we already had saved */
1094 SaveRefreshToken = login->cancel_refresh_token ? NULL : NewRefreshToken;
1095 SaveAccessToken = NewAccessToken;
1096 SaveExpirationTime = NewExpirationTime;
1098 /* Translation of the logic below:
1099 * if (c-client has a refresh token
1100 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1101 forget the Alpine refresh token;
1102 make the Alpine Refresh token = c-client refresh token.;
1103 signal that we changed the refresh token;
1104 In this situation we do not need to clear up the Alpine access token. This will
1105 expire, so we can use it until it expires. We can save the c-client refresh token
1106 together with the Alpine Access Token and the expiration date of the Alpine Access
1107 Token.
1108 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1109 forget the Alpine refresh token;
1110 if Alpine has an access token, forget it;
1111 reset the expiration time
1112 signal that we changed the refresh token; (because the service expired it)
1116 if(OldRefreshToken != NULL
1117 && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){
1118 if(NewRefreshToken) fs_give((void **) &NewRefreshToken);
1119 NewRefreshToken = cpystr(OldRefreshToken);
1120 ChangeRefreshToken++;
1121 SaveRefreshToken = OldRefreshToken;
1122 SaveAccessToken = NewAccessToken;
1123 SaveExpirationTime = NewExpirationTime;
1124 } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){
1125 fs_give((void **) &NewRefreshToken);
1126 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1127 NewExpirationTime = 0L;
1128 ChangeRefreshToken++;
1129 SaveRefreshToken = NULL;
1130 SaveAccessToken = NULL;
1131 SaveExpirationTime = 0L;
1134 if(OldAccessToken != NULL
1135 && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){
1136 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1137 NewAccessToken = cpystr(OldAccessToken);
1138 ChangeAccessToken++;
1139 NewExpirationTime = OldExpirationTime;
1140 SaveRefreshToken = NewRefreshToken;
1141 SaveAccessToken = NewAccessToken;
1142 SaveExpirationTime = NewExpirationTime;
1145 if(!registered){
1146 login->param[OA2_RefreshToken].value = SaveRefreshToken;
1147 login->access_token = SaveAccessToken;
1148 login->expiration = SaveExpirationTime;
1149 } else {
1150 oa2list->param[OA2_RefreshToken].value = SaveRefreshToken;
1151 oa2list->access_token = SaveAccessToken;
1152 oa2list->expiration = SaveExpirationTime;
1153 oa2list->first_time = login->first_time;
1154 oa2list->cancel_refresh_token = login->cancel_refresh_token;
1155 *login = *oa2list; /* load login pointer */
1158 if(!ChangeAccessToken && !ChangeRefreshToken && !login->cancel_refresh_token)
1159 return;
1161 /* get ready to save this information. The format will be
1162 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1163 * (spaces added for clarity, \001 is PWDAUTHSEP)
1165 if(token) fs_give((void **) &token);
1166 sprintf(tmp, "%lu", SaveExpirationTime);
1167 tmp[sizeof(tmp) - 1] = '\0';
1168 len = strlen(SaveRefreshToken ? SaveRefreshToken : "")
1169 + strlen(SaveAccessToken ? SaveAccessToken : "")
1170 + strlen(tmp) + 2;
1171 token = fs_get(len + 1);
1172 sprintf(token, "%s%c%s%c%lu",
1173 SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP,
1174 SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP,
1175 SaveExpirationTime);
1177 /* remember the access information for next time */
1178 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
1179 imap_set_passwd_auth(&mm_login_list, token,
1180 altuserforcache ? altuserforcache : user, hostlist,
1181 (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1182 #ifdef LOCAL_PASSWD_CACHE
1183 /* if requested, remember it on disk for next session */
1184 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
1185 set_passfile_passwd_auth(ps_global->pinerc, token,
1186 altuserforcache ? altuserforcache : user, hostlist,
1187 (mb->sslflag||mb->tlsflag),
1188 (preserve_password == -1 ? 0
1189 : (preserve_password == 0 ? 2 :1)), OA2NAME);
1190 #endif /* LOCAL_PASSWD_CACHE */
1192 ps_global->no_newmail_check_from_optionally_enter = 0;
1195 IDLIST *
1196 set_alpine_id(unsigned char *pname, unsigned char *pversion)
1198 IDLIST *id;
1200 if(!pname || !pversion) return NULL;
1202 id = fs_get(sizeof(IDLIST));
1203 id->name = cpystr("name");
1204 id->value = cpystr(pname);
1205 id->next = fs_get(sizeof(IDLIST));
1206 id->next->name = cpystr("version");
1207 id->next->value = cpystr(pversion);
1208 id->next->next = NULL;
1209 return id;
1212 /*----------------------------------------------------------------------
1213 receive notification from IMAP
1215 Args: stream -- Mail stream message is relevant to
1216 string -- The message text
1217 errflg -- Set if it is a serious error
1219 Result: message displayed in status line
1221 The facility is for general notices, such as connection to server;
1222 server shutting down etc... It is used infrequently.
1223 ----------------------------------------------------------------------*/
1224 void
1225 mm_notify(MAILSTREAM *stream, char *string, long int errflg)
1227 time_t now;
1228 struct tm *tm_now;
1230 now = time((time_t *)0);
1231 tm_now = localtime(&now);
1233 /* be sure to log the message... */
1234 #ifdef DEBUG
1235 if(ps_global->debug_imap || ps_global->debugmem)
1236 dprint((errflg == TCPDEBUG ? 7 : 2,
1237 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1238 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1239 tm_now->tm_mon+1, tm_now->tm_mday,
1240 (!errflg) ? "babble" :
1241 (errflg == ERROR) ? "error" :
1242 (errflg == WARN) ? "warning" :
1243 (errflg == PARSE) ? "parse" :
1244 (errflg == TCPDEBUG) ? "tcp" :
1245 (errflg == BYE) ? "bye" : "unknown",
1246 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1247 string ? string : "?"));
1248 #endif
1250 snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s",
1251 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1252 (int) MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70),
1253 string);
1254 ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols
1255 : sizeof(ps_global->last_error)-1] = '\0';
1258 * Then either set special bits in the pine struct or
1259 * display the message if it's tagged as an "ALERT" or
1260 * its errflg > NIL (i.e., WARN, or ERROR)
1262 if(errflg == BYE)
1264 * We'd like to sp_mark_stream_dead() here but we can't do that because
1265 * that might call mail_close and we are already in a c-client callback.
1266 * So just set the dead bit and clean it up later.
1268 sp_set_dead_stream(stream, 1);
1269 else if(!strncmp(string, "[TRYCREATE]", 11))
1270 ps_global->try_to_create = 1;
1271 else if(!strncmp(string, "[REFERRAL ", 10))
1272 ; /* handled in the imap_referral() callback */
1273 else if(!strncmp(string, "[ALERT]", 7))
1274 q_status_message2(SM_MODAL, 3, 3,
1275 _("Alert received while accessing \"%s\": %s"),
1276 (stream && stream->mailbox)
1277 ? stream->mailbox : "-no folder-",
1278 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000),
1279 SIZEOF_20KBUF-10000, string));
1280 else if(!strncmp(string, "[UNSEEN ", 8)){
1281 char *p;
1282 long n = 0;
1284 for(p = string + 8; isdigit(*p); p++)
1285 n = (n * 10) + (*p - '0');
1287 sp_set_first_unseen(stream, n);
1289 else if(!strncmp(string, "[READ-ONLY]", 11)
1290 && !(stream && stream->mailbox && IS_NEWS(stream)))
1291 q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s",
1292 (stream && stream->mailbox)
1293 ? stream->mailbox : "-no folder-",
1294 string + 11);
1295 else if((errflg && errflg != BYE && errflg != PARSE)
1296 && !ps_global->noshow_error
1297 && !(errflg == WARN
1298 && (ps_global->noshow_warn || (stream && stream->unhealthy))))
1299 q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0),
1300 3, 6, ps_global->last_error);
1304 /*----------------------------------------------------------------------
1305 Queue imap log message for display in the message line
1307 Args: string -- The message
1308 errflg -- flag set to 1 if pertains to an error
1310 Result: Message queued for display
1312 The c-client/imap reports most of it's status and errors here
1313 ---*/
1314 void
1315 mm_log(char *string, long int errflg)
1317 char message[sizeof(ps_global->c_client_error)];
1318 char *occurence;
1319 int was_capitalized;
1320 static char saw_kerberos_init_warning;
1321 time_t now;
1322 struct tm *tm_now;
1324 now = time((time_t *)0);
1325 tm_now = localtime(&now);
1327 dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? 1 :
1328 (errflg == TCPDEBUG) ? 10 : 2,
1329 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1330 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1331 tm_now->tm_mon+1, tm_now->tm_mday,
1332 (!errflg) ? "babble" :
1333 (errflg == ERROR) ? "error" :
1334 (errflg == WARN) ? "warning" :
1335 (errflg == PARSE) ? "parse" :
1336 (errflg == TCPDEBUG) ? "tcp" :
1337 (errflg == BYE) ? "bye" : "unknown",
1338 string ? string : "?"));
1340 if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){
1341 ps_global->try_to_create = 1;
1342 return;
1344 else if(ps_global->try_to_create
1345 || !strncmp(string, "[CLOSED]", 8)
1346 || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op")))
1348 * Don't display if creating new folder OR
1349 * warning about a dead stream ...
1351 return;
1353 strncpy(message, string, sizeof(message));
1354 message[sizeof(message) - 1] = '\0';
1356 if(errflg == WARN && srchstr(message, "try running kinit") != NULL){
1357 if(saw_kerberos_init_warning)
1358 return;
1360 saw_kerberos_init_warning = 1;
1363 /*---- replace all "mailbox" with "folder" ------*/
1364 occurence = srchstr(message, "mailbox");
1365 while(occurence) {
1366 if(!*(occurence+7)
1367 || isspace((unsigned char) *(occurence+7))
1368 || *(occurence+7) == ':'){
1369 was_capitalized = isupper((unsigned char) *occurence);
1370 rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder"));
1371 if(was_capitalized)
1372 *occurence = (errflg == PARSE ? 'A' : 'F');
1374 else
1375 occurence += 7;
1377 occurence = srchstr(occurence, "mailbox");
1380 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1381 occurence = srchstr(message, "GSSAPI");
1382 while(occurence) {
1383 if(!*(occurence+6)
1384 || isspace((unsigned char) *(occurence+6))
1385 || *(occurence+6) == ':')
1386 rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos");
1387 else
1388 occurence += 6;
1390 occurence = srchstr(occurence, "GSSAPI");
1393 if(errflg == ERROR)
1394 ps_global->mm_log_error = 1;
1396 if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error))
1397 strncpy(ps_global->c_client_error, message,
1398 sizeof(ps_global->c_client_error));
1400 if(ps_global->noshow_error
1401 || (ps_global->noshow_warn && errflg == WARN)
1402 || !(errflg == ERROR || errflg == WARN))
1403 return; /* Only care about errors; don't print when asked not to */
1405 /*---- Display the message ------*/
1406 q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER,
1407 3, 5, message);
1408 strncpy(ps_global->last_error, message, sizeof(ps_global->last_error));
1409 ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
1412 void
1413 mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial,
1414 char *method, char *usethisprompt, char *altuserforcache)
1416 if(method == NULL)
1417 return;
1418 if(strucmp(method, OA2NAME) == 0 || strucmp(method, BEARERNAME) == 0)
1419 mm_login_oauth2(mb, user, method, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache);
1422 void
1423 mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial,
1424 char *usethisprompt, char *altuserforcache)
1426 char tmp[MAILTMPLEN];
1427 char prompt[1000], *last;
1428 char port[20], non_def_port[20], insecure[20];
1429 char defuser[NETMAXUSER];
1430 char hostleadin[80], hostname[200], defubuf[200];
1431 char logleadin[80], pwleadin[50];
1432 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
1433 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1434 that the password will be sent unencrypted over the network. This is
1435 just a warning message that gets added parenthetically when the user
1436 is asked for a password. */
1437 char *insec = _(" (INSECURE)");
1438 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1439 after having already failed at least once. */
1440 char *retry = _("Retrying - ");
1441 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1442 char *hostlabel = _("HOST");
1443 /* TRANSLATORS: user is logging in as a particular user (a particular
1444 login name), this is just labelling that user name. */
1445 char *userlabel = _("USER");
1446 STRLIST_S hostlist[2];
1447 HelpType help ;
1448 int len, rc, q_line, flags;
1449 int oespace, avail, need, save_dont_use;
1450 int save_in_init;
1451 struct servent *sv;
1452 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1453 int preserve_password = -1;
1454 #endif
1456 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1457 trial, mb->user ? mb->user : "(null)",
1458 mb->service ? mb->service : "(null)",
1459 mb->port ? " port=" : "",
1460 mb->port ? comatose(mb->port) : "",
1461 altuserforcache ? " altuserforcache =" : "",
1462 altuserforcache ? altuserforcache : ""));
1463 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
1465 save_in_init = ps_global->in_init_seq;
1466 ps_global->in_init_seq = 0;
1467 ps_global->no_newmail_check_from_optionally_enter = 1;
1469 /* make sure errors are seen */
1470 if(ps_global->ttyo)
1471 flush_status_messages(0);
1473 /* redo app id in case we are loging in to an IMAP server that supports the IMAP ID extension */
1474 free_id(&ps_global->id);
1475 ps_global->id = set_alpine_id(PACKAGE_NAME, PACKAGE_VERSION);
1476 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
1479 * Add port number to hostname if going through a tunnel or something
1481 non_def_port[0] = '\0';
1482 if(mb->port && mb->service &&
1483 (sv = getservbyname(mb->service, "tcp")) &&
1484 (mb->port != ntohs(sv->s_port))){
1485 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
1486 non_def_port[sizeof(non_def_port)-1] = '\0';
1487 dprint((9, "mm_login: using non-default port=%s\n",
1488 non_def_port ? non_def_port : "?"));
1492 * set up host list for sybil servers...
1494 if(*non_def_port){
1495 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
1496 hostlist0[sizeof(hostlist0)-1] = '\0';
1497 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
1498 hostlist0[sizeof(hostlist0)-1] = '\0';
1499 hostlist[0].name = hostlist0;
1500 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1501 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
1502 hostlist1[sizeof(hostlist1)-1] = '\0';
1503 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
1504 hostlist1[sizeof(hostlist1)-1] = '\0';
1505 hostlist[0].next = &hostlist[1];
1506 hostlist[1].name = hostlist1;
1507 hostlist[1].next = NULL;
1509 else
1510 hostlist[0].next = NULL;
1512 else{
1513 hostlist[0].name = mb->host;
1514 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1515 hostlist[0].next = &hostlist[1];
1516 hostlist[1].name = mb->orighost;
1517 hostlist[1].next = NULL;
1519 else
1520 hostlist[0].next = NULL;
1523 if(hostlist[0].name){
1524 dprint((9, "mm_login: host=%s\n",
1525 hostlist[0].name ? hostlist[0].name : "?"));
1526 if(hostlist[0].next && hostlist[1].name){
1527 dprint((9, "mm_login: orighost=%s\n", hostlist[1].name));
1532 * Initialize user name with either
1533 * 1) /user= value in the stream being logged into,
1534 * or 2) the user name we're running under.
1536 * Note that VAR_USER_ID is not yet initialized if this login is
1537 * the one to access the remote config file. In that case, the user
1538 * can supply the username in the config file name with /user=.
1540 if(trial == 0L && !altuserforcache){
1541 strncpy(user, (*mb->user) ? mb->user :
1542 ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "",
1543 NETMAXUSER);
1544 user[NETMAXUSER-1] = '\0';
1546 /* try last working password associated with this host. */
1547 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1548 (mb->sslflag||mb->tlsflag))){
1549 dprint((9, "mm_login: found a password to try\n"));
1550 ps_global->no_newmail_check_from_optionally_enter = 0;
1551 ps_global->in_init_seq = save_in_init;
1552 return;
1555 #ifdef LOCAL_PASSWD_CACHE
1556 /* check to see if there's a password left over from last session */
1557 if(get_passfile_passwd(ps_global->pinerc, pwd,
1558 user, hostlist, (mb->sslflag||mb->tlsflag))){
1559 imap_set_passwd(&mm_login_list, *pwd, user,
1560 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1561 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1562 (mb->sslflag||mb->tlsflag));
1563 dprint((9, "mm_login: found a password in passfile to try\n"));
1564 ps_global->no_newmail_check_from_optionally_enter = 0;
1565 ps_global->in_init_seq = save_in_init;
1566 return;
1568 #endif /* LOCAL_PASSWD_CACHE */
1571 * If no explicit user name supplied and we've not logged in
1572 * with our local user name, see if we've visited this
1573 * host before as someone else.
1575 if(!*mb->user &&
1576 ((last = imap_get_user(mm_login_list, hostlist))
1577 #ifdef LOCAL_PASSWD_CACHE
1579 (last = get_passfile_user(ps_global->pinerc, hostlist))
1580 #endif /* LOCAL_PASSWD_CACHE */
1582 strncpy(user, last, NETMAXUSER);
1583 user[NETMAXUSER-1] = '\0';
1584 dprint((9, "mm_login: found user=%s\n",
1585 user ? user : "?"));
1587 /* try last working password associated with this host/user. */
1588 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1589 (mb->sslflag||mb->tlsflag))){
1590 dprint((9,
1591 "mm_login: found a password for user=%s to try\n",
1592 user ? user : "?"));
1593 ps_global->no_newmail_check_from_optionally_enter = 0;
1594 ps_global->in_init_seq = save_in_init;
1595 return;
1598 #ifdef LOCAL_PASSWD_CACHE
1599 /* check to see if there's a password left over from last session */
1600 if(get_passfile_passwd(ps_global->pinerc, pwd,
1601 user, hostlist, (mb->sslflag||mb->tlsflag))){
1602 imap_set_passwd(&mm_login_list, *pwd, user,
1603 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1604 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1605 (mb->sslflag||mb->tlsflag));
1606 dprint((9,
1607 "mm_login: found a password for user=%s in passfile to try\n",
1608 user ? user : "?"));
1609 ps_global->no_newmail_check_from_optionally_enter = 0;
1610 ps_global->in_init_seq = save_in_init;
1611 return;
1613 #endif /* LOCAL_PASSWD_CACHE */
1616 #if !defined(DOS) && !defined(OS2)
1617 if(!*mb->user && !*user &&
1618 (last = (ps_global->ui.login && ps_global->ui.login[0])
1619 ? ps_global->ui.login : NULL)
1621 strncpy(user, last, NETMAXUSER);
1622 user[NETMAXUSER-1] = '\0';
1623 dprint((9, "mm_login: found user=%s\n",
1624 user ? user : "?"));
1626 /* try last working password associated with this host. */
1627 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1628 (mb->sslflag||mb->tlsflag))){
1629 dprint((9, "mm_login:ui: found a password to try\n"));
1630 ps_global->no_newmail_check_from_optionally_enter = 0;
1631 ps_global->in_init_seq = save_in_init;
1632 return;
1635 #ifdef LOCAL_PASSWD_CACHE
1636 /* check to see if there's a password left over from last session */
1637 if(get_passfile_passwd(ps_global->pinerc, pwd,
1638 user, hostlist, (mb->sslflag||mb->tlsflag))){
1639 imap_set_passwd(&mm_login_list, *pwd, user,
1640 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1641 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1642 (mb->sslflag||mb->tlsflag));
1643 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1644 ps_global->no_newmail_check_from_optionally_enter = 0;
1645 ps_global->in_init_seq = save_in_init;
1646 return;
1648 #endif /* LOCAL_PASSWD_CACHE */
1650 #endif
1653 user[NETMAXUSER-1] = '\0';
1655 if(trial == 0)
1656 retry = "";
1659 * Even if we have a user now, user gets a chance to change it.
1661 ps_global->mangled_footer = 1;
1662 if(!*mb->user && !altuserforcache){
1664 help = NO_HELP;
1667 * Instead of offering user with a value that the user can edit,
1668 * we offer [user] as a default so that the user can type CR to
1669 * use it. Otherwise, the user has to type in whole name.
1671 strncpy(defuser, user, sizeof(defuser)-1);
1672 defuser[sizeof(defuser)-1] = '\0';
1673 user[0] = '\0';
1676 * Need space for "Retrying - "
1677 * "+ HOST: "
1678 * hostname
1679 * " (INSECURE)"
1680 * ENTER LOGIN NAME
1681 * " [defuser] : "
1682 * about 15 chars for input
1685 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1686 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1687 hostleadin[sizeof(hostleadin)-1] = '\0';
1689 strncpy(hostname, mb->host, sizeof(hostname)-1);
1690 hostname[sizeof(hostname)-1] = '\0';
1693 * Add port number to hostname if going through a tunnel or something
1695 if(*non_def_port)
1696 strncpy(port, non_def_port, sizeof(port));
1697 else
1698 port[0] = '\0';
1700 insecure[0] = '\0';
1701 /* if not encrypted and SSL/TLS is supported */
1702 if(!(mb->sslflag||mb->tlsflag) &&
1703 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1704 strncpy(insecure, insec, sizeof(insecure));
1706 /* TRANSLATORS: user is being asked to type in their login name */
1707 snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME"));
1709 snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "",
1710 (*defuser) ? defuser : "",
1711 (*defuser) ? "]" : "");
1712 defubuf[sizeof(defubuf)-1] = '\0';
1713 /* space reserved after prompt */
1714 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1716 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1717 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1718 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace;
1720 /* If we're retrying cut the hostname back to the first word. */
1721 if(avail < need && trial > 0){
1722 char *p;
1724 len = strlen(hostname);
1725 if((p = strchr(hostname, '.')) != NULL){
1726 *p = '\0';
1727 need -= (len - strlen(hostname));
1731 if(avail < need){
1732 need -= utf8_width(retry);
1733 retry = "";
1735 if(avail < need){
1737 /* reduce length of logleadin */
1738 len = utf8_width(logleadin);
1739 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1740 longer version doesn't fit on screen */
1741 snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN"));
1742 need -= (len - utf8_width(logleadin));
1744 if(avail < need){
1745 /* get two spaces from hostleadin */
1746 len = utf8_width(hostleadin);
1747 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1748 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1749 hostleadin[sizeof(hostleadin)-1] = '\0';
1750 need -= (len - utf8_width(hostleadin));
1752 /* get rid of port */
1753 if(avail < need && strlen(port) > 0){
1754 need -= strlen(port);
1755 port[0] = '\0';
1758 if(avail < need){
1759 int reduce_to;
1762 * Reduce space for hostname. Best we can do is 6 chars
1763 * with hos...
1765 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1766 len = strlen(hostname);
1767 strncpy(hostname+reduce_to-3, "...", 4);
1768 need -= (len - strlen(hostname));
1770 if(avail < need && strlen(insecure) > 0){
1771 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1772 need -= 3;
1773 insecure[strlen(insecure)-4] = ')';
1774 insecure[strlen(insecure)-3] = '\0';
1776 else{
1777 need -= utf8_width(insecure);
1778 insecure[0] = '\0';
1782 if(avail < need){
1783 if(strlen(defubuf) > 3){
1784 len = strlen(defubuf);
1785 strncpy(defubuf, " [..] :", 9);
1786 need -= (len - strlen(defubuf));
1789 if(avail < need)
1790 strncpy(defubuf, ":", 2);
1793 * If it still doesn't fit, optionally_enter gets
1794 * to worry about it.
1802 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s",
1803 retry, hostleadin, hostname, port, insecure, logleadin, defubuf);
1804 prompt[sizeof(prompt)-1] = '\0';
1806 while(1) {
1807 if(ps_global->ttyo)
1808 mm_login_alt_cue(mb);
1810 flags = OE_APPEND_CURRENT;
1811 save_dont_use = ps_global->dont_use_init_cmds;
1812 ps_global->dont_use_init_cmds = 1;
1813 #ifdef _WINDOWS
1814 if(!*user && *defuser){
1815 strncpy(user, defuser, NETMAXUSER);
1816 user[NETMAXUSER-1] = '\0';
1819 rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD,
1820 #ifdef LOCAL_PASSWD_CACHE
1821 is_using_passfile() ? 1 :
1822 #endif /* LOCAL_PASSWD_CACHE */
1823 0, 0, &preserve_password);
1824 ps_global->dont_use_init_cmds = save_dont_use;
1825 if(rc == 0 && *user && *pwd)
1826 goto nopwpmt;
1827 #else /* !_WINDOWS */
1828 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
1829 prompt, NULL, help, &flags);
1830 #endif /* !_WINDOWS */
1831 ps_global->dont_use_init_cmds = save_dont_use;
1833 if(rc == 3) {
1834 help = help == NO_HELP ? h_oe_login : NO_HELP;
1835 continue;
1838 /* default */
1839 if(rc == 0 && !*user){
1840 strncpy(user, defuser, NETMAXUSER);
1841 user[NETMAXUSER-1] = '\0';
1844 if(rc != 4)
1845 break;
1848 if(rc == 1 || !user[0]) {
1849 ps_global->user_says_cancel = (rc == 1);
1850 user[0] = '\0';
1853 else{
1854 strncpy(user, mb->user, NETMAXUSER);
1855 user[NETMAXUSER-1] = '\0';
1858 user[NETMAXUSER-1] = '\0';
1860 if(!(user[0] || altuserforcache)){
1861 ps_global->no_newmail_check_from_optionally_enter = 0;
1862 ps_global->in_init_seq = save_in_init;
1863 return;
1867 * Now that we have a user, we can check in the cache again to see
1868 * if there is a password there. Try last working password associated
1869 * with this host and user.
1871 if(trial == 0L && !*mb->user && !altuserforcache){
1872 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1873 (mb->sslflag||mb->tlsflag))){
1874 ps_global->no_newmail_check_from_optionally_enter = 0;
1875 ps_global->in_init_seq = save_in_init;
1876 return;
1879 #ifdef LOCAL_PASSWD_CACHE
1880 if(get_passfile_passwd(ps_global->pinerc, pwd,
1881 user, hostlist, (mb->sslflag||mb->tlsflag))){
1882 imap_set_passwd(&mm_login_list, *pwd, user,
1883 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1884 ps_global->no_newmail_check_from_optionally_enter = 0;
1885 ps_global->in_init_seq = save_in_init;
1886 return;
1888 #endif /* LOCAL_PASSWD_CACHE */
1890 else if(trial == 0 && altuserforcache){
1891 if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist,
1892 (mb->sslflag||mb->tlsflag))){
1893 ps_global->no_newmail_check_from_optionally_enter = 0;
1894 ps_global->in_init_seq = save_in_init;
1895 return;
1898 #ifdef LOCAL_PASSWD_CACHE
1899 if(get_passfile_passwd(ps_global->pinerc, pwd,
1900 altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
1901 imap_set_passwd(&mm_login_list, *pwd, altuserforcache,
1902 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1903 ps_global->no_newmail_check_from_optionally_enter = 0;
1904 ps_global->in_init_seq = save_in_init;
1905 return;
1907 #endif /* LOCAL_PASSWD_CACHE */
1911 * Didn't find password in cache or this isn't the first try. Ask user.
1913 help = NO_HELP;
1916 * Need space for "Retrying - "
1917 * "+ HOST: "
1918 * hostname
1919 * " (INSECURE) "
1920 * " USER: "
1921 * user
1922 * " ENTER PASSWORD: "
1923 * about 15 chars for input
1926 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1927 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1929 strncpy(hostname, mb->host, sizeof(hostname)-1);
1930 hostname[sizeof(hostname)-1] = '\0';
1933 * Add port number to hostname if going through a tunnel or something
1935 if(*non_def_port)
1936 strncpy(port, non_def_port, sizeof(port));
1937 else
1938 port[0] = '\0';
1940 insecure[0] = '\0';
1942 /* if not encrypted and SSL/TLS is supported */
1943 if(!(mb->sslflag||mb->tlsflag) &&
1944 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1945 strncpy(insecure, insec, sizeof(insecure));
1947 if(usethisprompt){
1948 strncpy(logleadin, usethisprompt, sizeof(logleadin));
1949 logleadin[sizeof(logleadin)-1] = '\0';
1950 defubuf[0] = '\0';
1951 user[0] = '\0';
1953 else{
1954 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1956 strncpy(defubuf, user, sizeof(defubuf)-1);
1957 defubuf[sizeof(defubuf)-1] = '\0';
1960 /* TRANSLATORS: user is being asked to type in their password */
1961 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD"));
1963 /* space reserved after prompt */
1964 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1966 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1967 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1968 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) +
1969 utf8_width(pwleadin) + oespace;
1971 if(avail < need && trial > 0){
1972 char *p;
1974 len = strlen(hostname);
1975 if((p = strchr(hostname, '.')) != NULL){
1976 *p = '\0';
1977 need -= (len - strlen(hostname));
1981 if(avail < need){
1982 need -= utf8_width(retry);
1983 retry = "";
1985 if(avail < need){
1987 if(!usethisprompt){
1988 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1989 need--;
1992 rplstr(pwleadin, sizeof(pwleadin), 1, "");
1993 need--;
1995 if(avail < need){
1996 /* get two spaces from hostleadin */
1997 len = utf8_width(hostleadin);
1998 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1999 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
2000 hostleadin[sizeof(hostleadin)-1] = '\0';
2001 need -= (len - utf8_width(hostleadin));
2003 /* get rid of port */
2004 if(avail < need && strlen(port) > 0){
2005 need -= strlen(port);
2006 port[0] = '\0';
2009 if(avail < need){
2010 len = utf8_width(pwleadin);
2011 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
2012 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD"));
2013 need -= (len - utf8_width(pwleadin));
2017 if(avail < need){
2018 int reduce_to;
2021 * Reduce space for hostname. Best we can do is 6 chars
2022 * with hos...
2024 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
2025 len = strlen(hostname);
2026 strncpy(hostname+reduce_to-3, "...", 4);
2027 need -= (len - strlen(hostname));
2029 if(avail < need && strlen(insecure) > 0){
2030 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
2031 need -= 3;
2032 insecure[strlen(insecure)-4] = ')';
2033 insecure[strlen(insecure)-3] = '\0';
2035 else{
2036 need -= utf8_width(insecure);
2037 insecure[0] = '\0';
2041 if(avail < need){
2042 len = utf8_width(logleadin);
2043 strncpy(logleadin, " ", sizeof(logleadin));
2044 logleadin[sizeof(logleadin)-1] = '\0';
2045 need -= (len - utf8_width(logleadin));
2047 if(avail < need){
2048 reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0;
2049 if(reduce_to)
2050 strncpy(defubuf+reduce_to-3, "...", 4);
2051 else
2052 defubuf[0] = '\0';
2059 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s",
2060 retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
2061 prompt[sizeof(prompt)-1] = '\0';
2063 tmp[0] = '\0';
2064 while(1) {
2065 if(ps_global->ttyo)
2066 mm_login_alt_cue(mb);
2068 save_dont_use = ps_global->dont_use_init_cmds;
2069 ps_global->dont_use_init_cmds = 1;
2070 flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
2071 flags |= OE_KEEP_TRAILING_SPACE;
2072 #ifdef _WINDOWS
2074 char *tmpp;
2075 tmpp = fs_get(NETMAXPASSWD*sizeof(char));
2076 rc = os_login_dialog(mb, user, NETMAXUSER, &tmpp, NETMAXPASSWD, 0, 1,
2077 &preserve_password);
2078 strncpy(tmp, tmpp, sizeof(tmp));
2079 tmp[sizeof(tmp)-1] = '\0';
2080 if(tmpp) fs_give((void **)&tmpp);
2082 #else /* !_WINDOWS */
2083 rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD,
2084 prompt, NULL, help, &flags);
2085 #endif /* !_WINDOWS */
2086 if(rc != 1) *pwd = cpystr(tmp);
2087 ps_global->dont_use_init_cmds = save_dont_use;
2089 if(rc == 3) {
2090 help = help == NO_HELP ? h_oe_passwd : NO_HELP;
2092 else if(rc == 4){
2094 else
2095 break;
2098 if(rc == 1 || !tmp[0]) {
2099 ps_global->user_says_cancel = (rc == 1);
2100 user[0] = '\0';
2101 ps_global->no_newmail_check_from_optionally_enter = 0;
2102 ps_global->in_init_seq = save_in_init;
2103 return;
2106 #ifdef _WINDOWS
2107 nopwpmt:
2108 #endif
2109 /* remember the password for next time */
2110 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
2111 imap_set_passwd(&mm_login_list, *pwd,
2112 altuserforcache ? altuserforcache : user, hostlist,
2113 (mb->sslflag||mb->tlsflag), 0, 0);
2114 #ifdef LOCAL_PASSWD_CACHE
2115 /* if requested, remember it on disk for next session */
2116 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
2117 set_passfile_passwd(ps_global->pinerc, *pwd,
2118 altuserforcache ? altuserforcache : user, hostlist,
2119 (mb->sslflag||mb->tlsflag),
2120 (preserve_password == -1 ? 0
2121 : (preserve_password == 0 ? 2 :1)));
2122 #endif /* LOCAL_PASSWD_CACHE */
2124 ps_global->no_newmail_check_from_optionally_enter = 0;
2128 void
2129 mm_login_alt_cue(NETMBX *mb)
2131 if(ps_global->ttyo){
2132 COLOR_PAIR *lastc;
2134 lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
2135 ps_global->VAR_TITLE_BACK_COLOR,
2136 PSC_REV | PSC_RET);
2138 mark_titlebar_dirty();
2139 PutLine0(0, ps_global->ttyo->screen_cols - 1,
2140 (mb->sslflag||mb->tlsflag) ? "+" : " ");
2142 if(lastc){
2143 (void)pico_set_colorp(lastc, PSC_NONE);
2144 free_color_pair(&lastc);
2147 fflush(stdout);
2152 /*----------------------------------------------------------------------
2153 Receive notification of an error writing to disk
2155 Args: stream -- The stream the error occurred on
2156 errcode -- The system error code (errno)
2157 serious -- Flag indicating error is serious (mail may be lost)
2159 Result: If error is non serious, the stream is marked as having an error
2160 and deletes are disallowed until error clears
2161 If error is serious this goes modal, allowing the user to retry
2162 or get a shell escape to fix the condition. When the condition is
2163 serious it means that mail existing in the mailbox will be lost
2164 if Pine exits without writing, so we try to induce the user to
2165 fix the error, go get someone that can fix the error, or whatever
2166 and don't provide an easy way out.
2167 ----*/
2168 long
2169 mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious)
2171 int i, j;
2172 char *p, *q, *s;
2173 static ESCKEY_S de_opts[] = {
2174 {'r', 'r', "R", "Retry"},
2175 {'f', 'f', "F", "FileBrowser"},
2176 {'s', 's', "S", "ShellPrompt"},
2177 {-1, 0, NULL, NULL}
2179 #define DE_COLS (ps_global->ttyo->screen_cols)
2180 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2182 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2183 #define DE_PMT \
2184 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2185 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2186 #define DE_STR2 \
2187 "The reported error number is %s. The last reported mail error was:"
2188 static char *de_msg[] = {
2189 "Please try to correct the error preventing Alpine from saving your",
2190 "mail folder. For example if the disk is out of space try removing",
2191 "unneeded files. You might also contact your system administrator.",
2193 "Both Alpine's File Browser and an option to enter the system's",
2194 "command prompt are offered to aid in fixing the problem. When",
2195 "you believe the problem is resolved, choose the \"Retry\" option.",
2196 "Be aware that messages may be lost or this folder left in an",
2197 "inaccessible condition if you exit or kill Alpine before the problem",
2198 "is resolved.",
2199 NULL};
2200 static char *de_shell_msg[] = {
2201 "\n\nPlease attempt to correct the error preventing saving of the",
2202 "mail folder. If you do not know how to correct the problem, contact",
2203 "your system administrator. To return to Alpine, type \"exit\".",
2204 NULL};
2206 dprint((0,
2207 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2208 DE_FOLDER(stream), errcode, serious ? "" : "not "));
2209 dprint((0, "***** message: \"%s\"\n\n",
2210 ps_global->last_error ? ps_global->last_error : "?"));
2212 if(!serious) {
2213 sp_set_io_error_on_stream(stream, 1);
2214 return (1) ;
2217 while(1){
2218 /* replace pine's body display with screen full of explanatory text */
2219 ClearLine(2);
2220 PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1)
2221 - strlen(DE_FOLDER(stream)))/2, 0),
2222 DE_STR1, DE_FOLDER(stream));
2223 ClearLine(3);
2224 PutLine1(3, 4, DE_STR2, long2string(errcode));
2226 PutLine0(4, 0, " \"");
2227 removing_leading_white_space(ps_global->last_error);
2228 for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){
2229 for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++)
2230 if(isspace((unsigned char)*q))
2231 s = q;
2233 if(*q && s)
2234 q = s;
2236 while(p < q)
2237 Writechar(*p++, 0);
2239 if(*(p = q)){
2240 ClearLine(++i);
2241 PutLine0(i, 0, " ");
2242 while(*p && isspace((unsigned char)*p))
2243 p++;
2245 else{
2246 Writechar('\"', 0);
2247 CleartoEOLN();
2248 break;
2252 ClearLine(++i);
2253 for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){
2254 ClearLine(i);
2255 PutLine0(i, 0, " ");
2256 Write_to_screen(de_msg[i-j]);
2259 while(i < DE_LINE)
2260 ClearLine(i++);
2262 switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts,
2263 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){
2264 case 'r' : /* Retry! */
2265 ps_global->mangled_screen = 1;
2266 return(0L);
2268 case 'f' : /* File Browser */
2270 char full_filename[MAXPATH+1], filename[MAXPATH+1];
2272 filename[0] = '\0';
2273 build_path(full_filename, ps_global->home_dir, filename,
2274 sizeof(full_filename));
2275 file_lister("DISK ERROR", full_filename, sizeof(full_filename),
2276 filename, sizeof(filename), FALSE, FB_SAVE);
2279 break;
2281 case 's' :
2282 EndInverse();
2283 end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0);
2284 end_tty_driver(ps_global);
2285 for(i = 0; de_shell_msg[i]; i++)
2286 puts(de_shell_msg[i]);
2289 * Don't use our piping mechanism to spawn a subshell here
2290 * since it will the server (thus reentering c-client).
2291 * Bad thing to do.
2293 #ifdef _WINDOWS
2294 #else
2295 system("csh");
2296 #endif
2297 init_tty_driver(ps_global);
2298 init_keyboard(F_ON(F_USE_FK,ps_global));
2299 break;
2302 if(ps_global->redrawer)
2303 (*ps_global->redrawer)();
2308 long
2309 pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host)
2311 long rv = 1L;
2312 char pmt[128];
2314 #ifdef _WINDOWS
2315 mswin_killsplash();
2316 #endif
2318 if(elapsed >= (long)ps_global->tcp_query_timeout){
2319 snprintf(pmt, sizeof(pmt),
2320 _("No reply in %s seconds from server %s. Break connection"),
2321 long2string(elapsed), host);
2322 pmt[sizeof(pmt)-1] = '\0';
2323 if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
2324 ps_global->user_says_cancel = 1;
2325 return(0L);
2329 ps_global->tcptimeout = 0;
2330 return(rv);
2335 * -------------------------------------------------------------
2336 * These are declared in pith/imap.h as mandatory to implement.
2337 * -------------------------------------------------------------
2342 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2344 long
2345 pine_tcptimeout(long int elapsed, long int sincelast, char *host)
2347 long rv = 1L; /* keep trying by default */
2348 unsigned long ch;
2350 ps_global->tcptimeout = 1;
2351 #ifdef DEBUG
2352 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2353 long2string(elapsed), host));
2354 if(debugfile)
2355 fflush(debugfile);
2356 #endif
2358 #ifdef _WINDOWS
2359 mswin_killsplash();
2360 #endif
2362 if(ps_global->noshow_timeout)
2363 return(rv);
2365 if(ps_global->can_interrupt
2366 && ps_global->close_connection_timeout > 0L
2367 && elapsed >= (long)ps_global->tcp_query_timeout
2368 && elapsed >= (long)ps_global->close_connection_timeout){
2369 ps_global->can_interrupt = 0; /* do not return here */
2370 ps_global->read_bail = 0;
2371 ps_global->user_says_cancel = 1;
2372 return 0;
2375 if(!ps_global->ttyo)
2376 return(pine_tcptimeout_noscreen(elapsed, sincelast, host));
2378 suspend_busy_cue();
2381 * Prompt after a minute (since by then things are probably really bad)
2382 * A prompt timeout means "keep trying"...
2384 if(elapsed >= (long)ps_global->tcp_query_timeout){
2385 int clear_inverse;
2387 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2388 if((clear_inverse = !InverseState()) != 0)
2389 StartInverse();
2391 Writechar(BELL, 0);
2393 PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0,
2394 _("No reply in %s seconds from server %s. Break connection?"),
2395 long2string(elapsed), host);
2396 CleartoEOLN();
2397 fflush(stdout);
2398 flush_input();
2399 ch = read_char(7);
2400 if(ps_global->read_bail || ch == 'y' || ch == 'Y'){
2401 ps_global->read_bail = 0;
2402 ps_global->user_says_cancel = 1;
2403 rv = 0L;
2406 if(clear_inverse)
2407 EndInverse();
2409 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2412 if(rv == 1L){ /* just warn 'em something's up */
2413 q_status_message2(SM_ORDER, 0, 0,
2414 _("No reply in %s seconds from server %s. Still Waiting..."),
2415 long2string(elapsed), host);
2416 flush_status_messages(0); /* make sure it's seen */
2419 mark_status_dirty(); /* make sure it gets cleared */
2421 resume_busy_cue((rv == 1) ? 3 : 0);
2422 ps_global->tcptimeout = 0;
2424 return(rv);
2427 QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota)
2429 QUOTALIST *cquota = NULL;
2431 if(pquota){
2432 cquota = mail_newquotalist();
2433 if (pquota->name && *pquota->name)
2434 cquota->name = cpystr(pquota->name);
2435 cquota->usage = pquota->usage;
2436 cquota->limit = pquota->limit;
2437 if (pquota->next)
2438 cquota->next = pine_quotalist_copy(pquota->next);
2440 return cquota;
2444 /* c-client callback to handle quota */
2446 void
2447 pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2449 ps_global->quota = pine_quotalist_copy (pquota);
2453 * C-client callback to handle SSL/TLS certificate validation failures
2455 * Returning 0 means error becomes fatal
2456 * Non-zero means certificate problem is ignored and SSL session is
2457 * established
2459 * We remember the answer and won't re-ask for subsequent open attempts to
2460 * the same hostname.
2462 long
2463 pine_sslcertquery(char *reason, char *host, char *cert)
2465 char tmp[500];
2466 char *unknown = "<unknown>";
2467 long rv = 0L;
2468 STRLIST_S hostlist;
2469 int ok_novalidate = 0, warned = 0;
2471 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2472 host ? host : "?", reason ? reason : "?",
2473 cert ? cert : "?"));
2475 hostlist.name = host ? host : "";
2476 hostlist.next = NULL;
2479 * See if we've been asked about this host before.
2481 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2482 /* we were asked before, did we say Yes? */
2483 if(ok_novalidate)
2484 rv++;
2486 if(rv){
2487 dprint((5,
2488 "sslcertificatequery: approved automatically\n"));
2489 return(rv);
2492 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2495 if(ps_global->ttyo){
2496 SCROLL_S sargs;
2497 STORE_S *in_store, *out_store;
2498 gf_io_t pc, gc;
2499 HANDLE_S *handles = NULL;
2500 int the_answer = 'n';
2502 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
2503 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
2504 goto try_wantto;
2506 so_puts(in_store, "<HTML><P>");
2507 so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server"));
2509 so_puts(in_store, "<P><CENTER>");
2510 so_puts(in_store, host ? host : unknown);
2511 so_puts(in_store, "</CENTER>");
2513 so_puts(in_store, "<P>");
2514 so_puts(in_store, _("The reason for the failure was"));
2516 /* squirrel away details */
2517 if(details_host)
2518 fs_give((void **)&details_host);
2519 if(details_reason)
2520 fs_give((void **)&details_reason);
2521 if(details_cert)
2522 fs_give((void **)&details_cert);
2524 details_host = cpystr(host ? host : unknown);
2525 details_reason = cpystr(reason ? reason : unknown);
2526 details_cert = cpystr(cert ? cert : unknown);
2528 so_puts(in_store, "<P><CENTER>");
2529 snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2530 reason ? reason : unknown);
2531 tmp[sizeof(tmp)-1] = '\0';
2533 so_puts(in_store, tmp);
2534 so_puts(in_store, "</CENTER>");
2536 so_puts(in_store, "<P>");
2537 so_puts(in_store, _("We have not verified the identity of your server. If you ignore this certificate validation problem and continue, you could end up connecting to an imposter server."));
2539 so_puts(in_store, "<P>");
2540 so_puts(in_store, _("If the certificate validation failure was expected and permanent you may avoid seeing this warning message in the future by adding the option"));
2542 so_puts(in_store, "<P><CENTER>");
2543 so_puts(in_store, "/novalidate-cert");
2544 so_puts(in_store, "</CENTER>");
2546 so_puts(in_store, "<P>");
2547 so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2549 so_puts(in_store, "<P><CENTER>");
2550 so_puts(in_store, host ? host : unknown);
2551 so_puts(in_store, "</CENTER>");
2553 so_puts(in_store, "<P>");
2554 so_puts(in_store, _("in your configuration, replace those characters with"));
2556 so_puts(in_store, "<P><CENTER>");
2557 so_puts(in_store, host ? host : unknown);
2558 so_puts(in_store, "/novalidate-cert");
2559 so_puts(in_store, "</CENTER>");
2561 so_puts(in_store, "<P>");
2562 so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2564 so_seek(in_store, 0L, 0);
2565 init_handles(&handles);
2566 gf_filter_init();
2567 gf_link_filter(gf_html2plain,
2568 gf_html2plain_opt(NULL,
2569 ps_global->ttyo->screen_cols, non_messageview_margin(),
2570 &handles, NULL, GFHP_LOCAL_HANDLES));
2571 gf_set_so_readc(&gc, in_store);
2572 gf_set_so_writec(&pc, out_store);
2573 gf_pipe(gc, pc);
2574 gf_clear_so_writec(out_store);
2575 gf_clear_so_readc(in_store);
2577 memset(&sargs, 0, sizeof(SCROLL_S));
2578 sargs.text.handles = handles;
2579 sargs.text.text = so_text(out_store);
2580 sargs.text.src = CharStar;
2581 sargs.text.desc = _("help text");
2582 sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2583 sargs.proc.tool = answer_cert_failure;
2584 sargs.proc.data.p = (void *)&the_answer;
2585 sargs.keys.menu = &ans_certquery_keymenu;
2586 /* don't want to re-enter c-client */
2587 sargs.quell_newmail = 1;
2588 setbitmap(sargs.keys.bitmap);
2589 sargs.help.text = h_tls_validation_failure;
2590 sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
2592 scrolltool(&sargs);
2594 if(the_answer == 'y')
2595 rv++;
2596 else if(the_answer == 'n')
2597 ps_global->user_says_cancel = 1;
2599 ps_global->mangled_screen = 1;
2600 ps_global->painted_body_on_startup = 0;
2601 ps_global->painted_footer_on_startup = 0;
2602 so_give(&in_store);
2603 so_give(&out_store);
2604 free_handles(&handles);
2605 if(details_host)
2606 fs_give((void **)&details_host);
2607 if(details_reason)
2608 fs_give((void **)&details_reason);
2609 if(details_cert)
2610 fs_give((void **)&details_cert);
2612 else{
2614 * If screen hasn't been initialized yet, use want_to.
2616 try_wantto:
2617 memset((void *)tmp, 0, sizeof(tmp));
2618 strncpy(tmp,
2619 reason ? reason : _("SSL/TLS certificate validation failure"),
2620 sizeof(tmp));
2621 tmp[sizeof(tmp)-1] = '\0';
2622 strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
2624 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
2625 rv++;
2628 if(rv == 0)
2629 q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
2630 host ? host : unknown);
2632 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
2634 dprint((5, "sslcertificatequery: %s\n",
2635 rv ? "approved" : "rejected"));
2637 return(rv);
2641 char *
2642 pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
2644 char buf[MAILTMPLEN];
2646 if((can_access(mulname, ACCESS_EXISTS) == 0)
2647 || !(can_access(name, ACCESS_EXISTS) == 0))
2648 return(mulname);
2650 snprintf(buf, sizeof(buf),
2651 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2652 last_cmpnt(name),
2653 strlen(last_cmpnt(name)) > 15 ? "..." : "");
2654 buf[sizeof(buf)-1] = '\0';
2655 if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
2656 rename_file(name, mulname);
2657 return(mulname);
2662 url_local_certdetails(char *url)
2664 if(!struncmp(url, "x-alpine-cert:", 14)){
2665 STORE_S *store;
2666 SCROLL_S sargs;
2667 char *folded;
2669 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
2670 q_status_message(SM_ORDER | SM_DING, 7, 10,
2671 _("Error allocating space for details."));
2672 return(0);
2675 so_puts(store, _("Host given by user:\n\n "));
2676 so_puts(store, details_host);
2677 so_puts(store, _("\n\nReason for failure:\n\n "));
2678 so_puts(store, details_reason);
2679 so_puts(store, _("\n\nCertificate being verified:\n\n"));
2680 folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
2681 so_puts(store, folded);
2682 fs_give((void **)&folded);
2683 so_puts(store, "\n");
2685 memset(&sargs, 0, sizeof(SCROLL_S));
2686 sargs.text.text = so_text(store);
2687 sargs.text.src = CharStar;
2688 sargs.text.desc = _("Details");
2689 sargs.bar.title = _("CERT VALIDATION DETAILS");
2690 sargs.help.text = NO_HELP;
2691 sargs.help.title = NULL;
2692 sargs.quell_newmail = 1;
2693 sargs.help.text = h_tls_failure_details;
2694 sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
2696 scrolltool(&sargs);
2698 so_give(&store); /* free resources associated with store */
2699 ps_global->mangled_screen = 1;
2700 return(1);
2703 return(0);
2708 * C-client callback to handle SSL/TLS certificate validation failures
2710 void
2711 pine_sslfailure(char *host, char *reason, long unsigned int flags)
2713 SCROLL_S sargs;
2714 STORE_S *store;
2715 int the_answer = 'n', indent, len, cols;
2716 char buf[500], buf2[500];
2717 char *folded;
2718 char *hst = host ? host : "<unknown>";
2719 char *rsn = reason ? reason : "<unknown>";
2720 char *notls = "/notls";
2721 STRLIST_S hostlist;
2722 int ok_novalidate = 0, warned = 0;
2725 dprint((1, "sslfailure: host=%s reason=%s\n",
2726 hst ? hst : "?",
2727 rsn ? rsn : "?"));
2729 if(flags & NET_SILENT)
2730 return;
2732 hostlist.name = host ? host : "";
2733 hostlist.next = NULL;
2736 * See if we've been told about this host before.
2738 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2739 /* we were told already */
2740 if(warned){
2741 snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn);
2742 buf[sizeof(buf)-1] = '\0';
2743 mm_log(buf, ERROR);
2744 return;
2748 cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2749 cols--;
2751 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS)))
2752 return;
2754 strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf));
2755 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2756 so_puts(store, folded);
2757 fs_give((void **)&folded);
2758 so_puts(store, "\n");
2760 if((len=strlen(hst)) <= cols){
2761 if((indent=((cols-len)/2)) > 0)
2762 so_puts(store, repeat_char(indent, SPACE));
2764 so_puts(store, hst);
2765 so_puts(store, "\n");
2767 else{
2768 strncpy(buf, hst, sizeof(buf));
2769 buf[sizeof(buf)-1] = '\0';
2770 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2771 so_puts(store, folded);
2772 fs_give((void **)&folded);
2775 so_puts(store, "\n");
2777 strncpy(buf, _("The reason for the failure was"), sizeof(buf));
2778 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2779 so_puts(store, folded);
2780 fs_give((void **)&folded);
2781 so_puts(store, "\n");
2783 if((len=strlen(rsn)) <= cols){
2784 if((indent=((cols-len)/2)) > 0)
2785 so_puts(store, repeat_char(indent, SPACE));
2787 so_puts(store, rsn);
2788 so_puts(store, "\n");
2790 else{
2791 strncpy(buf, rsn, sizeof(buf));
2792 buf[sizeof(buf)-1] = '\0';
2793 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2794 so_puts(store, folded);
2795 fs_give((void **)&folded);
2798 so_puts(store, "\n");
2800 strncpy(buf, _("This is just an informational message. With the current setup, SSL/TLS will not work. If this error re-occurs every time you run Alpine, your current setup is not compatible with the configuration of your mail server. You may want to add the option"), sizeof(buf));
2801 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2802 so_puts(store, folded);
2803 fs_give((void **)&folded);
2804 so_puts(store, "\n");
2806 if((len=strlen(notls)) <= cols){
2807 if((indent=((cols-len)/2)) > 0)
2808 so_puts(store, repeat_char(indent, SPACE));
2810 so_puts(store, notls);
2811 so_puts(store, "\n");
2813 else{
2814 strncpy(buf, notls, sizeof(buf));
2815 buf[sizeof(buf)-1] = '\0';
2816 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2817 so_puts(store, folded);
2818 fs_give((void **)&folded);
2821 so_puts(store, "\n");
2823 strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2824 sizeof(buf));
2825 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2826 so_puts(store, folded);
2827 fs_give((void **)&folded);
2828 so_puts(store, "\n");
2830 if((len=strlen(hst)) <= cols){
2831 if((indent=((cols-len)/2)) > 0)
2832 so_puts(store, repeat_char(indent, SPACE));
2834 so_puts(store, hst);
2835 so_puts(store, "\n");
2837 else{
2838 strncpy(buf, hst, sizeof(buf));
2839 buf[sizeof(buf)-1] = '\0';
2840 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2841 so_puts(store, folded);
2842 fs_give((void **)&folded);
2845 so_puts(store, "\n");
2847 strncpy(buf, _("in your configuration, replace those characters with"),
2848 sizeof(buf));
2849 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2850 so_puts(store, folded);
2851 fs_give((void **)&folded);
2852 so_puts(store, "\n");
2854 snprintf(buf2, sizeof(buf2), "%s%s", hst, notls);
2855 buf2[sizeof(buf2)-1] = '\0';
2856 if((len=strlen(buf2)) <= cols){
2857 if((indent=((cols-len)/2)) > 0)
2858 so_puts(store, repeat_char(indent, SPACE));
2860 so_puts(store, buf2);
2861 so_puts(store, "\n");
2863 else{
2864 strncpy(buf, buf2, sizeof(buf));
2865 buf[sizeof(buf)-1] = '\0';
2866 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2867 so_puts(store, folded);
2868 fs_give((void **)&folded);
2871 so_puts(store, "\n");
2873 if(ps_global->ttyo){
2874 strncpy(buf, _("Type RETURN to continue."), sizeof(buf));
2875 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2876 so_puts(store, folded);
2877 fs_give((void **)&folded);
2880 memset(&sargs, 0, sizeof(SCROLL_S));
2881 sargs.text.text = so_text(store);
2882 sargs.text.src = CharStar;
2883 sargs.text.desc = _("help text");
2884 sargs.bar.title = _("SSL/TLS FAILURE");
2885 sargs.proc.tool = answer_cert_failure;
2886 sargs.proc.data.p = (void *)&the_answer;
2887 sargs.keys.menu = &ans_certfail_keymenu;
2888 setbitmap(sargs.keys.bitmap);
2889 /* don't want to re-enter c-client */
2890 sargs.quell_newmail = 1;
2891 sargs.help.text = h_tls_failure;
2892 sargs.help.title = _("HELP FOR TLS/SSL FAILURE");
2894 if(ps_global->ttyo)
2895 scrolltool(&sargs);
2896 else{
2897 char **q, **qp;
2898 char *p;
2899 unsigned char c;
2900 int cnt = 0;
2903 * The screen isn't initialized yet, which should mean that this
2904 * is the result of a -p argument. Display_args_err knows how to deal
2905 * with the uninitialized screen, so we mess with the data to get it
2906 * in shape for display_args_err. This is pretty hacky.
2909 so_seek(store, 0L, 0); /* rewind */
2910 /* count the lines */
2911 while(so_readc(&c, store))
2912 if(c == '\n')
2913 cnt++;
2915 qp = q = (char **)fs_get((cnt+1) * sizeof(char *));
2916 memset(q, 0, (cnt+1) * sizeof(char *));
2918 so_seek(store, 0L, 0); /* rewind */
2919 p = buf;
2920 while(so_readc(&c, store)){
2921 if(c == '\n'){
2922 *p = '\0';
2923 *qp++ = cpystr(buf);
2924 p = buf;
2926 else
2927 *p++ = c;
2930 display_args_err(NULL, q, 0);
2931 free_list_array(&q);
2934 ps_global->mangled_screen = 1;
2935 ps_global->painted_body_on_startup = 0;
2936 ps_global->painted_footer_on_startup = 0;
2937 so_give(&store);
2939 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1);
2944 answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2946 int rv = 1;
2948 ps_global->next_screen = SCREEN_FUN_NULL;
2950 switch(cmd){
2951 case MC_YES :
2952 *(int *)(sparms->proc.data.p) = 'y';
2953 break;
2955 case MC_NO :
2956 *(int *)(sparms->proc.data.p) = 'n';
2957 break;
2959 default:
2960 alpine_panic("Unexpected command in answer_cert_failure");
2961 break;
2964 return(rv);
2969 oauth2_auth_answer(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2971 int rv = 1, rc;
2972 AUTH_CODE_S user;
2973 int q_line, flags;
2974 /* TRANSLATORS: user needs to input an access code from the server */
2975 char *accesscodelabel = _("Copy and Paste Access Code");
2976 char token[MAILTMPLEN], prompt[MAILTMPLEN];
2978 ps_global->next_screen = SCREEN_FUN_NULL;
2980 token[0] = '\0';
2981 switch(cmd){
2982 case MC_YES :
2983 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
2984 flags = OE_APPEND_CURRENT;
2985 sprintf(prompt, "%s: ", accesscodelabel);
2986 do {
2987 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
2988 prompt, NULL, NO_HELP, &flags);
2989 } while (rc != 0 && rc != 1);
2990 user.code = rc == 0 ? cpystr(token) : NULL;
2991 user.answer = 'e';
2992 rv = rc == 1 ? 0 : 1;
2993 break;
2995 case MC_NO :
2996 user.code = NULL;
2997 user.answer = 'e';
2998 break;
3000 default:
3001 alpine_panic("Unexpected command in oauth2_auth_answer");
3002 break;
3004 *(AUTH_CODE_S *) sparms->proc.data.p = user;
3005 return(rv);
3009 /*----------------------------------------------------------------------
3010 This can be used to prevent the flickering of the check_cue char
3011 caused by numerous (5000+) fetches by c-client. Right now, the only
3012 practical use found is newsgroup subsciption.
3014 check_cue_display will check if this global is set, and won't clear
3015 the check_cue_char if set.
3016 ----*/
3017 void
3018 set_read_predicted(int i)
3020 ps_global->read_predicted = i==1;
3021 #ifdef _WINDOWS
3022 if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global))
3023 check_cue_display(" ");
3024 #endif
3028 /*----------------------------------------------------------------------
3029 Exported method to retrieve logged in user name associated with stream
3031 Args: host -- host to find associated login name with.
3033 Result:
3034 ----*/
3035 void *
3036 pine_block_notify(int reason, void *data)
3038 switch(reason){
3039 case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */
3040 break;
3042 case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */
3043 break;
3045 case BLOCK_TCPWRITE: /* blocked on TCP write */
3046 case BLOCK_FILELOCK: /* blocked on file locking */
3047 #ifdef _WINDOWS
3048 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3049 check_cue_display(">");
3051 mswin_setcursor(MSWIN_CURSOR_BUSY);
3052 #endif
3053 break;
3055 case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */
3056 case BLOCK_TCPOPEN: /* blocked on TCP open */
3057 case BLOCK_TCPREAD: /* blocked on TCP read */
3058 case BLOCK_TCPCLOSE: /* blocked on TCP close */
3059 #ifdef _WINDOWS
3060 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3061 check_cue_display("<");
3063 mswin_setcursor(MSWIN_CURSOR_BUSY);
3064 #endif
3065 break;
3067 default :
3068 case BLOCK_NONE: /* not blocked */
3069 #ifdef _WINDOWS
3070 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3071 check_cue_display(" ");
3072 #endif
3073 break;
3077 return(NULL);
3081 void
3082 mm_expunged_current(long unsigned int rawno)
3084 /* expunged something we're viewing? */
3085 if(!ps_global->expunge_in_progress
3086 && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno))
3087 && (ps_global->prev_screen == mail_view_screen
3088 || ps_global->prev_screen == attachment_screen))){
3089 ps_global->next_screen = mail_index_screen;
3090 q_status_message(SM_ORDER | SM_DING , 3, 3,
3091 "Message you were viewing is gone!");
3096 #ifdef PASSFILE
3099 * Specific functions to support caching username/passwd/host
3100 * triples on disk for use from one session to the next...
3103 #define FIRSTCH 0x20
3104 #define LASTCH 0x7e
3105 #define TABSZ (LASTCH - FIRSTCH + 1)
3107 static int xlate_key;
3111 * xlate_in() - xlate_in the given character
3113 char
3114 xlate_in(int c)
3116 register int eti;
3118 eti = xlate_key;
3119 if((c >= FIRSTCH) && (c <= LASTCH)){
3120 eti += (c - FIRSTCH);
3121 eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0;
3122 return((xlate_key = eti) + FIRSTCH);
3124 else
3125 return(c);
3130 * xlate_out() - xlate_out the given character
3132 char
3133 xlate_out(char c)
3135 register int dti;
3136 register int xch;
3138 if((c >= FIRSTCH) && (c <= LASTCH)){
3139 xch = c - (dti = xlate_key);
3140 xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0;
3141 dti = (xch - FIRSTCH) + dti;
3142 dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0;
3143 xlate_key = dti;
3144 return(xch);
3146 else
3147 return(c);
3149 #endif /* PASSFILE */
3152 #ifdef LOCAL_PASSWD_CACHE
3155 int
3156 line_get(char *tmp, size_t len, char **textp)
3158 char *s;
3160 tmp[0] = '\0';
3161 if (*textp == NULL
3162 || (s = strchr(*textp, '\n')) == NULL
3163 || (s - *textp) > len - 1)
3164 return 0;
3166 *s = '\0';
3167 if(*(s-1) == '\r')
3168 *(s-1) = '\0';
3170 snprintf(tmp, len, "%s\n", *textp);
3171 tmp[len-1] = '\0';
3172 *textp = s+1;
3174 return 1;
3177 * For UNIX:
3178 * Passfile lines are
3180 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3182 * In pine4.40 and before there was no orig_hostname, and there still isn't
3183 * if it is the same as hostname.
3185 * else for WINDOWS:
3186 * Use Windows credentials. The TargetName of the credential is
3187 * UWash_Alpine_<hostname:port>\tuser\taltflag
3188 * and the blob consists of
3189 * passwd\torighost (if different from host)
3191 * We don't use anything fancy we just copy out all the credentials which
3192 * begin with TNAME and put them into our cache, so we don't lookup based
3193 * on the TargetName or anything like that. That was so we could re-use
3194 * the existing code and so that orighost data could be easily used.
3197 read_passfile(pinerc, l)
3198 char *pinerc;
3199 MMLOGIN_S **l;
3201 #ifdef WINCRED
3202 # if (WINCRED > 0)
3203 LPCTSTR lfilter = TEXT(TNAMESTAR);
3204 DWORD count, k;
3205 PCREDENTIAL *pcred;
3206 char *tmp, *blob, *target = NULL;
3207 char *host, *user, *sflags, *passwd, *orighost;
3208 char *ui[5];
3209 int i, j;
3211 if(using_passfile == 0)
3212 return(using_passfile);
3214 if(!g_CredInited){
3215 if(init_wincred_funcs() != 1){
3216 using_passfile = 0;
3217 return(using_passfile);
3221 dprint((9, "read_passfile\n"));
3223 using_passfile = 1;
3225 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
3226 if(pcred){
3227 for(k = 0; k < count; k++){
3229 host = user = sflags = passwd = orighost = NULL;
3230 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3232 target = lptstr_to_utf8(pcred[k]->TargetName);
3233 tmp = srchstr(target, TNAME);
3235 if(tmp){
3236 tmp += strlen(TNAME);
3237 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3238 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3239 ; /* find end of data */
3241 if(tmp[i])
3242 tmp[i++] = '\0'; /* tie off data */
3245 host = ui[0];
3246 user = ui[1];
3247 sflags = ui[2];
3250 blob = (char *) pcred[k]->CredentialBlob;
3251 if(blob){
3252 for(i = 0, j = 3; blob[i] && j < 5; j++){
3253 for(ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++)
3254 ; /* find end of data */
3256 if(blob[i])
3257 blob[i++] = '\0'; /* tie off data */
3260 passwd = ui[3];
3261 orighost = ui[4];
3264 if(passwd && host && user){ /* valid field? */
3265 STRLIST_S hostlist[2];
3266 int flags;
3268 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3269 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3270 hostlist[0].name = host;
3271 if(orighost){
3272 hostlist[0].next = &hostlist[1];
3273 hostlist[1].name = orighost;
3274 hostlist[1].next = NULL;
3276 else{
3277 hostlist[0].next = NULL;
3280 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3283 if(target)
3284 fs_give((void **) &target);
3287 g_CredFree((PVOID) pcred);
3291 return(1);
3293 # else /* old windows */
3294 using_passfile = 0;
3295 return(0);
3296 # endif
3298 #elif APPLEKEYCHAIN
3300 char target[MAILTMPLEN];
3301 char *tmp, *host, *user, *sflags, *passwd, *orighost;
3302 char *ui[5];
3303 int i, j, k, rc;
3304 SecKeychainAttributeList attrList;
3305 SecKeychainSearchRef searchRef = NULL;
3306 SecKeychainAttribute attrs[] = {
3307 { kSecAccountItemAttr, strlen(TNAME), TNAME }
3310 if(using_passfile == 0)
3311 return(using_passfile);
3313 dprint((9, "read_passfile\n"));
3316 /* search for only our items in the keychain */
3317 attrList.count = 1;
3318 attrList.attr = attrs;
3320 using_passfile = 1;
3321 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
3322 kSecGenericPasswordItemClass,
3323 &attrList,
3324 &searchRef))){
3325 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3326 if(searchRef){
3327 SecKeychainItemRef itemRef = NULL;
3328 SecKeychainAttributeInfo info;
3329 SecKeychainAttributeList *attrList = NULL;
3330 UInt32 blength = 0;
3331 char *blob = NULL;
3332 char *blobcopy = NULL; /* NULL terminated copy */
3334 UInt32 tags[] = {kSecAccountItemAttr,
3335 kSecServiceItemAttr};
3336 UInt32 formats[] = {0,0};
3338 dprint((10, "read_passfile: searchRef not NULL\n"));
3339 info.count = 2;
3340 info.tag = tags;
3341 info.format = formats;
3344 * Go through each item we found and put it
3345 * into our list.
3347 while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){
3348 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3349 rc = SecKeychainItemCopyAttributesAndData(itemRef,
3350 &info, NULL,
3351 &attrList,
3352 &blength,
3353 (void **) &blob);
3354 if(rc == 0 && attrList){
3355 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count));
3357 blobcopy = (char *) fs_get((blength + 1) * sizeof(char));
3358 strncpy(blobcopy, (char *) blob, blength);
3359 blobcopy[blength] = '\0';
3362 * I'm not real clear on how this works. It seems to be
3363 * necessary to combine the attributes from two passes
3364 * (attrList->count == 2) in order to get the full set
3365 * of attributes we inserted into the keychain in the
3366 * first place. So, we reset host...orighost outside of
3367 * the following for loop, not inside.
3369 host = user = sflags = passwd = orighost = NULL;
3370 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3372 for(k = 0; k < attrList->count; k++){
3374 if(attrList->attr[k].length){
3375 strncpy(target,
3376 (char *) attrList->attr[k].data,
3377 MIN(attrList->attr[k].length,sizeof(target)));
3378 target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0';
3381 tmp = target;
3382 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3383 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3384 ; /* find end of data */
3386 if(tmp[i])
3387 tmp[i++] = '\0'; /* tie off data */
3390 if(ui[0])
3391 host = ui[0];
3393 if(ui[1])
3394 user = ui[1];
3396 if(ui[2])
3397 sflags = ui[2];
3399 for(i = 0, j = 3; blobcopy[i] && j < 5; j++){
3400 for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++)
3401 ; /* find end of data */
3403 if(blobcopy[i])
3404 blobcopy[i++] = '\0'; /* tie off data */
3407 if(ui[3])
3408 passwd = ui[3];
3410 if(ui[4])
3411 orighost = ui[4];
3413 dprint((10, "read_passfile: host=%s user=%s sflags=%s passwd=%s orighost=%s\n", host?host:"", user?user:"", sflags?sflags:"", passwd?passwd:"", orighost?orighost:""));
3416 if(passwd && host && user){ /* valid field? */
3417 STRLIST_S hostlist[2];
3418 int flags;
3420 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3421 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3422 hostlist[0].name = host;
3423 if(orighost){
3424 hostlist[0].next = &hostlist[1];
3425 hostlist[1].name = orighost;
3426 hostlist[1].next = NULL;
3428 else{
3429 hostlist[0].next = NULL;
3432 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3435 if(blobcopy)
3436 fs_give((void **) & blobcopy);
3438 SecKeychainItemFreeAttributesAndData(attrList, (void *) blob);
3440 else{
3441 using_passfile = 0;
3442 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc));
3445 CFRelease(itemRef);
3446 itemRef = NULL;
3449 CFRelease(searchRef);
3451 else{
3452 using_passfile = 0;
3453 dprint((10, "read_passfile: searchRef NULL\n"));
3456 else{
3457 using_passfile = 0;
3458 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc));
3461 return(using_passfile);
3463 #else /* PASSFILE */
3465 char tmp[MAILTMPLEN], *ui[5];
3466 int i, j, n, rv = 0;
3467 size_t len = 0;
3468 char *tmptext = NULL;
3469 struct stat sbuf;
3470 #ifdef SMIME
3471 char tmp2[MAILTMPLEN];
3472 char *text = NULL, *text2 = NULL;
3473 int encrypted = 0;
3474 #endif /* SMIME */
3475 FILE *fp;
3477 if(using_passfile == 0)
3478 return(using_passfile);
3480 dprint((9, "read_passfile\n"));
3482 /* if there's no password to read, bag it!! */
3483 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){
3484 using_passfile = 0;
3485 return(using_passfile);
3488 #ifndef SMIME
3489 if(our_stat(tmp, &sbuf) == 0)
3490 len = sbuf.st_size;
3491 fp = our_fopen(tmp, "rb"); /* reopen to read data */
3492 #else
3493 /* the next call initializes the key/certificate pair used to
3494 * encrypt and decrypt a password file. The details of how this is
3495 * done is in the file pith/smime.c. During this setup we might call
3496 * smime_init(), but no matter what happens we must call smime_deinit()
3497 * there. The reason why this is so is because we can not assume that
3498 * the .pinerc file has been read by this time, so this code might not
3499 * know about the ps_global->smime structure or any of its components,
3500 * and it shouldn't because it only needs ps_global->pwdcert, so
3501 * do not init smime here, because the .pinerc might not have been
3502 * read and we do not really know where the keys and certificates really
3503 * are.
3504 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3505 * it is called for the first time and there are certificates at all,
3506 * or when it is called after the first time and the user refuses to
3507 * create a self-signed certificate. In this situation we will just
3508 * let the user live in an insecure world, but no more passwords will
3509 * be saved in the password file, and only those found there will be used.
3511 tmp2[0] = '\0';
3512 fgets(tmp2, sizeof(tmp2), fp);
3513 fclose(fp);
3514 if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
3515 /* there is an already existing password file, that is not encrypted
3516 * and there is no key to encrypt it yet, go again through setup_pwdcert
3517 * and encrypt it now.
3519 if(tmp2[0]){ /* not empty, UNencrypted password file */
3520 if(ps_global->pwdcert == NULL)
3521 rv = setup_pwdcert(&ps_global->pwdcert);
3522 if((rv == 0 || rv == -5) && ps_global->pwdcert == NULL)
3523 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3524 if(ps_global->pwdcert == NULL){
3525 q_status_message(SM_ORDER, 3, 3,
3526 " Failed to create private key. Using UNencrypted Password file. ");
3527 save_password = 0;
3529 else{
3530 if(rv == 1){
3531 q_status_message(SM_ORDER, 3, 3,
3532 " Failed to unlock private key. Using UNencrypted Password file. ");
3533 save_password = 0; /* do not save more passwords */
3536 if(ps_global->pwdcert != NULL
3537 && encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
3538 encrypted++;
3541 else {
3542 if(ps_global->pwdcert == NULL)
3543 rv = setup_pwdcert(&ps_global->pwdcert);
3544 encrypted++;
3548 * if password file is encrypted we attempt to decrypt. We ask the
3549 * user for the password to unlock the password file. If the user
3550 * enters the password and it unlocks the file, use it and keep saving
3551 * passwords in it. If the user enters the wrong passwords and does
3552 * not unlock it, we will not see that here, but in decrypt_file, so
3553 * the only other possibility is that the user cancels. In that case
3554 * we will see i == -1. In that case, we will let the user attempt
3555 * manual login to the server they want to login, but passwords will
3556 * not be saved so that the password file will not be saved
3557 * unencrypted and rewritten again.
3559 if(encrypted){
3560 text = text2 = decrypt_file((char *)tmp, &i, (PERSONAL_CERT *)ps_global->pwdcert);
3561 len = text2 ? strlen(text2) : 0;
3562 switch(i){
3563 case -2: using_passfile = 0;
3564 break;
3566 case 1 : save_password = 1;
3567 using_passfile = 1;
3568 break;
3570 case -1: save_password = 0;
3571 using_passfile = 1;
3572 break;
3574 default: break;
3577 #endif /* SMIME */
3579 if(using_passfile == 0){
3580 #ifdef SMIME
3581 if(text) fs_give((void **)&text);
3582 #endif /* SMIME */
3583 return using_passfile;
3586 if(len > 0){
3587 tmptext = fs_get(len + 1);
3588 #ifdef SMIME
3589 for(n = 0; encrypted ? line_get(tmptext, len + 1, &text2)
3590 : (fgets(tmptext, len+1, fp) != NULL); n++){
3591 #else /* SMIME */
3592 for(n = 0; fgets(tmptext, len+1, fp); n++){
3593 #endif /* SMIME */
3594 /*** do any necessary DEcryption here ***/
3595 xlate_key = n;
3596 for(i = 0; tmptext[i]; i++)
3597 tmptext[i] = xlate_out(tmptext[i]);
3599 if(i && tmptext[i-1] == '\n')
3600 tmptext[i-1] = '\0'; /* blast '\n' */
3602 dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
3603 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3604 for(i = 0, j = 0; tmptext[i] && j < 5; j++){
3605 for(ui[j] = &tmptext[i]; tmptext[i] && tmptext[i] != '\t'; i++)
3606 ; /* find end of data */
3608 if(tmptext[i])
3609 tmptext[i++] = '\0'; /* tie off data */
3612 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3613 if(ui[0] && ui[1] && ui[2]){ /* valid field? */
3614 STRLIST_S hostlist[2];
3615 char *s = ui[3] ? strchr(ui[3], PWDAUTHSEP) : NULL;
3616 int flags = ui[3] ? atoi(s ? ++s : ui[3]) : 0;
3618 hostlist[0].name = ui[2];
3619 if(ui[4]){
3620 hostlist[0].next = &hostlist[1];
3621 hostlist[1].name = ui[4];
3622 hostlist[1].next = NULL;
3624 else{
3625 hostlist[0].next = NULL;
3628 imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0);
3633 if (tmptext) fs_give((void **) &tmptext);
3634 #ifdef SMIME
3635 if (text) fs_give((void **)&text);
3636 #else /* SMIME */
3637 fclose(fp);
3638 #endif /* SMIME */
3639 return(1);
3640 #endif /* PASSFILE */
3645 void
3646 write_passfile(pinerc, l)
3647 char *pinerc;
3648 MMLOGIN_S *l;
3650 char *authend, *authtype;
3651 #ifdef WINCRED
3652 # if (WINCRED > 0)
3653 char target[4*MAILTMPLEN];
3654 char blob[4*MAILTMPLEN];
3655 CREDENTIAL cred;
3656 LPTSTR ltarget = 0;
3658 if(using_passfile == 0)
3659 return;
3661 dprint((9, "write_passfile\n"));
3663 for(; l; l = l->next){
3664 authtype = l->passwd;
3665 authend = strchr(l->passwd, PWDAUTHSEP);
3666 if(authend != NULL){
3667 *authend = '\0';
3668 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3669 *authend = PWDAUTHSEP;
3671 else
3672 sprintf(blob, "%d", l->altflag);
3674 snprintf(target, sizeof(target), "%s%s\t%s\t%s",
3675 TNAME,
3676 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3677 l->user ? l->user : "",
3678 blob);
3679 ltarget = utf8_to_lptstr((LPSTR) target);
3681 if(ltarget){
3682 snprintf(blob, sizeof(blob), "%s%s%s",
3683 l->passwd ? l->passwd : "",
3684 (l->hosts && l->hosts->next && l->hosts->next->name)
3685 ? "\t" : "",
3686 (l->hosts && l->hosts->next && l->hosts->next->name)
3687 ? l->hosts->next->name : "");
3688 memset((void *) &cred, 0, sizeof(cred));
3689 cred.Flags = 0;
3690 cred.Type = CRED_TYPE_GENERIC;
3691 cred.TargetName = ltarget;
3692 cred.CredentialBlobSize = strlen(blob)+1;
3693 cred.CredentialBlob = (LPBYTE) &blob;
3694 cred.Persist = CRED_PERSIST_ENTERPRISE;
3695 g_CredWriteW(&cred, 0);
3697 fs_give((void **) &ltarget);
3700 #endif /* WINCRED > 0 */
3702 #elif APPLEKEYCHAIN
3703 int rc;
3704 char target[4*MAILTMPLEN];
3705 char blob[4*MAILTMPLEN];
3706 SecKeychainItemRef itemRef = NULL;
3708 if(using_passfile == 0)
3709 return;
3711 dprint((9, "write_passfile\n"));
3713 for(; l; l = l->next){
3714 authtype = l->passwd;
3715 authend = strchr(l->passwd, PWDAUTHSEP);
3716 if(authend != NULL){
3717 *authend = '\0';
3718 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3719 *authend = PWDAUTHSEP;
3721 else
3722 sprintf(blob, "%d", l->altflag);
3724 snprintf(target, sizeof(target), "%s\t%s\t%s",
3725 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3726 l->user ? l->user : "",
3727 blob);
3729 snprintf(blob, sizeof(blob), "%s%s%s",
3730 l->passwd ? l->passwd : "",
3731 (l->hosts && l->hosts->next && l->hosts->next->name)
3732 ? "\t" : "",
3733 (l->hosts && l->hosts->next && l->hosts->next->name)
3734 ? l->hosts->next->name : "");
3736 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob));
3738 rc = SecKeychainAddGenericPassword(NULL,
3739 strlen(target), target,
3740 strlen(TNAME), TNAME,
3741 strlen(blob), blob,
3742 NULL);
3743 if(rc==0){
3744 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3746 else{
3747 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc));
3750 if(rc == errSecDuplicateItem){
3751 /* fix existing entry */
3752 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3753 itemRef = NULL;
3754 if(!(rc=SecKeychainFindGenericPassword(NULL,
3755 strlen(target), target,
3756 strlen(TNAME), TNAME,
3757 NULL, NULL,
3758 &itemRef)) && itemRef){
3760 rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob);
3761 if(!rc){
3762 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc));
3765 else{
3766 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc));
3771 #else /* PASSFILE */
3772 char tmp[4*MAILTMPLEN], blob[4*MAILTMPLEN];
3773 int i, n;
3774 FILE *fp;
3775 #ifdef SMIME
3776 char *text = NULL, tmp2[4*MAILTMPLEN];
3777 int len = 0;
3778 #endif
3780 if(using_passfile == 0)
3781 return;
3783 dprint((9, "write_passfile\n"));
3785 /* if there's no passfile to read, bag it!! */
3786 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "wb"))){
3787 using_passfile = 0;
3788 return;
3791 #ifdef SMIME
3792 strncpy(tmp2, tmp, sizeof(tmp2));
3793 tmp2[sizeof(tmp2)-1] = '\0';
3794 #endif /* SMIME */
3796 for(n = 0; l; l = l->next, n++){
3797 authtype = l->passwd;
3798 authend = strchr(l->passwd, PWDAUTHSEP);
3799 if(authend != NULL){
3800 *authend = '\0';
3801 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3802 *authend = PWDAUTHSEP;
3804 else
3805 sprintf(blob, "%d", l->altflag);
3807 /*** do any necessary ENcryption here ***/
3808 snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%s%s%s\n", l->passwd, l->user,
3809 l->hosts->name, blob,
3810 (l->hosts->next && l->hosts->next->name) ? "\t" : "",
3811 (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
3812 : "");
3813 dprint((10, "write_passfile: %s", tmp ? tmp : "?"));
3814 xlate_key = n;
3815 for(i = 0; tmp[i]; i++)
3816 tmp[i] = xlate_in(tmp[i]);
3818 #ifdef SMIME
3819 fs_resize((void **)&text, (len + strlen(tmp) + 1)*sizeof(char));
3820 text[len] = '\0';
3821 len += strlen(tmp) + 1;
3822 strncat(text, tmp, strlen(tmp));
3823 #else /* SMIME */
3824 fputs(tmp, fp);
3825 #endif /* SMIME */
3828 fclose(fp);
3829 #ifdef SMIME
3830 if(text != NULL){
3831 if(ps_global->pwdcert == NULL){
3832 q_status_message(SM_ORDER, 3, 3, "Attempting to encrypt password file");
3833 i = setup_pwdcert(&ps_global->pwdcert);
3834 if((i == 0 || i == -5) && ps_global->pwdcert == NULL)
3835 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3837 if(ps_global->pwdcert == NULL){ /* we tried but failed */
3838 if(i == -1)
3839 q_status_message1(SM_ORDER, 3, 3, "Error: no directory %s to save certificates", ps_global->pwdcertdir);
3840 else
3841 q_status_message(SM_ORDER, 3, 3, "Refusing to write non-encrypted password file");
3843 else if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0)
3844 q_status_message(SM_ORDER, 3, 3, "Failed to encrypt password file");
3845 fs_give((void **)&text); /* do not save this text */
3847 #endif /* SMIME */
3848 #endif /* PASSFILE */
3851 #endif /* LOCAL_PASSWD_CACHE */
3854 #if (WINCRED > 0)
3855 void
3856 erase_windows_credentials(void)
3858 LPCTSTR lfilter = TEXT(TNAMESTAR);
3859 DWORD count, k;
3860 PCREDENTIAL *pcred;
3862 if(!g_CredInited){
3863 if(init_wincred_funcs() != 1)
3864 return;
3867 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
3868 if(pcred){
3869 for(k = 0; k < count; k++)
3870 g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0);
3872 g_CredFree((PVOID) pcred);
3877 void
3878 ask_erase_credentials(void)
3880 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){
3881 erase_windows_credentials();
3882 q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased");
3884 else
3885 q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased");
3888 #endif /* WINCRED */
3891 #ifdef LOCAL_PASSWD_CACHE
3893 get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
3894 char *pinerc, **passwd, *user;
3895 STRLIST_S *hostlist;
3896 int altflag;
3898 return get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, NULL);
3902 * get_passfile_passwd_auth - return the password contained in the special passord
3903 * cache. The file is assumed to be in the same directory
3904 * as the pinerc with the name defined above.
3907 get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, authtype)
3908 char *pinerc, **passwd, *user;
3909 STRLIST_S *hostlist;
3910 int altflag;
3911 char *authtype;
3913 dprint((10, "get_passfile_passwd_auth\n"));
3914 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3915 ? imap_get_passwd_auth(passfile_cache, passwd,
3916 user, hostlist, altflag, authtype)
3917 : 0);
3920 void
3921 free_passfile_cache_work(MMLOGIN_S **pwdcache)
3923 if(pwdcache == NULL || *pwdcache == NULL)
3924 return;
3926 if((*pwdcache)->user) fs_give((void **)&(*pwdcache)->user);
3927 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
3928 if((*pwdcache)->hosts) free_strlist(&(*pwdcache)->hosts);
3929 free_passfile_cache_work(&(*pwdcache)->next);
3930 fs_give((void **)pwdcache);
3934 void
3935 free_passfile_cache(void)
3937 if(passfile_cache)
3938 free_passfile_cache_work(&passfile_cache);
3942 is_using_passfile(void)
3944 return(using_passfile == 1);
3948 * Just trying to guess the username the user might want to use on this
3949 * host, the user will confirm.
3951 char *
3952 get_passfile_user(pinerc, hostlist)
3953 char *pinerc;
3954 STRLIST_S *hostlist;
3956 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3957 ? imap_get_user(passfile_cache, hostlist)
3958 : NULL);
3963 preserve_prompt(char *pinerc)
3965 return preserve_prompt_auth(pinerc, NULL);
3969 preserve_prompt_auth(char *pinerc, char *authtype)
3971 #ifdef WINCRED
3972 # if (WINCRED > 0)
3973 #define PROMPT_PWD _("Preserve password for next login")
3974 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3976 * This prompt was going to be able to be turned on and off via a registry
3977 * setting controlled from the config menu. We decided to always use the
3978 * dialog for login, and there the prompt is unobtrusive enough to always
3979 * be in there. As a result, windows should never reach this, but now
3980 * OS X somewhat uses the behavior just described.
3982 if(mswin_store_pass_prompt()
3983 && (want_to(authtype
3984 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3985 : PROMPT_PWD,
3986 'y', 'x', NO_HELP, WT_NORM)
3987 == 'y'))
3988 return(1);
3989 else
3990 return(0);
3991 # else
3992 return(0);
3993 # endif
3995 #elif APPLEKEYCHAIN
3996 #define PROMPT_PWD _("Preserve password for next login")
3997 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3999 int rc;
4000 if((rc = macos_store_pass_prompt()) != 0){
4001 if(want_to(authtype
4002 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4003 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4004 == 'y'){
4005 if(rc == -1){
4006 macos_set_store_pass_prompt(1);
4007 q_status_message(SM_ORDER, 4, 4,
4008 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4010 return(1);
4012 else if(rc == -1){
4013 macos_set_store_pass_prompt(0);
4014 q_status_message(SM_ORDER, 4, 4,
4015 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4017 return(0);
4019 return(0);
4020 #else /* PASSFILE */
4021 #define PROMPT_PWD _("Preserve password on DISK for next login")
4022 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
4024 char tmp[MAILTMPLEN];
4025 struct stat sbuf;
4027 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || our_stat(tmp, &sbuf) < 0)
4028 return 0;
4030 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
4031 return(want_to(authtype
4032 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4033 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4034 == 'y');
4035 return(0);
4036 #endif /* PASSFILE */
4039 #endif /* LOCAL_PASSWD_CACHE */
4042 #ifdef APPLEKEYCHAIN
4045 * Returns:
4046 * 1 if store pass prompt is set in the "registry" to on
4047 * 0 if set to off
4048 * -1 if not set to anything
4051 macos_store_pass_prompt(void)
4053 char *data = NULL;
4054 UInt32 len = 0;
4055 int rc = -1;
4056 int val;
4058 if(storepassprompt == -1){
4059 if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL,
4060 strlen(TNAMEPROMPT),
4061 TNAMEPROMPT, &len,
4062 (void **) &data, NULL))){
4063 val = (len == 1 && data && data[0] == '1');
4067 if(storepassprompt == -1 && !rc){
4068 if(val)
4069 storepassprompt = 1;
4070 else
4071 storepassprompt = 0;
4074 return(storepassprompt);
4078 void
4079 macos_set_store_pass_prompt(int val)
4081 storepassprompt = val ? 1 : 0;
4083 SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT),
4084 TNAMEPROMPT, 1, val ? "1" : "0", NULL);
4088 void
4089 macos_erase_keychain(void)
4091 SecKeychainAttributeList attrList;
4092 SecKeychainSearchRef searchRef = NULL;
4093 SecKeychainAttribute attrs1[] = {
4094 { kSecAccountItemAttr, strlen(TNAME), TNAME }
4096 SecKeychainAttribute attrs2[] = {
4097 { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT }
4100 dprint((9, "macos_erase_keychain\n"));
4103 * Seems like we ought to be able to combine attrs1 and attrs2
4104 * into a single array, but I couldn't get it to work.
4107 /* search for only our items in the keychain */
4108 attrList.count = 1;
4109 attrList.attr = attrs1;
4111 if(!SecKeychainSearchCreateFromAttributes(NULL,
4112 kSecGenericPasswordItemClass,
4113 &attrList,
4114 &searchRef)){
4115 if(searchRef){
4116 SecKeychainItemRef itemRef = NULL;
4119 * Go through each item we found and put it
4120 * into our list.
4122 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4123 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4124 SecKeychainItemDelete(itemRef);
4125 CFRelease(itemRef);
4128 CFRelease(searchRef);
4132 attrList.count = 1;
4133 attrList.attr = attrs2;
4135 if(!SecKeychainSearchCreateFromAttributes(NULL,
4136 kSecGenericPasswordItemClass,
4137 &attrList,
4138 &searchRef)){
4139 if(searchRef){
4140 SecKeychainItemRef itemRef = NULL;
4143 * Go through each item we found and put it
4144 * into our list.
4146 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4147 SecKeychainItemDelete(itemRef);
4148 CFRelease(itemRef);
4151 CFRelease(searchRef);
4156 #endif /* APPLEKEYCHAIN */
4158 #ifdef LOCAL_PASSWD_CACHE
4160 void
4161 set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
4162 char *pinerc, *passwd, *user;
4163 STRLIST_S *hostlist;
4164 int altflag, already_prompted;
4166 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, NULL);
4169 * set_passfile_passwd - set the password file entry associated with
4170 * cache. The file is assumed to be in the same directory
4171 * as the pinerc with the name defined above.
4172 * already_prompted: 0 not prompted
4173 * 1 prompted, answered yes
4174 * 2 prompted, answered no
4176 void
4177 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, authtype)
4178 char *pinerc, *passwd, *user;
4179 STRLIST_S *hostlist;
4180 int altflag, already_prompted;
4181 char *authtype;
4183 dprint((10, "set_passfile_passwd_auth\n"));
4184 if(((already_prompted == 0 && preserve_prompt_auth(pinerc, authtype))
4185 || already_prompted == 1)
4186 && !ps_global->nowrite_password_cache
4187 && (passfile_cache || read_passfile(pinerc, &passfile_cache))){
4188 imap_set_passwd_auth(&passfile_cache, passwd, user, hostlist, altflag, 0, 0, authtype);
4189 write_passfile(pinerc, passfile_cache);
4193 void
4194 update_passfile_hostlist(pinerc, user, hostlist, altflag)
4195 char *pinerc;
4196 char *user;
4197 STRLIST_S *hostlist;
4198 int altflag;
4200 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, NULL);
4204 * Passfile lines are
4206 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4208 * In pine4.40 and before there was no orig_hostname.
4209 * This routine attempts to repair that.
4211 void
4212 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, authtype)
4213 char *pinerc;
4214 char *user;
4215 STRLIST_S *hostlist;
4216 int altflag;
4217 char *authtype;
4219 #ifdef WINCRED
4220 return;
4221 #else /* !WINCRED */
4222 MMLOGIN_S *l;
4223 size_t len = authtype ? strlen(authtype) : 0;
4224 size_t offset = authtype ? 1 : 0;
4226 for(l = passfile_cache; l; l = l->next)
4227 if(imap_same_host_auth(l->hosts, hostlist, authtype)
4228 && *user
4229 && !strcmp(user, l->user + len + offset)
4230 && l->altflag == altflag){
4231 break;
4234 if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
4235 && hostlist->next->name
4236 && !ps_global->nowrite_password_cache){
4237 l->hosts->next = new_strlist_auth(hostlist->next->name, authtype, PWDAUTHSEP);
4238 write_passfile(pinerc, passfile_cache);
4240 #endif /* !WINCRED */
4243 #endif /* LOCAL_PASSWD_CACHE */
4246 #if (WINCRED > 0)
4248 * Load and init the WinCred structure.
4249 * This gives us a way to skip the WinCred code
4250 * if the dll doesn't exist.
4253 init_wincred_funcs(void)
4255 if(!g_CredInited)
4257 HMODULE hmod;
4259 /* Assume the worst. */
4260 g_CredInited = -1;
4262 hmod = LoadLibrary(TEXT("advapi32.dll"));
4263 if(hmod)
4265 FARPROC fpCredWriteW;
4266 FARPROC fpCredEnumerateW;
4267 FARPROC fpCredDeleteW;
4268 FARPROC fpCredFree;
4270 fpCredWriteW = GetProcAddress(hmod, "CredWriteW");
4271 fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW");
4272 fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW");
4273 fpCredFree = GetProcAddress(hmod, "CredFree");
4275 if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree)
4277 g_CredWriteW = (CREDWRITEW *)fpCredWriteW;
4278 g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW;
4279 g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW;
4280 g_CredFree = (CREDFREE *)fpCredFree;
4282 g_CredInited = 1;
4286 mswin_set_erasecreds_callback(ask_erase_credentials);
4289 return g_CredInited;
4292 #endif /* WINCRED */