* clear out some warnings by gcc 9.3.1.
[alpine.git] / alpine / imap.c
blobf1654dece22d40f3cbae73a4e84685148f6107ae
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 0, /* expiration time */
174 0, /* first time indicator */
175 1, /* client secret required */
176 0 /* Cancel refresh token */
178 {OUTLOOK_NAME,
179 {"outlook.office365.com", "smtp.office365.com", NULL, NULL},
180 {{"client_id", NULL},
181 {"client_secret", NULL}, /* not used, but needed */
182 {"tenant", NULL}, /* used */
183 {"code", NULL}, /* not used, not needed */
184 {"refresh_token", NULL},
185 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
186 {"grant_type", "urn:ietf:params:oauth:grant-type:device_code"},
187 {"scope", NULL}, /* not used */
188 {"grant_type", "refresh_token"},
189 {"response_type", "code"}, /* not used */
190 {"state", NULL}, /* not used */
191 {"device_code", NULL} /* only used for frst time set up */
193 {{NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* Get Access Code, Not used */
194 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/devicecode", /* first time use and get device code information */
195 {OA2_Id, OA2_Scope, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}},
196 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
197 {OA2_Id, OA2_Redirect, OA2_DeviceCode, OA2_End, OA2_End, OA2_End, OA2_End}},
198 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
199 {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}
201 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information */
202 NULL, /* access token */
203 0, /* expiration time */
204 0, /* first time indicator */
205 0, /* client secret required */
206 0 /* Cancel refresh token */
208 {OUTLOOK_NAME,
209 {"outlook.office365.com", "smtp.office365.com", NULL, NULL},
210 {{"client_id", NULL},
211 {"client_secret", NULL}, /* not used, but needed */
212 {"tenant", NULL}, /* used */
213 {"code", NULL}, /* used during authorization */
214 {"refresh_token", NULL},
215 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
216 {"redirect_uri", "http://localhost"},
217 {"grant_type", "authorization_code"},
218 {"grant_type", "refresh_token"},
219 {"response_type", "code"},
220 {"state", NULL}, /* not used */
221 {"device_code", NULL} /* not used */
223 {{"GET", "https://login.microsoftonline.com/\001/oauth2/v2.0/authorize", /* Get Access Code */
224 {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End}},
225 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
226 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
227 {OA2_Id, OA2_Redirect, OA2_Scope, OA2_GrantTypeforAccessToken, OA2_Secret, OA2_Code, OA2_End}},
228 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
229 {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_Secret, OA2_End, OA2_End}}
231 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
232 NULL, /* access token */
233 0, /* expiration time */
234 0, /* first time indicator */
235 1, /* client secret required */
236 0 /* Cancel refresh token */
238 {YANDEX_NAME,
239 {"imap.yandex.com", "smtp.yandex.com", NULL, NULL},
240 {{"client_id", NULL},
241 {"client_secret", NULL}, /* not used, but needed */
242 {"tenant", NULL}, /* not used */
243 {"code", NULL}, /* used during authorization */
244 {"refresh_token", NULL},
245 {"scope", NULL}, /* not needed, so not used */
246 {"redirect_uri", "https://oauth.yandex.ru/verification_code"},
247 {"grant_type", "authorization_code"},
248 {"grant_type", "refresh_token"},
249 {"response_type", "code"},
250 {"state", NULL}, /* not used */
251 {"device_code", NULL} /* not used */
253 {{"GET", "https://oauth.yandex.com/authorize", /* Get Access Code */
254 {OA2_Id, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End, OA2_End}},
255 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
256 {"POST", "https://oauth.yandex.com/token", /* Get first Refresh Token and Access token */
257 {OA2_Id, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Secret, OA2_Code, OA2_End, OA2_End}},
258 {"POST", "https://oauth.yandex.com/token", /* Get access token from refresh token */
259 {OA2_Id, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_Secret, OA2_End, OA2_End, OA2_End}}
261 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
262 NULL, /* access token */
263 0, /* expiration time */
264 0, /* first time indicator */
265 1, /* client secret required */
266 0 /* Cancel refresh token */
268 { NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0},
272 xoauth2_flow_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
274 int rv = 0;
276 switch(cmd){
277 case MC_CHOICE:
278 *((*cl)->d.xf.selected) = (*cl)->d.xf.pat;
279 rv = simple_exit_cmd(flags);
281 case MC_EXIT:
282 rv = simple_exit_cmd(flags);
283 break;
285 default:
286 rv = -1;
289 if(rv > 0)
290 ps->mangled_body = 1;
292 return rv;
295 OAUTH2_S *
296 oauth2_select_flow(char *host)
298 OAUTH2_S *oa2list, *oa2;
299 int i, rv;
300 char *method;
302 if(ps_global->ttyo){
303 CONF_S *ctmp = NULL, *first_line = NULL;
304 OAUTH2_S *x_sel = NULL;
305 OPT_SCREEN_S screen;
306 char tmp[1024];
308 dprint((9, "xoauth2 select flow"));
309 ps_global->next_screen = SCREEN_FUN_NULL;
311 memset(&screen, 0, sizeof(screen));
313 for(i = 0; i < sizeof(tmp) && i < ps_global->ttyo->screen_cols; i++)
314 tmp[i] = '-';
315 tmp[i] = '\0';
317 new_confline(&ctmp);
318 ctmp->flags |= CF_NOSELECT;
319 ctmp->value = cpystr(tmp);
321 new_confline(&ctmp);
322 ctmp->flags |= CF_NOSELECT;
323 ctmp->value = cpystr(_("Please select below the authorization flow you would like to follow:"));
325 new_confline(&ctmp);
326 ctmp->flags |= CF_NOSELECT;
327 ctmp->value = cpystr(tmp);
329 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
330 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
331 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
332 new_confline(&ctmp);
333 if(!first_line)
334 first_line = ctmp;
335 method = oa2list->server_mthd[0].name ? "Authorize"
336 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
337 sprintf(tmp, "%s (%s)", oa2list->name, method);
338 ctmp->value = cpystr(tmp);
339 ctmp->d.xf.selected = &x_sel;
340 ctmp->d.xf.pat = oa2list;
341 ctmp->keymenu = &xoauth2_id_select_km;
342 ctmp->help = NO_HELP;
343 ctmp->help_title = NULL;
344 ctmp->tool = xoauth2_flow_tool;
345 ctmp->flags = CF_STARTITEM;
346 ctmp->valoffset = 4;
349 (void)conf_scroll_screen(ps_global, &screen, first_line, _("SELECT AUTHORIZATION FLOW"),
350 _("xoauth2"), 0, NULL);
351 oa2 = x_sel;
353 else{
354 char *s;
355 char prompt[1024];
356 char reply[1024];
357 int sel, n = 0, j;
359 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++)
360 n += strlen(oa2list->name); + 5; /* number, parenthesis, space */
361 n += 1024; /* large enough to display to lines of 80 characters in UTF-8 */
362 s = fs_get(n*sizeof(char));
363 strcpy(s, _("Please select below the authorization flow you would like to follow:"));
364 sprintf(s + strlen(s), _("Please select the client-id to use from the following list.\n\n"));
365 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
366 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
367 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i])
368 sprintf(s + strlen(s), " %d) %.70s\n", j++, oa2list->name);
370 display_init_err(s, 0);
372 strncpy(prompt, _("Enter your selection number: "), sizeof(prompt));
373 prompt[sizeof(prompt)-1] = '\0';
375 rv = optionally_enter(reply, 0, 0, sizeof(reply), prompt, NULL, NO_HELP, 0);
376 sel = atoi(reply);
377 rv = (sel >= 0 && sel < i) ? 0 : -1;
378 } while (rv != 0);
380 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
381 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
382 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
383 if(j == sel) break;
384 else j++;
387 oa2 = oa2list;
389 return oa2;
392 typedef struct auth_code_s {
393 char *code;
394 int answer;
395 } AUTH_CODE_S;
398 oauth2device_decode_reply(void *datap, void *replyp)
400 OAUTH2_DEVICEPROC_S *av = (OAUTH2_DEVICEPROC_S *)datap;
401 int reply = *(int *) replyp;
403 return reply < 0 ? av->code_failure : (reply == 0 ? av->code_success : av->code_wait);
407 oauth2_elapsed_done(void *aux_valuep)
409 OAUTH2_S *oauth2 = aux_valuep ? ((OAUTH2_DEVICEPROC_S *) aux_valuep)->xoauth2 : NULL;
410 static time_t savedt = 0, now;
411 int rv = 0;
413 if(aux_valuep == NULL) savedt = 0; /* reset last time we approved */
414 else{
415 now = time(0);
416 if(oauth2->devicecode.interval + now >= savedt)
417 savedt = now;
418 else
419 rv = -1;
421 return rv;
424 void
425 oauth2_set_device_info(OAUTH2_S *oa2, char *method)
427 char tmp[MAILTMPLEN];
428 char *code;
429 char *name = oa2->name;
430 int aux_rv_value;
431 OAUTH2_DEVICECODE_S *deviceinfo = &oa2->devicecode;
432 OAUTH2_DEVICEPROC_S aux_value;
434 if(ps_global->ttyo){
435 SCROLL_S sargs;
436 STORE_S *in_store, *out_store;
437 gf_io_t pc, gc;
438 HANDLE_S *handles = NULL;
439 AUTH_CODE_S user_input;
441 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
442 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
443 goto try_wantto;
445 aux_value.xoauth2 = oa2;
446 aux_value.code_success = 'e';
447 aux_value.code_failure = 'e';
448 aux_value.code_wait = NO_OP_COMMAND;
450 so_puts(in_store, "<HTML><P>");
451 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name);
452 so_puts(in_store, tmp);
453 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name, method),
454 so_puts(in_store, tmp);
456 if(deviceinfo->verification_uri && deviceinfo->user_code){
457 sprintf(tmp,
458 _("</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."),
459 deviceinfo->verification_uri, deviceinfo->verification_uri, deviceinfo->user_code);
460 so_puts(in_store, tmp);
462 else{
463 so_puts(in_store, "</P><P>");
464 so_puts(in_store, deviceinfo->message);
466 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
467 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), name);
468 so_puts(in_store, tmp);
469 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
471 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 "));
472 so_puts(in_store, _("to grant access to Alpine to your data. "));
473 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 "));
474 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 "));
475 so_puts(in_store, _("alive at the end of this process, and your connection will proceed from there."));
476 so_puts(in_store, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit"));
477 so_puts(in_store, _("</P></HTML>"));
479 so_seek(in_store, 0L, 0);
480 init_handles(&handles);
481 gf_filter_init();
482 gf_link_filter(gf_html2plain,
483 gf_html2plain_opt(NULL,
484 ps_global->ttyo->screen_cols, non_messageview_margin(),
485 &handles, NULL, GFHP_LOCAL_HANDLES));
486 gf_set_so_readc(&gc, in_store);
487 gf_set_so_writec(&pc, out_store);
488 gf_pipe(gc, pc);
489 gf_clear_so_writec(out_store);
490 gf_clear_so_readc(in_store);
492 memset(&sargs, 0, sizeof(SCROLL_S));
493 sargs.text.handles = handles;
494 sargs.text.text = so_text(out_store);
495 sargs.text.src = CharStar;
496 sargs.text.desc = _("help text");
497 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
498 sargs.proc.tool = oauth2_auth_answer;
499 sargs.proc.data.p = (void *)&user_input;
500 sargs.keys.menu = &oauth2_device_auth_keymenu;
501 /* don't want to re-enter c-client */
502 sargs.quell_newmail = 1;
503 setbitmap(sargs.keys.bitmap);
504 sargs.help.text = h_oauth2_start;
505 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
506 sargs.aux_function = oauth2deviceinfo_get_accesscode;
507 sargs.aux_value = (void *) &aux_value;
508 sargs.aux_condition = oauth2_elapsed_done;
509 sargs.decode_aux_rv_value = oauth2device_decode_reply;
510 sargs.aux_rv_value = (void *) &aux_rv_value;
512 do {
513 scrolltool(&sargs);
514 ps_global->mangled_screen = 1;
515 ps_global->painted_body_on_startup = 0;
516 ps_global->painted_footer_on_startup = 0;
517 } while (user_input.answer != 'e');
519 so_give(&in_store);
520 so_give(&out_store);
521 free_handles(&handles);
522 oauth2_elapsed_done(NULL);
524 else{
525 int flags, rc, q_line;
526 /* TRANSLATORS: user needs to input an access code from the server */
527 char prompt[MAILTMPLEN], token[MAILTMPLEN];
529 * If screen hasn't been initialized yet, use want_to.
531 try_wantto:
533 tmp_20k_buf[0] = '\0';
534 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
535 _("Authorizing Alpine Access to %s Email Services\n\n"), name);
536 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
538 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
539 _("Alpine is attempting to log you into your %s account, using the %s method. "), name, method),
540 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
542 if(deviceinfo->verification_uri && deviceinfo->user_code){
543 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
544 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
545 deviceinfo->verification_uri, deviceinfo->user_code);
546 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
548 else{
549 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
550 "%s\n\n", deviceinfo->message);
551 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
554 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
555 _("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);
556 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
558 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
559 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
560 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
562 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
563 "%s", _("to grant access to Alpine to your data.\n\n"));
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", _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
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", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
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", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
576 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
578 display_init_err(tmp_20k_buf, 0);
579 memset((void *)tmp, 0, sizeof(tmp));
580 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
581 tmp[sizeof(tmp)-1] = '\0';
583 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
584 int rv;
585 UCS ch;
586 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
587 flags = OE_APPEND_CURRENT;
589 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
590 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
591 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
593 aux_value.xoauth2 = oa2;
594 aux_value.code_success = 'y';
595 aux_value.code_failure = 'n';
596 aux_value.code_wait = 'w';
598 strncpy(tmp, _("Continue waiting"), sizeof(tmp));
599 tmp[sizeof(tmp)-1] = '\0';
600 do {
601 if(oauth2_elapsed_done((void *) &aux_value) == 0)
602 oauth2deviceinfo_get_accesscode((void *) &aux_value, (void *) &rv);
603 ch = oauth2device_decode_reply((void *) &aux_value, (void *) &rv);
604 } while (ch == 'w' || want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y');
605 oauth2_elapsed_done(NULL);
610 char *
611 oauth2_get_access_code(unsigned char *url, char *method, OAUTH2_S *oauth2, int *tryanother)
613 char tmp[MAILTMPLEN];
614 char *code;
616 if(ps_global->ttyo){
617 SCROLL_S sargs;
618 STORE_S *in_store, *out_store;
619 gf_io_t pc, gc;
620 HANDLE_S *handles = NULL;
621 AUTH_CODE_S user_input;
623 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
624 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
625 goto try_wantto;
627 so_puts(in_store, "<HTML><BODY><P>");
628 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2->name);
629 so_puts(in_store, tmp);
630 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
631 so_puts(in_store, tmp);
633 if(strucmp(oauth2->name, GMAIL_NAME) == 0){
634 so_puts(in_store, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
635 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."));
636 so_puts(in_store, _("<UL> "));
637 so_puts(in_store, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
638 so_puts(in_store, _("and create a project. The name of the project is not important."));
639 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."));
640 so_puts(in_store, _("<LI> Create OAUTH Credentials."));
641 so_puts(in_store, _("</UL> "));
642 so_puts(in_store, _("<P> As a result of this process, you will get a client-id and a client-secret."));
643 so_puts(in_store, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
644 so_puts(in_store, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
645 so_puts(in_store, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
646 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."));
649 so_puts(in_store, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
650 so_puts(in_store,"</P><P>");
651 sprintf(tmp_20k_buf, _("<A HREF=\"%s\">%s</A>"), url, url);
652 so_puts(in_store, tmp_20k_buf);
654 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
655 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2->name);
656 so_puts(in_store, tmp);
657 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
659 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. "));
660 so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page."));
661 so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
662 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. "));
663 so_puts(in_store, _(" Once you have completed this process, Alpine will proceed with authentication."));
664 so_puts(in_store, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
665 so_puts(in_store, _("</P></BODY></HTML>"));
667 so_seek(in_store, 0L, 0);
668 init_handles(&handles);
669 gf_filter_init();
670 gf_link_filter(gf_html2plain,
671 gf_html2plain_opt(NULL,
672 ps_global->ttyo->screen_cols, non_messageview_margin(),
673 &handles, NULL, GFHP_LOCAL_HANDLES));
674 gf_set_so_readc(&gc, in_store);
675 gf_set_so_writec(&pc, out_store);
676 gf_pipe(gc, pc);
677 gf_clear_so_writec(out_store);
678 gf_clear_so_readc(in_store);
680 memset(&sargs, 0, sizeof(SCROLL_S));
681 sargs.text.handles = handles;
682 sargs.text.text = so_text(out_store);
683 sargs.text.src = CharStar;
684 sargs.text.desc = _("help text");
685 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
686 sargs.proc.tool = oauth2_auth_answer;
687 sargs.proc.data.p = (void *)&user_input;
688 sargs.keys.menu = &oauth2_auth_keymenu;
689 /* don't want to re-enter c-client */
690 sargs.quell_newmail = 1;
691 setbitmap(sargs.keys.bitmap);
692 sargs.help.text = h_oauth2_start;
693 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
695 do {
696 scrolltool(&sargs);
697 ps_global->mangled_screen = 1;
698 ps_global->painted_body_on_startup = 0;
699 ps_global->painted_footer_on_startup = 0;
700 } while (user_input.answer != 'e');
702 if(!struncmp(user_input.code, "http://", 7)
703 || !struncmp(user_input.code, "https://", 8)){
704 char *s, *t;
705 s = strstr(user_input.code, "code=");
706 if(s != NULL){
707 t = strchr(s, '&');
708 if(t) *t = '\0';
709 code = cpystr(s+5);
710 if(t) *t = '&';
713 else code = user_input.code ? cpystr(user_input.code) : NULL;
714 if(user_input.code) fs_give((void **) &user_input.code);
716 if(code == NULL) *tryanother = 1;
718 so_give(&in_store);
719 so_give(&out_store);
720 free_handles(&handles);
722 else{
723 int flags, rc, q_line;
724 /* TRANSLATORS: user needs to input an access code from the server */
725 char *accesscodelabel = _("Copy and Paste Access Code");
726 char prompt[MAILTMPLEN], token[MAILTMPLEN];
728 * If screen hasn't been initialized yet, use want_to.
730 try_wantto:
731 tmp_20k_buf[0] = '\0';
732 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
733 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2->name);
734 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
736 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
737 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
738 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
740 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
741 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
742 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
744 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
745 "%s\n\n", url);
746 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
748 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
749 _("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);
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", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
754 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
756 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
757 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
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", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
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", _(" 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. "));
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", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
770 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
772 display_init_err(tmp_20k_buf, 0);
773 memset((void *)tmp, 0, sizeof(tmp));
774 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
775 tmp[sizeof(tmp)-1] = '\0';
777 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
778 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
779 flags = OE_APPEND_CURRENT;
780 sprintf(prompt, "%s: ", accesscodelabel);
781 do {
782 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
783 prompt, NULL, NO_HELP, &flags);
784 } while (rc != 0 && rc != 1);
785 if(!struncmp(token, "http://", 7)
786 || !struncmp(token, "https://", 8)){
787 char *s, *t;
788 s = strstr(token, "code=");
789 if(s != NULL){
790 t = strchr(s, '&');
791 if(t) *t = '\0';
792 code = cpystr(s+5);
793 if(t) *t = '&';
796 else code = token[0] ? cpystr(token) : NULL;
800 return code;
803 void mm_login_oauth2(NETMBX *, char *, char *, OAUTH2_S *, long int, char *, char *);
805 /* The purpose of this function is to report to c-client the values of the
806 * different tokens and codes so that c-client can try to log in the user
807 * to the server. This function DOES NOT attempt to get these values for
808 * the user. That is attempted in the c-client side (as best as it can be
809 * done given our circumstances: no http support, no javascript support,
810 * etc.). This is the best we can do:
812 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
813 * as best as we can. Unloaded means that there is no server known to
814 * connect, no access token, etc. Pretty much nothing is known about
815 * how to get access code, access token, etc. We ask the user to fill
816 * it up for us, if they can. If the user fills it up we save those
817 * values.
819 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
820 * save the information that c-client got for us.
822 * 3. When saving this information we use the password caching facilities,
823 * but we must do it in a different format so that old information and
824 * new information are not mixed. In order to accommodate this for new
825 * authentication methods, we save the information in the same fields,
826 * but this time we modify it slightly, so that old functions fail to
827 * understand the new information and so not modify it nor use it. The
828 * modification is simple: Every piece of information that was saved
829 * before is prepended XOAUTH2\001 to indicate the authentication
830 * method for which it works. The character \001 is a separator. New
831 * Alpine will know how to deal with this, but old versions, will not
832 * strip this prefix from the information and fail to get the
833 * information or modify it when needed. Only new versions of Alpine will
834 * know how to process this information.
835 * new_value = authenticator_method separator old_value
836 * authenticator_method = "XOAUTH2_S"
837 * separator = "U+1"
838 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
839 * authenticator is "XOAUTH2\001imap.gmail.com".
840 * In addition, the password field is not used to encode the password
841 * anymore, it is used to save login information needed in a format that
842 * the caller function chooses, but that must be preceded by the
843 * "authenticator_method separator" code as above.
845 void
846 mm_login_oauth2(NETMBX *mb, char *user, char *method,
847 OAUTH2_S *login, long int trial,
848 char *usethisprompt, char *altuserforcache)
850 char *token, tmp[MAILTMPLEN];
851 char prompt[4*MAILTMPLEN], value[4*MAILTMPLEN], *last;
852 char defuser[NETMAXUSER];
853 char hostleadin[80], hostname[200], defubuf[200];
854 char logleadin[80], pwleadin[50];
855 char *url_oauth2;
856 char *tool = NULL;
857 char *OldRefreshToken, *OldAccessToken;
858 char *NewRefreshToken, *NewAccessToken;
859 char *SaveRefreshToken, *SaveAccessToken;
860 /* TRANSLATORS: A label for the hostname that the user is logging in on */
861 char *hostlabel = _("HOST");
862 /* TRANSLATORS: user is logging in as a particular user (a particular
863 login name), this is just labelling that user name. */
864 char *userlabel = _("USER");
865 STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1];
866 HelpType help ;
867 int len, rc, q_line, flags, i, j;
868 int oespace, avail, need, save_dont_use;
869 int save_in_init;
870 int registered;
871 int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
872 OAUTH2_S *oa2list, *oa2;
873 XOAUTH2_INFO_S *x;
875 unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
876 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
877 int preserve_password = -1;
878 #endif
880 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
881 trial, mb->user ? mb->user : "(null)",
882 mb->service ? mb->service : "(null)",
883 mb->port ? " port=" : "",
884 mb->port ? comatose(mb->port) : "",
885 altuserforcache ? " altuserforcache =" : "",
886 altuserforcache ? altuserforcache : ""));
888 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
890 save_in_init = ps_global->in_init_seq;
891 ps_global->in_init_seq = 0;
892 ps_global->no_newmail_check_from_optionally_enter = 1;
894 /* make sure errors are seen */
895 if(ps_global->ttyo && !ps_global->noshow_error)
896 flush_status_messages(0);
898 token = NULL; /* start from scratch */
900 hostlist[0].name = mb->host;
901 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
902 hostlist[0].next = &hostlist[1];
903 hostlist[1].name = mb->orighost;
904 hostlist[1].next = NULL;
906 else
907 hostlist[0].next = NULL;
909 if(hostlist[0].name){
910 dprint((9, "mm_login_oauth2: host=%s\n",
911 hostlist[0].name ? hostlist[0].name : "?"));
912 if(hostlist[0].next && hostlist[1].name){
913 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name));
917 if(trial == 0L && !altuserforcache){
918 if(*mb->user != '\0')
919 strncpy(user, mb->user, NETMAXUSER);
920 else{
921 flags = OE_APPEND_CURRENT;
922 sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
923 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
924 prompt, NULL, NO_HELP, &flags);
926 user[NETMAXUSER-1] = '\0';
930 * We check to see if the server we are going to log in to is already
931 * registered. This gives us a list of servers with the same
932 * credentials, so we use the same credentials for all of them.
935 for(registered = 0, oa2list = alpine_oauth2_list;
936 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
937 oa2list++){
938 for(i = 0; i < OAUTH2_TOT_EQUIV
939 && oa2list->host[i] != NULL
940 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
941 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
942 registered++;
943 break;
947 if(registered){
948 x = oauth2_get_client_info(oa2list->name, user);
949 if(x && x->flow){
950 for(oa2list = alpine_oauth2_list;
951 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
952 oa2list++){
953 for(i = 0; i < OAUTH2_TOT_EQUIV
954 && oa2list->host[i] != NULL
955 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
956 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
957 char *flow = oa2list->server_mthd[0].name ? "Authorize"
958 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
959 if(!strucmp(x->flow, flow)) break; /* found it */
963 /* else use the one we found earlier, the user has to configure this better */
966 if(registered){
967 hostlist2[i = 0].name = mb->host;
968 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
969 hostlist2[++i].name = mb->orighost;
971 for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){
972 int k;
973 for(k = 0; k <= i && hostlist2[k].name
974 && strcmp(hostlist2[k].name, oa2list->host[j]); k++);
975 if(k == i + 1)
976 hostlist2[++i].name = oa2list->host[j];
978 hostlist2[i+1].name = NULL;
979 hostlist2[i+1].next = NULL;
980 for(j = i; j >= 0; j--)
981 hostlist2[j].next = &hostlist2[j+1];
985 * We check if we have a refresh token saved somewhere, if so
986 * we use it to get a new access token, otherwise we need to
987 * get an access code so we can get (and save) a refresh token
988 * and use the access token.
990 if(trial == 0L && !altuserforcache){
991 /* Search for a refresh token that is already loaded ... */
992 if(imap_get_passwd_auth(mm_login_list, &token, user,
993 registered ? hostlist2 : hostlist,
994 (mb->sslflag||mb->tlsflag), OA2NAME)){
995 dprint((9, "mm_login_oauth2: found a refresh token\n"));
996 ps_global->no_newmail_check_from_optionally_enter = 0;
997 ps_global->in_init_seq = save_in_init;
999 #ifdef LOCAL_PASSWD_CACHE
1000 /* or see if we have saved one in the local password cache and load it */
1001 else if(get_passfile_passwd_auth(ps_global->pinerc, &token,
1002 user, registered ? hostlist2 : hostlist,
1003 (mb->sslflag||mb->tlsflag), OA2NAME)){
1004 imap_set_passwd_auth(&mm_login_list, token, user,
1005 hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1006 update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist,
1007 (mb->sslflag||mb->tlsflag), OA2NAME);
1008 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
1009 ps_global->no_newmail_check_from_optionally_enter = 0;
1010 ps_global->in_init_seq = save_in_init;
1012 if(token && *token) preserve_password = 1; /* resave it, no need to ask */
1013 #endif /* LOCAL_PASSWD_CACHE */
1015 user[NETMAXUSER-1] = '\0';
1017 /* The Old* variables is what c_client knows */
1018 OldRefreshToken = login->cancel_refresh_token ? NULL : login->param[OA2_RefreshToken].value;
1019 OldAccessToken = login->access_token;
1020 OldExpirationTime = login->expiration;
1022 /* The New* variables is what Alpine knows */
1023 NewRefreshToken = NewAccessToken = NULL;
1024 NewExpirationTime = 0L;
1025 ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0;
1027 if(token && *token && !login->cancel_refresh_token){
1028 char *s, *t;
1030 s = token;
1031 t = strchr(s, PWDAUTHSEP);
1032 if(t == NULL)
1033 NewRefreshToken = cpystr(s);
1034 else {
1035 *t++ = '\0';
1036 NewRefreshToken = cpystr(s);
1037 s = t;
1038 t = strchr(s, PWDAUTHSEP);
1039 if(t == NULL)
1040 NewAccessToken = cpystr(s);
1041 else {
1042 *t++ = '\0';
1043 NewAccessToken = cpystr(s);
1044 s = t;
1045 NewExpirationTime = strtol(s, &s, 10);
1046 if(NewExpirationTime <= 0 || NewExpirationTime <= time(0))
1047 NewExpirationTime = 0L;
1050 /* check we got good information, and send good information below */
1051 if(NewRefreshToken && !*NewRefreshToken)
1052 fs_give((void **) &NewRefreshToken);
1053 if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken))
1054 fs_give((void **) &NewAccessToken);
1057 if(NewRefreshToken == NULL)
1058 login->first_time++;
1060 if(login->first_time){ /* count how many authorization methods we support */
1061 int nmethods, i, j;
1063 for(nmethods = 0, oa2 = alpine_oauth2_list; oa2 && oa2->name ; oa2++){
1064 for(j = 0; j < OAUTH2_TOT_EQUIV
1065 && oa2
1066 && oa2->host[j] != NULL
1067 && strucmp(oa2->host[j], mb->orighost) != 0; j++);
1068 if(oa2 && oa2->host && j < OAUTH2_TOT_EQUIV && oa2->host[j])
1069 nmethods++;
1072 if(nmethods > 1)
1073 oa2list = oauth2_select_flow(mb->orighost);
1075 if(!oa2list) registered = 0;
1078 /* Default to saving what we already had saved */
1080 SaveRefreshToken = login->cancel_refresh_token ? NULL : NewRefreshToken;
1081 SaveAccessToken = NewAccessToken;
1082 SaveExpirationTime = NewExpirationTime;
1084 /* Translation of the logic below:
1085 * if (c-client has a refresh token
1086 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1087 forget the Alpine refresh token;
1088 make the Alpine Refresh token = c-client refresh token.;
1089 signal that we changed the refresh token;
1090 In this situation we do not need to clear up the Alpine access token. This will
1091 expire, so we can use it until it expires. We can save the c-client refresh token
1092 together with the Alpine Access Token and the expiration date of the Alpine Access
1093 Token.
1094 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1095 forget the Alpine refresh token;
1096 if Alpine has an access token, forget it;
1097 reset the expiration time
1098 signal that we changed the refresh token; (because the service expired it)
1102 if(OldRefreshToken != NULL
1103 && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){
1104 if(NewRefreshToken) fs_give((void **) &NewRefreshToken);
1105 NewRefreshToken = cpystr(OldRefreshToken);
1106 ChangeRefreshToken++;
1107 SaveRefreshToken = OldRefreshToken;
1108 SaveAccessToken = NewAccessToken;
1109 SaveExpirationTime = NewExpirationTime;
1110 } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){
1111 fs_give((void **) &NewRefreshToken);
1112 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1113 NewExpirationTime = 0L;
1114 ChangeRefreshToken++;
1115 SaveRefreshToken = NULL;
1116 SaveAccessToken = NULL;
1117 SaveExpirationTime = 0L;
1120 if(OldAccessToken != NULL
1121 && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){
1122 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1123 NewAccessToken = cpystr(OldAccessToken);
1124 ChangeAccessToken++;
1125 NewExpirationTime = OldExpirationTime;
1126 SaveRefreshToken = NewRefreshToken;
1127 SaveAccessToken = NewAccessToken;
1128 SaveExpirationTime = NewExpirationTime;
1131 if(!registered){
1132 login->param[OA2_RefreshToken].value = SaveRefreshToken;
1133 login->access_token = SaveAccessToken;
1134 login->expiration = SaveExpirationTime;
1135 } else {
1136 oa2list->param[OA2_RefreshToken].value = SaveRefreshToken;
1137 oa2list->access_token = SaveAccessToken;
1138 oa2list->expiration = SaveExpirationTime;
1139 oa2list->first_time = login->first_time;
1140 oa2list->cancel_refresh_token = login->cancel_refresh_token;
1141 *login = *oa2list; /* load login pointer */
1144 if(!ChangeAccessToken && !ChangeRefreshToken && !login->cancel_refresh_token)
1145 return;
1147 /* get ready to save this information. The format will be
1148 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1149 * (spaces added for clarity, \001 is PWDAUTHSEP)
1151 if(token) fs_give((void **) &token);
1152 sprintf(tmp, "%lu", SaveExpirationTime);
1153 tmp[sizeof(tmp) - 1] = '\0';
1154 len = strlen(SaveRefreshToken ? SaveRefreshToken : "")
1155 + strlen(SaveAccessToken ? SaveAccessToken : "")
1156 + strlen(tmp) + 2;
1157 token = fs_get(len + 1);
1158 sprintf(token, "%s%c%s%c%lu",
1159 SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP,
1160 SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP,
1161 SaveExpirationTime);
1163 /* remember the access information for next time */
1164 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
1165 imap_set_passwd_auth(&mm_login_list, token,
1166 altuserforcache ? altuserforcache : user, hostlist,
1167 (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1168 #ifdef LOCAL_PASSWD_CACHE
1169 /* if requested, remember it on disk for next session */
1170 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
1171 set_passfile_passwd_auth(ps_global->pinerc, token,
1172 altuserforcache ? altuserforcache : user, hostlist,
1173 (mb->sslflag||mb->tlsflag),
1174 (preserve_password == -1 ? 0
1175 : (preserve_password == 0 ? 2 :1)), OA2NAME);
1176 #endif /* LOCAL_PASSWD_CACHE */
1178 ps_global->no_newmail_check_from_optionally_enter = 0;
1181 /*----------------------------------------------------------------------
1182 receive notification from IMAP
1184 Args: stream -- Mail stream message is relevant to
1185 string -- The message text
1186 errflg -- Set if it is a serious error
1188 Result: message displayed in status line
1190 The facility is for general notices, such as connection to server;
1191 server shutting down etc... It is used infrequently.
1192 ----------------------------------------------------------------------*/
1193 void
1194 mm_notify(MAILSTREAM *stream, char *string, long int errflg)
1196 time_t now;
1197 struct tm *tm_now;
1199 now = time((time_t *)0);
1200 tm_now = localtime(&now);
1202 /* be sure to log the message... */
1203 #ifdef DEBUG
1204 if(ps_global->debug_imap || ps_global->debugmem)
1205 dprint((errflg == TCPDEBUG ? 7 : 2,
1206 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1207 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1208 tm_now->tm_mon+1, tm_now->tm_mday,
1209 (!errflg) ? "babble" :
1210 (errflg == ERROR) ? "error" :
1211 (errflg == WARN) ? "warning" :
1212 (errflg == PARSE) ? "parse" :
1213 (errflg == TCPDEBUG) ? "tcp" :
1214 (errflg == BYE) ? "bye" : "unknown",
1215 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1216 string ? string : "?"));
1217 #endif
1219 snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s",
1220 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1221 (int) MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70),
1222 string);
1223 ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols
1224 : sizeof(ps_global->last_error)-1] = '\0';
1227 * Then either set special bits in the pine struct or
1228 * display the message if it's tagged as an "ALERT" or
1229 * its errflg > NIL (i.e., WARN, or ERROR)
1231 if(errflg == BYE)
1233 * We'd like to sp_mark_stream_dead() here but we can't do that because
1234 * that might call mail_close and we are already in a c-client callback.
1235 * So just set the dead bit and clean it up later.
1237 sp_set_dead_stream(stream, 1);
1238 else if(!strncmp(string, "[TRYCREATE]", 11))
1239 ps_global->try_to_create = 1;
1240 else if(!strncmp(string, "[REFERRAL ", 10))
1241 ; /* handled in the imap_referral() callback */
1242 else if(!strncmp(string, "[ALERT]", 7))
1243 q_status_message2(SM_MODAL, 3, 3,
1244 _("Alert received while accessing \"%s\": %s"),
1245 (stream && stream->mailbox)
1246 ? stream->mailbox : "-no folder-",
1247 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000),
1248 SIZEOF_20KBUF-10000, string));
1249 else if(!strncmp(string, "[UNSEEN ", 8)){
1250 char *p;
1251 long n = 0;
1253 for(p = string + 8; isdigit(*p); p++)
1254 n = (n * 10) + (*p - '0');
1256 sp_set_first_unseen(stream, n);
1258 else if(!strncmp(string, "[READ-ONLY]", 11)
1259 && !(stream && stream->mailbox && IS_NEWS(stream)))
1260 q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s",
1261 (stream && stream->mailbox)
1262 ? stream->mailbox : "-no folder-",
1263 string + 11);
1264 else if((errflg && errflg != BYE && errflg != PARSE)
1265 && !ps_global->noshow_error
1266 && !(errflg == WARN
1267 && (ps_global->noshow_warn || (stream && stream->unhealthy))))
1268 q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0),
1269 3, 6, ps_global->last_error);
1273 /*----------------------------------------------------------------------
1274 Queue imap log message for display in the message line
1276 Args: string -- The message
1277 errflg -- flag set to 1 if pertains to an error
1279 Result: Message queued for display
1281 The c-client/imap reports most of it's status and errors here
1282 ---*/
1283 void
1284 mm_log(char *string, long int errflg)
1286 char message[sizeof(ps_global->c_client_error)];
1287 char *occurence;
1288 int was_capitalized;
1289 static char saw_kerberos_init_warning;
1290 time_t now;
1291 struct tm *tm_now;
1293 now = time((time_t *)0);
1294 tm_now = localtime(&now);
1296 dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? 1 :
1297 (errflg == TCPDEBUG) ? 10 : 2,
1298 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1299 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1300 tm_now->tm_mon+1, tm_now->tm_mday,
1301 (!errflg) ? "babble" :
1302 (errflg == ERROR) ? "error" :
1303 (errflg == WARN) ? "warning" :
1304 (errflg == PARSE) ? "parse" :
1305 (errflg == TCPDEBUG) ? "tcp" :
1306 (errflg == BYE) ? "bye" : "unknown",
1307 string ? string : "?"));
1309 if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){
1310 ps_global->try_to_create = 1;
1311 return;
1313 else if(ps_global->try_to_create
1314 || !strncmp(string, "[CLOSED]", 8)
1315 || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op")))
1317 * Don't display if creating new folder OR
1318 * warning about a dead stream ...
1320 return;
1322 strncpy(message, string, sizeof(message));
1323 message[sizeof(message) - 1] = '\0';
1325 if(errflg == WARN && srchstr(message, "try running kinit") != NULL){
1326 if(saw_kerberos_init_warning)
1327 return;
1329 saw_kerberos_init_warning = 1;
1332 /*---- replace all "mailbox" with "folder" ------*/
1333 occurence = srchstr(message, "mailbox");
1334 while(occurence) {
1335 if(!*(occurence+7)
1336 || isspace((unsigned char) *(occurence+7))
1337 || *(occurence+7) == ':'){
1338 was_capitalized = isupper((unsigned char) *occurence);
1339 rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder"));
1340 if(was_capitalized)
1341 *occurence = (errflg == PARSE ? 'A' : 'F');
1343 else
1344 occurence += 7;
1346 occurence = srchstr(occurence, "mailbox");
1349 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1350 occurence = srchstr(message, "GSSAPI");
1351 while(occurence) {
1352 if(!*(occurence+6)
1353 || isspace((unsigned char) *(occurence+6))
1354 || *(occurence+6) == ':')
1355 rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos");
1356 else
1357 occurence += 6;
1359 occurence = srchstr(occurence, "GSSAPI");
1362 if(errflg == ERROR)
1363 ps_global->mm_log_error = 1;
1365 if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error))
1366 strncpy(ps_global->c_client_error, message,
1367 sizeof(ps_global->c_client_error));
1369 if(ps_global->noshow_error
1370 || (ps_global->noshow_warn && errflg == WARN)
1371 || !(errflg == ERROR || errflg == WARN))
1372 return; /* Only care about errors; don't print when asked not to */
1374 /*---- Display the message ------*/
1375 q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER,
1376 3, 5, message);
1377 strncpy(ps_global->last_error, message, sizeof(ps_global->last_error));
1378 ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
1381 void
1382 mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial,
1383 char *method, char *usethisprompt, char *altuserforcache)
1385 if(method == NULL)
1386 return;
1387 if(strucmp(method, OA2NAME) == 0 || strucmp(method, BEARERNAME) == 0)
1388 mm_login_oauth2(mb, user, method, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache);
1391 void
1392 mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial,
1393 char *usethisprompt, char *altuserforcache)
1395 char tmp[MAILTMPLEN];
1396 char prompt[1000], *last;
1397 char port[20], non_def_port[20], insecure[20];
1398 char defuser[NETMAXUSER];
1399 char hostleadin[80], hostname[200], defubuf[200];
1400 char logleadin[80], pwleadin[50];
1401 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
1402 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1403 that the password will be sent unencrypted over the network. This is
1404 just a warning message that gets added parenthetically when the user
1405 is asked for a password. */
1406 char *insec = _(" (INSECURE)");
1407 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1408 after having already failed at least once. */
1409 char *retry = _("Retrying - ");
1410 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1411 char *hostlabel = _("HOST");
1412 /* TRANSLATORS: user is logging in as a particular user (a particular
1413 login name), this is just labelling that user name. */
1414 char *userlabel = _("USER");
1415 STRLIST_S hostlist[2];
1416 HelpType help ;
1417 int len, rc, q_line, flags;
1418 int oespace, avail, need, save_dont_use;
1419 int save_in_init;
1420 struct servent *sv;
1421 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1422 int preserve_password = -1;
1423 #endif
1425 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1426 trial, mb->user ? mb->user : "(null)",
1427 mb->service ? mb->service : "(null)",
1428 mb->port ? " port=" : "",
1429 mb->port ? comatose(mb->port) : "",
1430 altuserforcache ? " altuserforcache =" : "",
1431 altuserforcache ? altuserforcache : ""));
1432 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
1434 save_in_init = ps_global->in_init_seq;
1435 ps_global->in_init_seq = 0;
1436 ps_global->no_newmail_check_from_optionally_enter = 1;
1438 /* make sure errors are seen */
1439 if(ps_global->ttyo)
1440 flush_status_messages(0);
1443 * Add port number to hostname if going through a tunnel or something
1445 non_def_port[0] = '\0';
1446 if(mb->port && mb->service &&
1447 (sv = getservbyname(mb->service, "tcp")) &&
1448 (mb->port != ntohs(sv->s_port))){
1449 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
1450 non_def_port[sizeof(non_def_port)-1] = '\0';
1451 dprint((9, "mm_login: using non-default port=%s\n",
1452 non_def_port ? non_def_port : "?"));
1456 * set up host list for sybil servers...
1458 if(*non_def_port){
1459 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
1460 hostlist0[sizeof(hostlist0)-1] = '\0';
1461 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
1462 hostlist0[sizeof(hostlist0)-1] = '\0';
1463 hostlist[0].name = hostlist0;
1464 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1465 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
1466 hostlist1[sizeof(hostlist1)-1] = '\0';
1467 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
1468 hostlist1[sizeof(hostlist1)-1] = '\0';
1469 hostlist[0].next = &hostlist[1];
1470 hostlist[1].name = hostlist1;
1471 hostlist[1].next = NULL;
1473 else
1474 hostlist[0].next = NULL;
1476 else{
1477 hostlist[0].name = mb->host;
1478 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1479 hostlist[0].next = &hostlist[1];
1480 hostlist[1].name = mb->orighost;
1481 hostlist[1].next = NULL;
1483 else
1484 hostlist[0].next = NULL;
1487 if(hostlist[0].name){
1488 dprint((9, "mm_login: host=%s\n",
1489 hostlist[0].name ? hostlist[0].name : "?"));
1490 if(hostlist[0].next && hostlist[1].name){
1491 dprint((9, "mm_login: orighost=%s\n", hostlist[1].name));
1496 * Initialize user name with either
1497 * 1) /user= value in the stream being logged into,
1498 * or 2) the user name we're running under.
1500 * Note that VAR_USER_ID is not yet initialized if this login is
1501 * the one to access the remote config file. In that case, the user
1502 * can supply the username in the config file name with /user=.
1504 if(trial == 0L && !altuserforcache){
1505 strncpy(user, (*mb->user) ? mb->user :
1506 ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "",
1507 NETMAXUSER);
1508 user[NETMAXUSER-1] = '\0';
1510 /* try last working password associated with this host. */
1511 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1512 (mb->sslflag||mb->tlsflag))){
1513 dprint((9, "mm_login: found a password to try\n"));
1514 ps_global->no_newmail_check_from_optionally_enter = 0;
1515 ps_global->in_init_seq = save_in_init;
1516 return;
1519 #ifdef LOCAL_PASSWD_CACHE
1520 /* check to see if there's a password left over from last session */
1521 if(get_passfile_passwd(ps_global->pinerc, pwd,
1522 user, hostlist, (mb->sslflag||mb->tlsflag))){
1523 imap_set_passwd(&mm_login_list, *pwd, user,
1524 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1525 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1526 (mb->sslflag||mb->tlsflag));
1527 dprint((9, "mm_login: found a password in passfile to try\n"));
1528 ps_global->no_newmail_check_from_optionally_enter = 0;
1529 ps_global->in_init_seq = save_in_init;
1530 return;
1532 #endif /* LOCAL_PASSWD_CACHE */
1535 * If no explicit user name supplied and we've not logged in
1536 * with our local user name, see if we've visited this
1537 * host before as someone else.
1539 if(!*mb->user &&
1540 ((last = imap_get_user(mm_login_list, hostlist))
1541 #ifdef LOCAL_PASSWD_CACHE
1543 (last = get_passfile_user(ps_global->pinerc, hostlist))
1544 #endif /* LOCAL_PASSWD_CACHE */
1546 strncpy(user, last, NETMAXUSER);
1547 user[NETMAXUSER-1] = '\0';
1548 dprint((9, "mm_login: found user=%s\n",
1549 user ? user : "?"));
1551 /* try last working password associated with this host/user. */
1552 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1553 (mb->sslflag||mb->tlsflag))){
1554 dprint((9,
1555 "mm_login: found a password for user=%s to try\n",
1556 user ? user : "?"));
1557 ps_global->no_newmail_check_from_optionally_enter = 0;
1558 ps_global->in_init_seq = save_in_init;
1559 return;
1562 #ifdef LOCAL_PASSWD_CACHE
1563 /* check to see if there's a password left over from last session */
1564 if(get_passfile_passwd(ps_global->pinerc, pwd,
1565 user, hostlist, (mb->sslflag||mb->tlsflag))){
1566 imap_set_passwd(&mm_login_list, *pwd, user,
1567 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1568 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1569 (mb->sslflag||mb->tlsflag));
1570 dprint((9,
1571 "mm_login: found a password for user=%s in passfile to try\n",
1572 user ? user : "?"));
1573 ps_global->no_newmail_check_from_optionally_enter = 0;
1574 ps_global->in_init_seq = save_in_init;
1575 return;
1577 #endif /* LOCAL_PASSWD_CACHE */
1580 #if !defined(DOS) && !defined(OS2)
1581 if(!*mb->user && !*user &&
1582 (last = (ps_global->ui.login && ps_global->ui.login[0])
1583 ? ps_global->ui.login : NULL)
1585 strncpy(user, last, NETMAXUSER);
1586 user[NETMAXUSER-1] = '\0';
1587 dprint((9, "mm_login: found user=%s\n",
1588 user ? user : "?"));
1590 /* try last working password associated with this host. */
1591 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1592 (mb->sslflag||mb->tlsflag))){
1593 dprint((9, "mm_login:ui: found a password to try\n"));
1594 ps_global->no_newmail_check_from_optionally_enter = 0;
1595 ps_global->in_init_seq = save_in_init;
1596 return;
1599 #ifdef LOCAL_PASSWD_CACHE
1600 /* check to see if there's a password left over from last session */
1601 if(get_passfile_passwd(ps_global->pinerc, pwd,
1602 user, hostlist, (mb->sslflag||mb->tlsflag))){
1603 imap_set_passwd(&mm_login_list, *pwd, user,
1604 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1605 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1606 (mb->sslflag||mb->tlsflag));
1607 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1608 ps_global->no_newmail_check_from_optionally_enter = 0;
1609 ps_global->in_init_seq = save_in_init;
1610 return;
1612 #endif /* LOCAL_PASSWD_CACHE */
1614 #endif
1617 user[NETMAXUSER-1] = '\0';
1619 if(trial == 0)
1620 retry = "";
1623 * Even if we have a user now, user gets a chance to change it.
1625 ps_global->mangled_footer = 1;
1626 if(!*mb->user && !altuserforcache){
1628 help = NO_HELP;
1631 * Instead of offering user with a value that the user can edit,
1632 * we offer [user] as a default so that the user can type CR to
1633 * use it. Otherwise, the user has to type in whole name.
1635 strncpy(defuser, user, sizeof(defuser)-1);
1636 defuser[sizeof(defuser)-1] = '\0';
1637 user[0] = '\0';
1640 * Need space for "Retrying - "
1641 * "+ HOST: "
1642 * hostname
1643 * " (INSECURE)"
1644 * ENTER LOGIN NAME
1645 * " [defuser] : "
1646 * about 15 chars for input
1649 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1650 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1651 hostleadin[sizeof(hostleadin)-1] = '\0';
1653 strncpy(hostname, mb->host, sizeof(hostname)-1);
1654 hostname[sizeof(hostname)-1] = '\0';
1657 * Add port number to hostname if going through a tunnel or something
1659 if(*non_def_port)
1660 strncpy(port, non_def_port, sizeof(port));
1661 else
1662 port[0] = '\0';
1664 insecure[0] = '\0';
1665 /* if not encrypted and SSL/TLS is supported */
1666 if(!(mb->sslflag||mb->tlsflag) &&
1667 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1668 strncpy(insecure, insec, sizeof(insecure));
1670 /* TRANSLATORS: user is being asked to type in their login name */
1671 snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME"));
1673 snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "",
1674 (*defuser) ? defuser : "",
1675 (*defuser) ? "]" : "");
1676 defubuf[sizeof(defubuf)-1] = '\0';
1677 /* space reserved after prompt */
1678 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1680 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1681 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1682 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace;
1684 /* If we're retrying cut the hostname back to the first word. */
1685 if(avail < need && trial > 0){
1686 char *p;
1688 len = strlen(hostname);
1689 if((p = strchr(hostname, '.')) != NULL){
1690 *p = '\0';
1691 need -= (len - strlen(hostname));
1695 if(avail < need){
1696 need -= utf8_width(retry);
1697 retry = "";
1699 if(avail < need){
1701 /* reduce length of logleadin */
1702 len = utf8_width(logleadin);
1703 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1704 longer version doesn't fit on screen */
1705 snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN"));
1706 need -= (len - utf8_width(logleadin));
1708 if(avail < need){
1709 /* get two spaces from hostleadin */
1710 len = utf8_width(hostleadin);
1711 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1712 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1713 hostleadin[sizeof(hostleadin)-1] = '\0';
1714 need -= (len - utf8_width(hostleadin));
1716 /* get rid of port */
1717 if(avail < need && strlen(port) > 0){
1718 need -= strlen(port);
1719 port[0] = '\0';
1722 if(avail < need){
1723 int reduce_to;
1726 * Reduce space for hostname. Best we can do is 6 chars
1727 * with hos...
1729 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1730 len = strlen(hostname);
1731 strncpy(hostname+reduce_to-3, "...", 4);
1732 need -= (len - strlen(hostname));
1734 if(avail < need && strlen(insecure) > 0){
1735 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1736 need -= 3;
1737 insecure[strlen(insecure)-4] = ')';
1738 insecure[strlen(insecure)-3] = '\0';
1740 else{
1741 need -= utf8_width(insecure);
1742 insecure[0] = '\0';
1746 if(avail < need){
1747 if(strlen(defubuf) > 3){
1748 len = strlen(defubuf);
1749 strncpy(defubuf, " [..] :", 9);
1750 need -= (len - strlen(defubuf));
1753 if(avail < need)
1754 strncpy(defubuf, ":", 2);
1757 * If it still doesn't fit, optionally_enter gets
1758 * to worry about it.
1766 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s",
1767 retry, hostleadin, hostname, port, insecure, logleadin, defubuf);
1768 prompt[sizeof(prompt)-1] = '\0';
1770 while(1) {
1771 if(ps_global->ttyo)
1772 mm_login_alt_cue(mb);
1774 flags = OE_APPEND_CURRENT;
1775 save_dont_use = ps_global->dont_use_init_cmds;
1776 ps_global->dont_use_init_cmds = 1;
1777 #ifdef _WINDOWS
1778 if(!*user && *defuser){
1779 strncpy(user, defuser, NETMAXUSER);
1780 user[NETMAXUSER-1] = '\0';
1783 rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD,
1784 #ifdef LOCAL_PASSWD_CACHE
1785 is_using_passfile() ? 1 :
1786 #endif /* LOCAL_PASSWD_CACHE */
1787 0, 0, &preserve_password);
1788 ps_global->dont_use_init_cmds = save_dont_use;
1789 if(rc == 0 && *user && *pwd)
1790 goto nopwpmt;
1791 #else /* !_WINDOWS */
1792 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
1793 prompt, NULL, help, &flags);
1794 #endif /* !_WINDOWS */
1795 ps_global->dont_use_init_cmds = save_dont_use;
1797 if(rc == 3) {
1798 help = help == NO_HELP ? h_oe_login : NO_HELP;
1799 continue;
1802 /* default */
1803 if(rc == 0 && !*user){
1804 strncpy(user, defuser, NETMAXUSER);
1805 user[NETMAXUSER-1] = '\0';
1808 if(rc != 4)
1809 break;
1812 if(rc == 1 || !user[0]) {
1813 ps_global->user_says_cancel = (rc == 1);
1814 user[0] = '\0';
1817 else{
1818 strncpy(user, mb->user, NETMAXUSER);
1819 user[NETMAXUSER-1] = '\0';
1822 user[NETMAXUSER-1] = '\0';
1824 if(!(user[0] || altuserforcache)){
1825 ps_global->no_newmail_check_from_optionally_enter = 0;
1826 ps_global->in_init_seq = save_in_init;
1827 return;
1831 * Now that we have a user, we can check in the cache again to see
1832 * if there is a password there. Try last working password associated
1833 * with this host and user.
1835 if(trial == 0L && !*mb->user && !altuserforcache){
1836 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1837 (mb->sslflag||mb->tlsflag))){
1838 ps_global->no_newmail_check_from_optionally_enter = 0;
1839 ps_global->in_init_seq = save_in_init;
1840 return;
1843 #ifdef LOCAL_PASSWD_CACHE
1844 if(get_passfile_passwd(ps_global->pinerc, pwd,
1845 user, hostlist, (mb->sslflag||mb->tlsflag))){
1846 imap_set_passwd(&mm_login_list, *pwd, user,
1847 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1848 ps_global->no_newmail_check_from_optionally_enter = 0;
1849 ps_global->in_init_seq = save_in_init;
1850 return;
1852 #endif /* LOCAL_PASSWD_CACHE */
1854 else if(trial == 0 && altuserforcache){
1855 if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist,
1856 (mb->sslflag||mb->tlsflag))){
1857 ps_global->no_newmail_check_from_optionally_enter = 0;
1858 ps_global->in_init_seq = save_in_init;
1859 return;
1862 #ifdef LOCAL_PASSWD_CACHE
1863 if(get_passfile_passwd(ps_global->pinerc, pwd,
1864 altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
1865 imap_set_passwd(&mm_login_list, *pwd, altuserforcache,
1866 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1867 ps_global->no_newmail_check_from_optionally_enter = 0;
1868 ps_global->in_init_seq = save_in_init;
1869 return;
1871 #endif /* LOCAL_PASSWD_CACHE */
1875 * Didn't find password in cache or this isn't the first try. Ask user.
1877 help = NO_HELP;
1880 * Need space for "Retrying - "
1881 * "+ HOST: "
1882 * hostname
1883 * " (INSECURE) "
1884 * " USER: "
1885 * user
1886 * " ENTER PASSWORD: "
1887 * about 15 chars for input
1890 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1891 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1893 strncpy(hostname, mb->host, sizeof(hostname)-1);
1894 hostname[sizeof(hostname)-1] = '\0';
1897 * Add port number to hostname if going through a tunnel or something
1899 if(*non_def_port)
1900 strncpy(port, non_def_port, sizeof(port));
1901 else
1902 port[0] = '\0';
1904 insecure[0] = '\0';
1906 /* if not encrypted and SSL/TLS is supported */
1907 if(!(mb->sslflag||mb->tlsflag) &&
1908 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1909 strncpy(insecure, insec, sizeof(insecure));
1911 if(usethisprompt){
1912 strncpy(logleadin, usethisprompt, sizeof(logleadin));
1913 logleadin[sizeof(logleadin)-1] = '\0';
1914 defubuf[0] = '\0';
1915 user[0] = '\0';
1917 else{
1918 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1920 strncpy(defubuf, user, sizeof(defubuf)-1);
1921 defubuf[sizeof(defubuf)-1] = '\0';
1924 /* TRANSLATORS: user is being asked to type in their password */
1925 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD"));
1927 /* space reserved after prompt */
1928 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1930 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1931 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1932 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) +
1933 utf8_width(pwleadin) + oespace;
1935 if(avail < need && trial > 0){
1936 char *p;
1938 len = strlen(hostname);
1939 if((p = strchr(hostname, '.')) != NULL){
1940 *p = '\0';
1941 need -= (len - strlen(hostname));
1945 if(avail < need){
1946 need -= utf8_width(retry);
1947 retry = "";
1949 if(avail < need){
1951 if(!usethisprompt){
1952 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1953 need--;
1956 rplstr(pwleadin, sizeof(pwleadin), 1, "");
1957 need--;
1959 if(avail < need){
1960 /* get two spaces from hostleadin */
1961 len = utf8_width(hostleadin);
1962 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1963 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1964 hostleadin[sizeof(hostleadin)-1] = '\0';
1965 need -= (len - utf8_width(hostleadin));
1967 /* get rid of port */
1968 if(avail < need && strlen(port) > 0){
1969 need -= strlen(port);
1970 port[0] = '\0';
1973 if(avail < need){
1974 len = utf8_width(pwleadin);
1975 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
1976 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD"));
1977 need -= (len - utf8_width(pwleadin));
1981 if(avail < need){
1982 int reduce_to;
1985 * Reduce space for hostname. Best we can do is 6 chars
1986 * with hos...
1988 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1989 len = strlen(hostname);
1990 strncpy(hostname+reduce_to-3, "...", 4);
1991 need -= (len - strlen(hostname));
1993 if(avail < need && strlen(insecure) > 0){
1994 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1995 need -= 3;
1996 insecure[strlen(insecure)-4] = ')';
1997 insecure[strlen(insecure)-3] = '\0';
1999 else{
2000 need -= utf8_width(insecure);
2001 insecure[0] = '\0';
2005 if(avail < need){
2006 len = utf8_width(logleadin);
2007 strncpy(logleadin, " ", sizeof(logleadin));
2008 logleadin[sizeof(logleadin)-1] = '\0';
2009 need -= (len - utf8_width(logleadin));
2011 if(avail < need){
2012 reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0;
2013 if(reduce_to)
2014 strncpy(defubuf+reduce_to-3, "...", 4);
2015 else
2016 defubuf[0] = '\0';
2023 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s",
2024 retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
2025 prompt[sizeof(prompt)-1] = '\0';
2027 tmp[0] = '\0';
2028 while(1) {
2029 if(ps_global->ttyo)
2030 mm_login_alt_cue(mb);
2032 save_dont_use = ps_global->dont_use_init_cmds;
2033 ps_global->dont_use_init_cmds = 1;
2034 flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
2035 flags |= OE_KEEP_TRAILING_SPACE;
2036 #ifdef _WINDOWS
2038 char *tmpp;
2039 tmpp = fs_get(NETMAXPASSWD*sizeof(char));
2040 rc = os_login_dialog(mb, user, NETMAXUSER, &tmpp, NETMAXPASSWD, 0, 1,
2041 &preserve_password);
2042 strncpy(tmp, tmpp, sizeof(tmp));
2043 tmp[sizeof(tmp)-1] = '\0';
2044 if(tmpp) fs_give((void **)&tmpp);
2046 #else /* !_WINDOWS */
2047 rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD,
2048 prompt, NULL, help, &flags);
2049 #endif /* !_WINDOWS */
2050 if(rc != 1) *pwd = cpystr(tmp);
2051 ps_global->dont_use_init_cmds = save_dont_use;
2053 if(rc == 3) {
2054 help = help == NO_HELP ? h_oe_passwd : NO_HELP;
2056 else if(rc == 4){
2058 else
2059 break;
2062 if(rc == 1 || !tmp[0]) {
2063 ps_global->user_says_cancel = (rc == 1);
2064 user[0] = '\0';
2065 ps_global->no_newmail_check_from_optionally_enter = 0;
2066 ps_global->in_init_seq = save_in_init;
2067 return;
2070 #ifdef _WINDOWS
2071 nopwpmt:
2072 #endif
2073 /* remember the password for next time */
2074 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
2075 imap_set_passwd(&mm_login_list, *pwd,
2076 altuserforcache ? altuserforcache : user, hostlist,
2077 (mb->sslflag||mb->tlsflag), 0, 0);
2078 #ifdef LOCAL_PASSWD_CACHE
2079 /* if requested, remember it on disk for next session */
2080 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
2081 set_passfile_passwd(ps_global->pinerc, *pwd,
2082 altuserforcache ? altuserforcache : user, hostlist,
2083 (mb->sslflag||mb->tlsflag),
2084 (preserve_password == -1 ? 0
2085 : (preserve_password == 0 ? 2 :1)));
2086 #endif /* LOCAL_PASSWD_CACHE */
2088 ps_global->no_newmail_check_from_optionally_enter = 0;
2092 void
2093 mm_login_alt_cue(NETMBX *mb)
2095 if(ps_global->ttyo){
2096 COLOR_PAIR *lastc;
2098 lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
2099 ps_global->VAR_TITLE_BACK_COLOR,
2100 PSC_REV | PSC_RET);
2102 mark_titlebar_dirty();
2103 PutLine0(0, ps_global->ttyo->screen_cols - 1,
2104 (mb->sslflag||mb->tlsflag) ? "+" : " ");
2106 if(lastc){
2107 (void)pico_set_colorp(lastc, PSC_NONE);
2108 free_color_pair(&lastc);
2111 fflush(stdout);
2116 /*----------------------------------------------------------------------
2117 Receive notification of an error writing to disk
2119 Args: stream -- The stream the error occurred on
2120 errcode -- The system error code (errno)
2121 serious -- Flag indicating error is serious (mail may be lost)
2123 Result: If error is non serious, the stream is marked as having an error
2124 and deletes are disallowed until error clears
2125 If error is serious this goes modal, allowing the user to retry
2126 or get a shell escape to fix the condition. When the condition is
2127 serious it means that mail existing in the mailbox will be lost
2128 if Pine exits without writing, so we try to induce the user to
2129 fix the error, go get someone that can fix the error, or whatever
2130 and don't provide an easy way out.
2131 ----*/
2132 long
2133 mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious)
2135 int i, j;
2136 char *p, *q, *s;
2137 static ESCKEY_S de_opts[] = {
2138 {'r', 'r', "R", "Retry"},
2139 {'f', 'f', "F", "FileBrowser"},
2140 {'s', 's', "S", "ShellPrompt"},
2141 {-1, 0, NULL, NULL}
2143 #define DE_COLS (ps_global->ttyo->screen_cols)
2144 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2146 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2147 #define DE_PMT \
2148 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2149 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2150 #define DE_STR2 \
2151 "The reported error number is %s. The last reported mail error was:"
2152 static char *de_msg[] = {
2153 "Please try to correct the error preventing Alpine from saving your",
2154 "mail folder. For example if the disk is out of space try removing",
2155 "unneeded files. You might also contact your system administrator.",
2157 "Both Alpine's File Browser and an option to enter the system's",
2158 "command prompt are offered to aid in fixing the problem. When",
2159 "you believe the problem is resolved, choose the \"Retry\" option.",
2160 "Be aware that messages may be lost or this folder left in an",
2161 "inaccessible condition if you exit or kill Alpine before the problem",
2162 "is resolved.",
2163 NULL};
2164 static char *de_shell_msg[] = {
2165 "\n\nPlease attempt to correct the error preventing saving of the",
2166 "mail folder. If you do not know how to correct the problem, contact",
2167 "your system administrator. To return to Alpine, type \"exit\".",
2168 NULL};
2170 dprint((0,
2171 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2172 DE_FOLDER(stream), errcode, serious ? "" : "not "));
2173 dprint((0, "***** message: \"%s\"\n\n",
2174 ps_global->last_error ? ps_global->last_error : "?"));
2176 if(!serious) {
2177 sp_set_io_error_on_stream(stream, 1);
2178 return (1) ;
2181 while(1){
2182 /* replace pine's body display with screen full of explanatory text */
2183 ClearLine(2);
2184 PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1)
2185 - strlen(DE_FOLDER(stream)))/2, 0),
2186 DE_STR1, DE_FOLDER(stream));
2187 ClearLine(3);
2188 PutLine1(3, 4, DE_STR2, long2string(errcode));
2190 PutLine0(4, 0, " \"");
2191 removing_leading_white_space(ps_global->last_error);
2192 for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){
2193 for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++)
2194 if(isspace((unsigned char)*q))
2195 s = q;
2197 if(*q && s)
2198 q = s;
2200 while(p < q)
2201 Writechar(*p++, 0);
2203 if(*(p = q)){
2204 ClearLine(++i);
2205 PutLine0(i, 0, " ");
2206 while(*p && isspace((unsigned char)*p))
2207 p++;
2209 else{
2210 Writechar('\"', 0);
2211 CleartoEOLN();
2212 break;
2216 ClearLine(++i);
2217 for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){
2218 ClearLine(i);
2219 PutLine0(i, 0, " ");
2220 Write_to_screen(de_msg[i-j]);
2223 while(i < DE_LINE)
2224 ClearLine(i++);
2226 switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts,
2227 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){
2228 case 'r' : /* Retry! */
2229 ps_global->mangled_screen = 1;
2230 return(0L);
2232 case 'f' : /* File Browser */
2234 char full_filename[MAXPATH+1], filename[MAXPATH+1];
2236 filename[0] = '\0';
2237 build_path(full_filename, ps_global->home_dir, filename,
2238 sizeof(full_filename));
2239 file_lister("DISK ERROR", full_filename, sizeof(full_filename),
2240 filename, sizeof(filename), FALSE, FB_SAVE);
2243 break;
2245 case 's' :
2246 EndInverse();
2247 end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0);
2248 end_tty_driver(ps_global);
2249 for(i = 0; de_shell_msg[i]; i++)
2250 puts(de_shell_msg[i]);
2253 * Don't use our piping mechanism to spawn a subshell here
2254 * since it will the server (thus reentering c-client).
2255 * Bad thing to do.
2257 #ifdef _WINDOWS
2258 #else
2259 system("csh");
2260 #endif
2261 init_tty_driver(ps_global);
2262 init_keyboard(F_ON(F_USE_FK,ps_global));
2263 break;
2266 if(ps_global->redrawer)
2267 (*ps_global->redrawer)();
2272 long
2273 pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host)
2275 long rv = 1L;
2276 char pmt[128];
2278 #ifdef _WINDOWS
2279 mswin_killsplash();
2280 #endif
2282 if(elapsed >= (long)ps_global->tcp_query_timeout){
2283 snprintf(pmt, sizeof(pmt),
2284 _("No reply in %s seconds from server %s. Break connection"),
2285 long2string(elapsed), host);
2286 pmt[sizeof(pmt)-1] = '\0';
2287 if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
2288 ps_global->user_says_cancel = 1;
2289 return(0L);
2293 ps_global->tcptimeout = 0;
2294 return(rv);
2299 * -------------------------------------------------------------
2300 * These are declared in pith/imap.h as mandatory to implement.
2301 * -------------------------------------------------------------
2306 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2308 long
2309 pine_tcptimeout(long int elapsed, long int sincelast, char *host)
2311 long rv = 1L; /* keep trying by default */
2312 unsigned long ch;
2314 ps_global->tcptimeout = 1;
2315 #ifdef DEBUG
2316 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2317 long2string(elapsed), host));
2318 if(debugfile)
2319 fflush(debugfile);
2320 #endif
2322 #ifdef _WINDOWS
2323 mswin_killsplash();
2324 #endif
2326 if(ps_global->noshow_timeout)
2327 return(rv);
2329 if(ps_global->can_interrupt
2330 && ps_global->close_connection_timeout > 0L
2331 && elapsed >= (long)ps_global->tcp_query_timeout
2332 && elapsed >= (long)ps_global->close_connection_timeout){
2333 ps_global->can_interrupt = 0; /* do not return here */
2334 ps_global->read_bail = 0;
2335 ps_global->user_says_cancel = 1;
2336 return 0;
2339 if(!ps_global->ttyo)
2340 return(pine_tcptimeout_noscreen(elapsed, sincelast, host));
2342 suspend_busy_cue();
2345 * Prompt after a minute (since by then things are probably really bad)
2346 * A prompt timeout means "keep trying"...
2348 if(elapsed >= (long)ps_global->tcp_query_timeout){
2349 int clear_inverse;
2351 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2352 if((clear_inverse = !InverseState()) != 0)
2353 StartInverse();
2355 Writechar(BELL, 0);
2357 PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0,
2358 _("No reply in %s seconds from server %s. Break connection?"),
2359 long2string(elapsed), host);
2360 CleartoEOLN();
2361 fflush(stdout);
2362 flush_input();
2363 ch = read_char(7);
2364 if(ps_global->read_bail || ch == 'y' || ch == 'Y'){
2365 ps_global->read_bail = 0;
2366 ps_global->user_says_cancel = 1;
2367 rv = 0L;
2370 if(clear_inverse)
2371 EndInverse();
2373 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2376 if(rv == 1L){ /* just warn 'em something's up */
2377 q_status_message2(SM_ORDER, 0, 0,
2378 _("No reply in %s seconds from server %s. Still Waiting..."),
2379 long2string(elapsed), host);
2380 flush_status_messages(0); /* make sure it's seen */
2383 mark_status_dirty(); /* make sure it gets cleared */
2385 resume_busy_cue((rv == 1) ? 3 : 0);
2386 ps_global->tcptimeout = 0;
2388 return(rv);
2391 QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota)
2393 QUOTALIST *cquota = NULL;
2395 if(pquota){
2396 cquota = mail_newquotalist();
2397 if (pquota->name && *pquota->name)
2398 cquota->name = cpystr(pquota->name);
2399 cquota->usage = pquota->usage;
2400 cquota->limit = pquota->limit;
2401 if (pquota->next)
2402 cquota->next = pine_quotalist_copy(pquota->next);
2404 return cquota;
2408 /* c-client callback to handle quota */
2410 void
2411 pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2413 ps_global->quota = pine_quotalist_copy (pquota);
2417 * C-client callback to handle SSL/TLS certificate validation failures
2419 * Returning 0 means error becomes fatal
2420 * Non-zero means certificate problem is ignored and SSL session is
2421 * established
2423 * We remember the answer and won't re-ask for subsequent open attempts to
2424 * the same hostname.
2426 long
2427 pine_sslcertquery(char *reason, char *host, char *cert)
2429 char tmp[500];
2430 char *unknown = "<unknown>";
2431 long rv = 0L;
2432 STRLIST_S hostlist;
2433 int ok_novalidate = 0, warned = 0;
2435 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2436 host ? host : "?", reason ? reason : "?",
2437 cert ? cert : "?"));
2439 hostlist.name = host ? host : "";
2440 hostlist.next = NULL;
2443 * See if we've been asked about this host before.
2445 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2446 /* we were asked before, did we say Yes? */
2447 if(ok_novalidate)
2448 rv++;
2450 if(rv){
2451 dprint((5,
2452 "sslcertificatequery: approved automatically\n"));
2453 return(rv);
2456 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2459 if(ps_global->ttyo){
2460 SCROLL_S sargs;
2461 STORE_S *in_store, *out_store;
2462 gf_io_t pc, gc;
2463 HANDLE_S *handles = NULL;
2464 int the_answer = 'n';
2466 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
2467 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
2468 goto try_wantto;
2470 so_puts(in_store, "<HTML><P>");
2471 so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server"));
2473 so_puts(in_store, "<P><CENTER>");
2474 so_puts(in_store, host ? host : unknown);
2475 so_puts(in_store, "</CENTER>");
2477 so_puts(in_store, "<P>");
2478 so_puts(in_store, _("The reason for the failure was"));
2480 /* squirrel away details */
2481 if(details_host)
2482 fs_give((void **)&details_host);
2483 if(details_reason)
2484 fs_give((void **)&details_reason);
2485 if(details_cert)
2486 fs_give((void **)&details_cert);
2488 details_host = cpystr(host ? host : unknown);
2489 details_reason = cpystr(reason ? reason : unknown);
2490 details_cert = cpystr(cert ? cert : unknown);
2492 so_puts(in_store, "<P><CENTER>");
2493 snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2494 reason ? reason : unknown);
2495 tmp[sizeof(tmp)-1] = '\0';
2497 so_puts(in_store, tmp);
2498 so_puts(in_store, "</CENTER>");
2500 so_puts(in_store, "<P>");
2501 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."));
2503 so_puts(in_store, "<P>");
2504 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"));
2506 so_puts(in_store, "<P><CENTER>");
2507 so_puts(in_store, "/novalidate-cert");
2508 so_puts(in_store, "</CENTER>");
2510 so_puts(in_store, "<P>");
2511 so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2513 so_puts(in_store, "<P><CENTER>");
2514 so_puts(in_store, host ? host : unknown);
2515 so_puts(in_store, "</CENTER>");
2517 so_puts(in_store, "<P>");
2518 so_puts(in_store, _("in your configuration, replace those characters with"));
2520 so_puts(in_store, "<P><CENTER>");
2521 so_puts(in_store, host ? host : unknown);
2522 so_puts(in_store, "/novalidate-cert");
2523 so_puts(in_store, "</CENTER>");
2525 so_puts(in_store, "<P>");
2526 so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2528 so_seek(in_store, 0L, 0);
2529 init_handles(&handles);
2530 gf_filter_init();
2531 gf_link_filter(gf_html2plain,
2532 gf_html2plain_opt(NULL,
2533 ps_global->ttyo->screen_cols, non_messageview_margin(),
2534 &handles, NULL, GFHP_LOCAL_HANDLES));
2535 gf_set_so_readc(&gc, in_store);
2536 gf_set_so_writec(&pc, out_store);
2537 gf_pipe(gc, pc);
2538 gf_clear_so_writec(out_store);
2539 gf_clear_so_readc(in_store);
2541 memset(&sargs, 0, sizeof(SCROLL_S));
2542 sargs.text.handles = handles;
2543 sargs.text.text = so_text(out_store);
2544 sargs.text.src = CharStar;
2545 sargs.text.desc = _("help text");
2546 sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2547 sargs.proc.tool = answer_cert_failure;
2548 sargs.proc.data.p = (void *)&the_answer;
2549 sargs.keys.menu = &ans_certquery_keymenu;
2550 /* don't want to re-enter c-client */
2551 sargs.quell_newmail = 1;
2552 setbitmap(sargs.keys.bitmap);
2553 sargs.help.text = h_tls_validation_failure;
2554 sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
2556 scrolltool(&sargs);
2558 if(the_answer == 'y')
2559 rv++;
2560 else if(the_answer == 'n')
2561 ps_global->user_says_cancel = 1;
2563 ps_global->mangled_screen = 1;
2564 ps_global->painted_body_on_startup = 0;
2565 ps_global->painted_footer_on_startup = 0;
2566 so_give(&in_store);
2567 so_give(&out_store);
2568 free_handles(&handles);
2569 if(details_host)
2570 fs_give((void **)&details_host);
2571 if(details_reason)
2572 fs_give((void **)&details_reason);
2573 if(details_cert)
2574 fs_give((void **)&details_cert);
2576 else{
2578 * If screen hasn't been initialized yet, use want_to.
2580 try_wantto:
2581 memset((void *)tmp, 0, sizeof(tmp));
2582 strncpy(tmp,
2583 reason ? reason : _("SSL/TLS certificate validation failure"),
2584 sizeof(tmp));
2585 tmp[sizeof(tmp)-1] = '\0';
2586 strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
2588 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
2589 rv++;
2592 if(rv == 0)
2593 q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
2594 host ? host : unknown);
2596 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
2598 dprint((5, "sslcertificatequery: %s\n",
2599 rv ? "approved" : "rejected"));
2601 return(rv);
2605 char *
2606 pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
2608 char buf[MAILTMPLEN];
2610 if((can_access(mulname, ACCESS_EXISTS) == 0)
2611 || !(can_access(name, ACCESS_EXISTS) == 0))
2612 return(mulname);
2614 snprintf(buf, sizeof(buf),
2615 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2616 last_cmpnt(name),
2617 strlen(last_cmpnt(name)) > 15 ? "..." : "");
2618 buf[sizeof(buf)-1] = '\0';
2619 if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
2620 rename_file(name, mulname);
2621 return(mulname);
2626 url_local_certdetails(char *url)
2628 if(!struncmp(url, "x-alpine-cert:", 14)){
2629 STORE_S *store;
2630 SCROLL_S sargs;
2631 char *folded;
2633 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
2634 q_status_message(SM_ORDER | SM_DING, 7, 10,
2635 _("Error allocating space for details."));
2636 return(0);
2639 so_puts(store, _("Host given by user:\n\n "));
2640 so_puts(store, details_host);
2641 so_puts(store, _("\n\nReason for failure:\n\n "));
2642 so_puts(store, details_reason);
2643 so_puts(store, _("\n\nCertificate being verified:\n\n"));
2644 folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
2645 so_puts(store, folded);
2646 fs_give((void **)&folded);
2647 so_puts(store, "\n");
2649 memset(&sargs, 0, sizeof(SCROLL_S));
2650 sargs.text.text = so_text(store);
2651 sargs.text.src = CharStar;
2652 sargs.text.desc = _("Details");
2653 sargs.bar.title = _("CERT VALIDATION DETAILS");
2654 sargs.help.text = NO_HELP;
2655 sargs.help.title = NULL;
2656 sargs.quell_newmail = 1;
2657 sargs.help.text = h_tls_failure_details;
2658 sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
2660 scrolltool(&sargs);
2662 so_give(&store); /* free resources associated with store */
2663 ps_global->mangled_screen = 1;
2664 return(1);
2667 return(0);
2672 * C-client callback to handle SSL/TLS certificate validation failures
2674 void
2675 pine_sslfailure(char *host, char *reason, long unsigned int flags)
2677 SCROLL_S sargs;
2678 STORE_S *store;
2679 int the_answer = 'n', indent, len, cols;
2680 char buf[500], buf2[500];
2681 char *folded;
2682 char *hst = host ? host : "<unknown>";
2683 char *rsn = reason ? reason : "<unknown>";
2684 char *notls = "/notls";
2685 STRLIST_S hostlist;
2686 int ok_novalidate = 0, warned = 0;
2689 dprint((1, "sslfailure: host=%s reason=%s\n",
2690 hst ? hst : "?",
2691 rsn ? rsn : "?"));
2693 if(flags & NET_SILENT)
2694 return;
2696 hostlist.name = host ? host : "";
2697 hostlist.next = NULL;
2700 * See if we've been told about this host before.
2702 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2703 /* we were told already */
2704 if(warned){
2705 snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn);
2706 buf[sizeof(buf)-1] = '\0';
2707 mm_log(buf, ERROR);
2708 return;
2712 cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2713 cols--;
2715 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS)))
2716 return;
2718 strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf));
2719 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2720 so_puts(store, folded);
2721 fs_give((void **)&folded);
2722 so_puts(store, "\n");
2724 if((len=strlen(hst)) <= cols){
2725 if((indent=((cols-len)/2)) > 0)
2726 so_puts(store, repeat_char(indent, SPACE));
2728 so_puts(store, hst);
2729 so_puts(store, "\n");
2731 else{
2732 strncpy(buf, hst, sizeof(buf));
2733 buf[sizeof(buf)-1] = '\0';
2734 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2735 so_puts(store, folded);
2736 fs_give((void **)&folded);
2739 so_puts(store, "\n");
2741 strncpy(buf, _("The reason for the failure was"), sizeof(buf));
2742 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2743 so_puts(store, folded);
2744 fs_give((void **)&folded);
2745 so_puts(store, "\n");
2747 if((len=strlen(rsn)) <= cols){
2748 if((indent=((cols-len)/2)) > 0)
2749 so_puts(store, repeat_char(indent, SPACE));
2751 so_puts(store, rsn);
2752 so_puts(store, "\n");
2754 else{
2755 strncpy(buf, rsn, sizeof(buf));
2756 buf[sizeof(buf)-1] = '\0';
2757 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2758 so_puts(store, folded);
2759 fs_give((void **)&folded);
2762 so_puts(store, "\n");
2764 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));
2765 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2766 so_puts(store, folded);
2767 fs_give((void **)&folded);
2768 so_puts(store, "\n");
2770 if((len=strlen(notls)) <= cols){
2771 if((indent=((cols-len)/2)) > 0)
2772 so_puts(store, repeat_char(indent, SPACE));
2774 so_puts(store, notls);
2775 so_puts(store, "\n");
2777 else{
2778 strncpy(buf, notls, sizeof(buf));
2779 buf[sizeof(buf)-1] = '\0';
2780 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2781 so_puts(store, folded);
2782 fs_give((void **)&folded);
2785 so_puts(store, "\n");
2787 strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2788 sizeof(buf));
2789 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2790 so_puts(store, folded);
2791 fs_give((void **)&folded);
2792 so_puts(store, "\n");
2794 if((len=strlen(hst)) <= cols){
2795 if((indent=((cols-len)/2)) > 0)
2796 so_puts(store, repeat_char(indent, SPACE));
2798 so_puts(store, hst);
2799 so_puts(store, "\n");
2801 else{
2802 strncpy(buf, hst, sizeof(buf));
2803 buf[sizeof(buf)-1] = '\0';
2804 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2805 so_puts(store, folded);
2806 fs_give((void **)&folded);
2809 so_puts(store, "\n");
2811 strncpy(buf, _("in your configuration, replace those characters with"),
2812 sizeof(buf));
2813 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2814 so_puts(store, folded);
2815 fs_give((void **)&folded);
2816 so_puts(store, "\n");
2818 snprintf(buf2, sizeof(buf2), "%s%s", hst, notls);
2819 buf2[sizeof(buf2)-1] = '\0';
2820 if((len=strlen(buf2)) <= cols){
2821 if((indent=((cols-len)/2)) > 0)
2822 so_puts(store, repeat_char(indent, SPACE));
2824 so_puts(store, buf2);
2825 so_puts(store, "\n");
2827 else{
2828 strncpy(buf, buf2, sizeof(buf));
2829 buf[sizeof(buf)-1] = '\0';
2830 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2831 so_puts(store, folded);
2832 fs_give((void **)&folded);
2835 so_puts(store, "\n");
2837 if(ps_global->ttyo){
2838 strncpy(buf, _("Type RETURN to continue."), sizeof(buf));
2839 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2840 so_puts(store, folded);
2841 fs_give((void **)&folded);
2844 memset(&sargs, 0, sizeof(SCROLL_S));
2845 sargs.text.text = so_text(store);
2846 sargs.text.src = CharStar;
2847 sargs.text.desc = _("help text");
2848 sargs.bar.title = _("SSL/TLS FAILURE");
2849 sargs.proc.tool = answer_cert_failure;
2850 sargs.proc.data.p = (void *)&the_answer;
2851 sargs.keys.menu = &ans_certfail_keymenu;
2852 setbitmap(sargs.keys.bitmap);
2853 /* don't want to re-enter c-client */
2854 sargs.quell_newmail = 1;
2855 sargs.help.text = h_tls_failure;
2856 sargs.help.title = _("HELP FOR TLS/SSL FAILURE");
2858 if(ps_global->ttyo)
2859 scrolltool(&sargs);
2860 else{
2861 char **q, **qp;
2862 char *p;
2863 unsigned char c;
2864 int cnt = 0;
2867 * The screen isn't initialized yet, which should mean that this
2868 * is the result of a -p argument. Display_args_err knows how to deal
2869 * with the uninitialized screen, so we mess with the data to get it
2870 * in shape for display_args_err. This is pretty hacky.
2873 so_seek(store, 0L, 0); /* rewind */
2874 /* count the lines */
2875 while(so_readc(&c, store))
2876 if(c == '\n')
2877 cnt++;
2879 qp = q = (char **)fs_get((cnt+1) * sizeof(char *));
2880 memset(q, 0, (cnt+1) * sizeof(char *));
2882 so_seek(store, 0L, 0); /* rewind */
2883 p = buf;
2884 while(so_readc(&c, store)){
2885 if(c == '\n'){
2886 *p = '\0';
2887 *qp++ = cpystr(buf);
2888 p = buf;
2890 else
2891 *p++ = c;
2894 display_args_err(NULL, q, 0);
2895 free_list_array(&q);
2898 ps_global->mangled_screen = 1;
2899 ps_global->painted_body_on_startup = 0;
2900 ps_global->painted_footer_on_startup = 0;
2901 so_give(&store);
2903 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1);
2908 answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2910 int rv = 1;
2912 ps_global->next_screen = SCREEN_FUN_NULL;
2914 switch(cmd){
2915 case MC_YES :
2916 *(int *)(sparms->proc.data.p) = 'y';
2917 break;
2919 case MC_NO :
2920 *(int *)(sparms->proc.data.p) = 'n';
2921 break;
2923 default:
2924 alpine_panic("Unexpected command in answer_cert_failure");
2925 break;
2928 return(rv);
2933 oauth2_auth_answer(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2935 int rv = 1, rc;
2936 AUTH_CODE_S user;
2937 int q_line, flags;
2938 /* TRANSLATORS: user needs to input an access code from the server */
2939 char *accesscodelabel = _("Copy and Paste Access Code");
2940 char token[MAILTMPLEN], prompt[MAILTMPLEN];
2942 ps_global->next_screen = SCREEN_FUN_NULL;
2944 token[0] = '\0';
2945 switch(cmd){
2946 case MC_YES :
2947 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
2948 flags = OE_APPEND_CURRENT;
2949 sprintf(prompt, "%s: ", accesscodelabel);
2950 do {
2951 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
2952 prompt, NULL, NO_HELP, &flags);
2953 } while (rc != 0 && rc != 1);
2954 user.code = rc == 0 ? cpystr(token) : NULL;
2955 user.answer = 'e';
2956 rv = rc == 1 ? 0 : 1;
2957 break;
2959 case MC_NO :
2960 user.code = NULL;
2961 user.answer = 'e';
2962 break;
2964 default:
2965 alpine_panic("Unexpected command in oauth2_auth_answer");
2966 break;
2968 *(AUTH_CODE_S *) sparms->proc.data.p = user;
2969 return(rv);
2973 /*----------------------------------------------------------------------
2974 This can be used to prevent the flickering of the check_cue char
2975 caused by numerous (5000+) fetches by c-client. Right now, the only
2976 practical use found is newsgroup subsciption.
2978 check_cue_display will check if this global is set, and won't clear
2979 the check_cue_char if set.
2980 ----*/
2981 void
2982 set_read_predicted(int i)
2984 ps_global->read_predicted = i==1;
2985 #ifdef _WINDOWS
2986 if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global))
2987 check_cue_display(" ");
2988 #endif
2992 /*----------------------------------------------------------------------
2993 Exported method to retrieve logged in user name associated with stream
2995 Args: host -- host to find associated login name with.
2997 Result:
2998 ----*/
2999 void *
3000 pine_block_notify(int reason, void *data)
3002 switch(reason){
3003 case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */
3004 break;
3006 case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */
3007 break;
3009 case BLOCK_TCPWRITE: /* blocked on TCP write */
3010 case BLOCK_FILELOCK: /* blocked on file locking */
3011 #ifdef _WINDOWS
3012 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3013 check_cue_display(">");
3015 mswin_setcursor(MSWIN_CURSOR_BUSY);
3016 #endif
3017 break;
3019 case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */
3020 case BLOCK_TCPOPEN: /* blocked on TCP open */
3021 case BLOCK_TCPREAD: /* blocked on TCP read */
3022 case BLOCK_TCPCLOSE: /* blocked on TCP close */
3023 #ifdef _WINDOWS
3024 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3025 check_cue_display("<");
3027 mswin_setcursor(MSWIN_CURSOR_BUSY);
3028 #endif
3029 break;
3031 default :
3032 case BLOCK_NONE: /* not blocked */
3033 #ifdef _WINDOWS
3034 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3035 check_cue_display(" ");
3036 #endif
3037 break;
3041 return(NULL);
3045 void
3046 mm_expunged_current(long unsigned int rawno)
3048 /* expunged something we're viewing? */
3049 if(!ps_global->expunge_in_progress
3050 && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno))
3051 && (ps_global->prev_screen == mail_view_screen
3052 || ps_global->prev_screen == attachment_screen))){
3053 ps_global->next_screen = mail_index_screen;
3054 q_status_message(SM_ORDER | SM_DING , 3, 3,
3055 "Message you were viewing is gone!");
3060 #ifdef PASSFILE
3063 * Specific functions to support caching username/passwd/host
3064 * triples on disk for use from one session to the next...
3067 #define FIRSTCH 0x20
3068 #define LASTCH 0x7e
3069 #define TABSZ (LASTCH - FIRSTCH + 1)
3071 static int xlate_key;
3075 * xlate_in() - xlate_in the given character
3077 char
3078 xlate_in(int c)
3080 register int eti;
3082 eti = xlate_key;
3083 if((c >= FIRSTCH) && (c <= LASTCH)){
3084 eti += (c - FIRSTCH);
3085 eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0;
3086 return((xlate_key = eti) + FIRSTCH);
3088 else
3089 return(c);
3094 * xlate_out() - xlate_out the given character
3096 char
3097 xlate_out(char c)
3099 register int dti;
3100 register int xch;
3102 if((c >= FIRSTCH) && (c <= LASTCH)){
3103 xch = c - (dti = xlate_key);
3104 xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0;
3105 dti = (xch - FIRSTCH) + dti;
3106 dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0;
3107 xlate_key = dti;
3108 return(xch);
3110 else
3111 return(c);
3113 #endif /* PASSFILE */
3116 #ifdef LOCAL_PASSWD_CACHE
3119 int
3120 line_get(char *tmp, size_t len, char **textp)
3122 char *s;
3124 tmp[0] = '\0';
3125 if (*textp == NULL
3126 || (s = strchr(*textp, '\n')) == NULL
3127 || (s - *textp) > len - 1)
3128 return 0;
3130 *s = '\0';
3131 if(*(s-1) == '\r')
3132 *(s-1) = '\0';
3134 snprintf(tmp, len, "%s\n", *textp);
3135 tmp[len-1] = '\0';
3136 *textp = s+1;
3138 return 1;
3141 * For UNIX:
3142 * Passfile lines are
3144 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3146 * In pine4.40 and before there was no orig_hostname, and there still isn't
3147 * if it is the same as hostname.
3149 * else for WINDOWS:
3150 * Use Windows credentials. The TargetName of the credential is
3151 * UWash_Alpine_<hostname:port>\tuser\taltflag
3152 * and the blob consists of
3153 * passwd\torighost (if different from host)
3155 * We don't use anything fancy we just copy out all the credentials which
3156 * begin with TNAME and put them into our cache, so we don't lookup based
3157 * on the TargetName or anything like that. That was so we could re-use
3158 * the existing code and so that orighost data could be easily used.
3161 read_passfile(pinerc, l)
3162 char *pinerc;
3163 MMLOGIN_S **l;
3165 #ifdef WINCRED
3166 # if (WINCRED > 0)
3167 LPCTSTR lfilter = TEXT(TNAMESTAR);
3168 DWORD count, k;
3169 PCREDENTIAL *pcred;
3170 char *tmp, *blob, *target = NULL;
3171 char *host, *user, *sflags, *passwd, *orighost;
3172 char *ui[5];
3173 int i, j;
3175 if(using_passfile == 0)
3176 return(using_passfile);
3178 if(!g_CredInited){
3179 if(init_wincred_funcs() != 1){
3180 using_passfile = 0;
3181 return(using_passfile);
3185 dprint((9, "read_passfile\n"));
3187 using_passfile = 1;
3189 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
3190 if(pcred){
3191 for(k = 0; k < count; k++){
3193 host = user = sflags = passwd = orighost = NULL;
3194 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3196 target = lptstr_to_utf8(pcred[k]->TargetName);
3197 tmp = srchstr(target, TNAME);
3199 if(tmp){
3200 tmp += strlen(TNAME);
3201 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3202 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3203 ; /* find end of data */
3205 if(tmp[i])
3206 tmp[i++] = '\0'; /* tie off data */
3209 host = ui[0];
3210 user = ui[1];
3211 sflags = ui[2];
3214 blob = (char *) pcred[k]->CredentialBlob;
3215 if(blob){
3216 for(i = 0, j = 3; blob[i] && j < 5; j++){
3217 for(ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++)
3218 ; /* find end of data */
3220 if(blob[i])
3221 blob[i++] = '\0'; /* tie off data */
3224 passwd = ui[3];
3225 orighost = ui[4];
3228 if(passwd && host && user){ /* valid field? */
3229 STRLIST_S hostlist[2];
3230 int flags;
3232 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3233 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3234 hostlist[0].name = host;
3235 if(orighost){
3236 hostlist[0].next = &hostlist[1];
3237 hostlist[1].name = orighost;
3238 hostlist[1].next = NULL;
3240 else{
3241 hostlist[0].next = NULL;
3244 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3247 if(target)
3248 fs_give((void **) &target);
3251 g_CredFree((PVOID) pcred);
3255 return(1);
3257 # else /* old windows */
3258 using_passfile = 0;
3259 return(0);
3260 # endif
3262 #elif APPLEKEYCHAIN
3264 char target[MAILTMPLEN];
3265 char *tmp, *host, *user, *sflags, *passwd, *orighost;
3266 char *ui[5];
3267 int i, j, k, rc;
3268 SecKeychainAttributeList attrList;
3269 SecKeychainSearchRef searchRef = NULL;
3270 SecKeychainAttribute attrs[] = {
3271 { kSecAccountItemAttr, strlen(TNAME), TNAME }
3274 if(using_passfile == 0)
3275 return(using_passfile);
3277 dprint((9, "read_passfile\n"));
3280 /* search for only our items in the keychain */
3281 attrList.count = 1;
3282 attrList.attr = attrs;
3284 using_passfile = 1;
3285 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
3286 kSecGenericPasswordItemClass,
3287 &attrList,
3288 &searchRef))){
3289 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3290 if(searchRef){
3291 SecKeychainItemRef itemRef = NULL;
3292 SecKeychainAttributeInfo info;
3293 SecKeychainAttributeList *attrList = NULL;
3294 UInt32 blength = 0;
3295 char *blob = NULL;
3296 char *blobcopy = NULL; /* NULL terminated copy */
3298 UInt32 tags[] = {kSecAccountItemAttr,
3299 kSecServiceItemAttr};
3300 UInt32 formats[] = {0,0};
3302 dprint((10, "read_passfile: searchRef not NULL\n"));
3303 info.count = 2;
3304 info.tag = tags;
3305 info.format = formats;
3308 * Go through each item we found and put it
3309 * into our list.
3311 while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){
3312 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3313 rc = SecKeychainItemCopyAttributesAndData(itemRef,
3314 &info, NULL,
3315 &attrList,
3316 &blength,
3317 (void **) &blob);
3318 if(rc == 0 && attrList){
3319 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count));
3321 blobcopy = (char *) fs_get((blength + 1) * sizeof(char));
3322 strncpy(blobcopy, (char *) blob, blength);
3323 blobcopy[blength] = '\0';
3326 * I'm not real clear on how this works. It seems to be
3327 * necessary to combine the attributes from two passes
3328 * (attrList->count == 2) in order to get the full set
3329 * of attributes we inserted into the keychain in the
3330 * first place. So, we reset host...orighost outside of
3331 * the following for loop, not inside.
3333 host = user = sflags = passwd = orighost = NULL;
3334 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3336 for(k = 0; k < attrList->count; k++){
3338 if(attrList->attr[k].length){
3339 strncpy(target,
3340 (char *) attrList->attr[k].data,
3341 MIN(attrList->attr[k].length,sizeof(target)));
3342 target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0';
3345 tmp = target;
3346 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3347 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3348 ; /* find end of data */
3350 if(tmp[i])
3351 tmp[i++] = '\0'; /* tie off data */
3354 if(ui[0])
3355 host = ui[0];
3357 if(ui[1])
3358 user = ui[1];
3360 if(ui[2])
3361 sflags = ui[2];
3363 for(i = 0, j = 3; blobcopy[i] && j < 5; j++){
3364 for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++)
3365 ; /* find end of data */
3367 if(blobcopy[i])
3368 blobcopy[i++] = '\0'; /* tie off data */
3371 if(ui[3])
3372 passwd = ui[3];
3374 if(ui[4])
3375 orighost = ui[4];
3377 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:""));
3380 if(passwd && host && user){ /* valid field? */
3381 STRLIST_S hostlist[2];
3382 int flags;
3384 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3385 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3386 hostlist[0].name = host;
3387 if(orighost){
3388 hostlist[0].next = &hostlist[1];
3389 hostlist[1].name = orighost;
3390 hostlist[1].next = NULL;
3392 else{
3393 hostlist[0].next = NULL;
3396 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3399 if(blobcopy)
3400 fs_give((void **) & blobcopy);
3402 SecKeychainItemFreeAttributesAndData(attrList, (void *) blob);
3404 else{
3405 using_passfile = 0;
3406 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc));
3409 CFRelease(itemRef);
3410 itemRef = NULL;
3413 CFRelease(searchRef);
3415 else{
3416 using_passfile = 0;
3417 dprint((10, "read_passfile: searchRef NULL\n"));
3420 else{
3421 using_passfile = 0;
3422 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc));
3425 return(using_passfile);
3427 #else /* PASSFILE */
3429 char tmp[MAILTMPLEN], *ui[5];
3430 int i, j, n, rv = 0;
3431 size_t len = 0;
3432 char *tmptext = NULL;
3433 struct stat sbuf;
3434 #ifdef SMIME
3435 char tmp2[MAILTMPLEN];
3436 char *text = NULL, *text2 = NULL;
3437 int encrypted = 0;
3438 #endif /* SMIME */
3439 FILE *fp;
3441 if(using_passfile == 0)
3442 return(using_passfile);
3444 dprint((9, "read_passfile\n"));
3446 /* if there's no password to read, bag it!! */
3447 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){
3448 using_passfile = 0;
3449 return(using_passfile);
3452 #ifndef SMIME
3453 if(our_stat(tmp, &sbuf) == 0)
3454 len = sbuf.st_size;
3455 fp = our_fopen(tmp, "rb"); /* reopen to read data */
3456 #else
3457 /* the next call initializes the key/certificate pair used to
3458 * encrypt and decrypt a password file. The details of how this is
3459 * done is in the file pith/smime.c. During this setup we might call
3460 * smime_init(), but no matter what happens we must call smime_deinit()
3461 * there. The reason why this is so is because we can not assume that
3462 * the .pinerc file has been read by this time, so this code might not
3463 * know about the ps_global->smime structure or any of its components,
3464 * and it shouldn't because it only needs ps_global->pwdcert, so
3465 * do not init smime here, because the .pinerc might not have been
3466 * read and we do not really know where the keys and certificates really
3467 * are.
3468 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3469 * it is called for the first time and there are certificates at all,
3470 * or when it is called after the first time and the user refuses to
3471 * create a self-signed certificate. In this situation we will just
3472 * let the user live in an insecure world, but no more passwords will
3473 * be saved in the password file, and only those found there will be used.
3475 tmp2[0] = '\0';
3476 fgets(tmp2, sizeof(tmp2), fp);
3477 fclose(fp);
3478 if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
3479 /* there is an already existing password file, that is not encrypted
3480 * and there is no key to encrypt it yet, go again through setup_pwdcert
3481 * and encrypt it now.
3483 if(tmp2[0]){ /* not empty, UNencrypted password file */
3484 if(ps_global->pwdcert == NULL)
3485 rv = setup_pwdcert(&ps_global->pwdcert);
3486 if((rv == 0 || rv == -5) && ps_global->pwdcert == NULL)
3487 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3488 if(ps_global->pwdcert == NULL){
3489 q_status_message(SM_ORDER, 3, 3,
3490 " Failed to create private key. Using UNencrypted Password file. ");
3491 save_password = 0;
3493 else{
3494 if(rv == 1){
3495 q_status_message(SM_ORDER, 3, 3,
3496 " Failed to unlock private key. Using UNencrypted Password file. ");
3497 save_password = 0; /* do not save more passwords */
3500 if(ps_global->pwdcert != NULL
3501 && encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
3502 encrypted++;
3505 else {
3506 if(ps_global->pwdcert == NULL)
3507 rv = setup_pwdcert(&ps_global->pwdcert);
3508 encrypted++;
3512 * if password file is encrypted we attempt to decrypt. We ask the
3513 * user for the password to unlock the password file. If the user
3514 * enters the password and it unlocks the file, use it and keep saving
3515 * passwords in it. If the user enters the wrong passwords and does
3516 * not unlock it, we will not see that here, but in decrypt_file, so
3517 * the only other possibility is that the user cancels. In that case
3518 * we will see i == -1. In that case, we will let the user attempt
3519 * manual login to the server they want to login, but passwords will
3520 * not be saved so that the password file will not be saved
3521 * unencrypted and rewritten again.
3523 if(encrypted){
3524 text = text2 = decrypt_file((char *)tmp, &i, (PERSONAL_CERT *)ps_global->pwdcert);
3525 len = text2 ? strlen(text2) : 0;
3526 switch(i){
3527 case -2: using_passfile = 0;
3528 break;
3530 case 1 : save_password = 1;
3531 using_passfile = 1;
3532 break;
3534 case -1: save_password = 0;
3535 using_passfile = 1;
3536 break;
3538 default: break;
3541 #endif /* SMIME */
3543 if(using_passfile == 0){
3544 #ifdef SMIME
3545 if(text) fs_give((void **)&text);
3546 #endif /* SMIME */
3547 return using_passfile;
3550 if(len > 0){
3551 tmptext = fs_get(len + 1);
3552 #ifdef SMIME
3553 for(n = 0; encrypted ? line_get(tmptext, len + 1, &text2)
3554 : (fgets(tmptext, len+1, fp) != NULL); n++){
3555 #else /* SMIME */
3556 for(n = 0; fgets(tmptext, len+1, fp); n++){
3557 #endif /* SMIME */
3558 /*** do any necessary DEcryption here ***/
3559 xlate_key = n;
3560 for(i = 0; tmptext[i]; i++)
3561 tmptext[i] = xlate_out(tmptext[i]);
3563 if(i && tmptext[i-1] == '\n')
3564 tmptext[i-1] = '\0'; /* blast '\n' */
3566 dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
3567 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3568 for(i = 0, j = 0; tmptext[i] && j < 5; j++){
3569 for(ui[j] = &tmptext[i]; tmptext[i] && tmptext[i] != '\t'; i++)
3570 ; /* find end of data */
3572 if(tmptext[i])
3573 tmptext[i++] = '\0'; /* tie off data */
3576 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3577 if(ui[0] && ui[1] && ui[2]){ /* valid field? */
3578 STRLIST_S hostlist[2];
3579 char *s = ui[3] ? strchr(ui[3], PWDAUTHSEP) : NULL;
3580 int flags = ui[3] ? atoi(s ? ++s : ui[3]) : 0;
3582 hostlist[0].name = ui[2];
3583 if(ui[4]){
3584 hostlist[0].next = &hostlist[1];
3585 hostlist[1].name = ui[4];
3586 hostlist[1].next = NULL;
3588 else{
3589 hostlist[0].next = NULL;
3592 imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0);
3597 if (tmptext) fs_give((void **) &tmptext);
3598 #ifdef SMIME
3599 if (text) fs_give((void **)&text);
3600 #else /* SMIME */
3601 fclose(fp);
3602 #endif /* SMIME */
3603 return(1);
3604 #endif /* PASSFILE */
3609 void
3610 write_passfile(pinerc, l)
3611 char *pinerc;
3612 MMLOGIN_S *l;
3614 char *authend, *authtype;
3615 #ifdef WINCRED
3616 # if (WINCRED > 0)
3617 char target[4*MAILTMPLEN];
3618 char blob[4*MAILTMPLEN];
3619 CREDENTIAL cred;
3620 LPTSTR ltarget = 0;
3622 if(using_passfile == 0)
3623 return;
3625 dprint((9, "write_passfile\n"));
3627 for(; l; l = l->next){
3628 authtype = l->passwd;
3629 authend = strchr(l->passwd, PWDAUTHSEP);
3630 if(authend != NULL){
3631 *authend = '\0';
3632 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3633 *authend = PWDAUTHSEP;
3635 else
3636 sprintf(blob, "%d", l->altflag);
3638 snprintf(target, sizeof(target), "%s%s\t%s\t%s",
3639 TNAME,
3640 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3641 l->user ? l->user : "",
3642 blob);
3643 ltarget = utf8_to_lptstr((LPSTR) target);
3645 if(ltarget){
3646 snprintf(blob, sizeof(blob), "%s%s%s",
3647 l->passwd ? l->passwd : "",
3648 (l->hosts && l->hosts->next && l->hosts->next->name)
3649 ? "\t" : "",
3650 (l->hosts && l->hosts->next && l->hosts->next->name)
3651 ? l->hosts->next->name : "");
3652 memset((void *) &cred, 0, sizeof(cred));
3653 cred.Flags = 0;
3654 cred.Type = CRED_TYPE_GENERIC;
3655 cred.TargetName = ltarget;
3656 cred.CredentialBlobSize = strlen(blob)+1;
3657 cred.CredentialBlob = (LPBYTE) &blob;
3658 cred.Persist = CRED_PERSIST_ENTERPRISE;
3659 g_CredWriteW(&cred, 0);
3661 fs_give((void **) &ltarget);
3664 #endif /* WINCRED > 0 */
3666 #elif APPLEKEYCHAIN
3667 int rc;
3668 char target[4*MAILTMPLEN];
3669 char blob[4*MAILTMPLEN];
3670 SecKeychainItemRef itemRef = NULL;
3672 if(using_passfile == 0)
3673 return;
3675 dprint((9, "write_passfile\n"));
3677 for(; l; l = l->next){
3678 authtype = l->passwd;
3679 authend = strchr(l->passwd, PWDAUTHSEP);
3680 if(authend != NULL){
3681 *authend = '\0';
3682 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3683 *authend = PWDAUTHSEP;
3685 else
3686 sprintf(blob, "%d", l->altflag);
3688 snprintf(target, sizeof(target), "%s\t%s\t%s",
3689 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3690 l->user ? l->user : "",
3691 blob);
3693 snprintf(blob, sizeof(blob), "%s%s%s",
3694 l->passwd ? l->passwd : "",
3695 (l->hosts && l->hosts->next && l->hosts->next->name)
3696 ? "\t" : "",
3697 (l->hosts && l->hosts->next && l->hosts->next->name)
3698 ? l->hosts->next->name : "");
3700 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob));
3702 rc = SecKeychainAddGenericPassword(NULL,
3703 strlen(target), target,
3704 strlen(TNAME), TNAME,
3705 strlen(blob), blob,
3706 NULL);
3707 if(rc==0){
3708 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3710 else{
3711 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc));
3714 if(rc == errSecDuplicateItem){
3715 /* fix existing entry */
3716 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3717 itemRef = NULL;
3718 if(!(rc=SecKeychainFindGenericPassword(NULL,
3719 strlen(target), target,
3720 strlen(TNAME), TNAME,
3721 NULL, NULL,
3722 &itemRef)) && itemRef){
3724 rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob);
3725 if(!rc){
3726 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc));
3729 else{
3730 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc));
3735 #else /* PASSFILE */
3736 char tmp[4*MAILTMPLEN], blob[4*MAILTMPLEN];
3737 int i, n;
3738 FILE *fp;
3739 #ifdef SMIME
3740 char *text = NULL, tmp2[4*MAILTMPLEN];
3741 int len = 0;
3742 #endif
3744 if(using_passfile == 0)
3745 return;
3747 dprint((9, "write_passfile\n"));
3749 /* if there's no passfile to read, bag it!! */
3750 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "wb"))){
3751 using_passfile = 0;
3752 return;
3755 #ifdef SMIME
3756 strncpy(tmp2, tmp, sizeof(tmp2));
3757 tmp2[sizeof(tmp2)-1] = '\0';
3758 #endif /* SMIME */
3760 for(n = 0; l; l = l->next, n++){
3761 authtype = l->passwd;
3762 authend = strchr(l->passwd, PWDAUTHSEP);
3763 if(authend != NULL){
3764 *authend = '\0';
3765 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3766 *authend = PWDAUTHSEP;
3768 else
3769 sprintf(blob, "%d", l->altflag);
3771 /*** do any necessary ENcryption here ***/
3772 snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%s%s%s\n", l->passwd, l->user,
3773 l->hosts->name, blob,
3774 (l->hosts->next && l->hosts->next->name) ? "\t" : "",
3775 (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
3776 : "");
3777 dprint((10, "write_passfile: %s", tmp ? tmp : "?"));
3778 xlate_key = n;
3779 for(i = 0; tmp[i]; i++)
3780 tmp[i] = xlate_in(tmp[i]);
3782 #ifdef SMIME
3783 fs_resize((void **)&text, (len + strlen(tmp) + 1)*sizeof(char));
3784 text[len] = '\0';
3785 len += strlen(tmp) + 1;
3786 strncat(text, tmp, strlen(tmp));
3787 #else /* SMIME */
3788 fputs(tmp, fp);
3789 #endif /* SMIME */
3792 fclose(fp);
3793 #ifdef SMIME
3794 if(text != NULL){
3795 if(ps_global->pwdcert == NULL){
3796 q_status_message(SM_ORDER, 3, 3, "Attempting to encrypt password file");
3797 i = setup_pwdcert(&ps_global->pwdcert);
3798 if((i == 0 || i == -5) && ps_global->pwdcert == NULL)
3799 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3801 if(ps_global->pwdcert == NULL){ /* we tried but failed */
3802 if(i == -1)
3803 q_status_message1(SM_ORDER, 3, 3, "Error: no directory %s to save certificates", ps_global->pwdcertdir);
3804 else
3805 q_status_message(SM_ORDER, 3, 3, "Refusing to write non-encrypted password file");
3807 else if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0)
3808 q_status_message(SM_ORDER, 3, 3, "Failed to encrypt password file");
3809 fs_give((void **)&text); /* do not save this text */
3811 #endif /* SMIME */
3812 #endif /* PASSFILE */
3815 #endif /* LOCAL_PASSWD_CACHE */
3818 #if (WINCRED > 0)
3819 void
3820 erase_windows_credentials(void)
3822 LPCTSTR lfilter = TEXT(TNAMESTAR);
3823 DWORD count, k;
3824 PCREDENTIAL *pcred;
3826 if(!g_CredInited){
3827 if(init_wincred_funcs() != 1)
3828 return;
3831 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
3832 if(pcred){
3833 for(k = 0; k < count; k++)
3834 g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0);
3836 g_CredFree((PVOID) pcred);
3841 void
3842 ask_erase_credentials(void)
3844 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){
3845 erase_windows_credentials();
3846 q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased");
3848 else
3849 q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased");
3852 #endif /* WINCRED */
3855 #ifdef LOCAL_PASSWD_CACHE
3857 get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
3858 char *pinerc, **passwd, *user;
3859 STRLIST_S *hostlist;
3860 int altflag;
3862 return get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, NULL);
3866 * get_passfile_passwd_auth - return the password contained in the special passord
3867 * cache. The file is assumed to be in the same directory
3868 * as the pinerc with the name defined above.
3871 get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, authtype)
3872 char *pinerc, **passwd, *user;
3873 STRLIST_S *hostlist;
3874 int altflag;
3875 char *authtype;
3877 dprint((10, "get_passfile_passwd_auth\n"));
3878 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3879 ? imap_get_passwd_auth(passfile_cache, passwd,
3880 user, hostlist, altflag, authtype)
3881 : 0);
3884 void
3885 free_passfile_cache_work(MMLOGIN_S **pwdcache)
3887 if(pwdcache == NULL || *pwdcache == NULL)
3888 return;
3890 if((*pwdcache)->user) fs_give((void **)&(*pwdcache)->user);
3891 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
3892 if((*pwdcache)->hosts) free_strlist(&(*pwdcache)->hosts);
3893 free_passfile_cache_work(&(*pwdcache)->next);
3894 fs_give((void **)pwdcache);
3898 void
3899 free_passfile_cache(void)
3901 if(passfile_cache)
3902 free_passfile_cache_work(&passfile_cache);
3906 is_using_passfile(void)
3908 return(using_passfile == 1);
3912 * Just trying to guess the username the user might want to use on this
3913 * host, the user will confirm.
3915 char *
3916 get_passfile_user(pinerc, hostlist)
3917 char *pinerc;
3918 STRLIST_S *hostlist;
3920 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3921 ? imap_get_user(passfile_cache, hostlist)
3922 : NULL);
3927 preserve_prompt(char *pinerc)
3929 return preserve_prompt_auth(pinerc, NULL);
3933 preserve_prompt_auth(char *pinerc, char *authtype)
3935 #ifdef WINCRED
3936 # if (WINCRED > 0)
3937 #define PROMPT_PWD _("Preserve password for next login")
3938 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3940 * This prompt was going to be able to be turned on and off via a registry
3941 * setting controlled from the config menu. We decided to always use the
3942 * dialog for login, and there the prompt is unobtrusive enough to always
3943 * be in there. As a result, windows should never reach this, but now
3944 * OS X somewhat uses the behavior just described.
3946 if(mswin_store_pass_prompt()
3947 && (want_to(authtype
3948 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3949 : PROMPT_PWD,
3950 'y', 'x', NO_HELP, WT_NORM)
3951 == 'y'))
3952 return(1);
3953 else
3954 return(0);
3955 # else
3956 return(0);
3957 # endif
3959 #elif APPLEKEYCHAIN
3960 #define PROMPT_PWD _("Preserve password for next login")
3961 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3963 int rc;
3964 if((rc = macos_store_pass_prompt()) != 0){
3965 if(want_to(authtype
3966 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3967 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
3968 == 'y'){
3969 if(rc == -1){
3970 macos_set_store_pass_prompt(1);
3971 q_status_message(SM_ORDER, 4, 4,
3972 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3974 return(1);
3976 else if(rc == -1){
3977 macos_set_store_pass_prompt(0);
3978 q_status_message(SM_ORDER, 4, 4,
3979 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3981 return(0);
3983 return(0);
3984 #else /* PASSFILE */
3985 #define PROMPT_PWD _("Preserve password on DISK for next login")
3986 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
3988 char tmp[MAILTMPLEN];
3989 struct stat sbuf;
3991 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || our_stat(tmp, &sbuf) < 0)
3992 return 0;
3994 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
3995 return(want_to(authtype
3996 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3997 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
3998 == 'y');
3999 return(0);
4000 #endif /* PASSFILE */
4003 #endif /* LOCAL_PASSWD_CACHE */
4006 #ifdef APPLEKEYCHAIN
4009 * Returns:
4010 * 1 if store pass prompt is set in the "registry" to on
4011 * 0 if set to off
4012 * -1 if not set to anything
4015 macos_store_pass_prompt(void)
4017 char *data = NULL;
4018 UInt32 len = 0;
4019 int rc = -1;
4020 int val;
4022 if(storepassprompt == -1){
4023 if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL,
4024 strlen(TNAMEPROMPT),
4025 TNAMEPROMPT, &len,
4026 (void **) &data, NULL))){
4027 val = (len == 1 && data && data[0] == '1');
4031 if(storepassprompt == -1 && !rc){
4032 if(val)
4033 storepassprompt = 1;
4034 else
4035 storepassprompt = 0;
4038 return(storepassprompt);
4042 void
4043 macos_set_store_pass_prompt(int val)
4045 storepassprompt = val ? 1 : 0;
4047 SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT),
4048 TNAMEPROMPT, 1, val ? "1" : "0", NULL);
4052 void
4053 macos_erase_keychain(void)
4055 SecKeychainAttributeList attrList;
4056 SecKeychainSearchRef searchRef = NULL;
4057 SecKeychainAttribute attrs1[] = {
4058 { kSecAccountItemAttr, strlen(TNAME), TNAME }
4060 SecKeychainAttribute attrs2[] = {
4061 { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT }
4064 dprint((9, "macos_erase_keychain\n"));
4067 * Seems like we ought to be able to combine attrs1 and attrs2
4068 * into a single array, but I couldn't get it to work.
4071 /* search for only our items in the keychain */
4072 attrList.count = 1;
4073 attrList.attr = attrs1;
4075 if(!SecKeychainSearchCreateFromAttributes(NULL,
4076 kSecGenericPasswordItemClass,
4077 &attrList,
4078 &searchRef)){
4079 if(searchRef){
4080 SecKeychainItemRef itemRef = NULL;
4083 * Go through each item we found and put it
4084 * into our list.
4086 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4087 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4088 SecKeychainItemDelete(itemRef);
4089 CFRelease(itemRef);
4092 CFRelease(searchRef);
4096 attrList.count = 1;
4097 attrList.attr = attrs2;
4099 if(!SecKeychainSearchCreateFromAttributes(NULL,
4100 kSecGenericPasswordItemClass,
4101 &attrList,
4102 &searchRef)){
4103 if(searchRef){
4104 SecKeychainItemRef itemRef = NULL;
4107 * Go through each item we found and put it
4108 * into our list.
4110 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4111 SecKeychainItemDelete(itemRef);
4112 CFRelease(itemRef);
4115 CFRelease(searchRef);
4120 #endif /* APPLEKEYCHAIN */
4122 #ifdef LOCAL_PASSWD_CACHE
4124 void
4125 set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
4126 char *pinerc, *passwd, *user;
4127 STRLIST_S *hostlist;
4128 int altflag, already_prompted;
4130 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, NULL);
4133 * set_passfile_passwd - set the password file entry associated with
4134 * cache. The file is assumed to be in the same directory
4135 * as the pinerc with the name defined above.
4136 * already_prompted: 0 not prompted
4137 * 1 prompted, answered yes
4138 * 2 prompted, answered no
4140 void
4141 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, authtype)
4142 char *pinerc, *passwd, *user;
4143 STRLIST_S *hostlist;
4144 int altflag, already_prompted;
4145 char *authtype;
4147 dprint((10, "set_passfile_passwd_auth\n"));
4148 if(((already_prompted == 0 && preserve_prompt_auth(pinerc, authtype))
4149 || already_prompted == 1)
4150 && !ps_global->nowrite_password_cache
4151 && (passfile_cache || read_passfile(pinerc, &passfile_cache))){
4152 imap_set_passwd_auth(&passfile_cache, passwd, user, hostlist, altflag, 0, 0, authtype);
4153 write_passfile(pinerc, passfile_cache);
4157 void
4158 update_passfile_hostlist(pinerc, user, hostlist, altflag)
4159 char *pinerc;
4160 char *user;
4161 STRLIST_S *hostlist;
4162 int altflag;
4164 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, NULL);
4168 * Passfile lines are
4170 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4172 * In pine4.40 and before there was no orig_hostname.
4173 * This routine attempts to repair that.
4175 void
4176 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, authtype)
4177 char *pinerc;
4178 char *user;
4179 STRLIST_S *hostlist;
4180 int altflag;
4181 char *authtype;
4183 #ifdef WINCRED
4184 return;
4185 #else /* !WINCRED */
4186 MMLOGIN_S *l;
4187 size_t len = authtype ? strlen(authtype) : 0;
4188 size_t offset = authtype ? 1 : 0;
4190 for(l = passfile_cache; l; l = l->next)
4191 if(imap_same_host_auth(l->hosts, hostlist, authtype)
4192 && *user
4193 && !strcmp(user, l->user + len + offset)
4194 && l->altflag == altflag){
4195 break;
4198 if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
4199 && hostlist->next->name
4200 && !ps_global->nowrite_password_cache){
4201 l->hosts->next = new_strlist_auth(hostlist->next->name, authtype, PWDAUTHSEP);
4202 write_passfile(pinerc, passfile_cache);
4204 #endif /* !WINCRED */
4207 #endif /* LOCAL_PASSWD_CACHE */
4210 #if (WINCRED > 0)
4212 * Load and init the WinCred structure.
4213 * This gives us a way to skip the WinCred code
4214 * if the dll doesn't exist.
4217 init_wincred_funcs(void)
4219 if(!g_CredInited)
4221 HMODULE hmod;
4223 /* Assume the worst. */
4224 g_CredInited = -1;
4226 hmod = LoadLibrary(TEXT("advapi32.dll"));
4227 if(hmod)
4229 FARPROC fpCredWriteW;
4230 FARPROC fpCredEnumerateW;
4231 FARPROC fpCredDeleteW;
4232 FARPROC fpCredFree;
4234 fpCredWriteW = GetProcAddress(hmod, "CredWriteW");
4235 fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW");
4236 fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW");
4237 fpCredFree = GetProcAddress(hmod, "CredFree");
4239 if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree)
4241 g_CredWriteW = (CREDWRITEW *)fpCredWriteW;
4242 g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW;
4243 g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW;
4244 g_CredFree = (CREDFREE *)fpCredFree;
4246 g_CredInited = 1;
4250 mswin_set_erasecreds_callback(ask_erase_credentials);
4253 return g_CredInited;
4256 #endif /* WINCRED */