* new http option for debug. This is mostly useful to debug XOAUTH2
[alpine.git] / alpine / imap.c
blob2e02ef6ed0954e497a51489d6f8d841fbb1a4911
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: imap.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2020 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
20 imap.c
21 The call back routines for the c-client/imap
22 - handles error messages and other notification
23 - handles prelimirary notification of new mail and expunged mail
24 - prompting for imap server login and password
26 ====*/
28 #include "headers.h"
29 #include "alpine.h"
30 #include "imap.h"
31 #include "status.h"
32 #include "mailview.h"
33 #include "mailcmd.h"
34 #include "radio.h"
35 #include "keymenu.h"
36 #include "signal.h"
37 #include "mailpart.h"
38 #include "mailindx.h"
39 #include "arg.h"
40 #include "busy.h"
41 #include "titlebar.h"
42 #include "xoauth2.h"
43 #include "xoauth2conf.h"
44 #include "confscroll.h"
45 #include "init.h"
46 #include "../pith/state.h"
47 #include "../pith/conf.h"
48 #include "../pith/msgno.h"
49 #include "../pith/filter.h"
50 #include "../pith/news.h"
51 #include "../pith/util.h"
52 #include "../pith/list.h"
53 #include "../pith/margin.h"
54 #ifdef SMIME
55 #include "../pith/smime.h"
56 #endif /* SMIME */
58 #if (WINCRED > 0)
59 #include <wincred.h>
60 #define TNAME "UWash_Alpine_"
61 #define TNAMESTAR "UWash_Alpine_*"
64 * WinCred Function prototypes
66 typedef BOOL (WINAPI CREDWRITEW) ( __in PCREDENTIALW Credential, __in DWORD Flags );
67 typedef BOOL (WINAPI CREDENUMERATEW) ( __in LPCWSTR Filter, __reserved DWORD Flags,
68 __out DWORD *Count, __deref_out_ecount(*Count) PCREDENTIALW **Credential );
69 typedef BOOL (WINAPI CREDDELETEW) ( __in LPCWSTR TargetName, __in DWORD Type,
70 __reserved DWORD Flags );
71 typedef VOID (WINAPI CREDFREE) ( __in PVOID Buffer );
74 * WinCred functions
76 int g_CredInited = 0; /* 1 for loaded successfully,
77 * -1 for not available.
78 * 0 for not initialized yet.
80 CREDWRITEW *g_CredWriteW;
81 CREDENUMERATEW *g_CredEnumerateW;
82 CREDDELETEW *g_CredDeleteW;
83 CREDFREE *g_CredFree;
85 #endif /* WINCRED */
87 #ifdef APPLEKEYCHAIN
88 #include <Security/SecKeychain.h>
89 #include <Security/SecKeychainItem.h>
90 #include <Security/SecKeychainSearch.h>
91 #define TNAME "UWash_Alpine"
92 #define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
94 int macos_store_pass_prompt(void);
95 void macos_set_store_pass_prompt(int);
97 static int storepassprompt = -1;
98 #endif /* APPLEKEYCHAIN */
102 * Internal prototypes
104 void mm_login_alt_cue(NETMBX *);
105 long pine_tcptimeout_noscreen(long, long, char *);
106 int answer_cert_failure(int, MSGNO_S *, SCROLL_S *);
107 int oauth2_auth_answer(int, MSGNO_S *, SCROLL_S *);
108 OAUTH2_S *oauth2_select_flow(char *);
109 int xoauth2_flow_tool(struct pine *, int, CONF_S **, unsigned int);
111 #ifdef LOCAL_PASSWD_CACHE
112 int read_passfile(char *, MMLOGIN_S **);
113 void write_passfile(char *, MMLOGIN_S *);
114 int preserve_prompt(char *);
115 int preserve_prompt_auth(char *, char *authtype);
116 void update_passfile_hostlist(char *, char *, STRLIST_S *, int);
117 void update_passfile_hostlist_auth(char *, char *, STRLIST_S *, int, char *);
118 void free_passfile_cache_work(MMLOGIN_S **);
120 static MMLOGIN_S *passfile_cache = NULL;
121 static int using_passfile = -1;
122 int save_password = 1;
123 #endif /* LOCAL_PASSWD_CACHE */
125 #ifdef PASSFILE
126 char xlate_in(int);
127 char xlate_out(char);
128 int line_get(char *, size_t, char **);
129 #endif /* PASSFILE */
131 #if (WINCRED > 0)
132 void ask_erase_credentials(void);
133 int init_wincred_funcs(void);
134 #endif /* WINCRED */
137 static char *details_cert, *details_host, *details_reason;
139 extern XOAUTH2_INFO_S xoauth_default[];
142 * This is the private information of the client, which is passed to
143 * c-client for processing. Every c-client application must have its
144 * own.
146 OAUTH2_S alpine_oauth2_list[] =
148 {GMAIL_NAME,
149 {"imap.gmail.com", "smtp.gmail.com", NULL, NULL},
150 {{"client_id", NULL},
151 {"client_secret", NULL},
152 {"tenant", NULL}, /* not used */
153 {"code", NULL}, /* access code from the authorization process */
154 {"refresh_token", NULL},
155 {"scope", "https://mail.google.com/"},
156 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
157 {"grant_type", "authorization_code"},
158 {"grant_type", "refresh_token"},
159 {"response_type", "code"},
160 {"state", NULL},
161 {"device_code", NULL} /* not used */
163 {{"GET", "https://accounts.google.com/o/oauth2/auth", /* authorization address, get access code */
164 {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End}},
165 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* Device Info information, not used */
166 {"POST", "https://accounts.google.com/o/oauth2/token", /* Address to get refresh token from access code */
167 {OA2_Id, OA2_Secret, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Code, OA2_End, OA2_End}},
168 {"POST", "https://accounts.google.com/o/oauth2/token", /* access token from refresh token */
169 {OA2_Id, OA2_Secret, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}
171 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information */
172 NULL, /* access token */
173 NULL, /* special IMAP ID */
174 0, /* do not hide */
175 0, /* expiration time */
176 0, /* first time indicator */
177 1, /* client secret required */
178 0 /* Cancel refresh token */
180 {OUTLOOK_NAME,
181 {"outlook.office365.com", "smtp.office365.com", NULL, NULL},
182 {{"client_id", NULL},
183 {"client_secret", NULL}, /* not used, but needed */
184 {"tenant", NULL}, /* used */
185 {"code", NULL}, /* not used, not needed */
186 {"refresh_token", NULL},
187 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
188 {"grant_type", "urn:ietf:params:oauth:grant-type:device_code"},
189 {"scope", NULL}, /* not used */
190 {"grant_type", "refresh_token"},
191 {"response_type", "code"}, /* not used */
192 {"state", NULL}, /* not used */
193 {"device_code", NULL} /* only used for frst time set up */
195 {{NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* Get Access Code, Not used */
196 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/devicecode", /* first time use and get device code information */
197 {OA2_Id, OA2_Scope, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}},
198 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
199 {OA2_Id, OA2_Redirect, OA2_DeviceCode, OA2_End, OA2_End, OA2_End, OA2_End}},
200 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
201 {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}
203 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information */
204 NULL, /* access token */
205 NULL, /* special IMAP ID */
206 0, /* do not hide */
207 0, /* expiration time */
208 0, /* first time indicator */
209 0, /* client secret required */
210 0 /* Cancel refresh token */
212 {OUTLOOK_NAME,
213 {"outlook.office365.com", "smtp.office365.com", NULL, NULL},
214 {{"client_id", NULL},
215 {"client_secret", NULL}, /* not used, but needed */
216 {"tenant", NULL}, /* used */
217 {"code", NULL}, /* used during authorization */
218 {"refresh_token", NULL},
219 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
220 {"redirect_uri", "http://localhost"},
221 {"grant_type", "authorization_code"},
222 {"grant_type", "refresh_token"},
223 {"response_type", "code"},
224 {"state", NULL}, /* not used */
225 {"device_code", NULL} /* not used */
227 {{"GET", "https://login.microsoftonline.com/\001/oauth2/v2.0/authorize", /* Get Access Code */
228 {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End}},
229 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
230 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
231 {OA2_Id, OA2_Redirect, OA2_Scope, OA2_GrantTypeforAccessToken, OA2_Secret, OA2_Code, OA2_End}},
232 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
233 {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_Secret, OA2_End, OA2_End}}
235 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
236 NULL, /* access token */
237 NULL, /* special IMAP ID */
238 0, /* do not hide */
239 0, /* expiration time */
240 0, /* first time indicator */
241 1, /* client secret required */
242 0 /* Cancel refresh token */
244 {YAHOO_NAME,
245 {"imap.mail.yahoo.com", "smtp.mail.yahoo.com", NULL, NULL},
246 {{"client_id", NULL},
247 {"client_secret", NULL}, /* used */
248 {"tenant", NULL}, /* not used */
249 {"code", NULL}, /* used during authorization */
250 {"refresh_token", NULL},
251 {"scope", NULL}, /* not used! */
252 {"redirect_uri", "oob"}, /* https://localhost */
253 {"grant_type", "authorization_code"},
254 {"grant_type", "refresh_token"},
255 {"response_type", "code"},
256 {"state", NULL}, /* used */
257 {"device_code", NULL} /* not used */
259 {{"GET", "https://api.login.yahoo.com/oauth2/request_auth", /* Get Access Code */
260 {OA2_Id, OA2_Redirect, OA2_Response, OA2_State, OA2_End, OA2_End, OA2_End}},
261 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
262 {"POST", "https://api.login.yahoo.com/oauth2/get_token", /* Get first Refresh Token and Access token */
263 {OA2_Id, OA2_Secret, OA2_Redirect, OA2_Code, OA2_GrantTypeforAccessToken, OA2_End, OA2_End}},
264 {"POST", "https://api.login.yahoo.com/oauth2/get_token", /* Get access token from refresh token */
265 {OA2_Id, OA2_Secret, OA2_Redirect, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End}}
267 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
268 NULL, /* access token */
269 "ALPINE_V1", /* special IMAP ID */
270 1, /* hide */
271 0, /* expiration time */
272 0, /* first time indicator */
273 1, /* client secret required */
274 0 /* Cancel refresh token */
276 {YANDEX_NAME,
277 {"imap.yandex.com", "smtp.yandex.com", NULL, NULL},
278 {{"client_id", NULL},
279 {"client_secret", NULL}, /* not used, but needed */
280 {"tenant", NULL}, /* not used */
281 {"code", NULL}, /* used during authorization */
282 {"refresh_token", NULL},
283 {"scope", NULL}, /* not needed, so not used */
284 {"redirect_uri", "https://oauth.yandex.ru/verification_code"},
285 {"grant_type", "authorization_code"},
286 {"grant_type", "refresh_token"},
287 {"response_type", "code"},
288 {"state", NULL}, /* not used */
289 {"device_code", NULL} /* not used */
291 {{"GET", "https://oauth.yandex.com/authorize", /* Get Access Code */
292 {OA2_Id, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End, OA2_End}},
293 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
294 {"POST", "https://oauth.yandex.com/token", /* Get first Refresh Token and Access token */
295 {OA2_Id, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Secret, OA2_Code, OA2_End, OA2_End}},
296 {"POST", "https://oauth.yandex.com/token", /* Get access token from refresh token */
297 {OA2_Id, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_Secret, OA2_End, OA2_End, OA2_End}}
299 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
300 NULL, /* access token */
301 NULL, /* special IMAP ID */
302 0, /* do not hide */
303 0, /* expiration time */
304 0, /* first time indicator */
305 1, /* client secret required */
306 0 /* Cancel refresh token */
308 { NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0, 0, 0, 0, 0},
312 xoauth2_flow_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
314 int rv = 0;
316 switch(cmd){
317 case MC_CHOICE:
318 *((*cl)->d.xf.selected) = (*cl)->d.xf.pat;
319 rv = simple_exit_cmd(flags);
321 case MC_EXIT:
322 rv = simple_exit_cmd(flags);
323 break;
325 default:
326 rv = -1;
329 if(rv > 0)
330 ps->mangled_body = 1;
332 return rv;
335 OAUTH2_S *
336 oauth2_select_flow(char *host)
338 OAUTH2_S *oa2list, *oa2;
339 int i, rv;
340 char *method;
342 if(ps_global->ttyo){
343 CONF_S *ctmp = NULL, *first_line = NULL;
344 OAUTH2_S *x_sel = NULL;
345 OPT_SCREEN_S screen;
346 char tmp[1024];
348 dprint((9, "xoauth2 select flow"));
349 ps_global->next_screen = SCREEN_FUN_NULL;
351 memset(&screen, 0, sizeof(screen));
353 for(i = 0; i < sizeof(tmp) && i < ps_global->ttyo->screen_cols; i++)
354 tmp[i] = '-';
355 tmp[i] = '\0';
357 new_confline(&ctmp);
358 ctmp->flags |= CF_NOSELECT;
359 ctmp->value = cpystr(tmp);
361 new_confline(&ctmp);
362 ctmp->flags |= CF_NOSELECT;
363 ctmp->value = cpystr(_("Please select below the authorization flow you would like to follow:"));
365 new_confline(&ctmp);
366 ctmp->flags |= CF_NOSELECT;
367 ctmp->value = cpystr(tmp);
369 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
370 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
371 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
372 new_confline(&ctmp);
373 if(!first_line)
374 first_line = ctmp;
375 method = oa2list->server_mthd[0].name ? "Authorize"
376 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
377 sprintf(tmp, "%s (%s)", oa2list->name, method);
378 ctmp->value = cpystr(tmp);
379 ctmp->d.xf.selected = &x_sel;
380 ctmp->d.xf.pat = oa2list;
381 ctmp->keymenu = &xoauth2_id_select_km;
382 ctmp->help = NO_HELP;
383 ctmp->help_title = NULL;
384 ctmp->tool = xoauth2_flow_tool;
385 ctmp->flags = CF_STARTITEM;
386 ctmp->valoffset = 4;
389 (void)conf_scroll_screen(ps_global, &screen, first_line, _("SELECT AUTHORIZATION FLOW"),
390 _("xoauth2"), 0, NULL);
391 oa2 = x_sel;
393 else{
394 char *s;
395 char prompt[1024];
396 char reply[1024];
397 int sel, n = 0, j;
399 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++)
400 n += strlen(oa2list->name); + 5; /* number, parenthesis, space */
401 n += 1024; /* large enough to display to lines of 80 characters in UTF-8 */
402 s = fs_get(n*sizeof(char));
403 strcpy(s, _("Please select below the authorization flow you would like to follow:"));
404 sprintf(s + strlen(s), _("Please select the client-id to use from the following list.\n\n"));
405 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
406 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
407 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i])
408 sprintf(s + strlen(s), " %d) %.70s\n", j++, oa2list->name);
410 display_init_err(s, 0);
412 strncpy(prompt, _("Enter your selection number: "), sizeof(prompt));
413 prompt[sizeof(prompt)-1] = '\0';
415 rv = optionally_enter(reply, 0, 0, sizeof(reply), prompt, NULL, NO_HELP, 0);
416 sel = atoi(reply);
417 rv = (sel >= 0 && sel < i) ? 0 : -1;
418 } while (rv != 0);
420 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
421 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
422 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
423 if(j == sel) break;
424 else j++;
427 oa2 = oa2list;
429 return oa2;
432 typedef struct auth_code_s {
433 char *code;
434 int answer;
435 } AUTH_CODE_S;
438 oauth2device_decode_reply(void *datap, void *replyp)
440 OAUTH2_DEVICEPROC_S *av = (OAUTH2_DEVICEPROC_S *)datap;
441 int reply = *(int *) replyp;
443 return reply < 0 ? av->code_failure : (reply == 0 ? av->code_success : av->code_wait);
447 oauth2_elapsed_done(void *aux_valuep)
449 OAUTH2_S *oauth2 = aux_valuep ? ((OAUTH2_DEVICEPROC_S *) aux_valuep)->xoauth2 : NULL;
450 static time_t savedt = 0, now;
451 int rv = 0;
453 if(aux_valuep == NULL) savedt = 0; /* reset last time we approved */
454 else{
455 now = time(0);
456 if(oauth2->devicecode.interval + now >= savedt)
457 savedt = now;
458 else
459 rv = -1;
461 return rv;
464 void
465 oauth2_set_device_info(OAUTH2_S *oa2, char *method)
467 char tmp[MAILTMPLEN];
468 char *code;
469 char *name = oa2->name;
470 int aux_rv_value;
471 OAUTH2_DEVICECODE_S *deviceinfo = &oa2->devicecode;
472 OAUTH2_DEVICEPROC_S aux_value;
474 if(ps_global->ttyo){
475 SCROLL_S sargs;
476 STORE_S *in_store, *out_store;
477 gf_io_t pc, gc;
478 HANDLE_S *handles = NULL;
479 AUTH_CODE_S user_input;
481 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
482 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
483 goto try_wantto;
485 aux_value.xoauth2 = oa2;
486 aux_value.code_success = 'e';
487 aux_value.code_failure = 'e';
488 aux_value.code_wait = NO_OP_COMMAND;
490 so_puts(in_store, "<HTML><P>");
491 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name);
492 so_puts(in_store, tmp);
493 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name, method),
494 so_puts(in_store, tmp);
496 if(deviceinfo->verification_uri && deviceinfo->user_code){
497 sprintf(tmp,
498 _("</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."),
499 deviceinfo->verification_uri, deviceinfo->verification_uri, deviceinfo->user_code);
500 so_puts(in_store, tmp);
502 else{
503 so_puts(in_store, "</P><P>");
504 so_puts(in_store, deviceinfo->message);
506 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
507 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), name);
508 so_puts(in_store, tmp);
509 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
511 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 "));
512 so_puts(in_store, _("to grant access to Alpine to your data. "));
513 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 "));
514 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 "));
515 so_puts(in_store, _("alive at the end of this process, and your connection will proceed from there."));
516 so_puts(in_store, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit"));
517 so_puts(in_store, _("</P></HTML>"));
519 so_seek(in_store, 0L, 0);
520 init_handles(&handles);
521 gf_filter_init();
522 gf_link_filter(gf_html2plain,
523 gf_html2plain_opt(NULL,
524 ps_global->ttyo->screen_cols, non_messageview_margin(),
525 &handles, NULL, GFHP_LOCAL_HANDLES));
526 gf_set_so_readc(&gc, in_store);
527 gf_set_so_writec(&pc, out_store);
528 gf_pipe(gc, pc);
529 gf_clear_so_writec(out_store);
530 gf_clear_so_readc(in_store);
532 memset(&sargs, 0, sizeof(SCROLL_S));
533 sargs.text.handles = handles;
534 sargs.text.text = so_text(out_store);
535 sargs.text.src = CharStar;
536 sargs.text.desc = _("help text");
537 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
538 sargs.proc.tool = oauth2_auth_answer;
539 sargs.proc.data.p = (void *)&user_input;
540 sargs.keys.menu = &oauth2_device_auth_keymenu;
541 /* don't want to re-enter c-client */
542 sargs.quell_newmail = 1;
543 setbitmap(sargs.keys.bitmap);
544 sargs.help.text = h_oauth2_start;
545 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
546 sargs.aux_function = oauth2deviceinfo_get_accesscode;
547 sargs.aux_value = (void *) &aux_value;
548 sargs.aux_condition = oauth2_elapsed_done;
549 sargs.decode_aux_rv_value = oauth2device_decode_reply;
550 sargs.aux_rv_value = (void *) &aux_rv_value;
552 do {
553 scrolltool(&sargs);
554 ps_global->mangled_screen = 1;
555 ps_global->painted_body_on_startup = 0;
556 ps_global->painted_footer_on_startup = 0;
557 } while (user_input.answer != 'e');
559 so_give(&in_store);
560 so_give(&out_store);
561 free_handles(&handles);
562 oauth2_elapsed_done(NULL);
564 else{
565 int flags, rc, q_line;
566 /* TRANSLATORS: user needs to input an access code from the server */
567 char prompt[MAILTMPLEN], token[MAILTMPLEN];
569 * If screen hasn't been initialized yet, use want_to.
571 try_wantto:
573 tmp_20k_buf[0] = '\0';
574 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
575 _("Authorizing Alpine Access to %s Email Services\n\n"), name);
576 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
578 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
579 _("Alpine is attempting to log you into your %s account, using the %s method. "), name, method),
580 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
582 if(deviceinfo->verification_uri && deviceinfo->user_code){
583 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
584 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
585 deviceinfo->verification_uri, deviceinfo->user_code);
586 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
588 else{
589 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
590 "%s\n\n", deviceinfo->message);
591 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
594 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
595 _("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);
596 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
598 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
599 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
600 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
602 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
603 "%s", _("to grant access to Alpine to your data.\n\n"));
604 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
606 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
607 "%s", _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
608 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
610 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
611 "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
612 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
614 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
615 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
616 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
618 display_init_err(tmp_20k_buf, 0);
619 memset((void *)tmp, 0, sizeof(tmp));
620 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
621 tmp[sizeof(tmp)-1] = '\0';
623 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
624 int rv;
625 UCS ch;
626 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
627 flags = OE_APPEND_CURRENT;
629 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
630 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
631 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
633 aux_value.xoauth2 = oa2;
634 aux_value.code_success = 'y';
635 aux_value.code_failure = 'n';
636 aux_value.code_wait = 'w';
638 strncpy(tmp, _("Continue waiting"), sizeof(tmp));
639 tmp[sizeof(tmp)-1] = '\0';
640 do {
641 if(oauth2_elapsed_done((void *) &aux_value) == 0)
642 oauth2deviceinfo_get_accesscode((void *) &aux_value, (void *) &rv);
643 ch = oauth2device_decode_reply((void *) &aux_value, (void *) &rv);
644 } while (ch == 'w' || want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y');
645 oauth2_elapsed_done(NULL);
650 char *
651 oauth2_get_access_code(unsigned char *url, char *method, OAUTH2_S *oauth2, int *tryanother)
653 char tmp[MAILTMPLEN];
654 char *code;
656 if(ps_global->ttyo){
657 SCROLL_S sargs;
658 STORE_S *in_store, *out_store;
659 gf_io_t pc, gc;
660 HANDLE_S *handles = NULL;
661 AUTH_CODE_S user_input;
663 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
664 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
665 goto try_wantto;
667 so_puts(in_store, "<HTML><BODY><P>");
668 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2->name);
669 so_puts(in_store, tmp);
670 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
671 so_puts(in_store, tmp);
673 if(strucmp(oauth2->name, GMAIL_NAME) == 0){
674 so_puts(in_store, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
675 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."));
676 so_puts(in_store, _("<UL> "));
677 so_puts(in_store, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
678 so_puts(in_store, _("and create a project. The name of the project is not important."));
679 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."));
680 so_puts(in_store, _("<LI> Create OAUTH Credentials."));
681 so_puts(in_store, _("</UL> "));
682 so_puts(in_store, _("<P> As a result of this process, you will get a client-id and a client-secret."));
683 so_puts(in_store, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
684 so_puts(in_store, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
685 so_puts(in_store, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
686 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."));
689 so_puts(in_store, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
690 so_puts(in_store,"</P><P>");
691 sprintf(tmp_20k_buf, _("<A HREF=\"%s\">%s</A>"), url, url);
692 so_puts(in_store, tmp_20k_buf);
694 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
695 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2->name);
696 so_puts(in_store, tmp);
697 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
699 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. "));
700 so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page."));
701 so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
702 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. "));
703 so_puts(in_store, _(" Once you have completed this process, Alpine will proceed with authentication."));
704 so_puts(in_store, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
705 so_puts(in_store, _("</P></BODY></HTML>"));
707 so_seek(in_store, 0L, 0);
708 init_handles(&handles);
709 gf_filter_init();
710 gf_link_filter(gf_html2plain,
711 gf_html2plain_opt(NULL,
712 ps_global->ttyo->screen_cols, non_messageview_margin(),
713 &handles, NULL, GFHP_LOCAL_HANDLES));
714 gf_set_so_readc(&gc, in_store);
715 gf_set_so_writec(&pc, out_store);
716 gf_pipe(gc, pc);
717 gf_clear_so_writec(out_store);
718 gf_clear_so_readc(in_store);
720 memset(&sargs, 0, sizeof(SCROLL_S));
721 sargs.text.handles = handles;
722 sargs.text.text = so_text(out_store);
723 sargs.text.src = CharStar;
724 sargs.text.desc = _("help text");
725 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
726 sargs.proc.tool = oauth2_auth_answer;
727 sargs.proc.data.p = (void *)&user_input;
728 sargs.keys.menu = &oauth2_auth_keymenu;
729 /* don't want to re-enter c-client */
730 sargs.quell_newmail = 1;
731 setbitmap(sargs.keys.bitmap);
732 sargs.help.text = h_oauth2_start;
733 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
735 do {
736 scrolltool(&sargs);
737 ps_global->mangled_screen = 1;
738 ps_global->painted_body_on_startup = 0;
739 ps_global->painted_footer_on_startup = 0;
740 } while (user_input.answer != 'e');
742 if(!struncmp(user_input.code, "http://", 7)
743 || !struncmp(user_input.code, "https://", 8)){
744 char *s, *t;
745 s = strstr(user_input.code, "code=");
746 if(s != NULL){
747 t = strchr(s, '&');
748 if(t) *t = '\0';
749 code = cpystr(s+5);
750 if(t) *t = '&';
753 else code = user_input.code ? cpystr(user_input.code) : NULL;
754 if(user_input.code) fs_give((void **) &user_input.code);
756 if(code == NULL) *tryanother = 1;
758 so_give(&in_store);
759 so_give(&out_store);
760 free_handles(&handles);
762 else{
763 int flags, rc, q_line;
764 /* TRANSLATORS: user needs to input an access code from the server */
765 char *accesscodelabel = _("Copy and Paste Access Code");
766 char prompt[MAILTMPLEN], token[MAILTMPLEN];
768 * If screen hasn't been initialized yet, use want_to.
770 try_wantto:
771 tmp_20k_buf[0] = '\0';
772 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
773 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2->name);
774 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
776 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
777 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
778 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
780 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
781 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
782 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
784 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
785 "%s\n\n", url);
786 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
788 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
789 _("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);
790 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
792 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
793 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
794 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
796 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
797 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
798 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
800 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
801 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
802 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
804 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
805 "%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. "));
806 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
808 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
809 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
810 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
812 display_init_err(tmp_20k_buf, 0);
813 memset((void *)tmp, 0, sizeof(tmp));
814 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
815 tmp[sizeof(tmp)-1] = '\0';
817 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
818 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
819 flags = OE_APPEND_CURRENT;
820 sprintf(prompt, "%s: ", accesscodelabel);
821 do {
822 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
823 prompt, NULL, NO_HELP, &flags);
824 } while (rc != 0 && rc != 1);
825 if(!struncmp(token, "http://", 7)
826 || !struncmp(token, "https://", 8)){
827 char *s, *t;
828 s = strstr(token, "code=");
829 if(s != NULL){
830 t = strchr(s, '&');
831 if(t) *t = '\0';
832 code = cpystr(s+5);
833 if(t) *t = '&';
836 else code = token[0] ? cpystr(token) : NULL;
840 return code;
843 void mm_login_oauth2(NETMBX *, char *, char *, OAUTH2_S *, long int, char *, char *);
845 /* The purpose of this function is to report to c-client the values of the
846 * different tokens and codes so that c-client can try to log in the user
847 * to the server. This function DOES NOT attempt to get these values for
848 * the user. That is attempted in the c-client side (as best as it can be
849 * done given our circumstances: no http support, no javascript support,
850 * etc.). This is the best we can do:
852 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
853 * as best as we can. Unloaded means that there is no server known to
854 * connect, no access token, etc. Pretty much nothing is known about
855 * how to get access code, access token, etc. We ask the user to fill
856 * it up for us, if they can. If the user fills it up we save those
857 * values.
859 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
860 * save the information that c-client got for us.
862 * 3. When saving this information we use the password caching facilities,
863 * but we must do it in a different format so that old information and
864 * new information are not mixed. In order to accommodate this for new
865 * authentication methods, we save the information in the same fields,
866 * but this time we modify it slightly, so that old functions fail to
867 * understand the new information and so not modify it nor use it. The
868 * modification is simple: Every piece of information that was saved
869 * before is prepended XOAUTH2\001 to indicate the authentication
870 * method for which it works. The character \001 is a separator. New
871 * Alpine will know how to deal with this, but old versions, will not
872 * strip this prefix from the information and fail to get the
873 * information or modify it when needed. Only new versions of Alpine will
874 * know how to process this information.
875 * new_value = authenticator_method separator old_value
876 * authenticator_method = "XOAUTH2_S"
877 * separator = "U+1"
878 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
879 * authenticator is "XOAUTH2\001imap.gmail.com".
880 * In addition, the password field is not used to encode the password
881 * anymore, it is used to save login information needed in a format that
882 * the caller function chooses, but that must be preceded by the
883 * "authenticator_method separator" code as above.
885 void
886 mm_login_oauth2(NETMBX *mb, char *user, char *method,
887 OAUTH2_S *login, long int trial,
888 char *usethisprompt, char *altuserforcache)
890 char *token, tmp[MAILTMPLEN];
891 char prompt[4*MAILTMPLEN], value[4*MAILTMPLEN], *last;
892 char defuser[NETMAXUSER];
893 char hostleadin[80], hostname[200], defubuf[200];
894 char logleadin[80], pwleadin[50];
895 char *url_oauth2;
896 char *tool = NULL;
897 char *OldRefreshToken, *OldAccessToken;
898 char *NewRefreshToken, *NewAccessToken;
899 char *SaveRefreshToken, *SaveAccessToken;
900 /* TRANSLATORS: A label for the hostname that the user is logging in on */
901 char *hostlabel = _("HOST");
902 /* TRANSLATORS: user is logging in as a particular user (a particular
903 login name), this is just labelling that user name. */
904 char *userlabel = _("USER");
905 STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1];
906 HelpType help ;
907 int len, rc, q_line, flags, i, j;
908 int oespace, avail, need, save_dont_use;
909 int save_in_init;
910 int registered;
911 int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
912 OAUTH2_S *oa2list, *oa2;
913 XOAUTH2_INFO_S *x;
915 unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
916 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
917 int preserve_password = -1;
918 #endif
920 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
921 trial, mb->user ? mb->user : "(null)",
922 mb->service ? mb->service : "(null)",
923 mb->port ? " port=" : "",
924 mb->port ? comatose(mb->port) : "",
925 altuserforcache ? " altuserforcache =" : "",
926 altuserforcache ? altuserforcache : ""));
928 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
930 save_in_init = ps_global->in_init_seq;
931 ps_global->in_init_seq = 0;
932 ps_global->no_newmail_check_from_optionally_enter = 1;
934 /* make sure errors are seen */
935 if(ps_global->ttyo && !ps_global->noshow_error)
936 flush_status_messages(0);
938 token = NULL; /* start from scratch */
940 hostlist[0].name = mb->host;
941 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
942 hostlist[0].next = &hostlist[1];
943 hostlist[1].name = mb->orighost;
944 hostlist[1].next = NULL;
946 else
947 hostlist[0].next = NULL;
949 if(hostlist[0].name){
950 dprint((9, "mm_login_oauth2: host=%s\n",
951 hostlist[0].name ? hostlist[0].name : "?"));
952 if(hostlist[0].next && hostlist[1].name){
953 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name));
957 if(trial == 0L && !altuserforcache){
958 if(*mb->user != '\0')
959 strncpy(user, mb->user, NETMAXUSER);
960 else{
961 flags = OE_APPEND_CURRENT;
962 sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
963 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
964 prompt, NULL, NO_HELP, &flags);
966 user[NETMAXUSER-1] = '\0';
970 * We check to see if the server we are going to log in to is already
971 * registered. This gives us a list of servers with the same
972 * credentials, so we use the same credentials for all of them.
975 for(registered = 0, oa2list = alpine_oauth2_list;
976 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
977 oa2list++){
978 for(i = 0; i < OAUTH2_TOT_EQUIV
979 && oa2list->host[i] != NULL
980 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
981 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
982 registered++;
983 break;
987 if(registered){
988 x = oauth2_get_client_info(oa2list->name, user);
989 if(x && x->flow){
990 for(oa2list = alpine_oauth2_list;
991 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
992 oa2list++){
993 for(i = 0; i < OAUTH2_TOT_EQUIV
994 && oa2list->host[i] != NULL
995 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
996 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
997 char *flow = oa2list->server_mthd[0].name ? "Authorize"
998 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
999 if(!strucmp(x->flow, flow)) break; /* found it */
1003 /* else use the one we found earlier, the user has to configure this better */
1006 if(registered){
1007 hostlist2[i = 0].name = mb->host;
1008 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
1009 hostlist2[++i].name = mb->orighost;
1011 for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){
1012 int k;
1013 for(k = 0; k <= i && hostlist2[k].name
1014 && strcmp(hostlist2[k].name, oa2list->host[j]); k++);
1015 if(k == i + 1)
1016 hostlist2[++i].name = oa2list->host[j];
1018 hostlist2[i+1].name = NULL;
1019 hostlist2[i+1].next = NULL;
1020 for(j = i; j >= 0; j--)
1021 hostlist2[j].next = &hostlist2[j+1];
1024 if(registered){ /* redo the app_id, no questions asked */
1025 free_id(&ps_global->id);
1026 ps_global->id = set_alpine_id(oa2list->app_id ? oa2list->app_id : PACKAGE_NAME, PACKAGE_VERSION);
1027 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
1031 * We check if we have a refresh token saved somewhere, if so
1032 * we use it to get a new access token, otherwise we need to
1033 * get an access code so we can get (and save) a refresh token
1034 * and use the access token.
1036 if(trial == 0L && !altuserforcache){
1037 /* Search for a refresh token that is already loaded ... */
1038 if(imap_get_passwd_auth(mm_login_list, &token, user,
1039 registered ? hostlist2 : hostlist,
1040 (mb->sslflag||mb->tlsflag), OA2NAME)){
1041 dprint((9, "mm_login_oauth2: found a refresh token\n"));
1042 ps_global->no_newmail_check_from_optionally_enter = 0;
1043 ps_global->in_init_seq = save_in_init;
1045 #ifdef LOCAL_PASSWD_CACHE
1046 /* or see if we have saved one in the local password cache and load it */
1047 else if(get_passfile_passwd_auth(ps_global->pinerc, &token,
1048 user, registered ? hostlist2 : hostlist,
1049 (mb->sslflag||mb->tlsflag), OA2NAME)){
1050 imap_set_passwd_auth(&mm_login_list, token, user,
1051 hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1052 update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist,
1053 (mb->sslflag||mb->tlsflag), OA2NAME);
1054 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
1055 ps_global->no_newmail_check_from_optionally_enter = 0;
1056 ps_global->in_init_seq = save_in_init;
1058 if(token && *token) preserve_password = 1; /* resave it, no need to ask */
1059 #endif /* LOCAL_PASSWD_CACHE */
1061 user[NETMAXUSER-1] = '\0';
1063 /* The Old* variables is what c_client knows */
1064 OldRefreshToken = login->cancel_refresh_token ? NULL : login->param[OA2_RefreshToken].value;
1065 OldAccessToken = login->access_token;
1066 OldExpirationTime = login->expiration;
1068 /* The New* variables is what Alpine knows */
1069 NewRefreshToken = NewAccessToken = NULL;
1070 NewExpirationTime = 0L;
1071 ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0;
1073 if(token && *token && !login->cancel_refresh_token){
1074 char *s, *t;
1076 s = token;
1077 t = strchr(s, PWDAUTHSEP);
1078 if(t == NULL)
1079 NewRefreshToken = cpystr(s);
1080 else {
1081 *t++ = '\0';
1082 NewRefreshToken = cpystr(s);
1083 s = t;
1084 t = strchr(s, PWDAUTHSEP);
1085 if(t == NULL)
1086 NewAccessToken = cpystr(s);
1087 else {
1088 *t++ = '\0';
1089 NewAccessToken = cpystr(s);
1090 s = t;
1091 NewExpirationTime = strtol(s, &s, 10);
1092 if(NewExpirationTime <= 0 || NewExpirationTime <= time(0))
1093 NewExpirationTime = 0L;
1096 /* check we got good information, and send good information below */
1097 if(NewRefreshToken && !*NewRefreshToken)
1098 fs_give((void **) &NewRefreshToken);
1099 if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken))
1100 fs_give((void **) &NewAccessToken);
1103 if(NewRefreshToken == NULL)
1104 login->first_time++;
1106 if(login->first_time){ /* count how many authorization methods we support */
1107 int nmethods, i, j;
1109 for(nmethods = 0, oa2 = alpine_oauth2_list; oa2 && oa2->name ; oa2++){
1110 for(j = 0; j < OAUTH2_TOT_EQUIV
1111 && oa2
1112 && oa2->host[j] != NULL
1113 && strucmp(oa2->host[j], mb->orighost) != 0; j++);
1114 if(oa2 && oa2->host && j < OAUTH2_TOT_EQUIV && oa2->host[j])
1115 nmethods++;
1118 if(nmethods > 1)
1119 oa2list = oauth2_select_flow(mb->orighost);
1121 if(!oa2list) registered = 0;
1124 /* Default to saving what we already had saved */
1126 SaveRefreshToken = login->cancel_refresh_token ? NULL : NewRefreshToken;
1127 SaveAccessToken = NewAccessToken;
1128 SaveExpirationTime = NewExpirationTime;
1130 /* Translation of the logic below:
1131 * if (c-client has a refresh token
1132 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1133 forget the Alpine refresh token;
1134 make the Alpine Refresh token = c-client refresh token.;
1135 signal that we changed the refresh token;
1136 In this situation we do not need to clear up the Alpine access token. This will
1137 expire, so we can use it until it expires. We can save the c-client refresh token
1138 together with the Alpine Access Token and the expiration date of the Alpine Access
1139 Token.
1140 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1141 forget the Alpine refresh token;
1142 if Alpine has an access token, forget it;
1143 reset the expiration time
1144 signal that we changed the refresh token; (because the service expired it)
1148 if(OldRefreshToken != NULL
1149 && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){
1150 if(NewRefreshToken) fs_give((void **) &NewRefreshToken);
1151 NewRefreshToken = cpystr(OldRefreshToken);
1152 ChangeRefreshToken++;
1153 SaveRefreshToken = OldRefreshToken;
1154 SaveAccessToken = NewAccessToken;
1155 SaveExpirationTime = NewExpirationTime;
1156 } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){
1157 fs_give((void **) &NewRefreshToken);
1158 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1159 NewExpirationTime = 0L;
1160 ChangeRefreshToken++;
1161 SaveRefreshToken = NULL;
1162 SaveAccessToken = NULL;
1163 SaveExpirationTime = 0L;
1166 if(OldAccessToken != NULL
1167 && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){
1168 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1169 NewAccessToken = cpystr(OldAccessToken);
1170 ChangeAccessToken++;
1171 NewExpirationTime = OldExpirationTime;
1172 SaveRefreshToken = NewRefreshToken;
1173 SaveAccessToken = NewAccessToken;
1174 SaveExpirationTime = NewExpirationTime;
1177 if(!registered){
1178 login->param[OA2_RefreshToken].value = SaveRefreshToken;
1179 login->access_token = SaveAccessToken;
1180 login->expiration = SaveExpirationTime;
1181 } else {
1182 oa2list->param[OA2_RefreshToken].value = SaveRefreshToken;
1183 oa2list->access_token = SaveAccessToken;
1184 oa2list->expiration = SaveExpirationTime;
1185 oa2list->first_time = login->first_time;
1186 oa2list->cancel_refresh_token = login->cancel_refresh_token;
1187 *login = *oa2list; /* load login pointer */
1190 if(!ChangeAccessToken && !ChangeRefreshToken && !login->cancel_refresh_token)
1191 return;
1193 /* get ready to save this information. The format will be
1194 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1195 * (spaces added for clarity, \001 is PWDAUTHSEP)
1197 if(token) fs_give((void **) &token);
1198 sprintf(tmp, "%lu", SaveExpirationTime);
1199 tmp[sizeof(tmp) - 1] = '\0';
1200 len = strlen(SaveRefreshToken ? SaveRefreshToken : "")
1201 + strlen(SaveAccessToken ? SaveAccessToken : "")
1202 + strlen(tmp) + 2;
1203 token = fs_get(len + 1);
1204 sprintf(token, "%s%c%s%c%lu",
1205 SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP,
1206 SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP,
1207 SaveExpirationTime);
1209 /* remember the access information for next time */
1210 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
1211 imap_set_passwd_auth(&mm_login_list, token,
1212 altuserforcache ? altuserforcache : user, hostlist,
1213 (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1214 #ifdef LOCAL_PASSWD_CACHE
1215 /* if requested, remember it on disk for next session */
1216 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
1217 set_passfile_passwd_auth(ps_global->pinerc, token,
1218 altuserforcache ? altuserforcache : user, hostlist,
1219 (mb->sslflag||mb->tlsflag),
1220 (preserve_password == -1 ? 0
1221 : (preserve_password == 0 ? 2 :1)), OA2NAME);
1222 #endif /* LOCAL_PASSWD_CACHE */
1224 ps_global->no_newmail_check_from_optionally_enter = 0;
1227 IDLIST *
1228 set_alpine_id(unsigned char *pname, unsigned char *pversion)
1230 IDLIST *id;
1232 if(!pname || !pversion) return NULL;
1234 id = fs_get(sizeof(IDLIST));
1235 id->name = cpystr("name");
1236 id->value = cpystr(pname);
1237 id->next = fs_get(sizeof(IDLIST));
1238 id->next->name = cpystr("version");
1239 id->next->value = cpystr(pversion);
1240 id->next->next = NULL;
1241 return id;
1244 /*----------------------------------------------------------------------
1245 receive notification from IMAP
1247 Args: stream -- Mail stream message is relevant to
1248 string -- The message text
1249 errflg -- Set if it is a serious error
1251 Result: message displayed in status line
1253 The facility is for general notices, such as connection to server;
1254 server shutting down etc... It is used infrequently.
1255 ----------------------------------------------------------------------*/
1256 void
1257 mm_notify(MAILSTREAM *stream, char *string, long int errflg)
1259 time_t now;
1260 struct tm *tm_now;
1262 now = time((time_t *)0);
1263 tm_now = localtime(&now);
1265 /* be sure to log the message... */
1266 #ifdef DEBUG
1267 if(ps_global->debug_imap || ps_global->debugmem)
1268 dprint((errflg == TCPDEBUG || errflg == HTTPDEBUG ? 7 : 2,
1269 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1270 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1271 tm_now->tm_mon+1, tm_now->tm_mday,
1272 (!errflg) ? "babble" :
1273 (errflg == ERROR) ? "error" :
1274 (errflg == WARN) ? "warning" :
1275 (errflg == PARSE) ? "parse" :
1276 (errflg == TCPDEBUG) ? "tcp" :
1277 (errflg == HTTPDEBUG) ? "http" :
1278 (errflg == BYE) ? "bye" : "unknown",
1279 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1280 string ? string : "?"));
1281 #endif
1283 snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s",
1284 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1285 (int) MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70),
1286 string);
1287 ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols
1288 : sizeof(ps_global->last_error)-1] = '\0';
1291 * Then either set special bits in the pine struct or
1292 * display the message if it's tagged as an "ALERT" or
1293 * its errflg > NIL (i.e., WARN, or ERROR)
1295 if(errflg == BYE)
1297 * We'd like to sp_mark_stream_dead() here but we can't do that because
1298 * that might call mail_close and we are already in a c-client callback.
1299 * So just set the dead bit and clean it up later.
1301 sp_set_dead_stream(stream, 1);
1302 else if(!strncmp(string, "[TRYCREATE]", 11))
1303 ps_global->try_to_create = 1;
1304 else if(!strncmp(string, "[REFERRAL ", 10))
1305 ; /* handled in the imap_referral() callback */
1306 else if(!strncmp(string, "[ALERT]", 7))
1307 q_status_message2(SM_MODAL, 3, 3,
1308 _("Alert received while accessing \"%s\": %s"),
1309 (stream && stream->mailbox)
1310 ? stream->mailbox : "-no folder-",
1311 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000),
1312 SIZEOF_20KBUF-10000, string));
1313 else if(!strncmp(string, "[UNSEEN ", 8)){
1314 char *p;
1315 long n = 0;
1317 for(p = string + 8; isdigit(*p); p++)
1318 n = (n * 10) + (*p - '0');
1320 sp_set_first_unseen(stream, n);
1322 else if(!strncmp(string, "[READ-ONLY]", 11)
1323 && !(stream && stream->mailbox && IS_NEWS(stream)))
1324 q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s",
1325 (stream && stream->mailbox)
1326 ? stream->mailbox : "-no folder-",
1327 string + 11);
1328 else if((errflg && errflg != BYE && errflg != PARSE)
1329 && !ps_global->noshow_error
1330 && !(errflg == WARN
1331 && (ps_global->noshow_warn || (stream && stream->unhealthy))))
1332 q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0),
1333 3, 6, ps_global->last_error);
1337 /*----------------------------------------------------------------------
1338 Queue imap log message for display in the message line
1340 Args: string -- The message
1341 errflg -- flag set to 1 if pertains to an error
1343 Result: Message queued for display
1345 The c-client/imap reports most of it's status and errors here
1346 ---*/
1347 void
1348 mm_log(char *string, long int errflg)
1350 char message[sizeof(ps_global->c_client_error)];
1351 char *occurence;
1352 int was_capitalized;
1353 static char saw_kerberos_init_warning;
1354 time_t now;
1355 struct tm *tm_now;
1357 now = time((time_t *)0);
1358 tm_now = localtime(&now);
1360 dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? 1 :
1361 (errflg == TCPDEBUG) ? 10 :
1362 ((errflg == HTTPDEBUG) && ps_global->debug_http) ? 1 :
1363 (errflg == HTTPDEBUG) ? 10 : 2,
1364 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1365 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1366 tm_now->tm_mon+1, tm_now->tm_mday,
1367 (!errflg) ? "babble" :
1368 (errflg == ERROR) ? "error" :
1369 (errflg == WARN) ? "warning" :
1370 (errflg == PARSE) ? "parse" :
1371 (errflg == TCPDEBUG) ? "tcp" :
1372 (errflg == HTTPDEBUG) ? "http" :
1373 (errflg == BYE) ? "bye" : "unknown",
1374 string ? string : "?"));
1376 if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){
1377 ps_global->try_to_create = 1;
1378 return;
1380 else if(ps_global->try_to_create
1381 || !strncmp(string, "[CLOSED]", 8)
1382 || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op")))
1384 * Don't display if creating new folder OR
1385 * warning about a dead stream ...
1387 return;
1389 strncpy(message, string, sizeof(message));
1390 message[sizeof(message) - 1] = '\0';
1392 if(errflg == WARN && srchstr(message, "try running kinit") != NULL){
1393 if(saw_kerberos_init_warning)
1394 return;
1396 saw_kerberos_init_warning = 1;
1399 /*---- replace all "mailbox" with "folder" ------*/
1400 occurence = srchstr(message, "mailbox");
1401 while(occurence) {
1402 if(!*(occurence+7)
1403 || isspace((unsigned char) *(occurence+7))
1404 || *(occurence+7) == ':'){
1405 was_capitalized = isupper((unsigned char) *occurence);
1406 rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder"));
1407 if(was_capitalized)
1408 *occurence = (errflg == PARSE ? 'A' : 'F');
1410 else
1411 occurence += 7;
1413 occurence = srchstr(occurence, "mailbox");
1416 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1417 occurence = srchstr(message, "GSSAPI");
1418 while(occurence) {
1419 if(!*(occurence+6)
1420 || isspace((unsigned char) *(occurence+6))
1421 || *(occurence+6) == ':')
1422 rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos");
1423 else
1424 occurence += 6;
1426 occurence = srchstr(occurence, "GSSAPI");
1429 if(errflg == ERROR)
1430 ps_global->mm_log_error = 1;
1432 if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error))
1433 strncpy(ps_global->c_client_error, message,
1434 sizeof(ps_global->c_client_error));
1436 if(ps_global->noshow_error
1437 || (ps_global->noshow_warn && errflg == WARN)
1438 || !(errflg == ERROR || errflg == WARN))
1439 return; /* Only care about errors; don't print when asked not to */
1441 /*---- Display the message ------*/
1442 q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER,
1443 3, 5, message);
1444 strncpy(ps_global->last_error, message, sizeof(ps_global->last_error));
1445 ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
1448 void
1449 mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial,
1450 char *method, char *usethisprompt, char *altuserforcache)
1452 if(method == NULL)
1453 return;
1454 if(strucmp(method, OA2NAME) == 0 || strucmp(method, BEARERNAME) == 0)
1455 mm_login_oauth2(mb, user, method, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache);
1458 void
1459 mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial,
1460 char *usethisprompt, char *altuserforcache)
1462 char tmp[MAILTMPLEN];
1463 char prompt[1000], *last;
1464 char port[20], non_def_port[20], insecure[20];
1465 char defuser[NETMAXUSER];
1466 char hostleadin[80], hostname[200], defubuf[200];
1467 char logleadin[80], pwleadin[50];
1468 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
1469 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1470 that the password will be sent unencrypted over the network. This is
1471 just a warning message that gets added parenthetically when the user
1472 is asked for a password. */
1473 char *insec = _(" (INSECURE)");
1474 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1475 after having already failed at least once. */
1476 char *retry = _("Retrying - ");
1477 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1478 char *hostlabel = _("HOST");
1479 /* TRANSLATORS: user is logging in as a particular user (a particular
1480 login name), this is just labelling that user name. */
1481 char *userlabel = _("USER");
1482 STRLIST_S hostlist[2];
1483 HelpType help ;
1484 int len, rc, q_line, flags;
1485 int oespace, avail, need, save_dont_use;
1486 int save_in_init;
1487 struct servent *sv;
1488 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1489 int preserve_password = -1;
1490 #endif
1492 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1493 trial, mb->user ? mb->user : "(null)",
1494 mb->service ? mb->service : "(null)",
1495 mb->port ? " port=" : "",
1496 mb->port ? comatose(mb->port) : "",
1497 altuserforcache ? " altuserforcache =" : "",
1498 altuserforcache ? altuserforcache : ""));
1499 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
1501 save_in_init = ps_global->in_init_seq;
1502 ps_global->in_init_seq = 0;
1503 ps_global->no_newmail_check_from_optionally_enter = 1;
1505 /* make sure errors are seen */
1506 if(ps_global->ttyo)
1507 flush_status_messages(0);
1509 /* redo app id in case we are loging in to an IMAP server that supports the IMAP ID extension */
1510 free_id(&ps_global->id);
1511 ps_global->id = set_alpine_id(PACKAGE_NAME, PACKAGE_VERSION);
1512 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
1515 * Add port number to hostname if going through a tunnel or something
1517 non_def_port[0] = '\0';
1518 if(mb->port && mb->service &&
1519 (sv = getservbyname(mb->service, "tcp")) &&
1520 (mb->port != ntohs(sv->s_port))){
1521 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
1522 non_def_port[sizeof(non_def_port)-1] = '\0';
1523 dprint((9, "mm_login: using non-default port=%s\n",
1524 non_def_port ? non_def_port : "?"));
1528 * set up host list for sybil servers...
1530 if(*non_def_port){
1531 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
1532 hostlist0[sizeof(hostlist0)-1] = '\0';
1533 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
1534 hostlist0[sizeof(hostlist0)-1] = '\0';
1535 hostlist[0].name = hostlist0;
1536 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1537 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
1538 hostlist1[sizeof(hostlist1)-1] = '\0';
1539 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
1540 hostlist1[sizeof(hostlist1)-1] = '\0';
1541 hostlist[0].next = &hostlist[1];
1542 hostlist[1].name = hostlist1;
1543 hostlist[1].next = NULL;
1545 else
1546 hostlist[0].next = NULL;
1548 else{
1549 hostlist[0].name = mb->host;
1550 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1551 hostlist[0].next = &hostlist[1];
1552 hostlist[1].name = mb->orighost;
1553 hostlist[1].next = NULL;
1555 else
1556 hostlist[0].next = NULL;
1559 if(hostlist[0].name){
1560 dprint((9, "mm_login: host=%s\n",
1561 hostlist[0].name ? hostlist[0].name : "?"));
1562 if(hostlist[0].next && hostlist[1].name){
1563 dprint((9, "mm_login: orighost=%s\n", hostlist[1].name));
1568 * Initialize user name with either
1569 * 1) /user= value in the stream being logged into,
1570 * or 2) the user name we're running under.
1572 * Note that VAR_USER_ID is not yet initialized if this login is
1573 * the one to access the remote config file. In that case, the user
1574 * can supply the username in the config file name with /user=.
1576 if(trial == 0L && !altuserforcache){
1577 strncpy(user, (*mb->user) ? mb->user :
1578 ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "",
1579 NETMAXUSER);
1580 user[NETMAXUSER-1] = '\0';
1582 /* try last working password associated with this host. */
1583 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1584 (mb->sslflag||mb->tlsflag))){
1585 dprint((9, "mm_login: found a password to try\n"));
1586 ps_global->no_newmail_check_from_optionally_enter = 0;
1587 ps_global->in_init_seq = save_in_init;
1588 return;
1591 #ifdef LOCAL_PASSWD_CACHE
1592 /* check to see if there's a password left over from last session */
1593 if(get_passfile_passwd(ps_global->pinerc, pwd,
1594 user, hostlist, (mb->sslflag||mb->tlsflag))){
1595 imap_set_passwd(&mm_login_list, *pwd, user,
1596 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1597 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1598 (mb->sslflag||mb->tlsflag));
1599 dprint((9, "mm_login: found a password in passfile to try\n"));
1600 ps_global->no_newmail_check_from_optionally_enter = 0;
1601 ps_global->in_init_seq = save_in_init;
1602 return;
1604 #endif /* LOCAL_PASSWD_CACHE */
1607 * If no explicit user name supplied and we've not logged in
1608 * with our local user name, see if we've visited this
1609 * host before as someone else.
1611 if(!*mb->user &&
1612 ((last = imap_get_user(mm_login_list, hostlist))
1613 #ifdef LOCAL_PASSWD_CACHE
1615 (last = get_passfile_user(ps_global->pinerc, hostlist))
1616 #endif /* LOCAL_PASSWD_CACHE */
1618 strncpy(user, last, NETMAXUSER);
1619 user[NETMAXUSER-1] = '\0';
1620 dprint((9, "mm_login: found user=%s\n",
1621 user ? user : "?"));
1623 /* try last working password associated with this host/user. */
1624 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1625 (mb->sslflag||mb->tlsflag))){
1626 dprint((9,
1627 "mm_login: found a password for user=%s to try\n",
1628 user ? user : "?"));
1629 ps_global->no_newmail_check_from_optionally_enter = 0;
1630 ps_global->in_init_seq = save_in_init;
1631 return;
1634 #ifdef LOCAL_PASSWD_CACHE
1635 /* check to see if there's a password left over from last session */
1636 if(get_passfile_passwd(ps_global->pinerc, pwd,
1637 user, hostlist, (mb->sslflag||mb->tlsflag))){
1638 imap_set_passwd(&mm_login_list, *pwd, user,
1639 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1640 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1641 (mb->sslflag||mb->tlsflag));
1642 dprint((9,
1643 "mm_login: found a password for user=%s in passfile to try\n",
1644 user ? user : "?"));
1645 ps_global->no_newmail_check_from_optionally_enter = 0;
1646 ps_global->in_init_seq = save_in_init;
1647 return;
1649 #endif /* LOCAL_PASSWD_CACHE */
1652 #if !defined(DOS) && !defined(OS2)
1653 if(!*mb->user && !*user &&
1654 (last = (ps_global->ui.login && ps_global->ui.login[0])
1655 ? ps_global->ui.login : NULL)
1657 strncpy(user, last, NETMAXUSER);
1658 user[NETMAXUSER-1] = '\0';
1659 dprint((9, "mm_login: found user=%s\n",
1660 user ? user : "?"));
1662 /* try last working password associated with this host. */
1663 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1664 (mb->sslflag||mb->tlsflag))){
1665 dprint((9, "mm_login:ui: found a password to try\n"));
1666 ps_global->no_newmail_check_from_optionally_enter = 0;
1667 ps_global->in_init_seq = save_in_init;
1668 return;
1671 #ifdef LOCAL_PASSWD_CACHE
1672 /* check to see if there's a password left over from last session */
1673 if(get_passfile_passwd(ps_global->pinerc, pwd,
1674 user, hostlist, (mb->sslflag||mb->tlsflag))){
1675 imap_set_passwd(&mm_login_list, *pwd, user,
1676 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1677 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1678 (mb->sslflag||mb->tlsflag));
1679 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1680 ps_global->no_newmail_check_from_optionally_enter = 0;
1681 ps_global->in_init_seq = save_in_init;
1682 return;
1684 #endif /* LOCAL_PASSWD_CACHE */
1686 #endif
1689 user[NETMAXUSER-1] = '\0';
1691 if(trial == 0)
1692 retry = "";
1695 * Even if we have a user now, user gets a chance to change it.
1697 ps_global->mangled_footer = 1;
1698 if(!*mb->user && !altuserforcache){
1700 help = NO_HELP;
1703 * Instead of offering user with a value that the user can edit,
1704 * we offer [user] as a default so that the user can type CR to
1705 * use it. Otherwise, the user has to type in whole name.
1707 strncpy(defuser, user, sizeof(defuser)-1);
1708 defuser[sizeof(defuser)-1] = '\0';
1709 user[0] = '\0';
1712 * Need space for "Retrying - "
1713 * "+ HOST: "
1714 * hostname
1715 * " (INSECURE)"
1716 * ENTER LOGIN NAME
1717 * " [defuser] : "
1718 * about 15 chars for input
1721 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1722 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1723 hostleadin[sizeof(hostleadin)-1] = '\0';
1725 strncpy(hostname, mb->host, sizeof(hostname)-1);
1726 hostname[sizeof(hostname)-1] = '\0';
1729 * Add port number to hostname if going through a tunnel or something
1731 if(*non_def_port)
1732 strncpy(port, non_def_port, sizeof(port));
1733 else
1734 port[0] = '\0';
1736 insecure[0] = '\0';
1737 /* if not encrypted and SSL/TLS is supported */
1738 if(!(mb->sslflag||mb->tlsflag) &&
1739 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1740 strncpy(insecure, insec, sizeof(insecure));
1742 /* TRANSLATORS: user is being asked to type in their login name */
1743 snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME"));
1745 snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "",
1746 (*defuser) ? defuser : "",
1747 (*defuser) ? "]" : "");
1748 defubuf[sizeof(defubuf)-1] = '\0';
1749 /* space reserved after prompt */
1750 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1752 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1753 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1754 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace;
1756 /* If we're retrying cut the hostname back to the first word. */
1757 if(avail < need && trial > 0){
1758 char *p;
1760 len = strlen(hostname);
1761 if((p = strchr(hostname, '.')) != NULL){
1762 *p = '\0';
1763 need -= (len - strlen(hostname));
1767 if(avail < need){
1768 need -= utf8_width(retry);
1769 retry = "";
1771 if(avail < need){
1773 /* reduce length of logleadin */
1774 len = utf8_width(logleadin);
1775 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1776 longer version doesn't fit on screen */
1777 snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN"));
1778 need -= (len - utf8_width(logleadin));
1780 if(avail < need){
1781 /* get two spaces from hostleadin */
1782 len = utf8_width(hostleadin);
1783 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1784 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1785 hostleadin[sizeof(hostleadin)-1] = '\0';
1786 need -= (len - utf8_width(hostleadin));
1788 /* get rid of port */
1789 if(avail < need && strlen(port) > 0){
1790 need -= strlen(port);
1791 port[0] = '\0';
1794 if(avail < need){
1795 int reduce_to;
1798 * Reduce space for hostname. Best we can do is 6 chars
1799 * with hos...
1801 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1802 len = strlen(hostname);
1803 strncpy(hostname+reduce_to-3, "...", 4);
1804 need -= (len - strlen(hostname));
1806 if(avail < need && strlen(insecure) > 0){
1807 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1808 need -= 3;
1809 insecure[strlen(insecure)-4] = ')';
1810 insecure[strlen(insecure)-3] = '\0';
1812 else{
1813 need -= utf8_width(insecure);
1814 insecure[0] = '\0';
1818 if(avail < need){
1819 if(strlen(defubuf) > 3){
1820 len = strlen(defubuf);
1821 strncpy(defubuf, " [..] :", 9);
1822 need -= (len - strlen(defubuf));
1825 if(avail < need)
1826 strncpy(defubuf, ":", 2);
1829 * If it still doesn't fit, optionally_enter gets
1830 * to worry about it.
1838 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s",
1839 retry, hostleadin, hostname, port, insecure, logleadin, defubuf);
1840 prompt[sizeof(prompt)-1] = '\0';
1842 while(1) {
1843 if(ps_global->ttyo)
1844 mm_login_alt_cue(mb);
1846 flags = OE_APPEND_CURRENT;
1847 save_dont_use = ps_global->dont_use_init_cmds;
1848 ps_global->dont_use_init_cmds = 1;
1849 #ifdef _WINDOWS
1850 if(!*user && *defuser){
1851 strncpy(user, defuser, NETMAXUSER);
1852 user[NETMAXUSER-1] = '\0';
1855 rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD,
1856 #ifdef LOCAL_PASSWD_CACHE
1857 is_using_passfile() ? 1 :
1858 #endif /* LOCAL_PASSWD_CACHE */
1859 0, 0, &preserve_password);
1860 ps_global->dont_use_init_cmds = save_dont_use;
1861 if(rc == 0 && *user && *pwd)
1862 goto nopwpmt;
1863 #else /* !_WINDOWS */
1864 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
1865 prompt, NULL, help, &flags);
1866 #endif /* !_WINDOWS */
1867 ps_global->dont_use_init_cmds = save_dont_use;
1869 if(rc == 3) {
1870 help = help == NO_HELP ? h_oe_login : NO_HELP;
1871 continue;
1874 /* default */
1875 if(rc == 0 && !*user){
1876 strncpy(user, defuser, NETMAXUSER);
1877 user[NETMAXUSER-1] = '\0';
1880 if(rc != 4)
1881 break;
1884 if(rc == 1 || !user[0]) {
1885 ps_global->user_says_cancel = (rc == 1);
1886 user[0] = '\0';
1889 else{
1890 strncpy(user, mb->user, NETMAXUSER);
1891 user[NETMAXUSER-1] = '\0';
1894 user[NETMAXUSER-1] = '\0';
1896 if(!(user[0] || altuserforcache)){
1897 ps_global->no_newmail_check_from_optionally_enter = 0;
1898 ps_global->in_init_seq = save_in_init;
1899 return;
1903 * Now that we have a user, we can check in the cache again to see
1904 * if there is a password there. Try last working password associated
1905 * with this host and user.
1907 if(trial == 0L && !*mb->user && !altuserforcache){
1908 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1909 (mb->sslflag||mb->tlsflag))){
1910 ps_global->no_newmail_check_from_optionally_enter = 0;
1911 ps_global->in_init_seq = save_in_init;
1912 return;
1915 #ifdef LOCAL_PASSWD_CACHE
1916 if(get_passfile_passwd(ps_global->pinerc, pwd,
1917 user, hostlist, (mb->sslflag||mb->tlsflag))){
1918 imap_set_passwd(&mm_login_list, *pwd, user,
1919 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1920 ps_global->no_newmail_check_from_optionally_enter = 0;
1921 ps_global->in_init_seq = save_in_init;
1922 return;
1924 #endif /* LOCAL_PASSWD_CACHE */
1926 else if(trial == 0 && altuserforcache){
1927 if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist,
1928 (mb->sslflag||mb->tlsflag))){
1929 ps_global->no_newmail_check_from_optionally_enter = 0;
1930 ps_global->in_init_seq = save_in_init;
1931 return;
1934 #ifdef LOCAL_PASSWD_CACHE
1935 if(get_passfile_passwd(ps_global->pinerc, pwd,
1936 altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
1937 imap_set_passwd(&mm_login_list, *pwd, altuserforcache,
1938 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1939 ps_global->no_newmail_check_from_optionally_enter = 0;
1940 ps_global->in_init_seq = save_in_init;
1941 return;
1943 #endif /* LOCAL_PASSWD_CACHE */
1947 * Didn't find password in cache or this isn't the first try. Ask user.
1949 help = NO_HELP;
1952 * Need space for "Retrying - "
1953 * "+ HOST: "
1954 * hostname
1955 * " (INSECURE) "
1956 * " USER: "
1957 * user
1958 * " ENTER PASSWORD: "
1959 * about 15 chars for input
1962 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1963 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1965 strncpy(hostname, mb->host, sizeof(hostname)-1);
1966 hostname[sizeof(hostname)-1] = '\0';
1969 * Add port number to hostname if going through a tunnel or something
1971 if(*non_def_port)
1972 strncpy(port, non_def_port, sizeof(port));
1973 else
1974 port[0] = '\0';
1976 insecure[0] = '\0';
1978 /* if not encrypted and SSL/TLS is supported */
1979 if(!(mb->sslflag||mb->tlsflag) &&
1980 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1981 strncpy(insecure, insec, sizeof(insecure));
1983 if(usethisprompt){
1984 strncpy(logleadin, usethisprompt, sizeof(logleadin));
1985 logleadin[sizeof(logleadin)-1] = '\0';
1986 defubuf[0] = '\0';
1987 user[0] = '\0';
1989 else{
1990 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1992 strncpy(defubuf, user, sizeof(defubuf)-1);
1993 defubuf[sizeof(defubuf)-1] = '\0';
1996 /* TRANSLATORS: user is being asked to type in their password */
1997 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD"));
1999 /* space reserved after prompt */
2000 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
2002 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2003 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
2004 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) +
2005 utf8_width(pwleadin) + oespace;
2007 if(avail < need && trial > 0){
2008 char *p;
2010 len = strlen(hostname);
2011 if((p = strchr(hostname, '.')) != NULL){
2012 *p = '\0';
2013 need -= (len - strlen(hostname));
2017 if(avail < need){
2018 need -= utf8_width(retry);
2019 retry = "";
2021 if(avail < need){
2023 if(!usethisprompt){
2024 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
2025 need--;
2028 rplstr(pwleadin, sizeof(pwleadin), 1, "");
2029 need--;
2031 if(avail < need){
2032 /* get two spaces from hostleadin */
2033 len = utf8_width(hostleadin);
2034 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
2035 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
2036 hostleadin[sizeof(hostleadin)-1] = '\0';
2037 need -= (len - utf8_width(hostleadin));
2039 /* get rid of port */
2040 if(avail < need && strlen(port) > 0){
2041 need -= strlen(port);
2042 port[0] = '\0';
2045 if(avail < need){
2046 len = utf8_width(pwleadin);
2047 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
2048 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD"));
2049 need -= (len - utf8_width(pwleadin));
2053 if(avail < need){
2054 int reduce_to;
2057 * Reduce space for hostname. Best we can do is 6 chars
2058 * with hos...
2060 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
2061 len = strlen(hostname);
2062 strncpy(hostname+reduce_to-3, "...", 4);
2063 need -= (len - strlen(hostname));
2065 if(avail < need && strlen(insecure) > 0){
2066 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
2067 need -= 3;
2068 insecure[strlen(insecure)-4] = ')';
2069 insecure[strlen(insecure)-3] = '\0';
2071 else{
2072 need -= utf8_width(insecure);
2073 insecure[0] = '\0';
2077 if(avail < need){
2078 len = utf8_width(logleadin);
2079 strncpy(logleadin, " ", sizeof(logleadin));
2080 logleadin[sizeof(logleadin)-1] = '\0';
2081 need -= (len - utf8_width(logleadin));
2083 if(avail < need){
2084 reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0;
2085 if(reduce_to)
2086 strncpy(defubuf+reduce_to-3, "...", 4);
2087 else
2088 defubuf[0] = '\0';
2095 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s",
2096 retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
2097 prompt[sizeof(prompt)-1] = '\0';
2099 tmp[0] = '\0';
2100 while(1) {
2101 if(ps_global->ttyo)
2102 mm_login_alt_cue(mb);
2104 save_dont_use = ps_global->dont_use_init_cmds;
2105 ps_global->dont_use_init_cmds = 1;
2106 flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
2107 flags |= OE_KEEP_TRAILING_SPACE;
2108 #ifdef _WINDOWS
2110 char *tmpp;
2111 tmpp = fs_get(NETMAXPASSWD*sizeof(char));
2112 rc = os_login_dialog(mb, user, NETMAXUSER, &tmpp, NETMAXPASSWD, 0, 1,
2113 &preserve_password);
2114 strncpy(tmp, tmpp, sizeof(tmp));
2115 tmp[sizeof(tmp)-1] = '\0';
2116 if(tmpp) fs_give((void **)&tmpp);
2118 #else /* !_WINDOWS */
2119 rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD,
2120 prompt, NULL, help, &flags);
2121 #endif /* !_WINDOWS */
2122 if(rc != 1) *pwd = cpystr(tmp);
2123 ps_global->dont_use_init_cmds = save_dont_use;
2125 if(rc == 3) {
2126 help = help == NO_HELP ? h_oe_passwd : NO_HELP;
2128 else if(rc == 4){
2130 else
2131 break;
2134 if(rc == 1 || !tmp[0]) {
2135 ps_global->user_says_cancel = (rc == 1);
2136 user[0] = '\0';
2137 ps_global->no_newmail_check_from_optionally_enter = 0;
2138 ps_global->in_init_seq = save_in_init;
2139 return;
2142 #ifdef _WINDOWS
2143 nopwpmt:
2144 #endif
2145 /* remember the password for next time */
2146 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
2147 imap_set_passwd(&mm_login_list, *pwd,
2148 altuserforcache ? altuserforcache : user, hostlist,
2149 (mb->sslflag||mb->tlsflag), 0, 0);
2150 #ifdef LOCAL_PASSWD_CACHE
2151 /* if requested, remember it on disk for next session */
2152 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
2153 set_passfile_passwd(ps_global->pinerc, *pwd,
2154 altuserforcache ? altuserforcache : user, hostlist,
2155 (mb->sslflag||mb->tlsflag),
2156 (preserve_password == -1 ? 0
2157 : (preserve_password == 0 ? 2 :1)));
2158 #endif /* LOCAL_PASSWD_CACHE */
2160 ps_global->no_newmail_check_from_optionally_enter = 0;
2164 void
2165 mm_login_alt_cue(NETMBX *mb)
2167 if(ps_global->ttyo){
2168 COLOR_PAIR *lastc;
2170 lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
2171 ps_global->VAR_TITLE_BACK_COLOR,
2172 PSC_REV | PSC_RET);
2174 mark_titlebar_dirty();
2175 PutLine0(0, ps_global->ttyo->screen_cols - 1,
2176 (mb->sslflag||mb->tlsflag) ? "+" : " ");
2178 if(lastc){
2179 (void)pico_set_colorp(lastc, PSC_NONE);
2180 free_color_pair(&lastc);
2183 fflush(stdout);
2188 /*----------------------------------------------------------------------
2189 Receive notification of an error writing to disk
2191 Args: stream -- The stream the error occurred on
2192 errcode -- The system error code (errno)
2193 serious -- Flag indicating error is serious (mail may be lost)
2195 Result: If error is non serious, the stream is marked as having an error
2196 and deletes are disallowed until error clears
2197 If error is serious this goes modal, allowing the user to retry
2198 or get a shell escape to fix the condition. When the condition is
2199 serious it means that mail existing in the mailbox will be lost
2200 if Pine exits without writing, so we try to induce the user to
2201 fix the error, go get someone that can fix the error, or whatever
2202 and don't provide an easy way out.
2203 ----*/
2204 long
2205 mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious)
2207 int i, j;
2208 char *p, *q, *s;
2209 static ESCKEY_S de_opts[] = {
2210 {'r', 'r', "R", "Retry"},
2211 {'f', 'f', "F", "FileBrowser"},
2212 {'s', 's', "S", "ShellPrompt"},
2213 {-1, 0, NULL, NULL}
2215 #define DE_COLS (ps_global->ttyo->screen_cols)
2216 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2218 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2219 #define DE_PMT \
2220 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2221 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2222 #define DE_STR2 \
2223 "The reported error number is %s. The last reported mail error was:"
2224 static char *de_msg[] = {
2225 "Please try to correct the error preventing Alpine from saving your",
2226 "mail folder. For example if the disk is out of space try removing",
2227 "unneeded files. You might also contact your system administrator.",
2229 "Both Alpine's File Browser and an option to enter the system's",
2230 "command prompt are offered to aid in fixing the problem. When",
2231 "you believe the problem is resolved, choose the \"Retry\" option.",
2232 "Be aware that messages may be lost or this folder left in an",
2233 "inaccessible condition if you exit or kill Alpine before the problem",
2234 "is resolved.",
2235 NULL};
2236 static char *de_shell_msg[] = {
2237 "\n\nPlease attempt to correct the error preventing saving of the",
2238 "mail folder. If you do not know how to correct the problem, contact",
2239 "your system administrator. To return to Alpine, type \"exit\".",
2240 NULL};
2242 dprint((0,
2243 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2244 DE_FOLDER(stream), errcode, serious ? "" : "not "));
2245 dprint((0, "***** message: \"%s\"\n\n",
2246 ps_global->last_error ? ps_global->last_error : "?"));
2248 if(!serious) {
2249 sp_set_io_error_on_stream(stream, 1);
2250 return (1) ;
2253 while(1){
2254 /* replace pine's body display with screen full of explanatory text */
2255 ClearLine(2);
2256 PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1)
2257 - strlen(DE_FOLDER(stream)))/2, 0),
2258 DE_STR1, DE_FOLDER(stream));
2259 ClearLine(3);
2260 PutLine1(3, 4, DE_STR2, long2string(errcode));
2262 PutLine0(4, 0, " \"");
2263 removing_leading_white_space(ps_global->last_error);
2264 for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){
2265 for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++)
2266 if(isspace((unsigned char)*q))
2267 s = q;
2269 if(*q && s)
2270 q = s;
2272 while(p < q)
2273 Writechar(*p++, 0);
2275 if(*(p = q)){
2276 ClearLine(++i);
2277 PutLine0(i, 0, " ");
2278 while(*p && isspace((unsigned char)*p))
2279 p++;
2281 else{
2282 Writechar('\"', 0);
2283 CleartoEOLN();
2284 break;
2288 ClearLine(++i);
2289 for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){
2290 ClearLine(i);
2291 PutLine0(i, 0, " ");
2292 Write_to_screen(de_msg[i-j]);
2295 while(i < DE_LINE)
2296 ClearLine(i++);
2298 switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts,
2299 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){
2300 case 'r' : /* Retry! */
2301 ps_global->mangled_screen = 1;
2302 return(0L);
2304 case 'f' : /* File Browser */
2306 char full_filename[MAXPATH+1], filename[MAXPATH+1];
2308 filename[0] = '\0';
2309 build_path(full_filename, ps_global->home_dir, filename,
2310 sizeof(full_filename));
2311 file_lister("DISK ERROR", full_filename, sizeof(full_filename),
2312 filename, sizeof(filename), FALSE, FB_SAVE);
2315 break;
2317 case 's' :
2318 EndInverse();
2319 end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0);
2320 end_tty_driver(ps_global);
2321 for(i = 0; de_shell_msg[i]; i++)
2322 puts(de_shell_msg[i]);
2325 * Don't use our piping mechanism to spawn a subshell here
2326 * since it will the server (thus reentering c-client).
2327 * Bad thing to do.
2329 #ifdef _WINDOWS
2330 #else
2331 system("csh");
2332 #endif
2333 init_tty_driver(ps_global);
2334 init_keyboard(F_ON(F_USE_FK,ps_global));
2335 break;
2338 if(ps_global->redrawer)
2339 (*ps_global->redrawer)();
2344 long
2345 pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host)
2347 long rv = 1L;
2348 char pmt[128];
2350 #ifdef _WINDOWS
2351 mswin_killsplash();
2352 #endif
2354 if(elapsed >= (long)ps_global->tcp_query_timeout){
2355 snprintf(pmt, sizeof(pmt),
2356 _("No reply in %s seconds from server %s. Break connection"),
2357 long2string(elapsed), host);
2358 pmt[sizeof(pmt)-1] = '\0';
2359 if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
2360 ps_global->user_says_cancel = 1;
2361 return(0L);
2365 ps_global->tcptimeout = 0;
2366 return(rv);
2371 * -------------------------------------------------------------
2372 * These are declared in pith/imap.h as mandatory to implement.
2373 * -------------------------------------------------------------
2378 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2380 long
2381 pine_tcptimeout(long int elapsed, long int sincelast, char *host)
2383 long rv = 1L; /* keep trying by default */
2384 unsigned long ch;
2386 ps_global->tcptimeout = 1;
2387 #ifdef DEBUG
2388 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2389 long2string(elapsed), host));
2390 if(debugfile)
2391 fflush(debugfile);
2392 #endif
2394 #ifdef _WINDOWS
2395 mswin_killsplash();
2396 #endif
2398 if(ps_global->noshow_timeout)
2399 return(rv);
2401 if(ps_global->can_interrupt
2402 && ps_global->close_connection_timeout > 0L
2403 && elapsed >= (long)ps_global->tcp_query_timeout
2404 && elapsed >= (long)ps_global->close_connection_timeout){
2405 ps_global->can_interrupt = 0; /* do not return here */
2406 ps_global->read_bail = 0;
2407 ps_global->user_says_cancel = 1;
2408 return 0;
2411 if(!ps_global->ttyo)
2412 return(pine_tcptimeout_noscreen(elapsed, sincelast, host));
2414 suspend_busy_cue();
2417 * Prompt after a minute (since by then things are probably really bad)
2418 * A prompt timeout means "keep trying"...
2420 if(elapsed >= (long)ps_global->tcp_query_timeout){
2421 int clear_inverse;
2423 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2424 if((clear_inverse = !InverseState()) != 0)
2425 StartInverse();
2427 Writechar(BELL, 0);
2429 PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0,
2430 _("No reply in %s seconds from server %s. Break connection?"),
2431 long2string(elapsed), host);
2432 CleartoEOLN();
2433 fflush(stdout);
2434 flush_input();
2435 ch = read_char(7);
2436 if(ps_global->read_bail || ch == 'y' || ch == 'Y'){
2437 ps_global->read_bail = 0;
2438 ps_global->user_says_cancel = 1;
2439 rv = 0L;
2442 if(clear_inverse)
2443 EndInverse();
2445 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2448 if(rv == 1L){ /* just warn 'em something's up */
2449 q_status_message2(SM_ORDER, 0, 0,
2450 _("No reply in %s seconds from server %s. Still Waiting..."),
2451 long2string(elapsed), host);
2452 flush_status_messages(0); /* make sure it's seen */
2455 mark_status_dirty(); /* make sure it gets cleared */
2457 resume_busy_cue((rv == 1) ? 3 : 0);
2458 ps_global->tcptimeout = 0;
2460 return(rv);
2463 QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota)
2465 QUOTALIST *cquota = NULL;
2467 if(pquota){
2468 cquota = mail_newquotalist();
2469 if (pquota->name && *pquota->name)
2470 cquota->name = cpystr(pquota->name);
2471 cquota->usage = pquota->usage;
2472 cquota->limit = pquota->limit;
2473 if (pquota->next)
2474 cquota->next = pine_quotalist_copy(pquota->next);
2476 return cquota;
2480 /* c-client callback to handle quota */
2482 void
2483 pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2485 ps_global->quota = pine_quotalist_copy (pquota);
2489 * C-client callback to handle SSL/TLS certificate validation failures
2491 * Returning 0 means error becomes fatal
2492 * Non-zero means certificate problem is ignored and SSL session is
2493 * established
2495 * We remember the answer and won't re-ask for subsequent open attempts to
2496 * the same hostname.
2498 long
2499 pine_sslcertquery(char *reason, char *host, char *cert)
2501 char tmp[500];
2502 char *unknown = "<unknown>";
2503 long rv = 0L;
2504 STRLIST_S hostlist;
2505 int ok_novalidate = 0, warned = 0;
2507 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2508 host ? host : "?", reason ? reason : "?",
2509 cert ? cert : "?"));
2511 hostlist.name = host ? host : "";
2512 hostlist.next = NULL;
2515 * See if we've been asked about this host before.
2517 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2518 /* we were asked before, did we say Yes? */
2519 if(ok_novalidate)
2520 rv++;
2522 if(rv){
2523 dprint((5,
2524 "sslcertificatequery: approved automatically\n"));
2525 return(rv);
2528 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2531 if(ps_global->ttyo){
2532 SCROLL_S sargs;
2533 STORE_S *in_store, *out_store;
2534 gf_io_t pc, gc;
2535 HANDLE_S *handles = NULL;
2536 int the_answer = 'n';
2538 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
2539 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
2540 goto try_wantto;
2542 so_puts(in_store, "<HTML><P>");
2543 so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server"));
2545 so_puts(in_store, "<P><CENTER>");
2546 so_puts(in_store, host ? host : unknown);
2547 so_puts(in_store, "</CENTER>");
2549 so_puts(in_store, "<P>");
2550 so_puts(in_store, _("The reason for the failure was"));
2552 /* squirrel away details */
2553 if(details_host)
2554 fs_give((void **)&details_host);
2555 if(details_reason)
2556 fs_give((void **)&details_reason);
2557 if(details_cert)
2558 fs_give((void **)&details_cert);
2560 details_host = cpystr(host ? host : unknown);
2561 details_reason = cpystr(reason ? reason : unknown);
2562 details_cert = cpystr(cert ? cert : unknown);
2564 so_puts(in_store, "<P><CENTER>");
2565 snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2566 reason ? reason : unknown);
2567 tmp[sizeof(tmp)-1] = '\0';
2569 so_puts(in_store, tmp);
2570 so_puts(in_store, "</CENTER>");
2572 so_puts(in_store, "<P>");
2573 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."));
2575 so_puts(in_store, "<P>");
2576 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"));
2578 so_puts(in_store, "<P><CENTER>");
2579 so_puts(in_store, "/novalidate-cert");
2580 so_puts(in_store, "</CENTER>");
2582 so_puts(in_store, "<P>");
2583 so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2585 so_puts(in_store, "<P><CENTER>");
2586 so_puts(in_store, host ? host : unknown);
2587 so_puts(in_store, "</CENTER>");
2589 so_puts(in_store, "<P>");
2590 so_puts(in_store, _("in your configuration, replace those characters with"));
2592 so_puts(in_store, "<P><CENTER>");
2593 so_puts(in_store, host ? host : unknown);
2594 so_puts(in_store, "/novalidate-cert");
2595 so_puts(in_store, "</CENTER>");
2597 so_puts(in_store, "<P>");
2598 so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2600 so_seek(in_store, 0L, 0);
2601 init_handles(&handles);
2602 gf_filter_init();
2603 gf_link_filter(gf_html2plain,
2604 gf_html2plain_opt(NULL,
2605 ps_global->ttyo->screen_cols, non_messageview_margin(),
2606 &handles, NULL, GFHP_LOCAL_HANDLES));
2607 gf_set_so_readc(&gc, in_store);
2608 gf_set_so_writec(&pc, out_store);
2609 gf_pipe(gc, pc);
2610 gf_clear_so_writec(out_store);
2611 gf_clear_so_readc(in_store);
2613 memset(&sargs, 0, sizeof(SCROLL_S));
2614 sargs.text.handles = handles;
2615 sargs.text.text = so_text(out_store);
2616 sargs.text.src = CharStar;
2617 sargs.text.desc = _("help text");
2618 sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2619 sargs.proc.tool = answer_cert_failure;
2620 sargs.proc.data.p = (void *)&the_answer;
2621 sargs.keys.menu = &ans_certquery_keymenu;
2622 /* don't want to re-enter c-client */
2623 sargs.quell_newmail = 1;
2624 setbitmap(sargs.keys.bitmap);
2625 sargs.help.text = h_tls_validation_failure;
2626 sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
2628 scrolltool(&sargs);
2630 if(the_answer == 'y')
2631 rv++;
2632 else if(the_answer == 'n')
2633 ps_global->user_says_cancel = 1;
2635 ps_global->mangled_screen = 1;
2636 ps_global->painted_body_on_startup = 0;
2637 ps_global->painted_footer_on_startup = 0;
2638 so_give(&in_store);
2639 so_give(&out_store);
2640 free_handles(&handles);
2641 if(details_host)
2642 fs_give((void **)&details_host);
2643 if(details_reason)
2644 fs_give((void **)&details_reason);
2645 if(details_cert)
2646 fs_give((void **)&details_cert);
2648 else{
2650 * If screen hasn't been initialized yet, use want_to.
2652 try_wantto:
2653 memset((void *)tmp, 0, sizeof(tmp));
2654 strncpy(tmp,
2655 reason ? reason : _("SSL/TLS certificate validation failure"),
2656 sizeof(tmp));
2657 tmp[sizeof(tmp)-1] = '\0';
2658 strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
2660 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
2661 rv++;
2664 if(rv == 0)
2665 q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
2666 host ? host : unknown);
2668 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
2670 dprint((5, "sslcertificatequery: %s\n",
2671 rv ? "approved" : "rejected"));
2673 return(rv);
2677 char *
2678 pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
2680 char buf[MAILTMPLEN];
2682 if((can_access(mulname, ACCESS_EXISTS) == 0)
2683 || !(can_access(name, ACCESS_EXISTS) == 0))
2684 return(mulname);
2686 snprintf(buf, sizeof(buf),
2687 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2688 last_cmpnt(name),
2689 strlen(last_cmpnt(name)) > 15 ? "..." : "");
2690 buf[sizeof(buf)-1] = '\0';
2691 if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
2692 rename_file(name, mulname);
2693 return(mulname);
2698 url_local_certdetails(char *url)
2700 if(!struncmp(url, "x-alpine-cert:", 14)){
2701 STORE_S *store;
2702 SCROLL_S sargs;
2703 char *folded;
2705 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
2706 q_status_message(SM_ORDER | SM_DING, 7, 10,
2707 _("Error allocating space for details."));
2708 return(0);
2711 so_puts(store, _("Host given by user:\n\n "));
2712 so_puts(store, details_host);
2713 so_puts(store, _("\n\nReason for failure:\n\n "));
2714 so_puts(store, details_reason);
2715 so_puts(store, _("\n\nCertificate being verified:\n\n"));
2716 folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
2717 so_puts(store, folded);
2718 fs_give((void **)&folded);
2719 so_puts(store, "\n");
2721 memset(&sargs, 0, sizeof(SCROLL_S));
2722 sargs.text.text = so_text(store);
2723 sargs.text.src = CharStar;
2724 sargs.text.desc = _("Details");
2725 sargs.bar.title = _("CERT VALIDATION DETAILS");
2726 sargs.help.text = NO_HELP;
2727 sargs.help.title = NULL;
2728 sargs.quell_newmail = 1;
2729 sargs.help.text = h_tls_failure_details;
2730 sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
2732 scrolltool(&sargs);
2734 so_give(&store); /* free resources associated with store */
2735 ps_global->mangled_screen = 1;
2736 return(1);
2739 return(0);
2744 * C-client callback to handle SSL/TLS certificate validation failures
2746 void
2747 pine_sslfailure(char *host, char *reason, long unsigned int flags)
2749 SCROLL_S sargs;
2750 STORE_S *store;
2751 int the_answer = 'n', indent, len, cols;
2752 char buf[500], buf2[500];
2753 char *folded;
2754 char *hst = host ? host : "<unknown>";
2755 char *rsn = reason ? reason : "<unknown>";
2756 char *notls = "/notls";
2757 STRLIST_S hostlist;
2758 int ok_novalidate = 0, warned = 0;
2761 dprint((1, "sslfailure: host=%s reason=%s\n",
2762 hst ? hst : "?",
2763 rsn ? rsn : "?"));
2765 if(flags & NET_SILENT)
2766 return;
2768 hostlist.name = host ? host : "";
2769 hostlist.next = NULL;
2772 * See if we've been told about this host before.
2774 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2775 /* we were told already */
2776 if(warned){
2777 snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn);
2778 buf[sizeof(buf)-1] = '\0';
2779 mm_log(buf, ERROR);
2780 return;
2784 cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2785 cols--;
2787 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS)))
2788 return;
2790 strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf));
2791 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2792 so_puts(store, folded);
2793 fs_give((void **)&folded);
2794 so_puts(store, "\n");
2796 if((len=strlen(hst)) <= cols){
2797 if((indent=((cols-len)/2)) > 0)
2798 so_puts(store, repeat_char(indent, SPACE));
2800 so_puts(store, hst);
2801 so_puts(store, "\n");
2803 else{
2804 strncpy(buf, hst, sizeof(buf));
2805 buf[sizeof(buf)-1] = '\0';
2806 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2807 so_puts(store, folded);
2808 fs_give((void **)&folded);
2811 so_puts(store, "\n");
2813 strncpy(buf, _("The reason for the failure was"), sizeof(buf));
2814 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2815 so_puts(store, folded);
2816 fs_give((void **)&folded);
2817 so_puts(store, "\n");
2819 if((len=strlen(rsn)) <= cols){
2820 if((indent=((cols-len)/2)) > 0)
2821 so_puts(store, repeat_char(indent, SPACE));
2823 so_puts(store, rsn);
2824 so_puts(store, "\n");
2826 else{
2827 strncpy(buf, rsn, sizeof(buf));
2828 buf[sizeof(buf)-1] = '\0';
2829 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2830 so_puts(store, folded);
2831 fs_give((void **)&folded);
2834 so_puts(store, "\n");
2836 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));
2837 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2838 so_puts(store, folded);
2839 fs_give((void **)&folded);
2840 so_puts(store, "\n");
2842 if((len=strlen(notls)) <= cols){
2843 if((indent=((cols-len)/2)) > 0)
2844 so_puts(store, repeat_char(indent, SPACE));
2846 so_puts(store, notls);
2847 so_puts(store, "\n");
2849 else{
2850 strncpy(buf, notls, sizeof(buf));
2851 buf[sizeof(buf)-1] = '\0';
2852 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2853 so_puts(store, folded);
2854 fs_give((void **)&folded);
2857 so_puts(store, "\n");
2859 strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2860 sizeof(buf));
2861 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2862 so_puts(store, folded);
2863 fs_give((void **)&folded);
2864 so_puts(store, "\n");
2866 if((len=strlen(hst)) <= cols){
2867 if((indent=((cols-len)/2)) > 0)
2868 so_puts(store, repeat_char(indent, SPACE));
2870 so_puts(store, hst);
2871 so_puts(store, "\n");
2873 else{
2874 strncpy(buf, hst, sizeof(buf));
2875 buf[sizeof(buf)-1] = '\0';
2876 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2877 so_puts(store, folded);
2878 fs_give((void **)&folded);
2881 so_puts(store, "\n");
2883 strncpy(buf, _("in your configuration, replace those characters with"),
2884 sizeof(buf));
2885 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2886 so_puts(store, folded);
2887 fs_give((void **)&folded);
2888 so_puts(store, "\n");
2890 snprintf(buf2, sizeof(buf2), "%s%s", hst, notls);
2891 buf2[sizeof(buf2)-1] = '\0';
2892 if((len=strlen(buf2)) <= cols){
2893 if((indent=((cols-len)/2)) > 0)
2894 so_puts(store, repeat_char(indent, SPACE));
2896 so_puts(store, buf2);
2897 so_puts(store, "\n");
2899 else{
2900 strncpy(buf, buf2, sizeof(buf));
2901 buf[sizeof(buf)-1] = '\0';
2902 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2903 so_puts(store, folded);
2904 fs_give((void **)&folded);
2907 so_puts(store, "\n");
2909 if(ps_global->ttyo){
2910 strncpy(buf, _("Type RETURN to continue."), sizeof(buf));
2911 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2912 so_puts(store, folded);
2913 fs_give((void **)&folded);
2916 memset(&sargs, 0, sizeof(SCROLL_S));
2917 sargs.text.text = so_text(store);
2918 sargs.text.src = CharStar;
2919 sargs.text.desc = _("help text");
2920 sargs.bar.title = _("SSL/TLS FAILURE");
2921 sargs.proc.tool = answer_cert_failure;
2922 sargs.proc.data.p = (void *)&the_answer;
2923 sargs.keys.menu = &ans_certfail_keymenu;
2924 setbitmap(sargs.keys.bitmap);
2925 /* don't want to re-enter c-client */
2926 sargs.quell_newmail = 1;
2927 sargs.help.text = h_tls_failure;
2928 sargs.help.title = _("HELP FOR TLS/SSL FAILURE");
2930 if(ps_global->ttyo)
2931 scrolltool(&sargs);
2932 else{
2933 char **q, **qp;
2934 char *p;
2935 unsigned char c;
2936 int cnt = 0;
2939 * The screen isn't initialized yet, which should mean that this
2940 * is the result of a -p argument. Display_args_err knows how to deal
2941 * with the uninitialized screen, so we mess with the data to get it
2942 * in shape for display_args_err. This is pretty hacky.
2945 so_seek(store, 0L, 0); /* rewind */
2946 /* count the lines */
2947 while(so_readc(&c, store))
2948 if(c == '\n')
2949 cnt++;
2951 qp = q = (char **)fs_get((cnt+1) * sizeof(char *));
2952 memset(q, 0, (cnt+1) * sizeof(char *));
2954 so_seek(store, 0L, 0); /* rewind */
2955 p = buf;
2956 while(so_readc(&c, store)){
2957 if(c == '\n'){
2958 *p = '\0';
2959 *qp++ = cpystr(buf);
2960 p = buf;
2962 else
2963 *p++ = c;
2966 display_args_err(NULL, q, 0);
2967 free_list_array(&q);
2970 ps_global->mangled_screen = 1;
2971 ps_global->painted_body_on_startup = 0;
2972 ps_global->painted_footer_on_startup = 0;
2973 so_give(&store);
2975 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1);
2980 answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2982 int rv = 1;
2984 ps_global->next_screen = SCREEN_FUN_NULL;
2986 switch(cmd){
2987 case MC_YES :
2988 *(int *)(sparms->proc.data.p) = 'y';
2989 break;
2991 case MC_NO :
2992 *(int *)(sparms->proc.data.p) = 'n';
2993 break;
2995 default:
2996 alpine_panic("Unexpected command in answer_cert_failure");
2997 break;
3000 return(rv);
3005 oauth2_auth_answer(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
3007 int rv = 1, rc;
3008 AUTH_CODE_S user;
3009 int q_line, flags;
3010 /* TRANSLATORS: user needs to input an access code from the server */
3011 char *accesscodelabel = _("Copy and Paste Access Code");
3012 char token[MAILTMPLEN], prompt[MAILTMPLEN];
3014 ps_global->next_screen = SCREEN_FUN_NULL;
3016 token[0] = '\0';
3017 switch(cmd){
3018 case MC_YES :
3019 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
3020 flags = OE_APPEND_CURRENT;
3021 sprintf(prompt, "%s: ", accesscodelabel);
3022 do {
3023 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
3024 prompt, NULL, NO_HELP, &flags);
3025 } while (rc != 0 && rc != 1);
3026 user.code = rc == 0 ? cpystr(token) : NULL;
3027 user.answer = 'e';
3028 rv = rc == 1 ? 0 : 1;
3029 break;
3031 case MC_NO :
3032 user.code = NULL;
3033 user.answer = 'e';
3034 break;
3036 default:
3037 alpine_panic("Unexpected command in oauth2_auth_answer");
3038 break;
3040 *(AUTH_CODE_S *) sparms->proc.data.p = user;
3041 return(rv);
3045 /*----------------------------------------------------------------------
3046 This can be used to prevent the flickering of the check_cue char
3047 caused by numerous (5000+) fetches by c-client. Right now, the only
3048 practical use found is newsgroup subsciption.
3050 check_cue_display will check if this global is set, and won't clear
3051 the check_cue_char if set.
3052 ----*/
3053 void
3054 set_read_predicted(int i)
3056 ps_global->read_predicted = i==1;
3057 #ifdef _WINDOWS
3058 if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global))
3059 check_cue_display(" ");
3060 #endif
3064 /*----------------------------------------------------------------------
3065 Exported method to retrieve logged in user name associated with stream
3067 Args: host -- host to find associated login name with.
3069 Result:
3070 ----*/
3071 void *
3072 pine_block_notify(int reason, void *data)
3074 switch(reason){
3075 case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */
3076 break;
3078 case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */
3079 break;
3081 case BLOCK_TCPWRITE: /* blocked on TCP write */
3082 case BLOCK_FILELOCK: /* blocked on file locking */
3083 #ifdef _WINDOWS
3084 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3085 check_cue_display(">");
3087 mswin_setcursor(MSWIN_CURSOR_BUSY);
3088 #endif
3089 break;
3091 case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */
3092 case BLOCK_TCPOPEN: /* blocked on TCP open */
3093 case BLOCK_TCPREAD: /* blocked on TCP read */
3094 case BLOCK_TCPCLOSE: /* blocked on TCP close */
3095 #ifdef _WINDOWS
3096 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3097 check_cue_display("<");
3099 mswin_setcursor(MSWIN_CURSOR_BUSY);
3100 #endif
3101 break;
3103 default :
3104 case BLOCK_NONE: /* not blocked */
3105 #ifdef _WINDOWS
3106 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3107 check_cue_display(" ");
3108 #endif
3109 break;
3113 return(NULL);
3117 void
3118 mm_expunged_current(long unsigned int rawno)
3120 /* expunged something we're viewing? */
3121 if(!ps_global->expunge_in_progress
3122 && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno))
3123 && (ps_global->prev_screen == mail_view_screen
3124 || ps_global->prev_screen == attachment_screen))){
3125 ps_global->next_screen = mail_index_screen;
3126 q_status_message(SM_ORDER | SM_DING , 3, 3,
3127 "Message you were viewing is gone!");
3132 #ifdef PASSFILE
3135 * Specific functions to support caching username/passwd/host
3136 * triples on disk for use from one session to the next...
3139 #define FIRSTCH 0x20
3140 #define LASTCH 0x7e
3141 #define TABSZ (LASTCH - FIRSTCH + 1)
3143 static int xlate_key;
3147 * xlate_in() - xlate_in the given character
3149 char
3150 xlate_in(int c)
3152 register int eti;
3154 eti = xlate_key;
3155 if((c >= FIRSTCH) && (c <= LASTCH)){
3156 eti += (c - FIRSTCH);
3157 eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0;
3158 return((xlate_key = eti) + FIRSTCH);
3160 else
3161 return(c);
3166 * xlate_out() - xlate_out the given character
3168 char
3169 xlate_out(char c)
3171 register int dti;
3172 register int xch;
3174 if((c >= FIRSTCH) && (c <= LASTCH)){
3175 xch = c - (dti = xlate_key);
3176 xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0;
3177 dti = (xch - FIRSTCH) + dti;
3178 dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0;
3179 xlate_key = dti;
3180 return(xch);
3182 else
3183 return(c);
3185 #endif /* PASSFILE */
3188 #ifdef LOCAL_PASSWD_CACHE
3191 int
3192 line_get(char *tmp, size_t len, char **textp)
3194 char *s;
3196 tmp[0] = '\0';
3197 if (*textp == NULL
3198 || (s = strchr(*textp, '\n')) == NULL
3199 || (s - *textp) > len - 1)
3200 return 0;
3202 *s = '\0';
3203 if(*(s-1) == '\r')
3204 *(s-1) = '\0';
3206 snprintf(tmp, len, "%s\n", *textp);
3207 tmp[len-1] = '\0';
3208 *textp = s+1;
3210 return 1;
3213 * For UNIX:
3214 * Passfile lines are
3216 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3218 * In pine4.40 and before there was no orig_hostname, and there still isn't
3219 * if it is the same as hostname.
3221 * else for WINDOWS:
3222 * Use Windows credentials. The TargetName of the credential is
3223 * UWash_Alpine_<hostname:port>\tuser\taltflag
3224 * and the blob consists of
3225 * passwd\torighost (if different from host)
3227 * We don't use anything fancy we just copy out all the credentials which
3228 * begin with TNAME and put them into our cache, so we don't lookup based
3229 * on the TargetName or anything like that. That was so we could re-use
3230 * the existing code and so that orighost data could be easily used.
3233 read_passfile(pinerc, l)
3234 char *pinerc;
3235 MMLOGIN_S **l;
3237 #ifdef WINCRED
3238 # if (WINCRED > 0)
3239 LPCTSTR lfilter = TEXT(TNAMESTAR);
3240 DWORD count, k;
3241 PCREDENTIAL *pcred;
3242 char *tmp, *blob, *target = NULL;
3243 char *host, *user, *sflags, *passwd, *orighost;
3244 char *ui[5];
3245 int i, j;
3247 if(using_passfile == 0)
3248 return(using_passfile);
3250 if(!g_CredInited){
3251 if(init_wincred_funcs() != 1){
3252 using_passfile = 0;
3253 return(using_passfile);
3257 dprint((9, "read_passfile\n"));
3259 using_passfile = 1;
3261 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
3262 if(pcred){
3263 for(k = 0; k < count; k++){
3265 host = user = sflags = passwd = orighost = NULL;
3266 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3268 target = lptstr_to_utf8(pcred[k]->TargetName);
3269 tmp = srchstr(target, TNAME);
3271 if(tmp){
3272 tmp += strlen(TNAME);
3273 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3274 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3275 ; /* find end of data */
3277 if(tmp[i])
3278 tmp[i++] = '\0'; /* tie off data */
3281 host = ui[0];
3282 user = ui[1];
3283 sflags = ui[2];
3286 blob = (char *) pcred[k]->CredentialBlob;
3287 if(blob){
3288 for(i = 0, j = 3; blob[i] && j < 5; j++){
3289 for(ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++)
3290 ; /* find end of data */
3292 if(blob[i])
3293 blob[i++] = '\0'; /* tie off data */
3296 passwd = ui[3];
3297 orighost = ui[4];
3300 if(passwd && host && user){ /* valid field? */
3301 STRLIST_S hostlist[2];
3302 int flags;
3304 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3305 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3306 hostlist[0].name = host;
3307 if(orighost){
3308 hostlist[0].next = &hostlist[1];
3309 hostlist[1].name = orighost;
3310 hostlist[1].next = NULL;
3312 else{
3313 hostlist[0].next = NULL;
3316 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3319 if(target)
3320 fs_give((void **) &target);
3323 g_CredFree((PVOID) pcred);
3327 return(1);
3329 # else /* old windows */
3330 using_passfile = 0;
3331 return(0);
3332 # endif
3334 #elif APPLEKEYCHAIN
3336 char target[MAILTMPLEN];
3337 char *tmp, *host, *user, *sflags, *passwd, *orighost;
3338 char *ui[5];
3339 int i, j, k, rc;
3340 SecKeychainAttributeList attrList;
3341 SecKeychainSearchRef searchRef = NULL;
3342 SecKeychainAttribute attrs[] = {
3343 { kSecAccountItemAttr, strlen(TNAME), TNAME }
3346 if(using_passfile == 0)
3347 return(using_passfile);
3349 dprint((9, "read_passfile\n"));
3352 /* search for only our items in the keychain */
3353 attrList.count = 1;
3354 attrList.attr = attrs;
3356 using_passfile = 1;
3357 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
3358 kSecGenericPasswordItemClass,
3359 &attrList,
3360 &searchRef))){
3361 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3362 if(searchRef){
3363 SecKeychainItemRef itemRef = NULL;
3364 SecKeychainAttributeInfo info;
3365 SecKeychainAttributeList *attrList = NULL;
3366 UInt32 blength = 0;
3367 char *blob = NULL;
3368 char *blobcopy = NULL; /* NULL terminated copy */
3370 UInt32 tags[] = {kSecAccountItemAttr,
3371 kSecServiceItemAttr};
3372 UInt32 formats[] = {0,0};
3374 dprint((10, "read_passfile: searchRef not NULL\n"));
3375 info.count = 2;
3376 info.tag = tags;
3377 info.format = formats;
3380 * Go through each item we found and put it
3381 * into our list.
3383 while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){
3384 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3385 rc = SecKeychainItemCopyAttributesAndData(itemRef,
3386 &info, NULL,
3387 &attrList,
3388 &blength,
3389 (void **) &blob);
3390 if(rc == 0 && attrList){
3391 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count));
3393 blobcopy = (char *) fs_get((blength + 1) * sizeof(char));
3394 strncpy(blobcopy, (char *) blob, blength);
3395 blobcopy[blength] = '\0';
3398 * I'm not real clear on how this works. It seems to be
3399 * necessary to combine the attributes from two passes
3400 * (attrList->count == 2) in order to get the full set
3401 * of attributes we inserted into the keychain in the
3402 * first place. So, we reset host...orighost outside of
3403 * the following for loop, not inside.
3405 host = user = sflags = passwd = orighost = NULL;
3406 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3408 for(k = 0; k < attrList->count; k++){
3410 if(attrList->attr[k].length){
3411 strncpy(target,
3412 (char *) attrList->attr[k].data,
3413 MIN(attrList->attr[k].length,sizeof(target)));
3414 target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0';
3417 tmp = target;
3418 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3419 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3420 ; /* find end of data */
3422 if(tmp[i])
3423 tmp[i++] = '\0'; /* tie off data */
3426 if(ui[0])
3427 host = ui[0];
3429 if(ui[1])
3430 user = ui[1];
3432 if(ui[2])
3433 sflags = ui[2];
3435 for(i = 0, j = 3; blobcopy[i] && j < 5; j++){
3436 for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++)
3437 ; /* find end of data */
3439 if(blobcopy[i])
3440 blobcopy[i++] = '\0'; /* tie off data */
3443 if(ui[3])
3444 passwd = ui[3];
3446 if(ui[4])
3447 orighost = ui[4];
3449 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:""));
3452 if(passwd && host && user){ /* valid field? */
3453 STRLIST_S hostlist[2];
3454 int flags;
3456 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3457 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3458 hostlist[0].name = host;
3459 if(orighost){
3460 hostlist[0].next = &hostlist[1];
3461 hostlist[1].name = orighost;
3462 hostlist[1].next = NULL;
3464 else{
3465 hostlist[0].next = NULL;
3468 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3471 if(blobcopy)
3472 fs_give((void **) & blobcopy);
3474 SecKeychainItemFreeAttributesAndData(attrList, (void *) blob);
3476 else{
3477 using_passfile = 0;
3478 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc));
3481 CFRelease(itemRef);
3482 itemRef = NULL;
3485 CFRelease(searchRef);
3487 else{
3488 using_passfile = 0;
3489 dprint((10, "read_passfile: searchRef NULL\n"));
3492 else{
3493 using_passfile = 0;
3494 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc));
3497 return(using_passfile);
3499 #else /* PASSFILE */
3501 char tmp[MAILTMPLEN], *ui[5];
3502 int i, j, n, rv = 0;
3503 size_t len = 0;
3504 char *tmptext = NULL;
3505 struct stat sbuf;
3506 #ifdef SMIME
3507 char tmp2[MAILTMPLEN];
3508 char *text = NULL, *text2 = NULL;
3509 int encrypted = 0;
3510 #endif /* SMIME */
3511 FILE *fp;
3513 if(using_passfile == 0)
3514 return(using_passfile);
3516 dprint((9, "read_passfile\n"));
3518 /* if there's no password to read, bag it!! */
3519 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){
3520 using_passfile = 0;
3521 return(using_passfile);
3524 #ifndef SMIME
3525 if(our_stat(tmp, &sbuf) == 0)
3526 len = sbuf.st_size;
3527 fp = our_fopen(tmp, "rb"); /* reopen to read data */
3528 #else
3529 /* the next call initializes the key/certificate pair used to
3530 * encrypt and decrypt a password file. The details of how this is
3531 * done is in the file pith/smime.c. During this setup we might call
3532 * smime_init(), but no matter what happens we must call smime_deinit()
3533 * there. The reason why this is so is because we can not assume that
3534 * the .pinerc file has been read by this time, so this code might not
3535 * know about the ps_global->smime structure or any of its components,
3536 * and it shouldn't because it only needs ps_global->pwdcert, so
3537 * do not init smime here, because the .pinerc might not have been
3538 * read and we do not really know where the keys and certificates really
3539 * are.
3540 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3541 * it is called for the first time and there are certificates at all,
3542 * or when it is called after the first time and the user refuses to
3543 * create a self-signed certificate. In this situation we will just
3544 * let the user live in an insecure world, but no more passwords will
3545 * be saved in the password file, and only those found there will be used.
3547 tmp2[0] = '\0';
3548 fgets(tmp2, sizeof(tmp2), fp);
3549 fclose(fp);
3550 if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
3551 /* there is an already existing password file, that is not encrypted
3552 * and there is no key to encrypt it yet, go again through setup_pwdcert
3553 * and encrypt it now.
3555 if(tmp2[0]){ /* not empty, UNencrypted password file */
3556 if(ps_global->pwdcert == NULL)
3557 rv = setup_pwdcert(&ps_global->pwdcert);
3558 if((rv == 0 || rv == -5) && ps_global->pwdcert == NULL)
3559 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3560 if(ps_global->pwdcert == NULL){
3561 q_status_message(SM_ORDER, 3, 3,
3562 " Failed to create private key. Using UNencrypted Password file. ");
3563 save_password = 0;
3565 else{
3566 if(rv == 1){
3567 q_status_message(SM_ORDER, 3, 3,
3568 " Failed to unlock private key. Using UNencrypted Password file. ");
3569 save_password = 0; /* do not save more passwords */
3572 if(ps_global->pwdcert != NULL
3573 && encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
3574 encrypted++;
3577 else {
3578 if(ps_global->pwdcert == NULL)
3579 rv = setup_pwdcert(&ps_global->pwdcert);
3580 encrypted++;
3584 * if password file is encrypted we attempt to decrypt. We ask the
3585 * user for the password to unlock the password file. If the user
3586 * enters the password and it unlocks the file, use it and keep saving
3587 * passwords in it. If the user enters the wrong passwords and does
3588 * not unlock it, we will not see that here, but in decrypt_file, so
3589 * the only other possibility is that the user cancels. In that case
3590 * we will see i == -1. In that case, we will let the user attempt
3591 * manual login to the server they want to login, but passwords will
3592 * not be saved so that the password file will not be saved
3593 * unencrypted and rewritten again.
3595 if(encrypted){
3596 text = text2 = decrypt_file((char *)tmp, &i, (PERSONAL_CERT *)ps_global->pwdcert);
3597 len = text2 ? strlen(text2) : 0;
3598 switch(i){
3599 case -2: using_passfile = 0;
3600 break;
3602 case 1 : save_password = 1;
3603 using_passfile = 1;
3604 break;
3606 case -1: save_password = 0;
3607 using_passfile = 1;
3608 break;
3610 default: break;
3613 #endif /* SMIME */
3615 if(using_passfile == 0){
3616 #ifdef SMIME
3617 if(text) fs_give((void **)&text);
3618 #endif /* SMIME */
3619 return using_passfile;
3622 if(len > 0){
3623 tmptext = fs_get(len + 1);
3624 #ifdef SMIME
3625 for(n = 0; encrypted ? line_get(tmptext, len + 1, &text2)
3626 : (fgets(tmptext, len+1, fp) != NULL); n++){
3627 #else /* SMIME */
3628 for(n = 0; fgets(tmptext, len+1, fp); n++){
3629 #endif /* SMIME */
3630 /*** do any necessary DEcryption here ***/
3631 xlate_key = n;
3632 for(i = 0; tmptext[i]; i++)
3633 tmptext[i] = xlate_out(tmptext[i]);
3635 if(i && tmptext[i-1] == '\n')
3636 tmptext[i-1] = '\0'; /* blast '\n' */
3638 dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
3639 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3640 for(i = 0, j = 0; tmptext[i] && j < 5; j++){
3641 for(ui[j] = &tmptext[i]; tmptext[i] && tmptext[i] != '\t'; i++)
3642 ; /* find end of data */
3644 if(tmptext[i])
3645 tmptext[i++] = '\0'; /* tie off data */
3648 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3649 if(ui[0] && ui[1] && ui[2]){ /* valid field? */
3650 STRLIST_S hostlist[2];
3651 char *s = ui[3] ? strchr(ui[3], PWDAUTHSEP) : NULL;
3652 int flags = ui[3] ? atoi(s ? ++s : ui[3]) : 0;
3654 hostlist[0].name = ui[2];
3655 if(ui[4]){
3656 hostlist[0].next = &hostlist[1];
3657 hostlist[1].name = ui[4];
3658 hostlist[1].next = NULL;
3660 else{
3661 hostlist[0].next = NULL;
3664 imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0);
3669 if (tmptext) fs_give((void **) &tmptext);
3670 #ifdef SMIME
3671 if (text) fs_give((void **)&text);
3672 #else /* SMIME */
3673 fclose(fp);
3674 #endif /* SMIME */
3675 return(1);
3676 #endif /* PASSFILE */
3681 void
3682 write_passfile(pinerc, l)
3683 char *pinerc;
3684 MMLOGIN_S *l;
3686 char *authend, *authtype;
3687 #ifdef WINCRED
3688 # if (WINCRED > 0)
3689 char target[10*MAILTMPLEN];
3690 char blob[10*MAILTMPLEN];
3691 CREDENTIAL cred;
3692 LPTSTR ltarget = 0;
3694 if(using_passfile == 0)
3695 return;
3697 dprint((9, "write_passfile\n"));
3699 for(; l; l = l->next){
3700 authtype = l->passwd;
3701 authend = strchr(l->passwd, PWDAUTHSEP);
3702 if(authend != NULL){
3703 *authend = '\0';
3704 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3705 *authend = PWDAUTHSEP;
3707 else
3708 sprintf(blob, "%d", l->altflag);
3710 snprintf(target, sizeof(target), "%s%s\t%s\t%s",
3711 TNAME,
3712 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3713 l->user ? l->user : "",
3714 blob);
3715 ltarget = utf8_to_lptstr((LPSTR) target);
3717 if(ltarget){
3718 snprintf(blob, sizeof(blob), "%s%s%s",
3719 l->passwd ? l->passwd : "",
3720 (l->hosts && l->hosts->next && l->hosts->next->name)
3721 ? "\t" : "",
3722 (l->hosts && l->hosts->next && l->hosts->next->name)
3723 ? l->hosts->next->name : "");
3724 memset((void *) &cred, 0, sizeof(cred));
3725 cred.Flags = 0;
3726 cred.Type = CRED_TYPE_GENERIC;
3727 cred.TargetName = ltarget;
3728 cred.CredentialBlobSize = strlen(blob)+1;
3729 cred.CredentialBlob = (LPBYTE) &blob;
3730 cred.Persist = CRED_PERSIST_ENTERPRISE;
3731 g_CredWriteW(&cred, 0);
3733 fs_give((void **) &ltarget);
3736 #endif /* WINCRED > 0 */
3738 #elif APPLEKEYCHAIN
3739 int rc;
3740 char target[10*MAILTMPLEN];
3741 char blob[10*MAILTMPLEN];
3742 SecKeychainItemRef itemRef = NULL;
3744 if(using_passfile == 0)
3745 return;
3747 dprint((9, "write_passfile\n"));
3749 for(; l; l = l->next){
3750 authtype = l->passwd;
3751 authend = strchr(l->passwd, PWDAUTHSEP);
3752 if(authend != NULL){
3753 *authend = '\0';
3754 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3755 *authend = PWDAUTHSEP;
3757 else
3758 sprintf(blob, "%d", l->altflag);
3760 snprintf(target, sizeof(target), "%s\t%s\t%s",
3761 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3762 l->user ? l->user : "",
3763 blob);
3765 snprintf(blob, sizeof(blob), "%s%s%s",
3766 l->passwd ? l->passwd : "",
3767 (l->hosts && l->hosts->next && l->hosts->next->name)
3768 ? "\t" : "",
3769 (l->hosts && l->hosts->next && l->hosts->next->name)
3770 ? l->hosts->next->name : "");
3772 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob));
3774 rc = SecKeychainAddGenericPassword(NULL,
3775 strlen(target), target,
3776 strlen(TNAME), TNAME,
3777 strlen(blob), blob,
3778 NULL);
3779 if(rc==0){
3780 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3782 else{
3783 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc));
3786 if(rc == errSecDuplicateItem){
3787 /* fix existing entry */
3788 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3789 itemRef = NULL;
3790 if(!(rc=SecKeychainFindGenericPassword(NULL,
3791 strlen(target), target,
3792 strlen(TNAME), TNAME,
3793 NULL, NULL,
3794 &itemRef)) && itemRef){
3796 rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob);
3797 if(!rc){
3798 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc));
3801 else{
3802 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc));
3807 #else /* PASSFILE */
3808 char tmp[10*MAILTMPLEN], blob[10*MAILTMPLEN];
3809 int i, n;
3810 FILE *fp;
3811 #ifdef SMIME
3812 char *text = NULL, tmp2[10*MAILTMPLEN];
3813 int len = 0;
3814 #endif
3816 if(using_passfile == 0)
3817 return;
3819 dprint((9, "write_passfile\n"));
3821 /* if there's no passfile to read, bag it!! */
3822 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "wb"))){
3823 using_passfile = 0;
3824 return;
3827 #ifdef SMIME
3828 strncpy(tmp2, tmp, sizeof(tmp2));
3829 tmp2[sizeof(tmp2)-1] = '\0';
3830 #endif /* SMIME */
3832 for(n = 0; l; l = l->next, n++){
3833 authtype = l->passwd;
3834 authend = strchr(l->passwd, PWDAUTHSEP);
3835 if(authend != NULL){
3836 *authend = '\0';
3837 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3838 *authend = PWDAUTHSEP;
3840 else
3841 sprintf(blob, "%d", l->altflag);
3843 /*** do any necessary ENcryption here ***/
3844 snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%s%s%s\n", l->passwd, l->user,
3845 l->hosts->name, blob,
3846 (l->hosts->next && l->hosts->next->name) ? "\t" : "",
3847 (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
3848 : "");
3849 dprint((10, "write_passfile: %s", tmp ? tmp : "?"));
3850 xlate_key = n;
3851 for(i = 0; tmp[i]; i++)
3852 tmp[i] = xlate_in(tmp[i]);
3854 #ifdef SMIME
3855 fs_resize((void **)&text, (len + strlen(tmp) + 1)*sizeof(char));
3856 text[len] = '\0';
3857 len += strlen(tmp) + 1;
3858 strncat(text, tmp, strlen(tmp));
3859 #else /* SMIME */
3860 fputs(tmp, fp);
3861 #endif /* SMIME */
3864 fclose(fp);
3865 #ifdef SMIME
3866 if(text != NULL){
3867 if(ps_global->pwdcert == NULL){
3868 q_status_message(SM_ORDER, 3, 3, "Attempting to encrypt password file");
3869 i = setup_pwdcert(&ps_global->pwdcert);
3870 if((i == 0 || i == -5) && ps_global->pwdcert == NULL)
3871 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3873 if(ps_global->pwdcert == NULL){ /* we tried but failed */
3874 if(i == -1)
3875 q_status_message1(SM_ORDER, 3, 3, "Error: no directory %s to save certificates", ps_global->pwdcertdir);
3876 else
3877 q_status_message(SM_ORDER, 3, 3, "Refusing to write non-encrypted password file");
3879 else if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0)
3880 q_status_message(SM_ORDER, 3, 3, "Failed to encrypt password file");
3881 fs_give((void **)&text); /* do not save this text */
3883 #endif /* SMIME */
3884 #endif /* PASSFILE */
3887 #endif /* LOCAL_PASSWD_CACHE */
3890 #if (WINCRED > 0)
3891 void
3892 erase_windows_credentials(void)
3894 LPCTSTR lfilter = TEXT(TNAMESTAR);
3895 DWORD count, k;
3896 PCREDENTIAL *pcred;
3898 if(!g_CredInited){
3899 if(init_wincred_funcs() != 1)
3900 return;
3903 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
3904 if(pcred){
3905 for(k = 0; k < count; k++)
3906 g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0);
3908 g_CredFree((PVOID) pcred);
3913 void
3914 ask_erase_credentials(void)
3916 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){
3917 erase_windows_credentials();
3918 q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased");
3920 else
3921 q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased");
3924 #endif /* WINCRED */
3927 #ifdef LOCAL_PASSWD_CACHE
3929 get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
3930 char *pinerc, **passwd, *user;
3931 STRLIST_S *hostlist;
3932 int altflag;
3934 return get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, NULL);
3938 * get_passfile_passwd_auth - return the password contained in the special passord
3939 * cache. The file is assumed to be in the same directory
3940 * as the pinerc with the name defined above.
3943 get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, authtype)
3944 char *pinerc, **passwd, *user;
3945 STRLIST_S *hostlist;
3946 int altflag;
3947 char *authtype;
3949 dprint((10, "get_passfile_passwd_auth\n"));
3950 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3951 ? imap_get_passwd_auth(passfile_cache, passwd,
3952 user, hostlist, altflag, authtype)
3953 : 0);
3956 void
3957 free_passfile_cache_work(MMLOGIN_S **pwdcache)
3959 if(pwdcache == NULL || *pwdcache == NULL)
3960 return;
3962 if((*pwdcache)->user) fs_give((void **)&(*pwdcache)->user);
3963 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
3964 if((*pwdcache)->hosts) free_strlist(&(*pwdcache)->hosts);
3965 free_passfile_cache_work(&(*pwdcache)->next);
3966 fs_give((void **)pwdcache);
3970 void
3971 free_passfile_cache(void)
3973 if(passfile_cache)
3974 free_passfile_cache_work(&passfile_cache);
3978 is_using_passfile(void)
3980 return(using_passfile == 1);
3984 * Just trying to guess the username the user might want to use on this
3985 * host, the user will confirm.
3987 char *
3988 get_passfile_user(pinerc, hostlist)
3989 char *pinerc;
3990 STRLIST_S *hostlist;
3992 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3993 ? imap_get_user(passfile_cache, hostlist)
3994 : NULL);
3999 preserve_prompt(char *pinerc)
4001 return preserve_prompt_auth(pinerc, NULL);
4005 preserve_prompt_auth(char *pinerc, char *authtype)
4007 #ifdef WINCRED
4008 # if (WINCRED > 0)
4009 #define PROMPT_PWD _("Preserve password for next login")
4010 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4012 * This prompt was going to be able to be turned on and off via a registry
4013 * setting controlled from the config menu. We decided to always use the
4014 * dialog for login, and there the prompt is unobtrusive enough to always
4015 * be in there. As a result, windows should never reach this, but now
4016 * OS X somewhat uses the behavior just described.
4018 if(mswin_store_pass_prompt()
4019 && (want_to(authtype
4020 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4021 : PROMPT_PWD,
4022 'y', 'x', NO_HELP, WT_NORM)
4023 == 'y'))
4024 return(1);
4025 else
4026 return(0);
4027 # else
4028 return(0);
4029 # endif
4031 #elif APPLEKEYCHAIN
4032 #define PROMPT_PWD _("Preserve password for next login")
4033 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4035 int rc;
4036 if((rc = macos_store_pass_prompt()) != 0){
4037 if(want_to(authtype
4038 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4039 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4040 == 'y'){
4041 if(rc == -1){
4042 macos_set_store_pass_prompt(1);
4043 q_status_message(SM_ORDER, 4, 4,
4044 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4046 return(1);
4048 else if(rc == -1){
4049 macos_set_store_pass_prompt(0);
4050 q_status_message(SM_ORDER, 4, 4,
4051 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4053 return(0);
4055 return(0);
4056 #else /* PASSFILE */
4057 #define PROMPT_PWD _("Preserve password on DISK for next login")
4058 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
4060 char tmp[MAILTMPLEN];
4061 struct stat sbuf;
4063 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || our_stat(tmp, &sbuf) < 0)
4064 return 0;
4066 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
4067 return(want_to(authtype
4068 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4069 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4070 == 'y');
4071 return(0);
4072 #endif /* PASSFILE */
4075 #endif /* LOCAL_PASSWD_CACHE */
4078 #ifdef APPLEKEYCHAIN
4081 * Returns:
4082 * 1 if store pass prompt is set in the "registry" to on
4083 * 0 if set to off
4084 * -1 if not set to anything
4087 macos_store_pass_prompt(void)
4089 char *data = NULL;
4090 UInt32 len = 0;
4091 int rc = -1;
4092 int val;
4094 if(storepassprompt == -1){
4095 if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL,
4096 strlen(TNAMEPROMPT),
4097 TNAMEPROMPT, &len,
4098 (void **) &data, NULL))){
4099 val = (len == 1 && data && data[0] == '1');
4103 if(storepassprompt == -1 && !rc){
4104 if(val)
4105 storepassprompt = 1;
4106 else
4107 storepassprompt = 0;
4110 return(storepassprompt);
4114 void
4115 macos_set_store_pass_prompt(int val)
4117 storepassprompt = val ? 1 : 0;
4119 SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT),
4120 TNAMEPROMPT, 1, val ? "1" : "0", NULL);
4124 void
4125 macos_erase_keychain(void)
4127 SecKeychainAttributeList attrList;
4128 SecKeychainSearchRef searchRef = NULL;
4129 SecKeychainAttribute attrs1[] = {
4130 { kSecAccountItemAttr, strlen(TNAME), TNAME }
4132 SecKeychainAttribute attrs2[] = {
4133 { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT }
4136 dprint((9, "macos_erase_keychain\n"));
4139 * Seems like we ought to be able to combine attrs1 and attrs2
4140 * into a single array, but I couldn't get it to work.
4143 /* search for only our items in the keychain */
4144 attrList.count = 1;
4145 attrList.attr = attrs1;
4147 if(!SecKeychainSearchCreateFromAttributes(NULL,
4148 kSecGenericPasswordItemClass,
4149 &attrList,
4150 &searchRef)){
4151 if(searchRef){
4152 SecKeychainItemRef itemRef = NULL;
4155 * Go through each item we found and put it
4156 * into our list.
4158 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4159 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4160 SecKeychainItemDelete(itemRef);
4161 CFRelease(itemRef);
4164 CFRelease(searchRef);
4168 attrList.count = 1;
4169 attrList.attr = attrs2;
4171 if(!SecKeychainSearchCreateFromAttributes(NULL,
4172 kSecGenericPasswordItemClass,
4173 &attrList,
4174 &searchRef)){
4175 if(searchRef){
4176 SecKeychainItemRef itemRef = NULL;
4179 * Go through each item we found and put it
4180 * into our list.
4182 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4183 SecKeychainItemDelete(itemRef);
4184 CFRelease(itemRef);
4187 CFRelease(searchRef);
4192 #endif /* APPLEKEYCHAIN */
4194 #ifdef LOCAL_PASSWD_CACHE
4196 void
4197 set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
4198 char *pinerc, *passwd, *user;
4199 STRLIST_S *hostlist;
4200 int altflag, already_prompted;
4202 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, NULL);
4205 * set_passfile_passwd - set the password file entry associated with
4206 * cache. The file is assumed to be in the same directory
4207 * as the pinerc with the name defined above.
4208 * already_prompted: 0 not prompted
4209 * 1 prompted, answered yes
4210 * 2 prompted, answered no
4212 void
4213 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, authtype)
4214 char *pinerc, *passwd, *user;
4215 STRLIST_S *hostlist;
4216 int altflag, already_prompted;
4217 char *authtype;
4219 dprint((10, "set_passfile_passwd_auth\n"));
4220 if(((already_prompted == 0 && preserve_prompt_auth(pinerc, authtype))
4221 || already_prompted == 1)
4222 && !ps_global->nowrite_password_cache
4223 && (passfile_cache || read_passfile(pinerc, &passfile_cache))){
4224 imap_set_passwd_auth(&passfile_cache, passwd, user, hostlist, altflag, 0, 0, authtype);
4225 write_passfile(pinerc, passfile_cache);
4229 void
4230 update_passfile_hostlist(pinerc, user, hostlist, altflag)
4231 char *pinerc;
4232 char *user;
4233 STRLIST_S *hostlist;
4234 int altflag;
4236 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, NULL);
4240 * Passfile lines are
4242 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4244 * In pine4.40 and before there was no orig_hostname.
4245 * This routine attempts to repair that.
4247 void
4248 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, authtype)
4249 char *pinerc;
4250 char *user;
4251 STRLIST_S *hostlist;
4252 int altflag;
4253 char *authtype;
4255 #ifdef WINCRED
4256 return;
4257 #else /* !WINCRED */
4258 MMLOGIN_S *l;
4259 size_t len = authtype ? strlen(authtype) : 0;
4260 size_t offset = authtype ? 1 : 0;
4262 for(l = passfile_cache; l; l = l->next)
4263 if(imap_same_host_auth(l->hosts, hostlist, authtype)
4264 && *user
4265 && !strcmp(user, l->user + len + offset)
4266 && l->altflag == altflag){
4267 break;
4270 if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
4271 && hostlist->next->name
4272 && !ps_global->nowrite_password_cache){
4273 l->hosts->next = new_strlist_auth(hostlist->next->name, authtype, PWDAUTHSEP);
4274 write_passfile(pinerc, passfile_cache);
4276 #endif /* !WINCRED */
4279 #endif /* LOCAL_PASSWD_CACHE */
4282 #if (WINCRED > 0)
4284 * Load and init the WinCred structure.
4285 * This gives us a way to skip the WinCred code
4286 * if the dll doesn't exist.
4289 init_wincred_funcs(void)
4291 if(!g_CredInited)
4293 HMODULE hmod;
4295 /* Assume the worst. */
4296 g_CredInited = -1;
4298 hmod = LoadLibrary(TEXT("advapi32.dll"));
4299 if(hmod)
4301 FARPROC fpCredWriteW;
4302 FARPROC fpCredEnumerateW;
4303 FARPROC fpCredDeleteW;
4304 FARPROC fpCredFree;
4306 fpCredWriteW = GetProcAddress(hmod, "CredWriteW");
4307 fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW");
4308 fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW");
4309 fpCredFree = GetProcAddress(hmod, "CredFree");
4311 if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree)
4313 g_CredWriteW = (CREDWRITEW *)fpCredWriteW;
4314 g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW;
4315 g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW;
4316 g_CredFree = (CREDFREE *)fpCredFree;
4318 g_CredInited = 1;
4322 mswin_set_erasecreds_callback(ask_erase_credentials);
4325 return g_CredInited;
4328 #endif /* WINCRED */