* Fix some compiler warnings for bad casting in some functions in the file
[alpine.git] / alpine / imap.c
blob511e2906e41aaa243e17542d30ac2e826477a7c2
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, NETMBX *mb)
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_o_t pc;
374 gf_i_t gc;
375 HANDLE_S *handles = NULL;
376 AUTH_CODE_S user_input;
378 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
379 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
380 goto try_wantto;
382 aux_value.xoauth2 = oa2;
383 aux_value.code_success = 'e';
384 aux_value.code_failure = 'e';
385 aux_value.code_wait = NO_OP_COMMAND;
387 so_puts(in_store, "<HTML><P>");
388 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name);
389 so_puts(in_store, tmp);
390 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account with username <B>%s</B> using the %s method."), name, mb->user, method),
391 so_puts(in_store, tmp);
393 if(deviceinfo->verification_uri && deviceinfo->user_code){
394 sprintf(tmp,
395 _("</P><P>To sign in, use a web browser to open the page <A HREF=\"%s\">%s</A> and enter the code %s."),
396 deviceinfo->verification_uri, deviceinfo->verification_uri, deviceinfo->user_code);
397 so_puts(in_store, tmp);
399 else{
400 so_puts(in_store, "</P><P>");
401 so_puts(in_store, (char *) deviceinfo->message);
403 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
404 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), name);
405 so_puts(in_store, tmp);
406 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
408 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 "));
409 so_puts(in_store, _("to grant access to Alpine to your data. "));
411 cache_method_message(in_store);
413 so_puts(in_store, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit."));
414 so_puts(in_store, _("</P></HTML>"));
416 so_seek(in_store, 0L, 0);
417 init_handles(&handles);
418 gf_filter_init();
419 gf_link_filter(gf_html2plain,
420 gf_html2plain_opt(NULL,
421 ps_global->ttyo->screen_cols, non_messageview_margin(),
422 &handles, NULL, GFHP_LOCAL_HANDLES));
423 gf_set_so_readc(&gc, in_store);
424 gf_set_so_writec(&pc, out_store);
425 gf_pipe(gc, pc);
426 gf_clear_so_writec(out_store);
427 gf_clear_so_readc(in_store);
429 memset(&sargs, 0, sizeof(SCROLL_S));
430 sargs.text.handles = handles;
431 sargs.text.text = so_text(out_store);
432 sargs.text.src = CharStar;
433 sargs.text.desc = _("help text");
434 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
435 sargs.proc.tool = oauth2_auth_answer;
436 sargs.proc.data.p = (void *)&user_input;
437 sargs.keys.menu = &oauth2_device_auth_keymenu;
438 /* don't want to re-enter c-client */
439 sargs.quell_newmail = 1;
440 setbitmap(sargs.keys.bitmap);
441 sargs.help.text = h_oauth2_start_device;
442 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
443 sargs.aux_function = oauth2deviceinfo_get_accesscode;
444 sargs.aux_value = (void *) &aux_value;
445 sargs.aux_condition = oauth2_elapsed_done;
446 sargs.decode_aux_rv_value = oauth2device_decode_reply;
447 sargs.aux_rv_value = (void *) &aux_rv_value;
449 do {
450 if(scrolltool(&sargs) == MC_NO)
451 ps_global->user_says_cancel = 1;
452 ps_global->mangled_screen = 1;
453 ps_global->painted_body_on_startup = 0;
454 ps_global->painted_footer_on_startup = 0;
455 } while (user_input.answer != 'e');
457 so_give(&in_store);
458 so_give(&out_store);
459 free_handles(&handles);
460 oauth2_elapsed_done(NULL);
462 else{
464 * If screen hasn't been initialized yet, use want_to.
466 try_wantto:
468 tmp_20k_buf[0] = '\0';
469 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
470 _("Authorizing Alpine Access to %s Email Services\n\n"), name);
471 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
473 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
474 _("Alpine is attempting to log you into your %s account with username %s using the %s method."), name, mb->user, method);
475 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
477 if(deviceinfo->verification_uri && deviceinfo->user_code){
478 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
479 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
480 deviceinfo->verification_uri, deviceinfo->user_code);
481 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
483 else{
484 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
485 "%s\n\n", deviceinfo->message);
486 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
489 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
490 _("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);
491 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
493 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
494 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
495 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
497 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
498 "%s", _("to grant access to Alpine to your data.\n\n"));
499 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
501 cache_method_message_no_screen();
503 display_init_err(tmp_20k_buf, 0);
504 memset((void *)tmp, 0, sizeof(tmp));
505 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
506 tmp[sizeof(tmp)-1] = '\0';
508 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
509 int rv;
510 UCS ch;
512 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
513 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
514 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
516 aux_value.xoauth2 = oa2;
517 aux_value.code_success = 'y';
518 aux_value.code_failure = 'n';
519 aux_value.code_wait = 'w';
521 strncpy(tmp, _("Continue waiting"), sizeof(tmp));
522 tmp[sizeof(tmp)-1] = '\0';
523 do {
524 if(oauth2_elapsed_done((void *) &aux_value) == 0)
525 oauth2deviceinfo_get_accesscode((void *) &aux_value, (void *) &rv);
526 ch = oauth2device_decode_reply((void *) &aux_value, (void *) &rv);
527 } while (ch == 'w' || want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y');
528 oauth2_elapsed_done(NULL);
533 char *
534 oauth2_get_access_code(unsigned char *url, char *method, OAUTH2_S *oauth2, NETMBX *mb, int *tryanother)
536 char tmp[MAILTMPLEN];
537 char *code = NULL;
539 dprint((2, "-- oauth2_get_access_code\n"));
540 ps_global->in_xoauth2_auth = 1;
541 if(ps_global->ttyo){
542 SCROLL_S sargs;
543 STORE_S *in_store, *out_store;
544 gf_o_t pc;
545 gf_i_t gc;
546 HANDLE_S *handles = NULL;
547 AUTH_CODE_S user_input;
549 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
550 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
551 goto try_wantto;
553 so_puts(in_store, "<HTML><BODY><P>");
554 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2->name);
555 so_puts(in_store, tmp);
556 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account with username <B>%s</B> using the %s method."), oauth2->name, mb->user, method),
557 so_puts(in_store, tmp);
559 if(strucmp((char *) oauth2->name, (char *) GMAIL_NAME) == 0 && strstr(url, (char *) GMAIL_ID) != NULL){
560 so_puts(in_store, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
561 so_puts(in_store, _("</P><P> First you must register Alpine with Google and create a client-id and client-secret.</P>"));
562 so_puts(in_store, _("<UL> "));
563 so_puts(in_store, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
564 so_puts(in_store, _("and create a project. The name of the project is not important."));
565 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."));
566 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."));
567 so_puts(in_store, _("<LI> Create OAUTH Credentials."));
568 so_puts(in_store, _("</UL> "));
569 so_puts(in_store, _("<P> As a result of this process, you will get a client-id and a client-secret."));
570 so_puts(in_store, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently,"));
571 so_puts(in_store, _(" then retry login into Gmail's server."));
572 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>."));
574 else{
575 so_puts(in_store, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
576 so_puts(in_store,"</P><P>");
577 sprintf(tmp_20k_buf, _("<A HREF=\"%s\">%s</A>"), url, url);
578 so_puts(in_store, tmp_20k_buf);
580 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
581 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2->name);
582 so_puts(in_store, tmp);
583 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
585 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. "));
586 so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page."));
587 so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
588 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. "));
590 cache_method_message(in_store);
593 so_puts(in_store, _("</P><P> If you do not wish to proceed, cancel by pressing 'E' to exit."));
594 so_puts(in_store, _("</P></BODY></HTML>"));
596 so_seek(in_store, 0L, 0);
597 init_handles(&handles);
598 gf_filter_init();
599 gf_link_filter(gf_html2plain,
600 gf_html2plain_opt(NULL,
601 ps_global->ttyo->screen_cols, non_messageview_margin(),
602 &handles, NULL, GFHP_LOCAL_HANDLES));
603 gf_set_so_readc(&gc, in_store);
604 gf_set_so_writec(&pc, out_store);
605 gf_pipe(gc, pc);
606 gf_clear_so_writec(out_store);
607 gf_clear_so_readc(in_store);
609 memset(&sargs, 0, sizeof(SCROLL_S));
610 sargs.text.handles = handles;
611 sargs.text.text = so_text(out_store);
612 sargs.text.src = CharStar;
613 sargs.text.desc = _("help text");
614 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
615 sargs.proc.tool = oauth2_auth_answer;
616 sargs.proc.data.p = (void *)&user_input;
617 sargs.keys.menu = &oauth2_auth_keymenu;
618 /* don't want to re-enter c-client */
619 sargs.quell_newmail = 1;
620 setbitmap(sargs.keys.bitmap);
621 sargs.help.text = h_oauth2_start;
622 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
624 do {
625 if(scrolltool(&sargs) == MC_NO)
626 ps_global->user_says_cancel = 1;
627 ps_global->mangled_screen = 1;
628 ps_global->painted_body_on_startup = 0;
629 ps_global->painted_footer_on_startup = 0;
630 } while (user_input.answer != 'e');
632 if(!struncmp(user_input.code, "http://", 7)
633 || !struncmp(user_input.code, "https://", 8)){
634 char *s, *t;
635 s = strstr(user_input.code, "code=");
636 if(s != NULL){
637 t = strchr(s, '&');
638 if(t) *t = '\0';
639 code = cpystr(s+5);
640 if(t) *t = '&';
643 else code = user_input.code ? cpystr(user_input.code) : NULL;
644 if(user_input.code) fs_give((void **) &user_input.code);
646 if(code == NULL) *tryanother = 1;
648 so_give(&in_store);
649 so_give(&out_store);
650 free_handles(&handles);
652 else{
653 int flags, rc, q_line;
654 /* TRANSLATORS: user needs to input an access code from the server */
655 char *accesscodelabel = _("Copy and Paste Access Code");
656 char prompt[MAILTMPLEN], token[MAILTMPLEN];
658 * If screen hasn't been initialized yet, use want_to.
660 try_wantto:
661 tmp_20k_buf[0] = '\0';
662 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
663 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2->name);
664 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
666 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
667 _("Alpine is attempting to log you into your %s account with username %s using the %s method."), oauth2->name, mb->user, method);
668 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
670 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
671 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
672 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
674 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
675 "%s\n\n", url);
676 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
678 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
679 _("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);
680 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
682 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
683 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
684 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
686 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
687 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
688 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
690 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
691 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
692 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
694 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
695 "%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. "));
696 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
698 cache_method_message_no_screen();
700 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
701 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
702 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
704 display_init_err(tmp_20k_buf, 0);
705 memset((void *)tmp, 0, sizeof(tmp));
706 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
707 tmp[sizeof(tmp)-1] = '\0';
709 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
710 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
711 flags = OE_APPEND_CURRENT;
712 sprintf(prompt, "%s: ", accesscodelabel);
713 do {
714 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
715 prompt, NULL, NO_HELP, &flags);
716 } while (rc != 0 && rc != 1);
717 if(!struncmp(token, "http://", 7)
718 || !struncmp(token, "https://", 8)){
719 char *s, *t;
720 s = strstr(token, "code=");
721 if(s != NULL){
722 t = strchr(s, '&');
723 if(t) *t = '\0';
724 code = cpystr(s+5);
725 if(t) *t = '&';
728 else code = token[0] ? cpystr(token) : NULL;
732 return code;
735 void mm_login_oauth2(NETMBX *, char *, char *, OAUTH2_S *, long int, char *, char *);
737 /* The purpose of this function is to report to c-client the values of the
738 * different tokens and codes so that c-client can try to log in the user
739 * to the server. This function DOES NOT attempt to get these values for
740 * the user. That is attempted in the c-client side (as best as it can be
741 * done given our circumstances: no http support, no javascript support,
742 * etc.). This is the best we can do:
744 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
745 * as best as we can. Unloaded means that there is no server known to
746 * connect, no access token, etc. Pretty much nothing is known about
747 * how to get access code, access token, etc. We ask the user to fill
748 * it up for us, if they can. If the user fills it up we save those
749 * values.
751 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
752 * save the information that c-client got for us.
754 * 3. When saving this information we use the password caching facilities,
755 * but we must do it in a different format so that old information and
756 * new information are not mixed. In order to accommodate this for new
757 * authentication methods, we save the information in the same fields,
758 * but this time we modify it slightly, so that old functions fail to
759 * understand the new information and so not modify it nor use it. The
760 * modification is simple: Every piece of information that was saved
761 * before is prepended XOAUTH2\001 to indicate the authentication
762 * method for which it works. The character \001 is a separator. New
763 * Alpine will know how to deal with this, but old versions, will not
764 * strip this prefix from the information and fail to get the
765 * information or modify it when needed. Only new versions of Alpine will
766 * know how to process this information.
767 * new_value = authenticator_method separator old_value
768 * authenticator_method = "XOAUTH2_S"
769 * separator = "U+1"
770 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
771 * authenticator is "XOAUTH2\001imap.gmail.com".
772 * In addition, the password field is not used to encode the password
773 * anymore, it is used to save login information needed in a format that
774 * the caller function chooses, but that must be preceded by the
775 * "authenticator_method separator" code as above.
777 void
778 mm_login_oauth2(NETMBX *mb, char *user, char *method,
779 OAUTH2_S *login, long int trial,
780 char *usethisprompt, char *altuserforcache)
782 char *token, tmp[MAILTMPLEN];
783 char prompt[4*MAILTMPLEN];
784 char *OldRefreshToken, *OldAccessToken;
785 char *NewRefreshToken, *NewAccessToken;
786 char *SaveRefreshToken, *SaveAccessToken;
787 /* TRANSLATORS: A label for the hostname that the user is logging in on */
788 char *hostlabel = _("HOST");
789 /* TRANSLATORS: user is logging in as a particular user (a particular
790 login name), this is just labelling that user name. */
791 char *userlabel = _("USER");
792 STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1];
793 int len, q_line, flags, i, j;
794 int save_in_init;
795 int registered;
796 int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
797 OAUTH2_S *oa2list, *oa2;
798 XOAUTH2_INFO_S *x;
799 unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
800 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
801 int preserve_password = -1;
802 #endif
804 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
805 trial, mb->user ? mb->user : "(null)",
806 mb->service ? mb->service : "(null)",
807 mb->port ? " port=" : "",
808 mb->port ? comatose(mb->port) : "",
809 altuserforcache ? " altuserforcache =" : "",
810 altuserforcache ? altuserforcache : ""));
812 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
814 save_in_init = ps_global->in_init_seq;
815 ps_global->in_init_seq = 0;
816 ps_global->no_newmail_check_from_optionally_enter = 1;
818 /* make sure errors are seen */
819 if(ps_global->ttyo && !ps_global->noshow_error
820 && login && (login->flags & OA2_OPENSTREAM))
821 flush_status_messages(0);
823 if(login && (login->flags & OA2_OPENSTREAM))
824 login->flags |= ~OA2_OPENSTREAM;
826 token = NULL; /* start from scratch */
828 hostlist[0].name = mb->host;
829 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
830 hostlist[0].next = &hostlist[1];
831 hostlist[1].name = mb->orighost;
832 hostlist[1].next = NULL;
834 else
835 hostlist[0].next = NULL;
837 if(hostlist[0].name){
838 dprint((9, "mm_login_oauth2: host=%s\n",
839 hostlist[0].name ? hostlist[0].name : "?"));
840 if(hostlist[0].next && hostlist[1].name){
841 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name));
845 if(trial == 0L && !altuserforcache){
846 if(*mb->user != '\0')
847 strncpy(user, mb->user, NETMAXUSER);
848 else{
849 flags = OE_APPEND_CURRENT;
850 sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
851 optionally_enter(user, q_line, 0, NETMAXUSER, prompt, NULL, NO_HELP, &flags);
853 user[NETMAXUSER-1] = '\0';
857 * We check to see if the server we are going to log in to is already
858 * registered. This gives us a list of servers with the same
859 * credentials, so we use the same credentials for all of them.
862 for(registered = 0, oa2list = alpine_oauth2_list;
863 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
864 oa2list++){
865 for(i = 0; i < OAUTH2_TOT_EQUIV
866 && oa2list->host[i] != NULL
867 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
868 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
869 registered++;
870 break;
874 if(registered){
875 x = oauth2_get_client_info(oa2list->name, user);
876 if(!x) return; /* user cancelled, let's get out of here */
877 if(x->flow){
878 int authorize = 0, device = 0;
879 for(oa2list = alpine_oauth2_list;
880 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
881 oa2list++){
882 for(i = 0; i < OAUTH2_TOT_EQUIV
883 && oa2list->host[i] != NULL
884 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
885 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
886 char *flow = oa2list->server_mthd[0].name ? "Authorize"
887 : (oa2list->server_mthd[1].name ? "Device" : NULL);
888 authorize += oa2list->server_mthd[0].name ? 1 : 0;
889 device += oa2list->server_mthd[1].name ? 1 : 0;
890 if(flow && !strucmp(x->flow, flow)) break; /* found it */
893 if(!oa2list || !oa2list->host || !oa2list->host[0]){
894 snprintf(tmp_20k_buf, SIZEOF_20KBUF,
895 _("%s does not support or recognize flow type \"%s\". Use %s%s%s"),
896 mb->orighost,
897 x->flow,
898 authorize ? "\"Authorize\"" : "",
899 authorize ? (device ? " or " : "") : "",
900 device ? "\"Device\"" : "");
901 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
902 q_status_message(SM_ORDER | SM_DING, 3, 5, tmp_20k_buf);
903 free_xoauth2_info(&x);
904 return;
907 free_xoauth2_info(&x);
910 if(registered){
911 hostlist2[i = 0].name = mb->host;
912 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
913 hostlist2[++i].name = mb->orighost;
915 for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){
916 int k;
917 for(k = 0; k <= i && hostlist2[k].name
918 && strcmp(hostlist2[k].name, oa2list->host[j]); k++);
919 if(k == i + 1)
920 hostlist2[++i].name = oa2list->host[j];
922 hostlist2[i+1].name = NULL;
923 hostlist2[i+1].next = NULL;
924 for(j = i; j >= 0; j--)
925 hostlist2[j].next = &hostlist2[j+1];
928 if(registered){ /* redo the app_id, no questions asked */
929 free_id(&ps_global->id);
930 ps_global->id = set_alpine_id(oa2list->app_id ? oa2list->app_id : PACKAGE_NAME, PACKAGE_VERSION);
931 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
934 if(registered)
935 oa2list->param[OA2_State].value = login->param[OA2_State].value;
937 if(login->cancel_refresh_token){
938 imap_delete_passwd_auth(&mm_login_list, user,
939 registered ? hostlist2 : hostlist,
940 (mb->sslflag||mb->tlsflag), OA2NAME);
941 #ifdef LOCAL_PASSWD_CACHE
942 write_passfile(ps_global->pinerc, mm_login_list);
943 #endif /* LOCAL_PASSWD_CACHE */
947 * We check if we have a refresh token saved somewhere, if so
948 * we use it to get a new access token, otherwise we need to
949 * get an access code so we can get (and save) a refresh token
950 * and use the access token.
952 if(trial == 0L && !altuserforcache){
953 /* Search for a refresh token that is already loaded ... */
954 if(imap_get_passwd_auth(mm_login_list, &token, user,
955 registered ? hostlist2 : hostlist,
956 (mb->sslflag||mb->tlsflag), OA2NAME)){
957 dprint((9, "mm_login_oauth2: found a refresh token\n"));
958 ps_global->no_newmail_check_from_optionally_enter = 0;
959 ps_global->in_init_seq = save_in_init;
961 #ifdef LOCAL_PASSWD_CACHE
962 /* or see if we have saved one in the local password cache and load it */
963 else if(get_passfile_passwd_auth(ps_global->pinerc, &token,
964 user, registered ? hostlist2 : hostlist,
965 (mb->sslflag||mb->tlsflag), OA2NAME)){
966 imap_set_passwd_auth(&mm_login_list, token, user,
967 hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
968 update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist,
969 (mb->sslflag||mb->tlsflag), OA2NAME);
970 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
971 ps_global->no_newmail_check_from_optionally_enter = 0;
972 ps_global->in_init_seq = save_in_init;
974 if(token && *token) preserve_password = 1; /* resave it, no need to ask */
975 #endif /* LOCAL_PASSWD_CACHE */
977 user[NETMAXUSER-1] = '\0';
979 /* The Old* variables is what c_client knows */
980 OldRefreshToken = login->cancel_refresh_token ? NULL : login->param[OA2_RefreshToken].value;
981 OldAccessToken = login->access_token;
982 OldExpirationTime = login->expiration;
984 /* The New* variables is what Alpine knows */
985 NewRefreshToken = NewAccessToken = NULL;
986 NewExpirationTime = 0L;
987 ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0;
989 /* We have done all steps that cancellation requires us by this time */
990 if(login->cancel_refresh_token)
991 login->cancel_refresh_token = 0;
993 if(token && *token){
994 char *s, *t;
996 s = token;
997 t = strchr(s, PWDAUTHSEP);
998 if(t == NULL)
999 NewRefreshToken = cpystr(s);
1000 else {
1001 *t++ = '\0';
1002 NewRefreshToken = cpystr(s);
1003 s = t;
1004 t = strchr(s, PWDAUTHSEP);
1005 if(t == NULL)
1006 NewAccessToken = cpystr(s);
1007 else {
1008 *t++ = '\0';
1009 NewAccessToken = cpystr(s);
1010 s = t;
1011 NewExpirationTime = strtol(s, &s, 10);
1012 if(NewExpirationTime <= 0 || NewExpirationTime <= time(0))
1013 NewExpirationTime = 0L;
1016 /* check we got good information, and send good information below */
1017 if(NewRefreshToken && !*NewRefreshToken)
1018 fs_give((void **) &NewRefreshToken);
1019 if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken))
1020 fs_give((void **) &NewAccessToken);
1023 if(NewRefreshToken == NULL)
1024 login->first_time++;
1026 if(login->first_time){ /* count how many authorization methods we support */
1027 int nmethods, j;
1029 for(nmethods = 0, oa2 = alpine_oauth2_list; oa2 && oa2->name ; oa2++){
1030 for(j = 0; j < OAUTH2_TOT_EQUIV
1031 && oa2
1032 && oa2->host[j] != NULL
1033 && strucmp(oa2->host[j], mb->orighost) != 0; j++);
1034 if(oa2 && oa2->host && j < OAUTH2_TOT_EQUIV && oa2->host[j]
1035 && ((oa2->server_mthd[0].name && (oa2->flags & OA2_AUTHORIZE))
1036 || (oa2->server_mthd[1].name && (oa2->flags & OA2_DEVICE))))
1037 nmethods++;
1040 if(nmethods > 1)
1041 oa2list = oauth2_select_flow(mb->orighost);
1043 if(!oa2list) registered = 0;
1046 /* Default to saving what we already had saved */
1048 SaveRefreshToken = NewRefreshToken;
1049 SaveAccessToken = NewAccessToken;
1050 SaveExpirationTime = NewExpirationTime;
1052 /* Translation of the logic below:
1053 * if (c-client has a refresh token
1054 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1055 forget the Alpine refresh token;
1056 make the Alpine Refresh token = c-client refresh token.;
1057 signal that we changed the refresh token;
1058 In this situation we do not need to clear up the Alpine access token. This will
1059 expire, so we can use it until it expires. We can save the c-client refresh token
1060 together with the Alpine Access Token and the expiration date of the Alpine Access
1061 Token.
1062 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1063 forget the Alpine refresh token;
1064 if Alpine has an access token, forget it;
1065 reset the expiration time
1066 signal that we changed the refresh token; (because the service expired it)
1070 if(OldRefreshToken != NULL
1071 && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){
1072 if(NewRefreshToken) fs_give((void **) &NewRefreshToken);
1073 NewRefreshToken = cpystr(OldRefreshToken);
1074 ChangeRefreshToken++;
1075 SaveRefreshToken = OldRefreshToken;
1076 SaveAccessToken = NewAccessToken;
1077 SaveExpirationTime = NewExpirationTime;
1078 } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){
1079 fs_give((void **) &NewRefreshToken);
1080 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1081 NewExpirationTime = 0L;
1082 ChangeRefreshToken++;
1083 SaveRefreshToken = NULL;
1084 SaveAccessToken = NULL;
1085 SaveExpirationTime = 0L;
1088 if(OldAccessToken != NULL
1089 && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){
1090 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1091 NewAccessToken = cpystr(OldAccessToken);
1092 NewAccessToken = OldAccessToken;
1093 ChangeAccessToken++;
1094 NewExpirationTime = OldExpirationTime;
1095 SaveRefreshToken = NewRefreshToken;
1096 SaveAccessToken = NewAccessToken;
1097 SaveExpirationTime = NewExpirationTime;
1100 if(!registered){
1101 login->param[OA2_RefreshToken].value = SaveRefreshToken;
1102 login->access_token = SaveAccessToken;
1103 login->expiration = SaveExpirationTime;
1104 } else {
1105 oa2list->param[OA2_RefreshToken].value = SaveRefreshToken;
1106 oa2list->access_token = SaveAccessToken;
1107 oa2list->expiration = SaveExpirationTime;
1108 oa2list->first_time = login->first_time;
1109 oa2list->cancel_refresh_token = login->cancel_refresh_token;
1110 *login = *oa2list; /* load login pointer */
1112 if(token) fs_give((void **) &token);
1114 if(!ChangeAccessToken && !ChangeRefreshToken)
1115 return;
1117 /* get ready to save this information. The format will be
1118 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1119 * (spaces added for clarity, \001 is PWDAUTHSEP)
1121 sprintf(tmp, "%lu", SaveExpirationTime);
1122 tmp[sizeof(tmp) - 1] = '\0';
1123 len = strlen(SaveRefreshToken ? SaveRefreshToken : "")
1124 + strlen(SaveAccessToken ? SaveAccessToken : "")
1125 + strlen(tmp) + 2;
1126 token = fs_get(len + 1);
1127 sprintf(token, "%s%c%s%c%lu",
1128 SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP,
1129 SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP,
1130 SaveExpirationTime);
1132 /* remember the access information for next time */
1133 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
1134 imap_set_passwd_auth(&mm_login_list, token,
1135 altuserforcache ? altuserforcache : user, hostlist,
1136 (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1137 #ifdef LOCAL_PASSWD_CACHE
1138 /* if requested, remember it on disk for next session */
1139 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
1140 set_passfile_passwd_auth(ps_global->pinerc, &token,
1141 altuserforcache ? altuserforcache : user, hostlist,
1142 (mb->sslflag||mb->tlsflag),
1143 (preserve_password == -1 ? 0
1144 : (preserve_password == 0 ? 2 :1)), OA2NAME);
1145 #endif /* LOCAL_PASSWD_CACHE */
1146 if (token) fs_give((void **) &token);
1147 ps_global->no_newmail_check_from_optionally_enter = 0;
1150 IDLIST *
1151 set_alpine_id(char *pname, char *pversion)
1153 IDLIST *id;
1155 if(!pname || !pversion) return NULL;
1157 id = fs_get(sizeof(IDLIST));
1158 id->name = cpystr("name");
1159 id->value = cpystr(pname);
1160 id->next = fs_get(sizeof(IDLIST));
1161 id->next->name = cpystr("version");
1162 id->next->value = cpystr(pversion);
1163 id->next->next = NULL;
1164 return id;
1167 void
1168 pine_delete_pwd(NETMBX *mb, char *user)
1170 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
1171 char port[20], non_def_port[20];
1172 STRLIST_S hostlist[2];
1173 MMLOGIN_S *l;
1174 struct servent *sv;
1175 /* do not invalidate password on cancel */
1176 if(ps_global->user_says_cancel != 0)
1177 return;
1179 dprint((9, "pine_delete_pwd\n"));
1181 /* setup hostlist */
1182 non_def_port[0] = '\0';
1183 if(mb->port && mb->service &&
1184 (sv = getservbyname(mb->service, "tcp")) &&
1185 (mb->port != ntohs(sv->s_port))){
1186 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
1187 non_def_port[sizeof(non_def_port)-1] = '\0';
1188 dprint((9, "mm_login: using non-default port=%s\n",
1189 non_def_port ? non_def_port : "?"));
1192 if(*non_def_port){
1193 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
1194 hostlist0[sizeof(hostlist0)-1] = '\0';
1195 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
1196 hostlist0[sizeof(hostlist0)-1] = '\0';
1197 hostlist[0].name = hostlist0;
1198 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1199 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
1200 hostlist1[sizeof(hostlist1)-1] = '\0';
1201 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
1202 hostlist1[sizeof(hostlist1)-1] = '\0';
1203 hostlist[0].next = &hostlist[1];
1204 hostlist[1].name = hostlist1;
1205 hostlist[1].next = NULL;
1207 else
1208 hostlist[0].next = NULL;
1210 else{
1211 hostlist[0].name = mb->host;
1212 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1213 hostlist[0].next = &hostlist[1];
1214 hostlist[1].name = mb->orighost;
1215 hostlist[1].next = NULL;
1217 else
1218 hostlist[0].next = NULL;
1220 imap_delete_passwd(&mm_login_list, user, hostlist, mb->sslflag||mb->tlsflag);
1221 #ifdef LOCAL_PASSWD_CACHE
1222 write_passfile(ps_global->pinerc, mm_login_list);
1223 #endif /* LOCAL_PASSWD_CACHE */
1226 /*----------------------------------------------------------------------
1227 receive notification from IMAP
1229 Args: stream -- Mail stream message is relevant to
1230 string -- The message text
1231 errflg -- Set if it is a serious error
1233 Result: message displayed in status line
1235 The facility is for general notices, such as connection to server;
1236 server shutting down etc... It is used infrequently.
1237 ----------------------------------------------------------------------*/
1238 void
1239 mm_notify(MAILSTREAM *stream, char *string, long int errflg)
1241 time_t now;
1242 struct tm *tm_now;
1244 now = time((time_t *)0);
1245 tm_now = localtime(&now);
1247 /* be sure to log the message... */
1248 #ifdef DEBUG
1249 if(ps_global->debug_imap || ps_global->debugmem)
1250 dprint((errflg == TCPDEBUG || errflg == HTTPDEBUG ? 7 : 2,
1251 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1252 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1253 tm_now->tm_mon+1, tm_now->tm_mday,
1254 (!errflg) ? "babble" :
1255 (errflg == ERROR) ? "error" :
1256 (errflg == WARN) ? "warning" :
1257 (errflg == PARSE) ? "parse" :
1258 (errflg == TCPDEBUG) ? "tcp" :
1259 (errflg == HTTPDEBUG) ? "http" :
1260 (errflg == BYE) ? "bye" : "unknown",
1261 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1262 string ? string : "?"));
1263 #endif
1265 snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s",
1266 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1267 (int) MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70),
1268 string);
1269 ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols
1270 : sizeof(ps_global->last_error)-1] = '\0';
1273 * Then either set special bits in the pine struct or
1274 * display the message if it's tagged as an "ALERT" or
1275 * its errflg > NIL (i.e., WARN, or ERROR)
1277 if(errflg == BYE)
1279 * We'd like to sp_mark_stream_dead() here but we can't do that because
1280 * that might call mail_close and we are already in a c-client callback.
1281 * So just set the dead bit and clean it up later.
1283 sp_set_dead_stream(stream, 1);
1284 else if(!strncmp(string, "[TRYCREATE]", 11))
1285 ps_global->try_to_create = 1;
1286 else if(!strncmp(string, "[REFERRAL ", 10))
1287 ; /* handled in the imap_referral() callback */
1288 else if(!strncmp(string, "[ALERT]", 7))
1289 q_status_message2(SM_MODAL, 3, 3,
1290 _("Alert received while accessing \"%s\": %s"),
1291 (stream && stream->mailbox)
1292 ? stream->mailbox : "-no folder-",
1293 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000),
1294 SIZEOF_20KBUF-10000, string));
1295 else if(!strncmp(string, "[UNSEEN ", 8)){
1296 char *p;
1297 long n = 0;
1299 for(p = string + 8; isdigit(*p); p++)
1300 n = (n * 10) + (*p - '0');
1302 sp_set_first_unseen(stream, n);
1304 else if(!strncmp(string, "[READ-ONLY]", 11)
1305 && !(stream && stream->mailbox && IS_NEWS(stream)))
1306 q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s",
1307 (stream && stream->mailbox)
1308 ? stream->mailbox : "-no folder-",
1309 string + 11);
1310 else if((errflg && errflg != BYE && errflg != PARSE)
1311 && !ps_global->noshow_error
1312 && !(errflg == WARN
1313 && (ps_global->noshow_warn || (stream && stream->unhealthy))))
1314 q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0),
1315 3, 6, ps_global->last_error);
1319 /*----------------------------------------------------------------------
1320 Queue imap log message for display in the message line
1322 Args: string -- The message
1323 errflg -- flag set to 1 if pertains to an error
1325 Result: Message queued for display
1327 The c-client/imap reports most of it's status and errors here
1328 ---*/
1329 void
1330 mm_log(char *string, long int errflg)
1332 char message[sizeof(ps_global->c_client_error)];
1333 char *occurence;
1334 int was_capitalized;
1335 static char saw_kerberos_init_warning;
1336 time_t now;
1337 struct tm *tm_now;
1339 now = time((time_t *)0);
1340 tm_now = localtime(&now);
1342 dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? debug :
1343 (errflg == TCPDEBUG) ? 10 :
1344 ((errflg == HTTPDEBUG) && ps_global->debug_http) ? debug :
1345 (errflg == HTTPDEBUG) ? 10 : 2,
1346 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1347 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1348 tm_now->tm_mon+1, tm_now->tm_mday,
1349 (!errflg) ? "babble" :
1350 (errflg == ERROR) ? "error" :
1351 (errflg == WARN) ? "warning" :
1352 (errflg == PARSE) ? "parse" :
1353 (errflg == TCPDEBUG) ? "tcp" :
1354 (errflg == HTTPDEBUG) ? "http" :
1355 (errflg == BYE) ? "bye" : "unknown",
1356 string ? string : "?"));
1358 if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){
1359 ps_global->try_to_create = 1;
1360 return;
1362 else if(ps_global->try_to_create
1363 || !strncmp(string, "[CLOSED]", 8)
1364 || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op")))
1366 * Don't display if creating new folder OR
1367 * warning about a dead stream ...
1369 return;
1371 /* if we took too long to authenticate, ignore this error */
1372 if(ps_global->in_xoauth2_auth && strstr(string, "[CLOSED]"))
1373 return;
1375 strncpy(message, string, sizeof(message));
1376 message[sizeof(message) - 1] = '\0';
1378 if(errflg == WARN && srchstr(message, "try running kinit") != NULL){
1379 if(saw_kerberos_init_warning)
1380 return;
1382 saw_kerberos_init_warning = 1;
1385 /*---- replace all "mailbox" with "folder" ------*/
1386 occurence = srchstr(message, "mailbox");
1387 while(occurence) {
1388 if(!*(occurence+7)
1389 || isspace((unsigned char) *(occurence+7))
1390 || *(occurence+7) == ':'){
1391 was_capitalized = isupper((unsigned char) *occurence);
1392 rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder"));
1393 if(was_capitalized)
1394 *occurence = (errflg == PARSE ? 'A' : 'F');
1396 else
1397 occurence += 7;
1399 occurence = srchstr(occurence, "mailbox");
1402 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1403 occurence = srchstr(message, "GSSAPI");
1404 while(occurence) {
1405 if(!*(occurence+6)
1406 || isspace((unsigned char) *(occurence+6))
1407 || *(occurence+6) == ':')
1408 rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos");
1409 else
1410 occurence += 6;
1412 occurence = srchstr(occurence, "GSSAPI");
1415 if(errflg == ERROR)
1416 ps_global->mm_log_error = 1;
1418 if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error))
1419 strncpy(ps_global->c_client_error, message,
1420 sizeof(ps_global->c_client_error));
1422 if(ps_global->noshow_error
1423 || (ps_global->noshow_warn && errflg == WARN)
1424 || !(errflg == ERROR || errflg == WARN))
1425 return; /* Only care about errors; don't print when asked not to */
1427 /*---- Display the message ------*/
1428 q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER,
1429 3, 5, message);
1430 strncpy(ps_global->last_error, message, sizeof(ps_global->last_error));
1431 ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
1434 void
1435 mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial,
1436 char *method, char *usethisprompt, char *altuserforcache)
1438 if(method == NULL)
1439 return;
1440 if(strucmp(method, OA2NAME) == 0 || strucmp(method, BEARERNAME) == 0)
1441 mm_login_oauth2(mb, user, method, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache);
1444 void
1445 mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial,
1446 char *usethisprompt, char *altuserforcache)
1448 char tmp[MAILTMPLEN];
1449 char prompt[1000], *last;
1450 char port[20], non_def_port[20], insecure[20];
1451 char defuser[NETMAXUSER];
1452 char hostleadin[80], hostname[200], defubuf[200];
1453 char logleadin[80], pwleadin[50];
1454 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
1455 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1456 that the password will be sent unencrypted over the network. This is
1457 just a warning message that gets added parenthetically when the user
1458 is asked for a password. */
1459 char *insec = _(" (INSECURE)");
1460 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1461 after having already failed at least once. */
1462 char *retry = _("Retrying - ");
1463 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1464 char *hostlabel = _("HOST");
1465 /* TRANSLATORS: user is logging in as a particular user (a particular
1466 login name), this is just labelling that user name. */
1467 char *userlabel = _("USER");
1468 STRLIST_S hostlist[2];
1469 HelpType help ;
1470 int len, rc, q_line, flags;
1471 int oespace, avail, need, save_dont_use;
1472 int save_in_init;
1473 struct servent *sv;
1474 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1475 int preserve_password = -1;
1476 #endif
1478 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1479 trial, mb->user ? mb->user : "(null)",
1480 mb->service ? mb->service : "(null)",
1481 mb->port ? " port=" : "",
1482 mb->port ? comatose(mb->port) : "",
1483 altuserforcache ? " altuserforcache =" : "",
1484 altuserforcache ? altuserforcache : ""));
1485 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
1487 save_in_init = ps_global->in_init_seq;
1488 ps_global->in_init_seq = 0;
1489 ps_global->no_newmail_check_from_optionally_enter = 1;
1491 /* make sure errors are seen */
1492 if(ps_global->ttyo)
1493 flush_status_messages(0);
1495 /* redo app id in case we are logging in to an IMAP server that supports the IMAP ID extension */
1496 free_id(&ps_global->id);
1497 ps_global->id = set_alpine_id(PACKAGE_NAME, PACKAGE_VERSION);
1498 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
1501 * Add port number to hostname if going through a tunnel or something
1503 non_def_port[0] = '\0';
1504 if(mb->port && mb->service &&
1505 (sv = getservbyname(mb->service, "tcp")) &&
1506 (mb->port != ntohs(sv->s_port))){
1507 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
1508 non_def_port[sizeof(non_def_port)-1] = '\0';
1509 dprint((9, "mm_login: using non-default port=%s\n", non_def_port));
1513 * set up host list for sybil servers...
1515 if(*non_def_port){
1516 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
1517 hostlist0[sizeof(hostlist0)-1] = '\0';
1518 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
1519 hostlist0[sizeof(hostlist0)-1] = '\0';
1520 hostlist[0].name = hostlist0;
1521 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1522 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
1523 hostlist1[sizeof(hostlist1)-1] = '\0';
1524 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
1525 hostlist1[sizeof(hostlist1)-1] = '\0';
1526 hostlist[0].next = &hostlist[1];
1527 hostlist[1].name = hostlist1;
1528 hostlist[1].next = NULL;
1530 else
1531 hostlist[0].next = NULL;
1533 else{
1534 hostlist[0].name = mb->host;
1535 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1536 hostlist[0].next = &hostlist[1];
1537 hostlist[1].name = mb->orighost;
1538 hostlist[1].next = NULL;
1540 else
1541 hostlist[0].next = NULL;
1544 if(hostlist[0].name){
1545 dprint((9, "mm_login: host=%s\n",
1546 hostlist[0].name ? hostlist[0].name : "?"));
1547 if(hostlist[0].next && hostlist[1].name){
1548 dprint((9, "mm_login: orighost=%s\n", hostlist[1].name));
1553 * Initialize user name with either
1554 * 1) /user= value in the stream being logged into,
1555 * or 2) the user name we're running under.
1557 * Note that VAR_USER_ID is not yet initialized if this login is
1558 * the one to access the remote config file. In that case, the user
1559 * can supply the username in the config file name with /user=.
1561 if(trial == 0L && !altuserforcache){
1562 strncpy(user, (*mb->user) ? mb->user :
1563 ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "",
1564 NETMAXUSER);
1565 user[NETMAXUSER-1] = '\0';
1567 /* try last working password associated with this host. */
1568 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1569 (mb->sslflag||mb->tlsflag))){
1570 dprint((9, "mm_login: found a password to try\n"));
1571 ps_global->no_newmail_check_from_optionally_enter = 0;
1572 ps_global->in_init_seq = save_in_init;
1573 return;
1576 #ifdef LOCAL_PASSWD_CACHE
1577 /* check to see if there's a password left over from last session */
1578 if(get_passfile_passwd(ps_global->pinerc, pwd,
1579 user, hostlist, (mb->sslflag||mb->tlsflag))){
1580 imap_set_passwd(&mm_login_list, *pwd, user,
1581 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1582 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1583 (mb->sslflag||mb->tlsflag));
1584 dprint((9, "mm_login: found a password in passfile to try\n"));
1585 ps_global->no_newmail_check_from_optionally_enter = 0;
1586 ps_global->in_init_seq = save_in_init;
1587 return;
1589 #endif /* LOCAL_PASSWD_CACHE */
1592 * If no explicit user name supplied and we've not logged in
1593 * with our local user name, see if we've visited this
1594 * host before as someone else.
1596 if(!*mb->user &&
1597 ((last = imap_get_user(mm_login_list, hostlist))
1598 #ifdef LOCAL_PASSWD_CACHE
1600 (last = get_passfile_user(ps_global->pinerc, hostlist))
1601 #endif /* LOCAL_PASSWD_CACHE */
1603 strncpy(user, last, NETMAXUSER);
1604 user[NETMAXUSER-1] = '\0';
1605 dprint((9, "mm_login: found user=%s\n",
1606 user ? user : "?"));
1608 /* try last working password associated with this host/user. */
1609 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1610 (mb->sslflag||mb->tlsflag))){
1611 dprint((9,
1612 "mm_login: found a password for user=%s to try\n",
1613 user ? user : "?"));
1614 ps_global->no_newmail_check_from_optionally_enter = 0;
1615 ps_global->in_init_seq = save_in_init;
1616 return;
1619 #ifdef LOCAL_PASSWD_CACHE
1620 /* check to see if there's a password left over from last session */
1621 if(get_passfile_passwd(ps_global->pinerc, pwd,
1622 user, hostlist, (mb->sslflag||mb->tlsflag))){
1623 imap_set_passwd(&mm_login_list, *pwd, user,
1624 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1625 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1626 (mb->sslflag||mb->tlsflag));
1627 dprint((9,
1628 "mm_login: found a password for user=%s in passfile to try\n",
1629 user ? user : "?"));
1630 ps_global->no_newmail_check_from_optionally_enter = 0;
1631 ps_global->in_init_seq = save_in_init;
1632 return;
1634 #endif /* LOCAL_PASSWD_CACHE */
1637 #if !defined(DOS) && !defined(OS2)
1638 if(!*mb->user && !*user &&
1639 (last = (ps_global->ui.login && ps_global->ui.login[0])
1640 ? ps_global->ui.login : NULL)
1642 strncpy(user, last, NETMAXUSER);
1643 user[NETMAXUSER-1] = '\0';
1644 dprint((9, "mm_login: found user=%s\n",
1645 user ? user : "?"));
1647 /* try last working password associated with this host. */
1648 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1649 (mb->sslflag||mb->tlsflag))){
1650 dprint((9, "mm_login:ui: found a password to try\n"));
1651 ps_global->no_newmail_check_from_optionally_enter = 0;
1652 ps_global->in_init_seq = save_in_init;
1653 return;
1656 #ifdef LOCAL_PASSWD_CACHE
1657 /* check to see if there's a password left over from last session */
1658 if(get_passfile_passwd(ps_global->pinerc, pwd,
1659 user, hostlist, (mb->sslflag||mb->tlsflag))){
1660 imap_set_passwd(&mm_login_list, *pwd, user,
1661 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1662 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1663 (mb->sslflag||mb->tlsflag));
1664 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1665 ps_global->no_newmail_check_from_optionally_enter = 0;
1666 ps_global->in_init_seq = save_in_init;
1667 return;
1669 #endif /* LOCAL_PASSWD_CACHE */
1671 #endif
1674 user[NETMAXUSER-1] = '\0';
1676 if(trial == 0)
1677 retry = "";
1680 * Even if we have a user now, user gets a chance to change it.
1682 ps_global->mangled_footer = 1;
1683 if(!*mb->user && !altuserforcache){
1685 help = NO_HELP;
1688 * Instead of offering user with a value that the user can edit,
1689 * we offer [user] as a default so that the user can type CR to
1690 * use it. Otherwise, the user has to type in whole name.
1692 strncpy(defuser, user, sizeof(defuser)-1);
1693 defuser[sizeof(defuser)-1] = '\0';
1694 user[0] = '\0';
1697 * Need space for "Retrying - "
1698 * "+ HOST: "
1699 * hostname
1700 * " (INSECURE)"
1701 * ENTER LOGIN NAME
1702 * " [defuser] : "
1703 * about 15 chars for input
1706 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1707 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1708 hostleadin[sizeof(hostleadin)-1] = '\0';
1710 strncpy(hostname, mb->host, sizeof(hostname)-1);
1711 hostname[sizeof(hostname)-1] = '\0';
1714 * Add port number to hostname if going through a tunnel or something
1716 if(*non_def_port)
1717 strncpy(port, non_def_port, sizeof(port));
1718 else
1719 port[0] = '\0';
1721 insecure[0] = '\0';
1722 /* if not encrypted and SSL/TLS is supported */
1723 if(!(mb->sslflag||mb->tlsflag) &&
1724 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1725 strncpy(insecure, insec, sizeof(insecure));
1727 /* TRANSLATORS: user is being asked to type in their login name */
1728 snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME"));
1730 snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "",
1731 (*defuser) ? defuser : "",
1732 (*defuser) ? "]" : "");
1733 defubuf[sizeof(defubuf)-1] = '\0';
1734 /* space reserved after prompt */
1735 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1737 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1738 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1739 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace;
1741 /* If we're retrying cut the hostname back to the first word. */
1742 if(avail < need && trial > 0){
1743 char *p;
1745 len = strlen(hostname);
1746 if((p = strchr(hostname, '.')) != NULL){
1747 *p = '\0';
1748 need -= (len - strlen(hostname));
1752 if(avail < need){
1753 need -= utf8_width(retry);
1754 retry = "";
1756 if(avail < need){
1758 /* reduce length of logleadin */
1759 len = utf8_width(logleadin);
1760 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1761 longer version doesn't fit on screen */
1762 snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN"));
1763 need -= (len - utf8_width(logleadin));
1765 if(avail < need){
1766 /* get two spaces from hostleadin */
1767 len = utf8_width(hostleadin);
1768 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1769 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1770 hostleadin[sizeof(hostleadin)-1] = '\0';
1771 need -= (len - utf8_width(hostleadin));
1773 /* get rid of port */
1774 if(avail < need && strlen(port) > 0){
1775 need -= strlen(port);
1776 port[0] = '\0';
1779 if(avail < need){
1780 int reduce_to;
1783 * Reduce space for hostname. Best we can do is 6 chars
1784 * with hos...
1786 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1787 len = strlen(hostname);
1788 strncpy(hostname+reduce_to-3, "...", 4);
1789 need -= (len - strlen(hostname));
1791 if(avail < need && strlen(insecure) > 0){
1792 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1793 need -= 3;
1794 insecure[strlen(insecure)-4] = ')';
1795 insecure[strlen(insecure)-3] = '\0';
1797 else{
1798 need -= utf8_width(insecure);
1799 insecure[0] = '\0';
1803 if(avail < need){
1804 if(strlen(defubuf) > 3){
1805 len = strlen(defubuf);
1806 strncpy(defubuf, " [..] :", 9);
1807 need -= (len - strlen(defubuf));
1810 if(avail < need)
1811 strncpy(defubuf, ":", 2);
1814 * If it still doesn't fit, optionally_enter gets
1815 * to worry about it.
1823 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s",
1824 retry, hostleadin, hostname, port, insecure, logleadin, defubuf);
1825 prompt[sizeof(prompt)-1] = '\0';
1827 while(1) {
1828 if(ps_global->ttyo)
1829 mm_login_alt_cue(mb);
1831 flags = OE_APPEND_CURRENT;
1832 save_dont_use = ps_global->dont_use_init_cmds;
1833 ps_global->dont_use_init_cmds = 1;
1834 #ifdef _WINDOWS
1835 if(!*user && *defuser){
1836 strncpy(user, defuser, NETMAXUSER);
1837 user[NETMAXUSER-1] = '\0';
1840 rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD,
1841 #ifdef LOCAL_PASSWD_CACHE
1842 is_using_passfile() ? 1 :
1843 #endif /* LOCAL_PASSWD_CACHE */
1844 0, 0, &preserve_password);
1845 ps_global->dont_use_init_cmds = save_dont_use;
1846 if(rc == 0 && *user && *pwd && **pwd)
1847 goto nopwpmt;
1848 #else /* !_WINDOWS */
1849 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
1850 prompt, NULL, help, &flags);
1851 #endif /* !_WINDOWS */
1852 ps_global->dont_use_init_cmds = save_dont_use;
1854 if(rc == 3) {
1855 help = help == NO_HELP ? h_oe_login : NO_HELP;
1856 continue;
1859 /* default */
1860 if(rc == 0 && !*user){
1861 strncpy(user, defuser, NETMAXUSER);
1862 user[NETMAXUSER-1] = '\0';
1865 if(rc != 4)
1866 break;
1869 if(rc == 1 || !user[0]) {
1870 ps_global->user_says_cancel = (rc == 1);
1871 user[0] = '\0';
1874 else{
1875 strncpy(user, mb->user, NETMAXUSER);
1876 user[NETMAXUSER-1] = '\0';
1879 user[NETMAXUSER-1] = '\0';
1881 if(!(user[0] || altuserforcache)){
1882 ps_global->no_newmail_check_from_optionally_enter = 0;
1883 ps_global->in_init_seq = save_in_init;
1884 return;
1888 * Now that we have a user, we can check in the cache again to see
1889 * if there is a password there. Try last working password associated
1890 * with this host and user.
1892 if(trial == 0L && !*mb->user && !altuserforcache){
1893 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1894 (mb->sslflag||mb->tlsflag))){
1895 ps_global->no_newmail_check_from_optionally_enter = 0;
1896 ps_global->in_init_seq = save_in_init;
1897 return;
1900 #ifdef LOCAL_PASSWD_CACHE
1901 if(get_passfile_passwd(ps_global->pinerc, pwd,
1902 user, hostlist, (mb->sslflag||mb->tlsflag))){
1903 imap_set_passwd(&mm_login_list, *pwd, user,
1904 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1905 ps_global->no_newmail_check_from_optionally_enter = 0;
1906 ps_global->in_init_seq = save_in_init;
1907 return;
1909 #endif /* LOCAL_PASSWD_CACHE */
1911 else if(trial == 0 && altuserforcache){
1912 if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist,
1913 (mb->sslflag||mb->tlsflag))){
1914 ps_global->no_newmail_check_from_optionally_enter = 0;
1915 ps_global->in_init_seq = save_in_init;
1916 return;
1919 #ifdef LOCAL_PASSWD_CACHE
1920 if(get_passfile_passwd(ps_global->pinerc, pwd,
1921 altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
1922 imap_set_passwd(&mm_login_list, *pwd, altuserforcache,
1923 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1924 ps_global->no_newmail_check_from_optionally_enter = 0;
1925 ps_global->in_init_seq = save_in_init;
1926 return;
1928 #endif /* LOCAL_PASSWD_CACHE */
1932 * Didn't find password in cache or this isn't the first try. Ask user.
1934 help = NO_HELP;
1937 * Need space for "Retrying - "
1938 * "+ HOST: "
1939 * hostname
1940 * " (INSECURE) "
1941 * " USER: "
1942 * user
1943 * " ENTER PASSWORD: "
1944 * about 15 chars for input
1947 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1948 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1950 strncpy(hostname, mb->host, sizeof(hostname)-1);
1951 hostname[sizeof(hostname)-1] = '\0';
1954 * Add port number to hostname if going through a tunnel or something
1956 if(*non_def_port)
1957 strncpy(port, non_def_port, sizeof(port));
1958 else
1959 port[0] = '\0';
1961 insecure[0] = '\0';
1963 /* if not encrypted and SSL/TLS is supported */
1964 if(!(mb->sslflag||mb->tlsflag) &&
1965 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1966 strncpy(insecure, insec, sizeof(insecure));
1968 if(usethisprompt){
1969 strncpy(logleadin, usethisprompt, sizeof(logleadin));
1970 logleadin[sizeof(logleadin)-1] = '\0';
1971 defubuf[0] = '\0';
1972 user[0] = '\0';
1974 else{
1975 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1977 strncpy(defubuf, user, sizeof(defubuf)-1);
1978 defubuf[sizeof(defubuf)-1] = '\0';
1981 /* TRANSLATORS: user is being asked to type in their password */
1982 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD"));
1984 /* space reserved after prompt */
1985 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1987 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1988 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1989 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) +
1990 utf8_width(pwleadin) + oespace;
1992 if(avail < need && trial > 0){
1993 char *p;
1995 len = strlen(hostname);
1996 if((p = strchr(hostname, '.')) != NULL){
1997 *p = '\0';
1998 need -= (len - strlen(hostname));
2002 if(avail < need){
2003 need -= utf8_width(retry);
2004 retry = "";
2006 if(avail < need){
2008 if(!usethisprompt){
2009 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
2010 need--;
2013 rplstr(pwleadin, sizeof(pwleadin), 1, "");
2014 need--;
2016 if(avail < need){
2017 /* get two spaces from hostleadin */
2018 len = utf8_width(hostleadin);
2019 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
2020 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
2021 hostleadin[sizeof(hostleadin)-1] = '\0';
2022 need -= (len - utf8_width(hostleadin));
2024 /* get rid of port */
2025 if(avail < need && strlen(port) > 0){
2026 need -= strlen(port);
2027 port[0] = '\0';
2030 if(avail < need){
2031 len = utf8_width(pwleadin);
2032 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
2033 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD"));
2034 need -= (len - utf8_width(pwleadin));
2038 if(avail < need){
2039 int reduce_to;
2042 * Reduce space for hostname. Best we can do is 6 chars
2043 * with hos...
2045 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
2046 len = strlen(hostname);
2047 strncpy(hostname+reduce_to-3, "...", 4);
2048 need -= (len - strlen(hostname));
2050 if(avail < need && strlen(insecure) > 0){
2051 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
2052 need -= 3;
2053 insecure[strlen(insecure)-4] = ')';
2054 insecure[strlen(insecure)-3] = '\0';
2056 else{
2057 need -= utf8_width(insecure);
2058 insecure[0] = '\0';
2062 if(avail < need){
2063 len = utf8_width(logleadin);
2064 strncpy(logleadin, " ", sizeof(logleadin));
2065 logleadin[sizeof(logleadin)-1] = '\0';
2066 need -= (len - utf8_width(logleadin));
2068 if(avail < need){
2069 reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0;
2070 if(reduce_to)
2071 strncpy(defubuf+reduce_to-3, "...", 4);
2072 else
2073 defubuf[0] = '\0';
2080 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s",
2081 retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
2082 prompt[sizeof(prompt)-1] = '\0';
2084 tmp[0] = '\0';
2085 while(1) {
2086 if(ps_global->ttyo)
2087 mm_login_alt_cue(mb);
2089 save_dont_use = ps_global->dont_use_init_cmds;
2090 ps_global->dont_use_init_cmds = 1;
2091 flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
2092 flags |= OE_KEEP_TRAILING_SPACE;
2093 #ifdef _WINDOWS
2095 char *tmpp;
2096 tmpp = fs_get(NETMAXPASSWD*sizeof(char));
2097 rc = os_login_dialog(mb, user, NETMAXUSER, &tmpp, NETMAXPASSWD, 0, 1,
2098 &preserve_password);
2099 strncpy(tmp, tmpp, sizeof(tmp));
2100 tmp[sizeof(tmp)-1] = '\0';
2101 if(tmpp) fs_give((void **)&tmpp);
2103 #else /* !_WINDOWS */
2104 rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD,
2105 prompt, NULL, help, &flags);
2106 #endif /* !_WINDOWS */
2107 if(rc != 1) *pwd = cpystr(tmp);
2108 ps_global->dont_use_init_cmds = save_dont_use;
2110 if(rc == 3) {
2111 help = help == NO_HELP ? h_oe_passwd : NO_HELP;
2113 else if(rc == 4){
2115 else
2116 break;
2119 if(rc == 1 || !tmp[0]) {
2120 ps_global->user_says_cancel = (rc == 1);
2121 user[0] = '\0';
2122 ps_global->no_newmail_check_from_optionally_enter = 0;
2123 ps_global->in_init_seq = save_in_init;
2124 return;
2127 #ifdef _WINDOWS
2128 nopwpmt:
2129 #endif
2130 /* remember the password for next time */
2131 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
2132 imap_set_passwd(&mm_login_list, *pwd,
2133 altuserforcache ? altuserforcache : user, hostlist,
2134 (mb->sslflag||mb->tlsflag), 0, 0);
2135 #ifdef LOCAL_PASSWD_CACHE
2136 /* if requested, remember it on disk for next session */
2137 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
2138 set_passfile_passwd(ps_global->pinerc, pwd,
2139 altuserforcache ? altuserforcache : user, hostlist,
2140 (mb->sslflag||mb->tlsflag),
2141 (preserve_password == -1 ? 0
2142 : (preserve_password == 0 ? 2 :1)));
2143 #endif /* LOCAL_PASSWD_CACHE */
2145 ps_global->no_newmail_check_from_optionally_enter = 0;
2149 void
2150 mm_login_alt_cue(NETMBX *mb)
2152 if(ps_global->ttyo){
2153 COLOR_PAIR *lastc;
2155 lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
2156 ps_global->VAR_TITLE_BACK_COLOR,
2157 PSC_REV | PSC_RET);
2159 mark_titlebar_dirty();
2160 PutLine0(0, ps_global->ttyo->screen_cols - 1,
2161 (mb->sslflag||mb->tlsflag) ? "+" : " ");
2163 if(lastc){
2164 (void)pico_set_colorp(lastc, PSC_NONE);
2165 free_color_pair(&lastc);
2168 fflush(stdout);
2173 /*----------------------------------------------------------------------
2174 Receive notification of an error writing to disk
2176 Args: stream -- The stream the error occurred on
2177 errcode -- The system error code (errno)
2178 serious -- Flag indicating error is serious (mail may be lost)
2180 Result: If error is non serious, the stream is marked as having an error
2181 and deletes are disallowed until error clears
2182 If error is serious this goes modal, allowing the user to retry
2183 or get a shell escape to fix the condition. When the condition is
2184 serious it means that mail existing in the mailbox will be lost
2185 if Pine exits without writing, so we try to induce the user to
2186 fix the error, go get someone that can fix the error, or whatever
2187 and don't provide an easy way out.
2188 ----*/
2189 long
2190 mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious)
2192 int i, j;
2193 char *p, *q, *s;
2194 static ESCKEY_S de_opts[] = {
2195 {'r', 'r', "R", "Retry"},
2196 {'f', 'f', "F", "FileBrowser"},
2197 {'s', 's', "S", "ShellPrompt"},
2198 {-1, 0, NULL, NULL}
2200 #define DE_COLS (ps_global->ttyo->screen_cols)
2201 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2203 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2204 #define DE_PMT \
2205 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2206 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2207 #define DE_STR2 \
2208 "The reported error number is %s. The last reported mail error was:"
2209 static char *de_msg[] = {
2210 "Please try to correct the error preventing Alpine from saving your",
2211 "mail folder. For example if the disk is out of space try removing",
2212 "unneeded files. You might also contact your system administrator.",
2214 "Both Alpine's File Browser and an option to enter the system's",
2215 "command prompt are offered to aid in fixing the problem. When",
2216 "you believe the problem is resolved, choose the \"Retry\" option.",
2217 "Be aware that messages may be lost or this folder left in an",
2218 "inaccessible condition if you exit or kill Alpine before the problem",
2219 "is resolved.",
2220 NULL};
2221 static char *de_shell_msg[] = {
2222 "\n\nPlease attempt to correct the error preventing saving of the",
2223 "mail folder. If you do not know how to correct the problem, contact",
2224 "your system administrator. To return to Alpine, type \"exit\".",
2225 NULL};
2227 dprint((0,
2228 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2229 DE_FOLDER(stream), errcode, serious ? "" : "not "));
2230 dprint((0, "***** message: \"%s\"\n\n",
2231 ps_global->last_error ? ps_global->last_error : "?"));
2233 if(!serious) {
2234 sp_set_io_error_on_stream(stream, 1);
2235 return (1) ;
2238 while(1){
2239 /* replace pine's body display with screen full of explanatory text */
2240 ClearLine(2);
2241 PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1)
2242 - strlen(DE_FOLDER(stream)))/2, 0),
2243 DE_STR1, DE_FOLDER(stream));
2244 ClearLine(3);
2245 PutLine1(3, 4, DE_STR2, long2string(errcode));
2247 PutLine0(4, 0, " \"");
2248 removing_leading_white_space(ps_global->last_error);
2249 for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){
2250 for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++)
2251 if(isspace((unsigned char)*q))
2252 s = q;
2254 if(*q && s)
2255 q = s;
2257 while(p < q)
2258 Writechar(*p++, 0);
2260 if(*(p = q)){
2261 ClearLine(++i);
2262 PutLine0(i, 0, " ");
2263 while(*p && isspace((unsigned char)*p))
2264 p++;
2266 else{
2267 Writechar('\"', 0);
2268 CleartoEOLN();
2269 break;
2273 ClearLine(++i);
2274 for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){
2275 ClearLine(i);
2276 PutLine0(i, 0, " ");
2277 Write_to_screen(de_msg[i-j]);
2280 while(i < DE_LINE)
2281 ClearLine(i++);
2283 switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts,
2284 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){
2285 case 'r' : /* Retry! */
2286 ps_global->mangled_screen = 1;
2287 return(0L);
2289 case 'f' : /* File Browser */
2291 char full_filename[MAXPATH+1], filename[MAXPATH+1];
2293 filename[0] = '\0';
2294 build_path(full_filename, ps_global->home_dir, filename,
2295 sizeof(full_filename));
2296 file_lister("DISK ERROR", full_filename, sizeof(full_filename),
2297 filename, sizeof(filename), FALSE, FB_SAVE);
2300 break;
2302 case 's' :
2303 EndInverse();
2304 end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0);
2305 end_tty_driver(ps_global);
2306 for(i = 0; de_shell_msg[i]; i++)
2307 puts(de_shell_msg[i]);
2310 * Don't use our piping mechanism to spawn a subshell here
2311 * since it will the server (thus reentering c-client).
2312 * Bad thing to do.
2314 #ifdef _WINDOWS
2315 #else
2316 system("csh");
2317 #endif
2318 init_tty_driver(ps_global);
2319 init_keyboard(F_ON(F_USE_FK,ps_global));
2320 break;
2323 if(ps_global->redrawer)
2324 (*ps_global->redrawer)();
2329 long
2330 pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host)
2332 long rv = 1L;
2333 char pmt[128];
2335 #ifdef _WINDOWS
2336 mswin_killsplash();
2337 #endif
2339 if(elapsed >= (long)ps_global->tcp_query_timeout){
2340 snprintf(pmt, sizeof(pmt),
2341 _("No reply in %s seconds from server %s. Break connection"),
2342 long2string(elapsed), host);
2343 pmt[sizeof(pmt)-1] = '\0';
2344 if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
2345 ps_global->user_says_cancel = 1;
2346 return(0L);
2350 ps_global->tcptimeout = 0;
2351 return(rv);
2356 * -------------------------------------------------------------
2357 * These are declared in pith/imap.h as mandatory to implement.
2358 * -------------------------------------------------------------
2363 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2365 long
2366 pine_tcptimeout(long int elapsed, long int sincelast, char *host)
2368 long rv = 1L; /* keep trying by default */
2369 unsigned long ch;
2371 ps_global->tcptimeout = 1;
2372 #ifdef DEBUG
2373 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2374 long2string(elapsed), host));
2375 if(debugfile)
2376 fflush(debugfile);
2377 #endif
2379 #ifdef _WINDOWS
2380 mswin_killsplash();
2381 #endif
2383 if(ps_global->noshow_timeout)
2384 return(rv);
2386 if(ps_global->can_interrupt
2387 && ps_global->close_connection_timeout > 0L
2388 && elapsed >= (long)ps_global->tcp_query_timeout
2389 && elapsed >= (long)ps_global->close_connection_timeout){
2390 ps_global->can_interrupt = 0; /* do not return here */
2391 ps_global->read_bail = 0;
2392 ps_global->user_says_cancel = 1;
2393 return 0;
2396 if(!ps_global->ttyo)
2397 return(pine_tcptimeout_noscreen(elapsed, sincelast, host));
2399 suspend_busy_cue();
2402 * Prompt after a minute (since by then things are probably really bad)
2403 * A prompt timeout means "keep trying"...
2405 if(elapsed >= (long)ps_global->tcp_query_timeout){
2406 int clear_inverse;
2408 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2409 if((clear_inverse = !InverseState()) != 0)
2410 StartInverse();
2412 Writechar(BELL, 0);
2414 PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0,
2415 _("No reply in %s seconds from server %s. Break connection?"),
2416 long2string(elapsed), host);
2417 CleartoEOLN();
2418 fflush(stdout);
2419 flush_input();
2420 ch = read_char(7);
2421 if(ps_global->read_bail || ch == 'y' || ch == 'Y'){
2422 ps_global->read_bail = 0;
2423 ps_global->user_says_cancel = 1;
2424 rv = 0L;
2427 if(clear_inverse)
2428 EndInverse();
2430 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2433 if(rv == 1L){ /* just warn 'em something's up */
2434 q_status_message2(SM_ORDER, 0, 0,
2435 _("No reply in %s seconds from server %s. Still Waiting..."),
2436 long2string(elapsed), host);
2437 flush_status_messages(0); /* make sure it's seen */
2440 mark_status_dirty(); /* make sure it gets cleared */
2442 resume_busy_cue((rv == 1) ? 3 : 0);
2443 ps_global->tcptimeout = 0;
2445 return(rv);
2448 QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota)
2450 QUOTALIST *cquota = NULL;
2452 if(pquota){
2453 cquota = mail_newquotalist();
2454 if (pquota->name && *pquota->name)
2455 cquota->name = cpystr(pquota->name);
2456 cquota->usage = pquota->usage;
2457 cquota->limit = pquota->limit;
2458 if (pquota->next)
2459 cquota->next = pine_quotalist_copy(pquota->next);
2461 return cquota;
2465 /* c-client callback to handle quota */
2467 void
2468 pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2470 ps_global->quota = pine_quotalist_copy (pquota);
2474 * C-client callback to handle SSL/TLS certificate validation failures
2476 * Returning 0 means error becomes fatal
2477 * Non-zero means certificate problem is ignored and SSL session is
2478 * established
2480 * We remember the answer and won't re-ask for subsequent open attempts to
2481 * the same hostname.
2483 long
2484 pine_sslcertquery(char *reason, char *host, char *cert)
2486 char tmp[500];
2487 char *unknown = "<unknown>";
2488 long rv = 0L;
2489 STRLIST_S hostlist;
2490 int ok_novalidate = 0, warned = 0;
2492 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2493 host ? host : "?", reason ? reason : "?",
2494 cert ? cert : "?"));
2496 hostlist.name = host ? host : "";
2497 hostlist.next = NULL;
2500 * See if we've been asked about this host before.
2502 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2503 /* we were asked before, did we say Yes? */
2504 if(ok_novalidate)
2505 rv++;
2507 if(rv){
2508 dprint((5,
2509 "sslcertificatequery: approved automatically\n"));
2510 return(rv);
2513 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2516 if(ps_global->ttyo){
2517 SCROLL_S sargs;
2518 STORE_S *in_store, *out_store;
2519 gf_o_t pc;
2520 gf_i_t gc;
2521 HANDLE_S *handles = NULL;
2522 int the_answer = 'n';
2524 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
2525 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
2526 goto try_wantto;
2528 so_puts(in_store, "<HTML><P>");
2529 so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server"));
2531 so_puts(in_store, "<P><CENTER>");
2532 so_puts(in_store, host ? host : unknown);
2533 so_puts(in_store, "</CENTER>");
2535 so_puts(in_store, "<P>");
2536 so_puts(in_store, _("The reason for the failure was"));
2538 /* squirrel away details */
2539 if(details_host)
2540 fs_give((void **)&details_host);
2541 if(details_reason)
2542 fs_give((void **)&details_reason);
2543 if(details_cert)
2544 fs_give((void **)&details_cert);
2546 details_host = cpystr(host ? host : unknown);
2547 details_reason = cpystr(reason ? reason : unknown);
2548 details_cert = cpystr(cert ? cert : unknown);
2550 so_puts(in_store, "<P><CENTER>");
2551 snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2552 reason ? reason : unknown);
2553 tmp[sizeof(tmp)-1] = '\0';
2555 so_puts(in_store, tmp);
2556 so_puts(in_store, "</CENTER>");
2558 so_puts(in_store, "<P>");
2559 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."));
2561 so_puts(in_store, "<P>");
2562 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"));
2564 so_puts(in_store, "<P><CENTER>");
2565 so_puts(in_store, "/novalidate-cert");
2566 so_puts(in_store, "</CENTER>");
2568 so_puts(in_store, "<P>");
2569 so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2571 so_puts(in_store, "<P><CENTER>");
2572 so_puts(in_store, host ? host : unknown);
2573 so_puts(in_store, "</CENTER>");
2575 so_puts(in_store, "<P>");
2576 so_puts(in_store, _("in your configuration, replace those characters with"));
2578 so_puts(in_store, "<P><CENTER>");
2579 so_puts(in_store, host ? host : unknown);
2580 so_puts(in_store, "/novalidate-cert");
2581 so_puts(in_store, "</CENTER>");
2583 so_puts(in_store, "<P>");
2584 so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2586 so_seek(in_store, 0L, 0);
2587 init_handles(&handles);
2588 gf_filter_init();
2589 gf_link_filter(gf_html2plain,
2590 gf_html2plain_opt(NULL,
2591 ps_global->ttyo->screen_cols, non_messageview_margin(),
2592 &handles, NULL, GFHP_LOCAL_HANDLES));
2593 gf_set_so_readc(&gc, in_store);
2594 gf_set_so_writec(&pc, out_store);
2595 gf_pipe(gc, pc);
2596 gf_clear_so_writec(out_store);
2597 gf_clear_so_readc(in_store);
2599 memset(&sargs, 0, sizeof(SCROLL_S));
2600 sargs.text.handles = handles;
2601 sargs.text.text = so_text(out_store);
2602 sargs.text.src = CharStar;
2603 sargs.text.desc = _("help text");
2604 sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2605 sargs.proc.tool = answer_cert_failure;
2606 sargs.proc.data.p = (void *)&the_answer;
2607 sargs.keys.menu = &ans_certquery_keymenu;
2608 /* don't want to re-enter c-client */
2609 sargs.quell_newmail = 1;
2610 setbitmap(sargs.keys.bitmap);
2611 sargs.help.text = h_tls_validation_failure;
2612 sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
2614 scrolltool(&sargs);
2616 if(the_answer == 'y')
2617 rv++;
2618 else if(the_answer == 'n')
2619 ps_global->user_says_cancel = 1;
2621 ps_global->mangled_screen = 1;
2622 ps_global->painted_body_on_startup = 0;
2623 ps_global->painted_footer_on_startup = 0;
2624 so_give(&in_store);
2625 so_give(&out_store);
2626 free_handles(&handles);
2627 if(details_host)
2628 fs_give((void **)&details_host);
2629 if(details_reason)
2630 fs_give((void **)&details_reason);
2631 if(details_cert)
2632 fs_give((void **)&details_cert);
2634 else{
2636 * If screen hasn't been initialized yet, use want_to.
2638 try_wantto:
2639 memset((void *)tmp, 0, sizeof(tmp));
2640 strncpy(tmp,
2641 reason ? reason : _("SSL/TLS certificate validation failure"),
2642 sizeof(tmp));
2643 tmp[sizeof(tmp)-1] = '\0';
2644 strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
2646 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
2647 rv++;
2650 if(rv == 0)
2651 q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
2652 host ? host : unknown);
2654 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
2656 dprint((5, "sslcertificatequery: %s\n",
2657 rv ? "approved" : "rejected"));
2659 return(rv);
2663 char *
2664 pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
2666 char buf[MAILTMPLEN];
2668 if((can_access(mulname, ACCESS_EXISTS) == 0)
2669 || !(can_access(name, ACCESS_EXISTS) == 0))
2670 return(mulname);
2672 snprintf(buf, sizeof(buf),
2673 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2674 last_cmpnt(name),
2675 strlen(last_cmpnt(name)) > 15 ? "..." : "");
2676 buf[sizeof(buf)-1] = '\0';
2677 if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
2678 rename_file(name, mulname);
2679 return(mulname);
2684 url_local_certdetails(char *url)
2686 if(!struncmp(url, "x-alpine-cert:", 14)){
2687 STORE_S *store;
2688 SCROLL_S sargs;
2689 char *folded;
2691 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
2692 q_status_message(SM_ORDER | SM_DING, 7, 10,
2693 _("Error allocating space for details."));
2694 return(0);
2697 so_puts(store, _("Host given by user:\n\n "));
2698 so_puts(store, details_host);
2699 so_puts(store, _("\n\nReason for failure:\n\n "));
2700 so_puts(store, details_reason);
2701 so_puts(store, _("\n\nCertificate being verified:\n\n"));
2702 folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
2703 so_puts(store, folded);
2704 fs_give((void **)&folded);
2705 so_puts(store, "\n");
2707 memset(&sargs, 0, sizeof(SCROLL_S));
2708 sargs.text.text = so_text(store);
2709 sargs.text.src = CharStar;
2710 sargs.text.desc = _("Details");
2711 sargs.bar.title = _("CERT VALIDATION DETAILS");
2712 sargs.help.text = NO_HELP;
2713 sargs.help.title = NULL;
2714 sargs.quell_newmail = 1;
2715 sargs.help.text = h_tls_failure_details;
2716 sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
2718 scrolltool(&sargs);
2720 so_give(&store); /* free resources associated with store */
2721 ps_global->mangled_screen = 1;
2722 return(1);
2725 return(0);
2730 * C-client callback to handle SSL/TLS certificate validation failures
2732 void
2733 pine_sslfailure(char *host, char *reason, long unsigned int flags)
2735 SCROLL_S sargs;
2736 STORE_S *store;
2737 int the_answer = 'n', indent, len, cols;
2738 char buf[500], buf2[500];
2739 char *folded;
2740 char *hst = host ? host : "<unknown>";
2741 char *rsn = reason ? reason : "<unknown>";
2742 char *notls = "/notls";
2743 STRLIST_S hostlist;
2744 int ok_novalidate = 0, warned = 0;
2747 dprint((1, "sslfailure: host=%s reason=%s\n",
2748 hst ? hst : "?",
2749 rsn ? rsn : "?"));
2751 if(flags & NET_SILENT)
2752 return;
2754 hostlist.name = host ? host : "";
2755 hostlist.next = NULL;
2758 * See if we've been told about this host before.
2760 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2761 /* we were told already */
2762 if(warned){
2763 snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn);
2764 buf[sizeof(buf)-1] = '\0';
2765 mm_log(buf, ERROR);
2766 return;
2770 cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2771 cols--;
2773 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS)))
2774 return;
2776 strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf));
2777 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2778 so_puts(store, folded);
2779 fs_give((void **)&folded);
2780 so_puts(store, "\n");
2782 if((len=strlen(hst)) <= cols){
2783 if((indent=((cols-len)/2)) > 0)
2784 so_puts(store, repeat_char(indent, SPACE));
2786 so_puts(store, hst);
2787 so_puts(store, "\n");
2789 else{
2790 strncpy(buf, hst, sizeof(buf));
2791 buf[sizeof(buf)-1] = '\0';
2792 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2793 so_puts(store, folded);
2794 fs_give((void **)&folded);
2797 so_puts(store, "\n");
2799 strncpy(buf, _("The reason for the failure was"), sizeof(buf));
2800 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2801 so_puts(store, folded);
2802 fs_give((void **)&folded);
2803 so_puts(store, "\n");
2805 if((len=strlen(rsn)) <= cols){
2806 if((indent=((cols-len)/2)) > 0)
2807 so_puts(store, repeat_char(indent, SPACE));
2809 so_puts(store, rsn);
2810 so_puts(store, "\n");
2812 else{
2813 strncpy(buf, rsn, sizeof(buf));
2814 buf[sizeof(buf)-1] = '\0';
2815 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2816 so_puts(store, folded);
2817 fs_give((void **)&folded);
2820 so_puts(store, "\n");
2822 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));
2823 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2824 so_puts(store, folded);
2825 fs_give((void **)&folded);
2826 so_puts(store, "\n");
2828 if((len=strlen(notls)) <= cols){
2829 if((indent=((cols-len)/2)) > 0)
2830 so_puts(store, repeat_char(indent, SPACE));
2832 so_puts(store, notls);
2833 so_puts(store, "\n");
2835 else{
2836 strncpy(buf, notls, sizeof(buf));
2837 buf[sizeof(buf)-1] = '\0';
2838 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2839 so_puts(store, folded);
2840 fs_give((void **)&folded);
2843 so_puts(store, "\n");
2845 strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2846 sizeof(buf));
2847 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2848 so_puts(store, folded);
2849 fs_give((void **)&folded);
2850 so_puts(store, "\n");
2852 if((len=strlen(hst)) <= cols){
2853 if((indent=((cols-len)/2)) > 0)
2854 so_puts(store, repeat_char(indent, SPACE));
2856 so_puts(store, hst);
2857 so_puts(store, "\n");
2859 else{
2860 strncpy(buf, hst, sizeof(buf));
2861 buf[sizeof(buf)-1] = '\0';
2862 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2863 so_puts(store, folded);
2864 fs_give((void **)&folded);
2867 so_puts(store, "\n");
2869 strncpy(buf, _("in your configuration, replace those characters with"),
2870 sizeof(buf));
2871 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2872 so_puts(store, folded);
2873 fs_give((void **)&folded);
2874 so_puts(store, "\n");
2876 snprintf(buf2, sizeof(buf2), "%s%s", hst, notls);
2877 buf2[sizeof(buf2)-1] = '\0';
2878 if((len=strlen(buf2)) <= cols){
2879 if((indent=((cols-len)/2)) > 0)
2880 so_puts(store, repeat_char(indent, SPACE));
2882 so_puts(store, buf2);
2883 so_puts(store, "\n");
2885 else{
2886 strncpy(buf, buf2, sizeof(buf));
2887 buf[sizeof(buf)-1] = '\0';
2888 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2889 so_puts(store, folded);
2890 fs_give((void **)&folded);
2893 so_puts(store, "\n");
2895 if(ps_global->ttyo){
2896 strncpy(buf, _("Type RETURN to continue."), sizeof(buf));
2897 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2898 so_puts(store, folded);
2899 fs_give((void **)&folded);
2902 memset(&sargs, 0, sizeof(SCROLL_S));
2903 sargs.text.text = so_text(store);
2904 sargs.text.src = CharStar;
2905 sargs.text.desc = _("help text");
2906 sargs.bar.title = _("SSL/TLS FAILURE");
2907 sargs.proc.tool = answer_cert_failure;
2908 sargs.proc.data.p = (void *)&the_answer;
2909 sargs.keys.menu = &ans_certfail_keymenu;
2910 setbitmap(sargs.keys.bitmap);
2911 /* don't want to re-enter c-client */
2912 sargs.quell_newmail = 1;
2913 sargs.help.text = h_tls_failure;
2914 sargs.help.title = _("HELP FOR TLS/SSL FAILURE");
2916 if(ps_global->ttyo)
2917 scrolltool(&sargs);
2918 else{
2919 char **q, **qp;
2920 char *p;
2921 unsigned char c;
2922 int cnt = 0;
2925 * The screen isn't initialized yet, which should mean that this
2926 * is the result of a -p argument. Display_args_err knows how to deal
2927 * with the uninitialized screen, so we mess with the data to get it
2928 * in shape for display_args_err. This is pretty hacky.
2931 so_seek(store, 0L, 0); /* rewind */
2932 /* count the lines */
2933 while(so_readc(&c, store))
2934 if(c == '\n')
2935 cnt++;
2937 qp = q = (char **)fs_get((cnt+1) * sizeof(char *));
2938 memset(q, 0, (cnt+1) * sizeof(char *));
2940 so_seek(store, 0L, 0); /* rewind */
2941 p = buf;
2942 while(so_readc(&c, store)){
2943 if(c == '\n'){
2944 *p = '\0';
2945 *qp++ = cpystr(buf);
2946 p = buf;
2948 else
2949 *p++ = c;
2952 display_args_err(NULL, q, 0);
2953 free_list_array(&q);
2956 ps_global->mangled_screen = 1;
2957 ps_global->painted_body_on_startup = 0;
2958 ps_global->painted_footer_on_startup = 0;
2959 so_give(&store);
2961 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1);
2966 answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2968 int rv = 1;
2970 ps_global->next_screen = SCREEN_FUN_NULL;
2972 switch(cmd){
2973 case MC_YES :
2974 *(int *)(sparms->proc.data.p) = 'y';
2975 break;
2977 case MC_NO :
2978 *(int *)(sparms->proc.data.p) = 'n';
2979 break;
2981 default:
2982 alpine_panic("Unexpected command in answer_cert_failure");
2983 break;
2986 return(rv);
2991 oauth2_auth_answer(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2993 int rv = 1, rc;
2994 AUTH_CODE_S user;
2995 int q_line, flags;
2996 /* TRANSLATORS: user needs to input an access code from the server */
2997 char *accesscodelabel = _("Copy and Paste Access Code");
2998 char token[4*MAILTMPLEN], prompt[4*MAILTMPLEN];
3000 ps_global->next_screen = SCREEN_FUN_NULL;
3002 token[0] = '\0';
3003 switch(cmd){
3004 case MC_YES :
3005 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
3006 flags = OE_APPEND_CURRENT;
3007 sprintf(prompt, "%s: ", accesscodelabel);
3008 do {
3009 rc = optionally_enter(token, q_line, 0, 4*MAILTMPLEN,
3010 prompt, NULL, NO_HELP, &flags);
3011 } while (rc != 0 && rc != 1);
3012 user.code = rc == 0 ? cpystr(token) : NULL;
3013 user.answer = 'e';
3014 rv = rc == 1 ? 0 : 1;
3015 break;
3017 case MC_NO :
3018 user.code = NULL;
3019 user.answer = 'e';
3020 break;
3022 default:
3023 alpine_panic("Unexpected command in oauth2_auth_answer");
3024 break;
3026 *(AUTH_CODE_S *) sparms->proc.data.p = user;
3027 return(rv);
3031 /*----------------------------------------------------------------------
3032 This can be used to prevent the flickering of the check_cue char
3033 caused by numerous (5000+) fetches by c-client. Right now, the only
3034 practical use found is newsgroup subsciption.
3036 check_cue_display will check if this global is set, and won't clear
3037 the check_cue_char if set.
3038 ----*/
3039 void
3040 set_read_predicted(int i)
3042 ps_global->read_predicted = i==1;
3043 #ifdef _WINDOWS
3044 if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global))
3045 check_cue_display(" ");
3046 #endif
3050 /*----------------------------------------------------------------------
3051 Exported method to retrieve logged in user name associated with stream
3053 Args: host -- host to find associated login name with.
3055 Result:
3056 ----*/
3057 void *
3058 pine_block_notify(int reason, void *data)
3060 switch(reason){
3061 case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */
3062 break;
3064 case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */
3065 break;
3067 case BLOCK_TCPWRITE: /* blocked on TCP write */
3068 case BLOCK_FILELOCK: /* blocked on file locking */
3069 #ifdef _WINDOWS
3070 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3071 check_cue_display(">");
3073 mswin_setcursor(MSWIN_CURSOR_BUSY);
3074 #endif
3075 break;
3077 case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */
3078 case BLOCK_TCPOPEN: /* blocked on TCP open */
3079 case BLOCK_TCPREAD: /* blocked on TCP read */
3080 case BLOCK_TCPCLOSE: /* blocked on TCP close */
3081 #ifdef _WINDOWS
3082 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3083 check_cue_display("<");
3085 mswin_setcursor(MSWIN_CURSOR_BUSY);
3086 #endif
3087 break;
3089 default :
3090 case BLOCK_NONE: /* not blocked */
3091 #ifdef _WINDOWS
3092 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3093 check_cue_display(" ");
3094 #endif
3095 break;
3099 return(NULL);
3103 void
3104 mm_expunged_current(long unsigned int rawno)
3106 /* expunged something we're viewing? */
3107 if(!ps_global->expunge_in_progress
3108 && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno))
3109 && (ps_global->prev_screen == mail_view_screen
3110 || ps_global->prev_screen == attachment_screen))){
3111 ps_global->next_screen = mail_index_screen;
3112 q_status_message(SM_ORDER | SM_DING , 3, 3,
3113 "Message you were viewing is gone!");
3118 #ifdef PASSFILE
3121 * Specific functions to support caching username/passwd/host
3122 * triples on disk for use from one session to the next...
3125 #define FIRSTCH 0x20
3126 #define LASTCH 0x7e
3127 #define TABSZ (LASTCH - FIRSTCH + 1)
3129 static int xlate_key;
3133 * xlate_in() - xlate_in the given character
3135 char
3136 xlate_in(int c)
3138 register int eti;
3140 eti = xlate_key;
3141 if((c >= FIRSTCH) && (c <= LASTCH)){
3142 eti += (c - FIRSTCH);
3143 eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0;
3144 return((xlate_key = eti) + FIRSTCH);
3146 else
3147 return(c);
3152 * xlate_out() - xlate_out the given character
3154 char
3155 xlate_out(char c)
3157 register int dti;
3158 register int xch;
3160 if((c >= FIRSTCH) && (c <= LASTCH)){
3161 xch = c - (dti = xlate_key);
3162 xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0;
3163 dti = (xch - FIRSTCH) + dti;
3164 dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0;
3165 xlate_key = dti;
3166 return(xch);
3168 else
3169 return(c);
3171 #endif /* PASSFILE */
3174 #ifdef LOCAL_PASSWD_CACHE
3176 int cache_method_was_setup (char *pinerc)
3178 int rv = 1;
3179 #ifdef PASSFILE
3180 char tmp[MAILTMPLEN];
3181 FILE *fp = NULL;
3182 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb")))
3183 rv = 0;
3184 if(fp != NULL) fclose(fp);
3185 #endif /* PASSFILE */
3186 return rv;
3189 int
3190 line_get(char *tmp, size_t len, char **textp)
3192 char *s;
3194 tmp[0] = '\0';
3195 if (*textp == NULL
3196 || (s = strchr(*textp, '\n')) == NULL
3197 || (s - *textp) > len - 1)
3198 return 0;
3200 *s = '\0';
3201 if(*(s-1) == '\r')
3202 *(s-1) = '\0';
3204 snprintf(tmp, len, "%s\n", *textp);
3205 tmp[len-1] = '\0';
3206 *textp = s+1;
3208 return 1;
3211 typedef struct pwd_s {
3212 char *blob;
3213 char **blobarray;
3214 char *host;
3215 char *user;
3216 char *sflags;
3217 char *passwd;
3218 char *orighost;
3219 int uid;
3220 } ALPINE_PWD_S;
3222 #define SAME_VALUE(X, Y) ((((X) == NULL && (Y) == NULL) \
3223 || ((X) && (Y) && !strcmp((X), (Y)))) ? 1 : 0)
3226 * For UNIX:
3227 * Passfile lines are
3229 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3231 * In pine4.40 and before there was no orig_hostname, and there still isn't
3232 * if it is the same as hostname.
3234 * else for WINDOWS:
3235 * Use Windows credentials. The TargetName of the credential is
3236 * UWash_Alpine_.partnumber-totalparts_<hostname:port>\tuser\taltflag
3237 * and the blob consists of
3238 * passwd\torighost (if different from host)
3240 * We don't use anything fancy we just copy out all the credentials which
3241 * begin with TNAME and put them into our cache, so we don't lookup based
3242 * on the TargetName or anything like that. That was so we could re-use
3243 * the existing code and so that orighost data could be easily used.
3246 read_passfile(char *pinerc, MMLOGIN_S **l)
3248 #ifdef WINCRED
3249 # if (WINCRED > 0)
3250 LPCTSTR lfilter = TEXT(TNAMESTAR);
3251 DWORD count, k;
3252 PCREDENTIAL *pcred;
3253 char *tmp, *blob, *target = NULL;
3254 ALPINE_PWD_S **pwd = NULL;
3255 int uid, rewrite_passfile = 0;
3256 char *ui[5];
3257 int i, j;
3258 unsigned long m, n, p, loc;
3260 if(using_passfile == 0)
3261 return(using_passfile);
3263 if(!g_CredInited){
3264 if (init_wincred_funcs() != 1) {
3265 using_passfile = 0;
3266 return(using_passfile);
3270 dprint((9, "read_passfile\n"));
3272 using_passfile = 1;
3274 /* this code exists because the XOAUTH2 support makes us save
3275 * access tokens as if they were passwords. However, some servers
3276 * produce extremely long access-tokens that do not fit in the credentials
3277 * and therefore need to be split into several entries.
3279 * The plan is the following:
3280 * step 1: Read and save all the information in the credentials
3281 * step 2: flatten the information into one line
3282 * step 3: process that line.
3284 if (g_CredEnumerateW(lfilter, 0, &count, &pcred)) {
3285 pwd = fs_get((count + 1)*sizeof(ALPINE_PWD_S *));
3286 memset((void *)pwd, 0, (count + 1)*sizeof(ALPINE_PWD_S *));
3287 if (pwd && pcred) {
3288 /* this is step 1 */
3289 for (k = 0; k < count; k++) { /* go through each credential */
3290 target = lptstr_to_utf8(pcred[k]->TargetName);
3291 tmp = srchstr(target, TNAME);
3292 if (tmp) {
3293 tmp += strlen(TNAME);
3294 if (*tmp == '.') {
3295 tmp++;
3296 m = strtoul(tmp, &tmp, 10);
3297 if(*tmp == '.'){
3298 tmp++;
3299 uid = m;
3300 m = strtoul(tmp, &tmp, 10);
3302 else{
3303 uid = 0; /* impossible value, uid >= 1, old format! */
3304 rewrite_passfile++;
3306 if (*tmp == '-') {
3307 tmp++;
3308 n = strtol(tmp, &tmp, 10);
3309 if (*tmp == '_') tmp++;
3311 else{
3312 char *s;
3313 for(s = tmp; *s && *s != '_'; s++);
3314 if(s && *s) tmp = s;
3317 else {
3318 m = n = 1;
3321 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3322 for (i = 0, j = 0; tmp[i] && j < 3; j++) {
3323 for (ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3324 ; /* find end of data */
3326 if (tmp[i])
3327 tmp[i++] = '\0'; /* tie off data */
3330 /* improve this. We are trying to find where we saved
3331 * this data, and in general this is fast if there is
3332 * only a few data, which is not unreasonable, but probably
3333 * can be done better.
3335 for (loc = 0; pwd[loc]
3336 && !(uid == pwd[loc]->uid
3337 && SAME_VALUE(ui[0], pwd[loc]->host)
3338 && SAME_VALUE(ui[1], pwd[loc]->user)
3339 && SAME_VALUE(ui[2], pwd[loc]->sflags)); loc++);
3341 if (pwd[loc] == NULL) {
3342 pwd[loc] = fs_get(sizeof(ALPINE_PWD_S));
3343 memset((void *) pwd[loc], 0, sizeof(ALPINE_PWD_S));
3344 pwd[loc]->blobarray = fs_get((n + 1) * sizeof(char*));
3345 memset((void *) pwd[loc]->blobarray, 0, (n + 1) * sizeof(char*));
3348 pwd[loc]->uid = uid;
3349 if (pwd[loc]->host == NULL)
3350 pwd[loc]->host = ui[0] ? cpystr(ui[0]) : NULL;
3351 if (pwd[loc]->user == NULL)
3352 pwd[loc]->user = ui[1] ? cpystr(ui[1]) : NULL;
3353 if (pwd[loc]->sflags == NULL)
3354 pwd[loc]->sflags = ui[2] ? cpystr(ui[2]) : NULL;
3355 blob = (char *) pcred[k]->CredentialBlob;
3356 pwd[loc]->blobarray[m - 1] = blob ? cpystr(blob) : NULL;
3358 if (target) fs_give((void**)&target);
3360 /* step 2 */
3361 for (k = 0; k < count; k++) {
3362 if (pwd[k]) {
3363 for (i = 0, j = 0; pwd[k]->blobarray[j]; j++)
3364 i += strlen(pwd[k]->blobarray[j]);
3365 pwd[k]->blob = fs_get(i + 1);
3366 pwd[k]->blob[0] = '\0';
3367 for (j = 0; pwd[k]->blobarray[j]; j++) {
3368 strcat(pwd[k]->blob, pwd[k]->blobarray[j]);
3369 fs_give((void **) &pwd[k]->blobarray[j]);
3371 fs_give((void **) &pwd[k]->blobarray);
3373 else k = count; /* we are done with this step! */
3375 /* step 3 */
3376 for (k = 0; k < count; k++) {
3377 if (pwd[k] && pwd[k]->blob) {
3378 blob = pwd[k]->blob;
3379 for (i = 0, j = 3; blob[i] && j < 5; j++) {
3380 for (ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++)
3381 ; /* find end of data */
3383 if (blob[i])
3384 blob[i++] = '\0'; /* tie off data */
3386 if (pwd[k]->passwd == NULL)
3387 pwd[k]->passwd = ui[3] ? cpystr(ui[3]) : NULL;
3388 if (pwd[k]->orighost == NULL)
3389 pwd[k]->orighost = ui[4] ? cpystr(ui[4]) : NULL;
3390 fs_give((void **) &pwd[k]->blob);
3393 /* now process all lines, and free memory */
3394 for (k = 0; k < count && pwd[k] != NULL; k++){
3395 if (pwd[k]->passwd && pwd[k]->host && pwd[k]->user) { /* valid field? */
3396 STRLIST_S hostlist[2];
3397 int flags;
3399 tmp = pwd[k]->sflags ? strchr(pwd[k]->sflags, PWDAUTHSEP) : NULL;
3400 flags = pwd[k]->sflags ? atoi(tmp ? ++tmp : pwd[k]->sflags) : 0;
3401 hostlist[0].name = pwd[k]->host;
3402 if (pwd[k]->orighost) {
3403 hostlist[0].next = &hostlist[1];
3404 hostlist[1].name = pwd[k]->orighost;
3405 hostlist[1].next = NULL;
3407 else {
3408 hostlist[0].next = NULL;
3410 imap_set_passwd(l, pwd[k]->passwd, pwd[k]->user, hostlist, flags & 0x01, 0, 0);
3412 if (pwd[k]->passwd) fs_give((void **) &pwd[k]->passwd);
3413 if (pwd[k]->user) fs_give((void **) &pwd[k]->user);
3414 if (pwd[k]->host) fs_give((void **) &pwd[k]->host);
3415 if (pwd[k]->sflags) fs_give((void **) &pwd[k]->sflags);
3416 if (pwd[k]->orighost) fs_give((void **) &pwd[k]->orighost);
3417 fs_give((void **) &pwd[k]);
3419 g_CredFree((PVOID)pcred);
3421 fs_give((void **) &pwd);
3422 if(rewrite_passfile) write_passfile(pinerc, *l);
3424 return(1);
3426 # else /* old windows */
3427 using_passfile = 0;
3428 return(0);
3429 # endif
3431 #elif APPLEKEYCHAIN
3433 char target[MAILTMPLEN];
3434 char *tmp, *host, *user, *sflags, *passwd, *orighost;
3435 char *ui[5];
3436 int i, j, k, rc;
3437 SecKeychainAttributeList attrList;
3438 SecKeychainSearchRef searchRef = NULL;
3439 SecKeychainAttribute attrs[] = {
3440 { kSecAccountItemAttr, strlen(TNAME), TNAME }
3443 if(using_passfile == 0)
3444 return(using_passfile);
3446 dprint((9, "read_passfile\n"));
3449 /* search for only our items in the keychain */
3450 attrList.count = 1;
3451 attrList.attr = attrs;
3453 using_passfile = 1;
3454 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
3455 kSecGenericPasswordItemClass,
3456 &attrList,
3457 &searchRef))){
3458 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3459 if(searchRef){
3460 SecKeychainItemRef itemRef = NULL;
3461 SecKeychainAttributeInfo info;
3462 SecKeychainAttributeList *attrList = NULL;
3463 UInt32 blength = 0;
3464 char *blob = NULL;
3465 char *blobcopy = NULL; /* NULL terminated copy */
3467 UInt32 tags[] = {kSecAccountItemAttr,
3468 kSecServiceItemAttr};
3469 UInt32 formats[] = {0,0};
3471 dprint((10, "read_passfile: searchRef not NULL\n"));
3472 info.count = 2;
3473 info.tag = tags;
3474 info.format = formats;
3477 * Go through each item we found and put it
3478 * into our list.
3480 while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){
3481 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3482 rc = SecKeychainItemCopyAttributesAndData(itemRef,
3483 &info, NULL,
3484 &attrList,
3485 &blength,
3486 (void **) &blob);
3487 if(rc == 0 && attrList){
3488 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count));
3490 blobcopy = (char *) fs_get((blength + 1) * sizeof(char));
3491 strncpy(blobcopy, (char *) blob, blength);
3492 blobcopy[blength] = '\0';
3495 * I'm not real clear on how this works. It seems to be
3496 * necessary to combine the attributes from two passes
3497 * (attrList->count == 2) in order to get the full set
3498 * of attributes we inserted into the keychain in the
3499 * first place. So, we reset host...orighost outside of
3500 * the following for loop, not inside.
3502 host = user = sflags = passwd = orighost = NULL;
3503 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3505 for(k = 0; k < attrList->count; k++){
3507 if(attrList->attr[k].length){
3508 strncpy(target,
3509 (char *) attrList->attr[k].data,
3510 MIN(attrList->attr[k].length,sizeof(target)));
3511 target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0';
3514 tmp = target;
3515 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3516 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3517 ; /* find end of data */
3519 if(tmp[i])
3520 tmp[i++] = '\0'; /* tie off data */
3523 if(ui[0])
3524 host = ui[0];
3526 if(ui[1])
3527 user = ui[1];
3529 if(ui[2])
3530 sflags = ui[2];
3532 for(i = 0, j = 3; blobcopy[i] && j < 5; j++){
3533 for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++)
3534 ; /* find end of data */
3536 if(blobcopy[i])
3537 blobcopy[i++] = '\0'; /* tie off data */
3540 if(ui[3])
3541 passwd = ui[3];
3543 if(ui[4])
3544 orighost = ui[4];
3546 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:""));
3549 if(passwd && host && user){ /* valid field? */
3550 STRLIST_S hostlist[2];
3551 int flags;
3553 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3554 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3555 hostlist[0].name = host;
3556 if(orighost){
3557 hostlist[0].next = &hostlist[1];
3558 hostlist[1].name = orighost;
3559 hostlist[1].next = NULL;
3561 else{
3562 hostlist[0].next = NULL;
3565 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3568 if(blobcopy)
3569 fs_give((void **) & blobcopy);
3571 SecKeychainItemFreeAttributesAndData(attrList, (void *) blob);
3573 else{
3574 using_passfile = 0;
3575 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc));
3578 CFRelease(itemRef);
3579 itemRef = NULL;
3582 CFRelease(searchRef);
3584 else{
3585 using_passfile = 0;
3586 dprint((10, "read_passfile: searchRef NULL\n"));
3589 else{
3590 using_passfile = 0;
3591 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc));
3594 return(using_passfile);
3596 #else /* PASSFILE */
3598 char tmp[MAILTMPLEN], *ui[5];
3599 int i, j, n, rv = 0, error = 0;
3600 size_t len = 0;
3601 char *tmptext = NULL;
3602 struct stat sbuf;
3603 #ifdef SMIME
3604 char tmp2[MAILTMPLEN];
3605 char *text = NULL, *text2 = NULL;
3606 int encrypted = 0;
3607 #endif /* SMIME */
3608 FILE *fp;
3610 if(using_passfile == 0)
3611 return(using_passfile);
3613 dprint((9, "read_passfile\n"));
3615 /* if there's no password to read, create it if we can encrypt it,
3616 * or else let the user create it and bail out of here.
3618 tmp[0] = '\0';
3619 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){
3620 #ifdef SMIME
3621 i = our_creat(tmp, 0600);
3622 if(i >= 0){
3623 close(i);
3624 if(!(fp = our_fopen(tmp, "rb")))
3625 error++;
3627 else error++;
3628 #else
3629 error++;
3630 #endif
3633 if(error){
3634 using_passfile = 0;
3635 return(using_passfile);
3638 #ifndef SMIME
3639 if(our_stat(tmp, &sbuf) == 0)
3640 len = sbuf.st_size;
3641 fp = our_fopen(tmp, "rb"); /* reopen to read data */
3642 #else
3643 /* the next call initializes the key/certificate pair used to
3644 * encrypt and decrypt a password file. The details of how this is
3645 * done is in the file pith/smime.c. During this setup we might call
3646 * smime_init(), but no matter what happens we must call smime_deinit()
3647 * there. The reason why this is so is because we can not assume that
3648 * the .pinerc file has been read by this time, so this code might not
3649 * know about the ps_global->smime structure or any of its components,
3650 * and it shouldn't because it only needs ps_global->pwdcert, so
3651 * do not init smime here, because the .pinerc might not have been
3652 * read and we do not really know where the keys and certificates really
3653 * are.
3654 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3655 * it is called for the first time and there are certificates at all,
3656 * or when it is called after the first time and the user refuses to
3657 * create a self-signed certificate. In this situation we will just
3658 * let the user live in an insecure world, but no more passwords will
3659 * be saved in the password file, and only those found there will be used.
3661 tmp2[0] = '\0';
3662 fgets(tmp2, sizeof(tmp2), fp);
3663 fclose(fp);
3664 if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
3665 /* there is an already existing password file, that is not encrypted
3666 * and there is no key to encrypt it yet, go again through setup_pwdcert
3667 * and encrypt it now.
3669 if(tmp2[0]){ /* not empty, UNencrypted password file */
3670 if(ps_global->pwdcert == NULL)
3671 rv = setup_pwdcert(&ps_global->pwdcert);
3672 if((rv == 0 || rv == -5) && ps_global->pwdcert == NULL)
3673 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3674 if(ps_global->pwdcert == NULL){
3675 q_status_message(SM_ORDER, 3, 3,
3676 " Failed to create private key. Using UNencrypted Password file. ");
3677 save_password = 0;
3679 else{
3680 if(rv == 1){
3681 q_status_message(SM_ORDER, 3, 3,
3682 " Failed to unlock private key. Using UNencrypted Password file. ");
3683 save_password = 0; /* do not save more passwords */
3686 if(ps_global->pwdcert != NULL
3687 && encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
3688 encrypted++;
3691 else {
3692 if(ps_global->pwdcert == NULL)
3693 rv = setup_pwdcert(&ps_global->pwdcert);
3694 encrypted++;
3698 * if password file is encrypted we attempt to decrypt. We ask the
3699 * user for the password to unlock the password file. If the user
3700 * enters the password and it unlocks the file, use it and keep saving
3701 * passwords in it. If the user enters the wrong passwords and does
3702 * not unlock it, we will not see that here, but in decrypt_file, so
3703 * the only other possibility is that the user cancels. In that case
3704 * we will see i == -1. In that case, we will let the user attempt
3705 * manual login to the server they want to login, but passwords will
3706 * not be saved so that the password file will not be saved
3707 * unencrypted and rewritten again.
3709 if(encrypted){
3710 text = text2 = decrypt_file((char *)tmp, &i, (PERSONAL_CERT *)ps_global->pwdcert);
3711 len = text2 ? strlen(text2) : 0;
3712 switch(i){
3713 case -2: using_passfile = 0;
3714 break;
3716 case 1 : save_password = 1;
3717 using_passfile = 1;
3718 break;
3720 case -1: save_password = 0;
3721 using_passfile = 1;
3722 break;
3724 default: break;
3727 #endif /* SMIME */
3729 if(using_passfile == 0){
3730 #ifdef SMIME
3731 if(text) fs_give((void **)&text);
3732 #endif /* SMIME */
3733 return using_passfile;
3736 if(len > 0){
3737 tmptext = fs_get(len + 1);
3738 #ifdef SMIME
3739 for(n = 0; encrypted ? line_get(tmptext, len + 1, &text2)
3740 : (fgets(tmptext, len+1, fp) != NULL); n++){
3741 #else /* SMIME */
3742 for(n = 0; fgets(tmptext, len+1, fp); n++){
3743 #endif /* SMIME */
3744 /*** do any necessary DEcryption here ***/
3745 xlate_key = n;
3746 for(i = 0; tmptext[i]; i++)
3747 tmptext[i] = xlate_out(tmptext[i]);
3749 if(i && tmptext[i-1] == '\n')
3750 tmptext[i-1] = '\0'; /* blast '\n' */
3752 dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
3753 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3754 for(i = 0, j = 0; tmptext[i] && j < 5; j++){
3755 for(ui[j] = &tmptext[i]; tmptext[i] && tmptext[i] != '\t'; i++)
3756 ; /* find end of data */
3758 if(tmptext[i])
3759 tmptext[i++] = '\0'; /* tie off data */
3762 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3763 if(ui[0] && ui[1] && ui[2]){ /* valid field? */
3764 STRLIST_S hostlist[2];
3765 char *s = ui[3] ? strchr(ui[3], PWDAUTHSEP) : NULL;
3766 int flags = ui[3] ? atoi(s ? ++s : ui[3]) : 0;
3768 hostlist[0].name = ui[2];
3769 if(ui[4]){
3770 hostlist[0].next = &hostlist[1];
3771 hostlist[1].name = ui[4];
3772 hostlist[1].next = NULL;
3774 else{
3775 hostlist[0].next = NULL;
3778 imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0);
3783 if (tmptext) fs_give((void **) &tmptext);
3784 #ifdef SMIME
3785 if (text) fs_give((void **)&text);
3786 #else /* SMIME */
3787 fclose(fp);
3788 #endif /* SMIME */
3789 return(1);
3790 #endif /* PASSFILE */
3795 void
3796 write_passfile(char *pinerc, MMLOGIN_S *l)
3798 char *authend, *authtype;
3799 #ifdef WINCRED
3800 # if (WINCRED > 0)
3801 unsigned long bloblen = 0, len;
3802 int i, totalparts, k, uid;
3803 char target[MAXPWDBUFFERSIZE];
3804 char *blob = NIL, blob2[50], *blobp;
3805 char part[MAILTMPLEN];
3806 CREDENTIAL cred;
3807 LPTSTR ltarget = 0;
3809 if(using_passfile == 0)
3810 return;
3812 dprint((9, "write_passfile\n"));
3814 erase_windows_credentials(); /* erase all passwords from credentials */
3815 /* start writing them back to credentials manager */
3816 for(uid = 1; l; l = l->next, uid++){ /* enforce that uid >= 1 */
3817 /* determine how many parts to create first */
3818 len = (l->passwd ? strlen(l->passwd) : 0)
3819 + ((l->hosts && l->hosts->next && l->hosts->next->name) ? 1 : 0)
3820 + ((l->hosts && l->hosts->next && l->hosts->next->name) ? strlen(l->hosts->next->name) : 0) + 1;
3822 if(len > bloblen){
3823 bloblen = len;
3824 fs_resize((void **) &blob, bloblen);
3827 sprintf(blob, "%s%s%s",
3828 l->passwd ? l->passwd : "",
3829 (l->hosts && l->hosts->next && l->hosts->next->name)
3830 ? "\t" : "",
3831 (l->hosts && l->hosts->next && l->hosts->next->name)
3832 ? l->hosts->next->name : "");
3833 i = len - 1; /* strlen(blob) */
3834 blobp = blob;
3835 for (totalparts = 1; i > MAXPWDBUFFERSIZE; totalparts++, i -= PWDBUFFERSIZE);
3836 authtype = l->passwd;
3837 authend = strchr(l->passwd, PWDAUTHSEP);
3839 if (authend != NULL){
3840 *authend = '\0';
3841 sprintf(blob2, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3842 *authend = PWDAUTHSEP;
3844 else
3845 sprintf(blob2, "%d", l->altflag);
3847 for (k = 1, i = len - 1, blobp = blob; k <= totalparts; k++) {
3848 snprintf(target, sizeof(target), "%s.%d.%d-%d_%s\t%s\t%s",
3849 TNAME, uid, k, totalparts,
3850 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3851 l->user ? l->user : "",
3852 blob2);
3853 ltarget = utf8_to_lptstr((LPSTR)target);
3854 if (ltarget) {
3855 memset((void*)&cred, 0, sizeof(cred));
3856 cred.Flags = 0;
3857 cred.Type = CRED_TYPE_GENERIC;
3858 cred.TargetName = ltarget;
3859 if (i > MAXPWDBUFFERSIZE) {
3860 strncpy(part, blobp, PWDBUFFERSIZE);
3861 part[PWDBUFFERSIZE] = '\0';
3862 blobp += PWDBUFFERSIZE;
3863 i -= PWDBUFFERSIZE;
3865 else
3866 strcpy(part, blobp);
3867 cred.CredentialBlobSize = strlen(part) + 1;
3868 cred.CredentialBlob = (LPBYTE)&part;
3869 cred.Persist = CRED_PERSIST_ENTERPRISE;
3870 g_CredWriteW(&cred, 0);
3871 fs_give((void**)&ltarget);
3875 if(blob) fs_give((void **) &blob);
3877 #endif /* WINCRED > 0 */
3879 #elif APPLEKEYCHAIN
3880 int rc;
3881 char target[10*MAILTMPLEN];
3882 char blob[10*MAILTMPLEN];
3883 SecKeychainItemRef itemRef = NULL;
3885 if(using_passfile == 0)
3886 return;
3888 dprint((9, "write_passfile\n"));
3890 for(; l; l = l->next){
3891 authtype = l->passwd;
3892 authend = strchr(l->passwd, PWDAUTHSEP);
3893 if(authend != NULL){
3894 *authend = '\0';
3895 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3896 *authend = PWDAUTHSEP;
3898 else
3899 sprintf(blob, "%d", l->altflag);
3901 snprintf(target, sizeof(target), "%s\t%s\t%s",
3902 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3903 l->user ? l->user : "",
3904 blob);
3906 snprintf(blob, sizeof(blob), "%s%s%s",
3907 l->passwd ? l->passwd : "",
3908 (l->hosts && l->hosts->next && l->hosts->next->name)
3909 ? "\t" : "",
3910 (l->hosts && l->hosts->next && l->hosts->next->name)
3911 ? l->hosts->next->name : "");
3913 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob));
3915 rc = SecKeychainAddGenericPassword(NULL,
3916 strlen(target), target,
3917 strlen(TNAME), TNAME,
3918 strlen(blob), blob,
3919 NULL);
3920 if(rc==0){
3921 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3923 else{
3924 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc));
3927 if(rc == errSecDuplicateItem){
3928 /* fix existing entry */
3929 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3930 itemRef = NULL;
3931 if(!(rc=SecKeychainFindGenericPassword(NULL,
3932 strlen(target), target,
3933 strlen(TNAME), TNAME,
3934 NULL, NULL,
3935 &itemRef)) && itemRef){
3937 rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob);
3938 if(!rc){
3939 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc));
3942 else{
3943 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc));
3948 #else /* PASSFILE */
3949 char *tmp = NULL, passfile[MAXPATH + 1], blob[MAILTMPLEN];
3950 int i, n;
3951 size_t tmplen = 0, newlen;
3952 FILE *fp;
3953 #ifdef SMIME
3954 char *text = NULL, tmp2[MAXPATH + 1];
3955 int len = 0;
3956 #endif
3958 if(using_passfile == 0)
3959 return;
3961 dprint((9, "write_passfile\n"));
3963 /* if there's no passfile to read, bag it!! */
3964 if(!passfile_name(pinerc, passfile, sizeof(passfile)) || !(fp = our_fopen(passfile, "wb"))){
3965 using_passfile = 0;
3966 return;
3969 #ifdef SMIME
3970 strncpy(tmp2, passfile, sizeof(tmp2));
3971 tmp2[sizeof(tmp2)-1] = '\0';
3972 #endif /* SMIME */
3974 for(n = 0; l; l = l->next, n++){
3975 authtype = l->passwd;
3976 authend = strchr(l->passwd, PWDAUTHSEP);
3977 if(authend != NULL){
3978 *authend = '\0';
3979 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3980 *authend = PWDAUTHSEP;
3982 else
3983 sprintf(blob, "%d", l->altflag);
3985 newlen = strlen(l->passwd) + strlen(l->user) + strlen(l->hosts->name)
3986 + strlen(blob) + strlen((l->hosts->next && l->hosts->next->name) ? "\t" : "")
3987 + strlen((l->hosts->next && l->hosts->next->name) ? l->hosts->next->name : "")
3988 + 4 + 1;
3990 if(tmplen < newlen){
3991 fs_resize((void **)&tmp, newlen);
3992 tmplen = newlen;
3995 /*** do any necessary ENcryption here ***/
3996 sprintf(tmp, "%s\t%s\t%s\t%s%s%s\n", l->passwd, l->user,
3997 l->hosts->name, blob,
3998 (l->hosts->next && l->hosts->next->name) ? "\t" : "",
3999 (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
4000 : "");
4001 dprint((10, "write_passfile: %s", tmp ? tmp : "?"));
4002 xlate_key = n;
4003 for(i = 0; tmp[i]; i++)
4004 tmp[i] = xlate_in(tmp[i]);
4006 #ifdef SMIME
4007 fs_resize((void **)&text, (len + strlen(tmp) + 1)*sizeof(char));
4008 text[len] = '\0';
4009 len += strlen(tmp) + 1;
4010 strncat(text, tmp, strlen(tmp));
4011 #else /* SMIME */
4012 fputs(tmp, fp);
4013 #endif /* SMIME */
4016 if(tmp) fs_give((void **) &tmp);
4017 fclose(fp);
4018 #ifdef SMIME
4019 if(text != NULL){
4020 i = 0; /* to quell gcc */
4021 if(ps_global->pwdcert == NULL){
4022 q_status_message(SM_ORDER, 3, 3, "Attempting to encrypt password file");
4023 i = setup_pwdcert(&ps_global->pwdcert);
4024 if((i == 0 || i == -5) && ps_global->pwdcert == NULL)
4025 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
4027 if(ps_global->pwdcert == NULL){ /* we tried but failed */
4028 if(i == -1)
4029 q_status_message1(SM_ORDER, 3, 3, "Error: no directory %s to save certificates", ps_global->pwdcertdir);
4030 else
4031 q_status_message(SM_ORDER, 3, 3, "Refusing to write non-encrypted password file");
4033 else if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0)
4034 q_status_message(SM_ORDER, 3, 3, "Failed to encrypt password file");
4035 fs_give((void **)&text); /* do not save this text */
4037 #endif /* SMIME */
4038 #endif /* PASSFILE */
4041 #endif /* LOCAL_PASSWD_CACHE */
4044 #if (WINCRED > 0)
4045 void
4046 erase_windows_credentials(void)
4048 LPCTSTR lfilter = TEXT(TNAMESTAR);
4049 DWORD count, k;
4050 PCREDENTIAL *pcred;
4052 if(!g_CredInited){
4053 if(init_wincred_funcs() != 1)
4054 return;
4057 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
4058 if(pcred){
4059 for(k = 0; k < count; k++)
4060 g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0);
4062 g_CredFree((PVOID) pcred);
4067 void
4068 ask_erase_credentials(void)
4070 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){
4071 erase_windows_credentials();
4072 q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased");
4074 else
4075 q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased");
4078 #endif /* WINCRED */
4081 #ifdef LOCAL_PASSWD_CACHE
4083 get_passfile_passwd(char *pinerc, char **passwd, char *user, STRLIST_S *hostlist, int altflag)
4085 return get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, NULL);
4089 * get_passfile_passwd_auth - return the password contained in the special password
4090 * cache. The file is assumed to be in the same directory
4091 * as the pinerc with the name defined above.
4094 get_passfile_passwd_auth(char *pinerc, char **passwd, char *user, STRLIST_S *hostlist, int altflag, char *authtype)
4096 dprint((10, "get_passfile_passwd_auth\n"));
4097 return((mm_login_list || read_passfile(pinerc, &mm_login_list))
4098 ? imap_get_passwd_auth(mm_login_list, passwd,
4099 user, hostlist, altflag, authtype)
4100 : 0);
4104 is_using_passfile(void)
4106 return(using_passfile == 1);
4110 * Just trying to guess the username the user might want to use on this
4111 * host, the user will confirm.
4113 char *
4114 get_passfile_user(char *pinerc, STRLIST_S *hostlist)
4116 return((mm_login_list || read_passfile(pinerc, &mm_login_list))
4117 ? imap_get_user(mm_login_list, hostlist)
4118 : NULL);
4123 preserve_prompt(char *pinerc)
4125 return preserve_prompt_auth(pinerc, NULL);
4129 preserve_prompt_auth(char *pinerc, char *authtype)
4131 ps_global->preserve_password = 0;
4132 #ifdef WINCRED
4133 # if (WINCRED > 0)
4134 #define PROMPT_PWD _("Preserve password for next login")
4135 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4137 * This prompt was going to be able to be turned on and off via a registry
4138 * setting controlled from the config menu. We decided to always use the
4139 * dialog for login, and there the prompt is unobtrusive enough to always
4140 * be in there. As a result, windows should never reach this, but now
4141 * OS X somewhat uses the behavior just described.
4143 if(mswin_store_pass_prompt()
4144 && (want_to(authtype
4145 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4146 : PROMPT_PWD,
4147 'y', 'x', NO_HELP, WT_NORM)
4148 == 'y')){
4149 ps_global->preserve_password = 1;
4150 return(1);
4152 else
4153 return(0);
4154 # else
4155 return(0);
4156 # endif
4158 #elif APPLEKEYCHAIN
4159 #define PROMPT_PWD _("Preserve password for next login")
4160 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4162 int rc;
4163 if((rc = macos_store_pass_prompt()) != 0){
4164 if(want_to(authtype
4165 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4166 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4167 == 'y'){
4168 ps_global->preserve_password = 1;
4169 if(rc == -1){
4170 macos_set_store_pass_prompt(1);
4171 q_status_message(SM_ORDER, 4, 4,
4172 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4174 return(1);
4176 else if(rc == -1){
4177 macos_set_store_pass_prompt(0);
4178 q_status_message(SM_ORDER, 4, 4,
4179 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4181 return(0);
4183 return(0);
4184 #else /* PASSFILE */
4185 #define PROMPT_PWD _("Preserve password on DISK for next login")
4186 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
4188 char tmp[MAILTMPLEN];
4189 struct stat sbuf;
4191 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || our_stat(tmp, &sbuf) < 0)
4192 return 0;
4194 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
4195 if(want_to(authtype
4196 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4197 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4198 == 'y'){
4199 ps_global->preserve_password = 1;
4200 return 1;
4202 return(0);
4203 #endif /* PASSFILE */
4206 #endif /* LOCAL_PASSWD_CACHE */
4209 #ifdef APPLEKEYCHAIN
4212 * Returns:
4213 * 1 if store pass prompt is set in the "registry" to on
4214 * 0 if set to off
4215 * -1 if not set to anything
4218 macos_store_pass_prompt(void)
4220 char *data = NULL;
4221 UInt32 len = 0;
4222 int rc = -1;
4223 int val;
4225 if(storepassprompt == -1){
4226 if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL,
4227 strlen(TNAMEPROMPT),
4228 TNAMEPROMPT, &len,
4229 (void **) &data, NULL))){
4230 val = (len == 1 && data && data[0] == '1');
4234 if(storepassprompt == -1 && !rc){
4235 if(val)
4236 storepassprompt = 1;
4237 else
4238 storepassprompt = 0;
4241 return(storepassprompt);
4245 void
4246 macos_set_store_pass_prompt(int val)
4248 storepassprompt = val ? 1 : 0;
4250 SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT),
4251 TNAMEPROMPT, 1, val ? "1" : "0", NULL);
4255 void
4256 macos_erase_keychain(void)
4258 SecKeychainAttributeList attrList;
4259 SecKeychainSearchRef searchRef = NULL;
4260 SecKeychainAttribute attrs1[] = {
4261 { kSecAccountItemAttr, strlen(TNAME), TNAME }
4263 SecKeychainAttribute attrs2[] = {
4264 { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT }
4267 dprint((9, "macos_erase_keychain\n"));
4270 * Seems like we ought to be able to combine attrs1 and attrs2
4271 * into a single array, but I couldn't get it to work.
4274 /* search for only our items in the keychain */
4275 attrList.count = 1;
4276 attrList.attr = attrs1;
4278 if(!SecKeychainSearchCreateFromAttributes(NULL,
4279 kSecGenericPasswordItemClass,
4280 &attrList,
4281 &searchRef)){
4282 if(searchRef){
4283 SecKeychainItemRef itemRef = NULL;
4286 * Go through each item we found and put it
4287 * into our list.
4289 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4290 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4291 SecKeychainItemDelete(itemRef);
4292 CFRelease(itemRef);
4295 CFRelease(searchRef);
4299 attrList.count = 1;
4300 attrList.attr = attrs2;
4302 if(!SecKeychainSearchCreateFromAttributes(NULL,
4303 kSecGenericPasswordItemClass,
4304 &attrList,
4305 &searchRef)){
4306 if(searchRef){
4307 SecKeychainItemRef itemRef = NULL;
4310 * Go through each item we found and put it
4311 * into our list.
4313 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4314 SecKeychainItemDelete(itemRef);
4315 CFRelease(itemRef);
4318 CFRelease(searchRef);
4323 #endif /* APPLEKEYCHAIN */
4325 #ifdef LOCAL_PASSWD_CACHE
4327 void
4328 set_passfile_passwd(char *pinerc, char **passwd, char *user, STRLIST_S *hostlist, int altflag, int already_prompted)
4330 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, NULL);
4333 * set_passfile_passwd - set the password file entry associated with
4334 * cache. The file is assumed to be in the same directory
4335 * as the pinerc with the name defined above.
4336 * already_prompted: 0 not prompted
4337 * 1 prompted, answered yes
4338 * 2 prompted, answered no
4340 void
4341 set_passfile_passwd_auth(char *pinerc, char **passwd, char *user, STRLIST_S *hostlist, int altflag, int already_prompted, char *authtype)
4343 dprint((10, "set_passfile_passwd_auth\n"));
4344 if(((already_prompted == 0 && preserve_prompt_auth(pinerc, authtype))
4345 || already_prompted == 1)
4346 && !ps_global->nowrite_password_cache
4347 && (mm_login_list || read_passfile(pinerc, &mm_login_list))){
4348 imap_set_passwd_auth(&mm_login_list, *passwd, user, hostlist, altflag, 0, 0, authtype);
4349 write_passfile(pinerc, mm_login_list);
4353 void
4354 update_passfile_hostlist(char *pinerc, char *user, STRLIST_S *hostlist, int altflag)
4356 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, NULL);
4360 * Passfile lines are
4362 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4364 * In pine4.40 and before there was no orig_hostname.
4365 * This routine attempts to repair that.
4367 void
4368 update_passfile_hostlist_auth(char *pinerc, char *user, STRLIST_S *hostlist, int altflag, char *authtype)
4370 #ifdef WINCRED
4371 return;
4372 #else /* !WINCRED */
4373 MMLOGIN_S *l;
4374 size_t len = authtype ? strlen(authtype) : 0;
4375 size_t offset = authtype ? 1 : 0;
4377 for(l = mm_login_list; l; l = l->next)
4378 if(imap_same_host_auth(l->hosts, hostlist, authtype)
4379 && *user
4380 && !strcmp(user, l->user + len + offset)
4381 && l->altflag == altflag){
4382 break;
4385 if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
4386 && hostlist->next->name
4387 && !ps_global->nowrite_password_cache){
4388 l->hosts->next = new_strlist_auth(hostlist->next->name, authtype, PWDAUTHSEP);
4389 write_passfile(pinerc, mm_login_list);
4391 #endif /* !WINCRED */
4394 #endif /* LOCAL_PASSWD_CACHE */
4397 #if (WINCRED > 0)
4399 * Load and init the WinCred structure.
4400 * This gives us a way to skip the WinCred code
4401 * if the dll doesn't exist.
4404 init_wincred_funcs(void)
4406 if(!g_CredInited)
4408 HMODULE hmod;
4410 /* Assume the worst. */
4411 g_CredInited = -1;
4413 hmod = LoadLibrary(TEXT("advapi32.dll"));
4414 if(hmod)
4416 FARPROC fpCredWriteW;
4417 FARPROC fpCredEnumerateW;
4418 FARPROC fpCredDeleteW;
4419 FARPROC fpCredFree;
4421 fpCredWriteW = GetProcAddress(hmod, "CredWriteW");
4422 fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW");
4423 fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW");
4424 fpCredFree = GetProcAddress(hmod, "CredFree");
4426 if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree)
4428 g_CredWriteW = (CREDWRITEW *)fpCredWriteW;
4429 g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW;
4430 g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW;
4431 g_CredFree = (CREDFREE *)fpCredFree;
4433 g_CredInited = 1;
4437 mswin_set_erasecreds_callback(ask_erase_credentials);
4440 return g_CredInited;
4443 #endif /* WINCRED */