* Bug fix: Tcp and http debug information is not printed unless the
[alpine.git] / alpine / imap.c
blob578281adb18557c887ee782f108ffbd09240e1df
1 /*
2 * ========================================================================
3 * Copyright 2013-2021 Eduardo Chappa
4 * Copyright 2006-2009 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 /*======================================================================
16 imap.c
17 The call back routines for the c-client/imap
18 - handles error messages and other notification
19 - handles prelimirary notification of new mail and expunged mail
20 - prompting for imap server login and password
22 ====*/
24 #include "headers.h"
25 #include "alpine.h"
26 #include "imap.h"
27 #include "status.h"
28 #include "mailview.h"
29 #include "mailcmd.h"
30 #include "radio.h"
31 #include "keymenu.h"
32 #include "signal.h"
33 #include "mailpart.h"
34 #include "mailindx.h"
35 #include "arg.h"
36 #include "busy.h"
37 #include "titlebar.h"
38 #include "xoauth2.h"
39 #include "xoauth2conf.h"
40 #include "confscroll.h"
41 #include "init.h"
42 #include "../pith/state.h"
43 #include "../pith/conf.h"
44 #include "../pith/msgno.h"
45 #include "../pith/filter.h"
46 #include "../pith/news.h"
47 #include "../pith/util.h"
48 #include "../pith/list.h"
49 #include "../pith/margin.h"
50 #ifdef SMIME
51 #include "../pith/smime.h"
52 #endif /* SMIME */
54 #if (WINCRED > 0)
55 #include <wincred.h>
56 #define TNAME "UWash_Alpine_"
57 #define TNAMESTAR "UWash_Alpine_*"
58 #define PWDBUFFERSIZE (250)
59 #define MAXPWDBUFFERSIZE (2*PWDBUFFERSIZE) /* This number must be less than 512 */
62 * WinCred Function prototypes
64 typedef BOOL (WINAPI CREDWRITEW) ( __in PCREDENTIALW Credential, __in DWORD Flags );
65 typedef BOOL (WINAPI CREDENUMERATEW) ( __in LPCWSTR Filter, __reserved DWORD Flags,
66 __out DWORD *Count, __deref_out_ecount(*Count) PCREDENTIALW **Credential );
67 typedef BOOL (WINAPI CREDDELETEW) ( __in LPCWSTR TargetName, __in DWORD Type,
68 __reserved DWORD Flags );
69 typedef VOID (WINAPI CREDFREE) ( __in PVOID Buffer );
72 * WinCred functions
74 int g_CredInited = 0; /* 1 for loaded successfully,
75 * -1 for not available.
76 * 0 for not initialized yet.
78 CREDWRITEW *g_CredWriteW;
79 CREDENUMERATEW *g_CredEnumerateW;
80 CREDDELETEW *g_CredDeleteW;
81 CREDFREE *g_CredFree;
83 #endif /* WINCRED */
85 #ifdef APPLEKEYCHAIN
86 #include <Security/SecKeychain.h>
87 #include <Security/SecKeychainItem.h>
88 #include <Security/SecKeychainSearch.h>
89 #define TNAME "UWash_Alpine"
90 #define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
92 int macos_store_pass_prompt(void);
93 void macos_set_store_pass_prompt(int);
95 static int storepassprompt = -1;
96 #endif /* APPLEKEYCHAIN */
100 * Internal prototypes
102 void mm_login_alt_cue(NETMBX *);
103 long pine_tcptimeout_noscreen(long, long, char *);
104 int answer_cert_failure(int, MSGNO_S *, SCROLL_S *);
105 int oauth2_auth_answer(int, MSGNO_S *, SCROLL_S *);
106 OAUTH2_S *oauth2_select_flow(char *);
107 int xoauth2_flow_tool(struct pine *, int, CONF_S **, unsigned int);
108 void cache_method_message(STORE_S *);
109 void cache_method_message_no_screen(void);
111 #ifdef LOCAL_PASSWD_CACHE
112 int cache_method_was_setup(char *);
113 int read_passfile(char *, MMLOGIN_S **);
114 void write_passfile(char *, MMLOGIN_S *);
115 int preserve_prompt(char *);
116 int preserve_prompt_auth(char *, char *authtype);
117 void update_passfile_hostlist(char *, char *, STRLIST_S *, int);
118 void update_passfile_hostlist_auth(char *, char *, STRLIST_S *, int, char *);
119 void free_passfile_cache_work(MMLOGIN_S **);
121 static MMLOGIN_S *passfile_cache = NULL;
122 static int using_passfile = -1;
123 int save_password = 1;
124 #endif /* LOCAL_PASSWD_CACHE */
126 #ifdef PASSFILE
127 char xlate_in(int);
128 char xlate_out(char);
129 int line_get(char *, size_t, char **);
130 #endif /* PASSFILE */
132 #if (WINCRED > 0)
133 void ask_erase_credentials(void);
134 int init_wincred_funcs(void);
135 #endif /* WINCRED */
138 static char *details_cert, *details_host, *details_reason;
140 extern XOAUTH2_INFO_S xoauth_default[];
141 extern OAUTH2_S alpine_oauth2_list[];
143 void
144 cache_method_message(STORE_S *in_store)
146 #ifdef LOCAL_PASSWD_CACHE
147 if(cache_method_was_setup(ps_global->pinerc)){
148 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 "));
149 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 "));
150 so_puts(in_store, _("alive at the end of this process, and your connection will proceed from there."));
152 else{
153 q_status_message(SM_ORDER | SM_DING, 3, 3, _("Create a password file in order to save the login information"));
154 so_puts(in_store, _("</P><P> Although your version if Alpine was compiled with password file support, this has not been set up yet. "));
155 so_puts(in_store, _("as a result, the next time you open this folder, you will go through this process once again.\n\n"));
157 #else
158 so_puts(in_store, _("</P><P> Your version of Alpine was not built with password file support, as a result you will not be able to save your "));
159 so_puts(in_store, _("access token, which means that you will have to repeat this process the next time you login to this server. "));
160 so_puts(in_store, _("You can fix this by either recompiling alpine with password file support or by downloading a version of Alpine to your "));
161 so_puts(in_store, _("computer that was compiled with password file support."));
162 #endif /* LOCAL_PASSWD_CACHE */
165 void
166 cache_method_message_no_screen (void)
168 #ifdef LOCAL_PASSWD_CACHE
169 if(cache_method_was_setup(ps_global->pinerc)){
170 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
171 _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
172 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
174 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
175 "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
176 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
178 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
179 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
180 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
182 else{
183 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
184 _(" Although your version if Alpine was compiled with password file support, this has not been set up yet. "));
185 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
186 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
187 _("as a result, the next time you open this folder, you will go through this process once again.\n\n"));
188 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
190 #else
191 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
192 _("Your version of Alpine was not built with password file support, as a result you will not be able to save your "));
193 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
194 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
195 _("access token, which means that you will have to repeat this process the next time you login to this server. "));
196 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
197 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
198 _("You can fix this by either recompiling alpine with password file support or by downloading a version of Alpine to your "));
199 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
200 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
201 _("computer that was compiled with password file support.\n\n"));
202 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
203 #endif /* LOCAL_PASSWD_CACHE */
207 xoauth2_flow_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
209 int rv = 0;
211 switch(cmd){
212 case MC_CHOICE:
213 *((*cl)->d.xf.selected) = (*cl)->d.xf.pat;
214 rv = simple_exit_cmd(flags);
216 case MC_EXIT:
217 rv = simple_exit_cmd(flags);
218 break;
220 default:
221 rv = -1;
224 if(rv > 0)
225 ps->mangled_body = 1;
227 return rv;
230 OAUTH2_S *
231 oauth2_select_flow(char *host)
233 OAUTH2_S *oa2list, *oa2;
234 int i = 0, rv;
235 char *method;
237 if(ps_global->ttyo){
238 CONF_S *ctmp = NULL, *first_line = NULL;
239 OAUTH2_S *x_sel = NULL;
240 OPT_SCREEN_S screen;
241 char tmp[1024];
243 dprint((9, "xoauth2 select flow"));
244 ps_global->next_screen = SCREEN_FUN_NULL;
246 memset(&screen, 0, sizeof(screen));
248 for(i = 0; i < sizeof(tmp) && i < ps_global->ttyo->screen_cols; i++)
249 tmp[i] = '-';
250 tmp[i] = '\0';
252 new_confline(&ctmp);
253 ctmp->flags |= CF_NOSELECT;
254 ctmp->value = cpystr(tmp);
256 new_confline(&ctmp);
257 ctmp->flags |= CF_NOSELECT;
258 ctmp->value = cpystr(_("Please select below the authorization flow you would like to follow:"));
260 new_confline(&ctmp);
261 ctmp->flags |= CF_NOSELECT;
262 ctmp->value = cpystr(tmp);
264 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
265 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
266 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
267 new_confline(&ctmp);
268 if(!first_line)
269 first_line = ctmp;
270 method = oa2list->server_mthd[0].name ? "Authorize"
271 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
272 sprintf(tmp, "%s (%s)", oa2list->name, method);
273 ctmp->value = cpystr(tmp);
274 ctmp->d.xf.selected = &x_sel;
275 ctmp->d.xf.pat = oa2list;
276 ctmp->keymenu = &xoauth2_id_select_km;
277 ctmp->help = NO_HELP;
278 ctmp->help_title = NULL;
279 ctmp->tool = xoauth2_flow_tool;
280 ctmp->flags = CF_STARTITEM;
281 ctmp->valoffset = 4;
284 (void)conf_scroll_screen(ps_global, &screen, first_line, _("SELECT AUTHORIZATION FLOW"),
285 _("xoauth2"), 0, NULL);
286 oa2 = x_sel;
288 else{
289 char *s;
290 char prompt[1024];
291 char reply[1024];
292 int sel, n = 0, j;
294 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++)
295 n += strlen((char *) oa2list->name) + 5; /* number, parenthesis, space */
296 n += 1024; /* large enough to display lines of 80 characters in UTF-8 */
297 s = fs_get(n*sizeof(char));
298 strcpy(s, _("Please select below the authorization flow you would like to follow:"));
299 sprintf(s + strlen(s), _("Please select the client-id to use from the following list.\n\n"));
300 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
301 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
302 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i])
303 sprintf(s + strlen(s), " %d) %.70s\n", j++, oa2list->name);
305 display_init_err(s, 0);
307 strncpy(prompt, _("Enter your selection number: "), sizeof(prompt));
308 prompt[sizeof(prompt)-1] = '\0';
310 rv = optionally_enter(reply, 0, 0, sizeof(reply), prompt, NULL, NO_HELP, 0);
311 sel = atoi(reply);
312 rv = (sel >= 0 && sel < i) ? 0 : -1;
313 } while (rv != 0);
315 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
316 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
317 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
318 if(j == sel) break;
319 else j++;
322 oa2 = oa2list;
324 return oa2;
327 typedef struct auth_code_s {
328 char *code;
329 int answer;
330 } AUTH_CODE_S;
333 oauth2device_decode_reply(void *datap, void *replyp)
335 OAUTH2_DEVICEPROC_S *av = (OAUTH2_DEVICEPROC_S *)datap;
336 int reply = *(int *) replyp;
338 return reply < 0 ? av->code_failure : (reply == 0 ? av->code_success : av->code_wait);
342 oauth2_elapsed_done(void *aux_valuep)
344 OAUTH2_S *oauth2 = aux_valuep ? ((OAUTH2_DEVICEPROC_S *) aux_valuep)->xoauth2 : NULL;
345 static time_t savedt = 0, now;
346 int rv = 0;
348 if(aux_valuep == NULL) savedt = 0; /* reset last time we approved */
349 else{
350 now = time(0);
351 if(oauth2->devicecode.interval + now >= savedt)
352 savedt = now;
353 else
354 rv = -1;
356 return rv;
359 void
360 oauth2_set_device_info(OAUTH2_S *oa2, char *method)
362 char tmp[MAILTMPLEN];
363 char *name = (char *) oa2->name;
364 int aux_rv_value;
365 OAUTH2_DEVICECODE_S *deviceinfo = &oa2->devicecode;
366 OAUTH2_DEVICEPROC_S aux_value;
368 if(ps_global->ttyo){
369 SCROLL_S sargs;
370 STORE_S *in_store, *out_store;
371 gf_io_t pc, gc;
372 HANDLE_S *handles = NULL;
373 AUTH_CODE_S user_input;
375 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
376 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
377 goto try_wantto;
379 aux_value.xoauth2 = oa2;
380 aux_value.code_success = 'e';
381 aux_value.code_failure = 'e';
382 aux_value.code_wait = NO_OP_COMMAND;
384 so_puts(in_store, "<HTML><P>");
385 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name);
386 so_puts(in_store, tmp);
387 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name, method),
388 so_puts(in_store, tmp);
390 if(deviceinfo->verification_uri && deviceinfo->user_code){
391 sprintf(tmp,
392 _("</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."),
393 deviceinfo->verification_uri, deviceinfo->verification_uri, deviceinfo->user_code);
394 so_puts(in_store, tmp);
396 else{
397 so_puts(in_store, "</P><P>");
398 so_puts(in_store, (char *) deviceinfo->message);
400 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
401 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), name);
402 so_puts(in_store, tmp);
403 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
405 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 "));
406 so_puts(in_store, _("to grant access to Alpine to your data. "));
408 cache_method_message(in_store);
410 so_puts(in_store, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit"));
411 so_puts(in_store, _("</P></HTML>"));
413 so_seek(in_store, 0L, 0);
414 init_handles(&handles);
415 gf_filter_init();
416 gf_link_filter(gf_html2plain,
417 gf_html2plain_opt(NULL,
418 ps_global->ttyo->screen_cols, non_messageview_margin(),
419 &handles, NULL, GFHP_LOCAL_HANDLES));
420 gf_set_so_readc(&gc, in_store);
421 gf_set_so_writec(&pc, out_store);
422 gf_pipe(gc, pc);
423 gf_clear_so_writec(out_store);
424 gf_clear_so_readc(in_store);
426 memset(&sargs, 0, sizeof(SCROLL_S));
427 sargs.text.handles = handles;
428 sargs.text.text = so_text(out_store);
429 sargs.text.src = CharStar;
430 sargs.text.desc = _("help text");
431 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
432 sargs.proc.tool = oauth2_auth_answer;
433 sargs.proc.data.p = (void *)&user_input;
434 sargs.keys.menu = &oauth2_device_auth_keymenu;
435 /* don't want to re-enter c-client */
436 sargs.quell_newmail = 1;
437 setbitmap(sargs.keys.bitmap);
438 sargs.help.text = h_oauth2_start;
439 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
440 sargs.aux_function = oauth2deviceinfo_get_accesscode;
441 sargs.aux_value = (void *) &aux_value;
442 sargs.aux_condition = oauth2_elapsed_done;
443 sargs.decode_aux_rv_value = oauth2device_decode_reply;
444 sargs.aux_rv_value = (void *) &aux_rv_value;
446 do {
447 scrolltool(&sargs);
448 ps_global->mangled_screen = 1;
449 ps_global->painted_body_on_startup = 0;
450 ps_global->painted_footer_on_startup = 0;
451 } while (user_input.answer != 'e');
453 so_give(&in_store);
454 so_give(&out_store);
455 free_handles(&handles);
456 oauth2_elapsed_done(NULL);
458 else{
460 * If screen hasn't been initialized yet, use want_to.
462 try_wantto:
464 tmp_20k_buf[0] = '\0';
465 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
466 _("Authorizing Alpine Access to %s Email Services\n\n"), name);
467 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
469 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
470 _("Alpine is attempting to log you into your %s account, using the %s method. "), name, method),
471 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
473 if(deviceinfo->verification_uri && deviceinfo->user_code){
474 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
475 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
476 deviceinfo->verification_uri, deviceinfo->user_code);
477 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
479 else{
480 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
481 "%s\n\n", deviceinfo->message);
482 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
485 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
486 _("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);
487 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
489 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
490 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
491 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
493 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
494 "%s", _("to grant access to Alpine to your data.\n\n"));
495 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
497 cache_method_message_no_screen();
499 display_init_err(tmp_20k_buf, 0);
500 memset((void *)tmp, 0, sizeof(tmp));
501 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
502 tmp[sizeof(tmp)-1] = '\0';
504 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
505 int rv;
506 UCS ch;
508 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
509 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
510 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
512 aux_value.xoauth2 = oa2;
513 aux_value.code_success = 'y';
514 aux_value.code_failure = 'n';
515 aux_value.code_wait = 'w';
517 strncpy(tmp, _("Continue waiting"), sizeof(tmp));
518 tmp[sizeof(tmp)-1] = '\0';
519 do {
520 if(oauth2_elapsed_done((void *) &aux_value) == 0)
521 oauth2deviceinfo_get_accesscode((void *) &aux_value, (void *) &rv);
522 ch = oauth2device_decode_reply((void *) &aux_value, (void *) &rv);
523 } while (ch == 'w' || want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y');
524 oauth2_elapsed_done(NULL);
529 char *
530 oauth2_get_access_code(unsigned char *url, char *method, OAUTH2_S *oauth2, int *tryanother)
532 char tmp[MAILTMPLEN];
533 char *code = NULL;
535 if(ps_global->ttyo){
536 SCROLL_S sargs;
537 STORE_S *in_store, *out_store;
538 gf_io_t pc, gc;
539 HANDLE_S *handles = NULL;
540 AUTH_CODE_S user_input;
542 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
543 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
544 goto try_wantto;
546 so_puts(in_store, "<HTML><BODY><P>");
547 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2->name);
548 so_puts(in_store, tmp);
549 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
550 so_puts(in_store, tmp);
552 if(strucmp((char *) oauth2->name, (char *) GMAIL_NAME) == 0){
553 so_puts(in_store, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
554 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."));
555 so_puts(in_store, _("<UL> "));
556 so_puts(in_store, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
557 so_puts(in_store, _("and create a project. The name of the project is not important."));
558 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."));
559 so_puts(in_store, _("<LI> Create OAUTH Credentials."));
560 so_puts(in_store, _("</UL> "));
561 so_puts(in_store, _("<P> As a result of this process, you will get a client-id and a client-secret."));
562 so_puts(in_store, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
563 so_puts(in_store, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
564 so_puts(in_store, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
565 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."));
568 so_puts(in_store, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
569 so_puts(in_store,"</P><P>");
570 sprintf(tmp_20k_buf, _("<A HREF=\"%s\">%s</A>"), url, url);
571 so_puts(in_store, tmp_20k_buf);
573 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
574 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2->name);
575 so_puts(in_store, tmp);
576 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
578 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. "));
579 so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page."));
580 so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
581 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. "));
583 cache_method_message(in_store);
585 so_puts(in_store, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
586 so_puts(in_store, _("</P></BODY></HTML>"));
588 so_seek(in_store, 0L, 0);
589 init_handles(&handles);
590 gf_filter_init();
591 gf_link_filter(gf_html2plain,
592 gf_html2plain_opt(NULL,
593 ps_global->ttyo->screen_cols, non_messageview_margin(),
594 &handles, NULL, GFHP_LOCAL_HANDLES));
595 gf_set_so_readc(&gc, in_store);
596 gf_set_so_writec(&pc, out_store);
597 gf_pipe(gc, pc);
598 gf_clear_so_writec(out_store);
599 gf_clear_so_readc(in_store);
601 memset(&sargs, 0, sizeof(SCROLL_S));
602 sargs.text.handles = handles;
603 sargs.text.text = so_text(out_store);
604 sargs.text.src = CharStar;
605 sargs.text.desc = _("help text");
606 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
607 sargs.proc.tool = oauth2_auth_answer;
608 sargs.proc.data.p = (void *)&user_input;
609 sargs.keys.menu = &oauth2_auth_keymenu;
610 /* don't want to re-enter c-client */
611 sargs.quell_newmail = 1;
612 setbitmap(sargs.keys.bitmap);
613 sargs.help.text = h_oauth2_start;
614 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
616 do {
617 scrolltool(&sargs);
618 ps_global->mangled_screen = 1;
619 ps_global->painted_body_on_startup = 0;
620 ps_global->painted_footer_on_startup = 0;
621 } while (user_input.answer != 'e');
623 if(!struncmp(user_input.code, "http://", 7)
624 || !struncmp(user_input.code, "https://", 8)){
625 char *s, *t;
626 s = strstr(user_input.code, "code=");
627 if(s != NULL){
628 t = strchr(s, '&');
629 if(t) *t = '\0';
630 code = cpystr(s+5);
631 if(t) *t = '&';
634 else code = user_input.code ? cpystr(user_input.code) : NULL;
635 if(user_input.code) fs_give((void **) &user_input.code);
637 if(code == NULL) *tryanother = 1;
639 so_give(&in_store);
640 so_give(&out_store);
641 free_handles(&handles);
643 else{
644 int flags, rc, q_line;
645 /* TRANSLATORS: user needs to input an access code from the server */
646 char *accesscodelabel = _("Copy and Paste Access Code");
647 char prompt[MAILTMPLEN], token[MAILTMPLEN];
649 * If screen hasn't been initialized yet, use want_to.
651 try_wantto:
652 tmp_20k_buf[0] = '\0';
653 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
654 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2->name);
655 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
657 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
658 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
659 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
661 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
662 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
663 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
665 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
666 "%s\n\n", url);
667 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
669 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
670 _("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);
671 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
673 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
674 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
675 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
677 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
678 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
679 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
681 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
682 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
683 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
685 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
686 "%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. "));
687 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
689 cache_method_message_no_screen();
691 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
692 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
693 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
695 display_init_err(tmp_20k_buf, 0);
696 memset((void *)tmp, 0, sizeof(tmp));
697 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
698 tmp[sizeof(tmp)-1] = '\0';
700 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
701 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
702 flags = OE_APPEND_CURRENT;
703 sprintf(prompt, "%s: ", accesscodelabel);
704 do {
705 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
706 prompt, NULL, NO_HELP, &flags);
707 } while (rc != 0 && rc != 1);
708 if(!struncmp(token, "http://", 7)
709 || !struncmp(token, "https://", 8)){
710 char *s, *t;
711 s = strstr(token, "code=");
712 if(s != NULL){
713 t = strchr(s, '&');
714 if(t) *t = '\0';
715 code = cpystr(s+5);
716 if(t) *t = '&';
719 else code = token[0] ? cpystr(token) : NULL;
723 return code;
726 void mm_login_oauth2(NETMBX *, char *, char *, OAUTH2_S *, long int, char *, char *);
728 /* The purpose of this function is to report to c-client the values of the
729 * different tokens and codes so that c-client can try to log in the user
730 * to the server. This function DOES NOT attempt to get these values for
731 * the user. That is attempted in the c-client side (as best as it can be
732 * done given our circumstances: no http support, no javascript support,
733 * etc.). This is the best we can do:
735 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
736 * as best as we can. Unloaded means that there is no server known to
737 * connect, no access token, etc. Pretty much nothing is known about
738 * how to get access code, access token, etc. We ask the user to fill
739 * it up for us, if they can. If the user fills it up we save those
740 * values.
742 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
743 * save the information that c-client got for us.
745 * 3. When saving this information we use the password caching facilities,
746 * but we must do it in a different format so that old information and
747 * new information are not mixed. In order to accommodate this for new
748 * authentication methods, we save the information in the same fields,
749 * but this time we modify it slightly, so that old functions fail to
750 * understand the new information and so not modify it nor use it. The
751 * modification is simple: Every piece of information that was saved
752 * before is prepended XOAUTH2\001 to indicate the authentication
753 * method for which it works. The character \001 is a separator. New
754 * Alpine will know how to deal with this, but old versions, will not
755 * strip this prefix from the information and fail to get the
756 * information or modify it when needed. Only new versions of Alpine will
757 * know how to process this information.
758 * new_value = authenticator_method separator old_value
759 * authenticator_method = "XOAUTH2_S"
760 * separator = "U+1"
761 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
762 * authenticator is "XOAUTH2\001imap.gmail.com".
763 * In addition, the password field is not used to encode the password
764 * anymore, it is used to save login information needed in a format that
765 * the caller function chooses, but that must be preceded by the
766 * "authenticator_method separator" code as above.
768 void
769 mm_login_oauth2(NETMBX *mb, char *user, char *method,
770 OAUTH2_S *login, long int trial,
771 char *usethisprompt, char *altuserforcache)
773 char *token, tmp[MAILTMPLEN];
774 char prompt[4*MAILTMPLEN];
775 char *OldRefreshToken, *OldAccessToken;
776 char *NewRefreshToken, *NewAccessToken;
777 char *SaveRefreshToken, *SaveAccessToken;
778 /* TRANSLATORS: A label for the hostname that the user is logging in on */
779 char *hostlabel = _("HOST");
780 /* TRANSLATORS: user is logging in as a particular user (a particular
781 login name), this is just labelling that user name. */
782 char *userlabel = _("USER");
783 STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1];
784 int len, q_line, flags, i, j;
785 int save_in_init;
786 int registered;
787 int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
788 OAUTH2_S *oa2list, *oa2;
789 XOAUTH2_INFO_S *x;
791 unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
792 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
793 int preserve_password = -1;
794 #endif
796 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
797 trial, mb->user ? mb->user : "(null)",
798 mb->service ? mb->service : "(null)",
799 mb->port ? " port=" : "",
800 mb->port ? comatose(mb->port) : "",
801 altuserforcache ? " altuserforcache =" : "",
802 altuserforcache ? altuserforcache : ""));
804 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
806 save_in_init = ps_global->in_init_seq;
807 ps_global->in_init_seq = 0;
808 ps_global->no_newmail_check_from_optionally_enter = 1;
810 /* make sure errors are seen */
811 if(ps_global->ttyo && !ps_global->noshow_error)
812 flush_status_messages(0);
814 token = NULL; /* start from scratch */
816 hostlist[0].name = mb->host;
817 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
818 hostlist[0].next = &hostlist[1];
819 hostlist[1].name = mb->orighost;
820 hostlist[1].next = NULL;
822 else
823 hostlist[0].next = NULL;
825 if(hostlist[0].name){
826 dprint((9, "mm_login_oauth2: host=%s\n",
827 hostlist[0].name ? hostlist[0].name : "?"));
828 if(hostlist[0].next && hostlist[1].name){
829 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name));
833 if(trial == 0L && !altuserforcache){
834 if(*mb->user != '\0')
835 strncpy(user, mb->user, NETMAXUSER);
836 else{
837 flags = OE_APPEND_CURRENT;
838 sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
839 optionally_enter(user, q_line, 0, NETMAXUSER, prompt, NULL, NO_HELP, &flags);
841 user[NETMAXUSER-1] = '\0';
845 * We check to see if the server we are going to log in to is already
846 * registered. This gives us a list of servers with the same
847 * credentials, so we use the same credentials for all of them.
850 for(registered = 0, oa2list = alpine_oauth2_list;
851 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
852 oa2list++){
853 for(i = 0; i < OAUTH2_TOT_EQUIV
854 && oa2list->host[i] != NULL
855 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
856 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
857 registered++;
858 break;
862 if(registered){
863 x = oauth2_get_client_info(oa2list->name, user);
864 if(x && x->flow){
865 for(oa2list = alpine_oauth2_list;
866 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
867 oa2list++){
868 for(i = 0; i < OAUTH2_TOT_EQUIV
869 && oa2list->host[i] != NULL
870 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
871 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
872 char *flow = oa2list->server_mthd[0].name ? "Authorize"
873 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
874 if(!strucmp(x->flow, flow)) break; /* found it */
878 /* else use the one we found earlier, the user has to configure this better */
881 if(registered){
882 hostlist2[i = 0].name = mb->host;
883 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
884 hostlist2[++i].name = mb->orighost;
886 for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){
887 int k;
888 for(k = 0; k <= i && hostlist2[k].name
889 && strcmp(hostlist2[k].name, oa2list->host[j]); k++);
890 if(k == i + 1)
891 hostlist2[++i].name = oa2list->host[j];
893 hostlist2[i+1].name = NULL;
894 hostlist2[i+1].next = NULL;
895 for(j = i; j >= 0; j--)
896 hostlist2[j].next = &hostlist2[j+1];
899 if(registered){ /* redo the app_id, no questions asked */
900 free_id(&ps_global->id);
901 ps_global->id = set_alpine_id(oa2list->app_id ? oa2list->app_id : PACKAGE_NAME, PACKAGE_VERSION);
902 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
906 * We check if we have a refresh token saved somewhere, if so
907 * we use it to get a new access token, otherwise we need to
908 * get an access code so we can get (and save) a refresh token
909 * and use the access token.
911 if(trial == 0L && !altuserforcache){
912 /* Search for a refresh token that is already loaded ... */
913 if(imap_get_passwd_auth(mm_login_list, &token, user,
914 registered ? hostlist2 : hostlist,
915 (mb->sslflag||mb->tlsflag), OA2NAME)){
916 dprint((9, "mm_login_oauth2: found a refresh token\n"));
917 ps_global->no_newmail_check_from_optionally_enter = 0;
918 ps_global->in_init_seq = save_in_init;
920 #ifdef LOCAL_PASSWD_CACHE
921 /* or see if we have saved one in the local password cache and load it */
922 else if(get_passfile_passwd_auth(ps_global->pinerc, &token,
923 user, registered ? hostlist2 : hostlist,
924 (mb->sslflag||mb->tlsflag), OA2NAME)){
925 imap_set_passwd_auth(&mm_login_list, token, user,
926 hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
927 update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist,
928 (mb->sslflag||mb->tlsflag), OA2NAME);
929 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
930 ps_global->no_newmail_check_from_optionally_enter = 0;
931 ps_global->in_init_seq = save_in_init;
933 if(token && *token) preserve_password = 1; /* resave it, no need to ask */
934 #endif /* LOCAL_PASSWD_CACHE */
936 user[NETMAXUSER-1] = '\0';
938 /* The Old* variables is what c_client knows */
939 OldRefreshToken = login->cancel_refresh_token ? NULL : login->param[OA2_RefreshToken].value;
940 OldAccessToken = login->access_token;
941 OldExpirationTime = login->expiration;
943 /* The New* variables is what Alpine knows */
944 NewRefreshToken = NewAccessToken = NULL;
945 NewExpirationTime = 0L;
946 ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0;
948 if(token && *token && !login->cancel_refresh_token){
949 char *s, *t;
951 s = token;
952 t = strchr(s, PWDAUTHSEP);
953 if(t == NULL)
954 NewRefreshToken = cpystr(s);
955 else {
956 *t++ = '\0';
957 NewRefreshToken = cpystr(s);
958 s = t;
959 t = strchr(s, PWDAUTHSEP);
960 if(t == NULL)
961 NewAccessToken = cpystr(s);
962 else {
963 *t++ = '\0';
964 NewAccessToken = cpystr(s);
965 s = t;
966 NewExpirationTime = strtol(s, &s, 10);
967 if(NewExpirationTime <= 0 || NewExpirationTime <= time(0))
968 NewExpirationTime = 0L;
971 /* check we got good information, and send good information below */
972 if(NewRefreshToken && !*NewRefreshToken)
973 fs_give((void **) &NewRefreshToken);
974 if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken))
975 fs_give((void **) &NewAccessToken);
978 if(NewRefreshToken == NULL)
979 login->first_time++;
981 if(login->first_time){ /* count how many authorization methods we support */
982 int nmethods, j;
984 for(nmethods = 0, oa2 = alpine_oauth2_list; oa2 && oa2->name ; oa2++){
985 for(j = 0; j < OAUTH2_TOT_EQUIV
986 && oa2
987 && oa2->host[j] != NULL
988 && strucmp(oa2->host[j], mb->orighost) != 0; j++);
989 if(oa2 && oa2->host && j < OAUTH2_TOT_EQUIV && oa2->host[j]
990 && ((oa2->server_mthd[0].name && (oa2->flags & OA2_AUTHORIZE))
991 || (oa2->server_mthd[1].name && (oa2->flags & OA2_DEVICE))))
992 nmethods++;
995 if(nmethods > 1)
996 oa2list = oauth2_select_flow(mb->orighost);
998 if(!oa2list) registered = 0;
1001 /* Default to saving what we already had saved */
1003 SaveRefreshToken = login->cancel_refresh_token ? NULL : NewRefreshToken;
1004 SaveAccessToken = NewAccessToken;
1005 SaveExpirationTime = NewExpirationTime;
1007 /* Translation of the logic below:
1008 * if (c-client has a refresh token
1009 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1010 forget the Alpine refresh token;
1011 make the Alpine Refresh token = c-client refresh token.;
1012 signal that we changed the refresh token;
1013 In this situation we do not need to clear up the Alpine access token. This will
1014 expire, so we can use it until it expires. We can save the c-client refresh token
1015 together with the Alpine Access Token and the expiration date of the Alpine Access
1016 Token.
1017 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1018 forget the Alpine refresh token;
1019 if Alpine has an access token, forget it;
1020 reset the expiration time
1021 signal that we changed the refresh token; (because the service expired it)
1025 if(OldRefreshToken != NULL
1026 && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){
1027 if(NewRefreshToken) fs_give((void **) &NewRefreshToken);
1028 NewRefreshToken = cpystr(OldRefreshToken);
1029 ChangeRefreshToken++;
1030 SaveRefreshToken = OldRefreshToken;
1031 SaveAccessToken = NewAccessToken;
1032 SaveExpirationTime = NewExpirationTime;
1033 } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){
1034 fs_give((void **) &NewRefreshToken);
1035 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1036 NewExpirationTime = 0L;
1037 ChangeRefreshToken++;
1038 SaveRefreshToken = NULL;
1039 SaveAccessToken = NULL;
1040 SaveExpirationTime = 0L;
1043 if(OldAccessToken != NULL
1044 && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){
1045 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1046 NewAccessToken = cpystr(OldAccessToken);
1047 ChangeAccessToken++;
1048 NewExpirationTime = OldExpirationTime;
1049 SaveRefreshToken = NewRefreshToken;
1050 SaveAccessToken = NewAccessToken;
1051 SaveExpirationTime = NewExpirationTime;
1054 if(!registered){
1055 login->param[OA2_RefreshToken].value = SaveRefreshToken;
1056 login->access_token = SaveAccessToken;
1057 login->expiration = SaveExpirationTime;
1058 } else {
1059 oa2list->param[OA2_RefreshToken].value = SaveRefreshToken;
1060 oa2list->access_token = SaveAccessToken;
1061 oa2list->expiration = SaveExpirationTime;
1062 oa2list->first_time = login->first_time;
1063 oa2list->cancel_refresh_token = login->cancel_refresh_token;
1064 *login = *oa2list; /* load login pointer */
1067 if(!ChangeAccessToken && !ChangeRefreshToken && !login->cancel_refresh_token)
1068 return;
1070 /* get ready to save this information. The format will be
1071 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1072 * (spaces added for clarity, \001 is PWDAUTHSEP)
1074 if(token) fs_give((void **) &token);
1075 sprintf(tmp, "%lu", SaveExpirationTime);
1076 tmp[sizeof(tmp) - 1] = '\0';
1077 len = strlen(SaveRefreshToken ? SaveRefreshToken : "")
1078 + strlen(SaveAccessToken ? SaveAccessToken : "")
1079 + strlen(tmp) + 2;
1080 token = fs_get(len + 1);
1081 sprintf(token, "%s%c%s%c%lu",
1082 SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP,
1083 SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP,
1084 SaveExpirationTime);
1086 /* remember the access information for next time */
1087 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
1088 imap_set_passwd_auth(&mm_login_list, token,
1089 altuserforcache ? altuserforcache : user, hostlist,
1090 (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1091 #ifdef LOCAL_PASSWD_CACHE
1092 /* if requested, remember it on disk for next session */
1093 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
1094 set_passfile_passwd_auth(ps_global->pinerc, token,
1095 altuserforcache ? altuserforcache : user, hostlist,
1096 (mb->sslflag||mb->tlsflag),
1097 (preserve_password == -1 ? 0
1098 : (preserve_password == 0 ? 2 :1)), OA2NAME);
1099 #endif /* LOCAL_PASSWD_CACHE */
1101 ps_global->no_newmail_check_from_optionally_enter = 0;
1104 IDLIST *
1105 set_alpine_id(char *pname, char *pversion)
1107 IDLIST *id;
1109 if(!pname || !pversion) return NULL;
1111 id = fs_get(sizeof(IDLIST));
1112 id->name = cpystr("name");
1113 id->value = cpystr(pname);
1114 id->next = fs_get(sizeof(IDLIST));
1115 id->next->name = cpystr("version");
1116 id->next->value = cpystr(pversion);
1117 id->next->next = NULL;
1118 return id;
1121 /*----------------------------------------------------------------------
1122 receive notification from IMAP
1124 Args: stream -- Mail stream message is relevant to
1125 string -- The message text
1126 errflg -- Set if it is a serious error
1128 Result: message displayed in status line
1130 The facility is for general notices, such as connection to server;
1131 server shutting down etc... It is used infrequently.
1132 ----------------------------------------------------------------------*/
1133 void
1134 mm_notify(MAILSTREAM *stream, char *string, long int errflg)
1136 time_t now;
1137 struct tm *tm_now;
1139 now = time((time_t *)0);
1140 tm_now = localtime(&now);
1142 /* be sure to log the message... */
1143 #ifdef DEBUG
1144 if(ps_global->debug_imap || ps_global->debugmem)
1145 dprint((errflg == TCPDEBUG || errflg == HTTPDEBUG ? 7 : 2,
1146 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1147 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1148 tm_now->tm_mon+1, tm_now->tm_mday,
1149 (!errflg) ? "babble" :
1150 (errflg == ERROR) ? "error" :
1151 (errflg == WARN) ? "warning" :
1152 (errflg == PARSE) ? "parse" :
1153 (errflg == TCPDEBUG) ? "tcp" :
1154 (errflg == HTTPDEBUG) ? "http" :
1155 (errflg == BYE) ? "bye" : "unknown",
1156 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1157 string ? string : "?"));
1158 #endif
1160 snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s",
1161 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1162 (int) MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70),
1163 string);
1164 ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols
1165 : sizeof(ps_global->last_error)-1] = '\0';
1168 * Then either set special bits in the pine struct or
1169 * display the message if it's tagged as an "ALERT" or
1170 * its errflg > NIL (i.e., WARN, or ERROR)
1172 if(errflg == BYE)
1174 * We'd like to sp_mark_stream_dead() here but we can't do that because
1175 * that might call mail_close and we are already in a c-client callback.
1176 * So just set the dead bit and clean it up later.
1178 sp_set_dead_stream(stream, 1);
1179 else if(!strncmp(string, "[TRYCREATE]", 11))
1180 ps_global->try_to_create = 1;
1181 else if(!strncmp(string, "[REFERRAL ", 10))
1182 ; /* handled in the imap_referral() callback */
1183 else if(!strncmp(string, "[ALERT]", 7))
1184 q_status_message2(SM_MODAL, 3, 3,
1185 _("Alert received while accessing \"%s\": %s"),
1186 (stream && stream->mailbox)
1187 ? stream->mailbox : "-no folder-",
1188 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000),
1189 SIZEOF_20KBUF-10000, string));
1190 else if(!strncmp(string, "[UNSEEN ", 8)){
1191 char *p;
1192 long n = 0;
1194 for(p = string + 8; isdigit(*p); p++)
1195 n = (n * 10) + (*p - '0');
1197 sp_set_first_unseen(stream, n);
1199 else if(!strncmp(string, "[READ-ONLY]", 11)
1200 && !(stream && stream->mailbox && IS_NEWS(stream)))
1201 q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s",
1202 (stream && stream->mailbox)
1203 ? stream->mailbox : "-no folder-",
1204 string + 11);
1205 else if((errflg && errflg != BYE && errflg != PARSE)
1206 && !ps_global->noshow_error
1207 && !(errflg == WARN
1208 && (ps_global->noshow_warn || (stream && stream->unhealthy))))
1209 q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0),
1210 3, 6, ps_global->last_error);
1214 /*----------------------------------------------------------------------
1215 Queue imap log message for display in the message line
1217 Args: string -- The message
1218 errflg -- flag set to 1 if pertains to an error
1220 Result: Message queued for display
1222 The c-client/imap reports most of it's status and errors here
1223 ---*/
1224 void
1225 mm_log(char *string, long int errflg)
1227 char message[sizeof(ps_global->c_client_error)];
1228 char *occurence;
1229 int was_capitalized;
1230 static char saw_kerberos_init_warning;
1231 time_t now;
1232 struct tm *tm_now;
1234 now = time((time_t *)0);
1235 tm_now = localtime(&now);
1237 dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? debug :
1238 (errflg == TCPDEBUG) ? 10 :
1239 ((errflg == HTTPDEBUG) && ps_global->debug_http) ? debug :
1240 (errflg == HTTPDEBUG) ? 10 : 2,
1241 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1242 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1243 tm_now->tm_mon+1, tm_now->tm_mday,
1244 (!errflg) ? "babble" :
1245 (errflg == ERROR) ? "error" :
1246 (errflg == WARN) ? "warning" :
1247 (errflg == PARSE) ? "parse" :
1248 (errflg == TCPDEBUG) ? "tcp" :
1249 (errflg == HTTPDEBUG) ? "http" :
1250 (errflg == BYE) ? "bye" : "unknown",
1251 string ? string : "?"));
1253 if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){
1254 ps_global->try_to_create = 1;
1255 return;
1257 else if(ps_global->try_to_create
1258 || !strncmp(string, "[CLOSED]", 8)
1259 || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op")))
1261 * Don't display if creating new folder OR
1262 * warning about a dead stream ...
1264 return;
1266 strncpy(message, string, sizeof(message));
1267 message[sizeof(message) - 1] = '\0';
1269 if(errflg == WARN && srchstr(message, "try running kinit") != NULL){
1270 if(saw_kerberos_init_warning)
1271 return;
1273 saw_kerberos_init_warning = 1;
1276 /*---- replace all "mailbox" with "folder" ------*/
1277 occurence = srchstr(message, "mailbox");
1278 while(occurence) {
1279 if(!*(occurence+7)
1280 || isspace((unsigned char) *(occurence+7))
1281 || *(occurence+7) == ':'){
1282 was_capitalized = isupper((unsigned char) *occurence);
1283 rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder"));
1284 if(was_capitalized)
1285 *occurence = (errflg == PARSE ? 'A' : 'F');
1287 else
1288 occurence += 7;
1290 occurence = srchstr(occurence, "mailbox");
1293 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1294 occurence = srchstr(message, "GSSAPI");
1295 while(occurence) {
1296 if(!*(occurence+6)
1297 || isspace((unsigned char) *(occurence+6))
1298 || *(occurence+6) == ':')
1299 rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos");
1300 else
1301 occurence += 6;
1303 occurence = srchstr(occurence, "GSSAPI");
1306 if(errflg == ERROR)
1307 ps_global->mm_log_error = 1;
1309 if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error))
1310 strncpy(ps_global->c_client_error, message,
1311 sizeof(ps_global->c_client_error));
1313 if(ps_global->noshow_error
1314 || (ps_global->noshow_warn && errflg == WARN)
1315 || !(errflg == ERROR || errflg == WARN))
1316 return; /* Only care about errors; don't print when asked not to */
1318 /*---- Display the message ------*/
1319 q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER,
1320 3, 5, message);
1321 strncpy(ps_global->last_error, message, sizeof(ps_global->last_error));
1322 ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
1325 void
1326 mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial,
1327 char *method, char *usethisprompt, char *altuserforcache)
1329 if(method == NULL)
1330 return;
1331 if(strucmp(method, OA2NAME) == 0 || strucmp(method, BEARERNAME) == 0)
1332 mm_login_oauth2(mb, user, method, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache);
1335 void
1336 mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial,
1337 char *usethisprompt, char *altuserforcache)
1339 char tmp[MAILTMPLEN];
1340 char prompt[1000], *last;
1341 char port[20], non_def_port[20], insecure[20];
1342 char defuser[NETMAXUSER];
1343 char hostleadin[80], hostname[200], defubuf[200];
1344 char logleadin[80], pwleadin[50];
1345 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
1346 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1347 that the password will be sent unencrypted over the network. This is
1348 just a warning message that gets added parenthetically when the user
1349 is asked for a password. */
1350 char *insec = _(" (INSECURE)");
1351 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1352 after having already failed at least once. */
1353 char *retry = _("Retrying - ");
1354 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1355 char *hostlabel = _("HOST");
1356 /* TRANSLATORS: user is logging in as a particular user (a particular
1357 login name), this is just labelling that user name. */
1358 char *userlabel = _("USER");
1359 STRLIST_S hostlist[2];
1360 HelpType help ;
1361 int len, rc, q_line, flags;
1362 int oespace, avail, need, save_dont_use;
1363 int save_in_init;
1364 struct servent *sv;
1365 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1366 int preserve_password = -1;
1367 #endif
1369 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1370 trial, mb->user ? mb->user : "(null)",
1371 mb->service ? mb->service : "(null)",
1372 mb->port ? " port=" : "",
1373 mb->port ? comatose(mb->port) : "",
1374 altuserforcache ? " altuserforcache =" : "",
1375 altuserforcache ? altuserforcache : ""));
1376 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
1378 save_in_init = ps_global->in_init_seq;
1379 ps_global->in_init_seq = 0;
1380 ps_global->no_newmail_check_from_optionally_enter = 1;
1382 /* make sure errors are seen */
1383 if(ps_global->ttyo)
1384 flush_status_messages(0);
1386 /* redo app id in case we are logging in to an IMAP server that supports the IMAP ID extension */
1387 free_id(&ps_global->id);
1388 ps_global->id = set_alpine_id(PACKAGE_NAME, PACKAGE_VERSION);
1389 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
1392 * Add port number to hostname if going through a tunnel or something
1394 non_def_port[0] = '\0';
1395 if(mb->port && mb->service &&
1396 (sv = getservbyname(mb->service, "tcp")) &&
1397 (mb->port != ntohs(sv->s_port))){
1398 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
1399 non_def_port[sizeof(non_def_port)-1] = '\0';
1400 dprint((9, "mm_login: using non-default port=%s\n", non_def_port));
1404 * set up host list for sybil servers...
1406 if(*non_def_port){
1407 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
1408 hostlist0[sizeof(hostlist0)-1] = '\0';
1409 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
1410 hostlist0[sizeof(hostlist0)-1] = '\0';
1411 hostlist[0].name = hostlist0;
1412 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1413 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
1414 hostlist1[sizeof(hostlist1)-1] = '\0';
1415 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
1416 hostlist1[sizeof(hostlist1)-1] = '\0';
1417 hostlist[0].next = &hostlist[1];
1418 hostlist[1].name = hostlist1;
1419 hostlist[1].next = NULL;
1421 else
1422 hostlist[0].next = NULL;
1424 else{
1425 hostlist[0].name = mb->host;
1426 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1427 hostlist[0].next = &hostlist[1];
1428 hostlist[1].name = mb->orighost;
1429 hostlist[1].next = NULL;
1431 else
1432 hostlist[0].next = NULL;
1435 if(hostlist[0].name){
1436 dprint((9, "mm_login: host=%s\n",
1437 hostlist[0].name ? hostlist[0].name : "?"));
1438 if(hostlist[0].next && hostlist[1].name){
1439 dprint((9, "mm_login: orighost=%s\n", hostlist[1].name));
1444 * Initialize user name with either
1445 * 1) /user= value in the stream being logged into,
1446 * or 2) the user name we're running under.
1448 * Note that VAR_USER_ID is not yet initialized if this login is
1449 * the one to access the remote config file. In that case, the user
1450 * can supply the username in the config file name with /user=.
1452 if(trial == 0L && !altuserforcache){
1453 strncpy(user, (*mb->user) ? mb->user :
1454 ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "",
1455 NETMAXUSER);
1456 user[NETMAXUSER-1] = '\0';
1458 /* try last working password associated with this host. */
1459 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1460 (mb->sslflag||mb->tlsflag))){
1461 dprint((9, "mm_login: found a password to try\n"));
1462 ps_global->no_newmail_check_from_optionally_enter = 0;
1463 ps_global->in_init_seq = save_in_init;
1464 return;
1467 #ifdef LOCAL_PASSWD_CACHE
1468 /* check to see if there's a password left over from last session */
1469 if(get_passfile_passwd(ps_global->pinerc, pwd,
1470 user, hostlist, (mb->sslflag||mb->tlsflag))){
1471 imap_set_passwd(&mm_login_list, *pwd, user,
1472 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1473 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1474 (mb->sslflag||mb->tlsflag));
1475 dprint((9, "mm_login: found a password in passfile to try\n"));
1476 ps_global->no_newmail_check_from_optionally_enter = 0;
1477 ps_global->in_init_seq = save_in_init;
1478 return;
1480 #endif /* LOCAL_PASSWD_CACHE */
1483 * If no explicit user name supplied and we've not logged in
1484 * with our local user name, see if we've visited this
1485 * host before as someone else.
1487 if(!*mb->user &&
1488 ((last = imap_get_user(mm_login_list, hostlist))
1489 #ifdef LOCAL_PASSWD_CACHE
1491 (last = get_passfile_user(ps_global->pinerc, hostlist))
1492 #endif /* LOCAL_PASSWD_CACHE */
1494 strncpy(user, last, NETMAXUSER);
1495 user[NETMAXUSER-1] = '\0';
1496 dprint((9, "mm_login: found user=%s\n",
1497 user ? user : "?"));
1499 /* try last working password associated with this host/user. */
1500 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1501 (mb->sslflag||mb->tlsflag))){
1502 dprint((9,
1503 "mm_login: found a password for user=%s to try\n",
1504 user ? user : "?"));
1505 ps_global->no_newmail_check_from_optionally_enter = 0;
1506 ps_global->in_init_seq = save_in_init;
1507 return;
1510 #ifdef LOCAL_PASSWD_CACHE
1511 /* check to see if there's a password left over from last session */
1512 if(get_passfile_passwd(ps_global->pinerc, pwd,
1513 user, hostlist, (mb->sslflag||mb->tlsflag))){
1514 imap_set_passwd(&mm_login_list, *pwd, user,
1515 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1516 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1517 (mb->sslflag||mb->tlsflag));
1518 dprint((9,
1519 "mm_login: found a password for user=%s in passfile to try\n",
1520 user ? user : "?"));
1521 ps_global->no_newmail_check_from_optionally_enter = 0;
1522 ps_global->in_init_seq = save_in_init;
1523 return;
1525 #endif /* LOCAL_PASSWD_CACHE */
1528 #if !defined(DOS) && !defined(OS2)
1529 if(!*mb->user && !*user &&
1530 (last = (ps_global->ui.login && ps_global->ui.login[0])
1531 ? ps_global->ui.login : NULL)
1533 strncpy(user, last, NETMAXUSER);
1534 user[NETMAXUSER-1] = '\0';
1535 dprint((9, "mm_login: found user=%s\n",
1536 user ? user : "?"));
1538 /* try last working password associated with this host. */
1539 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1540 (mb->sslflag||mb->tlsflag))){
1541 dprint((9, "mm_login:ui: found a password to try\n"));
1542 ps_global->no_newmail_check_from_optionally_enter = 0;
1543 ps_global->in_init_seq = save_in_init;
1544 return;
1547 #ifdef LOCAL_PASSWD_CACHE
1548 /* check to see if there's a password left over from last session */
1549 if(get_passfile_passwd(ps_global->pinerc, pwd,
1550 user, hostlist, (mb->sslflag||mb->tlsflag))){
1551 imap_set_passwd(&mm_login_list, *pwd, user,
1552 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1553 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1554 (mb->sslflag||mb->tlsflag));
1555 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1556 ps_global->no_newmail_check_from_optionally_enter = 0;
1557 ps_global->in_init_seq = save_in_init;
1558 return;
1560 #endif /* LOCAL_PASSWD_CACHE */
1562 #endif
1565 user[NETMAXUSER-1] = '\0';
1567 if(trial == 0)
1568 retry = "";
1571 * Even if we have a user now, user gets a chance to change it.
1573 ps_global->mangled_footer = 1;
1574 if(!*mb->user && !altuserforcache){
1576 help = NO_HELP;
1579 * Instead of offering user with a value that the user can edit,
1580 * we offer [user] as a default so that the user can type CR to
1581 * use it. Otherwise, the user has to type in whole name.
1583 strncpy(defuser, user, sizeof(defuser)-1);
1584 defuser[sizeof(defuser)-1] = '\0';
1585 user[0] = '\0';
1588 * Need space for "Retrying - "
1589 * "+ HOST: "
1590 * hostname
1591 * " (INSECURE)"
1592 * ENTER LOGIN NAME
1593 * " [defuser] : "
1594 * about 15 chars for input
1597 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1598 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1599 hostleadin[sizeof(hostleadin)-1] = '\0';
1601 strncpy(hostname, mb->host, sizeof(hostname)-1);
1602 hostname[sizeof(hostname)-1] = '\0';
1605 * Add port number to hostname if going through a tunnel or something
1607 if(*non_def_port)
1608 strncpy(port, non_def_port, sizeof(port));
1609 else
1610 port[0] = '\0';
1612 insecure[0] = '\0';
1613 /* if not encrypted and SSL/TLS is supported */
1614 if(!(mb->sslflag||mb->tlsflag) &&
1615 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1616 strncpy(insecure, insec, sizeof(insecure));
1618 /* TRANSLATORS: user is being asked to type in their login name */
1619 snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME"));
1621 snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "",
1622 (*defuser) ? defuser : "",
1623 (*defuser) ? "]" : "");
1624 defubuf[sizeof(defubuf)-1] = '\0';
1625 /* space reserved after prompt */
1626 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1628 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1629 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1630 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace;
1632 /* If we're retrying cut the hostname back to the first word. */
1633 if(avail < need && trial > 0){
1634 char *p;
1636 len = strlen(hostname);
1637 if((p = strchr(hostname, '.')) != NULL){
1638 *p = '\0';
1639 need -= (len - strlen(hostname));
1643 if(avail < need){
1644 need -= utf8_width(retry);
1645 retry = "";
1647 if(avail < need){
1649 /* reduce length of logleadin */
1650 len = utf8_width(logleadin);
1651 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1652 longer version doesn't fit on screen */
1653 snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN"));
1654 need -= (len - utf8_width(logleadin));
1656 if(avail < need){
1657 /* get two spaces from hostleadin */
1658 len = utf8_width(hostleadin);
1659 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1660 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1661 hostleadin[sizeof(hostleadin)-1] = '\0';
1662 need -= (len - utf8_width(hostleadin));
1664 /* get rid of port */
1665 if(avail < need && strlen(port) > 0){
1666 need -= strlen(port);
1667 port[0] = '\0';
1670 if(avail < need){
1671 int reduce_to;
1674 * Reduce space for hostname. Best we can do is 6 chars
1675 * with hos...
1677 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1678 len = strlen(hostname);
1679 strncpy(hostname+reduce_to-3, "...", 4);
1680 need -= (len - strlen(hostname));
1682 if(avail < need && strlen(insecure) > 0){
1683 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1684 need -= 3;
1685 insecure[strlen(insecure)-4] = ')';
1686 insecure[strlen(insecure)-3] = '\0';
1688 else{
1689 need -= utf8_width(insecure);
1690 insecure[0] = '\0';
1694 if(avail < need){
1695 if(strlen(defubuf) > 3){
1696 len = strlen(defubuf);
1697 strncpy(defubuf, " [..] :", 9);
1698 need -= (len - strlen(defubuf));
1701 if(avail < need)
1702 strncpy(defubuf, ":", 2);
1705 * If it still doesn't fit, optionally_enter gets
1706 * to worry about it.
1714 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s",
1715 retry, hostleadin, hostname, port, insecure, logleadin, defubuf);
1716 prompt[sizeof(prompt)-1] = '\0';
1718 while(1) {
1719 if(ps_global->ttyo)
1720 mm_login_alt_cue(mb);
1722 flags = OE_APPEND_CURRENT;
1723 save_dont_use = ps_global->dont_use_init_cmds;
1724 ps_global->dont_use_init_cmds = 1;
1725 #ifdef _WINDOWS
1726 if(!*user && *defuser){
1727 strncpy(user, defuser, NETMAXUSER);
1728 user[NETMAXUSER-1] = '\0';
1731 rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD,
1732 #ifdef LOCAL_PASSWD_CACHE
1733 is_using_passfile() ? 1 :
1734 #endif /* LOCAL_PASSWD_CACHE */
1735 0, 0, &preserve_password);
1736 ps_global->dont_use_init_cmds = save_dont_use;
1737 if(rc == 0 && *user && *pwd)
1738 goto nopwpmt;
1739 #else /* !_WINDOWS */
1740 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
1741 prompt, NULL, help, &flags);
1742 #endif /* !_WINDOWS */
1743 ps_global->dont_use_init_cmds = save_dont_use;
1745 if(rc == 3) {
1746 help = help == NO_HELP ? h_oe_login : NO_HELP;
1747 continue;
1750 /* default */
1751 if(rc == 0 && !*user){
1752 strncpy(user, defuser, NETMAXUSER);
1753 user[NETMAXUSER-1] = '\0';
1756 if(rc != 4)
1757 break;
1760 if(rc == 1 || !user[0]) {
1761 ps_global->user_says_cancel = (rc == 1);
1762 user[0] = '\0';
1765 else{
1766 strncpy(user, mb->user, NETMAXUSER);
1767 user[NETMAXUSER-1] = '\0';
1770 user[NETMAXUSER-1] = '\0';
1772 if(!(user[0] || altuserforcache)){
1773 ps_global->no_newmail_check_from_optionally_enter = 0;
1774 ps_global->in_init_seq = save_in_init;
1775 return;
1779 * Now that we have a user, we can check in the cache again to see
1780 * if there is a password there. Try last working password associated
1781 * with this host and user.
1783 if(trial == 0L && !*mb->user && !altuserforcache){
1784 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1785 (mb->sslflag||mb->tlsflag))){
1786 ps_global->no_newmail_check_from_optionally_enter = 0;
1787 ps_global->in_init_seq = save_in_init;
1788 return;
1791 #ifdef LOCAL_PASSWD_CACHE
1792 if(get_passfile_passwd(ps_global->pinerc, pwd,
1793 user, hostlist, (mb->sslflag||mb->tlsflag))){
1794 imap_set_passwd(&mm_login_list, *pwd, user,
1795 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1796 ps_global->no_newmail_check_from_optionally_enter = 0;
1797 ps_global->in_init_seq = save_in_init;
1798 return;
1800 #endif /* LOCAL_PASSWD_CACHE */
1802 else if(trial == 0 && altuserforcache){
1803 if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist,
1804 (mb->sslflag||mb->tlsflag))){
1805 ps_global->no_newmail_check_from_optionally_enter = 0;
1806 ps_global->in_init_seq = save_in_init;
1807 return;
1810 #ifdef LOCAL_PASSWD_CACHE
1811 if(get_passfile_passwd(ps_global->pinerc, pwd,
1812 altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
1813 imap_set_passwd(&mm_login_list, *pwd, altuserforcache,
1814 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1815 ps_global->no_newmail_check_from_optionally_enter = 0;
1816 ps_global->in_init_seq = save_in_init;
1817 return;
1819 #endif /* LOCAL_PASSWD_CACHE */
1823 * Didn't find password in cache or this isn't the first try. Ask user.
1825 help = NO_HELP;
1828 * Need space for "Retrying - "
1829 * "+ HOST: "
1830 * hostname
1831 * " (INSECURE) "
1832 * " USER: "
1833 * user
1834 * " ENTER PASSWORD: "
1835 * about 15 chars for input
1838 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1839 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1841 strncpy(hostname, mb->host, sizeof(hostname)-1);
1842 hostname[sizeof(hostname)-1] = '\0';
1845 * Add port number to hostname if going through a tunnel or something
1847 if(*non_def_port)
1848 strncpy(port, non_def_port, sizeof(port));
1849 else
1850 port[0] = '\0';
1852 insecure[0] = '\0';
1854 /* if not encrypted and SSL/TLS is supported */
1855 if(!(mb->sslflag||mb->tlsflag) &&
1856 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1857 strncpy(insecure, insec, sizeof(insecure));
1859 if(usethisprompt){
1860 strncpy(logleadin, usethisprompt, sizeof(logleadin));
1861 logleadin[sizeof(logleadin)-1] = '\0';
1862 defubuf[0] = '\0';
1863 user[0] = '\0';
1865 else{
1866 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1868 strncpy(defubuf, user, sizeof(defubuf)-1);
1869 defubuf[sizeof(defubuf)-1] = '\0';
1872 /* TRANSLATORS: user is being asked to type in their password */
1873 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD"));
1875 /* space reserved after prompt */
1876 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1878 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1879 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1880 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) +
1881 utf8_width(pwleadin) + oespace;
1883 if(avail < need && trial > 0){
1884 char *p;
1886 len = strlen(hostname);
1887 if((p = strchr(hostname, '.')) != NULL){
1888 *p = '\0';
1889 need -= (len - strlen(hostname));
1893 if(avail < need){
1894 need -= utf8_width(retry);
1895 retry = "";
1897 if(avail < need){
1899 if(!usethisprompt){
1900 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1901 need--;
1904 rplstr(pwleadin, sizeof(pwleadin), 1, "");
1905 need--;
1907 if(avail < need){
1908 /* get two spaces from hostleadin */
1909 len = utf8_width(hostleadin);
1910 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1911 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1912 hostleadin[sizeof(hostleadin)-1] = '\0';
1913 need -= (len - utf8_width(hostleadin));
1915 /* get rid of port */
1916 if(avail < need && strlen(port) > 0){
1917 need -= strlen(port);
1918 port[0] = '\0';
1921 if(avail < need){
1922 len = utf8_width(pwleadin);
1923 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
1924 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD"));
1925 need -= (len - utf8_width(pwleadin));
1929 if(avail < need){
1930 int reduce_to;
1933 * Reduce space for hostname. Best we can do is 6 chars
1934 * with hos...
1936 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1937 len = strlen(hostname);
1938 strncpy(hostname+reduce_to-3, "...", 4);
1939 need -= (len - strlen(hostname));
1941 if(avail < need && strlen(insecure) > 0){
1942 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1943 need -= 3;
1944 insecure[strlen(insecure)-4] = ')';
1945 insecure[strlen(insecure)-3] = '\0';
1947 else{
1948 need -= utf8_width(insecure);
1949 insecure[0] = '\0';
1953 if(avail < need){
1954 len = utf8_width(logleadin);
1955 strncpy(logleadin, " ", sizeof(logleadin));
1956 logleadin[sizeof(logleadin)-1] = '\0';
1957 need -= (len - utf8_width(logleadin));
1959 if(avail < need){
1960 reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0;
1961 if(reduce_to)
1962 strncpy(defubuf+reduce_to-3, "...", 4);
1963 else
1964 defubuf[0] = '\0';
1971 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s",
1972 retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
1973 prompt[sizeof(prompt)-1] = '\0';
1975 tmp[0] = '\0';
1976 while(1) {
1977 if(ps_global->ttyo)
1978 mm_login_alt_cue(mb);
1980 save_dont_use = ps_global->dont_use_init_cmds;
1981 ps_global->dont_use_init_cmds = 1;
1982 flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
1983 flags |= OE_KEEP_TRAILING_SPACE;
1984 #ifdef _WINDOWS
1986 char *tmpp;
1987 tmpp = fs_get(NETMAXPASSWD*sizeof(char));
1988 rc = os_login_dialog(mb, user, NETMAXUSER, &tmpp, NETMAXPASSWD, 0, 1,
1989 &preserve_password);
1990 strncpy(tmp, tmpp, sizeof(tmp));
1991 tmp[sizeof(tmp)-1] = '\0';
1992 if(tmpp) fs_give((void **)&tmpp);
1994 #else /* !_WINDOWS */
1995 rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD,
1996 prompt, NULL, help, &flags);
1997 #endif /* !_WINDOWS */
1998 if(rc != 1) *pwd = cpystr(tmp);
1999 ps_global->dont_use_init_cmds = save_dont_use;
2001 if(rc == 3) {
2002 help = help == NO_HELP ? h_oe_passwd : NO_HELP;
2004 else if(rc == 4){
2006 else
2007 break;
2010 if(rc == 1 || !tmp[0]) {
2011 ps_global->user_says_cancel = (rc == 1);
2012 user[0] = '\0';
2013 ps_global->no_newmail_check_from_optionally_enter = 0;
2014 ps_global->in_init_seq = save_in_init;
2015 return;
2018 #ifdef _WINDOWS
2019 nopwpmt:
2020 #endif
2021 /* remember the password for next time */
2022 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
2023 imap_set_passwd(&mm_login_list, *pwd,
2024 altuserforcache ? altuserforcache : user, hostlist,
2025 (mb->sslflag||mb->tlsflag), 0, 0);
2026 #ifdef LOCAL_PASSWD_CACHE
2027 /* if requested, remember it on disk for next session */
2028 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
2029 set_passfile_passwd(ps_global->pinerc, *pwd,
2030 altuserforcache ? altuserforcache : user, hostlist,
2031 (mb->sslflag||mb->tlsflag),
2032 (preserve_password == -1 ? 0
2033 : (preserve_password == 0 ? 2 :1)));
2034 #endif /* LOCAL_PASSWD_CACHE */
2036 ps_global->no_newmail_check_from_optionally_enter = 0;
2040 void
2041 mm_login_alt_cue(NETMBX *mb)
2043 if(ps_global->ttyo){
2044 COLOR_PAIR *lastc;
2046 lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
2047 ps_global->VAR_TITLE_BACK_COLOR,
2048 PSC_REV | PSC_RET);
2050 mark_titlebar_dirty();
2051 PutLine0(0, ps_global->ttyo->screen_cols - 1,
2052 (mb->sslflag||mb->tlsflag) ? "+" : " ");
2054 if(lastc){
2055 (void)pico_set_colorp(lastc, PSC_NONE);
2056 free_color_pair(&lastc);
2059 fflush(stdout);
2064 /*----------------------------------------------------------------------
2065 Receive notification of an error writing to disk
2067 Args: stream -- The stream the error occurred on
2068 errcode -- The system error code (errno)
2069 serious -- Flag indicating error is serious (mail may be lost)
2071 Result: If error is non serious, the stream is marked as having an error
2072 and deletes are disallowed until error clears
2073 If error is serious this goes modal, allowing the user to retry
2074 or get a shell escape to fix the condition. When the condition is
2075 serious it means that mail existing in the mailbox will be lost
2076 if Pine exits without writing, so we try to induce the user to
2077 fix the error, go get someone that can fix the error, or whatever
2078 and don't provide an easy way out.
2079 ----*/
2080 long
2081 mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious)
2083 int i, j;
2084 char *p, *q, *s;
2085 static ESCKEY_S de_opts[] = {
2086 {'r', 'r', "R", "Retry"},
2087 {'f', 'f', "F", "FileBrowser"},
2088 {'s', 's', "S", "ShellPrompt"},
2089 {-1, 0, NULL, NULL}
2091 #define DE_COLS (ps_global->ttyo->screen_cols)
2092 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2094 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2095 #define DE_PMT \
2096 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2097 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2098 #define DE_STR2 \
2099 "The reported error number is %s. The last reported mail error was:"
2100 static char *de_msg[] = {
2101 "Please try to correct the error preventing Alpine from saving your",
2102 "mail folder. For example if the disk is out of space try removing",
2103 "unneeded files. You might also contact your system administrator.",
2105 "Both Alpine's File Browser and an option to enter the system's",
2106 "command prompt are offered to aid in fixing the problem. When",
2107 "you believe the problem is resolved, choose the \"Retry\" option.",
2108 "Be aware that messages may be lost or this folder left in an",
2109 "inaccessible condition if you exit or kill Alpine before the problem",
2110 "is resolved.",
2111 NULL};
2112 static char *de_shell_msg[] = {
2113 "\n\nPlease attempt to correct the error preventing saving of the",
2114 "mail folder. If you do not know how to correct the problem, contact",
2115 "your system administrator. To return to Alpine, type \"exit\".",
2116 NULL};
2118 dprint((0,
2119 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2120 DE_FOLDER(stream), errcode, serious ? "" : "not "));
2121 dprint((0, "***** message: \"%s\"\n\n",
2122 ps_global->last_error ? ps_global->last_error : "?"));
2124 if(!serious) {
2125 sp_set_io_error_on_stream(stream, 1);
2126 return (1) ;
2129 while(1){
2130 /* replace pine's body display with screen full of explanatory text */
2131 ClearLine(2);
2132 PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1)
2133 - strlen(DE_FOLDER(stream)))/2, 0),
2134 DE_STR1, DE_FOLDER(stream));
2135 ClearLine(3);
2136 PutLine1(3, 4, DE_STR2, long2string(errcode));
2138 PutLine0(4, 0, " \"");
2139 removing_leading_white_space(ps_global->last_error);
2140 for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){
2141 for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++)
2142 if(isspace((unsigned char)*q))
2143 s = q;
2145 if(*q && s)
2146 q = s;
2148 while(p < q)
2149 Writechar(*p++, 0);
2151 if(*(p = q)){
2152 ClearLine(++i);
2153 PutLine0(i, 0, " ");
2154 while(*p && isspace((unsigned char)*p))
2155 p++;
2157 else{
2158 Writechar('\"', 0);
2159 CleartoEOLN();
2160 break;
2164 ClearLine(++i);
2165 for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){
2166 ClearLine(i);
2167 PutLine0(i, 0, " ");
2168 Write_to_screen(de_msg[i-j]);
2171 while(i < DE_LINE)
2172 ClearLine(i++);
2174 switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts,
2175 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){
2176 case 'r' : /* Retry! */
2177 ps_global->mangled_screen = 1;
2178 return(0L);
2180 case 'f' : /* File Browser */
2182 char full_filename[MAXPATH+1], filename[MAXPATH+1];
2184 filename[0] = '\0';
2185 build_path(full_filename, ps_global->home_dir, filename,
2186 sizeof(full_filename));
2187 file_lister("DISK ERROR", full_filename, sizeof(full_filename),
2188 filename, sizeof(filename), FALSE, FB_SAVE);
2191 break;
2193 case 's' :
2194 EndInverse();
2195 end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0);
2196 end_tty_driver(ps_global);
2197 for(i = 0; de_shell_msg[i]; i++)
2198 puts(de_shell_msg[i]);
2201 * Don't use our piping mechanism to spawn a subshell here
2202 * since it will the server (thus reentering c-client).
2203 * Bad thing to do.
2205 #ifdef _WINDOWS
2206 #else
2207 system("csh");
2208 #endif
2209 init_tty_driver(ps_global);
2210 init_keyboard(F_ON(F_USE_FK,ps_global));
2211 break;
2214 if(ps_global->redrawer)
2215 (*ps_global->redrawer)();
2220 long
2221 pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host)
2223 long rv = 1L;
2224 char pmt[128];
2226 #ifdef _WINDOWS
2227 mswin_killsplash();
2228 #endif
2230 if(elapsed >= (long)ps_global->tcp_query_timeout){
2231 snprintf(pmt, sizeof(pmt),
2232 _("No reply in %s seconds from server %s. Break connection"),
2233 long2string(elapsed), host);
2234 pmt[sizeof(pmt)-1] = '\0';
2235 if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
2236 ps_global->user_says_cancel = 1;
2237 return(0L);
2241 ps_global->tcptimeout = 0;
2242 return(rv);
2247 * -------------------------------------------------------------
2248 * These are declared in pith/imap.h as mandatory to implement.
2249 * -------------------------------------------------------------
2254 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2256 long
2257 pine_tcptimeout(long int elapsed, long int sincelast, char *host)
2259 long rv = 1L; /* keep trying by default */
2260 unsigned long ch;
2262 ps_global->tcptimeout = 1;
2263 #ifdef DEBUG
2264 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2265 long2string(elapsed), host));
2266 if(debugfile)
2267 fflush(debugfile);
2268 #endif
2270 #ifdef _WINDOWS
2271 mswin_killsplash();
2272 #endif
2274 if(ps_global->noshow_timeout)
2275 return(rv);
2277 if(ps_global->can_interrupt
2278 && ps_global->close_connection_timeout > 0L
2279 && elapsed >= (long)ps_global->tcp_query_timeout
2280 && elapsed >= (long)ps_global->close_connection_timeout){
2281 ps_global->can_interrupt = 0; /* do not return here */
2282 ps_global->read_bail = 0;
2283 ps_global->user_says_cancel = 1;
2284 return 0;
2287 if(!ps_global->ttyo)
2288 return(pine_tcptimeout_noscreen(elapsed, sincelast, host));
2290 suspend_busy_cue();
2293 * Prompt after a minute (since by then things are probably really bad)
2294 * A prompt timeout means "keep trying"...
2296 if(elapsed >= (long)ps_global->tcp_query_timeout){
2297 int clear_inverse;
2299 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2300 if((clear_inverse = !InverseState()) != 0)
2301 StartInverse();
2303 Writechar(BELL, 0);
2305 PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0,
2306 _("No reply in %s seconds from server %s. Break connection?"),
2307 long2string(elapsed), host);
2308 CleartoEOLN();
2309 fflush(stdout);
2310 flush_input();
2311 ch = read_char(7);
2312 if(ps_global->read_bail || ch == 'y' || ch == 'Y'){
2313 ps_global->read_bail = 0;
2314 ps_global->user_says_cancel = 1;
2315 rv = 0L;
2318 if(clear_inverse)
2319 EndInverse();
2321 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2324 if(rv == 1L){ /* just warn 'em something's up */
2325 q_status_message2(SM_ORDER, 0, 0,
2326 _("No reply in %s seconds from server %s. Still Waiting..."),
2327 long2string(elapsed), host);
2328 flush_status_messages(0); /* make sure it's seen */
2331 mark_status_dirty(); /* make sure it gets cleared */
2333 resume_busy_cue((rv == 1) ? 3 : 0);
2334 ps_global->tcptimeout = 0;
2336 return(rv);
2339 QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota)
2341 QUOTALIST *cquota = NULL;
2343 if(pquota){
2344 cquota = mail_newquotalist();
2345 if (pquota->name && *pquota->name)
2346 cquota->name = cpystr(pquota->name);
2347 cquota->usage = pquota->usage;
2348 cquota->limit = pquota->limit;
2349 if (pquota->next)
2350 cquota->next = pine_quotalist_copy(pquota->next);
2352 return cquota;
2356 /* c-client callback to handle quota */
2358 void
2359 pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2361 ps_global->quota = pine_quotalist_copy (pquota);
2365 * C-client callback to handle SSL/TLS certificate validation failures
2367 * Returning 0 means error becomes fatal
2368 * Non-zero means certificate problem is ignored and SSL session is
2369 * established
2371 * We remember the answer and won't re-ask for subsequent open attempts to
2372 * the same hostname.
2374 long
2375 pine_sslcertquery(char *reason, char *host, char *cert)
2377 char tmp[500];
2378 char *unknown = "<unknown>";
2379 long rv = 0L;
2380 STRLIST_S hostlist;
2381 int ok_novalidate = 0, warned = 0;
2383 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2384 host ? host : "?", reason ? reason : "?",
2385 cert ? cert : "?"));
2387 hostlist.name = host ? host : "";
2388 hostlist.next = NULL;
2391 * See if we've been asked about this host before.
2393 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2394 /* we were asked before, did we say Yes? */
2395 if(ok_novalidate)
2396 rv++;
2398 if(rv){
2399 dprint((5,
2400 "sslcertificatequery: approved automatically\n"));
2401 return(rv);
2404 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2407 if(ps_global->ttyo){
2408 SCROLL_S sargs;
2409 STORE_S *in_store, *out_store;
2410 gf_io_t pc, gc;
2411 HANDLE_S *handles = NULL;
2412 int the_answer = 'n';
2414 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
2415 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
2416 goto try_wantto;
2418 so_puts(in_store, "<HTML><P>");
2419 so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server"));
2421 so_puts(in_store, "<P><CENTER>");
2422 so_puts(in_store, host ? host : unknown);
2423 so_puts(in_store, "</CENTER>");
2425 so_puts(in_store, "<P>");
2426 so_puts(in_store, _("The reason for the failure was"));
2428 /* squirrel away details */
2429 if(details_host)
2430 fs_give((void **)&details_host);
2431 if(details_reason)
2432 fs_give((void **)&details_reason);
2433 if(details_cert)
2434 fs_give((void **)&details_cert);
2436 details_host = cpystr(host ? host : unknown);
2437 details_reason = cpystr(reason ? reason : unknown);
2438 details_cert = cpystr(cert ? cert : unknown);
2440 so_puts(in_store, "<P><CENTER>");
2441 snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2442 reason ? reason : unknown);
2443 tmp[sizeof(tmp)-1] = '\0';
2445 so_puts(in_store, tmp);
2446 so_puts(in_store, "</CENTER>");
2448 so_puts(in_store, "<P>");
2449 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."));
2451 so_puts(in_store, "<P>");
2452 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"));
2454 so_puts(in_store, "<P><CENTER>");
2455 so_puts(in_store, "/novalidate-cert");
2456 so_puts(in_store, "</CENTER>");
2458 so_puts(in_store, "<P>");
2459 so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2461 so_puts(in_store, "<P><CENTER>");
2462 so_puts(in_store, host ? host : unknown);
2463 so_puts(in_store, "</CENTER>");
2465 so_puts(in_store, "<P>");
2466 so_puts(in_store, _("in your configuration, replace those characters with"));
2468 so_puts(in_store, "<P><CENTER>");
2469 so_puts(in_store, host ? host : unknown);
2470 so_puts(in_store, "/novalidate-cert");
2471 so_puts(in_store, "</CENTER>");
2473 so_puts(in_store, "<P>");
2474 so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2476 so_seek(in_store, 0L, 0);
2477 init_handles(&handles);
2478 gf_filter_init();
2479 gf_link_filter(gf_html2plain,
2480 gf_html2plain_opt(NULL,
2481 ps_global->ttyo->screen_cols, non_messageview_margin(),
2482 &handles, NULL, GFHP_LOCAL_HANDLES));
2483 gf_set_so_readc(&gc, in_store);
2484 gf_set_so_writec(&pc, out_store);
2485 gf_pipe(gc, pc);
2486 gf_clear_so_writec(out_store);
2487 gf_clear_so_readc(in_store);
2489 memset(&sargs, 0, sizeof(SCROLL_S));
2490 sargs.text.handles = handles;
2491 sargs.text.text = so_text(out_store);
2492 sargs.text.src = CharStar;
2493 sargs.text.desc = _("help text");
2494 sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2495 sargs.proc.tool = answer_cert_failure;
2496 sargs.proc.data.p = (void *)&the_answer;
2497 sargs.keys.menu = &ans_certquery_keymenu;
2498 /* don't want to re-enter c-client */
2499 sargs.quell_newmail = 1;
2500 setbitmap(sargs.keys.bitmap);
2501 sargs.help.text = h_tls_validation_failure;
2502 sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
2504 scrolltool(&sargs);
2506 if(the_answer == 'y')
2507 rv++;
2508 else if(the_answer == 'n')
2509 ps_global->user_says_cancel = 1;
2511 ps_global->mangled_screen = 1;
2512 ps_global->painted_body_on_startup = 0;
2513 ps_global->painted_footer_on_startup = 0;
2514 so_give(&in_store);
2515 so_give(&out_store);
2516 free_handles(&handles);
2517 if(details_host)
2518 fs_give((void **)&details_host);
2519 if(details_reason)
2520 fs_give((void **)&details_reason);
2521 if(details_cert)
2522 fs_give((void **)&details_cert);
2524 else{
2526 * If screen hasn't been initialized yet, use want_to.
2528 try_wantto:
2529 memset((void *)tmp, 0, sizeof(tmp));
2530 strncpy(tmp,
2531 reason ? reason : _("SSL/TLS certificate validation failure"),
2532 sizeof(tmp));
2533 tmp[sizeof(tmp)-1] = '\0';
2534 strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
2536 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
2537 rv++;
2540 if(rv == 0)
2541 q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
2542 host ? host : unknown);
2544 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
2546 dprint((5, "sslcertificatequery: %s\n",
2547 rv ? "approved" : "rejected"));
2549 return(rv);
2553 char *
2554 pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
2556 char buf[MAILTMPLEN];
2558 if((can_access(mulname, ACCESS_EXISTS) == 0)
2559 || !(can_access(name, ACCESS_EXISTS) == 0))
2560 return(mulname);
2562 snprintf(buf, sizeof(buf),
2563 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2564 last_cmpnt(name),
2565 strlen(last_cmpnt(name)) > 15 ? "..." : "");
2566 buf[sizeof(buf)-1] = '\0';
2567 if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
2568 rename_file(name, mulname);
2569 return(mulname);
2574 url_local_certdetails(char *url)
2576 if(!struncmp(url, "x-alpine-cert:", 14)){
2577 STORE_S *store;
2578 SCROLL_S sargs;
2579 char *folded;
2581 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
2582 q_status_message(SM_ORDER | SM_DING, 7, 10,
2583 _("Error allocating space for details."));
2584 return(0);
2587 so_puts(store, _("Host given by user:\n\n "));
2588 so_puts(store, details_host);
2589 so_puts(store, _("\n\nReason for failure:\n\n "));
2590 so_puts(store, details_reason);
2591 so_puts(store, _("\n\nCertificate being verified:\n\n"));
2592 folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
2593 so_puts(store, folded);
2594 fs_give((void **)&folded);
2595 so_puts(store, "\n");
2597 memset(&sargs, 0, sizeof(SCROLL_S));
2598 sargs.text.text = so_text(store);
2599 sargs.text.src = CharStar;
2600 sargs.text.desc = _("Details");
2601 sargs.bar.title = _("CERT VALIDATION DETAILS");
2602 sargs.help.text = NO_HELP;
2603 sargs.help.title = NULL;
2604 sargs.quell_newmail = 1;
2605 sargs.help.text = h_tls_failure_details;
2606 sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
2608 scrolltool(&sargs);
2610 so_give(&store); /* free resources associated with store */
2611 ps_global->mangled_screen = 1;
2612 return(1);
2615 return(0);
2620 * C-client callback to handle SSL/TLS certificate validation failures
2622 void
2623 pine_sslfailure(char *host, char *reason, long unsigned int flags)
2625 SCROLL_S sargs;
2626 STORE_S *store;
2627 int the_answer = 'n', indent, len, cols;
2628 char buf[500], buf2[500];
2629 char *folded;
2630 char *hst = host ? host : "<unknown>";
2631 char *rsn = reason ? reason : "<unknown>";
2632 char *notls = "/notls";
2633 STRLIST_S hostlist;
2634 int ok_novalidate = 0, warned = 0;
2637 dprint((1, "sslfailure: host=%s reason=%s\n",
2638 hst ? hst : "?",
2639 rsn ? rsn : "?"));
2641 if(flags & NET_SILENT)
2642 return;
2644 hostlist.name = host ? host : "";
2645 hostlist.next = NULL;
2648 * See if we've been told about this host before.
2650 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2651 /* we were told already */
2652 if(warned){
2653 snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn);
2654 buf[sizeof(buf)-1] = '\0';
2655 mm_log(buf, ERROR);
2656 return;
2660 cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2661 cols--;
2663 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS)))
2664 return;
2666 strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf));
2667 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2668 so_puts(store, folded);
2669 fs_give((void **)&folded);
2670 so_puts(store, "\n");
2672 if((len=strlen(hst)) <= cols){
2673 if((indent=((cols-len)/2)) > 0)
2674 so_puts(store, repeat_char(indent, SPACE));
2676 so_puts(store, hst);
2677 so_puts(store, "\n");
2679 else{
2680 strncpy(buf, hst, sizeof(buf));
2681 buf[sizeof(buf)-1] = '\0';
2682 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2683 so_puts(store, folded);
2684 fs_give((void **)&folded);
2687 so_puts(store, "\n");
2689 strncpy(buf, _("The reason for the failure was"), sizeof(buf));
2690 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2691 so_puts(store, folded);
2692 fs_give((void **)&folded);
2693 so_puts(store, "\n");
2695 if((len=strlen(rsn)) <= cols){
2696 if((indent=((cols-len)/2)) > 0)
2697 so_puts(store, repeat_char(indent, SPACE));
2699 so_puts(store, rsn);
2700 so_puts(store, "\n");
2702 else{
2703 strncpy(buf, rsn, sizeof(buf));
2704 buf[sizeof(buf)-1] = '\0';
2705 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2706 so_puts(store, folded);
2707 fs_give((void **)&folded);
2710 so_puts(store, "\n");
2712 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));
2713 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2714 so_puts(store, folded);
2715 fs_give((void **)&folded);
2716 so_puts(store, "\n");
2718 if((len=strlen(notls)) <= cols){
2719 if((indent=((cols-len)/2)) > 0)
2720 so_puts(store, repeat_char(indent, SPACE));
2722 so_puts(store, notls);
2723 so_puts(store, "\n");
2725 else{
2726 strncpy(buf, notls, sizeof(buf));
2727 buf[sizeof(buf)-1] = '\0';
2728 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2729 so_puts(store, folded);
2730 fs_give((void **)&folded);
2733 so_puts(store, "\n");
2735 strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2736 sizeof(buf));
2737 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2738 so_puts(store, folded);
2739 fs_give((void **)&folded);
2740 so_puts(store, "\n");
2742 if((len=strlen(hst)) <= cols){
2743 if((indent=((cols-len)/2)) > 0)
2744 so_puts(store, repeat_char(indent, SPACE));
2746 so_puts(store, hst);
2747 so_puts(store, "\n");
2749 else{
2750 strncpy(buf, hst, sizeof(buf));
2751 buf[sizeof(buf)-1] = '\0';
2752 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2753 so_puts(store, folded);
2754 fs_give((void **)&folded);
2757 so_puts(store, "\n");
2759 strncpy(buf, _("in your configuration, replace those characters with"),
2760 sizeof(buf));
2761 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2762 so_puts(store, folded);
2763 fs_give((void **)&folded);
2764 so_puts(store, "\n");
2766 snprintf(buf2, sizeof(buf2), "%s%s", hst, notls);
2767 buf2[sizeof(buf2)-1] = '\0';
2768 if((len=strlen(buf2)) <= cols){
2769 if((indent=((cols-len)/2)) > 0)
2770 so_puts(store, repeat_char(indent, SPACE));
2772 so_puts(store, buf2);
2773 so_puts(store, "\n");
2775 else{
2776 strncpy(buf, buf2, sizeof(buf));
2777 buf[sizeof(buf)-1] = '\0';
2778 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2779 so_puts(store, folded);
2780 fs_give((void **)&folded);
2783 so_puts(store, "\n");
2785 if(ps_global->ttyo){
2786 strncpy(buf, _("Type RETURN to continue."), sizeof(buf));
2787 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2788 so_puts(store, folded);
2789 fs_give((void **)&folded);
2792 memset(&sargs, 0, sizeof(SCROLL_S));
2793 sargs.text.text = so_text(store);
2794 sargs.text.src = CharStar;
2795 sargs.text.desc = _("help text");
2796 sargs.bar.title = _("SSL/TLS FAILURE");
2797 sargs.proc.tool = answer_cert_failure;
2798 sargs.proc.data.p = (void *)&the_answer;
2799 sargs.keys.menu = &ans_certfail_keymenu;
2800 setbitmap(sargs.keys.bitmap);
2801 /* don't want to re-enter c-client */
2802 sargs.quell_newmail = 1;
2803 sargs.help.text = h_tls_failure;
2804 sargs.help.title = _("HELP FOR TLS/SSL FAILURE");
2806 if(ps_global->ttyo)
2807 scrolltool(&sargs);
2808 else{
2809 char **q, **qp;
2810 char *p;
2811 unsigned char c;
2812 int cnt = 0;
2815 * The screen isn't initialized yet, which should mean that this
2816 * is the result of a -p argument. Display_args_err knows how to deal
2817 * with the uninitialized screen, so we mess with the data to get it
2818 * in shape for display_args_err. This is pretty hacky.
2821 so_seek(store, 0L, 0); /* rewind */
2822 /* count the lines */
2823 while(so_readc(&c, store))
2824 if(c == '\n')
2825 cnt++;
2827 qp = q = (char **)fs_get((cnt+1) * sizeof(char *));
2828 memset(q, 0, (cnt+1) * sizeof(char *));
2830 so_seek(store, 0L, 0); /* rewind */
2831 p = buf;
2832 while(so_readc(&c, store)){
2833 if(c == '\n'){
2834 *p = '\0';
2835 *qp++ = cpystr(buf);
2836 p = buf;
2838 else
2839 *p++ = c;
2842 display_args_err(NULL, q, 0);
2843 free_list_array(&q);
2846 ps_global->mangled_screen = 1;
2847 ps_global->painted_body_on_startup = 0;
2848 ps_global->painted_footer_on_startup = 0;
2849 so_give(&store);
2851 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1);
2856 answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2858 int rv = 1;
2860 ps_global->next_screen = SCREEN_FUN_NULL;
2862 switch(cmd){
2863 case MC_YES :
2864 *(int *)(sparms->proc.data.p) = 'y';
2865 break;
2867 case MC_NO :
2868 *(int *)(sparms->proc.data.p) = 'n';
2869 break;
2871 default:
2872 alpine_panic("Unexpected command in answer_cert_failure");
2873 break;
2876 return(rv);
2881 oauth2_auth_answer(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2883 int rv = 1, rc;
2884 AUTH_CODE_S user;
2885 int q_line, flags;
2886 /* TRANSLATORS: user needs to input an access code from the server */
2887 char *accesscodelabel = _("Copy and Paste Access Code");
2888 char token[MAILTMPLEN], prompt[MAILTMPLEN];
2890 ps_global->next_screen = SCREEN_FUN_NULL;
2892 token[0] = '\0';
2893 switch(cmd){
2894 case MC_YES :
2895 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
2896 flags = OE_APPEND_CURRENT;
2897 sprintf(prompt, "%s: ", accesscodelabel);
2898 do {
2899 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
2900 prompt, NULL, NO_HELP, &flags);
2901 } while (rc != 0 && rc != 1);
2902 user.code = rc == 0 ? cpystr(token) : NULL;
2903 user.answer = 'e';
2904 rv = rc == 1 ? 0 : 1;
2905 break;
2907 case MC_NO :
2908 user.code = NULL;
2909 user.answer = 'e';
2910 break;
2912 default:
2913 alpine_panic("Unexpected command in oauth2_auth_answer");
2914 break;
2916 *(AUTH_CODE_S *) sparms->proc.data.p = user;
2917 return(rv);
2921 /*----------------------------------------------------------------------
2922 This can be used to prevent the flickering of the check_cue char
2923 caused by numerous (5000+) fetches by c-client. Right now, the only
2924 practical use found is newsgroup subsciption.
2926 check_cue_display will check if this global is set, and won't clear
2927 the check_cue_char if set.
2928 ----*/
2929 void
2930 set_read_predicted(int i)
2932 ps_global->read_predicted = i==1;
2933 #ifdef _WINDOWS
2934 if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global))
2935 check_cue_display(" ");
2936 #endif
2940 /*----------------------------------------------------------------------
2941 Exported method to retrieve logged in user name associated with stream
2943 Args: host -- host to find associated login name with.
2945 Result:
2946 ----*/
2947 void *
2948 pine_block_notify(int reason, void *data)
2950 switch(reason){
2951 case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */
2952 break;
2954 case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */
2955 break;
2957 case BLOCK_TCPWRITE: /* blocked on TCP write */
2958 case BLOCK_FILELOCK: /* blocked on file locking */
2959 #ifdef _WINDOWS
2960 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
2961 check_cue_display(">");
2963 mswin_setcursor(MSWIN_CURSOR_BUSY);
2964 #endif
2965 break;
2967 case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */
2968 case BLOCK_TCPOPEN: /* blocked on TCP open */
2969 case BLOCK_TCPREAD: /* blocked on TCP read */
2970 case BLOCK_TCPCLOSE: /* blocked on TCP close */
2971 #ifdef _WINDOWS
2972 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
2973 check_cue_display("<");
2975 mswin_setcursor(MSWIN_CURSOR_BUSY);
2976 #endif
2977 break;
2979 default :
2980 case BLOCK_NONE: /* not blocked */
2981 #ifdef _WINDOWS
2982 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
2983 check_cue_display(" ");
2984 #endif
2985 break;
2989 return(NULL);
2993 void
2994 mm_expunged_current(long unsigned int rawno)
2996 /* expunged something we're viewing? */
2997 if(!ps_global->expunge_in_progress
2998 && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno))
2999 && (ps_global->prev_screen == mail_view_screen
3000 || ps_global->prev_screen == attachment_screen))){
3001 ps_global->next_screen = mail_index_screen;
3002 q_status_message(SM_ORDER | SM_DING , 3, 3,
3003 "Message you were viewing is gone!");
3008 #ifdef PASSFILE
3011 * Specific functions to support caching username/passwd/host
3012 * triples on disk for use from one session to the next...
3015 #define FIRSTCH 0x20
3016 #define LASTCH 0x7e
3017 #define TABSZ (LASTCH - FIRSTCH + 1)
3019 static int xlate_key;
3023 * xlate_in() - xlate_in the given character
3025 char
3026 xlate_in(int c)
3028 register int eti;
3030 eti = xlate_key;
3031 if((c >= FIRSTCH) && (c <= LASTCH)){
3032 eti += (c - FIRSTCH);
3033 eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0;
3034 return((xlate_key = eti) + FIRSTCH);
3036 else
3037 return(c);
3042 * xlate_out() - xlate_out the given character
3044 char
3045 xlate_out(char c)
3047 register int dti;
3048 register int xch;
3050 if((c >= FIRSTCH) && (c <= LASTCH)){
3051 xch = c - (dti = xlate_key);
3052 xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0;
3053 dti = (xch - FIRSTCH) + dti;
3054 dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0;
3055 xlate_key = dti;
3056 return(xch);
3058 else
3059 return(c);
3061 #endif /* PASSFILE */
3064 #ifdef LOCAL_PASSWD_CACHE
3066 int cache_method_was_setup (char *pinerc)
3068 int rv = 1;
3069 #ifdef PASSFILE
3070 char tmp[MAILTMPLEN];
3071 FILE *fp = NULL;
3072 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb")))
3073 rv = 0;
3074 if(fp != NULL) fclose(fp);
3075 #endif /* PASSFILE */
3076 return rv;
3079 int
3080 line_get(char *tmp, size_t len, char **textp)
3082 char *s;
3084 tmp[0] = '\0';
3085 if (*textp == NULL
3086 || (s = strchr(*textp, '\n')) == NULL
3087 || (s - *textp) > len - 1)
3088 return 0;
3090 *s = '\0';
3091 if(*(s-1) == '\r')
3092 *(s-1) = '\0';
3094 snprintf(tmp, len, "%s\n", *textp);
3095 tmp[len-1] = '\0';
3096 *textp = s+1;
3098 return 1;
3101 typedef struct pwd_s {
3102 char *blob;
3103 char **blobarray;
3104 char *host;
3105 char *user;
3106 char *sflags;
3107 char *passwd;
3108 char *orighost;
3109 } ALPINE_PWD_S;
3111 #define SAME_VALUE(X, Y) ((((X) == NULL && (Y) == NULL) \
3112 || ((X) && (Y) && !strcmp((X), (Y)))) ? 1 : 0)
3115 * For UNIX:
3116 * Passfile lines are
3118 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3120 * In pine4.40 and before there was no orig_hostname, and there still isn't
3121 * if it is the same as hostname.
3123 * else for WINDOWS:
3124 * Use Windows credentials. The TargetName of the credential is
3125 * UWash_Alpine_.partnumber-totalparts_<hostname:port>\tuser\taltflag
3126 * and the blob consists of
3127 * passwd\torighost (if different from host)
3129 * We don't use anything fancy we just copy out all the credentials which
3130 * begin with TNAME and put them into our cache, so we don't lookup based
3131 * on the TargetName or anything like that. That was so we could re-use
3132 * the existing code and so that orighost data could be easily used.
3135 read_passfile(pinerc, l)
3136 char *pinerc;
3137 MMLOGIN_S **l;
3139 #ifdef WINCRED
3140 # if (WINCRED > 0)
3141 LPCTSTR lfilter = TEXT(TNAMESTAR);
3142 DWORD count, k;
3143 PCREDENTIAL *pcred;
3144 char *tmp, *blob, *target = NULL;
3145 ALPINE_PWD_S **pwd;
3146 char *ui[5];
3147 int i, j;
3148 unsigned long m, n, p, loc;
3150 if(using_passfile == 0)
3151 return(using_passfile);
3153 if(!g_CredInited){
3154 if (init_wincred_funcs() != 1) {
3155 using_passfile = 0;
3156 return(using_passfile);
3160 dprint((9, "read_passfile\n"));
3162 using_passfile = 1;
3164 /* this code exists because the XOAUTH2 support makes us save
3165 * access tokens as if they were passwords. However, some servers
3166 * produce extremely long access-tokens that do not fit in the credentials
3167 * and therefore need to be split into several entries.
3169 * The plan is the following:
3170 * step 1: Read and save all the information in the credentials
3171 * step 2: flatten the information into one line
3172 * step 3: process that line.
3174 if (g_CredEnumerateW(lfilter, 0, &count, &pcred)) {
3175 pwd = fs_get((count + 1)*sizeof(ALPINE_PWD_S *));
3176 memset((void *)pwd, 0, (count + 1)*sizeof(ALPINE_PWD_S *));
3177 if (pwd && pcred) {
3178 /* this is step 1 */
3179 for (k = 0; k < count; k++) { /* go through each credential */
3180 target = lptstr_to_utf8(pcred[k]->TargetName);
3181 tmp = srchstr(target, TNAME);
3182 if (tmp) {
3183 tmp += strlen(TNAME);
3184 if (*tmp == '.') {
3185 tmp++;
3186 m = strtoul(tmp, &tmp, 10);
3187 if (*tmp == '-') {
3188 tmp++;
3189 n = strtol(tmp, &tmp, 10);
3190 if (*tmp == '_') tmp++;
3193 else {
3194 m = n = 1;
3197 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3198 for (i = 0, j = 0; tmp[i] && j < 3; j++) {
3199 for (ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3200 ; /* find end of data */
3202 if (tmp[i])
3203 tmp[i++] = '\0'; /* tie off data */
3206 /* improve this. We are trying to find where we saved
3207 * this data, and in general this is fast if there is
3208 * only a few data, which is not unreasonable, but probably
3209 * can be done better.
3211 for (loc = 0; pwd[loc]
3212 && !(SAME_VALUE(ui[0], pwd[loc]->host)
3213 && SAME_VALUE(ui[1], pwd[loc]->user)
3214 && SAME_VALUE(ui[2], pwd[loc]->sflags)); loc++);
3216 if (pwd[loc] == NULL) {
3217 pwd[loc] = fs_get(sizeof(ALPINE_PWD_S));
3218 memset((void *) pwd[loc], 0, sizeof(ALPINE_PWD_S));
3219 pwd[loc]->blobarray = fs_get((n + 1) * sizeof(char*));
3220 memset((void *) pwd[loc]->blobarray, 0, (n + 1) * sizeof(char*));
3223 if (pwd[loc]->host == NULL)
3224 pwd[loc]->host = ui[0] ? cpystr(ui[0]) : NULL;
3225 if (pwd[loc]->user == NULL)
3226 pwd[loc]->user = ui[1] ? cpystr(ui[1]) : NULL;
3227 if (pwd[loc]->sflags == NULL)
3228 pwd[loc]->sflags = ui[2] ? cpystr(ui[2]) : NULL;
3229 blob = (char *) pcred[k]->CredentialBlob;
3230 pwd[loc]->blobarray[m - 1] = blob ? cpystr(blob) : NULL;
3232 if (target) fs_give((void**)&target);
3234 /* step 2 */
3235 for (k = 0; k < count; k++) {
3236 if (pwd[k]) {
3237 for (i = 0, j = 0; pwd[k]->blobarray[j]; j++)
3238 i += strlen(pwd[k]->blobarray[j]);
3239 pwd[k]->blob = fs_get(i + 1);
3240 pwd[k]->blob[0] = '\0';
3241 for (j = 0; pwd[k]->blobarray[j]; j++) {
3242 strcat(pwd[k]->blob, pwd[k]->blobarray[j]);
3243 fs_give((void **) &pwd[k]->blobarray[j]);
3245 fs_give((void **) pwd[k]->blobarray);
3247 else k = count; /* we are done with this step! */
3249 /* step 3 */
3250 for (k = 0; k < count; k++) {
3251 if (pwd[k] && pwd[k]->blob) {
3252 blob = pwd[k]->blob;
3253 for (i = 0, j = 3; blob[i] && j < 5; j++) {
3254 for (ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++)
3255 ; /* find end of data */
3257 if (blob[i])
3258 blob[i++] = '\0'; /* tie off data */
3260 if (pwd[k]->passwd == NULL)
3261 pwd[k]->passwd = ui[3] ? cpystr(ui[3]) : NULL;
3262 if (pwd[k]->orighost == NULL)
3263 pwd[k]->orighost = ui[4] ? cpystr(ui[4]) : NULL;
3264 fs_give((void **) &pwd[k]->blob);
3267 /* now process all lines, and free memory */
3268 for (k = 0; k < count && pwd[k] != NULL; k++){
3269 if (pwd[k]->passwd && pwd[k]->host && pwd[k]->user) { /* valid field? */
3270 STRLIST_S hostlist[2];
3271 int flags;
3273 tmp = pwd[k]->sflags ? strchr(pwd[k]->sflags, PWDAUTHSEP) : NULL;
3274 flags = pwd[k]->sflags ? atoi(tmp ? ++tmp : pwd[k]->sflags) : 0;
3275 hostlist[0].name = pwd[k]->host;
3276 if (pwd[k]->orighost) {
3277 hostlist[0].next = &hostlist[1];
3278 hostlist[1].name = pwd[k]->orighost;
3279 hostlist[1].next = NULL;
3281 else {
3282 hostlist[0].next = NULL;
3284 imap_set_passwd(l, pwd[k]->passwd, pwd[k]->user, hostlist, flags & 0x01, 0, 0);
3286 if (pwd[k]->passwd) fs_give((void **) &pwd[k]->passwd);
3287 if (pwd[k]->user) fs_give((void **) &pwd[k]->user);
3288 if (pwd[k]->host) fs_give((void **) &pwd[k]->host);
3289 if (pwd[k]->sflags) fs_give((void **) &pwd[k]->sflags);
3290 if (pwd[k]->orighost) fs_give((void **) &pwd[k]->orighost);
3291 fs_give((void **) &pwd[k]);
3293 g_CredFree((PVOID)pcred);
3295 fs_give((void **) pwd);
3297 return(1);
3299 # else /* old windows */
3300 using_passfile = 0;
3301 return(0);
3302 # endif
3304 #elif APPLEKEYCHAIN
3306 char target[MAILTMPLEN];
3307 char *tmp, *host, *user, *sflags, *passwd, *orighost;
3308 char *ui[5];
3309 int i, j, k, rc;
3310 SecKeychainAttributeList attrList;
3311 SecKeychainSearchRef searchRef = NULL;
3312 SecKeychainAttribute attrs[] = {
3313 { kSecAccountItemAttr, strlen(TNAME), TNAME }
3316 if(using_passfile == 0)
3317 return(using_passfile);
3319 dprint((9, "read_passfile\n"));
3322 /* search for only our items in the keychain */
3323 attrList.count = 1;
3324 attrList.attr = attrs;
3326 using_passfile = 1;
3327 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
3328 kSecGenericPasswordItemClass,
3329 &attrList,
3330 &searchRef))){
3331 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3332 if(searchRef){
3333 SecKeychainItemRef itemRef = NULL;
3334 SecKeychainAttributeInfo info;
3335 SecKeychainAttributeList *attrList = NULL;
3336 UInt32 blength = 0;
3337 char *blob = NULL;
3338 char *blobcopy = NULL; /* NULL terminated copy */
3340 UInt32 tags[] = {kSecAccountItemAttr,
3341 kSecServiceItemAttr};
3342 UInt32 formats[] = {0,0};
3344 dprint((10, "read_passfile: searchRef not NULL\n"));
3345 info.count = 2;
3346 info.tag = tags;
3347 info.format = formats;
3350 * Go through each item we found and put it
3351 * into our list.
3353 while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){
3354 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3355 rc = SecKeychainItemCopyAttributesAndData(itemRef,
3356 &info, NULL,
3357 &attrList,
3358 &blength,
3359 (void **) &blob);
3360 if(rc == 0 && attrList){
3361 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count));
3363 blobcopy = (char *) fs_get((blength + 1) * sizeof(char));
3364 strncpy(blobcopy, (char *) blob, blength);
3365 blobcopy[blength] = '\0';
3368 * I'm not real clear on how this works. It seems to be
3369 * necessary to combine the attributes from two passes
3370 * (attrList->count == 2) in order to get the full set
3371 * of attributes we inserted into the keychain in the
3372 * first place. So, we reset host...orighost outside of
3373 * the following for loop, not inside.
3375 host = user = sflags = passwd = orighost = NULL;
3376 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3378 for(k = 0; k < attrList->count; k++){
3380 if(attrList->attr[k].length){
3381 strncpy(target,
3382 (char *) attrList->attr[k].data,
3383 MIN(attrList->attr[k].length,sizeof(target)));
3384 target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0';
3387 tmp = target;
3388 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3389 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3390 ; /* find end of data */
3392 if(tmp[i])
3393 tmp[i++] = '\0'; /* tie off data */
3396 if(ui[0])
3397 host = ui[0];
3399 if(ui[1])
3400 user = ui[1];
3402 if(ui[2])
3403 sflags = ui[2];
3405 for(i = 0, j = 3; blobcopy[i] && j < 5; j++){
3406 for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++)
3407 ; /* find end of data */
3409 if(blobcopy[i])
3410 blobcopy[i++] = '\0'; /* tie off data */
3413 if(ui[3])
3414 passwd = ui[3];
3416 if(ui[4])
3417 orighost = ui[4];
3419 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:""));
3422 if(passwd && host && user){ /* valid field? */
3423 STRLIST_S hostlist[2];
3424 int flags;
3426 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3427 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3428 hostlist[0].name = host;
3429 if(orighost){
3430 hostlist[0].next = &hostlist[1];
3431 hostlist[1].name = orighost;
3432 hostlist[1].next = NULL;
3434 else{
3435 hostlist[0].next = NULL;
3438 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3441 if(blobcopy)
3442 fs_give((void **) & blobcopy);
3444 SecKeychainItemFreeAttributesAndData(attrList, (void *) blob);
3446 else{
3447 using_passfile = 0;
3448 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc));
3451 CFRelease(itemRef);
3452 itemRef = NULL;
3455 CFRelease(searchRef);
3457 else{
3458 using_passfile = 0;
3459 dprint((10, "read_passfile: searchRef NULL\n"));
3462 else{
3463 using_passfile = 0;
3464 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc));
3467 return(using_passfile);
3469 #else /* PASSFILE */
3471 char tmp[MAILTMPLEN], *ui[5];
3472 int i, j, n, rv = 0;
3473 size_t len = 0;
3474 char *tmptext = NULL;
3475 struct stat sbuf;
3476 #ifdef SMIME
3477 char tmp2[MAILTMPLEN];
3478 char *text = NULL, *text2 = NULL;
3479 int encrypted = 0;
3480 #endif /* SMIME */
3481 FILE *fp;
3483 if(using_passfile == 0)
3484 return(using_passfile);
3486 dprint((9, "read_passfile\n"));
3488 /* if there's no password to read, bag it!! */
3489 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){
3490 using_passfile = 0;
3491 return(using_passfile);
3494 #ifndef SMIME
3495 if(our_stat(tmp, &sbuf) == 0)
3496 len = sbuf.st_size;
3497 fp = our_fopen(tmp, "rb"); /* reopen to read data */
3498 #else
3499 /* the next call initializes the key/certificate pair used to
3500 * encrypt and decrypt a password file. The details of how this is
3501 * done is in the file pith/smime.c. During this setup we might call
3502 * smime_init(), but no matter what happens we must call smime_deinit()
3503 * there. The reason why this is so is because we can not assume that
3504 * the .pinerc file has been read by this time, so this code might not
3505 * know about the ps_global->smime structure or any of its components,
3506 * and it shouldn't because it only needs ps_global->pwdcert, so
3507 * do not init smime here, because the .pinerc might not have been
3508 * read and we do not really know where the keys and certificates really
3509 * are.
3510 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3511 * it is called for the first time and there are certificates at all,
3512 * or when it is called after the first time and the user refuses to
3513 * create a self-signed certificate. In this situation we will just
3514 * let the user live in an insecure world, but no more passwords will
3515 * be saved in the password file, and only those found there will be used.
3517 tmp2[0] = '\0';
3518 fgets(tmp2, sizeof(tmp2), fp);
3519 fclose(fp);
3520 if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
3521 /* there is an already existing password file, that is not encrypted
3522 * and there is no key to encrypt it yet, go again through setup_pwdcert
3523 * and encrypt it now.
3525 if(tmp2[0]){ /* not empty, UNencrypted password file */
3526 if(ps_global->pwdcert == NULL)
3527 rv = setup_pwdcert(&ps_global->pwdcert);
3528 if((rv == 0 || rv == -5) && ps_global->pwdcert == NULL)
3529 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3530 if(ps_global->pwdcert == NULL){
3531 q_status_message(SM_ORDER, 3, 3,
3532 " Failed to create private key. Using UNencrypted Password file. ");
3533 save_password = 0;
3535 else{
3536 if(rv == 1){
3537 q_status_message(SM_ORDER, 3, 3,
3538 " Failed to unlock private key. Using UNencrypted Password file. ");
3539 save_password = 0; /* do not save more passwords */
3542 if(ps_global->pwdcert != NULL
3543 && encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
3544 encrypted++;
3547 else {
3548 if(ps_global->pwdcert == NULL)
3549 rv = setup_pwdcert(&ps_global->pwdcert);
3550 encrypted++;
3554 * if password file is encrypted we attempt to decrypt. We ask the
3555 * user for the password to unlock the password file. If the user
3556 * enters the password and it unlocks the file, use it and keep saving
3557 * passwords in it. If the user enters the wrong passwords and does
3558 * not unlock it, we will not see that here, but in decrypt_file, so
3559 * the only other possibility is that the user cancels. In that case
3560 * we will see i == -1. In that case, we will let the user attempt
3561 * manual login to the server they want to login, but passwords will
3562 * not be saved so that the password file will not be saved
3563 * unencrypted and rewritten again.
3565 if(encrypted){
3566 text = text2 = decrypt_file((char *)tmp, &i, (PERSONAL_CERT *)ps_global->pwdcert);
3567 len = text2 ? strlen(text2) : 0;
3568 switch(i){
3569 case -2: using_passfile = 0;
3570 break;
3572 case 1 : save_password = 1;
3573 using_passfile = 1;
3574 break;
3576 case -1: save_password = 0;
3577 using_passfile = 1;
3578 break;
3580 default: break;
3583 #endif /* SMIME */
3585 if(using_passfile == 0){
3586 #ifdef SMIME
3587 if(text) fs_give((void **)&text);
3588 #endif /* SMIME */
3589 return using_passfile;
3592 if(len > 0){
3593 tmptext = fs_get(len + 1);
3594 #ifdef SMIME
3595 for(n = 0; encrypted ? line_get(tmptext, len + 1, &text2)
3596 : (fgets(tmptext, len+1, fp) != NULL); n++){
3597 #else /* SMIME */
3598 for(n = 0; fgets(tmptext, len+1, fp); n++){
3599 #endif /* SMIME */
3600 /*** do any necessary DEcryption here ***/
3601 xlate_key = n;
3602 for(i = 0; tmptext[i]; i++)
3603 tmptext[i] = xlate_out(tmptext[i]);
3605 if(i && tmptext[i-1] == '\n')
3606 tmptext[i-1] = '\0'; /* blast '\n' */
3608 dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
3609 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3610 for(i = 0, j = 0; tmptext[i] && j < 5; j++){
3611 for(ui[j] = &tmptext[i]; tmptext[i] && tmptext[i] != '\t'; i++)
3612 ; /* find end of data */
3614 if(tmptext[i])
3615 tmptext[i++] = '\0'; /* tie off data */
3618 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3619 if(ui[0] && ui[1] && ui[2]){ /* valid field? */
3620 STRLIST_S hostlist[2];
3621 char *s = ui[3] ? strchr(ui[3], PWDAUTHSEP) : NULL;
3622 int flags = ui[3] ? atoi(s ? ++s : ui[3]) : 0;
3624 hostlist[0].name = ui[2];
3625 if(ui[4]){
3626 hostlist[0].next = &hostlist[1];
3627 hostlist[1].name = ui[4];
3628 hostlist[1].next = NULL;
3630 else{
3631 hostlist[0].next = NULL;
3634 imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0);
3639 if (tmptext) fs_give((void **) &tmptext);
3640 #ifdef SMIME
3641 if (text) fs_give((void **)&text);
3642 #else /* SMIME */
3643 fclose(fp);
3644 #endif /* SMIME */
3645 return(1);
3646 #endif /* PASSFILE */
3651 void
3652 write_passfile(pinerc, l)
3653 char *pinerc;
3654 MMLOGIN_S *l;
3656 char *authend, *authtype;
3657 #ifdef WINCRED
3658 # if (WINCRED > 0)
3659 int i, j, k;
3660 char target[10*MAILTMPLEN];
3661 char blob[10 * MAILTMPLEN], blob2[10*MAILTMPLEN], *blobp;
3662 char part[MAILTMPLEN];
3663 CREDENTIAL cred;
3664 LPTSTR ltarget = 0;
3666 if(using_passfile == 0)
3667 return;
3669 dprint((9, "write_passfile\n"));
3671 for(; l; l = l->next){
3672 /* determine how many parts to create first */
3673 snprintf(blob, sizeof(blob), "%s%s%s",
3674 l->passwd ? l->passwd : "",
3675 (l->hosts&& l->hosts->next&& l->hosts->next->name)
3676 ? "\t" : "",
3677 (l->hosts&& l->hosts->next&& l->hosts->next->name)
3678 ? l->hosts->next->name : "");
3679 i = strlen(blob);
3680 blobp = blob;
3681 for (j = 1; i > MAXPWDBUFFERSIZE; j++, i -= PWDBUFFERSIZE);
3682 authtype = l->passwd;
3683 authend = strchr(l->passwd, PWDAUTHSEP);
3684 if (authend != NULL){
3685 *authend = '\0';
3686 sprintf(blob2, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3687 *authend = PWDAUTHSEP;
3689 else
3690 sprintf(blob2, "%d", l->altflag);
3691 for (k = 1, i = strlen(blob), blobp = blob; k <= j; k++) {
3692 snprintf(target, sizeof(target), "%s.%d-%d_%s\t%s\t%s",
3693 TNAME, k, j,
3694 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3695 l->user ? l->user : "",
3696 blob2);
3697 ltarget = utf8_to_lptstr((LPSTR)target);
3698 if (ltarget) {
3699 memset((void*)&cred, 0, sizeof(cred));
3700 cred.Flags = 0;
3701 cred.Type = CRED_TYPE_GENERIC;
3702 cred.TargetName = ltarget;
3703 if (i > MAXPWDBUFFERSIZE) {
3704 strncpy(part, blobp, PWDBUFFERSIZE);
3705 part[PWDBUFFERSIZE] = '\0';
3706 blobp += PWDBUFFERSIZE;
3707 i -= PWDBUFFERSIZE;
3709 else
3710 strcpy(part, blobp);
3711 cred.CredentialBlobSize = strlen(part) + 1;
3712 cred.CredentialBlob = (LPBYTE)&part;
3713 cred.Persist = CRED_PERSIST_ENTERPRISE;
3714 g_CredWriteW(&cred, 0);
3715 fs_give((void**)&ltarget);
3719 #endif /* WINCRED > 0 */
3721 #elif APPLEKEYCHAIN
3722 int rc;
3723 char target[10*MAILTMPLEN];
3724 char blob[10*MAILTMPLEN];
3725 SecKeychainItemRef itemRef = NULL;
3727 if(using_passfile == 0)
3728 return;
3730 dprint((9, "write_passfile\n"));
3732 for(; l; l = l->next){
3733 authtype = l->passwd;
3734 authend = strchr(l->passwd, PWDAUTHSEP);
3735 if(authend != NULL){
3736 *authend = '\0';
3737 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3738 *authend = PWDAUTHSEP;
3740 else
3741 sprintf(blob, "%d", l->altflag);
3743 snprintf(target, sizeof(target), "%s\t%s\t%s",
3744 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3745 l->user ? l->user : "",
3746 blob);
3748 snprintf(blob, sizeof(blob), "%s%s%s",
3749 l->passwd ? l->passwd : "",
3750 (l->hosts && l->hosts->next && l->hosts->next->name)
3751 ? "\t" : "",
3752 (l->hosts && l->hosts->next && l->hosts->next->name)
3753 ? l->hosts->next->name : "");
3755 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob));
3757 rc = SecKeychainAddGenericPassword(NULL,
3758 strlen(target), target,
3759 strlen(TNAME), TNAME,
3760 strlen(blob), blob,
3761 NULL);
3762 if(rc==0){
3763 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3765 else{
3766 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc));
3769 if(rc == errSecDuplicateItem){
3770 /* fix existing entry */
3771 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3772 itemRef = NULL;
3773 if(!(rc=SecKeychainFindGenericPassword(NULL,
3774 strlen(target), target,
3775 strlen(TNAME), TNAME,
3776 NULL, NULL,
3777 &itemRef)) && itemRef){
3779 rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob);
3780 if(!rc){
3781 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc));
3784 else{
3785 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc));
3790 #else /* PASSFILE */
3791 char tmp[10*MAILTMPLEN], blob[10*MAILTMPLEN];
3792 int i, n;
3793 FILE *fp;
3794 #ifdef SMIME
3795 char *text = NULL, tmp2[10*MAILTMPLEN];
3796 int len = 0;
3797 #endif
3799 if(using_passfile == 0)
3800 return;
3802 dprint((9, "write_passfile\n"));
3804 /* if there's no passfile to read, bag it!! */
3805 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "wb"))){
3806 using_passfile = 0;
3807 return;
3810 #ifdef SMIME
3811 strncpy(tmp2, tmp, sizeof(tmp2));
3812 tmp2[sizeof(tmp2)-1] = '\0';
3813 #endif /* SMIME */
3815 for(n = 0; l; l = l->next, n++){
3816 authtype = l->passwd;
3817 authend = strchr(l->passwd, PWDAUTHSEP);
3818 if(authend != NULL){
3819 *authend = '\0';
3820 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3821 *authend = PWDAUTHSEP;
3823 else
3824 sprintf(blob, "%d", l->altflag);
3826 /*** do any necessary ENcryption here ***/
3827 snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%s%s%s\n", l->passwd, l->user,
3828 l->hosts->name, blob,
3829 (l->hosts->next && l->hosts->next->name) ? "\t" : "",
3830 (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
3831 : "");
3832 dprint((10, "write_passfile: %s", tmp ? tmp : "?"));
3833 xlate_key = n;
3834 for(i = 0; tmp[i]; i++)
3835 tmp[i] = xlate_in(tmp[i]);
3837 #ifdef SMIME
3838 fs_resize((void **)&text, (len + strlen(tmp) + 1)*sizeof(char));
3839 text[len] = '\0';
3840 len += strlen(tmp) + 1;
3841 strncat(text, tmp, strlen(tmp));
3842 #else /* SMIME */
3843 fputs(tmp, fp);
3844 #endif /* SMIME */
3847 fclose(fp);
3848 #ifdef SMIME
3849 if(text != NULL){
3850 i = 0; /* to quell gcc */
3851 if(ps_global->pwdcert == NULL){
3852 q_status_message(SM_ORDER, 3, 3, "Attempting to encrypt password file");
3853 i = setup_pwdcert(&ps_global->pwdcert);
3854 if((i == 0 || i == -5) && ps_global->pwdcert == NULL)
3855 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3857 if(ps_global->pwdcert == NULL){ /* we tried but failed */
3858 if(i == -1)
3859 q_status_message1(SM_ORDER, 3, 3, "Error: no directory %s to save certificates", ps_global->pwdcertdir);
3860 else
3861 q_status_message(SM_ORDER, 3, 3, "Refusing to write non-encrypted password file");
3863 else if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0)
3864 q_status_message(SM_ORDER, 3, 3, "Failed to encrypt password file");
3865 fs_give((void **)&text); /* do not save this text */
3867 #endif /* SMIME */
3868 #endif /* PASSFILE */
3871 #endif /* LOCAL_PASSWD_CACHE */
3874 #if (WINCRED > 0)
3875 void
3876 erase_windows_credentials(void)
3878 LPCTSTR lfilter = TEXT(TNAMESTAR);
3879 DWORD count, k;
3880 PCREDENTIAL *pcred;
3882 if(!g_CredInited){
3883 if(init_wincred_funcs() != 1)
3884 return;
3887 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
3888 if(pcred){
3889 for(k = 0; k < count; k++)
3890 g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0);
3892 g_CredFree((PVOID) pcred);
3897 void
3898 ask_erase_credentials(void)
3900 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){
3901 erase_windows_credentials();
3902 q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased");
3904 else
3905 q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased");
3908 #endif /* WINCRED */
3911 #ifdef LOCAL_PASSWD_CACHE
3913 get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
3914 char *pinerc, **passwd, *user;
3915 STRLIST_S *hostlist;
3916 int altflag;
3918 return get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, NULL);
3922 * get_passfile_passwd_auth - return the password contained in the special password
3923 * cache. The file is assumed to be in the same directory
3924 * as the pinerc with the name defined above.
3927 get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, authtype)
3928 char *pinerc, **passwd, *user;
3929 STRLIST_S *hostlist;
3930 int altflag;
3931 char *authtype;
3933 dprint((10, "get_passfile_passwd_auth\n"));
3934 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3935 ? imap_get_passwd_auth(passfile_cache, passwd,
3936 user, hostlist, altflag, authtype)
3937 : 0);
3940 void
3941 free_passfile_cache_work(MMLOGIN_S **pwdcache)
3943 if(pwdcache == NULL || *pwdcache == NULL)
3944 return;
3946 if((*pwdcache)->user) fs_give((void **)&(*pwdcache)->user);
3947 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
3948 if((*pwdcache)->hosts) free_strlist(&(*pwdcache)->hosts);
3949 free_passfile_cache_work(&(*pwdcache)->next);
3950 fs_give((void **)pwdcache);
3954 void
3955 free_passfile_cache(void)
3957 if(passfile_cache)
3958 free_passfile_cache_work(&passfile_cache);
3962 is_using_passfile(void)
3964 return(using_passfile == 1);
3968 * Just trying to guess the username the user might want to use on this
3969 * host, the user will confirm.
3971 char *
3972 get_passfile_user(pinerc, hostlist)
3973 char *pinerc;
3974 STRLIST_S *hostlist;
3976 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3977 ? imap_get_user(passfile_cache, hostlist)
3978 : NULL);
3983 preserve_prompt(char *pinerc)
3985 return preserve_prompt_auth(pinerc, NULL);
3989 preserve_prompt_auth(char *pinerc, char *authtype)
3991 #ifdef WINCRED
3992 # if (WINCRED > 0)
3993 #define PROMPT_PWD _("Preserve password for next login")
3994 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3996 * This prompt was going to be able to be turned on and off via a registry
3997 * setting controlled from the config menu. We decided to always use the
3998 * dialog for login, and there the prompt is unobtrusive enough to always
3999 * be in there. As a result, windows should never reach this, but now
4000 * OS X somewhat uses the behavior just described.
4002 if(mswin_store_pass_prompt()
4003 && (want_to(authtype
4004 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4005 : PROMPT_PWD,
4006 'y', 'x', NO_HELP, WT_NORM)
4007 == 'y'))
4008 return(1);
4009 else
4010 return(0);
4011 # else
4012 return(0);
4013 # endif
4015 #elif APPLEKEYCHAIN
4016 #define PROMPT_PWD _("Preserve password for next login")
4017 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4019 int rc;
4020 if((rc = macos_store_pass_prompt()) != 0){
4021 if(want_to(authtype
4022 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4023 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4024 == 'y'){
4025 if(rc == -1){
4026 macos_set_store_pass_prompt(1);
4027 q_status_message(SM_ORDER, 4, 4,
4028 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4030 return(1);
4032 else if(rc == -1){
4033 macos_set_store_pass_prompt(0);
4034 q_status_message(SM_ORDER, 4, 4,
4035 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4037 return(0);
4039 return(0);
4040 #else /* PASSFILE */
4041 #define PROMPT_PWD _("Preserve password on DISK for next login")
4042 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
4044 char tmp[MAILTMPLEN];
4045 struct stat sbuf;
4047 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || our_stat(tmp, &sbuf) < 0)
4048 return 0;
4050 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
4051 return(want_to(authtype
4052 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4053 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4054 == 'y');
4055 return(0);
4056 #endif /* PASSFILE */
4059 #endif /* LOCAL_PASSWD_CACHE */
4062 #ifdef APPLEKEYCHAIN
4065 * Returns:
4066 * 1 if store pass prompt is set in the "registry" to on
4067 * 0 if set to off
4068 * -1 if not set to anything
4071 macos_store_pass_prompt(void)
4073 char *data = NULL;
4074 UInt32 len = 0;
4075 int rc = -1;
4076 int val;
4078 if(storepassprompt == -1){
4079 if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL,
4080 strlen(TNAMEPROMPT),
4081 TNAMEPROMPT, &len,
4082 (void **) &data, NULL))){
4083 val = (len == 1 && data && data[0] == '1');
4087 if(storepassprompt == -1 && !rc){
4088 if(val)
4089 storepassprompt = 1;
4090 else
4091 storepassprompt = 0;
4094 return(storepassprompt);
4098 void
4099 macos_set_store_pass_prompt(int val)
4101 storepassprompt = val ? 1 : 0;
4103 SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT),
4104 TNAMEPROMPT, 1, val ? "1" : "0", NULL);
4108 void
4109 macos_erase_keychain(void)
4111 SecKeychainAttributeList attrList;
4112 SecKeychainSearchRef searchRef = NULL;
4113 SecKeychainAttribute attrs1[] = {
4114 { kSecAccountItemAttr, strlen(TNAME), TNAME }
4116 SecKeychainAttribute attrs2[] = {
4117 { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT }
4120 dprint((9, "macos_erase_keychain\n"));
4123 * Seems like we ought to be able to combine attrs1 and attrs2
4124 * into a single array, but I couldn't get it to work.
4127 /* search for only our items in the keychain */
4128 attrList.count = 1;
4129 attrList.attr = attrs1;
4131 if(!SecKeychainSearchCreateFromAttributes(NULL,
4132 kSecGenericPasswordItemClass,
4133 &attrList,
4134 &searchRef)){
4135 if(searchRef){
4136 SecKeychainItemRef itemRef = NULL;
4139 * Go through each item we found and put it
4140 * into our list.
4142 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4143 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4144 SecKeychainItemDelete(itemRef);
4145 CFRelease(itemRef);
4148 CFRelease(searchRef);
4152 attrList.count = 1;
4153 attrList.attr = attrs2;
4155 if(!SecKeychainSearchCreateFromAttributes(NULL,
4156 kSecGenericPasswordItemClass,
4157 &attrList,
4158 &searchRef)){
4159 if(searchRef){
4160 SecKeychainItemRef itemRef = NULL;
4163 * Go through each item we found and put it
4164 * into our list.
4166 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4167 SecKeychainItemDelete(itemRef);
4168 CFRelease(itemRef);
4171 CFRelease(searchRef);
4176 #endif /* APPLEKEYCHAIN */
4178 #ifdef LOCAL_PASSWD_CACHE
4180 void
4181 set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
4182 char *pinerc, *passwd, *user;
4183 STRLIST_S *hostlist;
4184 int altflag, already_prompted;
4186 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, NULL);
4189 * set_passfile_passwd - set the password file entry associated with
4190 * cache. The file is assumed to be in the same directory
4191 * as the pinerc with the name defined above.
4192 * already_prompted: 0 not prompted
4193 * 1 prompted, answered yes
4194 * 2 prompted, answered no
4196 void
4197 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, authtype)
4198 char *pinerc, *passwd, *user;
4199 STRLIST_S *hostlist;
4200 int altflag, already_prompted;
4201 char *authtype;
4203 dprint((10, "set_passfile_passwd_auth\n"));
4204 if(((already_prompted == 0 && preserve_prompt_auth(pinerc, authtype))
4205 || already_prompted == 1)
4206 && !ps_global->nowrite_password_cache
4207 && (passfile_cache || read_passfile(pinerc, &passfile_cache))){
4208 imap_set_passwd_auth(&passfile_cache, passwd, user, hostlist, altflag, 0, 0, authtype);
4209 write_passfile(pinerc, passfile_cache);
4213 void
4214 update_passfile_hostlist(pinerc, user, hostlist, altflag)
4215 char *pinerc;
4216 char *user;
4217 STRLIST_S *hostlist;
4218 int altflag;
4220 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, NULL);
4224 * Passfile lines are
4226 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4228 * In pine4.40 and before there was no orig_hostname.
4229 * This routine attempts to repair that.
4231 void
4232 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, authtype)
4233 char *pinerc;
4234 char *user;
4235 STRLIST_S *hostlist;
4236 int altflag;
4237 char *authtype;
4239 #ifdef WINCRED
4240 return;
4241 #else /* !WINCRED */
4242 MMLOGIN_S *l;
4243 size_t len = authtype ? strlen(authtype) : 0;
4244 size_t offset = authtype ? 1 : 0;
4246 for(l = passfile_cache; l; l = l->next)
4247 if(imap_same_host_auth(l->hosts, hostlist, authtype)
4248 && *user
4249 && !strcmp(user, l->user + len + offset)
4250 && l->altflag == altflag){
4251 break;
4254 if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
4255 && hostlist->next->name
4256 && !ps_global->nowrite_password_cache){
4257 l->hosts->next = new_strlist_auth(hostlist->next->name, authtype, PWDAUTHSEP);
4258 write_passfile(pinerc, passfile_cache);
4260 #endif /* !WINCRED */
4263 #endif /* LOCAL_PASSWD_CACHE */
4266 #if (WINCRED > 0)
4268 * Load and init the WinCred structure.
4269 * This gives us a way to skip the WinCred code
4270 * if the dll doesn't exist.
4273 init_wincred_funcs(void)
4275 if(!g_CredInited)
4277 HMODULE hmod;
4279 /* Assume the worst. */
4280 g_CredInited = -1;
4282 hmod = LoadLibrary(TEXT("advapi32.dll"));
4283 if(hmod)
4285 FARPROC fpCredWriteW;
4286 FARPROC fpCredEnumerateW;
4287 FARPROC fpCredDeleteW;
4288 FARPROC fpCredFree;
4290 fpCredWriteW = GetProcAddress(hmod, "CredWriteW");
4291 fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW");
4292 fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW");
4293 fpCredFree = GetProcAddress(hmod, "CredFree");
4295 if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree)
4297 g_CredWriteW = (CREDWRITEW *)fpCredWriteW;
4298 g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW;
4299 g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW;
4300 g_CredFree = (CREDFREE *)fpCredFree;
4302 g_CredInited = 1;
4306 mswin_set_erasecreds_callback(ask_erase_credentials);
4309 return g_CredInited;
4312 #endif /* WINCRED */