* Change x10host.com to alpineapp.email.
[alpine.git] / alpine / imap.c
blob78081fb0fb6505fc2948d6cbc90894df2e24071a
1 /*
2 * ========================================================================
3 * Copyright 2013-2022 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 (240)
59 #define MAXPWDBUFFERSIZE (2*PWDBUFFERSIZE) /* This number must be less than 512 with some room to TNAME and other extra characters */
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 *);
120 static int using_passfile = -1;
121 int save_password = 1;
122 #endif /* LOCAL_PASSWD_CACHE */
124 #ifdef PASSFILE
125 char xlate_in(int);
126 char xlate_out(char);
127 int line_get(char *, size_t, char **);
128 #endif /* PASSFILE */
130 #if (WINCRED > 0)
131 void ask_erase_credentials(void);
132 int init_wincred_funcs(void);
133 #endif /* WINCRED */
136 static char *details_cert, *details_host, *details_reason;
138 extern XOAUTH2_INFO_S xoauth_default[];
139 extern OAUTH2_S alpine_oauth2_list[];
141 void
142 cache_method_message(STORE_S *in_store)
144 #ifdef LOCAL_PASSWD_CACHE
145 if(cache_method_was_setup(ps_global->pinerc)){
146 so_puts(in_store, _("</P><P> Once you have authorized Alpine, Alpine will ask you if you want to preserve the Refresh Token and Access Code. If you do "));
147 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 "));
148 so_puts(in_store, _("alive at the end of this process, and your connection will proceed from there."));
150 else{
151 q_status_message(SM_ORDER | SM_DING, 3, 3, _("Create a password file in order to save the login information"));
152 so_puts(in_store, _("</P><P> Although your version of Alpine was compiled with password file support, this has not been set up yet. "));
153 so_puts(in_store, _("as a result, the next time you open this folder, you will go through this process once again.\n\n"));
155 #else
156 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 "));
157 so_puts(in_store, _("access token, which means that you will have to repeat this process the next time you login to this server. "));
158 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 "));
159 so_puts(in_store, _("computer that was compiled with password file support."));
160 #endif /* LOCAL_PASSWD_CACHE */
163 void
164 cache_method_message_no_screen (void)
166 #ifdef LOCAL_PASSWD_CACHE
167 if(cache_method_was_setup(ps_global->pinerc)){
168 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
169 _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
170 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
172 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
173 "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
174 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
176 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
177 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
178 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
180 else{
181 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
182 _(" Although your version of Alpine was compiled with password file support, this has not been set up yet. "));
183 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
184 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
185 _("as a result, the next time you open this folder, you will go through this process once again.\n\n"));
186 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
188 #else
189 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
190 _("Your version of Alpine was not built with password file support, as a result you will not be able to save your "));
191 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
192 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
193 _("access token, which means that you will have to repeat this process the next time you login to this server. "));
194 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
195 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
196 _("You can fix this by either recompiling alpine with password file support or by downloading a version of Alpine to your "));
197 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
198 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf), "%s",
199 _("computer that was compiled with password file support.\n\n"));
200 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
201 #endif /* LOCAL_PASSWD_CACHE */
205 xoauth2_flow_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
207 int rv = 0;
209 switch(cmd){
210 case MC_CHOICE:
211 *((*cl)->d.xf.selected) = (*cl)->d.xf.pat;
212 rv = simple_exit_cmd(flags);
214 case MC_EXIT:
215 rv = simple_exit_cmd(flags);
216 break;
218 default:
219 rv = -1;
222 if(rv > 0)
223 ps->mangled_body = 1;
225 return rv;
228 OAUTH2_S *
229 oauth2_select_flow(char *host)
231 OAUTH2_S *oa2list, *oa2;
232 int i = 0, rv;
233 char *method;
235 dprint((2, "-- oauth2_select_flow()\n"));
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 dprint((2, "-- oauth2_set_device_info\n"));
369 ps_global->in_xoauth2_auth = 1;
370 if(ps_global->ttyo){
371 SCROLL_S sargs;
372 STORE_S *in_store, *out_store;
373 gf_io_t pc, gc;
374 HANDLE_S *handles = NULL;
375 AUTH_CODE_S user_input;
377 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
378 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
379 goto try_wantto;
381 aux_value.xoauth2 = oa2;
382 aux_value.code_success = 'e';
383 aux_value.code_failure = 'e';
384 aux_value.code_wait = NO_OP_COMMAND;
386 so_puts(in_store, "<HTML><P>");
387 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name);
388 so_puts(in_store, tmp);
389 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name, method),
390 so_puts(in_store, tmp);
392 if(deviceinfo->verification_uri && deviceinfo->user_code){
393 sprintf(tmp,
394 _("</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."),
395 deviceinfo->verification_uri, deviceinfo->verification_uri, deviceinfo->user_code);
396 so_puts(in_store, tmp);
398 else{
399 so_puts(in_store, "</P><P>");
400 so_puts(in_store, (char *) deviceinfo->message);
402 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
403 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), name);
404 so_puts(in_store, tmp);
405 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
407 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 "));
408 so_puts(in_store, _("to grant access to Alpine to your data. "));
410 cache_method_message(in_store);
412 so_puts(in_store, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit."));
413 so_puts(in_store, _("</P></HTML>"));
415 so_seek(in_store, 0L, 0);
416 init_handles(&handles);
417 gf_filter_init();
418 gf_link_filter(gf_html2plain,
419 gf_html2plain_opt(NULL,
420 ps_global->ttyo->screen_cols, non_messageview_margin(),
421 &handles, NULL, GFHP_LOCAL_HANDLES));
422 gf_set_so_readc(&gc, in_store);
423 gf_set_so_writec(&pc, out_store);
424 gf_pipe(gc, pc);
425 gf_clear_so_writec(out_store);
426 gf_clear_so_readc(in_store);
428 memset(&sargs, 0, sizeof(SCROLL_S));
429 sargs.text.handles = handles;
430 sargs.text.text = so_text(out_store);
431 sargs.text.src = CharStar;
432 sargs.text.desc = _("help text");
433 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
434 sargs.proc.tool = oauth2_auth_answer;
435 sargs.proc.data.p = (void *)&user_input;
436 sargs.keys.menu = &oauth2_device_auth_keymenu;
437 /* don't want to re-enter c-client */
438 sargs.quell_newmail = 1;
439 setbitmap(sargs.keys.bitmap);
440 sargs.help.text = h_oauth2_start_device;
441 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
442 sargs.aux_function = oauth2deviceinfo_get_accesscode;
443 sargs.aux_value = (void *) &aux_value;
444 sargs.aux_condition = oauth2_elapsed_done;
445 sargs.decode_aux_rv_value = oauth2device_decode_reply;
446 sargs.aux_rv_value = (void *) &aux_rv_value;
448 do {
449 if(scrolltool(&sargs) == MC_NO)
450 ps_global->user_says_cancel = 1;
451 ps_global->mangled_screen = 1;
452 ps_global->painted_body_on_startup = 0;
453 ps_global->painted_footer_on_startup = 0;
454 } while (user_input.answer != 'e');
456 so_give(&in_store);
457 so_give(&out_store);
458 free_handles(&handles);
459 oauth2_elapsed_done(NULL);
461 else{
463 * If screen hasn't been initialized yet, use want_to.
465 try_wantto:
467 tmp_20k_buf[0] = '\0';
468 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
469 _("Authorizing Alpine Access to %s Email Services\n\n"), name);
470 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
472 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
473 _("Alpine is attempting to log you into your %s account, using the %s method. "), name, method),
474 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
476 if(deviceinfo->verification_uri && deviceinfo->user_code){
477 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
478 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
479 deviceinfo->verification_uri, deviceinfo->user_code);
480 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
482 else{
483 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
484 "%s\n\n", deviceinfo->message);
485 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
488 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
489 _("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);
490 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
492 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
493 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
494 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
496 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
497 "%s", _("to grant access to Alpine to your data.\n\n"));
498 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
500 cache_method_message_no_screen();
502 display_init_err(tmp_20k_buf, 0);
503 memset((void *)tmp, 0, sizeof(tmp));
504 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
505 tmp[sizeof(tmp)-1] = '\0';
507 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
508 int rv;
509 UCS ch;
511 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
512 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
513 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
515 aux_value.xoauth2 = oa2;
516 aux_value.code_success = 'y';
517 aux_value.code_failure = 'n';
518 aux_value.code_wait = 'w';
520 strncpy(tmp, _("Continue waiting"), sizeof(tmp));
521 tmp[sizeof(tmp)-1] = '\0';
522 do {
523 if(oauth2_elapsed_done((void *) &aux_value) == 0)
524 oauth2deviceinfo_get_accesscode((void *) &aux_value, (void *) &rv);
525 ch = oauth2device_decode_reply((void *) &aux_value, (void *) &rv);
526 } while (ch == 'w' || want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y');
527 oauth2_elapsed_done(NULL);
532 char *
533 oauth2_get_access_code(unsigned char *url, char *method, OAUTH2_S *oauth2, int *tryanother)
535 char tmp[MAILTMPLEN];
536 char *code = NULL;
538 dprint((2, "-- oauth2_get_access_code\n"));
539 ps_global->in_xoauth2_auth = 1;
540 if(ps_global->ttyo){
541 SCROLL_S sargs;
542 STORE_S *in_store, *out_store;
543 gf_io_t pc, gc;
544 HANDLE_S *handles = NULL;
545 AUTH_CODE_S user_input;
547 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
548 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
549 goto try_wantto;
551 so_puts(in_store, "<HTML><BODY><P>");
552 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2->name);
553 so_puts(in_store, tmp);
554 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
555 so_puts(in_store, tmp);
557 if(strucmp((char *) oauth2->name, (char *) GMAIL_NAME) == 0 && strstr(url, (char *) GMAIL_ID) != NULL){
558 so_puts(in_store, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
559 so_puts(in_store, _("</P><P> First you must register Alpine with Google and create a client-id and client-secret.</P>"));
560 so_puts(in_store, _("<UL> "));
561 so_puts(in_store, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
562 so_puts(in_store, _("and create a project. The name of the project is not important."));
563 so_puts(in_store, _("<LI> Go to the OAuth 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."));
564 so_puts(in_store, _("<LI> This will take you to several screens where you must input the required information. You can always use your email address for developer and contact information. Do not add scopes when you get to the scopes screen and add your email address to the screen to add Test Users."));
565 so_puts(in_store, _("<LI> Create OAUTH Credentials."));
566 so_puts(in_store, _("</UL> "));
567 so_puts(in_store, _("<P> As a result of this process, you will get a client-id and a client-secret."));
568 so_puts(in_store, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently,"));
569 so_puts(in_store, _(" then retry login into Gmail's server."));
570 so_puts(in_store, _(" More detailed and up to date information on how to configure Alpine for Gmail can be found at the following <A href=\"https://alpineapp.email/alpine/alpine-info/misc/RegisteringAlpineinGmail.html\">link</A>."));
572 else{
573 so_puts(in_store, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
574 so_puts(in_store,"</P><P>");
575 sprintf(tmp_20k_buf, _("<A HREF=\"%s\">%s</A>"), url, url);
576 so_puts(in_store, tmp_20k_buf);
578 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
579 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2->name);
580 so_puts(in_store, tmp);
581 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
583 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. "));
584 so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page."));
585 so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
586 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. "));
588 cache_method_message(in_store);
591 so_puts(in_store, _("</P><P> If you do not wish to proceed, cancel by pressing 'E' to exit."));
592 so_puts(in_store, _("</P></BODY></HTML>"));
594 so_seek(in_store, 0L, 0);
595 init_handles(&handles);
596 gf_filter_init();
597 gf_link_filter(gf_html2plain,
598 gf_html2plain_opt(NULL,
599 ps_global->ttyo->screen_cols, non_messageview_margin(),
600 &handles, NULL, GFHP_LOCAL_HANDLES));
601 gf_set_so_readc(&gc, in_store);
602 gf_set_so_writec(&pc, out_store);
603 gf_pipe(gc, pc);
604 gf_clear_so_writec(out_store);
605 gf_clear_so_readc(in_store);
607 memset(&sargs, 0, sizeof(SCROLL_S));
608 sargs.text.handles = handles;
609 sargs.text.text = so_text(out_store);
610 sargs.text.src = CharStar;
611 sargs.text.desc = _("help text");
612 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
613 sargs.proc.tool = oauth2_auth_answer;
614 sargs.proc.data.p = (void *)&user_input;
615 sargs.keys.menu = &oauth2_auth_keymenu;
616 /* don't want to re-enter c-client */
617 sargs.quell_newmail = 1;
618 setbitmap(sargs.keys.bitmap);
619 sargs.help.text = h_oauth2_start;
620 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
622 do {
623 if(scrolltool(&sargs) == MC_NO)
624 ps_global->user_says_cancel = 1;
625 ps_global->mangled_screen = 1;
626 ps_global->painted_body_on_startup = 0;
627 ps_global->painted_footer_on_startup = 0;
628 } while (user_input.answer != 'e');
630 if(!struncmp(user_input.code, "http://", 7)
631 || !struncmp(user_input.code, "https://", 8)){
632 char *s, *t;
633 s = strstr(user_input.code, "code=");
634 if(s != NULL){
635 t = strchr(s, '&');
636 if(t) *t = '\0';
637 code = cpystr(s+5);
638 if(t) *t = '&';
641 else code = user_input.code ? cpystr(user_input.code) : NULL;
642 if(user_input.code) fs_give((void **) &user_input.code);
644 if(code == NULL) *tryanother = 1;
646 so_give(&in_store);
647 so_give(&out_store);
648 free_handles(&handles);
650 else{
651 int flags, rc, q_line;
652 /* TRANSLATORS: user needs to input an access code from the server */
653 char *accesscodelabel = _("Copy and Paste Access Code");
654 char prompt[MAILTMPLEN], token[MAILTMPLEN];
656 * If screen hasn't been initialized yet, use want_to.
658 try_wantto:
659 tmp_20k_buf[0] = '\0';
660 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
661 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2->name);
662 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
664 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
665 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
666 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
668 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
669 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
670 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
672 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
673 "%s\n\n", url);
674 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
676 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
677 _("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);
678 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
680 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
681 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
682 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
684 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
685 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
686 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
688 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
689 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
690 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
692 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
693 "%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. "));
694 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
696 cache_method_message_no_screen();
698 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
699 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
700 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
702 display_init_err(tmp_20k_buf, 0);
703 memset((void *)tmp, 0, sizeof(tmp));
704 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
705 tmp[sizeof(tmp)-1] = '\0';
707 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
708 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
709 flags = OE_APPEND_CURRENT;
710 sprintf(prompt, "%s: ", accesscodelabel);
711 do {
712 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
713 prompt, NULL, NO_HELP, &flags);
714 } while (rc != 0 && rc != 1);
715 if(!struncmp(token, "http://", 7)
716 || !struncmp(token, "https://", 8)){
717 char *s, *t;
718 s = strstr(token, "code=");
719 if(s != NULL){
720 t = strchr(s, '&');
721 if(t) *t = '\0';
722 code = cpystr(s+5);
723 if(t) *t = '&';
726 else code = token[0] ? cpystr(token) : NULL;
730 return code;
733 void mm_login_oauth2(NETMBX *, char *, char *, OAUTH2_S *, long int, char *, char *);
735 /* The purpose of this function is to report to c-client the values of the
736 * different tokens and codes so that c-client can try to log in the user
737 * to the server. This function DOES NOT attempt to get these values for
738 * the user. That is attempted in the c-client side (as best as it can be
739 * done given our circumstances: no http support, no javascript support,
740 * etc.). This is the best we can do:
742 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
743 * as best as we can. Unloaded means that there is no server known to
744 * connect, no access token, etc. Pretty much nothing is known about
745 * how to get access code, access token, etc. We ask the user to fill
746 * it up for us, if they can. If the user fills it up we save those
747 * values.
749 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
750 * save the information that c-client got for us.
752 * 3. When saving this information we use the password caching facilities,
753 * but we must do it in a different format so that old information and
754 * new information are not mixed. In order to accommodate this for new
755 * authentication methods, we save the information in the same fields,
756 * but this time we modify it slightly, so that old functions fail to
757 * understand the new information and so not modify it nor use it. The
758 * modification is simple: Every piece of information that was saved
759 * before is prepended XOAUTH2\001 to indicate the authentication
760 * method for which it works. The character \001 is a separator. New
761 * Alpine will know how to deal with this, but old versions, will not
762 * strip this prefix from the information and fail to get the
763 * information or modify it when needed. Only new versions of Alpine will
764 * know how to process this information.
765 * new_value = authenticator_method separator old_value
766 * authenticator_method = "XOAUTH2_S"
767 * separator = "U+1"
768 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
769 * authenticator is "XOAUTH2\001imap.gmail.com".
770 * In addition, the password field is not used to encode the password
771 * anymore, it is used to save login information needed in a format that
772 * the caller function chooses, but that must be preceded by the
773 * "authenticator_method separator" code as above.
775 void
776 mm_login_oauth2(NETMBX *mb, char *user, char *method,
777 OAUTH2_S *login, long int trial,
778 char *usethisprompt, char *altuserforcache)
780 char *token, tmp[MAILTMPLEN];
781 char prompt[4*MAILTMPLEN];
782 char *OldRefreshToken, *OldAccessToken;
783 char *NewRefreshToken, *NewAccessToken;
784 char *SaveRefreshToken, *SaveAccessToken;
785 /* TRANSLATORS: A label for the hostname that the user is logging in on */
786 char *hostlabel = _("HOST");
787 /* TRANSLATORS: user is logging in as a particular user (a particular
788 login name), this is just labelling that user name. */
789 char *userlabel = _("USER");
790 STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1];
791 int len, q_line, flags, i, j;
792 int save_in_init;
793 int registered;
794 int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
795 OAUTH2_S *oa2list, *oa2;
796 XOAUTH2_INFO_S *x;
797 unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
798 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
799 int preserve_password = -1;
800 #endif
802 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
803 trial, mb->user ? mb->user : "(null)",
804 mb->service ? mb->service : "(null)",
805 mb->port ? " port=" : "",
806 mb->port ? comatose(mb->port) : "",
807 altuserforcache ? " altuserforcache =" : "",
808 altuserforcache ? altuserforcache : ""));
810 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
812 save_in_init = ps_global->in_init_seq;
813 ps_global->in_init_seq = 0;
814 ps_global->no_newmail_check_from_optionally_enter = 1;
816 /* make sure errors are seen */
817 if(ps_global->ttyo && !ps_global->noshow_error
818 && login && (login->flags & OA2_OPENSTREAM))
819 flush_status_messages(0);
821 if(login && (login->flags & OA2_OPENSTREAM))
822 login->flags |= ~OA2_OPENSTREAM;
824 token = NULL; /* start from scratch */
826 hostlist[0].name = mb->host;
827 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
828 hostlist[0].next = &hostlist[1];
829 hostlist[1].name = mb->orighost;
830 hostlist[1].next = NULL;
832 else
833 hostlist[0].next = NULL;
835 if(hostlist[0].name){
836 dprint((9, "mm_login_oauth2: host=%s\n",
837 hostlist[0].name ? hostlist[0].name : "?"));
838 if(hostlist[0].next && hostlist[1].name){
839 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name));
843 if(trial == 0L && !altuserforcache){
844 if(*mb->user != '\0')
845 strncpy(user, mb->user, NETMAXUSER);
846 else{
847 flags = OE_APPEND_CURRENT;
848 sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
849 optionally_enter(user, q_line, 0, NETMAXUSER, prompt, NULL, NO_HELP, &flags);
851 user[NETMAXUSER-1] = '\0';
855 * We check to see if the server we are going to log in to is already
856 * registered. This gives us a list of servers with the same
857 * credentials, so we use the same credentials for all of them.
860 for(registered = 0, oa2list = alpine_oauth2_list;
861 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
862 oa2list++){
863 for(i = 0; i < OAUTH2_TOT_EQUIV
864 && oa2list->host[i] != NULL
865 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
866 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
867 registered++;
868 break;
872 if(registered){
873 x = oauth2_get_client_info(oa2list->name, user);
874 if(!x) return; /* user cancelled, let's get out of here */
875 if(x->flow){
876 int authorize = 0, device = 0;
877 for(oa2list = alpine_oauth2_list;
878 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
879 oa2list++){
880 for(i = 0; i < OAUTH2_TOT_EQUIV
881 && oa2list->host[i] != NULL
882 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
883 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
884 char *flow = oa2list->server_mthd[0].name ? "Authorize"
885 : (oa2list->server_mthd[1].name ? "Device" : NULL);
886 authorize += oa2list->server_mthd[0].name ? 1 : 0;
887 device += oa2list->server_mthd[1].name ? 1 : 0;
888 if(flow && !strucmp(x->flow, flow)) break; /* found it */
891 if(!oa2list || !oa2list->host || !oa2list->host[0]){
892 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
893 _("%s does not support or recognize flow type \"%s\". Use %s%s%s"),
894 mb->orighost,
895 x->flow,
896 authorize ? "\"Authorize\"" : "",
897 authorize ? (device ? " or " : "") : "",
898 device ? "\"Device\"" : "");
899 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
900 q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
901 free_xoauth2_info(&x);
902 return;
905 free_xoauth2_info(&x);
908 if(registered){
909 hostlist2[i = 0].name = mb->host;
910 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
911 hostlist2[++i].name = mb->orighost;
913 for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){
914 int k;
915 for(k = 0; k <= i && hostlist2[k].name
916 && strcmp(hostlist2[k].name, oa2list->host[j]); k++);
917 if(k == i + 1)
918 hostlist2[++i].name = oa2list->host[j];
920 hostlist2[i+1].name = NULL;
921 hostlist2[i+1].next = NULL;
922 for(j = i; j >= 0; j--)
923 hostlist2[j].next = &hostlist2[j+1];
926 if(registered){ /* redo the app_id, no questions asked */
927 free_id(&ps_global->id);
928 ps_global->id = set_alpine_id(oa2list->app_id ? oa2list->app_id : PACKAGE_NAME, PACKAGE_VERSION);
929 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
932 if(registered)
933 oa2list->param[OA2_State].value = login->param[OA2_State].value;
935 if(login->cancel_refresh_token){
936 imap_delete_passwd_auth(&mm_login_list, user,
937 registered ? hostlist2 : hostlist,
938 (mb->sslflag||mb->tlsflag), OA2NAME);
939 #ifdef LOCAL_PASSWD_CACHE
940 write_passfile(ps_global->pinerc, mm_login_list);
941 #endif /* LOCAL_PASSWD_CACHE */
945 * We check if we have a refresh token saved somewhere, if so
946 * we use it to get a new access token, otherwise we need to
947 * get an access code so we can get (and save) a refresh token
948 * and use the access token.
950 if(trial == 0L && !altuserforcache){
951 /* Search for a refresh token that is already loaded ... */
952 if(imap_get_passwd_auth(mm_login_list, &token, user,
953 registered ? hostlist2 : hostlist,
954 (mb->sslflag||mb->tlsflag), OA2NAME)){
955 dprint((9, "mm_login_oauth2: found a refresh token\n"));
956 ps_global->no_newmail_check_from_optionally_enter = 0;
957 ps_global->in_init_seq = save_in_init;
959 #ifdef LOCAL_PASSWD_CACHE
960 /* or see if we have saved one in the local password cache and load it */
961 else if(get_passfile_passwd_auth(ps_global->pinerc, &token,
962 user, registered ? hostlist2 : hostlist,
963 (mb->sslflag||mb->tlsflag), OA2NAME)){
964 imap_set_passwd_auth(&mm_login_list, token, user,
965 hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
966 update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist,
967 (mb->sslflag||mb->tlsflag), OA2NAME);
968 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
969 ps_global->no_newmail_check_from_optionally_enter = 0;
970 ps_global->in_init_seq = save_in_init;
972 if(token && *token) preserve_password = 1; /* resave it, no need to ask */
973 #endif /* LOCAL_PASSWD_CACHE */
975 user[NETMAXUSER-1] = '\0';
977 /* The Old* variables is what c_client knows */
978 OldRefreshToken = login->cancel_refresh_token ? NULL : login->param[OA2_RefreshToken].value;
979 OldAccessToken = login->access_token;
980 OldExpirationTime = login->expiration;
982 /* The New* variables is what Alpine knows */
983 NewRefreshToken = NewAccessToken = NULL;
984 NewExpirationTime = 0L;
985 ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0;
987 /* We have done all steps that cancellation requires us by this time */
988 if(login->cancel_refresh_token)
989 login->cancel_refresh_token = 0;
991 if(token && *token){
992 char *s, *t;
994 s = token;
995 t = strchr(s, PWDAUTHSEP);
996 if(t == NULL)
997 NewRefreshToken = cpystr(s);
998 else {
999 *t++ = '\0';
1000 NewRefreshToken = cpystr(s);
1001 s = t;
1002 t = strchr(s, PWDAUTHSEP);
1003 if(t == NULL)
1004 NewAccessToken = cpystr(s);
1005 else {
1006 *t++ = '\0';
1007 NewAccessToken = cpystr(s);
1008 s = t;
1009 NewExpirationTime = strtol(s, &s, 10);
1010 if(NewExpirationTime <= 0 || NewExpirationTime <= time(0))
1011 NewExpirationTime = 0L;
1014 /* check we got good information, and send good information below */
1015 if(NewRefreshToken && !*NewRefreshToken)
1016 fs_give((void **) &NewRefreshToken);
1017 if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken))
1018 fs_give((void **) &NewAccessToken);
1021 if(NewRefreshToken == NULL)
1022 login->first_time++;
1024 if(login->first_time){ /* count how many authorization methods we support */
1025 int nmethods, j;
1027 for(nmethods = 0, oa2 = alpine_oauth2_list; oa2 && oa2->name ; oa2++){
1028 for(j = 0; j < OAUTH2_TOT_EQUIV
1029 && oa2
1030 && oa2->host[j] != NULL
1031 && strucmp(oa2->host[j], mb->orighost) != 0; j++);
1032 if(oa2 && oa2->host && j < OAUTH2_TOT_EQUIV && oa2->host[j]
1033 && ((oa2->server_mthd[0].name && (oa2->flags & OA2_AUTHORIZE))
1034 || (oa2->server_mthd[1].name && (oa2->flags & OA2_DEVICE))))
1035 nmethods++;
1038 if(nmethods > 1)
1039 oa2list = oauth2_select_flow(mb->orighost);
1041 if(!oa2list) registered = 0;
1044 /* Default to saving what we already had saved */
1046 SaveRefreshToken = NewRefreshToken;
1047 SaveAccessToken = NewAccessToken;
1048 SaveExpirationTime = NewExpirationTime;
1050 /* Translation of the logic below:
1051 * if (c-client has a refresh token
1052 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1053 forget the Alpine refresh token;
1054 make the Alpine Refresh token = c-client refresh token.;
1055 signal that we changed the refresh token;
1056 In this situation we do not need to clear up the Alpine access token. This will
1057 expire, so we can use it until it expires. We can save the c-client refresh token
1058 together with the Alpine Access Token and the expiration date of the Alpine Access
1059 Token.
1060 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1061 forget the Alpine refresh token;
1062 if Alpine has an access token, forget it;
1063 reset the expiration time
1064 signal that we changed the refresh token; (because the service expired it)
1068 if(OldRefreshToken != NULL
1069 && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){
1070 if(NewRefreshToken) fs_give((void **) &NewRefreshToken);
1071 NewRefreshToken = cpystr(OldRefreshToken);
1072 ChangeRefreshToken++;
1073 SaveRefreshToken = OldRefreshToken;
1074 SaveAccessToken = NewAccessToken;
1075 SaveExpirationTime = NewExpirationTime;
1076 } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){
1077 fs_give((void **) &NewRefreshToken);
1078 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1079 NewExpirationTime = 0L;
1080 ChangeRefreshToken++;
1081 SaveRefreshToken = NULL;
1082 SaveAccessToken = NULL;
1083 SaveExpirationTime = 0L;
1086 if(OldAccessToken != NULL
1087 && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){
1088 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1089 NewAccessToken = cpystr(OldAccessToken);
1090 NewAccessToken = OldAccessToken;
1091 ChangeAccessToken++;
1092 NewExpirationTime = OldExpirationTime;
1093 SaveRefreshToken = NewRefreshToken;
1094 SaveAccessToken = NewAccessToken;
1095 SaveExpirationTime = NewExpirationTime;
1098 if(!registered){
1099 login->param[OA2_RefreshToken].value = SaveRefreshToken;
1100 login->access_token = SaveAccessToken;
1101 login->expiration = SaveExpirationTime;
1102 } else {
1103 oa2list->param[OA2_RefreshToken].value = SaveRefreshToken;
1104 oa2list->access_token = SaveAccessToken;
1105 oa2list->expiration = SaveExpirationTime;
1106 oa2list->first_time = login->first_time;
1107 oa2list->cancel_refresh_token = login->cancel_refresh_token;
1108 *login = *oa2list; /* load login pointer */
1110 if(token) fs_give((void **) &token);
1112 if(!ChangeAccessToken && !ChangeRefreshToken)
1113 return;
1115 /* get ready to save this information. The format will be
1116 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1117 * (spaces added for clarity, \001 is PWDAUTHSEP)
1119 sprintf(tmp, "%lu", SaveExpirationTime);
1120 tmp[sizeof(tmp) - 1] = '\0';
1121 len = strlen(SaveRefreshToken ? SaveRefreshToken : "")
1122 + strlen(SaveAccessToken ? SaveAccessToken : "")
1123 + strlen(tmp) + 2;
1124 token = fs_get(len + 1);
1125 sprintf(token, "%s%c%s%c%lu",
1126 SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP,
1127 SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP,
1128 SaveExpirationTime);
1130 /* remember the access information for next time */
1131 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
1132 imap_set_passwd_auth(&mm_login_list, token,
1133 altuserforcache ? altuserforcache : user, hostlist,
1134 (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1135 #ifdef LOCAL_PASSWD_CACHE
1136 /* if requested, remember it on disk for next session */
1137 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
1138 set_passfile_passwd_auth(ps_global->pinerc, token,
1139 altuserforcache ? altuserforcache : user, hostlist,
1140 (mb->sslflag||mb->tlsflag),
1141 (preserve_password == -1 ? 0
1142 : (preserve_password == 0 ? 2 :1)), OA2NAME);
1143 #endif /* LOCAL_PASSWD_CACHE */
1144 if (token) fs_give((void **) &token);
1145 ps_global->no_newmail_check_from_optionally_enter = 0;
1148 IDLIST *
1149 set_alpine_id(char *pname, char *pversion)
1151 IDLIST *id;
1153 if(!pname || !pversion) return NULL;
1155 id = fs_get(sizeof(IDLIST));
1156 id->name = cpystr("name");
1157 id->value = cpystr(pname);
1158 id->next = fs_get(sizeof(IDLIST));
1159 id->next->name = cpystr("version");
1160 id->next->value = cpystr(pversion);
1161 id->next->next = NULL;
1162 return id;
1165 void
1166 pine_delete_pwd(NETMBX *mb, char *user)
1168 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
1169 char port[20], non_def_port[20];
1170 STRLIST_S hostlist[2];
1171 MMLOGIN_S *l;
1172 struct servent *sv;
1173 /* do not invalidate password on cancel */
1174 if(ps_global->user_says_cancel != 0)
1175 return;
1177 dprint((9, "pine_delete_pwd\n"));
1179 /* setup hostlist */
1180 non_def_port[0] = '\0';
1181 if(mb->port && mb->service &&
1182 (sv = getservbyname(mb->service, "tcp")) &&
1183 (mb->port != ntohs(sv->s_port))){
1184 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
1185 non_def_port[sizeof(non_def_port)-1] = '\0';
1186 dprint((9, "mm_login: using non-default port=%s\n",
1187 non_def_port ? non_def_port : "?"));
1190 if(*non_def_port){
1191 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
1192 hostlist0[sizeof(hostlist0)-1] = '\0';
1193 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
1194 hostlist0[sizeof(hostlist0)-1] = '\0';
1195 hostlist[0].name = hostlist0;
1196 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1197 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
1198 hostlist1[sizeof(hostlist1)-1] = '\0';
1199 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
1200 hostlist1[sizeof(hostlist1)-1] = '\0';
1201 hostlist[0].next = &hostlist[1];
1202 hostlist[1].name = hostlist1;
1203 hostlist[1].next = NULL;
1205 else
1206 hostlist[0].next = NULL;
1208 else{
1209 hostlist[0].name = mb->host;
1210 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1211 hostlist[0].next = &hostlist[1];
1212 hostlist[1].name = mb->orighost;
1213 hostlist[1].next = NULL;
1215 else
1216 hostlist[0].next = NULL;
1218 imap_delete_passwd(&mm_login_list, user, hostlist, mb->sslflag||mb->tlsflag);
1219 #ifdef LOCAL_PASSWD_CACHE
1220 write_passfile(ps_global->pinerc, mm_login_list);
1221 #endif /* LOCAL_PASSWD_CACHE */
1224 /*----------------------------------------------------------------------
1225 receive notification from IMAP
1227 Args: stream -- Mail stream message is relevant to
1228 string -- The message text
1229 errflg -- Set if it is a serious error
1231 Result: message displayed in status line
1233 The facility is for general notices, such as connection to server;
1234 server shutting down etc... It is used infrequently.
1235 ----------------------------------------------------------------------*/
1236 void
1237 mm_notify(MAILSTREAM *stream, char *string, long int errflg)
1239 time_t now;
1240 struct tm *tm_now;
1242 now = time((time_t *)0);
1243 tm_now = localtime(&now);
1245 /* be sure to log the message... */
1246 #ifdef DEBUG
1247 if(ps_global->debug_imap || ps_global->debugmem)
1248 dprint((errflg == TCPDEBUG || errflg == HTTPDEBUG ? 7 : 2,
1249 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1250 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1251 tm_now->tm_mon+1, tm_now->tm_mday,
1252 (!errflg) ? "babble" :
1253 (errflg == ERROR) ? "error" :
1254 (errflg == WARN) ? "warning" :
1255 (errflg == PARSE) ? "parse" :
1256 (errflg == TCPDEBUG) ? "tcp" :
1257 (errflg == HTTPDEBUG) ? "http" :
1258 (errflg == BYE) ? "bye" : "unknown",
1259 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1260 string ? string : "?"));
1261 #endif
1263 snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s",
1264 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1265 (int) MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70),
1266 string);
1267 ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols
1268 : sizeof(ps_global->last_error)-1] = '\0';
1271 * Then either set special bits in the pine struct or
1272 * display the message if it's tagged as an "ALERT" or
1273 * its errflg > NIL (i.e., WARN, or ERROR)
1275 if(errflg == BYE)
1277 * We'd like to sp_mark_stream_dead() here but we can't do that because
1278 * that might call mail_close and we are already in a c-client callback.
1279 * So just set the dead bit and clean it up later.
1281 sp_set_dead_stream(stream, 1);
1282 else if(!strncmp(string, "[TRYCREATE]", 11))
1283 ps_global->try_to_create = 1;
1284 else if(!strncmp(string, "[REFERRAL ", 10))
1285 ; /* handled in the imap_referral() callback */
1286 else if(!strncmp(string, "[ALERT]", 7))
1287 q_status_message2(SM_MODAL, 3, 3,
1288 _("Alert received while accessing \"%s\": %s"),
1289 (stream && stream->mailbox)
1290 ? stream->mailbox : "-no folder-",
1291 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000),
1292 SIZEOF_20KBUF-10000, string));
1293 else if(!strncmp(string, "[UNSEEN ", 8)){
1294 char *p;
1295 long n = 0;
1297 for(p = string + 8; isdigit(*p); p++)
1298 n = (n * 10) + (*p - '0');
1300 sp_set_first_unseen(stream, n);
1302 else if(!strncmp(string, "[READ-ONLY]", 11)
1303 && !(stream && stream->mailbox && IS_NEWS(stream)))
1304 q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s",
1305 (stream && stream->mailbox)
1306 ? stream->mailbox : "-no folder-",
1307 string + 11);
1308 else if((errflg && errflg != BYE && errflg != PARSE)
1309 && !ps_global->noshow_error
1310 && !(errflg == WARN
1311 && (ps_global->noshow_warn || (stream && stream->unhealthy))))
1312 q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0),
1313 3, 6, ps_global->last_error);
1317 /*----------------------------------------------------------------------
1318 Queue imap log message for display in the message line
1320 Args: string -- The message
1321 errflg -- flag set to 1 if pertains to an error
1323 Result: Message queued for display
1325 The c-client/imap reports most of it's status and errors here
1326 ---*/
1327 void
1328 mm_log(char *string, long int errflg)
1330 char message[sizeof(ps_global->c_client_error)];
1331 char *occurence;
1332 int was_capitalized;
1333 static char saw_kerberos_init_warning;
1334 time_t now;
1335 struct tm *tm_now;
1337 now = time((time_t *)0);
1338 tm_now = localtime(&now);
1340 dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? debug :
1341 (errflg == TCPDEBUG) ? 10 :
1342 ((errflg == HTTPDEBUG) && ps_global->debug_http) ? debug :
1343 (errflg == HTTPDEBUG) ? 10 : 2,
1344 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1345 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1346 tm_now->tm_mon+1, tm_now->tm_mday,
1347 (!errflg) ? "babble" :
1348 (errflg == ERROR) ? "error" :
1349 (errflg == WARN) ? "warning" :
1350 (errflg == PARSE) ? "parse" :
1351 (errflg == TCPDEBUG) ? "tcp" :
1352 (errflg == HTTPDEBUG) ? "http" :
1353 (errflg == BYE) ? "bye" : "unknown",
1354 string ? string : "?"));
1356 if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){
1357 ps_global->try_to_create = 1;
1358 return;
1360 else if(ps_global->try_to_create
1361 || !strncmp(string, "[CLOSED]", 8)
1362 || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op")))
1364 * Don't display if creating new folder OR
1365 * warning about a dead stream ...
1367 return;
1369 /* if we took too long to authenticate, ignore this error */
1370 if(ps_global->in_xoauth2_auth && strstr(string, "[CLOSED]"))
1371 return;
1373 strncpy(message, string, sizeof(message));
1374 message[sizeof(message) - 1] = '\0';
1376 if(errflg == WARN && srchstr(message, "try running kinit") != NULL){
1377 if(saw_kerberos_init_warning)
1378 return;
1380 saw_kerberos_init_warning = 1;
1383 /*---- replace all "mailbox" with "folder" ------*/
1384 occurence = srchstr(message, "mailbox");
1385 while(occurence) {
1386 if(!*(occurence+7)
1387 || isspace((unsigned char) *(occurence+7))
1388 || *(occurence+7) == ':'){
1389 was_capitalized = isupper((unsigned char) *occurence);
1390 rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder"));
1391 if(was_capitalized)
1392 *occurence = (errflg == PARSE ? 'A' : 'F');
1394 else
1395 occurence += 7;
1397 occurence = srchstr(occurence, "mailbox");
1400 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1401 occurence = srchstr(message, "GSSAPI");
1402 while(occurence) {
1403 if(!*(occurence+6)
1404 || isspace((unsigned char) *(occurence+6))
1405 || *(occurence+6) == ':')
1406 rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos");
1407 else
1408 occurence += 6;
1410 occurence = srchstr(occurence, "GSSAPI");
1413 if(errflg == ERROR)
1414 ps_global->mm_log_error = 1;
1416 if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error))
1417 strncpy(ps_global->c_client_error, message,
1418 sizeof(ps_global->c_client_error));
1420 if(ps_global->noshow_error
1421 || (ps_global->noshow_warn && errflg == WARN)
1422 || !(errflg == ERROR || errflg == WARN))
1423 return; /* Only care about errors; don't print when asked not to */
1425 /*---- Display the message ------*/
1426 q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER,
1427 3, 5, message);
1428 strncpy(ps_global->last_error, message, sizeof(ps_global->last_error));
1429 ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
1432 void
1433 mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial,
1434 char *method, char *usethisprompt, char *altuserforcache)
1436 if(method == NULL)
1437 return;
1438 if(strucmp(method, OA2NAME) == 0 || strucmp(method, BEARERNAME) == 0)
1439 mm_login_oauth2(mb, user, method, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache);
1442 void
1443 mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial,
1444 char *usethisprompt, char *altuserforcache)
1446 char tmp[MAILTMPLEN];
1447 char prompt[1000], *last;
1448 char port[20], non_def_port[20], insecure[20];
1449 char defuser[NETMAXUSER];
1450 char hostleadin[80], hostname[200], defubuf[200];
1451 char logleadin[80], pwleadin[50];
1452 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
1453 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1454 that the password will be sent unencrypted over the network. This is
1455 just a warning message that gets added parenthetically when the user
1456 is asked for a password. */
1457 char *insec = _(" (INSECURE)");
1458 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1459 after having already failed at least once. */
1460 char *retry = _("Retrying - ");
1461 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1462 char *hostlabel = _("HOST");
1463 /* TRANSLATORS: user is logging in as a particular user (a particular
1464 login name), this is just labelling that user name. */
1465 char *userlabel = _("USER");
1466 STRLIST_S hostlist[2];
1467 HelpType help ;
1468 int len, rc, q_line, flags;
1469 int oespace, avail, need, save_dont_use;
1470 int save_in_init;
1471 struct servent *sv;
1472 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1473 int preserve_password = -1;
1474 #endif
1476 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1477 trial, mb->user ? mb->user : "(null)",
1478 mb->service ? mb->service : "(null)",
1479 mb->port ? " port=" : "",
1480 mb->port ? comatose(mb->port) : "",
1481 altuserforcache ? " altuserforcache =" : "",
1482 altuserforcache ? altuserforcache : ""));
1483 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
1485 save_in_init = ps_global->in_init_seq;
1486 ps_global->in_init_seq = 0;
1487 ps_global->no_newmail_check_from_optionally_enter = 1;
1489 /* make sure errors are seen */
1490 if(ps_global->ttyo)
1491 flush_status_messages(0);
1493 /* redo app id in case we are logging in to an IMAP server that supports the IMAP ID extension */
1494 free_id(&ps_global->id);
1495 ps_global->id = set_alpine_id(PACKAGE_NAME, PACKAGE_VERSION);
1496 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
1499 * Add port number to hostname if going through a tunnel or something
1501 non_def_port[0] = '\0';
1502 if(mb->port && mb->service &&
1503 (sv = getservbyname(mb->service, "tcp")) &&
1504 (mb->port != ntohs(sv->s_port))){
1505 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
1506 non_def_port[sizeof(non_def_port)-1] = '\0';
1507 dprint((9, "mm_login: using non-default port=%s\n", non_def_port));
1511 * set up host list for sybil servers...
1513 if(*non_def_port){
1514 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
1515 hostlist0[sizeof(hostlist0)-1] = '\0';
1516 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
1517 hostlist0[sizeof(hostlist0)-1] = '\0';
1518 hostlist[0].name = hostlist0;
1519 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1520 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
1521 hostlist1[sizeof(hostlist1)-1] = '\0';
1522 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
1523 hostlist1[sizeof(hostlist1)-1] = '\0';
1524 hostlist[0].next = &hostlist[1];
1525 hostlist[1].name = hostlist1;
1526 hostlist[1].next = NULL;
1528 else
1529 hostlist[0].next = NULL;
1531 else{
1532 hostlist[0].name = mb->host;
1533 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1534 hostlist[0].next = &hostlist[1];
1535 hostlist[1].name = mb->orighost;
1536 hostlist[1].next = NULL;
1538 else
1539 hostlist[0].next = NULL;
1542 if(hostlist[0].name){
1543 dprint((9, "mm_login: host=%s\n",
1544 hostlist[0].name ? hostlist[0].name : "?"));
1545 if(hostlist[0].next && hostlist[1].name){
1546 dprint((9, "mm_login: orighost=%s\n", hostlist[1].name));
1551 * Initialize user name with either
1552 * 1) /user= value in the stream being logged into,
1553 * or 2) the user name we're running under.
1555 * Note that VAR_USER_ID is not yet initialized if this login is
1556 * the one to access the remote config file. In that case, the user
1557 * can supply the username in the config file name with /user=.
1559 if(trial == 0L && !altuserforcache){
1560 strncpy(user, (*mb->user) ? mb->user :
1561 ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "",
1562 NETMAXUSER);
1563 user[NETMAXUSER-1] = '\0';
1565 /* try last working password associated with this host. */
1566 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1567 (mb->sslflag||mb->tlsflag))){
1568 dprint((9, "mm_login: found a password to try\n"));
1569 ps_global->no_newmail_check_from_optionally_enter = 0;
1570 ps_global->in_init_seq = save_in_init;
1571 return;
1574 #ifdef LOCAL_PASSWD_CACHE
1575 /* check to see if there's a password left over from last session */
1576 if(get_passfile_passwd(ps_global->pinerc, pwd,
1577 user, hostlist, (mb->sslflag||mb->tlsflag))){
1578 imap_set_passwd(&mm_login_list, *pwd, user,
1579 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1580 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1581 (mb->sslflag||mb->tlsflag));
1582 dprint((9, "mm_login: found a password in passfile to try\n"));
1583 ps_global->no_newmail_check_from_optionally_enter = 0;
1584 ps_global->in_init_seq = save_in_init;
1585 return;
1587 #endif /* LOCAL_PASSWD_CACHE */
1590 * If no explicit user name supplied and we've not logged in
1591 * with our local user name, see if we've visited this
1592 * host before as someone else.
1594 if(!*mb->user &&
1595 ((last = imap_get_user(mm_login_list, hostlist))
1596 #ifdef LOCAL_PASSWD_CACHE
1598 (last = get_passfile_user(ps_global->pinerc, hostlist))
1599 #endif /* LOCAL_PASSWD_CACHE */
1601 strncpy(user, last, NETMAXUSER);
1602 user[NETMAXUSER-1] = '\0';
1603 dprint((9, "mm_login: found user=%s\n",
1604 user ? user : "?"));
1606 /* try last working password associated with this host/user. */
1607 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1608 (mb->sslflag||mb->tlsflag))){
1609 dprint((9,
1610 "mm_login: found a password for user=%s to try\n",
1611 user ? user : "?"));
1612 ps_global->no_newmail_check_from_optionally_enter = 0;
1613 ps_global->in_init_seq = save_in_init;
1614 return;
1617 #ifdef LOCAL_PASSWD_CACHE
1618 /* check to see if there's a password left over from last session */
1619 if(get_passfile_passwd(ps_global->pinerc, pwd,
1620 user, hostlist, (mb->sslflag||mb->tlsflag))){
1621 imap_set_passwd(&mm_login_list, *pwd, user,
1622 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1623 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1624 (mb->sslflag||mb->tlsflag));
1625 dprint((9,
1626 "mm_login: found a password for user=%s in passfile to try\n",
1627 user ? user : "?"));
1628 ps_global->no_newmail_check_from_optionally_enter = 0;
1629 ps_global->in_init_seq = save_in_init;
1630 return;
1632 #endif /* LOCAL_PASSWD_CACHE */
1635 #if !defined(DOS) && !defined(OS2)
1636 if(!*mb->user && !*user &&
1637 (last = (ps_global->ui.login && ps_global->ui.login[0])
1638 ? ps_global->ui.login : NULL)
1640 strncpy(user, last, NETMAXUSER);
1641 user[NETMAXUSER-1] = '\0';
1642 dprint((9, "mm_login: found user=%s\n",
1643 user ? user : "?"));
1645 /* try last working password associated with this host. */
1646 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1647 (mb->sslflag||mb->tlsflag))){
1648 dprint((9, "mm_login:ui: found a password to try\n"));
1649 ps_global->no_newmail_check_from_optionally_enter = 0;
1650 ps_global->in_init_seq = save_in_init;
1651 return;
1654 #ifdef LOCAL_PASSWD_CACHE
1655 /* check to see if there's a password left over from last session */
1656 if(get_passfile_passwd(ps_global->pinerc, pwd,
1657 user, hostlist, (mb->sslflag||mb->tlsflag))){
1658 imap_set_passwd(&mm_login_list, *pwd, user,
1659 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1660 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1661 (mb->sslflag||mb->tlsflag));
1662 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1663 ps_global->no_newmail_check_from_optionally_enter = 0;
1664 ps_global->in_init_seq = save_in_init;
1665 return;
1667 #endif /* LOCAL_PASSWD_CACHE */
1669 #endif
1672 user[NETMAXUSER-1] = '\0';
1674 if(trial == 0)
1675 retry = "";
1678 * Even if we have a user now, user gets a chance to change it.
1680 ps_global->mangled_footer = 1;
1681 if(!*mb->user && !altuserforcache){
1683 help = NO_HELP;
1686 * Instead of offering user with a value that the user can edit,
1687 * we offer [user] as a default so that the user can type CR to
1688 * use it. Otherwise, the user has to type in whole name.
1690 strncpy(defuser, user, sizeof(defuser)-1);
1691 defuser[sizeof(defuser)-1] = '\0';
1692 user[0] = '\0';
1695 * Need space for "Retrying - "
1696 * "+ HOST: "
1697 * hostname
1698 * " (INSECURE)"
1699 * ENTER LOGIN NAME
1700 * " [defuser] : "
1701 * about 15 chars for input
1704 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1705 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1706 hostleadin[sizeof(hostleadin)-1] = '\0';
1708 strncpy(hostname, mb->host, sizeof(hostname)-1);
1709 hostname[sizeof(hostname)-1] = '\0';
1712 * Add port number to hostname if going through a tunnel or something
1714 if(*non_def_port)
1715 strncpy(port, non_def_port, sizeof(port));
1716 else
1717 port[0] = '\0';
1719 insecure[0] = '\0';
1720 /* if not encrypted and SSL/TLS is supported */
1721 if(!(mb->sslflag||mb->tlsflag) &&
1722 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1723 strncpy(insecure, insec, sizeof(insecure));
1725 /* TRANSLATORS: user is being asked to type in their login name */
1726 snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME"));
1728 snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "",
1729 (*defuser) ? defuser : "",
1730 (*defuser) ? "]" : "");
1731 defubuf[sizeof(defubuf)-1] = '\0';
1732 /* space reserved after prompt */
1733 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1735 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1736 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1737 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace;
1739 /* If we're retrying cut the hostname back to the first word. */
1740 if(avail < need && trial > 0){
1741 char *p;
1743 len = strlen(hostname);
1744 if((p = strchr(hostname, '.')) != NULL){
1745 *p = '\0';
1746 need -= (len - strlen(hostname));
1750 if(avail < need){
1751 need -= utf8_width(retry);
1752 retry = "";
1754 if(avail < need){
1756 /* reduce length of logleadin */
1757 len = utf8_width(logleadin);
1758 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1759 longer version doesn't fit on screen */
1760 snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN"));
1761 need -= (len - utf8_width(logleadin));
1763 if(avail < need){
1764 /* get two spaces from hostleadin */
1765 len = utf8_width(hostleadin);
1766 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1767 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1768 hostleadin[sizeof(hostleadin)-1] = '\0';
1769 need -= (len - utf8_width(hostleadin));
1771 /* get rid of port */
1772 if(avail < need && strlen(port) > 0){
1773 need -= strlen(port);
1774 port[0] = '\0';
1777 if(avail < need){
1778 int reduce_to;
1781 * Reduce space for hostname. Best we can do is 6 chars
1782 * with hos...
1784 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1785 len = strlen(hostname);
1786 strncpy(hostname+reduce_to-3, "...", 4);
1787 need -= (len - strlen(hostname));
1789 if(avail < need && strlen(insecure) > 0){
1790 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1791 need -= 3;
1792 insecure[strlen(insecure)-4] = ')';
1793 insecure[strlen(insecure)-3] = '\0';
1795 else{
1796 need -= utf8_width(insecure);
1797 insecure[0] = '\0';
1801 if(avail < need){
1802 if(strlen(defubuf) > 3){
1803 len = strlen(defubuf);
1804 strncpy(defubuf, " [..] :", 9);
1805 need -= (len - strlen(defubuf));
1808 if(avail < need)
1809 strncpy(defubuf, ":", 2);
1812 * If it still doesn't fit, optionally_enter gets
1813 * to worry about it.
1821 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s",
1822 retry, hostleadin, hostname, port, insecure, logleadin, defubuf);
1823 prompt[sizeof(prompt)-1] = '\0';
1825 while(1) {
1826 if(ps_global->ttyo)
1827 mm_login_alt_cue(mb);
1829 flags = OE_APPEND_CURRENT;
1830 save_dont_use = ps_global->dont_use_init_cmds;
1831 ps_global->dont_use_init_cmds = 1;
1832 #ifdef _WINDOWS
1833 if(!*user && *defuser){
1834 strncpy(user, defuser, NETMAXUSER);
1835 user[NETMAXUSER-1] = '\0';
1838 rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD,
1839 #ifdef LOCAL_PASSWD_CACHE
1840 is_using_passfile() ? 1 :
1841 #endif /* LOCAL_PASSWD_CACHE */
1842 0, 0, &preserve_password);
1843 ps_global->dont_use_init_cmds = save_dont_use;
1844 if(rc == 0 && *user && *pwd && **pwd)
1845 goto nopwpmt;
1846 #else /* !_WINDOWS */
1847 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
1848 prompt, NULL, help, &flags);
1849 #endif /* !_WINDOWS */
1850 ps_global->dont_use_init_cmds = save_dont_use;
1852 if(rc == 3) {
1853 help = help == NO_HELP ? h_oe_login : NO_HELP;
1854 continue;
1857 /* default */
1858 if(rc == 0 && !*user){
1859 strncpy(user, defuser, NETMAXUSER);
1860 user[NETMAXUSER-1] = '\0';
1863 if(rc != 4)
1864 break;
1867 if(rc == 1 || !user[0]) {
1868 ps_global->user_says_cancel = (rc == 1);
1869 user[0] = '\0';
1872 else{
1873 strncpy(user, mb->user, NETMAXUSER);
1874 user[NETMAXUSER-1] = '\0';
1877 user[NETMAXUSER-1] = '\0';
1879 if(!(user[0] || altuserforcache)){
1880 ps_global->no_newmail_check_from_optionally_enter = 0;
1881 ps_global->in_init_seq = save_in_init;
1882 return;
1886 * Now that we have a user, we can check in the cache again to see
1887 * if there is a password there. Try last working password associated
1888 * with this host and user.
1890 if(trial == 0L && !*mb->user && !altuserforcache){
1891 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1892 (mb->sslflag||mb->tlsflag))){
1893 ps_global->no_newmail_check_from_optionally_enter = 0;
1894 ps_global->in_init_seq = save_in_init;
1895 return;
1898 #ifdef LOCAL_PASSWD_CACHE
1899 if(get_passfile_passwd(ps_global->pinerc, pwd,
1900 user, hostlist, (mb->sslflag||mb->tlsflag))){
1901 imap_set_passwd(&mm_login_list, *pwd, user,
1902 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1903 ps_global->no_newmail_check_from_optionally_enter = 0;
1904 ps_global->in_init_seq = save_in_init;
1905 return;
1907 #endif /* LOCAL_PASSWD_CACHE */
1909 else if(trial == 0 && altuserforcache){
1910 if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist,
1911 (mb->sslflag||mb->tlsflag))){
1912 ps_global->no_newmail_check_from_optionally_enter = 0;
1913 ps_global->in_init_seq = save_in_init;
1914 return;
1917 #ifdef LOCAL_PASSWD_CACHE
1918 if(get_passfile_passwd(ps_global->pinerc, pwd,
1919 altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
1920 imap_set_passwd(&mm_login_list, *pwd, altuserforcache,
1921 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1922 ps_global->no_newmail_check_from_optionally_enter = 0;
1923 ps_global->in_init_seq = save_in_init;
1924 return;
1926 #endif /* LOCAL_PASSWD_CACHE */
1930 * Didn't find password in cache or this isn't the first try. Ask user.
1932 help = NO_HELP;
1935 * Need space for "Retrying - "
1936 * "+ HOST: "
1937 * hostname
1938 * " (INSECURE) "
1939 * " USER: "
1940 * user
1941 * " ENTER PASSWORD: "
1942 * about 15 chars for input
1945 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1946 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1948 strncpy(hostname, mb->host, sizeof(hostname)-1);
1949 hostname[sizeof(hostname)-1] = '\0';
1952 * Add port number to hostname if going through a tunnel or something
1954 if(*non_def_port)
1955 strncpy(port, non_def_port, sizeof(port));
1956 else
1957 port[0] = '\0';
1959 insecure[0] = '\0';
1961 /* if not encrypted and SSL/TLS is supported */
1962 if(!(mb->sslflag||mb->tlsflag) &&
1963 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1964 strncpy(insecure, insec, sizeof(insecure));
1966 if(usethisprompt){
1967 strncpy(logleadin, usethisprompt, sizeof(logleadin));
1968 logleadin[sizeof(logleadin)-1] = '\0';
1969 defubuf[0] = '\0';
1970 user[0] = '\0';
1972 else{
1973 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1975 strncpy(defubuf, user, sizeof(defubuf)-1);
1976 defubuf[sizeof(defubuf)-1] = '\0';
1979 /* TRANSLATORS: user is being asked to type in their password */
1980 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD"));
1982 /* space reserved after prompt */
1983 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1985 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1986 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1987 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) +
1988 utf8_width(pwleadin) + oespace;
1990 if(avail < need && trial > 0){
1991 char *p;
1993 len = strlen(hostname);
1994 if((p = strchr(hostname, '.')) != NULL){
1995 *p = '\0';
1996 need -= (len - strlen(hostname));
2000 if(avail < need){
2001 need -= utf8_width(retry);
2002 retry = "";
2004 if(avail < need){
2006 if(!usethisprompt){
2007 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
2008 need--;
2011 rplstr(pwleadin, sizeof(pwleadin), 1, "");
2012 need--;
2014 if(avail < need){
2015 /* get two spaces from hostleadin */
2016 len = utf8_width(hostleadin);
2017 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
2018 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
2019 hostleadin[sizeof(hostleadin)-1] = '\0';
2020 need -= (len - utf8_width(hostleadin));
2022 /* get rid of port */
2023 if(avail < need && strlen(port) > 0){
2024 need -= strlen(port);
2025 port[0] = '\0';
2028 if(avail < need){
2029 len = utf8_width(pwleadin);
2030 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
2031 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD"));
2032 need -= (len - utf8_width(pwleadin));
2036 if(avail < need){
2037 int reduce_to;
2040 * Reduce space for hostname. Best we can do is 6 chars
2041 * with hos...
2043 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
2044 len = strlen(hostname);
2045 strncpy(hostname+reduce_to-3, "...", 4);
2046 need -= (len - strlen(hostname));
2048 if(avail < need && strlen(insecure) > 0){
2049 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
2050 need -= 3;
2051 insecure[strlen(insecure)-4] = ')';
2052 insecure[strlen(insecure)-3] = '\0';
2054 else{
2055 need -= utf8_width(insecure);
2056 insecure[0] = '\0';
2060 if(avail < need){
2061 len = utf8_width(logleadin);
2062 strncpy(logleadin, " ", sizeof(logleadin));
2063 logleadin[sizeof(logleadin)-1] = '\0';
2064 need -= (len - utf8_width(logleadin));
2066 if(avail < need){
2067 reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0;
2068 if(reduce_to)
2069 strncpy(defubuf+reduce_to-3, "...", 4);
2070 else
2071 defubuf[0] = '\0';
2078 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s",
2079 retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
2080 prompt[sizeof(prompt)-1] = '\0';
2082 tmp[0] = '\0';
2083 while(1) {
2084 if(ps_global->ttyo)
2085 mm_login_alt_cue(mb);
2087 save_dont_use = ps_global->dont_use_init_cmds;
2088 ps_global->dont_use_init_cmds = 1;
2089 flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
2090 flags |= OE_KEEP_TRAILING_SPACE;
2091 #ifdef _WINDOWS
2093 char *tmpp;
2094 tmpp = fs_get(NETMAXPASSWD*sizeof(char));
2095 rc = os_login_dialog(mb, user, NETMAXUSER, &tmpp, NETMAXPASSWD, 0, 1,
2096 &preserve_password);
2097 strncpy(tmp, tmpp, sizeof(tmp));
2098 tmp[sizeof(tmp)-1] = '\0';
2099 if(tmpp) fs_give((void **)&tmpp);
2101 #else /* !_WINDOWS */
2102 rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD,
2103 prompt, NULL, help, &flags);
2104 #endif /* !_WINDOWS */
2105 if(rc != 1) *pwd = cpystr(tmp);
2106 ps_global->dont_use_init_cmds = save_dont_use;
2108 if(rc == 3) {
2109 help = help == NO_HELP ? h_oe_passwd : NO_HELP;
2111 else if(rc == 4){
2113 else
2114 break;
2117 if(rc == 1 || !tmp[0]) {
2118 ps_global->user_says_cancel = (rc == 1);
2119 user[0] = '\0';
2120 ps_global->no_newmail_check_from_optionally_enter = 0;
2121 ps_global->in_init_seq = save_in_init;
2122 return;
2125 #ifdef _WINDOWS
2126 nopwpmt:
2127 #endif
2128 /* remember the password for next time */
2129 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
2130 imap_set_passwd(&mm_login_list, *pwd,
2131 altuserforcache ? altuserforcache : user, hostlist,
2132 (mb->sslflag||mb->tlsflag), 0, 0);
2133 #ifdef LOCAL_PASSWD_CACHE
2134 /* if requested, remember it on disk for next session */
2135 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
2136 set_passfile_passwd(ps_global->pinerc, *pwd,
2137 altuserforcache ? altuserforcache : user, hostlist,
2138 (mb->sslflag||mb->tlsflag),
2139 (preserve_password == -1 ? 0
2140 : (preserve_password == 0 ? 2 :1)));
2141 #endif /* LOCAL_PASSWD_CACHE */
2143 ps_global->no_newmail_check_from_optionally_enter = 0;
2147 void
2148 mm_login_alt_cue(NETMBX *mb)
2150 if(ps_global->ttyo){
2151 COLOR_PAIR *lastc;
2153 lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
2154 ps_global->VAR_TITLE_BACK_COLOR,
2155 PSC_REV | PSC_RET);
2157 mark_titlebar_dirty();
2158 PutLine0(0, ps_global->ttyo->screen_cols - 1,
2159 (mb->sslflag||mb->tlsflag) ? "+" : " ");
2161 if(lastc){
2162 (void)pico_set_colorp(lastc, PSC_NONE);
2163 free_color_pair(&lastc);
2166 fflush(stdout);
2171 /*----------------------------------------------------------------------
2172 Receive notification of an error writing to disk
2174 Args: stream -- The stream the error occurred on
2175 errcode -- The system error code (errno)
2176 serious -- Flag indicating error is serious (mail may be lost)
2178 Result: If error is non serious, the stream is marked as having an error
2179 and deletes are disallowed until error clears
2180 If error is serious this goes modal, allowing the user to retry
2181 or get a shell escape to fix the condition. When the condition is
2182 serious it means that mail existing in the mailbox will be lost
2183 if Pine exits without writing, so we try to induce the user to
2184 fix the error, go get someone that can fix the error, or whatever
2185 and don't provide an easy way out.
2186 ----*/
2187 long
2188 mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious)
2190 int i, j;
2191 char *p, *q, *s;
2192 static ESCKEY_S de_opts[] = {
2193 {'r', 'r', "R", "Retry"},
2194 {'f', 'f', "F", "FileBrowser"},
2195 {'s', 's', "S", "ShellPrompt"},
2196 {-1, 0, NULL, NULL}
2198 #define DE_COLS (ps_global->ttyo->screen_cols)
2199 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2201 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2202 #define DE_PMT \
2203 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2204 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2205 #define DE_STR2 \
2206 "The reported error number is %s. The last reported mail error was:"
2207 static char *de_msg[] = {
2208 "Please try to correct the error preventing Alpine from saving your",
2209 "mail folder. For example if the disk is out of space try removing",
2210 "unneeded files. You might also contact your system administrator.",
2212 "Both Alpine's File Browser and an option to enter the system's",
2213 "command prompt are offered to aid in fixing the problem. When",
2214 "you believe the problem is resolved, choose the \"Retry\" option.",
2215 "Be aware that messages may be lost or this folder left in an",
2216 "inaccessible condition if you exit or kill Alpine before the problem",
2217 "is resolved.",
2218 NULL};
2219 static char *de_shell_msg[] = {
2220 "\n\nPlease attempt to correct the error preventing saving of the",
2221 "mail folder. If you do not know how to correct the problem, contact",
2222 "your system administrator. To return to Alpine, type \"exit\".",
2223 NULL};
2225 dprint((0,
2226 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2227 DE_FOLDER(stream), errcode, serious ? "" : "not "));
2228 dprint((0, "***** message: \"%s\"\n\n",
2229 ps_global->last_error ? ps_global->last_error : "?"));
2231 if(!serious) {
2232 sp_set_io_error_on_stream(stream, 1);
2233 return (1) ;
2236 while(1){
2237 /* replace pine's body display with screen full of explanatory text */
2238 ClearLine(2);
2239 PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1)
2240 - strlen(DE_FOLDER(stream)))/2, 0),
2241 DE_STR1, DE_FOLDER(stream));
2242 ClearLine(3);
2243 PutLine1(3, 4, DE_STR2, long2string(errcode));
2245 PutLine0(4, 0, " \"");
2246 removing_leading_white_space(ps_global->last_error);
2247 for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){
2248 for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++)
2249 if(isspace((unsigned char)*q))
2250 s = q;
2252 if(*q && s)
2253 q = s;
2255 while(p < q)
2256 Writechar(*p++, 0);
2258 if(*(p = q)){
2259 ClearLine(++i);
2260 PutLine0(i, 0, " ");
2261 while(*p && isspace((unsigned char)*p))
2262 p++;
2264 else{
2265 Writechar('\"', 0);
2266 CleartoEOLN();
2267 break;
2271 ClearLine(++i);
2272 for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){
2273 ClearLine(i);
2274 PutLine0(i, 0, " ");
2275 Write_to_screen(de_msg[i-j]);
2278 while(i < DE_LINE)
2279 ClearLine(i++);
2281 switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts,
2282 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){
2283 case 'r' : /* Retry! */
2284 ps_global->mangled_screen = 1;
2285 return(0L);
2287 case 'f' : /* File Browser */
2289 char full_filename[MAXPATH+1], filename[MAXPATH+1];
2291 filename[0] = '\0';
2292 build_path(full_filename, ps_global->home_dir, filename,
2293 sizeof(full_filename));
2294 file_lister("DISK ERROR", full_filename, sizeof(full_filename),
2295 filename, sizeof(filename), FALSE, FB_SAVE);
2298 break;
2300 case 's' :
2301 EndInverse();
2302 end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0);
2303 end_tty_driver(ps_global);
2304 for(i = 0; de_shell_msg[i]; i++)
2305 puts(de_shell_msg[i]);
2308 * Don't use our piping mechanism to spawn a subshell here
2309 * since it will the server (thus reentering c-client).
2310 * Bad thing to do.
2312 #ifdef _WINDOWS
2313 #else
2314 system("csh");
2315 #endif
2316 init_tty_driver(ps_global);
2317 init_keyboard(F_ON(F_USE_FK,ps_global));
2318 break;
2321 if(ps_global->redrawer)
2322 (*ps_global->redrawer)();
2327 long
2328 pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host)
2330 long rv = 1L;
2331 char pmt[128];
2333 #ifdef _WINDOWS
2334 mswin_killsplash();
2335 #endif
2337 if(elapsed >= (long)ps_global->tcp_query_timeout){
2338 snprintf(pmt, sizeof(pmt),
2339 _("No reply in %s seconds from server %s. Break connection"),
2340 long2string(elapsed), host);
2341 pmt[sizeof(pmt)-1] = '\0';
2342 if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
2343 ps_global->user_says_cancel = 1;
2344 return(0L);
2348 ps_global->tcptimeout = 0;
2349 return(rv);
2354 * -------------------------------------------------------------
2355 * These are declared in pith/imap.h as mandatory to implement.
2356 * -------------------------------------------------------------
2361 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2363 long
2364 pine_tcptimeout(long int elapsed, long int sincelast, char *host)
2366 long rv = 1L; /* keep trying by default */
2367 unsigned long ch;
2369 ps_global->tcptimeout = 1;
2370 #ifdef DEBUG
2371 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2372 long2string(elapsed), host));
2373 if(debugfile)
2374 fflush(debugfile);
2375 #endif
2377 #ifdef _WINDOWS
2378 mswin_killsplash();
2379 #endif
2381 if(ps_global->noshow_timeout)
2382 return(rv);
2384 if(ps_global->can_interrupt
2385 && ps_global->close_connection_timeout > 0L
2386 && elapsed >= (long)ps_global->tcp_query_timeout
2387 && elapsed >= (long)ps_global->close_connection_timeout){
2388 ps_global->can_interrupt = 0; /* do not return here */
2389 ps_global->read_bail = 0;
2390 ps_global->user_says_cancel = 1;
2391 return 0;
2394 if(!ps_global->ttyo)
2395 return(pine_tcptimeout_noscreen(elapsed, sincelast, host));
2397 suspend_busy_cue();
2400 * Prompt after a minute (since by then things are probably really bad)
2401 * A prompt timeout means "keep trying"...
2403 if(elapsed >= (long)ps_global->tcp_query_timeout){
2404 int clear_inverse;
2406 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2407 if((clear_inverse = !InverseState()) != 0)
2408 StartInverse();
2410 Writechar(BELL, 0);
2412 PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0,
2413 _("No reply in %s seconds from server %s. Break connection?"),
2414 long2string(elapsed), host);
2415 CleartoEOLN();
2416 fflush(stdout);
2417 flush_input();
2418 ch = read_char(7);
2419 if(ps_global->read_bail || ch == 'y' || ch == 'Y'){
2420 ps_global->read_bail = 0;
2421 ps_global->user_says_cancel = 1;
2422 rv = 0L;
2425 if(clear_inverse)
2426 EndInverse();
2428 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2431 if(rv == 1L){ /* just warn 'em something's up */
2432 q_status_message2(SM_ORDER, 0, 0,
2433 _("No reply in %s seconds from server %s. Still Waiting..."),
2434 long2string(elapsed), host);
2435 flush_status_messages(0); /* make sure it's seen */
2438 mark_status_dirty(); /* make sure it gets cleared */
2440 resume_busy_cue((rv == 1) ? 3 : 0);
2441 ps_global->tcptimeout = 0;
2443 return(rv);
2446 QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota)
2448 QUOTALIST *cquota = NULL;
2450 if(pquota){
2451 cquota = mail_newquotalist();
2452 if (pquota->name && *pquota->name)
2453 cquota->name = cpystr(pquota->name);
2454 cquota->usage = pquota->usage;
2455 cquota->limit = pquota->limit;
2456 if (pquota->next)
2457 cquota->next = pine_quotalist_copy(pquota->next);
2459 return cquota;
2463 /* c-client callback to handle quota */
2465 void
2466 pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2468 ps_global->quota = pine_quotalist_copy (pquota);
2472 * C-client callback to handle SSL/TLS certificate validation failures
2474 * Returning 0 means error becomes fatal
2475 * Non-zero means certificate problem is ignored and SSL session is
2476 * established
2478 * We remember the answer and won't re-ask for subsequent open attempts to
2479 * the same hostname.
2481 long
2482 pine_sslcertquery(char *reason, char *host, char *cert)
2484 char tmp[500];
2485 char *unknown = "<unknown>";
2486 long rv = 0L;
2487 STRLIST_S hostlist;
2488 int ok_novalidate = 0, warned = 0;
2490 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2491 host ? host : "?", reason ? reason : "?",
2492 cert ? cert : "?"));
2494 hostlist.name = host ? host : "";
2495 hostlist.next = NULL;
2498 * See if we've been asked about this host before.
2500 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2501 /* we were asked before, did we say Yes? */
2502 if(ok_novalidate)
2503 rv++;
2505 if(rv){
2506 dprint((5,
2507 "sslcertificatequery: approved automatically\n"));
2508 return(rv);
2511 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2514 if(ps_global->ttyo){
2515 SCROLL_S sargs;
2516 STORE_S *in_store, *out_store;
2517 gf_io_t pc, gc;
2518 HANDLE_S *handles = NULL;
2519 int the_answer = 'n';
2521 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
2522 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
2523 goto try_wantto;
2525 so_puts(in_store, "<HTML><P>");
2526 so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server"));
2528 so_puts(in_store, "<P><CENTER>");
2529 so_puts(in_store, host ? host : unknown);
2530 so_puts(in_store, "</CENTER>");
2532 so_puts(in_store, "<P>");
2533 so_puts(in_store, _("The reason for the failure was"));
2535 /* squirrel away details */
2536 if(details_host)
2537 fs_give((void **)&details_host);
2538 if(details_reason)
2539 fs_give((void **)&details_reason);
2540 if(details_cert)
2541 fs_give((void **)&details_cert);
2543 details_host = cpystr(host ? host : unknown);
2544 details_reason = cpystr(reason ? reason : unknown);
2545 details_cert = cpystr(cert ? cert : unknown);
2547 so_puts(in_store, "<P><CENTER>");
2548 snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2549 reason ? reason : unknown);
2550 tmp[sizeof(tmp)-1] = '\0';
2552 so_puts(in_store, tmp);
2553 so_puts(in_store, "</CENTER>");
2555 so_puts(in_store, "<P>");
2556 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."));
2558 so_puts(in_store, "<P>");
2559 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"));
2561 so_puts(in_store, "<P><CENTER>");
2562 so_puts(in_store, "/novalidate-cert");
2563 so_puts(in_store, "</CENTER>");
2565 so_puts(in_store, "<P>");
2566 so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2568 so_puts(in_store, "<P><CENTER>");
2569 so_puts(in_store, host ? host : unknown);
2570 so_puts(in_store, "</CENTER>");
2572 so_puts(in_store, "<P>");
2573 so_puts(in_store, _("in your configuration, replace those characters with"));
2575 so_puts(in_store, "<P><CENTER>");
2576 so_puts(in_store, host ? host : unknown);
2577 so_puts(in_store, "/novalidate-cert");
2578 so_puts(in_store, "</CENTER>");
2580 so_puts(in_store, "<P>");
2581 so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2583 so_seek(in_store, 0L, 0);
2584 init_handles(&handles);
2585 gf_filter_init();
2586 gf_link_filter(gf_html2plain,
2587 gf_html2plain_opt(NULL,
2588 ps_global->ttyo->screen_cols, non_messageview_margin(),
2589 &handles, NULL, GFHP_LOCAL_HANDLES));
2590 gf_set_so_readc(&gc, in_store);
2591 gf_set_so_writec(&pc, out_store);
2592 gf_pipe(gc, pc);
2593 gf_clear_so_writec(out_store);
2594 gf_clear_so_readc(in_store);
2596 memset(&sargs, 0, sizeof(SCROLL_S));
2597 sargs.text.handles = handles;
2598 sargs.text.text = so_text(out_store);
2599 sargs.text.src = CharStar;
2600 sargs.text.desc = _("help text");
2601 sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2602 sargs.proc.tool = answer_cert_failure;
2603 sargs.proc.data.p = (void *)&the_answer;
2604 sargs.keys.menu = &ans_certquery_keymenu;
2605 /* don't want to re-enter c-client */
2606 sargs.quell_newmail = 1;
2607 setbitmap(sargs.keys.bitmap);
2608 sargs.help.text = h_tls_validation_failure;
2609 sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
2611 scrolltool(&sargs);
2613 if(the_answer == 'y')
2614 rv++;
2615 else if(the_answer == 'n')
2616 ps_global->user_says_cancel = 1;
2618 ps_global->mangled_screen = 1;
2619 ps_global->painted_body_on_startup = 0;
2620 ps_global->painted_footer_on_startup = 0;
2621 so_give(&in_store);
2622 so_give(&out_store);
2623 free_handles(&handles);
2624 if(details_host)
2625 fs_give((void **)&details_host);
2626 if(details_reason)
2627 fs_give((void **)&details_reason);
2628 if(details_cert)
2629 fs_give((void **)&details_cert);
2631 else{
2633 * If screen hasn't been initialized yet, use want_to.
2635 try_wantto:
2636 memset((void *)tmp, 0, sizeof(tmp));
2637 strncpy(tmp,
2638 reason ? reason : _("SSL/TLS certificate validation failure"),
2639 sizeof(tmp));
2640 tmp[sizeof(tmp)-1] = '\0';
2641 strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
2643 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
2644 rv++;
2647 if(rv == 0)
2648 q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
2649 host ? host : unknown);
2651 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
2653 dprint((5, "sslcertificatequery: %s\n",
2654 rv ? "approved" : "rejected"));
2656 return(rv);
2660 char *
2661 pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
2663 char buf[MAILTMPLEN];
2665 if((can_access(mulname, ACCESS_EXISTS) == 0)
2666 || !(can_access(name, ACCESS_EXISTS) == 0))
2667 return(mulname);
2669 snprintf(buf, sizeof(buf),
2670 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2671 last_cmpnt(name),
2672 strlen(last_cmpnt(name)) > 15 ? "..." : "");
2673 buf[sizeof(buf)-1] = '\0';
2674 if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
2675 rename_file(name, mulname);
2676 return(mulname);
2681 url_local_certdetails(char *url)
2683 if(!struncmp(url, "x-alpine-cert:", 14)){
2684 STORE_S *store;
2685 SCROLL_S sargs;
2686 char *folded;
2688 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
2689 q_status_message(SM_ORDER | SM_DING, 7, 10,
2690 _("Error allocating space for details."));
2691 return(0);
2694 so_puts(store, _("Host given by user:\n\n "));
2695 so_puts(store, details_host);
2696 so_puts(store, _("\n\nReason for failure:\n\n "));
2697 so_puts(store, details_reason);
2698 so_puts(store, _("\n\nCertificate being verified:\n\n"));
2699 folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
2700 so_puts(store, folded);
2701 fs_give((void **)&folded);
2702 so_puts(store, "\n");
2704 memset(&sargs, 0, sizeof(SCROLL_S));
2705 sargs.text.text = so_text(store);
2706 sargs.text.src = CharStar;
2707 sargs.text.desc = _("Details");
2708 sargs.bar.title = _("CERT VALIDATION DETAILS");
2709 sargs.help.text = NO_HELP;
2710 sargs.help.title = NULL;
2711 sargs.quell_newmail = 1;
2712 sargs.help.text = h_tls_failure_details;
2713 sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
2715 scrolltool(&sargs);
2717 so_give(&store); /* free resources associated with store */
2718 ps_global->mangled_screen = 1;
2719 return(1);
2722 return(0);
2727 * C-client callback to handle SSL/TLS certificate validation failures
2729 void
2730 pine_sslfailure(char *host, char *reason, long unsigned int flags)
2732 SCROLL_S sargs;
2733 STORE_S *store;
2734 int the_answer = 'n', indent, len, cols;
2735 char buf[500], buf2[500];
2736 char *folded;
2737 char *hst = host ? host : "<unknown>";
2738 char *rsn = reason ? reason : "<unknown>";
2739 char *notls = "/notls";
2740 STRLIST_S hostlist;
2741 int ok_novalidate = 0, warned = 0;
2744 dprint((1, "sslfailure: host=%s reason=%s\n",
2745 hst ? hst : "?",
2746 rsn ? rsn : "?"));
2748 if(flags & NET_SILENT)
2749 return;
2751 hostlist.name = host ? host : "";
2752 hostlist.next = NULL;
2755 * See if we've been told about this host before.
2757 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2758 /* we were told already */
2759 if(warned){
2760 snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn);
2761 buf[sizeof(buf)-1] = '\0';
2762 mm_log(buf, ERROR);
2763 return;
2767 cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2768 cols--;
2770 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS)))
2771 return;
2773 strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf));
2774 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2775 so_puts(store, folded);
2776 fs_give((void **)&folded);
2777 so_puts(store, "\n");
2779 if((len=strlen(hst)) <= cols){
2780 if((indent=((cols-len)/2)) > 0)
2781 so_puts(store, repeat_char(indent, SPACE));
2783 so_puts(store, hst);
2784 so_puts(store, "\n");
2786 else{
2787 strncpy(buf, hst, sizeof(buf));
2788 buf[sizeof(buf)-1] = '\0';
2789 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2790 so_puts(store, folded);
2791 fs_give((void **)&folded);
2794 so_puts(store, "\n");
2796 strncpy(buf, _("The reason for the failure was"), sizeof(buf));
2797 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2798 so_puts(store, folded);
2799 fs_give((void **)&folded);
2800 so_puts(store, "\n");
2802 if((len=strlen(rsn)) <= cols){
2803 if((indent=((cols-len)/2)) > 0)
2804 so_puts(store, repeat_char(indent, SPACE));
2806 so_puts(store, rsn);
2807 so_puts(store, "\n");
2809 else{
2810 strncpy(buf, rsn, sizeof(buf));
2811 buf[sizeof(buf)-1] = '\0';
2812 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2813 so_puts(store, folded);
2814 fs_give((void **)&folded);
2817 so_puts(store, "\n");
2819 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));
2820 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2821 so_puts(store, folded);
2822 fs_give((void **)&folded);
2823 so_puts(store, "\n");
2825 if((len=strlen(notls)) <= cols){
2826 if((indent=((cols-len)/2)) > 0)
2827 so_puts(store, repeat_char(indent, SPACE));
2829 so_puts(store, notls);
2830 so_puts(store, "\n");
2832 else{
2833 strncpy(buf, notls, sizeof(buf));
2834 buf[sizeof(buf)-1] = '\0';
2835 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2836 so_puts(store, folded);
2837 fs_give((void **)&folded);
2840 so_puts(store, "\n");
2842 strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2843 sizeof(buf));
2844 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2845 so_puts(store, folded);
2846 fs_give((void **)&folded);
2847 so_puts(store, "\n");
2849 if((len=strlen(hst)) <= cols){
2850 if((indent=((cols-len)/2)) > 0)
2851 so_puts(store, repeat_char(indent, SPACE));
2853 so_puts(store, hst);
2854 so_puts(store, "\n");
2856 else{
2857 strncpy(buf, hst, sizeof(buf));
2858 buf[sizeof(buf)-1] = '\0';
2859 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2860 so_puts(store, folded);
2861 fs_give((void **)&folded);
2864 so_puts(store, "\n");
2866 strncpy(buf, _("in your configuration, replace those characters with"),
2867 sizeof(buf));
2868 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2869 so_puts(store, folded);
2870 fs_give((void **)&folded);
2871 so_puts(store, "\n");
2873 snprintf(buf2, sizeof(buf2), "%s%s", hst, notls);
2874 buf2[sizeof(buf2)-1] = '\0';
2875 if((len=strlen(buf2)) <= cols){
2876 if((indent=((cols-len)/2)) > 0)
2877 so_puts(store, repeat_char(indent, SPACE));
2879 so_puts(store, buf2);
2880 so_puts(store, "\n");
2882 else{
2883 strncpy(buf, buf2, sizeof(buf));
2884 buf[sizeof(buf)-1] = '\0';
2885 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2886 so_puts(store, folded);
2887 fs_give((void **)&folded);
2890 so_puts(store, "\n");
2892 if(ps_global->ttyo){
2893 strncpy(buf, _("Type RETURN to continue."), sizeof(buf));
2894 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2895 so_puts(store, folded);
2896 fs_give((void **)&folded);
2899 memset(&sargs, 0, sizeof(SCROLL_S));
2900 sargs.text.text = so_text(store);
2901 sargs.text.src = CharStar;
2902 sargs.text.desc = _("help text");
2903 sargs.bar.title = _("SSL/TLS FAILURE");
2904 sargs.proc.tool = answer_cert_failure;
2905 sargs.proc.data.p = (void *)&the_answer;
2906 sargs.keys.menu = &ans_certfail_keymenu;
2907 setbitmap(sargs.keys.bitmap);
2908 /* don't want to re-enter c-client */
2909 sargs.quell_newmail = 1;
2910 sargs.help.text = h_tls_failure;
2911 sargs.help.title = _("HELP FOR TLS/SSL FAILURE");
2913 if(ps_global->ttyo)
2914 scrolltool(&sargs);
2915 else{
2916 char **q, **qp;
2917 char *p;
2918 unsigned char c;
2919 int cnt = 0;
2922 * The screen isn't initialized yet, which should mean that this
2923 * is the result of a -p argument. Display_args_err knows how to deal
2924 * with the uninitialized screen, so we mess with the data to get it
2925 * in shape for display_args_err. This is pretty hacky.
2928 so_seek(store, 0L, 0); /* rewind */
2929 /* count the lines */
2930 while(so_readc(&c, store))
2931 if(c == '\n')
2932 cnt++;
2934 qp = q = (char **)fs_get((cnt+1) * sizeof(char *));
2935 memset(q, 0, (cnt+1) * sizeof(char *));
2937 so_seek(store, 0L, 0); /* rewind */
2938 p = buf;
2939 while(so_readc(&c, store)){
2940 if(c == '\n'){
2941 *p = '\0';
2942 *qp++ = cpystr(buf);
2943 p = buf;
2945 else
2946 *p++ = c;
2949 display_args_err(NULL, q, 0);
2950 free_list_array(&q);
2953 ps_global->mangled_screen = 1;
2954 ps_global->painted_body_on_startup = 0;
2955 ps_global->painted_footer_on_startup = 0;
2956 so_give(&store);
2958 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1);
2963 answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2965 int rv = 1;
2967 ps_global->next_screen = SCREEN_FUN_NULL;
2969 switch(cmd){
2970 case MC_YES :
2971 *(int *)(sparms->proc.data.p) = 'y';
2972 break;
2974 case MC_NO :
2975 *(int *)(sparms->proc.data.p) = 'n';
2976 break;
2978 default:
2979 alpine_panic("Unexpected command in answer_cert_failure");
2980 break;
2983 return(rv);
2988 oauth2_auth_answer(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2990 int rv = 1, rc;
2991 AUTH_CODE_S user;
2992 int q_line, flags;
2993 /* TRANSLATORS: user needs to input an access code from the server */
2994 char *accesscodelabel = _("Copy and Paste Access Code");
2995 char token[4*MAILTMPLEN], prompt[4*MAILTMPLEN];
2997 ps_global->next_screen = SCREEN_FUN_NULL;
2999 token[0] = '\0';
3000 switch(cmd){
3001 case MC_YES :
3002 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
3003 flags = OE_APPEND_CURRENT;
3004 sprintf(prompt, "%s: ", accesscodelabel);
3005 do {
3006 rc = optionally_enter(token, q_line, 0, 4*MAILTMPLEN,
3007 prompt, NULL, NO_HELP, &flags);
3008 } while (rc != 0 && rc != 1);
3009 user.code = rc == 0 ? cpystr(token) : NULL;
3010 user.answer = 'e';
3011 rv = rc == 1 ? 0 : 1;
3012 break;
3014 case MC_NO :
3015 user.code = NULL;
3016 user.answer = 'e';
3017 break;
3019 default:
3020 alpine_panic("Unexpected command in oauth2_auth_answer");
3021 break;
3023 *(AUTH_CODE_S *) sparms->proc.data.p = user;
3024 return(rv);
3028 /*----------------------------------------------------------------------
3029 This can be used to prevent the flickering of the check_cue char
3030 caused by numerous (5000+) fetches by c-client. Right now, the only
3031 practical use found is newsgroup subsciption.
3033 check_cue_display will check if this global is set, and won't clear
3034 the check_cue_char if set.
3035 ----*/
3036 void
3037 set_read_predicted(int i)
3039 ps_global->read_predicted = i==1;
3040 #ifdef _WINDOWS
3041 if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global))
3042 check_cue_display(" ");
3043 #endif
3047 /*----------------------------------------------------------------------
3048 Exported method to retrieve logged in user name associated with stream
3050 Args: host -- host to find associated login name with.
3052 Result:
3053 ----*/
3054 void *
3055 pine_block_notify(int reason, void *data)
3057 switch(reason){
3058 case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */
3059 break;
3061 case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */
3062 break;
3064 case BLOCK_TCPWRITE: /* blocked on TCP write */
3065 case BLOCK_FILELOCK: /* blocked on file locking */
3066 #ifdef _WINDOWS
3067 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3068 check_cue_display(">");
3070 mswin_setcursor(MSWIN_CURSOR_BUSY);
3071 #endif
3072 break;
3074 case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */
3075 case BLOCK_TCPOPEN: /* blocked on TCP open */
3076 case BLOCK_TCPREAD: /* blocked on TCP read */
3077 case BLOCK_TCPCLOSE: /* blocked on TCP close */
3078 #ifdef _WINDOWS
3079 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3080 check_cue_display("<");
3082 mswin_setcursor(MSWIN_CURSOR_BUSY);
3083 #endif
3084 break;
3086 default :
3087 case BLOCK_NONE: /* not blocked */
3088 #ifdef _WINDOWS
3089 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3090 check_cue_display(" ");
3091 #endif
3092 break;
3096 return(NULL);
3100 void
3101 mm_expunged_current(long unsigned int rawno)
3103 /* expunged something we're viewing? */
3104 if(!ps_global->expunge_in_progress
3105 && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno))
3106 && (ps_global->prev_screen == mail_view_screen
3107 || ps_global->prev_screen == attachment_screen))){
3108 ps_global->next_screen = mail_index_screen;
3109 q_status_message(SM_ORDER | SM_DING , 3, 3,
3110 "Message you were viewing is gone!");
3115 #ifdef PASSFILE
3118 * Specific functions to support caching username/passwd/host
3119 * triples on disk for use from one session to the next...
3122 #define FIRSTCH 0x20
3123 #define LASTCH 0x7e
3124 #define TABSZ (LASTCH - FIRSTCH + 1)
3126 static int xlate_key;
3130 * xlate_in() - xlate_in the given character
3132 char
3133 xlate_in(int c)
3135 register int eti;
3137 eti = xlate_key;
3138 if((c >= FIRSTCH) && (c <= LASTCH)){
3139 eti += (c - FIRSTCH);
3140 eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0;
3141 return((xlate_key = eti) + FIRSTCH);
3143 else
3144 return(c);
3149 * xlate_out() - xlate_out the given character
3151 char
3152 xlate_out(char c)
3154 register int dti;
3155 register int xch;
3157 if((c >= FIRSTCH) && (c <= LASTCH)){
3158 xch = c - (dti = xlate_key);
3159 xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0;
3160 dti = (xch - FIRSTCH) + dti;
3161 dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0;
3162 xlate_key = dti;
3163 return(xch);
3165 else
3166 return(c);
3168 #endif /* PASSFILE */
3171 #ifdef LOCAL_PASSWD_CACHE
3173 int cache_method_was_setup (char *pinerc)
3175 int rv = 1;
3176 #ifdef PASSFILE
3177 char tmp[MAILTMPLEN];
3178 FILE *fp = NULL;
3179 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb")))
3180 rv = 0;
3181 if(fp != NULL) fclose(fp);
3182 #endif /* PASSFILE */
3183 return rv;
3186 int
3187 line_get(char *tmp, size_t len, char **textp)
3189 char *s;
3191 tmp[0] = '\0';
3192 if (*textp == NULL
3193 || (s = strchr(*textp, '\n')) == NULL
3194 || (s - *textp) > len - 1)
3195 return 0;
3197 *s = '\0';
3198 if(*(s-1) == '\r')
3199 *(s-1) = '\0';
3201 snprintf(tmp, len, "%s\n", *textp);
3202 tmp[len-1] = '\0';
3203 *textp = s+1;
3205 return 1;
3208 typedef struct pwd_s {
3209 char *blob;
3210 char **blobarray;
3211 char *host;
3212 char *user;
3213 char *sflags;
3214 char *passwd;
3215 char *orighost;
3216 int uid;
3217 } ALPINE_PWD_S;
3219 #define SAME_VALUE(X, Y) ((((X) == NULL && (Y) == NULL) \
3220 || ((X) && (Y) && !strcmp((X), (Y)))) ? 1 : 0)
3223 * For UNIX:
3224 * Passfile lines are
3226 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3228 * In pine4.40 and before there was no orig_hostname, and there still isn't
3229 * if it is the same as hostname.
3231 * else for WINDOWS:
3232 * Use Windows credentials. The TargetName of the credential is
3233 * UWash_Alpine_.partnumber-totalparts_<hostname:port>\tuser\taltflag
3234 * and the blob consists of
3235 * passwd\torighost (if different from host)
3237 * We don't use anything fancy we just copy out all the credentials which
3238 * begin with TNAME and put them into our cache, so we don't lookup based
3239 * on the TargetName or anything like that. That was so we could re-use
3240 * the existing code and so that orighost data could be easily used.
3243 read_passfile(pinerc, l)
3244 char *pinerc;
3245 MMLOGIN_S **l;
3247 #ifdef WINCRED
3248 # if (WINCRED > 0)
3249 LPCTSTR lfilter = TEXT(TNAMESTAR);
3250 DWORD count, k;
3251 PCREDENTIAL *pcred;
3252 char *tmp, *blob, *target = NULL;
3253 ALPINE_PWD_S **pwd = NULL;
3254 int uid, rewrite_passfile = 0;
3255 char *ui[5];
3256 int i, j;
3257 unsigned long m, n, p, loc;
3259 if(using_passfile == 0)
3260 return(using_passfile);
3262 if(!g_CredInited){
3263 if (init_wincred_funcs() != 1) {
3264 using_passfile = 0;
3265 return(using_passfile);
3269 dprint((9, "read_passfile\n"));
3271 using_passfile = 1;
3273 /* this code exists because the XOAUTH2 support makes us save
3274 * access tokens as if they were passwords. However, some servers
3275 * produce extremely long access-tokens that do not fit in the credentials
3276 * and therefore need to be split into several entries.
3278 * The plan is the following:
3279 * step 1: Read and save all the information in the credentials
3280 * step 2: flatten the information into one line
3281 * step 3: process that line.
3283 if (g_CredEnumerateW(lfilter, 0, &count, &pcred)) {
3284 pwd = fs_get((count + 1)*sizeof(ALPINE_PWD_S *));
3285 memset((void *)pwd, 0, (count + 1)*sizeof(ALPINE_PWD_S *));
3286 if (pwd && pcred) {
3287 /* this is step 1 */
3288 for (k = 0; k < count; k++) { /* go through each credential */
3289 target = lptstr_to_utf8(pcred[k]->TargetName);
3290 tmp = srchstr(target, TNAME);
3291 if (tmp) {
3292 tmp += strlen(TNAME);
3293 if (*tmp == '.') {
3294 tmp++;
3295 m = strtoul(tmp, &tmp, 10);
3296 if(*tmp == '.'){
3297 tmp++;
3298 uid = m;
3299 m = strtoul(tmp, &tmp, 10);
3301 else{
3302 uid = 0; /* impossible value, uid >= 1, old format! */
3303 rewrite_passfile++;
3305 if (*tmp == '-') {
3306 tmp++;
3307 n = strtol(tmp, &tmp, 10);
3308 if (*tmp == '_') tmp++;
3310 else{
3311 char *s;
3312 for(s = tmp; *s && *s != '_'; s++);
3313 if(s && *s) tmp = s;
3316 else {
3317 m = n = 1;
3320 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3321 for (i = 0, j = 0; tmp[i] && j < 3; j++) {
3322 for (ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3323 ; /* find end of data */
3325 if (tmp[i])
3326 tmp[i++] = '\0'; /* tie off data */
3329 /* improve this. We are trying to find where we saved
3330 * this data, and in general this is fast if there is
3331 * only a few data, which is not unreasonable, but probably
3332 * can be done better.
3334 for (loc = 0; pwd[loc]
3335 && !(uid == pwd[loc]->uid
3336 && SAME_VALUE(ui[0], pwd[loc]->host)
3337 && SAME_VALUE(ui[1], pwd[loc]->user)
3338 && SAME_VALUE(ui[2], pwd[loc]->sflags)); loc++);
3340 if (pwd[loc] == NULL) {
3341 pwd[loc] = fs_get(sizeof(ALPINE_PWD_S));
3342 memset((void *) pwd[loc], 0, sizeof(ALPINE_PWD_S));
3343 pwd[loc]->blobarray = fs_get((n + 1) * sizeof(char*));
3344 memset((void *) pwd[loc]->blobarray, 0, (n + 1) * sizeof(char*));
3347 pwd[loc]->uid = uid;
3348 if (pwd[loc]->host == NULL)
3349 pwd[loc]->host = ui[0] ? cpystr(ui[0]) : NULL;
3350 if (pwd[loc]->user == NULL)
3351 pwd[loc]->user = ui[1] ? cpystr(ui[1]) : NULL;
3352 if (pwd[loc]->sflags == NULL)
3353 pwd[loc]->sflags = ui[2] ? cpystr(ui[2]) : NULL;
3354 blob = (char *) pcred[k]->CredentialBlob;
3355 pwd[loc]->blobarray[m - 1] = blob ? cpystr(blob) : NULL;
3357 if (target) fs_give((void**)&target);
3359 /* step 2 */
3360 for (k = 0; k < count; k++) {
3361 if (pwd[k]) {
3362 for (i = 0, j = 0; pwd[k]->blobarray[j]; j++)
3363 i += strlen(pwd[k]->blobarray[j]);
3364 pwd[k]->blob = fs_get(i + 1);
3365 pwd[k]->blob[0] = '\0';
3366 for (j = 0; pwd[k]->blobarray[j]; j++) {
3367 strcat(pwd[k]->blob, pwd[k]->blobarray[j]);
3368 fs_give((void **) &pwd[k]->blobarray[j]);
3370 fs_give((void **) &pwd[k]->blobarray);
3372 else k = count; /* we are done with this step! */
3374 /* step 3 */
3375 for (k = 0; k < count; k++) {
3376 if (pwd[k] && pwd[k]->blob) {
3377 blob = pwd[k]->blob;
3378 for (i = 0, j = 3; blob[i] && j < 5; j++) {
3379 for (ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++)
3380 ; /* find end of data */
3382 if (blob[i])
3383 blob[i++] = '\0'; /* tie off data */
3385 if (pwd[k]->passwd == NULL)
3386 pwd[k]->passwd = ui[3] ? cpystr(ui[3]) : NULL;
3387 if (pwd[k]->orighost == NULL)
3388 pwd[k]->orighost = ui[4] ? cpystr(ui[4]) : NULL;
3389 fs_give((void **) &pwd[k]->blob);
3392 /* now process all lines, and free memory */
3393 for (k = 0; k < count && pwd[k] != NULL; k++){
3394 if (pwd[k]->passwd && pwd[k]->host && pwd[k]->user) { /* valid field? */
3395 STRLIST_S hostlist[2];
3396 int flags;
3398 tmp = pwd[k]->sflags ? strchr(pwd[k]->sflags, PWDAUTHSEP) : NULL;
3399 flags = pwd[k]->sflags ? atoi(tmp ? ++tmp : pwd[k]->sflags) : 0;
3400 hostlist[0].name = pwd[k]->host;
3401 if (pwd[k]->orighost) {
3402 hostlist[0].next = &hostlist[1];
3403 hostlist[1].name = pwd[k]->orighost;
3404 hostlist[1].next = NULL;
3406 else {
3407 hostlist[0].next = NULL;
3409 imap_set_passwd(l, pwd[k]->passwd, pwd[k]->user, hostlist, flags & 0x01, 0, 0);
3411 if (pwd[k]->passwd) fs_give((void **) &pwd[k]->passwd);
3412 if (pwd[k]->user) fs_give((void **) &pwd[k]->user);
3413 if (pwd[k]->host) fs_give((void **) &pwd[k]->host);
3414 if (pwd[k]->sflags) fs_give((void **) &pwd[k]->sflags);
3415 if (pwd[k]->orighost) fs_give((void **) &pwd[k]->orighost);
3416 fs_give((void **) &pwd[k]);
3418 g_CredFree((PVOID)pcred);
3420 fs_give((void **) &pwd);
3421 if(rewrite_passfile) write_passfile(pinerc, *l);
3423 return(1);
3425 # else /* old windows */
3426 using_passfile = 0;
3427 return(0);
3428 # endif
3430 #elif APPLEKEYCHAIN
3432 char target[MAILTMPLEN];
3433 char *tmp, *host, *user, *sflags, *passwd, *orighost;
3434 char *ui[5];
3435 int i, j, k, rc;
3436 SecKeychainAttributeList attrList;
3437 SecKeychainSearchRef searchRef = NULL;
3438 SecKeychainAttribute attrs[] = {
3439 { kSecAccountItemAttr, strlen(TNAME), TNAME }
3442 if(using_passfile == 0)
3443 return(using_passfile);
3445 dprint((9, "read_passfile\n"));
3448 /* search for only our items in the keychain */
3449 attrList.count = 1;
3450 attrList.attr = attrs;
3452 using_passfile = 1;
3453 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
3454 kSecGenericPasswordItemClass,
3455 &attrList,
3456 &searchRef))){
3457 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3458 if(searchRef){
3459 SecKeychainItemRef itemRef = NULL;
3460 SecKeychainAttributeInfo info;
3461 SecKeychainAttributeList *attrList = NULL;
3462 UInt32 blength = 0;
3463 char *blob = NULL;
3464 char *blobcopy = NULL; /* NULL terminated copy */
3466 UInt32 tags[] = {kSecAccountItemAttr,
3467 kSecServiceItemAttr};
3468 UInt32 formats[] = {0,0};
3470 dprint((10, "read_passfile: searchRef not NULL\n"));
3471 info.count = 2;
3472 info.tag = tags;
3473 info.format = formats;
3476 * Go through each item we found and put it
3477 * into our list.
3479 while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){
3480 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3481 rc = SecKeychainItemCopyAttributesAndData(itemRef,
3482 &info, NULL,
3483 &attrList,
3484 &blength,
3485 (void **) &blob);
3486 if(rc == 0 && attrList){
3487 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count));
3489 blobcopy = (char *) fs_get((blength + 1) * sizeof(char));
3490 strncpy(blobcopy, (char *) blob, blength);
3491 blobcopy[blength] = '\0';
3494 * I'm not real clear on how this works. It seems to be
3495 * necessary to combine the attributes from two passes
3496 * (attrList->count == 2) in order to get the full set
3497 * of attributes we inserted into the keychain in the
3498 * first place. So, we reset host...orighost outside of
3499 * the following for loop, not inside.
3501 host = user = sflags = passwd = orighost = NULL;
3502 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3504 for(k = 0; k < attrList->count; k++){
3506 if(attrList->attr[k].length){
3507 strncpy(target,
3508 (char *) attrList->attr[k].data,
3509 MIN(attrList->attr[k].length,sizeof(target)));
3510 target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0';
3513 tmp = target;
3514 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3515 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3516 ; /* find end of data */
3518 if(tmp[i])
3519 tmp[i++] = '\0'; /* tie off data */
3522 if(ui[0])
3523 host = ui[0];
3525 if(ui[1])
3526 user = ui[1];
3528 if(ui[2])
3529 sflags = ui[2];
3531 for(i = 0, j = 3; blobcopy[i] && j < 5; j++){
3532 for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++)
3533 ; /* find end of data */
3535 if(blobcopy[i])
3536 blobcopy[i++] = '\0'; /* tie off data */
3539 if(ui[3])
3540 passwd = ui[3];
3542 if(ui[4])
3543 orighost = ui[4];
3545 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:""));
3548 if(passwd && host && user){ /* valid field? */
3549 STRLIST_S hostlist[2];
3550 int flags;
3552 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3553 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3554 hostlist[0].name = host;
3555 if(orighost){
3556 hostlist[0].next = &hostlist[1];
3557 hostlist[1].name = orighost;
3558 hostlist[1].next = NULL;
3560 else{
3561 hostlist[0].next = NULL;
3564 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3567 if(blobcopy)
3568 fs_give((void **) & blobcopy);
3570 SecKeychainItemFreeAttributesAndData(attrList, (void *) blob);
3572 else{
3573 using_passfile = 0;
3574 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc));
3577 CFRelease(itemRef);
3578 itemRef = NULL;
3581 CFRelease(searchRef);
3583 else{
3584 using_passfile = 0;
3585 dprint((10, "read_passfile: searchRef NULL\n"));
3588 else{
3589 using_passfile = 0;
3590 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc));
3593 return(using_passfile);
3595 #else /* PASSFILE */
3597 char tmp[MAILTMPLEN], *ui[5];
3598 int i, j, n, rv = 0, error = 0;
3599 size_t len = 0;
3600 char *tmptext = NULL;
3601 struct stat sbuf;
3602 #ifdef SMIME
3603 char tmp2[MAILTMPLEN];
3604 char *text = NULL, *text2 = NULL;
3605 int encrypted = 0;
3606 #endif /* SMIME */
3607 FILE *fp;
3609 if(using_passfile == 0)
3610 return(using_passfile);
3612 dprint((9, "read_passfile\n"));
3614 /* if there's no password to read, create it if we can encrypt it,
3615 * or else let the user create it and bail out of here.
3617 tmp[0] = '\0';
3618 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){
3619 #ifdef SMIME
3620 i = our_creat(tmp, 0600);
3621 if(i >= 0){
3622 close(i);
3623 if(!(fp = our_fopen(tmp, "rb")))
3624 error++;
3626 else error++;
3627 #else
3628 error++;
3629 #endif
3632 if(error){
3633 using_passfile = 0;
3634 return(using_passfile);
3637 #ifndef SMIME
3638 if(our_stat(tmp, &sbuf) == 0)
3639 len = sbuf.st_size;
3640 fp = our_fopen(tmp, "rb"); /* reopen to read data */
3641 #else
3642 /* the next call initializes the key/certificate pair used to
3643 * encrypt and decrypt a password file. The details of how this is
3644 * done is in the file pith/smime.c. During this setup we might call
3645 * smime_init(), but no matter what happens we must call smime_deinit()
3646 * there. The reason why this is so is because we can not assume that
3647 * the .pinerc file has been read by this time, so this code might not
3648 * know about the ps_global->smime structure or any of its components,
3649 * and it shouldn't because it only needs ps_global->pwdcert, so
3650 * do not init smime here, because the .pinerc might not have been
3651 * read and we do not really know where the keys and certificates really
3652 * are.
3653 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3654 * it is called for the first time and there are certificates at all,
3655 * or when it is called after the first time and the user refuses to
3656 * create a self-signed certificate. In this situation we will just
3657 * let the user live in an insecure world, but no more passwords will
3658 * be saved in the password file, and only those found there will be used.
3660 tmp2[0] = '\0';
3661 fgets(tmp2, sizeof(tmp2), fp);
3662 fclose(fp);
3663 if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
3664 /* there is an already existing password file, that is not encrypted
3665 * and there is no key to encrypt it yet, go again through setup_pwdcert
3666 * and encrypt it now.
3668 if(tmp2[0]){ /* not empty, UNencrypted password file */
3669 if(ps_global->pwdcert == NULL)
3670 rv = setup_pwdcert(&ps_global->pwdcert);
3671 if((rv == 0 || rv == -5) && ps_global->pwdcert == NULL)
3672 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3673 if(ps_global->pwdcert == NULL){
3674 q_status_message(SM_ORDER, 3, 3,
3675 " Failed to create private key. Using UNencrypted Password file. ");
3676 save_password = 0;
3678 else{
3679 if(rv == 1){
3680 q_status_message(SM_ORDER, 3, 3,
3681 " Failed to unlock private key. Using UNencrypted Password file. ");
3682 save_password = 0; /* do not save more passwords */
3685 if(ps_global->pwdcert != NULL
3686 && encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
3687 encrypted++;
3690 else {
3691 if(ps_global->pwdcert == NULL)
3692 rv = setup_pwdcert(&ps_global->pwdcert);
3693 encrypted++;
3697 * if password file is encrypted we attempt to decrypt. We ask the
3698 * user for the password to unlock the password file. If the user
3699 * enters the password and it unlocks the file, use it and keep saving
3700 * passwords in it. If the user enters the wrong passwords and does
3701 * not unlock it, we will not see that here, but in decrypt_file, so
3702 * the only other possibility is that the user cancels. In that case
3703 * we will see i == -1. In that case, we will let the user attempt
3704 * manual login to the server they want to login, but passwords will
3705 * not be saved so that the password file will not be saved
3706 * unencrypted and rewritten again.
3708 if(encrypted){
3709 text = text2 = decrypt_file((char *)tmp, &i, (PERSONAL_CERT *)ps_global->pwdcert);
3710 len = text2 ? strlen(text2) : 0;
3711 switch(i){
3712 case -2: using_passfile = 0;
3713 break;
3715 case 1 : save_password = 1;
3716 using_passfile = 1;
3717 break;
3719 case -1: save_password = 0;
3720 using_passfile = 1;
3721 break;
3723 default: break;
3726 #endif /* SMIME */
3728 if(using_passfile == 0){
3729 #ifdef SMIME
3730 if(text) fs_give((void **)&text);
3731 #endif /* SMIME */
3732 return using_passfile;
3735 if(len > 0){
3736 tmptext = fs_get(len + 1);
3737 #ifdef SMIME
3738 for(n = 0; encrypted ? line_get(tmptext, len + 1, &text2)
3739 : (fgets(tmptext, len+1, fp) != NULL); n++){
3740 #else /* SMIME */
3741 for(n = 0; fgets(tmptext, len+1, fp); n++){
3742 #endif /* SMIME */
3743 /*** do any necessary DEcryption here ***/
3744 xlate_key = n;
3745 for(i = 0; tmptext[i]; i++)
3746 tmptext[i] = xlate_out(tmptext[i]);
3748 if(i && tmptext[i-1] == '\n')
3749 tmptext[i-1] = '\0'; /* blast '\n' */
3751 dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
3752 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3753 for(i = 0, j = 0; tmptext[i] && j < 5; j++){
3754 for(ui[j] = &tmptext[i]; tmptext[i] && tmptext[i] != '\t'; i++)
3755 ; /* find end of data */
3757 if(tmptext[i])
3758 tmptext[i++] = '\0'; /* tie off data */
3761 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3762 if(ui[0] && ui[1] && ui[2]){ /* valid field? */
3763 STRLIST_S hostlist[2];
3764 char *s = ui[3] ? strchr(ui[3], PWDAUTHSEP) : NULL;
3765 int flags = ui[3] ? atoi(s ? ++s : ui[3]) : 0;
3767 hostlist[0].name = ui[2];
3768 if(ui[4]){
3769 hostlist[0].next = &hostlist[1];
3770 hostlist[1].name = ui[4];
3771 hostlist[1].next = NULL;
3773 else{
3774 hostlist[0].next = NULL;
3777 imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0);
3782 if (tmptext) fs_give((void **) &tmptext);
3783 #ifdef SMIME
3784 if (text) fs_give((void **)&text);
3785 #else /* SMIME */
3786 fclose(fp);
3787 #endif /* SMIME */
3788 return(1);
3789 #endif /* PASSFILE */
3794 void
3795 write_passfile(pinerc, l)
3796 char *pinerc;
3797 MMLOGIN_S *l;
3799 char *authend, *authtype;
3800 #ifdef WINCRED
3801 # if (WINCRED > 0)
3802 unsigned long bloblen = 0, len;
3803 int i, totalparts, k, uid;
3804 char target[MAXPWDBUFFERSIZE];
3805 char *blob = NIL, blob2[50], *blobp;
3806 char part[MAILTMPLEN];
3807 CREDENTIAL cred;
3808 LPTSTR ltarget = 0;
3810 if(using_passfile == 0)
3811 return;
3813 dprint((9, "write_passfile\n"));
3815 erase_windows_credentials(); /* erase all passwords from credentials */
3816 /* start writing them back to credentials manager */
3817 for(uid = 1; l; l = l->next, uid++){ /* enforce that uid >= 1 */
3818 /* determine how many parts to create first */
3819 len = (l->passwd ? strlen(l->passwd) : 0)
3820 + ((l->hosts && l->hosts->next && l->hosts->next->name) ? 1 : 0)
3821 + ((l->hosts && l->hosts->next && l->hosts->next->name) ? strlen(l->hosts->next->name) : 0) + 1;
3823 if(len > bloblen){
3824 bloblen = len;
3825 fs_resize((void **) &blob, bloblen);
3828 sprintf(blob, "%s%s%s",
3829 l->passwd ? l->passwd : "",
3830 (l->hosts && l->hosts->next && l->hosts->next->name)
3831 ? "\t" : "",
3832 (l->hosts && l->hosts->next && l->hosts->next->name)
3833 ? l->hosts->next->name : "");
3834 i = len - 1; /* strlen(blob) */
3835 blobp = blob;
3836 for (totalparts = 1; i > MAXPWDBUFFERSIZE; totalparts++, i -= PWDBUFFERSIZE);
3837 authtype = l->passwd;
3838 authend = strchr(l->passwd, PWDAUTHSEP);
3840 if (authend != NULL){
3841 *authend = '\0';
3842 sprintf(blob2, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3843 *authend = PWDAUTHSEP;
3845 else
3846 sprintf(blob2, "%d", l->altflag);
3848 for (k = 1, i = len - 1, blobp = blob; k <= totalparts; k++) {
3849 snprintf(target, sizeof(target), "%s.%d.%d-%d_%s\t%s\t%s",
3850 TNAME, uid, k, totalparts,
3851 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3852 l->user ? l->user : "",
3853 blob2);
3854 ltarget = utf8_to_lptstr((LPSTR)target);
3855 if (ltarget) {
3856 memset((void*)&cred, 0, sizeof(cred));
3857 cred.Flags = 0;
3858 cred.Type = CRED_TYPE_GENERIC;
3859 cred.TargetName = ltarget;
3860 if (i > MAXPWDBUFFERSIZE) {
3861 strncpy(part, blobp, PWDBUFFERSIZE);
3862 part[PWDBUFFERSIZE] = '\0';
3863 blobp += PWDBUFFERSIZE;
3864 i -= PWDBUFFERSIZE;
3866 else
3867 strcpy(part, blobp);
3868 cred.CredentialBlobSize = strlen(part) + 1;
3869 cred.CredentialBlob = (LPBYTE)&part;
3870 cred.Persist = CRED_PERSIST_ENTERPRISE;
3871 g_CredWriteW(&cred, 0);
3872 fs_give((void**)&ltarget);
3876 if(blob) fs_give((void **) &blob);
3878 #endif /* WINCRED > 0 */
3880 #elif APPLEKEYCHAIN
3881 int rc;
3882 char target[10*MAILTMPLEN];
3883 char blob[10*MAILTMPLEN];
3884 SecKeychainItemRef itemRef = NULL;
3886 if(using_passfile == 0)
3887 return;
3889 dprint((9, "write_passfile\n"));
3891 for(; l; l = l->next){
3892 authtype = l->passwd;
3893 authend = strchr(l->passwd, PWDAUTHSEP);
3894 if(authend != NULL){
3895 *authend = '\0';
3896 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3897 *authend = PWDAUTHSEP;
3899 else
3900 sprintf(blob, "%d", l->altflag);
3902 snprintf(target, sizeof(target), "%s\t%s\t%s",
3903 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3904 l->user ? l->user : "",
3905 blob);
3907 snprintf(blob, sizeof(blob), "%s%s%s",
3908 l->passwd ? l->passwd : "",
3909 (l->hosts && l->hosts->next && l->hosts->next->name)
3910 ? "\t" : "",
3911 (l->hosts && l->hosts->next && l->hosts->next->name)
3912 ? l->hosts->next->name : "");
3914 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob));
3916 rc = SecKeychainAddGenericPassword(NULL,
3917 strlen(target), target,
3918 strlen(TNAME), TNAME,
3919 strlen(blob), blob,
3920 NULL);
3921 if(rc==0){
3922 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3924 else{
3925 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc));
3928 if(rc == errSecDuplicateItem){
3929 /* fix existing entry */
3930 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3931 itemRef = NULL;
3932 if(!(rc=SecKeychainFindGenericPassword(NULL,
3933 strlen(target), target,
3934 strlen(TNAME), TNAME,
3935 NULL, NULL,
3936 &itemRef)) && itemRef){
3938 rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob);
3939 if(!rc){
3940 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc));
3943 else{
3944 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc));
3949 #else /* PASSFILE */
3950 char *tmp = NULL, passfile[MAXPATH + 1], blob[MAILTMPLEN];
3951 int i, n;
3952 size_t tmplen = 0, newlen;
3953 FILE *fp;
3954 #ifdef SMIME
3955 char *text = NULL, tmp2[MAXPATH + 1];
3956 int len = 0;
3957 #endif
3959 if(using_passfile == 0)
3960 return;
3962 dprint((9, "write_passfile\n"));
3964 /* if there's no passfile to read, bag it!! */
3965 if(!passfile_name(pinerc, passfile, sizeof(passfile)) || !(fp = our_fopen(passfile, "wb"))){
3966 using_passfile = 0;
3967 return;
3970 #ifdef SMIME
3971 strncpy(tmp2, passfile, sizeof(tmp2));
3972 tmp2[sizeof(tmp2)-1] = '\0';
3973 #endif /* SMIME */
3975 for(n = 0; l; l = l->next, n++){
3976 authtype = l->passwd;
3977 authend = strchr(l->passwd, PWDAUTHSEP);
3978 if(authend != NULL){
3979 *authend = '\0';
3980 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3981 *authend = PWDAUTHSEP;
3983 else
3984 sprintf(blob, "%d", l->altflag);
3986 newlen = strlen(l->passwd) + strlen(l->user) + strlen(l->hosts->name)
3987 + strlen(blob) + strlen((l->hosts->next && l->hosts->next->name) ? "\t" : "")
3988 + strlen((l->hosts->next && l->hosts->next->name) ? l->hosts->next->name : "")
3989 + 4 + 1;
3991 if(tmplen < newlen){
3992 fs_resize((void **)&tmp, newlen);
3993 tmplen = newlen;
3996 /*** do any necessary ENcryption here ***/
3997 sprintf(tmp, "%s\t%s\t%s\t%s%s%s\n", l->passwd, l->user,
3998 l->hosts->name, blob,
3999 (l->hosts->next && l->hosts->next->name) ? "\t" : "",
4000 (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
4001 : "");
4002 dprint((10, "write_passfile: %s", tmp ? tmp : "?"));
4003 xlate_key = n;
4004 for(i = 0; tmp[i]; i++)
4005 tmp[i] = xlate_in(tmp[i]);
4007 #ifdef SMIME
4008 fs_resize((void **)&text, (len + strlen(tmp) + 1)*sizeof(char));
4009 text[len] = '\0';
4010 len += strlen(tmp) + 1;
4011 strncat(text, tmp, strlen(tmp));
4012 #else /* SMIME */
4013 fputs(tmp, fp);
4014 #endif /* SMIME */
4017 if(tmp) fs_give((void **) &tmp);
4018 fclose(fp);
4019 #ifdef SMIME
4020 if(text != NULL){
4021 i = 0; /* to quell gcc */
4022 if(ps_global->pwdcert == NULL){
4023 q_status_message(SM_ORDER, 3, 3, "Attempting to encrypt password file");
4024 i = setup_pwdcert(&ps_global->pwdcert);
4025 if((i == 0 || i == -5) && ps_global->pwdcert == NULL)
4026 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
4028 if(ps_global->pwdcert == NULL){ /* we tried but failed */
4029 if(i == -1)
4030 q_status_message1(SM_ORDER, 3, 3, "Error: no directory %s to save certificates", ps_global->pwdcertdir);
4031 else
4032 q_status_message(SM_ORDER, 3, 3, "Refusing to write non-encrypted password file");
4034 else if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0)
4035 q_status_message(SM_ORDER, 3, 3, "Failed to encrypt password file");
4036 fs_give((void **)&text); /* do not save this text */
4038 #endif /* SMIME */
4039 #endif /* PASSFILE */
4042 #endif /* LOCAL_PASSWD_CACHE */
4045 #if (WINCRED > 0)
4046 void
4047 erase_windows_credentials(void)
4049 LPCTSTR lfilter = TEXT(TNAMESTAR);
4050 DWORD count, k;
4051 PCREDENTIAL *pcred;
4053 if(!g_CredInited){
4054 if(init_wincred_funcs() != 1)
4055 return;
4058 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
4059 if(pcred){
4060 for(k = 0; k < count; k++)
4061 g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0);
4063 g_CredFree((PVOID) pcred);
4068 void
4069 ask_erase_credentials(void)
4071 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){
4072 erase_windows_credentials();
4073 q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased");
4075 else
4076 q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased");
4079 #endif /* WINCRED */
4082 #ifdef LOCAL_PASSWD_CACHE
4084 get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
4085 char *pinerc, **passwd, *user;
4086 STRLIST_S *hostlist;
4087 int altflag;
4089 return get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, NULL);
4093 * get_passfile_passwd_auth - return the password contained in the special password
4094 * cache. The file is assumed to be in the same directory
4095 * as the pinerc with the name defined above.
4098 get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, authtype)
4099 char *pinerc, **passwd, *user;
4100 STRLIST_S *hostlist;
4101 int altflag;
4102 char *authtype;
4104 dprint((10, "get_passfile_passwd_auth\n"));
4105 return((mm_login_list || read_passfile(pinerc, &mm_login_list))
4106 ? imap_get_passwd_auth(mm_login_list, passwd,
4107 user, hostlist, altflag, authtype)
4108 : 0);
4112 is_using_passfile(void)
4114 return(using_passfile == 1);
4118 * Just trying to guess the username the user might want to use on this
4119 * host, the user will confirm.
4121 char *
4122 get_passfile_user(pinerc, hostlist)
4123 char *pinerc;
4124 STRLIST_S *hostlist;
4126 return((mm_login_list || read_passfile(pinerc, &mm_login_list))
4127 ? imap_get_user(mm_login_list, hostlist)
4128 : NULL);
4133 preserve_prompt(char *pinerc)
4135 return preserve_prompt_auth(pinerc, NULL);
4139 preserve_prompt_auth(char *pinerc, char *authtype)
4141 ps_global->preserve_password = 0;
4142 #ifdef WINCRED
4143 # if (WINCRED > 0)
4144 #define PROMPT_PWD _("Preserve password for next login")
4145 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4147 * This prompt was going to be able to be turned on and off via a registry
4148 * setting controlled from the config menu. We decided to always use the
4149 * dialog for login, and there the prompt is unobtrusive enough to always
4150 * be in there. As a result, windows should never reach this, but now
4151 * OS X somewhat uses the behavior just described.
4153 if(mswin_store_pass_prompt()
4154 && (want_to(authtype
4155 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4156 : PROMPT_PWD,
4157 'y', 'x', NO_HELP, WT_NORM)
4158 == 'y')){
4159 ps_global->preserve_password = 1;
4160 return(1);
4162 else
4163 return(0);
4164 # else
4165 return(0);
4166 # endif
4168 #elif APPLEKEYCHAIN
4169 #define PROMPT_PWD _("Preserve password for next login")
4170 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4172 int rc;
4173 if((rc = macos_store_pass_prompt()) != 0){
4174 if(want_to(authtype
4175 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4176 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4177 == 'y'){
4178 ps_global->preserve_password = 1;
4179 if(rc == -1){
4180 macos_set_store_pass_prompt(1);
4181 q_status_message(SM_ORDER, 4, 4,
4182 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4184 return(1);
4186 else if(rc == -1){
4187 macos_set_store_pass_prompt(0);
4188 q_status_message(SM_ORDER, 4, 4,
4189 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4191 return(0);
4193 return(0);
4194 #else /* PASSFILE */
4195 #define PROMPT_PWD _("Preserve password on DISK for next login")
4196 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
4198 char tmp[MAILTMPLEN];
4199 struct stat sbuf;
4201 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || our_stat(tmp, &sbuf) < 0)
4202 return 0;
4204 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
4205 if(want_to(authtype
4206 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4207 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4208 == 'y'){
4209 ps_global->preserve_password = 1;
4210 return 1;
4212 return(0);
4213 #endif /* PASSFILE */
4216 #endif /* LOCAL_PASSWD_CACHE */
4219 #ifdef APPLEKEYCHAIN
4222 * Returns:
4223 * 1 if store pass prompt is set in the "registry" to on
4224 * 0 if set to off
4225 * -1 if not set to anything
4228 macos_store_pass_prompt(void)
4230 char *data = NULL;
4231 UInt32 len = 0;
4232 int rc = -1;
4233 int val;
4235 if(storepassprompt == -1){
4236 if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL,
4237 strlen(TNAMEPROMPT),
4238 TNAMEPROMPT, &len,
4239 (void **) &data, NULL))){
4240 val = (len == 1 && data && data[0] == '1');
4244 if(storepassprompt == -1 && !rc){
4245 if(val)
4246 storepassprompt = 1;
4247 else
4248 storepassprompt = 0;
4251 return(storepassprompt);
4255 void
4256 macos_set_store_pass_prompt(int val)
4258 storepassprompt = val ? 1 : 0;
4260 SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT),
4261 TNAMEPROMPT, 1, val ? "1" : "0", NULL);
4265 void
4266 macos_erase_keychain(void)
4268 SecKeychainAttributeList attrList;
4269 SecKeychainSearchRef searchRef = NULL;
4270 SecKeychainAttribute attrs1[] = {
4271 { kSecAccountItemAttr, strlen(TNAME), TNAME }
4273 SecKeychainAttribute attrs2[] = {
4274 { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT }
4277 dprint((9, "macos_erase_keychain\n"));
4280 * Seems like we ought to be able to combine attrs1 and attrs2
4281 * into a single array, but I couldn't get it to work.
4284 /* search for only our items in the keychain */
4285 attrList.count = 1;
4286 attrList.attr = attrs1;
4288 if(!SecKeychainSearchCreateFromAttributes(NULL,
4289 kSecGenericPasswordItemClass,
4290 &attrList,
4291 &searchRef)){
4292 if(searchRef){
4293 SecKeychainItemRef itemRef = NULL;
4296 * Go through each item we found and put it
4297 * into our list.
4299 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4300 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4301 SecKeychainItemDelete(itemRef);
4302 CFRelease(itemRef);
4305 CFRelease(searchRef);
4309 attrList.count = 1;
4310 attrList.attr = attrs2;
4312 if(!SecKeychainSearchCreateFromAttributes(NULL,
4313 kSecGenericPasswordItemClass,
4314 &attrList,
4315 &searchRef)){
4316 if(searchRef){
4317 SecKeychainItemRef itemRef = NULL;
4320 * Go through each item we found and put it
4321 * into our list.
4323 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4324 SecKeychainItemDelete(itemRef);
4325 CFRelease(itemRef);
4328 CFRelease(searchRef);
4333 #endif /* APPLEKEYCHAIN */
4335 #ifdef LOCAL_PASSWD_CACHE
4337 void
4338 set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
4339 char *pinerc, *passwd, *user;
4340 STRLIST_S *hostlist;
4341 int altflag, already_prompted;
4343 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, NULL);
4346 * set_passfile_passwd - set the password file entry associated with
4347 * cache. The file is assumed to be in the same directory
4348 * as the pinerc with the name defined above.
4349 * already_prompted: 0 not prompted
4350 * 1 prompted, answered yes
4351 * 2 prompted, answered no
4353 void
4354 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, authtype)
4355 char *pinerc, *passwd, *user;
4356 STRLIST_S *hostlist;
4357 int altflag, already_prompted;
4358 char *authtype;
4360 dprint((10, "set_passfile_passwd_auth\n"));
4361 if(((already_prompted == 0 && preserve_prompt_auth(pinerc, authtype))
4362 || already_prompted == 1)
4363 && !ps_global->nowrite_password_cache
4364 && (mm_login_list || read_passfile(pinerc, &mm_login_list))){
4365 imap_set_passwd_auth(&mm_login_list, passwd, user, hostlist, altflag, 0, 0, authtype);
4366 write_passfile(pinerc, mm_login_list);
4370 void
4371 update_passfile_hostlist(pinerc, user, hostlist, altflag)
4372 char *pinerc;
4373 char *user;
4374 STRLIST_S *hostlist;
4375 int altflag;
4377 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, NULL);
4381 * Passfile lines are
4383 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4385 * In pine4.40 and before there was no orig_hostname.
4386 * This routine attempts to repair that.
4388 void
4389 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, authtype)
4390 char *pinerc;
4391 char *user;
4392 STRLIST_S *hostlist;
4393 int altflag;
4394 char *authtype;
4396 #ifdef WINCRED
4397 return;
4398 #else /* !WINCRED */
4399 MMLOGIN_S *l;
4400 size_t len = authtype ? strlen(authtype) : 0;
4401 size_t offset = authtype ? 1 : 0;
4403 for(l = mm_login_list; l; l = l->next)
4404 if(imap_same_host_auth(l->hosts, hostlist, authtype)
4405 && *user
4406 && !strcmp(user, l->user + len + offset)
4407 && l->altflag == altflag){
4408 break;
4411 if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
4412 && hostlist->next->name
4413 && !ps_global->nowrite_password_cache){
4414 l->hosts->next = new_strlist_auth(hostlist->next->name, authtype, PWDAUTHSEP);
4415 write_passfile(pinerc, mm_login_list);
4417 #endif /* !WINCRED */
4420 #endif /* LOCAL_PASSWD_CACHE */
4423 #if (WINCRED > 0)
4425 * Load and init the WinCred structure.
4426 * This gives us a way to skip the WinCred code
4427 * if the dll doesn't exist.
4430 init_wincred_funcs(void)
4432 if(!g_CredInited)
4434 HMODULE hmod;
4436 /* Assume the worst. */
4437 g_CredInited = -1;
4439 hmod = LoadLibrary(TEXT("advapi32.dll"));
4440 if(hmod)
4442 FARPROC fpCredWriteW;
4443 FARPROC fpCredEnumerateW;
4444 FARPROC fpCredDeleteW;
4445 FARPROC fpCredFree;
4447 fpCredWriteW = GetProcAddress(hmod, "CredWriteW");
4448 fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW");
4449 fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW");
4450 fpCredFree = GetProcAddress(hmod, "CredFree");
4452 if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree)
4454 g_CredWriteW = (CREDWRITEW *)fpCredWriteW;
4455 g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW;
4456 g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW;
4457 g_CredFree = (CREDFREE *)fpCredFree;
4459 g_CredInited = 1;
4463 mswin_set_erasecreds_callback(ask_erase_credentials);
4466 return g_CredInited;
4469 #endif /* WINCRED */