* A few improvements to the XOAUTH2 code. Mainly documentation for users.
[alpine.git] / alpine / imap.c
blob51f8bdc6281fca375d2d3399ddefe424fe00abee
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 */
177 {OUTLOOK_NAME,
178 {"outlook.office365.com", "smtp.office365.com", NULL, NULL},
179 {{"client_id", NULL},
180 {"client_secret", NULL}, /* not used, but needed */
181 {"tenant", NULL}, /* used */
182 {"code", NULL}, /* not used, not needed */
183 {"refresh_token", NULL},
184 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
185 {"grant_type", "urn:ietf:params:oauth:grant-type:device_code"},
186 {"scope", NULL}, /* not used */
187 {"grant_type", "refresh_token"},
188 {"response_type", "code"}, /* not used */
189 {"state", NULL}, /* not used */
190 {"device_code", NULL} /* only used for frst time set up */
192 {{NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* Get Access Code, Not used */
193 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/devicecode", /* first time use and get device code information */
194 {OA2_Id, OA2_Scope, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}},
195 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
196 {OA2_Id, OA2_Redirect, OA2_DeviceCode, OA2_End, OA2_End, OA2_End, OA2_End}},
197 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
198 {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}
200 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information */
201 NULL, /* access token */
202 0, /* expiration time */
203 0, /* first time indicator */
204 0 /* client secret required */
206 {OUTLOOK_NAME,
207 {"outlook.office365.com", "smtp.office365.com", NULL, NULL},
208 {{"client_id", NULL},
209 {"client_secret", NULL}, /* not used, but needed */
210 {"tenant", NULL}, /* used */
211 {"code", NULL}, /* used during authorization */
212 {"refresh_token", NULL},
213 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
214 {"redirect_uri", "http://localhost"},
215 {"grant_type", "authorization_code"},
216 {"grant_type", "refresh_token"},
217 {"response_type", "code"},
218 {"state", NULL}, /* not used */
219 {"device_code", NULL} /* not used */
221 {{"GET", "https://login.microsoftonline.com/\001/oauth2/v2.0/authorize", /* Get Access Code */
222 {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End}},
223 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
224 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
225 {OA2_Id, OA2_Redirect, OA2_Scope, OA2_GrantTypeforAccessToken, OA2_Secret, OA2_Code, OA2_End}},
226 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
227 {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_Secret, OA2_End, OA2_End}}
229 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
230 NULL, /* access token */
231 0, /* expiration time */
232 0, /* first time indicator */
233 1 /* client secret required */
235 {YANDEX_NAME,
236 {"imap.yandex.com", "smtp.yandex.com", NULL, NULL},
237 {{"client_id", NULL},
238 {"client_secret", NULL}, /* not used, but needed */
239 {"tenant", NULL}, /* not used */
240 {"code", NULL}, /* used during authorization */
241 {"refresh_token", NULL},
242 {"scope", NULL}, /* not needed, so not used */
243 {"redirect_uri", "https://oauth.yandex.ru/verification_code"},
244 {"grant_type", "authorization_code"},
245 {"grant_type", "refresh_token"},
246 {"response_type", "code"},
247 {"state", NULL}, /* not used */
248 {"device_code", NULL} /* not used */
250 {{"GET", "https://oauth.yandex.com/authorize", /* Get Access Code */
251 {OA2_Id, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End, OA2_End}},
252 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
253 {"POST", "https://oauth.yandex.com/token", /* Get first Refresh Token and Access token */
254 {OA2_Id, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Secret, OA2_Code, OA2_End, OA2_End}},
255 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
256 {OA2_Id, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_Secret, OA2_End, OA2_End, OA2_End}}
258 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
259 NULL, /* access token */
260 0, /* expiration time */
261 0, /* first time indicator */
262 1 /* client secret required */
264 { NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0},
268 xoauth2_flow_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
270 int rv = 0;
272 switch(cmd){
273 case MC_CHOICE:
274 *((*cl)->d.xf.selected) = (*cl)->d.xf.pat;
275 rv = simple_exit_cmd(flags);
277 case MC_EXIT:
278 rv = simple_exit_cmd(flags);
279 break;
281 default:
282 rv = -1;
285 if(rv > 0)
286 ps->mangled_body = 1;
288 return rv;
291 OAUTH2_S *
292 oauth2_select_flow(char *host)
294 OAUTH2_S *oa2list, *oa2;
295 int i, n, rv;
296 char *method;
298 if(ps_global->ttyo){
299 CONF_S *ctmp = NULL, *first_line = NULL;
300 OAUTH2_S *x_sel = NULL;
301 OPT_SCREEN_S screen;
302 char tmp[1024];
304 dprint((9, "xoauth2 select flow"));
305 ps_global->next_screen = SCREEN_FUN_NULL;
307 memset(&screen, 0, sizeof(screen));
309 for(i = 0; i < sizeof(tmp) && i < ps_global->ttyo->screen_cols; i++)
310 tmp[i] = '-';
311 tmp[i] = '\0';
313 new_confline(&ctmp);
314 ctmp->flags |= CF_NOSELECT;
315 ctmp->value = cpystr(tmp);
317 new_confline(&ctmp);
318 ctmp->flags |= CF_NOSELECT;
319 ctmp->value = cpystr(_("Please select below the authorization flow you would like to follow:"));
321 new_confline(&ctmp);
322 ctmp->flags |= CF_NOSELECT;
323 ctmp->value = cpystr(tmp);
325 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
326 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
327 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
328 new_confline(&ctmp);
329 if(!first_line)
330 first_line = ctmp;
331 method = oa2list->server_mthd[0].name ? "Authorize"
332 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
333 sprintf(tmp, "%s (%s)", oa2list->name, method);
334 ctmp->value = cpystr(tmp);
335 ctmp->d.xf.selected = &x_sel;
336 ctmp->d.xf.pat = oa2list;
337 ctmp->keymenu = &xoauth2_id_select_km;
338 ctmp->help = NO_HELP;
339 ctmp->help_title = NULL;
340 ctmp->tool = xoauth2_flow_tool;
341 ctmp->flags = CF_STARTITEM;
342 ctmp->valoffset = 4;
345 (void)conf_scroll_screen(ps_global, &screen, first_line, _("SELECT AUTHORIZATION FLOW"),
346 _("xoauth2"), 0, NULL);
347 oa2 = x_sel;
349 else{
350 char *s;
351 char prompt[1024];
352 char reply[1024];
353 int sel, j;
355 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++)
356 n += strlen(oa2list->name); + 5; /* number, parenthesis, space */
357 n += 1024; /* large enough to display to lines of 80 characters in UTF-8 */
358 s = fs_get(n*sizeof(char));
359 strcpy(s, _("Please select below the authorization flow you would like to follow:"));
360 sprintf(s + strlen(s), _("Please select the client-id to use from the following list.\n\n"));
361 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
362 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
363 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i])
364 sprintf(s + strlen(s), " %d) %.70s\n", j++, oa2list->name);
366 display_init_err(s, 0);
368 strncpy(prompt, _("Enter your selection number: "), sizeof(prompt));
369 prompt[sizeof(prompt)-1] = '\0';
371 rv = optionally_enter(reply, 0, 0, sizeof(reply), prompt, NULL, NO_HELP, 0);
372 sel = atoi(reply);
373 rv = (sel >= 0 && sel < i) ? 0 : -1;
374 } while (rv != 0);
376 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
377 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
378 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
379 if(j == sel) break;
380 else j++;
383 oa2 = oa2list;
385 return oa2;
388 typedef struct auth_code_s {
389 char *code;
390 int answer;
391 } AUTH_CODE_S;
394 oauth2device_decode_reply(void *datap, void *replyp)
396 OAUTH2_DEVICEPROC_S *av = (OAUTH2_DEVICEPROC_S *)datap;
397 int reply = *(int *) replyp;
399 return reply < 0 ? av->code_failure : (reply == 0 ? av->code_success : av->code_wait);
403 oauth2_elapsed_done(void *aux_valuep)
405 OAUTH2_S *oauth2 = aux_valuep ? ((OAUTH2_DEVICEPROC_S *) aux_valuep)->xoauth2 : NULL;
406 static time_t savedt = 0, now;
407 int rv = 0;
409 if(aux_valuep == NULL) savedt = 0; /* reset last time we approved */
410 else{
411 now = time(0);
412 if(oauth2->devicecode.interval + now >= savedt)
413 savedt = now;
414 else
415 rv = -1;
417 return rv;
420 void
421 oauth2_set_device_info(OAUTH2_S *oa2, char *method)
423 char tmp[MAILTMPLEN];
424 char *code;
425 char *name = oa2->name;
426 int aux_rv_value;
427 OAUTH2_DEVICECODE_S *deviceinfo = &oa2->devicecode;
428 OAUTH2_DEVICEPROC_S aux_value;
430 if(ps_global->ttyo){
431 SCROLL_S sargs;
432 STORE_S *in_store, *out_store;
433 gf_io_t pc, gc;
434 HANDLE_S *handles = NULL;
435 AUTH_CODE_S user_input;
437 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
438 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
439 goto try_wantto;
441 aux_value.xoauth2 = oa2;
442 aux_value.code_success = 'e';
443 aux_value.code_failure = 'e';
444 aux_value.code_wait = NO_OP_COMMAND;
446 so_puts(in_store, "<HTML><P>");
447 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name);
448 so_puts(in_store, tmp);
449 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name, method),
450 so_puts(in_store, tmp);
452 if(deviceinfo->verification_uri && deviceinfo->user_code){
453 sprintf(tmp,
454 _("</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."),
455 deviceinfo->verification_uri, deviceinfo->verification_uri, deviceinfo->user_code);
456 so_puts(in_store, tmp);
458 else{
459 so_puts(in_store, "</P><P>");
460 so_puts(in_store, deviceinfo->message);
462 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
463 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), name);
464 so_puts(in_store, tmp);
465 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
467 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 "));
468 so_puts(in_store, _("to grant access to Alpine to your data. "));
469 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 "));
470 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 "));
471 so_puts(in_store, _("alive at the end of this process, and your connection will proceed from there."));
472 so_puts(in_store, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit"));
473 so_puts(in_store, _("</P></HTML>"));
475 so_seek(in_store, 0L, 0);
476 init_handles(&handles);
477 gf_filter_init();
478 gf_link_filter(gf_html2plain,
479 gf_html2plain_opt(NULL,
480 ps_global->ttyo->screen_cols, non_messageview_margin(),
481 &handles, NULL, GFHP_LOCAL_HANDLES));
482 gf_set_so_readc(&gc, in_store);
483 gf_set_so_writec(&pc, out_store);
484 gf_pipe(gc, pc);
485 gf_clear_so_writec(out_store);
486 gf_clear_so_readc(in_store);
488 memset(&sargs, 0, sizeof(SCROLL_S));
489 sargs.text.handles = handles;
490 sargs.text.text = so_text(out_store);
491 sargs.text.src = CharStar;
492 sargs.text.desc = _("help text");
493 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
494 sargs.proc.tool = oauth2_auth_answer;
495 sargs.proc.data.p = (void *)&user_input;
496 sargs.keys.menu = &oauth2_device_auth_keymenu;
497 /* don't want to re-enter c-client */
498 sargs.quell_newmail = 1;
499 setbitmap(sargs.keys.bitmap);
500 sargs.help.text = h_oauth2_start;
501 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
502 sargs.aux_function = oauth2deviceinfo_get_accesscode;
503 sargs.aux_value = (void *) &aux_value;
504 sargs.aux_condition = oauth2_elapsed_done;
505 sargs.decode_aux_rv_value = oauth2device_decode_reply;
506 sargs.aux_rv_value = (void *) &aux_rv_value;
508 do {
509 scrolltool(&sargs);
510 ps_global->mangled_screen = 1;
511 ps_global->painted_body_on_startup = 0;
512 ps_global->painted_footer_on_startup = 0;
513 } while (user_input.answer != 'e');
515 so_give(&in_store);
516 so_give(&out_store);
517 free_handles(&handles);
518 oauth2_elapsed_done(NULL);
520 else{
521 int flags, rc, q_line;
522 /* TRANSLATORS: user needs to input an access code from the server */
523 char prompt[MAILTMPLEN], token[MAILTMPLEN];
525 * If screen hasn't been initialized yet, use want_to.
527 try_wantto:
529 tmp_20k_buf[0] = '\0';
530 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
531 _("Authorizing Alpine Access to %s Email Services\n\n"), name);
532 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
534 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
535 _("Alpine is attempting to log you into your %s account, using the %s method. "), name, method),
536 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
538 if(deviceinfo->verification_uri && deviceinfo->user_code){
539 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
540 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
541 deviceinfo->verification_uri, deviceinfo->user_code);
542 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
544 else{
545 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
546 "%s\n\n", deviceinfo->message);
547 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
550 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
551 _("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);
552 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
554 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
555 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
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", _("to grant access to Alpine to your data.\n\n"));
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", _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
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", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
568 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
570 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
571 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
572 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
574 display_init_err(tmp_20k_buf, 0);
575 memset((void *)tmp, 0, sizeof(tmp));
576 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
577 tmp[sizeof(tmp)-1] = '\0';
579 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
580 int rv;
581 UCS ch;
582 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
583 flags = OE_APPEND_CURRENT;
585 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
586 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
587 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
589 aux_value.xoauth2 = oa2;
590 aux_value.code_success = 'y';
591 aux_value.code_failure = 'n';
592 aux_value.code_wait = 'w';
594 strncpy(tmp, _("Continue waiting"), sizeof(tmp));
595 tmp[sizeof(tmp)-1] = '\0';
596 do {
597 if(oauth2_elapsed_done((void *) &aux_value) == 0)
598 oauth2deviceinfo_get_accesscode((void *) &aux_value, (void *) &rv);
599 ch = oauth2device_decode_reply((void *) &aux_value, (void *) &rv);
600 } while (ch == 'w' || want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y');
601 oauth2_elapsed_done(NULL);
606 char *
607 oauth2_get_access_code(unsigned char *url, char *method, OAUTH2_S *oauth2, int *tryanother)
609 char tmp[MAILTMPLEN];
610 char *code;
612 if(ps_global->ttyo){
613 SCROLL_S sargs;
614 STORE_S *in_store, *out_store;
615 gf_io_t pc, gc;
616 HANDLE_S *handles = NULL;
617 AUTH_CODE_S user_input;
619 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
620 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
621 goto try_wantto;
623 so_puts(in_store, "<HTML><BODY><P>");
624 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2->name);
625 so_puts(in_store, tmp);
626 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
627 so_puts(in_store, tmp);
629 if(strucmp(oauth2->name, GMAIL_NAME) == 0){
630 so_puts(in_store, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
631 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."));
632 so_puts(in_store, _("<UL> "));
633 so_puts(in_store, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
634 so_puts(in_store, _("and create a project. The name of the project is not important."));
635 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."));
636 so_puts(in_store, _("<LI> Create OAUTH Credentials."));
637 so_puts(in_store, _("</UL> "));
638 so_puts(in_store, _("<P> As a result of this process, you will get a client-id and a client-secret."));
639 so_puts(in_store, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
640 so_puts(in_store, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
641 so_puts(in_store, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
642 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."));
645 so_puts(in_store, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
646 so_puts(in_store,"</P><P>");
647 sprintf(tmp_20k_buf, _("<A HREF=\"%s\">%s</A>"), url, url);
648 so_puts(in_store, tmp_20k_buf);
650 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
651 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2->name);
652 so_puts(in_store, tmp);
653 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
655 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. "));
656 so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page."));
657 so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
658 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. "));
659 so_puts(in_store, _(" Once you have completed this process, Alpine will proceed with authentication."));
660 so_puts(in_store, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
661 so_puts(in_store, _("</P></BODY></HTML>"));
663 so_seek(in_store, 0L, 0);
664 init_handles(&handles);
665 gf_filter_init();
666 gf_link_filter(gf_html2plain,
667 gf_html2plain_opt(NULL,
668 ps_global->ttyo->screen_cols, non_messageview_margin(),
669 &handles, NULL, GFHP_LOCAL_HANDLES));
670 gf_set_so_readc(&gc, in_store);
671 gf_set_so_writec(&pc, out_store);
672 gf_pipe(gc, pc);
673 gf_clear_so_writec(out_store);
674 gf_clear_so_readc(in_store);
676 memset(&sargs, 0, sizeof(SCROLL_S));
677 sargs.text.handles = handles;
678 sargs.text.text = so_text(out_store);
679 sargs.text.src = CharStar;
680 sargs.text.desc = _("help text");
681 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
682 sargs.proc.tool = oauth2_auth_answer;
683 sargs.proc.data.p = (void *)&user_input;
684 sargs.keys.menu = &oauth2_auth_keymenu;
685 /* don't want to re-enter c-client */
686 sargs.quell_newmail = 1;
687 setbitmap(sargs.keys.bitmap);
688 sargs.help.text = h_oauth2_start;
689 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
691 do {
692 scrolltool(&sargs);
693 ps_global->mangled_screen = 1;
694 ps_global->painted_body_on_startup = 0;
695 ps_global->painted_footer_on_startup = 0;
696 } while (user_input.answer != 'e');
698 if(!struncmp(user_input.code, "http://", 7)
699 || !struncmp(user_input.code, "https://", 8)){
700 char *s, *t;
701 s = strstr(user_input.code, "code=");
702 if(s != NULL){
703 t = strchr(s, '&');
704 if(t) *t = '\0';
705 code = cpystr(s+5);
706 if(t) *t = '&';
709 else code = user_input.code ? cpystr(user_input.code) : NULL;
710 if(user_input.code) fs_give((void **) &user_input.code);
712 if(code == NULL) *tryanother = 1;
714 so_give(&in_store);
715 so_give(&out_store);
716 free_handles(&handles);
718 else{
719 int flags, rc, q_line;
720 /* TRANSLATORS: user needs to input an access code from the server */
721 char *accesscodelabel = _("Copy and Paste Access Code");
722 char prompt[MAILTMPLEN], token[MAILTMPLEN];
724 * If screen hasn't been initialized yet, use want_to.
726 try_wantto:
727 tmp_20k_buf[0] = '\0';
728 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
729 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2->name);
730 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
732 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
733 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
734 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
736 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
737 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
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", 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 _("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);
746 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
748 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
749 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
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", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
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", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
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 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. "));
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", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
766 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
768 display_init_err(tmp_20k_buf, 0);
769 memset((void *)tmp, 0, sizeof(tmp));
770 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
771 tmp[sizeof(tmp)-1] = '\0';
773 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
774 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
775 flags = OE_APPEND_CURRENT;
776 sprintf(prompt, "%s: ", accesscodelabel);
777 do {
778 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
779 prompt, NULL, NO_HELP, &flags);
780 } while (rc != 0 && rc != 1);
781 if(!struncmp(token, "http://", 7)
782 || !struncmp(token, "https://", 8)){
783 char *s, *t;
784 s = strstr(token, "code=");
785 if(s != NULL){
786 t = strchr(s, '&');
787 if(t) *t = '\0';
788 code = cpystr(s+5);
789 if(t) *t = '&';
792 else code = token[0] ? cpystr(token) : NULL;
796 return code;
799 void mm_login_oauth2(NETMBX *, char *, char *, OAUTH2_S *, long int, char *, char *);
801 /* The purpose of this function is to report to c-client the values of the
802 * different tokens and codes so that c-client can try to log in the user
803 * to the server. This function DOES NOT attempt to get these values for
804 * the user. That is attempted in the c-client side (as best as it can be
805 * done given our circumstances: no http support, no javascript support,
806 * etc.). This is the best we can do:
808 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
809 * as best as we can. Unloaded means that there is no server known to
810 * connect, no access token, etc. Pretty much nothing is known about
811 * how to get access code, access token, etc. We ask the user to fill
812 * it up for us, if they can. If the user fills it up we save those
813 * values.
815 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
816 * save the information that c-client got for us.
818 * 3. When saving this information we use the password caching facilities,
819 * but we must do it in a different format so that old information and
820 * new information are not mixed. In order to accommodate this for new
821 * authentication methods, we save the information in the same fields,
822 * but this time we modify it slightly, so that old functions fail to
823 * understand the new information and so not modify it nor use it. The
824 * modification is simple: Every piece of information that was saved
825 * before is prepended XOAUTH2\001 to indicate the authentication
826 * method for which it works. The character \001 is a separator. New
827 * Alpine will know how to deal with this, but old versions, will not
828 * strip this prefix from the information and fail to get the
829 * information or modify it when needed. Only new versions of Alpine will
830 * know how to process this information.
831 * new_value = authenticator_method separator old_value
832 * authenticator_method = "XOAUTH2_S"
833 * separator = "U+1"
834 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
835 * authenticator is "XOAUTH2\001imap.gmail.com".
836 * In addition, the password field is not used to encode the password
837 * anymore, it is used to save login information needed in a format that
838 * the caller function chooses, but that must be preceded by the
839 * "authenticator_method separator" code as above.
841 void
842 mm_login_oauth2(NETMBX *mb, char *user, char *method,
843 OAUTH2_S *login, long int trial,
844 char *usethisprompt, char *altuserforcache)
846 char *token, tmp[MAILTMPLEN];
847 char prompt[4*MAILTMPLEN], value[4*MAILTMPLEN], *last;
848 char defuser[NETMAXUSER];
849 char hostleadin[80], hostname[200], defubuf[200];
850 char logleadin[80], pwleadin[50];
851 char *url_oauth2;
852 char *tool = NULL;
853 char *OldRefreshToken, *OldAccessToken;
854 char *NewRefreshToken, *NewAccessToken;
855 char *SaveRefreshToken, *SaveAccessToken;
856 /* TRANSLATORS: A label for the hostname that the user is logging in on */
857 char *hostlabel = _("HOST");
858 /* TRANSLATORS: user is logging in as a particular user (a particular
859 login name), this is just labelling that user name. */
860 char *userlabel = _("USER");
861 STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1];
862 HelpType help ;
863 int len, rc, q_line, flags, i, j;
864 int oespace, avail, need, save_dont_use;
865 int save_in_init;
866 int registered;
867 int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
868 OAUTH2_S *oa2list, *oa2;
869 XOAUTH2_INFO_S *x;
871 unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
872 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
873 int preserve_password = -1;
874 #endif
876 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
877 trial, mb->user ? mb->user : "(null)",
878 mb->service ? mb->service : "(null)",
879 mb->port ? " port=" : "",
880 mb->port ? comatose(mb->port) : "",
881 altuserforcache ? " altuserforcache =" : "",
882 altuserforcache ? altuserforcache : ""));
884 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
886 save_in_init = ps_global->in_init_seq;
887 ps_global->in_init_seq = 0;
888 ps_global->no_newmail_check_from_optionally_enter = 1;
890 /* make sure errors are seen */
891 if(ps_global->ttyo)
892 flush_status_messages(0);
894 token = NULL; /* start from scratch */
896 hostlist[0].name = mb->host;
897 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
898 hostlist[0].next = &hostlist[1];
899 hostlist[1].name = mb->orighost;
900 hostlist[1].next = NULL;
902 else
903 hostlist[0].next = NULL;
905 if(hostlist[0].name){
906 dprint((9, "mm_login_oauth2: host=%s\n",
907 hostlist[0].name ? hostlist[0].name : "?"));
908 if(hostlist[0].next && hostlist[1].name){
909 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name));
913 if(trial == 0L && !altuserforcache){
914 if(*mb->user != '\0')
915 strncpy(user, mb->user, NETMAXUSER);
916 else{
917 flags = OE_APPEND_CURRENT;
918 sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
919 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
920 prompt, NULL, NO_HELP, &flags);
922 user[NETMAXUSER-1] = '\0';
926 * We check to see if the server we are going to log in to is already
927 * registered. This gives us a list of servers with the same
928 * credentials, so we use the same credentials for all of them.
931 for(registered = 0, oa2list = alpine_oauth2_list;
932 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
933 oa2list++){
934 for(i = 0; i < OAUTH2_TOT_EQUIV
935 && oa2list->host[i] != NULL
936 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
937 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
938 registered++;
939 break;
943 if(registered){
944 x = oauth2_get_client_info(oa2list->name, user);
945 if(x && x->flow){
946 for(oa2list = alpine_oauth2_list;
947 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
948 oa2list++){
949 for(i = 0; i < OAUTH2_TOT_EQUIV
950 && oa2list->host[i] != NULL
951 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
952 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
953 char *flow = oa2list->server_mthd[0].name ? "Authorize"
954 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
955 if(!strucmp(x->flow, flow)) break; /* found it */
959 /* else use the one we found earlier, the user has to configure this better */
962 if(registered){
963 hostlist2[i = 0].name = mb->host;
964 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
965 hostlist2[++i].name = mb->orighost;
967 for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){
968 int k;
969 for(k = 0; k <= i && hostlist2[k].name
970 && strcmp(hostlist2[k].name, oa2list->host[j]); k++);
971 if(k == i + 1)
972 hostlist2[++i].name = oa2list->host[j];
974 hostlist2[i+1].name = NULL;
975 hostlist2[i+1].next = NULL;
976 for(j = i; j >= 0; j--)
977 hostlist2[j].next = &hostlist2[j+1];
981 * We check if we have a refresh token saved somewhere, if so
982 * we use it to get a new access token, otherwise we need to
983 * get an access code so we can get (and save) a refresh token
984 * and use the access token.
986 if(trial == 0L && !altuserforcache){
987 /* Search for a refresh token that is already loaded ... */
988 if(imap_get_passwd_auth(mm_login_list, &token, user,
989 registered ? hostlist2 : hostlist,
990 (mb->sslflag||mb->tlsflag), OA2NAME)){
991 dprint((9, "mm_login_oauth2: found a refresh token\n"));
992 ps_global->no_newmail_check_from_optionally_enter = 0;
993 ps_global->in_init_seq = save_in_init;
995 #ifdef LOCAL_PASSWD_CACHE
996 /* or see if we have saved one in the local password cache and load it */
997 else if(get_passfile_passwd_auth(ps_global->pinerc, &token,
998 user, registered ? hostlist2 : hostlist,
999 (mb->sslflag||mb->tlsflag), OA2NAME)){
1000 imap_set_passwd_auth(&mm_login_list, token, user,
1001 hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1002 update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist,
1003 (mb->sslflag||mb->tlsflag), OA2NAME);
1004 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
1005 ps_global->no_newmail_check_from_optionally_enter = 0;
1006 ps_global->in_init_seq = save_in_init;
1008 if(token && *token) preserve_password = 1; /* resave it, no need to ask */
1009 #endif /* LOCAL_PASSWD_CACHE */
1011 user[NETMAXUSER-1] = '\0';
1013 /* The Old* variables is what c_client knows */
1014 OldRefreshToken = login->param[OA2_RefreshToken].value;
1015 OldAccessToken = login->access_token;
1016 OldExpirationTime = login->expiration;
1018 /* The New* variables is what Alpine knows */
1019 NewRefreshToken = NewAccessToken = NULL;
1020 NewExpirationTime = 0L;
1021 ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0;
1023 if(token && *token){
1024 char *s, *t;
1026 s = token;
1027 t = strchr(s, PWDAUTHSEP);
1028 if(t == NULL)
1029 NewRefreshToken = cpystr(s);
1030 else {
1031 *t++ = '\0';
1032 NewRefreshToken = cpystr(s);
1033 s = t;
1034 t = strchr(s, PWDAUTHSEP);
1035 if(t == NULL)
1036 NewAccessToken = cpystr(s);
1037 else {
1038 *t++ = '\0';
1039 NewAccessToken = cpystr(s);
1040 s = t;
1041 NewExpirationTime = strtol(s, &s, 10);
1042 if(NewExpirationTime <= 0 || NewExpirationTime <= time(0))
1043 NewExpirationTime = 0L;
1046 /* check we got good information, and send good information below */
1047 if(NewRefreshToken && !*NewRefreshToken)
1048 fs_give((void **) &NewRefreshToken);
1049 if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken))
1050 fs_give((void **) &NewAccessToken);
1052 else login->first_time++;
1054 if(login->first_time){ /* count how many authorization methods we support */
1055 int nmethods, i, j;
1057 for(nmethods = 0, oa2 = alpine_oauth2_list; oa2 && oa2->name ; oa2++){
1058 for(j = 0; j < OAUTH2_TOT_EQUIV
1059 && oa2
1060 && oa2->host[j] != NULL
1061 && strucmp(oa2->host[j], mb->orighost) != 0; j++);
1062 if(oa2 && oa2->host && j < OAUTH2_TOT_EQUIV && oa2->host[j])
1063 nmethods++;
1066 if(nmethods > 1)
1067 oa2list = oauth2_select_flow(mb->orighost);
1069 if(!oa2list) registered = 0;
1072 /* Default to saving what we already had saved */
1074 SaveRefreshToken = NewRefreshToken;
1075 SaveAccessToken = NewAccessToken;
1076 SaveExpirationTime = NewExpirationTime;
1078 /* Translation of the logic below:
1079 * if (c-client has a refresh token
1080 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1081 forget the Alpine refresh token;
1082 make the Alpine Refresh token = c-client refresh token.;
1083 signal that we changed the refresh token;
1084 In this situation we do not need to clear up the Alpine access token. This will
1085 expire, so we can use it until it expires. We can save the c-client refresh token
1086 together with the Alpine Access Token and the expiration date of the Alpine Access
1087 Token.
1088 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1089 forget the Alpine refresh token;
1090 if Alpine has an access token, forget it;
1091 reset the expiration time
1092 signal that we changed the refresh token; (because the service expired it)
1096 if(OldRefreshToken != NULL
1097 && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){
1098 if(NewRefreshToken) fs_give((void **) &NewRefreshToken);
1099 NewRefreshToken = cpystr(OldRefreshToken);
1100 ChangeRefreshToken++;
1101 SaveRefreshToken = OldRefreshToken;
1102 SaveAccessToken = NewAccessToken;
1103 SaveExpirationTime = NewExpirationTime;
1104 } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){
1105 fs_give((void **) &NewRefreshToken);
1106 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1107 NewExpirationTime = 0L;
1108 ChangeRefreshToken++;
1109 SaveRefreshToken = NULL;
1110 SaveAccessToken = NULL;
1111 SaveExpirationTime = 0L;
1114 if(OldAccessToken != NULL
1115 && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){
1116 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1117 NewAccessToken = cpystr(OldAccessToken);
1118 ChangeAccessToken++;
1119 NewExpirationTime = OldExpirationTime;
1120 SaveRefreshToken = NewRefreshToken;
1121 SaveAccessToken = NewAccessToken;
1122 SaveExpirationTime = NewExpirationTime;
1125 if(!registered){
1126 login->param[OA2_RefreshToken].value = SaveRefreshToken;
1127 login->access_token = SaveAccessToken;
1128 login->expiration = SaveExpirationTime;
1129 } else {
1130 oa2list->param[OA2_RefreshToken].value = SaveRefreshToken;
1131 oa2list->access_token = SaveAccessToken;
1132 oa2list->expiration = SaveExpirationTime;
1133 oa2list->first_time = login->first_time;
1134 *login = *oa2list; /* load login pointer */
1137 if(!ChangeAccessToken && !ChangeRefreshToken)
1138 return;
1140 /* get ready to save this information. The format will be
1141 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1142 * (spaces added for clarity, \001 is PWDAUTHSEP)
1144 if(token) fs_give((void **) &token);
1145 sprintf(tmp, "%lu", SaveExpirationTime);
1146 tmp[sizeof(tmp) - 1] = '\0';
1147 len = strlen(SaveRefreshToken ? SaveRefreshToken : "")
1148 + strlen(SaveAccessToken ? SaveAccessToken : "")
1149 + strlen(tmp) + 2;
1150 token = fs_get(len + 1);
1151 sprintf(token, "%s%c%s%c%lu",
1152 SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP,
1153 SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP,
1154 SaveExpirationTime);
1156 /* remember the access information for next time */
1157 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
1158 imap_set_passwd_auth(&mm_login_list, token,
1159 altuserforcache ? altuserforcache : user, hostlist,
1160 (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1161 #ifdef LOCAL_PASSWD_CACHE
1162 /* if requested, remember it on disk for next session */
1163 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
1164 set_passfile_passwd_auth(ps_global->pinerc, token,
1165 altuserforcache ? altuserforcache : user, hostlist,
1166 (mb->sslflag||mb->tlsflag),
1167 (preserve_password == -1 ? 0
1168 : (preserve_password == 0 ? 2 :1)), OA2NAME);
1169 #endif /* LOCAL_PASSWD_CACHE */
1171 ps_global->no_newmail_check_from_optionally_enter = 0;
1174 /*----------------------------------------------------------------------
1175 receive notification from IMAP
1177 Args: stream -- Mail stream message is relevant to
1178 string -- The message text
1179 errflg -- Set if it is a serious error
1181 Result: message displayed in status line
1183 The facility is for general notices, such as connection to server;
1184 server shutting down etc... It is used infrequently.
1185 ----------------------------------------------------------------------*/
1186 void
1187 mm_notify(MAILSTREAM *stream, char *string, long int errflg)
1189 time_t now;
1190 struct tm *tm_now;
1192 now = time((time_t *)0);
1193 tm_now = localtime(&now);
1195 /* be sure to log the message... */
1196 #ifdef DEBUG
1197 if(ps_global->debug_imap || ps_global->debugmem)
1198 dprint((errflg == TCPDEBUG ? 7 : 2,
1199 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1200 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1201 tm_now->tm_mon+1, tm_now->tm_mday,
1202 (!errflg) ? "babble" :
1203 (errflg == ERROR) ? "error" :
1204 (errflg == WARN) ? "warning" :
1205 (errflg == PARSE) ? "parse" :
1206 (errflg == TCPDEBUG) ? "tcp" :
1207 (errflg == BYE) ? "bye" : "unknown",
1208 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1209 string ? string : "?"));
1210 #endif
1212 snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s",
1213 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1214 (int) MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70),
1215 string);
1216 ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols
1217 : sizeof(ps_global->last_error)-1] = '\0';
1220 * Then either set special bits in the pine struct or
1221 * display the message if it's tagged as an "ALERT" or
1222 * its errflg > NIL (i.e., WARN, or ERROR)
1224 if(errflg == BYE)
1226 * We'd like to sp_mark_stream_dead() here but we can't do that because
1227 * that might call mail_close and we are already in a c-client callback.
1228 * So just set the dead bit and clean it up later.
1230 sp_set_dead_stream(stream, 1);
1231 else if(!strncmp(string, "[TRYCREATE]", 11))
1232 ps_global->try_to_create = 1;
1233 else if(!strncmp(string, "[REFERRAL ", 10))
1234 ; /* handled in the imap_referral() callback */
1235 else if(!strncmp(string, "[ALERT]", 7))
1236 q_status_message2(SM_MODAL, 3, 3,
1237 _("Alert received while accessing \"%s\": %s"),
1238 (stream && stream->mailbox)
1239 ? stream->mailbox : "-no folder-",
1240 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000),
1241 SIZEOF_20KBUF-10000, string));
1242 else if(!strncmp(string, "[UNSEEN ", 8)){
1243 char *p;
1244 long n = 0;
1246 for(p = string + 8; isdigit(*p); p++)
1247 n = (n * 10) + (*p - '0');
1249 sp_set_first_unseen(stream, n);
1251 else if(!strncmp(string, "[READ-ONLY]", 11)
1252 && !(stream && stream->mailbox && IS_NEWS(stream)))
1253 q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s",
1254 (stream && stream->mailbox)
1255 ? stream->mailbox : "-no folder-",
1256 string + 11);
1257 else if((errflg && errflg != BYE && errflg != PARSE)
1258 && !ps_global->noshow_error
1259 && !(errflg == WARN
1260 && (ps_global->noshow_warn || (stream && stream->unhealthy))))
1261 q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0),
1262 3, 6, ps_global->last_error);
1266 /*----------------------------------------------------------------------
1267 Queue imap log message for display in the message line
1269 Args: string -- The message
1270 errflg -- flag set to 1 if pertains to an error
1272 Result: Message queued for display
1274 The c-client/imap reports most of it's status and errors here
1275 ---*/
1276 void
1277 mm_log(char *string, long int errflg)
1279 char message[sizeof(ps_global->c_client_error)];
1280 char *occurence;
1281 int was_capitalized;
1282 static char saw_kerberos_init_warning;
1283 time_t now;
1284 struct tm *tm_now;
1286 now = time((time_t *)0);
1287 tm_now = localtime(&now);
1289 dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? 1 :
1290 (errflg == TCPDEBUG) ? 10 : 2,
1291 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1292 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1293 tm_now->tm_mon+1, tm_now->tm_mday,
1294 (!errflg) ? "babble" :
1295 (errflg == ERROR) ? "error" :
1296 (errflg == WARN) ? "warning" :
1297 (errflg == PARSE) ? "parse" :
1298 (errflg == TCPDEBUG) ? "tcp" :
1299 (errflg == BYE) ? "bye" : "unknown",
1300 string ? string : "?"));
1302 if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){
1303 ps_global->try_to_create = 1;
1304 return;
1306 else if(ps_global->try_to_create
1307 || !strncmp(string, "[CLOSED]", 8)
1308 || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op")))
1310 * Don't display if creating new folder OR
1311 * warning about a dead stream ...
1313 return;
1315 strncpy(message, string, sizeof(message));
1316 message[sizeof(message) - 1] = '\0';
1318 if(errflg == WARN && srchstr(message, "try running kinit") != NULL){
1319 if(saw_kerberos_init_warning)
1320 return;
1322 saw_kerberos_init_warning = 1;
1325 /*---- replace all "mailbox" with "folder" ------*/
1326 occurence = srchstr(message, "mailbox");
1327 while(occurence) {
1328 if(!*(occurence+7)
1329 || isspace((unsigned char) *(occurence+7))
1330 || *(occurence+7) == ':'){
1331 was_capitalized = isupper((unsigned char) *occurence);
1332 rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder"));
1333 if(was_capitalized)
1334 *occurence = (errflg == PARSE ? 'A' : 'F');
1336 else
1337 occurence += 7;
1339 occurence = srchstr(occurence, "mailbox");
1342 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1343 occurence = srchstr(message, "GSSAPI");
1344 while(occurence) {
1345 if(!*(occurence+6)
1346 || isspace((unsigned char) *(occurence+6))
1347 || *(occurence+6) == ':')
1348 rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos");
1349 else
1350 occurence += 6;
1352 occurence = srchstr(occurence, "GSSAPI");
1355 if(errflg == ERROR)
1356 ps_global->mm_log_error = 1;
1358 if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error))
1359 strncpy(ps_global->c_client_error, message,
1360 sizeof(ps_global->c_client_error));
1362 if(ps_global->noshow_error
1363 || (ps_global->noshow_warn && errflg == WARN)
1364 || !(errflg == ERROR || errflg == WARN))
1365 return; /* Only care about errors; don't print when asked not to */
1367 /*---- Display the message ------*/
1368 q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER,
1369 3, 5, message);
1370 strncpy(ps_global->last_error, message, sizeof(ps_global->last_error));
1371 ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
1374 void
1375 mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial,
1376 char *method, char *usethisprompt, char *altuserforcache)
1378 if(method == NULL)
1379 return;
1380 if(strucmp(method, OA2NAME) == 0 || strucmp(method, BEARERNAME) == 0)
1381 mm_login_oauth2(mb, user, method, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache);
1384 void
1385 mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial,
1386 char *usethisprompt, char *altuserforcache)
1388 char tmp[MAILTMPLEN];
1389 char prompt[1000], *last;
1390 char port[20], non_def_port[20], insecure[20];
1391 char defuser[NETMAXUSER];
1392 char hostleadin[80], hostname[200], defubuf[200];
1393 char logleadin[80], pwleadin[50];
1394 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
1395 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1396 that the password will be sent unencrypted over the network. This is
1397 just a warning message that gets added parenthetically when the user
1398 is asked for a password. */
1399 char *insec = _(" (INSECURE)");
1400 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1401 after having already failed at least once. */
1402 char *retry = _("Retrying - ");
1403 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1404 char *hostlabel = _("HOST");
1405 /* TRANSLATORS: user is logging in as a particular user (a particular
1406 login name), this is just labelling that user name. */
1407 char *userlabel = _("USER");
1408 STRLIST_S hostlist[2];
1409 HelpType help ;
1410 int len, rc, q_line, flags;
1411 int oespace, avail, need, save_dont_use;
1412 int save_in_init;
1413 struct servent *sv;
1414 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1415 int preserve_password = -1;
1416 #endif
1418 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1419 trial, mb->user ? mb->user : "(null)",
1420 mb->service ? mb->service : "(null)",
1421 mb->port ? " port=" : "",
1422 mb->port ? comatose(mb->port) : "",
1423 altuserforcache ? " altuserforcache =" : "",
1424 altuserforcache ? altuserforcache : ""));
1425 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
1427 save_in_init = ps_global->in_init_seq;
1428 ps_global->in_init_seq = 0;
1429 ps_global->no_newmail_check_from_optionally_enter = 1;
1431 /* make sure errors are seen */
1432 if(ps_global->ttyo)
1433 flush_status_messages(0);
1436 * Add port number to hostname if going through a tunnel or something
1438 non_def_port[0] = '\0';
1439 if(mb->port && mb->service &&
1440 (sv = getservbyname(mb->service, "tcp")) &&
1441 (mb->port != ntohs(sv->s_port))){
1442 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
1443 non_def_port[sizeof(non_def_port)-1] = '\0';
1444 dprint((9, "mm_login: using non-default port=%s\n",
1445 non_def_port ? non_def_port : "?"));
1449 * set up host list for sybil servers...
1451 if(*non_def_port){
1452 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
1453 hostlist0[sizeof(hostlist0)-1] = '\0';
1454 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
1455 hostlist0[sizeof(hostlist0)-1] = '\0';
1456 hostlist[0].name = hostlist0;
1457 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1458 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
1459 hostlist1[sizeof(hostlist1)-1] = '\0';
1460 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
1461 hostlist1[sizeof(hostlist1)-1] = '\0';
1462 hostlist[0].next = &hostlist[1];
1463 hostlist[1].name = hostlist1;
1464 hostlist[1].next = NULL;
1466 else
1467 hostlist[0].next = NULL;
1469 else{
1470 hostlist[0].name = mb->host;
1471 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1472 hostlist[0].next = &hostlist[1];
1473 hostlist[1].name = mb->orighost;
1474 hostlist[1].next = NULL;
1476 else
1477 hostlist[0].next = NULL;
1480 if(hostlist[0].name){
1481 dprint((9, "mm_login: host=%s\n",
1482 hostlist[0].name ? hostlist[0].name : "?"));
1483 if(hostlist[0].next && hostlist[1].name){
1484 dprint((9, "mm_login: orighost=%s\n", hostlist[1].name));
1489 * Initialize user name with either
1490 * 1) /user= value in the stream being logged into,
1491 * or 2) the user name we're running under.
1493 * Note that VAR_USER_ID is not yet initialized if this login is
1494 * the one to access the remote config file. In that case, the user
1495 * can supply the username in the config file name with /user=.
1497 if(trial == 0L && !altuserforcache){
1498 strncpy(user, (*mb->user) ? mb->user :
1499 ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "",
1500 NETMAXUSER);
1501 user[NETMAXUSER-1] = '\0';
1503 /* try last working password associated with this host. */
1504 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1505 (mb->sslflag||mb->tlsflag))){
1506 dprint((9, "mm_login: found a password to try\n"));
1507 ps_global->no_newmail_check_from_optionally_enter = 0;
1508 ps_global->in_init_seq = save_in_init;
1509 return;
1512 #ifdef LOCAL_PASSWD_CACHE
1513 /* check to see if there's a password left over from last session */
1514 if(get_passfile_passwd(ps_global->pinerc, pwd,
1515 user, hostlist, (mb->sslflag||mb->tlsflag))){
1516 imap_set_passwd(&mm_login_list, *pwd, user,
1517 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1518 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1519 (mb->sslflag||mb->tlsflag));
1520 dprint((9, "mm_login: found a password in passfile to try\n"));
1521 ps_global->no_newmail_check_from_optionally_enter = 0;
1522 ps_global->in_init_seq = save_in_init;
1523 return;
1525 #endif /* LOCAL_PASSWD_CACHE */
1528 * If no explicit user name supplied and we've not logged in
1529 * with our local user name, see if we've visited this
1530 * host before as someone else.
1532 if(!*mb->user &&
1533 ((last = imap_get_user(mm_login_list, hostlist))
1534 #ifdef LOCAL_PASSWD_CACHE
1536 (last = get_passfile_user(ps_global->pinerc, hostlist))
1537 #endif /* LOCAL_PASSWD_CACHE */
1539 strncpy(user, last, NETMAXUSER);
1540 user[NETMAXUSER-1] = '\0';
1541 dprint((9, "mm_login: found user=%s\n",
1542 user ? user : "?"));
1544 /* try last working password associated with this host/user. */
1545 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1546 (mb->sslflag||mb->tlsflag))){
1547 dprint((9,
1548 "mm_login: found a password for user=%s to try\n",
1549 user ? user : "?"));
1550 ps_global->no_newmail_check_from_optionally_enter = 0;
1551 ps_global->in_init_seq = save_in_init;
1552 return;
1555 #ifdef LOCAL_PASSWD_CACHE
1556 /* check to see if there's a password left over from last session */
1557 if(get_passfile_passwd(ps_global->pinerc, pwd,
1558 user, hostlist, (mb->sslflag||mb->tlsflag))){
1559 imap_set_passwd(&mm_login_list, *pwd, user,
1560 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1561 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1562 (mb->sslflag||mb->tlsflag));
1563 dprint((9,
1564 "mm_login: found a password for user=%s in passfile to try\n",
1565 user ? user : "?"));
1566 ps_global->no_newmail_check_from_optionally_enter = 0;
1567 ps_global->in_init_seq = save_in_init;
1568 return;
1570 #endif /* LOCAL_PASSWD_CACHE */
1573 #if !defined(DOS) && !defined(OS2)
1574 if(!*mb->user && !*user &&
1575 (last = (ps_global->ui.login && ps_global->ui.login[0])
1576 ? ps_global->ui.login : NULL)
1578 strncpy(user, last, NETMAXUSER);
1579 user[NETMAXUSER-1] = '\0';
1580 dprint((9, "mm_login: found user=%s\n",
1581 user ? user : "?"));
1583 /* try last working password associated with this host. */
1584 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1585 (mb->sslflag||mb->tlsflag))){
1586 dprint((9, "mm_login:ui: found a password to try\n"));
1587 ps_global->no_newmail_check_from_optionally_enter = 0;
1588 ps_global->in_init_seq = save_in_init;
1589 return;
1592 #ifdef LOCAL_PASSWD_CACHE
1593 /* check to see if there's a password left over from last session */
1594 if(get_passfile_passwd(ps_global->pinerc, pwd,
1595 user, hostlist, (mb->sslflag||mb->tlsflag))){
1596 imap_set_passwd(&mm_login_list, *pwd, user,
1597 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1598 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1599 (mb->sslflag||mb->tlsflag));
1600 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1601 ps_global->no_newmail_check_from_optionally_enter = 0;
1602 ps_global->in_init_seq = save_in_init;
1603 return;
1605 #endif /* LOCAL_PASSWD_CACHE */
1607 #endif
1610 user[NETMAXUSER-1] = '\0';
1612 if(trial == 0)
1613 retry = "";
1616 * Even if we have a user now, user gets a chance to change it.
1618 ps_global->mangled_footer = 1;
1619 if(!*mb->user && !altuserforcache){
1621 help = NO_HELP;
1624 * Instead of offering user with a value that the user can edit,
1625 * we offer [user] as a default so that the user can type CR to
1626 * use it. Otherwise, the user has to type in whole name.
1628 strncpy(defuser, user, sizeof(defuser)-1);
1629 defuser[sizeof(defuser)-1] = '\0';
1630 user[0] = '\0';
1633 * Need space for "Retrying - "
1634 * "+ HOST: "
1635 * hostname
1636 * " (INSECURE)"
1637 * ENTER LOGIN NAME
1638 * " [defuser] : "
1639 * about 15 chars for input
1642 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1643 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1644 hostleadin[sizeof(hostleadin)-1] = '\0';
1646 strncpy(hostname, mb->host, sizeof(hostname)-1);
1647 hostname[sizeof(hostname)-1] = '\0';
1650 * Add port number to hostname if going through a tunnel or something
1652 if(*non_def_port)
1653 strncpy(port, non_def_port, sizeof(port));
1654 else
1655 port[0] = '\0';
1657 insecure[0] = '\0';
1658 /* if not encrypted and SSL/TLS is supported */
1659 if(!(mb->sslflag||mb->tlsflag) &&
1660 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1661 strncpy(insecure, insec, sizeof(insecure));
1663 /* TRANSLATORS: user is being asked to type in their login name */
1664 snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME"));
1666 snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "",
1667 (*defuser) ? defuser : "",
1668 (*defuser) ? "]" : "");
1669 defubuf[sizeof(defubuf)-1] = '\0';
1670 /* space reserved after prompt */
1671 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1673 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1674 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1675 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace;
1677 /* If we're retrying cut the hostname back to the first word. */
1678 if(avail < need && trial > 0){
1679 char *p;
1681 len = strlen(hostname);
1682 if((p = strchr(hostname, '.')) != NULL){
1683 *p = '\0';
1684 need -= (len - strlen(hostname));
1688 if(avail < need){
1689 need -= utf8_width(retry);
1690 retry = "";
1692 if(avail < need){
1694 /* reduce length of logleadin */
1695 len = utf8_width(logleadin);
1696 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1697 longer version doesn't fit on screen */
1698 snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN"));
1699 need -= (len - utf8_width(logleadin));
1701 if(avail < need){
1702 /* get two spaces from hostleadin */
1703 len = utf8_width(hostleadin);
1704 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1705 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1706 hostleadin[sizeof(hostleadin)-1] = '\0';
1707 need -= (len - utf8_width(hostleadin));
1709 /* get rid of port */
1710 if(avail < need && strlen(port) > 0){
1711 need -= strlen(port);
1712 port[0] = '\0';
1715 if(avail < need){
1716 int reduce_to;
1719 * Reduce space for hostname. Best we can do is 6 chars
1720 * with hos...
1722 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1723 len = strlen(hostname);
1724 strncpy(hostname+reduce_to-3, "...", 4);
1725 need -= (len - strlen(hostname));
1727 if(avail < need && strlen(insecure) > 0){
1728 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1729 need -= 3;
1730 insecure[strlen(insecure)-4] = ')';
1731 insecure[strlen(insecure)-3] = '\0';
1733 else{
1734 need -= utf8_width(insecure);
1735 insecure[0] = '\0';
1739 if(avail < need){
1740 if(strlen(defubuf) > 3){
1741 len = strlen(defubuf);
1742 strncpy(defubuf, " [..] :", 9);
1743 need -= (len - strlen(defubuf));
1746 if(avail < need)
1747 strncpy(defubuf, ":", 2);
1750 * If it still doesn't fit, optionally_enter gets
1751 * to worry about it.
1759 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s",
1760 retry, hostleadin, hostname, port, insecure, logleadin, defubuf);
1761 prompt[sizeof(prompt)-1] = '\0';
1763 while(1) {
1764 if(ps_global->ttyo)
1765 mm_login_alt_cue(mb);
1767 flags = OE_APPEND_CURRENT;
1768 save_dont_use = ps_global->dont_use_init_cmds;
1769 ps_global->dont_use_init_cmds = 1;
1770 #ifdef _WINDOWS
1771 if(!*user && *defuser){
1772 strncpy(user, defuser, NETMAXUSER);
1773 user[NETMAXUSER-1] = '\0';
1776 rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD,
1777 #ifdef LOCAL_PASSWD_CACHE
1778 is_using_passfile() ? 1 :
1779 #endif /* LOCAL_PASSWD_CACHE */
1780 0, 0, &preserve_password);
1781 ps_global->dont_use_init_cmds = save_dont_use;
1782 if(rc == 0 && *user && *pwd)
1783 goto nopwpmt;
1784 #else /* !_WINDOWS */
1785 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
1786 prompt, NULL, help, &flags);
1787 #endif /* !_WINDOWS */
1788 ps_global->dont_use_init_cmds = save_dont_use;
1790 if(rc == 3) {
1791 help = help == NO_HELP ? h_oe_login : NO_HELP;
1792 continue;
1795 /* default */
1796 if(rc == 0 && !*user){
1797 strncpy(user, defuser, NETMAXUSER);
1798 user[NETMAXUSER-1] = '\0';
1801 if(rc != 4)
1802 break;
1805 if(rc == 1 || !user[0]) {
1806 ps_global->user_says_cancel = (rc == 1);
1807 user[0] = '\0';
1810 else{
1811 strncpy(user, mb->user, NETMAXUSER);
1812 user[NETMAXUSER-1] = '\0';
1815 user[NETMAXUSER-1] = '\0';
1817 if(!(user[0] || altuserforcache)){
1818 ps_global->no_newmail_check_from_optionally_enter = 0;
1819 ps_global->in_init_seq = save_in_init;
1820 return;
1824 * Now that we have a user, we can check in the cache again to see
1825 * if there is a password there. Try last working password associated
1826 * with this host and user.
1828 if(trial == 0L && !*mb->user && !altuserforcache){
1829 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1830 (mb->sslflag||mb->tlsflag))){
1831 ps_global->no_newmail_check_from_optionally_enter = 0;
1832 ps_global->in_init_seq = save_in_init;
1833 return;
1836 #ifdef LOCAL_PASSWD_CACHE
1837 if(get_passfile_passwd(ps_global->pinerc, pwd,
1838 user, hostlist, (mb->sslflag||mb->tlsflag))){
1839 imap_set_passwd(&mm_login_list, *pwd, user,
1840 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1841 ps_global->no_newmail_check_from_optionally_enter = 0;
1842 ps_global->in_init_seq = save_in_init;
1843 return;
1845 #endif /* LOCAL_PASSWD_CACHE */
1847 else if(trial == 0 && altuserforcache){
1848 if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist,
1849 (mb->sslflag||mb->tlsflag))){
1850 ps_global->no_newmail_check_from_optionally_enter = 0;
1851 ps_global->in_init_seq = save_in_init;
1852 return;
1855 #ifdef LOCAL_PASSWD_CACHE
1856 if(get_passfile_passwd(ps_global->pinerc, pwd,
1857 altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
1858 imap_set_passwd(&mm_login_list, *pwd, altuserforcache,
1859 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1860 ps_global->no_newmail_check_from_optionally_enter = 0;
1861 ps_global->in_init_seq = save_in_init;
1862 return;
1864 #endif /* LOCAL_PASSWD_CACHE */
1868 * Didn't find password in cache or this isn't the first try. Ask user.
1870 help = NO_HELP;
1873 * Need space for "Retrying - "
1874 * "+ HOST: "
1875 * hostname
1876 * " (INSECURE) "
1877 * " USER: "
1878 * user
1879 * " ENTER PASSWORD: "
1880 * about 15 chars for input
1883 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1884 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1886 strncpy(hostname, mb->host, sizeof(hostname)-1);
1887 hostname[sizeof(hostname)-1] = '\0';
1890 * Add port number to hostname if going through a tunnel or something
1892 if(*non_def_port)
1893 strncpy(port, non_def_port, sizeof(port));
1894 else
1895 port[0] = '\0';
1897 insecure[0] = '\0';
1899 /* if not encrypted and SSL/TLS is supported */
1900 if(!(mb->sslflag||mb->tlsflag) &&
1901 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1902 strncpy(insecure, insec, sizeof(insecure));
1904 if(usethisprompt){
1905 strncpy(logleadin, usethisprompt, sizeof(logleadin));
1906 logleadin[sizeof(logleadin)-1] = '\0';
1907 defubuf[0] = '\0';
1908 user[0] = '\0';
1910 else{
1911 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1913 strncpy(defubuf, user, sizeof(defubuf)-1);
1914 defubuf[sizeof(defubuf)-1] = '\0';
1917 /* TRANSLATORS: user is being asked to type in their password */
1918 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD"));
1920 /* space reserved after prompt */
1921 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1923 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1924 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1925 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) +
1926 utf8_width(pwleadin) + oespace;
1928 if(avail < need && trial > 0){
1929 char *p;
1931 len = strlen(hostname);
1932 if((p = strchr(hostname, '.')) != NULL){
1933 *p = '\0';
1934 need -= (len - strlen(hostname));
1938 if(avail < need){
1939 need -= utf8_width(retry);
1940 retry = "";
1942 if(avail < need){
1944 if(!usethisprompt){
1945 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1946 need--;
1949 rplstr(pwleadin, sizeof(pwleadin), 1, "");
1950 need--;
1952 if(avail < need){
1953 /* get two spaces from hostleadin */
1954 len = utf8_width(hostleadin);
1955 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1956 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1957 hostleadin[sizeof(hostleadin)-1] = '\0';
1958 need -= (len - utf8_width(hostleadin));
1960 /* get rid of port */
1961 if(avail < need && strlen(port) > 0){
1962 need -= strlen(port);
1963 port[0] = '\0';
1966 if(avail < need){
1967 len = utf8_width(pwleadin);
1968 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
1969 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD"));
1970 need -= (len - utf8_width(pwleadin));
1974 if(avail < need){
1975 int reduce_to;
1978 * Reduce space for hostname. Best we can do is 6 chars
1979 * with hos...
1981 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1982 len = strlen(hostname);
1983 strncpy(hostname+reduce_to-3, "...", 4);
1984 need -= (len - strlen(hostname));
1986 if(avail < need && strlen(insecure) > 0){
1987 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1988 need -= 3;
1989 insecure[strlen(insecure)-4] = ')';
1990 insecure[strlen(insecure)-3] = '\0';
1992 else{
1993 need -= utf8_width(insecure);
1994 insecure[0] = '\0';
1998 if(avail < need){
1999 len = utf8_width(logleadin);
2000 strncpy(logleadin, " ", sizeof(logleadin));
2001 logleadin[sizeof(logleadin)-1] = '\0';
2002 need -= (len - utf8_width(logleadin));
2004 if(avail < need){
2005 reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0;
2006 if(reduce_to)
2007 strncpy(defubuf+reduce_to-3, "...", 4);
2008 else
2009 defubuf[0] = '\0';
2016 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s",
2017 retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
2018 prompt[sizeof(prompt)-1] = '\0';
2020 tmp[0] = '\0';
2021 while(1) {
2022 if(ps_global->ttyo)
2023 mm_login_alt_cue(mb);
2025 save_dont_use = ps_global->dont_use_init_cmds;
2026 ps_global->dont_use_init_cmds = 1;
2027 flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
2028 flags |= OE_KEEP_TRAILING_SPACE;
2029 #ifdef _WINDOWS
2030 rc = os_login_dialog(mb, user, NETMAXUSER, tmp, NETMAXPASSWD, 0, 1,
2031 &preserve_password);
2032 #else /* !_WINDOWS */
2033 rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD,
2034 prompt, NULL, help, &flags);
2035 #endif /* !_WINDOWS */
2036 if(rc != 1) *pwd = cpystr(tmp);
2037 ps_global->dont_use_init_cmds = save_dont_use;
2039 if(rc == 3) {
2040 help = help == NO_HELP ? h_oe_passwd : NO_HELP;
2042 else if(rc == 4){
2044 else
2045 break;
2048 if(rc == 1 || !tmp[0]) {
2049 ps_global->user_says_cancel = (rc == 1);
2050 user[0] = '\0';
2051 ps_global->no_newmail_check_from_optionally_enter = 0;
2052 ps_global->in_init_seq = save_in_init;
2053 return;
2056 #ifdef _WINDOWS
2057 nopwpmt:
2058 #endif
2059 /* remember the password for next time */
2060 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
2061 imap_set_passwd(&mm_login_list, *pwd,
2062 altuserforcache ? altuserforcache : user, hostlist,
2063 (mb->sslflag||mb->tlsflag), 0, 0);
2064 #ifdef LOCAL_PASSWD_CACHE
2065 /* if requested, remember it on disk for next session */
2066 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
2067 set_passfile_passwd(ps_global->pinerc, *pwd,
2068 altuserforcache ? altuserforcache : user, hostlist,
2069 (mb->sslflag||mb->tlsflag),
2070 (preserve_password == -1 ? 0
2071 : (preserve_password == 0 ? 2 :1)));
2072 #endif /* LOCAL_PASSWD_CACHE */
2074 ps_global->no_newmail_check_from_optionally_enter = 0;
2078 void
2079 mm_login_alt_cue(NETMBX *mb)
2081 if(ps_global->ttyo){
2082 COLOR_PAIR *lastc;
2084 lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
2085 ps_global->VAR_TITLE_BACK_COLOR,
2086 PSC_REV | PSC_RET);
2088 mark_titlebar_dirty();
2089 PutLine0(0, ps_global->ttyo->screen_cols - 1,
2090 (mb->sslflag||mb->tlsflag) ? "+" : " ");
2092 if(lastc){
2093 (void)pico_set_colorp(lastc, PSC_NONE);
2094 free_color_pair(&lastc);
2097 fflush(stdout);
2102 /*----------------------------------------------------------------------
2103 Receive notification of an error writing to disk
2105 Args: stream -- The stream the error occurred on
2106 errcode -- The system error code (errno)
2107 serious -- Flag indicating error is serious (mail may be lost)
2109 Result: If error is non serious, the stream is marked as having an error
2110 and deletes are disallowed until error clears
2111 If error is serious this goes modal, allowing the user to retry
2112 or get a shell escape to fix the condition. When the condition is
2113 serious it means that mail existing in the mailbox will be lost
2114 if Pine exits without writing, so we try to induce the user to
2115 fix the error, go get someone that can fix the error, or whatever
2116 and don't provide an easy way out.
2117 ----*/
2118 long
2119 mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious)
2121 int i, j;
2122 char *p, *q, *s;
2123 static ESCKEY_S de_opts[] = {
2124 {'r', 'r', "R", "Retry"},
2125 {'f', 'f', "F", "FileBrowser"},
2126 {'s', 's', "S", "ShellPrompt"},
2127 {-1, 0, NULL, NULL}
2129 #define DE_COLS (ps_global->ttyo->screen_cols)
2130 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2132 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2133 #define DE_PMT \
2134 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2135 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2136 #define DE_STR2 \
2137 "The reported error number is %s. The last reported mail error was:"
2138 static char *de_msg[] = {
2139 "Please try to correct the error preventing Alpine from saving your",
2140 "mail folder. For example if the disk is out of space try removing",
2141 "unneeded files. You might also contact your system administrator.",
2143 "Both Alpine's File Browser and an option to enter the system's",
2144 "command prompt are offered to aid in fixing the problem. When",
2145 "you believe the problem is resolved, choose the \"Retry\" option.",
2146 "Be aware that messages may be lost or this folder left in an",
2147 "inaccessible condition if you exit or kill Alpine before the problem",
2148 "is resolved.",
2149 NULL};
2150 static char *de_shell_msg[] = {
2151 "\n\nPlease attempt to correct the error preventing saving of the",
2152 "mail folder. If you do not know how to correct the problem, contact",
2153 "your system administrator. To return to Alpine, type \"exit\".",
2154 NULL};
2156 dprint((0,
2157 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2158 DE_FOLDER(stream), errcode, serious ? "" : "not "));
2159 dprint((0, "***** message: \"%s\"\n\n",
2160 ps_global->last_error ? ps_global->last_error : "?"));
2162 if(!serious) {
2163 sp_set_io_error_on_stream(stream, 1);
2164 return (1) ;
2167 while(1){
2168 /* replace pine's body display with screen full of explanatory text */
2169 ClearLine(2);
2170 PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1)
2171 - strlen(DE_FOLDER(stream)))/2, 0),
2172 DE_STR1, DE_FOLDER(stream));
2173 ClearLine(3);
2174 PutLine1(3, 4, DE_STR2, long2string(errcode));
2176 PutLine0(4, 0, " \"");
2177 removing_leading_white_space(ps_global->last_error);
2178 for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){
2179 for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++)
2180 if(isspace((unsigned char)*q))
2181 s = q;
2183 if(*q && s)
2184 q = s;
2186 while(p < q)
2187 Writechar(*p++, 0);
2189 if(*(p = q)){
2190 ClearLine(++i);
2191 PutLine0(i, 0, " ");
2192 while(*p && isspace((unsigned char)*p))
2193 p++;
2195 else{
2196 Writechar('\"', 0);
2197 CleartoEOLN();
2198 break;
2202 ClearLine(++i);
2203 for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){
2204 ClearLine(i);
2205 PutLine0(i, 0, " ");
2206 Write_to_screen(de_msg[i-j]);
2209 while(i < DE_LINE)
2210 ClearLine(i++);
2212 switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts,
2213 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){
2214 case 'r' : /* Retry! */
2215 ps_global->mangled_screen = 1;
2216 return(0L);
2218 case 'f' : /* File Browser */
2220 char full_filename[MAXPATH+1], filename[MAXPATH+1];
2222 filename[0] = '\0';
2223 build_path(full_filename, ps_global->home_dir, filename,
2224 sizeof(full_filename));
2225 file_lister("DISK ERROR", full_filename, sizeof(full_filename),
2226 filename, sizeof(filename), FALSE, FB_SAVE);
2229 break;
2231 case 's' :
2232 EndInverse();
2233 end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0);
2234 end_tty_driver(ps_global);
2235 for(i = 0; de_shell_msg[i]; i++)
2236 puts(de_shell_msg[i]);
2239 * Don't use our piping mechanism to spawn a subshell here
2240 * since it will the server (thus reentering c-client).
2241 * Bad thing to do.
2243 #ifdef _WINDOWS
2244 #else
2245 system("csh");
2246 #endif
2247 init_tty_driver(ps_global);
2248 init_keyboard(F_ON(F_USE_FK,ps_global));
2249 break;
2252 if(ps_global->redrawer)
2253 (*ps_global->redrawer)();
2258 long
2259 pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host)
2261 long rv = 1L;
2262 char pmt[128];
2264 #ifdef _WINDOWS
2265 mswin_killsplash();
2266 #endif
2268 if(elapsed >= (long)ps_global->tcp_query_timeout){
2269 snprintf(pmt, sizeof(pmt),
2270 _("No reply in %s seconds from server %s. Break connection"),
2271 long2string(elapsed), host);
2272 pmt[sizeof(pmt)-1] = '\0';
2273 if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
2274 ps_global->user_says_cancel = 1;
2275 return(0L);
2279 ps_global->tcptimeout = 0;
2280 return(rv);
2285 * -------------------------------------------------------------
2286 * These are declared in pith/imap.h as mandatory to implement.
2287 * -------------------------------------------------------------
2292 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2294 long
2295 pine_tcptimeout(long int elapsed, long int sincelast, char *host)
2297 long rv = 1L; /* keep trying by default */
2298 unsigned long ch;
2300 ps_global->tcptimeout = 1;
2301 #ifdef DEBUG
2302 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2303 long2string(elapsed), host));
2304 if(debugfile)
2305 fflush(debugfile);
2306 #endif
2308 #ifdef _WINDOWS
2309 mswin_killsplash();
2310 #endif
2312 if(ps_global->noshow_timeout)
2313 return(rv);
2315 if(ps_global->can_interrupt
2316 && ps_global->close_connection_timeout > 0L
2317 && elapsed >= (long)ps_global->tcp_query_timeout
2318 && elapsed >= (long)ps_global->close_connection_timeout){
2319 ps_global->can_interrupt = 0; /* do not return here */
2320 ps_global->read_bail = 0;
2321 ps_global->user_says_cancel = 1;
2322 return 0;
2325 if(!ps_global->ttyo)
2326 return(pine_tcptimeout_noscreen(elapsed, sincelast, host));
2328 suspend_busy_cue();
2331 * Prompt after a minute (since by then things are probably really bad)
2332 * A prompt timeout means "keep trying"...
2334 if(elapsed >= (long)ps_global->tcp_query_timeout){
2335 int clear_inverse;
2337 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2338 if((clear_inverse = !InverseState()) != 0)
2339 StartInverse();
2341 Writechar(BELL, 0);
2343 PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0,
2344 _("No reply in %s seconds from server %s. Break connection?"),
2345 long2string(elapsed), host);
2346 CleartoEOLN();
2347 fflush(stdout);
2348 flush_input();
2349 ch = read_char(7);
2350 if(ps_global->read_bail || ch == 'y' || ch == 'Y'){
2351 ps_global->read_bail = 0;
2352 ps_global->user_says_cancel = 1;
2353 rv = 0L;
2356 if(clear_inverse)
2357 EndInverse();
2359 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2362 if(rv == 1L){ /* just warn 'em something's up */
2363 q_status_message2(SM_ORDER, 0, 0,
2364 _("No reply in %s seconds from server %s. Still Waiting..."),
2365 long2string(elapsed), host);
2366 flush_status_messages(0); /* make sure it's seen */
2369 mark_status_dirty(); /* make sure it gets cleared */
2371 resume_busy_cue((rv == 1) ? 3 : 0);
2372 ps_global->tcptimeout = 0;
2374 return(rv);
2377 QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota)
2379 QUOTALIST *cquota = NULL;
2381 if(pquota){
2382 cquota = mail_newquotalist();
2383 if (pquota->name && *pquota->name)
2384 cquota->name = cpystr(pquota->name);
2385 cquota->usage = pquota->usage;
2386 cquota->limit = pquota->limit;
2387 if (pquota->next)
2388 cquota->next = pine_quotalist_copy(pquota->next);
2390 return cquota;
2394 /* c-client callback to handle quota */
2396 void
2397 pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2399 ps_global->quota = pine_quotalist_copy (pquota);
2403 * C-client callback to handle SSL/TLS certificate validation failures
2405 * Returning 0 means error becomes fatal
2406 * Non-zero means certificate problem is ignored and SSL session is
2407 * established
2409 * We remember the answer and won't re-ask for subsequent open attempts to
2410 * the same hostname.
2412 long
2413 pine_sslcertquery(char *reason, char *host, char *cert)
2415 char tmp[500];
2416 char *unknown = "<unknown>";
2417 long rv = 0L;
2418 STRLIST_S hostlist;
2419 int ok_novalidate = 0, warned = 0;
2421 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2422 host ? host : "?", reason ? reason : "?",
2423 cert ? cert : "?"));
2425 hostlist.name = host ? host : "";
2426 hostlist.next = NULL;
2429 * See if we've been asked about this host before.
2431 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2432 /* we were asked before, did we say Yes? */
2433 if(ok_novalidate)
2434 rv++;
2436 if(rv){
2437 dprint((5,
2438 "sslcertificatequery: approved automatically\n"));
2439 return(rv);
2442 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2445 if(ps_global->ttyo){
2446 SCROLL_S sargs;
2447 STORE_S *in_store, *out_store;
2448 gf_io_t pc, gc;
2449 HANDLE_S *handles = NULL;
2450 int the_answer = 'n';
2452 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
2453 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
2454 goto try_wantto;
2456 so_puts(in_store, "<HTML><P>");
2457 so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server"));
2459 so_puts(in_store, "<P><CENTER>");
2460 so_puts(in_store, host ? host : unknown);
2461 so_puts(in_store, "</CENTER>");
2463 so_puts(in_store, "<P>");
2464 so_puts(in_store, _("The reason for the failure was"));
2466 /* squirrel away details */
2467 if(details_host)
2468 fs_give((void **)&details_host);
2469 if(details_reason)
2470 fs_give((void **)&details_reason);
2471 if(details_cert)
2472 fs_give((void **)&details_cert);
2474 details_host = cpystr(host ? host : unknown);
2475 details_reason = cpystr(reason ? reason : unknown);
2476 details_cert = cpystr(cert ? cert : unknown);
2478 so_puts(in_store, "<P><CENTER>");
2479 snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2480 reason ? reason : unknown);
2481 tmp[sizeof(tmp)-1] = '\0';
2483 so_puts(in_store, tmp);
2484 so_puts(in_store, "</CENTER>");
2486 so_puts(in_store, "<P>");
2487 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."));
2489 so_puts(in_store, "<P>");
2490 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"));
2492 so_puts(in_store, "<P><CENTER>");
2493 so_puts(in_store, "/novalidate-cert");
2494 so_puts(in_store, "</CENTER>");
2496 so_puts(in_store, "<P>");
2497 so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2499 so_puts(in_store, "<P><CENTER>");
2500 so_puts(in_store, host ? host : unknown);
2501 so_puts(in_store, "</CENTER>");
2503 so_puts(in_store, "<P>");
2504 so_puts(in_store, _("in your configuration, replace those characters with"));
2506 so_puts(in_store, "<P><CENTER>");
2507 so_puts(in_store, host ? host : unknown);
2508 so_puts(in_store, "/novalidate-cert");
2509 so_puts(in_store, "</CENTER>");
2511 so_puts(in_store, "<P>");
2512 so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2514 so_seek(in_store, 0L, 0);
2515 init_handles(&handles);
2516 gf_filter_init();
2517 gf_link_filter(gf_html2plain,
2518 gf_html2plain_opt(NULL,
2519 ps_global->ttyo->screen_cols, non_messageview_margin(),
2520 &handles, NULL, GFHP_LOCAL_HANDLES));
2521 gf_set_so_readc(&gc, in_store);
2522 gf_set_so_writec(&pc, out_store);
2523 gf_pipe(gc, pc);
2524 gf_clear_so_writec(out_store);
2525 gf_clear_so_readc(in_store);
2527 memset(&sargs, 0, sizeof(SCROLL_S));
2528 sargs.text.handles = handles;
2529 sargs.text.text = so_text(out_store);
2530 sargs.text.src = CharStar;
2531 sargs.text.desc = _("help text");
2532 sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2533 sargs.proc.tool = answer_cert_failure;
2534 sargs.proc.data.p = (void *)&the_answer;
2535 sargs.keys.menu = &ans_certquery_keymenu;
2536 /* don't want to re-enter c-client */
2537 sargs.quell_newmail = 1;
2538 setbitmap(sargs.keys.bitmap);
2539 sargs.help.text = h_tls_validation_failure;
2540 sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
2542 scrolltool(&sargs);
2544 if(the_answer == 'y')
2545 rv++;
2546 else if(the_answer == 'n')
2547 ps_global->user_says_cancel = 1;
2549 ps_global->mangled_screen = 1;
2550 ps_global->painted_body_on_startup = 0;
2551 ps_global->painted_footer_on_startup = 0;
2552 so_give(&in_store);
2553 so_give(&out_store);
2554 free_handles(&handles);
2555 if(details_host)
2556 fs_give((void **)&details_host);
2557 if(details_reason)
2558 fs_give((void **)&details_reason);
2559 if(details_cert)
2560 fs_give((void **)&details_cert);
2562 else{
2564 * If screen hasn't been initialized yet, use want_to.
2566 try_wantto:
2567 memset((void *)tmp, 0, sizeof(tmp));
2568 strncpy(tmp,
2569 reason ? reason : _("SSL/TLS certificate validation failure"),
2570 sizeof(tmp));
2571 tmp[sizeof(tmp)-1] = '\0';
2572 strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
2574 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
2575 rv++;
2578 if(rv == 0)
2579 q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
2580 host ? host : unknown);
2582 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
2584 dprint((5, "sslcertificatequery: %s\n",
2585 rv ? "approved" : "rejected"));
2587 return(rv);
2591 char *
2592 pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
2594 char buf[MAILTMPLEN];
2596 if((can_access(mulname, ACCESS_EXISTS) == 0)
2597 || !(can_access(name, ACCESS_EXISTS) == 0))
2598 return(mulname);
2600 snprintf(buf, sizeof(buf),
2601 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2602 last_cmpnt(name),
2603 strlen(last_cmpnt(name)) > 15 ? "..." : "");
2604 buf[sizeof(buf)-1] = '\0';
2605 if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
2606 rename_file(name, mulname);
2607 return(mulname);
2612 url_local_certdetails(char *url)
2614 if(!struncmp(url, "x-alpine-cert:", 14)){
2615 STORE_S *store;
2616 SCROLL_S sargs;
2617 char *folded;
2619 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
2620 q_status_message(SM_ORDER | SM_DING, 7, 10,
2621 _("Error allocating space for details."));
2622 return(0);
2625 so_puts(store, _("Host given by user:\n\n "));
2626 so_puts(store, details_host);
2627 so_puts(store, _("\n\nReason for failure:\n\n "));
2628 so_puts(store, details_reason);
2629 so_puts(store, _("\n\nCertificate being verified:\n\n"));
2630 folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
2631 so_puts(store, folded);
2632 fs_give((void **)&folded);
2633 so_puts(store, "\n");
2635 memset(&sargs, 0, sizeof(SCROLL_S));
2636 sargs.text.text = so_text(store);
2637 sargs.text.src = CharStar;
2638 sargs.text.desc = _("Details");
2639 sargs.bar.title = _("CERT VALIDATION DETAILS");
2640 sargs.help.text = NO_HELP;
2641 sargs.help.title = NULL;
2642 sargs.quell_newmail = 1;
2643 sargs.help.text = h_tls_failure_details;
2644 sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
2646 scrolltool(&sargs);
2648 so_give(&store); /* free resources associated with store */
2649 ps_global->mangled_screen = 1;
2650 return(1);
2653 return(0);
2658 * C-client callback to handle SSL/TLS certificate validation failures
2660 void
2661 pine_sslfailure(char *host, char *reason, long unsigned int flags)
2663 SCROLL_S sargs;
2664 STORE_S *store;
2665 int the_answer = 'n', indent, len, cols;
2666 char buf[500], buf2[500];
2667 char *folded;
2668 char *hst = host ? host : "<unknown>";
2669 char *rsn = reason ? reason : "<unknown>";
2670 char *notls = "/notls";
2671 STRLIST_S hostlist;
2672 int ok_novalidate = 0, warned = 0;
2675 dprint((1, "sslfailure: host=%s reason=%s\n",
2676 hst ? hst : "?",
2677 rsn ? rsn : "?"));
2679 if(flags & NET_SILENT)
2680 return;
2682 hostlist.name = host ? host : "";
2683 hostlist.next = NULL;
2686 * See if we've been told about this host before.
2688 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2689 /* we were told already */
2690 if(warned){
2691 snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn);
2692 buf[sizeof(buf)-1] = '\0';
2693 mm_log(buf, ERROR);
2694 return;
2698 cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2699 cols--;
2701 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS)))
2702 return;
2704 strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf));
2705 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2706 so_puts(store, folded);
2707 fs_give((void **)&folded);
2708 so_puts(store, "\n");
2710 if((len=strlen(hst)) <= cols){
2711 if((indent=((cols-len)/2)) > 0)
2712 so_puts(store, repeat_char(indent, SPACE));
2714 so_puts(store, hst);
2715 so_puts(store, "\n");
2717 else{
2718 strncpy(buf, hst, sizeof(buf));
2719 buf[sizeof(buf)-1] = '\0';
2720 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2721 so_puts(store, folded);
2722 fs_give((void **)&folded);
2725 so_puts(store, "\n");
2727 strncpy(buf, _("The reason for the failure was"), sizeof(buf));
2728 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2729 so_puts(store, folded);
2730 fs_give((void **)&folded);
2731 so_puts(store, "\n");
2733 if((len=strlen(rsn)) <= cols){
2734 if((indent=((cols-len)/2)) > 0)
2735 so_puts(store, repeat_char(indent, SPACE));
2737 so_puts(store, rsn);
2738 so_puts(store, "\n");
2740 else{
2741 strncpy(buf, rsn, sizeof(buf));
2742 buf[sizeof(buf)-1] = '\0';
2743 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2744 so_puts(store, folded);
2745 fs_give((void **)&folded);
2748 so_puts(store, "\n");
2750 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));
2751 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2752 so_puts(store, folded);
2753 fs_give((void **)&folded);
2754 so_puts(store, "\n");
2756 if((len=strlen(notls)) <= cols){
2757 if((indent=((cols-len)/2)) > 0)
2758 so_puts(store, repeat_char(indent, SPACE));
2760 so_puts(store, notls);
2761 so_puts(store, "\n");
2763 else{
2764 strncpy(buf, notls, sizeof(buf));
2765 buf[sizeof(buf)-1] = '\0';
2766 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2767 so_puts(store, folded);
2768 fs_give((void **)&folded);
2771 so_puts(store, "\n");
2773 strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2774 sizeof(buf));
2775 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2776 so_puts(store, folded);
2777 fs_give((void **)&folded);
2778 so_puts(store, "\n");
2780 if((len=strlen(hst)) <= cols){
2781 if((indent=((cols-len)/2)) > 0)
2782 so_puts(store, repeat_char(indent, SPACE));
2784 so_puts(store, hst);
2785 so_puts(store, "\n");
2787 else{
2788 strncpy(buf, hst, sizeof(buf));
2789 buf[sizeof(buf)-1] = '\0';
2790 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2791 so_puts(store, folded);
2792 fs_give((void **)&folded);
2795 so_puts(store, "\n");
2797 strncpy(buf, _("in your configuration, replace those characters with"),
2798 sizeof(buf));
2799 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2800 so_puts(store, folded);
2801 fs_give((void **)&folded);
2802 so_puts(store, "\n");
2804 snprintf(buf2, sizeof(buf2), "%s%s", hst, notls);
2805 buf2[sizeof(buf2)-1] = '\0';
2806 if((len=strlen(buf2)) <= cols){
2807 if((indent=((cols-len)/2)) > 0)
2808 so_puts(store, repeat_char(indent, SPACE));
2810 so_puts(store, buf2);
2811 so_puts(store, "\n");
2813 else{
2814 strncpy(buf, buf2, sizeof(buf));
2815 buf[sizeof(buf)-1] = '\0';
2816 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2817 so_puts(store, folded);
2818 fs_give((void **)&folded);
2821 so_puts(store, "\n");
2823 if(ps_global->ttyo){
2824 strncpy(buf, _("Type RETURN to continue."), sizeof(buf));
2825 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2826 so_puts(store, folded);
2827 fs_give((void **)&folded);
2830 memset(&sargs, 0, sizeof(SCROLL_S));
2831 sargs.text.text = so_text(store);
2832 sargs.text.src = CharStar;
2833 sargs.text.desc = _("help text");
2834 sargs.bar.title = _("SSL/TLS FAILURE");
2835 sargs.proc.tool = answer_cert_failure;
2836 sargs.proc.data.p = (void *)&the_answer;
2837 sargs.keys.menu = &ans_certfail_keymenu;
2838 setbitmap(sargs.keys.bitmap);
2839 /* don't want to re-enter c-client */
2840 sargs.quell_newmail = 1;
2841 sargs.help.text = h_tls_failure;
2842 sargs.help.title = _("HELP FOR TLS/SSL FAILURE");
2844 if(ps_global->ttyo)
2845 scrolltool(&sargs);
2846 else{
2847 char **q, **qp;
2848 char *p;
2849 unsigned char c;
2850 int cnt = 0;
2853 * The screen isn't initialized yet, which should mean that this
2854 * is the result of a -p argument. Display_args_err knows how to deal
2855 * with the uninitialized screen, so we mess with the data to get it
2856 * in shape for display_args_err. This is pretty hacky.
2859 so_seek(store, 0L, 0); /* rewind */
2860 /* count the lines */
2861 while(so_readc(&c, store))
2862 if(c == '\n')
2863 cnt++;
2865 qp = q = (char **)fs_get((cnt+1) * sizeof(char *));
2866 memset(q, 0, (cnt+1) * sizeof(char *));
2868 so_seek(store, 0L, 0); /* rewind */
2869 p = buf;
2870 while(so_readc(&c, store)){
2871 if(c == '\n'){
2872 *p = '\0';
2873 *qp++ = cpystr(buf);
2874 p = buf;
2876 else
2877 *p++ = c;
2880 display_args_err(NULL, q, 0);
2881 free_list_array(&q);
2884 ps_global->mangled_screen = 1;
2885 ps_global->painted_body_on_startup = 0;
2886 ps_global->painted_footer_on_startup = 0;
2887 so_give(&store);
2889 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1);
2894 answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2896 int rv = 1;
2898 ps_global->next_screen = SCREEN_FUN_NULL;
2900 switch(cmd){
2901 case MC_YES :
2902 *(int *)(sparms->proc.data.p) = 'y';
2903 break;
2905 case MC_NO :
2906 *(int *)(sparms->proc.data.p) = 'n';
2907 break;
2909 default:
2910 alpine_panic("Unexpected command in answer_cert_failure");
2911 break;
2914 return(rv);
2919 oauth2_auth_answer(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2921 int rv = 1, rc;
2922 AUTH_CODE_S user;
2923 int q_line, flags;
2924 /* TRANSLATORS: user needs to input an access code from the server */
2925 char *accesscodelabel = _("Copy and Paste Access Code");
2926 char token[MAILTMPLEN], prompt[MAILTMPLEN];
2928 ps_global->next_screen = SCREEN_FUN_NULL;
2930 token[0] = '\0';
2931 switch(cmd){
2932 case MC_YES :
2933 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
2934 flags = OE_APPEND_CURRENT;
2935 sprintf(prompt, "%s: ", accesscodelabel);
2936 do {
2937 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
2938 prompt, NULL, NO_HELP, &flags);
2939 } while (rc != 0 && rc != 1);
2940 user.code = rc == 0 ? cpystr(token) : NULL;
2941 user.answer = 'e';
2942 rv = rc == 1 ? 0 : 1;
2943 break;
2945 case MC_NO :
2946 user.code = NULL;
2947 user.answer = 'e';
2948 break;
2950 default:
2951 alpine_panic("Unexpected command in oauth2_auth_answer");
2952 break;
2954 *(AUTH_CODE_S *) sparms->proc.data.p = user;
2955 return(rv);
2959 /*----------------------------------------------------------------------
2960 This can be used to prevent the flickering of the check_cue char
2961 caused by numerous (5000+) fetches by c-client. Right now, the only
2962 practical use found is newsgroup subsciption.
2964 check_cue_display will check if this global is set, and won't clear
2965 the check_cue_char if set.
2966 ----*/
2967 void
2968 set_read_predicted(int i)
2970 ps_global->read_predicted = i==1;
2971 #ifdef _WINDOWS
2972 if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global))
2973 check_cue_display(" ");
2974 #endif
2978 /*----------------------------------------------------------------------
2979 Exported method to retrieve logged in user name associated with stream
2981 Args: host -- host to find associated login name with.
2983 Result:
2984 ----*/
2985 void *
2986 pine_block_notify(int reason, void *data)
2988 switch(reason){
2989 case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */
2990 break;
2992 case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */
2993 break;
2995 case BLOCK_TCPWRITE: /* blocked on TCP write */
2996 case BLOCK_FILELOCK: /* blocked on file locking */
2997 #ifdef _WINDOWS
2998 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
2999 check_cue_display(">");
3001 mswin_setcursor(MSWIN_CURSOR_BUSY);
3002 #endif
3003 break;
3005 case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */
3006 case BLOCK_TCPOPEN: /* blocked on TCP open */
3007 case BLOCK_TCPREAD: /* blocked on TCP read */
3008 case BLOCK_TCPCLOSE: /* blocked on TCP close */
3009 #ifdef _WINDOWS
3010 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3011 check_cue_display("<");
3013 mswin_setcursor(MSWIN_CURSOR_BUSY);
3014 #endif
3015 break;
3017 default :
3018 case BLOCK_NONE: /* not blocked */
3019 #ifdef _WINDOWS
3020 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3021 check_cue_display(" ");
3022 #endif
3023 break;
3027 return(NULL);
3031 void
3032 mm_expunged_current(long unsigned int rawno)
3034 /* expunged something we're viewing? */
3035 if(!ps_global->expunge_in_progress
3036 && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno))
3037 && (ps_global->prev_screen == mail_view_screen
3038 || ps_global->prev_screen == attachment_screen))){
3039 ps_global->next_screen = mail_index_screen;
3040 q_status_message(SM_ORDER | SM_DING , 3, 3,
3041 "Message you were viewing is gone!");
3046 #ifdef PASSFILE
3049 * Specific functions to support caching username/passwd/host
3050 * triples on disk for use from one session to the next...
3053 #define FIRSTCH 0x20
3054 #define LASTCH 0x7e
3055 #define TABSZ (LASTCH - FIRSTCH + 1)
3057 static int xlate_key;
3061 * xlate_in() - xlate_in the given character
3063 char
3064 xlate_in(int c)
3066 register int eti;
3068 eti = xlate_key;
3069 if((c >= FIRSTCH) && (c <= LASTCH)){
3070 eti += (c - FIRSTCH);
3071 eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0;
3072 return((xlate_key = eti) + FIRSTCH);
3074 else
3075 return(c);
3080 * xlate_out() - xlate_out the given character
3082 char
3083 xlate_out(char c)
3085 register int dti;
3086 register int xch;
3088 if((c >= FIRSTCH) && (c <= LASTCH)){
3089 xch = c - (dti = xlate_key);
3090 xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0;
3091 dti = (xch - FIRSTCH) + dti;
3092 dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0;
3093 xlate_key = dti;
3094 return(xch);
3096 else
3097 return(c);
3099 #endif /* PASSFILE */
3102 #ifdef LOCAL_PASSWD_CACHE
3105 int
3106 line_get(char *tmp, size_t len, char **textp)
3108 char *s;
3110 tmp[0] = '\0';
3111 if (*textp == NULL
3112 || (s = strchr(*textp, '\n')) == NULL
3113 || (s - *textp) > len - 1)
3114 return 0;
3116 *s = '\0';
3117 if(*(s-1) == '\r')
3118 *(s-1) = '\0';
3120 snprintf(tmp, len, "%s\n", *textp);
3121 tmp[len-1] = '\0';
3122 *textp = s+1;
3124 return 1;
3127 * For UNIX:
3128 * Passfile lines are
3130 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3132 * In pine4.40 and before there was no orig_hostname, and there still isn't
3133 * if it is the same as hostname.
3135 * else for WINDOWS:
3136 * Use Windows credentials. The TargetName of the credential is
3137 * UWash_Alpine_<hostname:port>\tuser\taltflag
3138 * and the blob consists of
3139 * passwd\torighost (if different from host)
3141 * We don't use anything fancy we just copy out all the credentials which
3142 * begin with TNAME and put them into our cache, so we don't lookup based
3143 * on the TargetName or anything like that. That was so we could re-use
3144 * the existing code and so that orighost data could be easily used.
3147 read_passfile(pinerc, l)
3148 char *pinerc;
3149 MMLOGIN_S **l;
3151 #ifdef WINCRED
3152 # if (WINCRED > 0)
3153 LPCTSTR lfilter = TEXT(TNAMESTAR);
3154 DWORD count, k;
3155 PCREDENTIAL *pcred;
3156 char *tmp, *blob, *target = NULL;
3157 char *host, *user, *sflags, *passwd, *orighost;
3158 char *ui[5];
3159 int i, j;
3161 if(using_passfile == 0)
3162 return(using_passfile);
3164 if(!g_CredInited){
3165 if(init_wincred_funcs() != 1){
3166 using_passfile = 0;
3167 return(using_passfile);
3171 dprint((9, "read_passfile\n"));
3173 using_passfile = 1;
3175 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
3176 if(pcred){
3177 for(k = 0; k < count; k++){
3179 host = user = sflags = passwd = orighost = NULL;
3180 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3182 target = lptstr_to_utf8(pcred[k]->TargetName);
3183 tmp = srchstr(target, TNAME);
3185 if(tmp){
3186 tmp += strlen(TNAME);
3187 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3188 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3189 ; /* find end of data */
3191 if(tmp[i])
3192 tmp[i++] = '\0'; /* tie off data */
3195 host = ui[0];
3196 user = ui[1];
3197 sflags = ui[2];
3200 blob = (char *) pcred[k]->CredentialBlob;
3201 if(blob){
3202 for(i = 0, j = 3; blob[i] && j < 5; j++){
3203 for(ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++)
3204 ; /* find end of data */
3206 if(blob[i])
3207 blob[i++] = '\0'; /* tie off data */
3210 passwd = ui[3];
3211 orighost = ui[4];
3214 if(passwd && host && user){ /* valid field? */
3215 STRLIST_S hostlist[2];
3216 int flags;
3218 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3219 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3220 hostlist[0].name = host;
3221 if(orighost){
3222 hostlist[0].next = &hostlist[1];
3223 hostlist[1].name = orighost;
3224 hostlist[1].next = NULL;
3226 else{
3227 hostlist[0].next = NULL;
3230 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3233 if(target)
3234 fs_give((void **) &target);
3237 g_CredFree((PVOID) pcred);
3241 return(1);
3243 # else /* old windows */
3244 using_passfile = 0;
3245 return(0);
3246 # endif
3248 #elif APPLEKEYCHAIN
3250 char target[MAILTMPLEN];
3251 char *tmp, *host, *user, *sflags, *passwd, *orighost;
3252 char *ui[5];
3253 int i, j, k, rc;
3254 SecKeychainAttributeList attrList;
3255 SecKeychainSearchRef searchRef = NULL;
3256 SecKeychainAttribute attrs[] = {
3257 { kSecAccountItemAttr, strlen(TNAME), TNAME }
3260 if(using_passfile == 0)
3261 return(using_passfile);
3263 dprint((9, "read_passfile\n"));
3266 /* search for only our items in the keychain */
3267 attrList.count = 1;
3268 attrList.attr = attrs;
3270 using_passfile = 1;
3271 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
3272 kSecGenericPasswordItemClass,
3273 &attrList,
3274 &searchRef))){
3275 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3276 if(searchRef){
3277 SecKeychainItemRef itemRef = NULL;
3278 SecKeychainAttributeInfo info;
3279 SecKeychainAttributeList *attrList = NULL;
3280 UInt32 blength = 0;
3281 char *blob = NULL;
3282 char *blobcopy = NULL; /* NULL terminated copy */
3284 UInt32 tags[] = {kSecAccountItemAttr,
3285 kSecServiceItemAttr};
3286 UInt32 formats[] = {0,0};
3288 dprint((10, "read_passfile: searchRef not NULL\n"));
3289 info.count = 2;
3290 info.tag = tags;
3291 info.format = formats;
3294 * Go through each item we found and put it
3295 * into our list.
3297 while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){
3298 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3299 rc = SecKeychainItemCopyAttributesAndData(itemRef,
3300 &info, NULL,
3301 &attrList,
3302 &blength,
3303 (void **) &blob);
3304 if(rc == 0 && attrList){
3305 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count));
3307 blobcopy = (char *) fs_get((blength + 1) * sizeof(char));
3308 strncpy(blobcopy, (char *) blob, blength);
3309 blobcopy[blength] = '\0';
3312 * I'm not real clear on how this works. It seems to be
3313 * necessary to combine the attributes from two passes
3314 * (attrList->count == 2) in order to get the full set
3315 * of attributes we inserted into the keychain in the
3316 * first place. So, we reset host...orighost outside of
3317 * the following for loop, not inside.
3319 host = user = sflags = passwd = orighost = NULL;
3320 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3322 for(k = 0; k < attrList->count; k++){
3324 if(attrList->attr[k].length){
3325 strncpy(target,
3326 (char *) attrList->attr[k].data,
3327 MIN(attrList->attr[k].length,sizeof(target)));
3328 target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0';
3331 tmp = target;
3332 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3333 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3334 ; /* find end of data */
3336 if(tmp[i])
3337 tmp[i++] = '\0'; /* tie off data */
3340 if(ui[0])
3341 host = ui[0];
3343 if(ui[1])
3344 user = ui[1];
3346 if(ui[2])
3347 sflags = ui[2];
3349 for(i = 0, j = 3; blobcopy[i] && j < 5; j++){
3350 for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++)
3351 ; /* find end of data */
3353 if(blobcopy[i])
3354 blobcopy[i++] = '\0'; /* tie off data */
3357 if(ui[3])
3358 passwd = ui[3];
3360 if(ui[4])
3361 orighost = ui[4];
3363 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:""));
3366 if(passwd && host && user){ /* valid field? */
3367 STRLIST_S hostlist[2];
3368 int flags;
3370 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3371 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3372 hostlist[0].name = host;
3373 if(orighost){
3374 hostlist[0].next = &hostlist[1];
3375 hostlist[1].name = orighost;
3376 hostlist[1].next = NULL;
3378 else{
3379 hostlist[0].next = NULL;
3382 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3385 if(blobcopy)
3386 fs_give((void **) & blobcopy);
3388 SecKeychainItemFreeAttributesAndData(attrList, (void *) blob);
3390 else{
3391 using_passfile = 0;
3392 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc));
3395 CFRelease(itemRef);
3396 itemRef = NULL;
3399 CFRelease(searchRef);
3401 else{
3402 using_passfile = 0;
3403 dprint((10, "read_passfile: searchRef NULL\n"));
3406 else{
3407 using_passfile = 0;
3408 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc));
3411 return(using_passfile);
3413 #else /* PASSFILE */
3415 char tmp[MAILTMPLEN], *ui[5];
3416 int i, j, n, rv = 0;
3417 size_t len = 0;
3418 char *tmptext = NULL;
3419 struct stat sbuf;
3420 #ifdef SMIME
3421 char tmp2[MAILTMPLEN];
3422 char *text = NULL, *text2 = NULL;
3423 int encrypted = 0;
3424 #endif /* SMIME */
3425 FILE *fp;
3427 if(using_passfile == 0)
3428 return(using_passfile);
3430 dprint((9, "read_passfile\n"));
3432 /* if there's no password to read, bag it!! */
3433 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){
3434 using_passfile = 0;
3435 return(using_passfile);
3438 #ifndef SMIME
3439 if(our_stat(tmp, &sbuf) == 0)
3440 len = sbuf.st_size;
3441 fp = our_fopen(tmp, "rb"); /* reopen to read data */
3442 #else
3443 /* the next call initializes the key/certificate pair used to
3444 * encrypt and decrypt a password file. The details of how this is
3445 * done is in the file pith/smime.c. During this setup we might call
3446 * smime_init(), but no matter what happens we must call smime_deinit()
3447 * there. The reason why this is so is because we can not assume that
3448 * the .pinerc file has been read by this time, so this code might not
3449 * know about the ps_global->smime structure or any of its components,
3450 * and it shouldn't because it only needs ps_global->pwdcert, so
3451 * do not init smime here, because the .pinerc might not have been
3452 * read and we do not really know where the keys and certificates really
3453 * are.
3454 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3455 * it is called for the first time and there are certificates at all,
3456 * or when it is called after the first time and the user refuses to
3457 * create a self-signed certificate. In this situation we will just
3458 * let the user live in an insecure world, but no more passwords will
3459 * be saved in the password file, and only those found there will be used.
3461 tmp2[0] = '\0';
3462 fgets(tmp2, sizeof(tmp2), fp);
3463 fclose(fp);
3464 if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
3465 /* there is an already existing password file, that is not encrypted
3466 * and there is no key to encrypt it yet, go again through setup_pwdcert
3467 * and encrypt it now.
3469 if(tmp2[0]){ /* not empty, UNencrypted password file */
3470 if(ps_global->pwdcert == NULL)
3471 rv = setup_pwdcert(&ps_global->pwdcert);
3472 if((rv == 0 || rv == -5) && ps_global->pwdcert == NULL)
3473 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3474 if(ps_global->pwdcert == NULL){
3475 q_status_message(SM_ORDER, 3, 3,
3476 " Failed to create private key. Using UNencrypted Password file. ");
3477 save_password = 0;
3479 else{
3480 if(rv == 1){
3481 q_status_message(SM_ORDER, 3, 3,
3482 " Failed to unlock private key. Using UNencrypted Password file. ");
3483 save_password = 0; /* do not save more passwords */
3486 if(ps_global->pwdcert != NULL
3487 && encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
3488 encrypted++;
3491 else {
3492 if(ps_global->pwdcert == NULL)
3493 rv = setup_pwdcert(&ps_global->pwdcert);
3494 encrypted++;
3498 * if password file is encrypted we attempt to decrypt. We ask the
3499 * user for the password to unlock the password file. If the user
3500 * enters the password and it unlocks the file, use it and keep saving
3501 * passwords in it. If the user enters the wrong passwords and does
3502 * not unlock it, we will not see that here, but in decrypt_file, so
3503 * the only other possibility is that the user cancels. In that case
3504 * we will see i == -1. In that case, we will let the user attempt
3505 * manual login to the server they want to login, but passwords will
3506 * not be saved so that the password file will not be saved
3507 * unencrypted and rewritten again.
3509 if(encrypted){
3510 text = text2 = decrypt_file((char *)tmp, &i, (PERSONAL_CERT *)ps_global->pwdcert);
3511 len = text2 ? strlen(text2) : 0;
3512 switch(i){
3513 case -2: using_passfile = 0;
3514 break;
3516 case 1 : save_password = 1;
3517 using_passfile = 1;
3518 break;
3520 case -1: save_password = 0;
3521 using_passfile = 1;
3522 break;
3524 default: break;
3527 #endif /* SMIME */
3529 if(using_passfile == 0){
3530 #ifdef SMIME
3531 if(text) fs_give((void **)&text);
3532 #endif /* SMIME */
3533 return using_passfile;
3536 if(len > 0){
3537 tmptext = fs_get(len + 1);
3538 #ifdef SMIME
3539 for(n = 0; encrypted ? line_get(tmptext, len + 1, &text2)
3540 : (fgets(tmptext, len+1, fp) != NULL); n++){
3541 #else /* SMIME */
3542 for(n = 0; fgets(tmptext, len+1, fp); n++){
3543 #endif /* SMIME */
3544 /*** do any necessary DEcryption here ***/
3545 xlate_key = n;
3546 for(i = 0; tmptext[i]; i++)
3547 tmptext[i] = xlate_out(tmptext[i]);
3549 if(i && tmptext[i-1] == '\n')
3550 tmptext[i-1] = '\0'; /* blast '\n' */
3552 dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
3553 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3554 for(i = 0, j = 0; tmptext[i] && j < 5; j++){
3555 for(ui[j] = &tmptext[i]; tmptext[i] && tmptext[i] != '\t'; i++)
3556 ; /* find end of data */
3558 if(tmptext[i])
3559 tmptext[i++] = '\0'; /* tie off data */
3562 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3563 if(ui[0] && ui[1] && ui[2]){ /* valid field? */
3564 STRLIST_S hostlist[2];
3565 char *s = ui[3] ? strchr(ui[3], PWDAUTHSEP) : NULL;
3566 int flags = ui[3] ? atoi(s ? ++s : ui[3]) : 0;
3568 hostlist[0].name = ui[2];
3569 if(ui[4]){
3570 hostlist[0].next = &hostlist[1];
3571 hostlist[1].name = ui[4];
3572 hostlist[1].next = NULL;
3574 else{
3575 hostlist[0].next = NULL;
3578 imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0);
3583 if (tmptext) fs_give((void **) &tmptext);
3584 #ifdef SMIME
3585 if (text) fs_give((void **)&text);
3586 #else /* SMIME */
3587 fclose(fp);
3588 #endif /* SMIME */
3589 return(1);
3590 #endif /* PASSFILE */
3595 void
3596 write_passfile(pinerc, l)
3597 char *pinerc;
3598 MMLOGIN_S *l;
3600 char *authend, *authtype;
3601 #ifdef WINCRED
3602 # if (WINCRED > 0)
3603 char target[4*MAILTMPLEN];
3604 char blob[4*MAILTMPLEN];
3605 CREDENTIAL cred;
3606 LPTSTR ltarget = 0;
3608 if(using_passfile == 0)
3609 return;
3611 dprint((9, "write_passfile\n"));
3613 for(; l; l = l->next){
3614 authtype = l->passwd;
3615 authend = strchr(l->passwd, PWDAUTHSEP);
3616 if(authend != NULL){
3617 *authend = '\0';
3618 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3619 *authend = PWDAUTHSEP;
3621 else
3622 sprintf(blob, "%d", l->altflag);
3624 snprintf(target, sizeof(target), "%s%s\t%s\t%s",
3625 TNAME,
3626 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3627 l->user ? l->user : "",
3628 blob);
3629 ltarget = utf8_to_lptstr((LPSTR) target);
3631 if(ltarget){
3632 snprintf(blob, sizeof(blob), "%s%s%s",
3633 l->passwd ? l->passwd : "",
3634 (l->hosts && l->hosts->next && l->hosts->next->name)
3635 ? "\t" : "",
3636 (l->hosts && l->hosts->next && l->hosts->next->name)
3637 ? l->hosts->next->name : "");
3638 memset((void *) &cred, 0, sizeof(cred));
3639 cred.Flags = 0;
3640 cred.Type = CRED_TYPE_GENERIC;
3641 cred.TargetName = ltarget;
3642 cred.CredentialBlobSize = strlen(blob)+1;
3643 cred.CredentialBlob = (LPBYTE) &blob;
3644 cred.Persist = CRED_PERSIST_ENTERPRISE;
3645 g_CredWriteW(&cred, 0);
3647 fs_give((void **) &ltarget);
3650 #endif /* WINCRED > 0 */
3652 #elif APPLEKEYCHAIN
3653 int rc;
3654 char target[4*MAILTMPLEN];
3655 char blob[4*MAILTMPLEN];
3656 SecKeychainItemRef itemRef = NULL;
3658 if(using_passfile == 0)
3659 return;
3661 dprint((9, "write_passfile\n"));
3663 for(; l; l = l->next){
3664 authtype = l->passwd;
3665 authend = strchr(l->passwd, PWDAUTHSEP);
3666 if(authend != NULL){
3667 *authend = '\0';
3668 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3669 *authend = PWDAUTHSEP;
3671 else
3672 sprintf(blob, "%d", l->altflag);
3674 snprintf(target, sizeof(target), "%s\t%s\t%s",
3675 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3676 l->user ? l->user : "",
3677 blob);
3679 snprintf(blob, sizeof(blob), "%s%s%s",
3680 l->passwd ? l->passwd : "",
3681 (l->hosts && l->hosts->next && l->hosts->next->name)
3682 ? "\t" : "",
3683 (l->hosts && l->hosts->next && l->hosts->next->name)
3684 ? l->hosts->next->name : "");
3686 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob));
3688 rc = SecKeychainAddGenericPassword(NULL,
3689 strlen(target), target,
3690 strlen(TNAME), TNAME,
3691 strlen(blob), blob,
3692 NULL);
3693 if(rc==0){
3694 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3696 else{
3697 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc));
3700 if(rc == errSecDuplicateItem){
3701 /* fix existing entry */
3702 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3703 itemRef = NULL;
3704 if(!(rc=SecKeychainFindGenericPassword(NULL,
3705 strlen(target), target,
3706 strlen(TNAME), TNAME,
3707 NULL, NULL,
3708 &itemRef)) && itemRef){
3710 rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob);
3711 if(!rc){
3712 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc));
3715 else{
3716 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc));
3721 #else /* PASSFILE */
3722 char tmp[4*MAILTMPLEN], blob[4*MAILTMPLEN];
3723 int i, n;
3724 FILE *fp;
3725 #ifdef SMIME
3726 char *text = NULL, tmp2[4*MAILTMPLEN];
3727 int len = 0;
3728 #endif
3730 if(using_passfile == 0)
3731 return;
3733 dprint((9, "write_passfile\n"));
3735 /* if there's no passfile to read, bag it!! */
3736 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "wb"))){
3737 using_passfile = 0;
3738 return;
3741 #ifdef SMIME
3742 strncpy(tmp2, tmp, sizeof(tmp2));
3743 tmp2[sizeof(tmp2)-1] = '\0';
3744 #endif /* SMIME */
3746 for(n = 0; l; l = l->next, n++){
3747 authtype = l->passwd;
3748 authend = strchr(l->passwd, PWDAUTHSEP);
3749 if(authend != NULL){
3750 *authend = '\0';
3751 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3752 *authend = PWDAUTHSEP;
3754 else
3755 sprintf(blob, "%d", l->altflag);
3757 /*** do any necessary ENcryption here ***/
3758 snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%s%s%s\n", l->passwd, l->user,
3759 l->hosts->name, blob,
3760 (l->hosts->next && l->hosts->next->name) ? "\t" : "",
3761 (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
3762 : "");
3763 dprint((10, "write_passfile: %s", tmp ? tmp : "?"));
3764 xlate_key = n;
3765 for(i = 0; tmp[i]; i++)
3766 tmp[i] = xlate_in(tmp[i]);
3768 #ifdef SMIME
3769 fs_resize((void **)&text, (len + strlen(tmp) + 1)*sizeof(char));
3770 text[len] = '\0';
3771 len += strlen(tmp) + 1;
3772 strncat(text, tmp, strlen(tmp));
3773 #else /* SMIME */
3774 fputs(tmp, fp);
3775 #endif /* SMIME */
3778 fclose(fp);
3779 #ifdef SMIME
3780 if(text != NULL){
3781 if(ps_global->pwdcert == NULL){
3782 q_status_message(SM_ORDER, 3, 3, "Attempting to encrypt password file");
3783 i = setup_pwdcert(&ps_global->pwdcert);
3784 if((i == 0 || i == -5) && ps_global->pwdcert == NULL)
3785 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3787 if(ps_global->pwdcert == NULL){ /* we tried but failed */
3788 if(i == -1)
3789 q_status_message1(SM_ORDER, 3, 3, "Error: no directory %s to save certificates", ps_global->pwdcertdir);
3790 else
3791 q_status_message(SM_ORDER, 3, 3, "Refusing to write non-encrypted password file");
3793 else if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0)
3794 q_status_message(SM_ORDER, 3, 3, "Failed to encrypt password file");
3795 fs_give((void **)&text); /* do not save this text */
3797 #endif /* SMIME */
3798 #endif /* PASSFILE */
3801 #endif /* LOCAL_PASSWD_CACHE */
3804 #if (WINCRED > 0)
3805 void
3806 erase_windows_credentials(void)
3808 LPCTSTR lfilter = TEXT(TNAMESTAR);
3809 DWORD count, k;
3810 PCREDENTIAL *pcred;
3812 if(!g_CredInited){
3813 if(init_wincred_funcs() != 1)
3814 return;
3817 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
3818 if(pcred){
3819 for(k = 0; k < count; k++)
3820 g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0);
3822 g_CredFree((PVOID) pcred);
3827 void
3828 ask_erase_credentials(void)
3830 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){
3831 erase_windows_credentials();
3832 q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased");
3834 else
3835 q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased");
3838 #endif /* WINCRED */
3841 #ifdef LOCAL_PASSWD_CACHE
3843 get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
3844 char *pinerc, **passwd, *user;
3845 STRLIST_S *hostlist;
3846 int altflag;
3848 return get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, NULL);
3852 * get_passfile_passwd_auth - return the password contained in the special passord
3853 * cache. The file is assumed to be in the same directory
3854 * as the pinerc with the name defined above.
3857 get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, authtype)
3858 char *pinerc, **passwd, *user;
3859 STRLIST_S *hostlist;
3860 int altflag;
3861 char *authtype;
3863 dprint((10, "get_passfile_passwd_auth\n"));
3864 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3865 ? imap_get_passwd_auth(passfile_cache, passwd,
3866 user, hostlist, altflag, authtype)
3867 : 0);
3870 void
3871 free_passfile_cache_work(MMLOGIN_S **pwdcache)
3873 if(pwdcache == NULL || *pwdcache == NULL)
3874 return;
3876 if((*pwdcache)->user) fs_give((void **)&(*pwdcache)->user);
3877 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
3878 if((*pwdcache)->hosts) free_strlist(&(*pwdcache)->hosts);
3879 free_passfile_cache_work(&(*pwdcache)->next);
3880 fs_give((void **)pwdcache);
3884 void
3885 free_passfile_cache(void)
3887 if(passfile_cache)
3888 free_passfile_cache_work(&passfile_cache);
3892 is_using_passfile(void)
3894 return(using_passfile == 1);
3898 * Just trying to guess the username the user might want to use on this
3899 * host, the user will confirm.
3901 char *
3902 get_passfile_user(pinerc, hostlist)
3903 char *pinerc;
3904 STRLIST_S *hostlist;
3906 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3907 ? imap_get_user(passfile_cache, hostlist)
3908 : NULL);
3913 preserve_prompt(char *pinerc)
3915 return preserve_prompt_auth(pinerc, NULL);
3919 preserve_prompt_auth(char *pinerc, char *authtype)
3921 #ifdef WINCRED
3922 # if (WINCRED > 0)
3923 #define PROMPT_PWD _("Preserve password for next login")
3924 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3926 * This prompt was going to be able to be turned on and off via a registry
3927 * setting controlled from the config menu. We decided to always use the
3928 * dialog for login, and there the prompt is unobtrusive enough to always
3929 * be in there. As a result, windows should never reach this, but now
3930 * OS X somewhat uses the behavior just described.
3932 if(mswin_store_pass_prompt()
3933 && (want_to(authtype
3934 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3935 : PROMPT_PWD,
3936 'y', 'x', NO_HELP, WT_NORM)
3937 == 'y'))
3938 return(1);
3939 else
3940 return(0);
3941 # else
3942 return(0);
3943 # endif
3945 #elif APPLEKEYCHAIN
3946 #define PROMPT_PWD _("Preserve password for next login")
3947 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3949 int rc;
3950 if((rc = macos_store_pass_prompt()) != 0){
3951 if(want_to(authtype
3952 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3953 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
3954 == 'y'){
3955 if(rc == -1){
3956 macos_set_store_pass_prompt(1);
3957 q_status_message(SM_ORDER, 4, 4,
3958 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3960 return(1);
3962 else if(rc == -1){
3963 macos_set_store_pass_prompt(0);
3964 q_status_message(SM_ORDER, 4, 4,
3965 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3967 return(0);
3969 return(0);
3970 #else /* PASSFILE */
3971 #define PROMPT_PWD _("Preserve password on DISK for next login")
3972 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
3974 char tmp[MAILTMPLEN];
3975 struct stat sbuf;
3977 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || our_stat(tmp, &sbuf) < 0)
3978 return 0;
3980 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
3981 return(want_to(authtype
3982 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3983 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
3984 == 'y');
3985 return(0);
3986 #endif /* PASSFILE */
3989 #endif /* LOCAL_PASSWD_CACHE */
3992 #ifdef APPLEKEYCHAIN
3995 * Returns:
3996 * 1 if store pass prompt is set in the "registry" to on
3997 * 0 if set to off
3998 * -1 if not set to anything
4001 macos_store_pass_prompt(void)
4003 char *data = NULL;
4004 UInt32 len = 0;
4005 int rc = -1;
4006 int val;
4008 if(storepassprompt == -1){
4009 if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL,
4010 strlen(TNAMEPROMPT),
4011 TNAMEPROMPT, &len,
4012 (void **) &data, NULL))){
4013 val = (len == 1 && data && data[0] == '1');
4017 if(storepassprompt == -1 && !rc){
4018 if(val)
4019 storepassprompt = 1;
4020 else
4021 storepassprompt = 0;
4024 return(storepassprompt);
4028 void
4029 macos_set_store_pass_prompt(int val)
4031 storepassprompt = val ? 1 : 0;
4033 SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT),
4034 TNAMEPROMPT, 1, val ? "1" : "0", NULL);
4038 void
4039 macos_erase_keychain(void)
4041 SecKeychainAttributeList attrList;
4042 SecKeychainSearchRef searchRef = NULL;
4043 SecKeychainAttribute attrs1[] = {
4044 { kSecAccountItemAttr, strlen(TNAME), TNAME }
4046 SecKeychainAttribute attrs2[] = {
4047 { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT }
4050 dprint((9, "macos_erase_keychain\n"));
4053 * Seems like we ought to be able to combine attrs1 and attrs2
4054 * into a single array, but I couldn't get it to work.
4057 /* search for only our items in the keychain */
4058 attrList.count = 1;
4059 attrList.attr = attrs1;
4061 if(!SecKeychainSearchCreateFromAttributes(NULL,
4062 kSecGenericPasswordItemClass,
4063 &attrList,
4064 &searchRef)){
4065 if(searchRef){
4066 SecKeychainItemRef itemRef = NULL;
4069 * Go through each item we found and put it
4070 * into our list.
4072 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4073 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4074 SecKeychainItemDelete(itemRef);
4075 CFRelease(itemRef);
4078 CFRelease(searchRef);
4082 attrList.count = 1;
4083 attrList.attr = attrs2;
4085 if(!SecKeychainSearchCreateFromAttributes(NULL,
4086 kSecGenericPasswordItemClass,
4087 &attrList,
4088 &searchRef)){
4089 if(searchRef){
4090 SecKeychainItemRef itemRef = NULL;
4093 * Go through each item we found and put it
4094 * into our list.
4096 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4097 SecKeychainItemDelete(itemRef);
4098 CFRelease(itemRef);
4101 CFRelease(searchRef);
4106 #endif /* APPLEKEYCHAIN */
4108 #ifdef LOCAL_PASSWD_CACHE
4110 void
4111 set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
4112 char *pinerc, *passwd, *user;
4113 STRLIST_S *hostlist;
4114 int altflag, already_prompted;
4116 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, NULL);
4119 * set_passfile_passwd - set the password file entry associated with
4120 * cache. The file is assumed to be in the same directory
4121 * as the pinerc with the name defined above.
4122 * already_prompted: 0 not prompted
4123 * 1 prompted, answered yes
4124 * 2 prompted, answered no
4126 void
4127 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, authtype)
4128 char *pinerc, *passwd, *user;
4129 STRLIST_S *hostlist;
4130 int altflag, already_prompted;
4131 char *authtype;
4133 dprint((10, "set_passfile_passwd_auth\n"));
4134 if(((already_prompted == 0 && preserve_prompt_auth(pinerc, authtype))
4135 || already_prompted == 1)
4136 && !ps_global->nowrite_password_cache
4137 && (passfile_cache || read_passfile(pinerc, &passfile_cache))){
4138 imap_set_passwd_auth(&passfile_cache, passwd, user, hostlist, altflag, 0, 0, authtype);
4139 write_passfile(pinerc, passfile_cache);
4143 void
4144 update_passfile_hostlist(pinerc, user, hostlist, altflag)
4145 char *pinerc;
4146 char *user;
4147 STRLIST_S *hostlist;
4148 int altflag;
4150 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, NULL);
4154 * Passfile lines are
4156 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4158 * In pine4.40 and before there was no orig_hostname.
4159 * This routine attempts to repair that.
4161 void
4162 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, authtype)
4163 char *pinerc;
4164 char *user;
4165 STRLIST_S *hostlist;
4166 int altflag;
4167 char *authtype;
4169 #ifdef WINCRED
4170 return;
4171 #else /* !WINCRED */
4172 MMLOGIN_S *l;
4173 size_t len = authtype ? strlen(authtype) : 0;
4174 size_t offset = authtype ? 1 : 0;
4176 for(l = passfile_cache; l; l = l->next)
4177 if(imap_same_host_auth(l->hosts, hostlist, authtype)
4178 && *user
4179 && !strcmp(user, l->user + len + offset)
4180 && l->altflag == altflag){
4181 break;
4184 if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
4185 && hostlist->next->name
4186 && !ps_global->nowrite_password_cache){
4187 l->hosts->next = new_strlist_auth(hostlist->next->name, authtype, PWDAUTHSEP);
4188 write_passfile(pinerc, passfile_cache);
4190 #endif /* !WINCRED */
4193 #endif /* LOCAL_PASSWD_CACHE */
4196 #if (WINCRED > 0)
4198 * Load and init the WinCred structure.
4199 * This gives us a way to skip the WinCred code
4200 * if the dll doesn't exist.
4203 init_wincred_funcs(void)
4205 if(!g_CredInited)
4207 HMODULE hmod;
4209 /* Assume the worst. */
4210 g_CredInited = -1;
4212 hmod = LoadLibrary(TEXT("advapi32.dll"));
4213 if(hmod)
4215 FARPROC fpCredWriteW;
4216 FARPROC fpCredEnumerateW;
4217 FARPROC fpCredDeleteW;
4218 FARPROC fpCredFree;
4220 fpCredWriteW = GetProcAddress(hmod, "CredWriteW");
4221 fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW");
4222 fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW");
4223 fpCredFree = GetProcAddress(hmod, "CredFree");
4225 if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree)
4227 g_CredWriteW = (CREDWRITEW *)fpCredWriteW;
4228 g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW;
4229 g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW;
4230 g_CredFree = (CREDFREE *)fpCredFree;
4232 g_CredInited = 1;
4236 mswin_set_erasecreds_callback(ask_erase_credentials);
4239 return g_CredInited;
4242 #endif /* WINCRED */