* New alpha version 2.24.1
[alpine.git] / alpine / imap.c
blob887c56e9808ea674d4a29e87f6664eb35492086b
1 /*
2 * ========================================================================
3 * Copyright 2013-2021 Eduardo Chappa
4 * Copyright 2006-2009 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 /*======================================================================
16 imap.c
17 The call back routines for the c-client/imap
18 - handles error messages and other notification
19 - handles prelimirary notification of new mail and expunged mail
20 - prompting for imap server login and password
22 ====*/
24 #include "headers.h"
25 #include "alpine.h"
26 #include "imap.h"
27 #include "status.h"
28 #include "mailview.h"
29 #include "mailcmd.h"
30 #include "radio.h"
31 #include "keymenu.h"
32 #include "signal.h"
33 #include "mailpart.h"
34 #include "mailindx.h"
35 #include "arg.h"
36 #include "busy.h"
37 #include "titlebar.h"
38 #include "xoauth2.h"
39 #include "xoauth2conf.h"
40 #include "confscroll.h"
41 #include "init.h"
42 #include "../pith/state.h"
43 #include "../pith/conf.h"
44 #include "../pith/msgno.h"
45 #include "../pith/filter.h"
46 #include "../pith/news.h"
47 #include "../pith/util.h"
48 #include "../pith/list.h"
49 #include "../pith/margin.h"
50 #ifdef SMIME
51 #include "../pith/smime.h"
52 #endif /* SMIME */
54 #if (WINCRED > 0)
55 #include <wincred.h>
56 #define TNAME "UWash_Alpine_"
57 #define TNAMESTAR "UWash_Alpine_*"
58 #define PWDBUFFERSIZE (250)
59 #define MAXPWDBUFFERSIZE (2*PWDBUFFERSIZE) /* This number must be less than 512 */
62 * WinCred Function prototypes
64 typedef BOOL (WINAPI CREDWRITEW) ( __in PCREDENTIALW Credential, __in DWORD Flags );
65 typedef BOOL (WINAPI CREDENUMERATEW) ( __in LPCWSTR Filter, __reserved DWORD Flags,
66 __out DWORD *Count, __deref_out_ecount(*Count) PCREDENTIALW **Credential );
67 typedef BOOL (WINAPI CREDDELETEW) ( __in LPCWSTR TargetName, __in DWORD Type,
68 __reserved DWORD Flags );
69 typedef VOID (WINAPI CREDFREE) ( __in PVOID Buffer );
72 * WinCred functions
74 int g_CredInited = 0; /* 1 for loaded successfully,
75 * -1 for not available.
76 * 0 for not initialized yet.
78 CREDWRITEW *g_CredWriteW;
79 CREDENUMERATEW *g_CredEnumerateW;
80 CREDDELETEW *g_CredDeleteW;
81 CREDFREE *g_CredFree;
83 #endif /* WINCRED */
85 #ifdef APPLEKEYCHAIN
86 #include <Security/SecKeychain.h>
87 #include <Security/SecKeychainItem.h>
88 #include <Security/SecKeychainSearch.h>
89 #define TNAME "UWash_Alpine"
90 #define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
92 int macos_store_pass_prompt(void);
93 void macos_set_store_pass_prompt(int);
95 static int storepassprompt = -1;
96 #endif /* APPLEKEYCHAIN */
100 * Internal prototypes
102 void mm_login_alt_cue(NETMBX *);
103 long pine_tcptimeout_noscreen(long, long, char *);
104 int answer_cert_failure(int, MSGNO_S *, SCROLL_S *);
105 int oauth2_auth_answer(int, MSGNO_S *, SCROLL_S *);
106 OAUTH2_S *oauth2_select_flow(char *);
107 int xoauth2_flow_tool(struct pine *, int, CONF_S **, unsigned int);
109 #ifdef LOCAL_PASSWD_CACHE
110 int read_passfile(char *, MMLOGIN_S **);
111 void write_passfile(char *, MMLOGIN_S *);
112 int preserve_prompt(char *);
113 int preserve_prompt_auth(char *, char *authtype);
114 void update_passfile_hostlist(char *, char *, STRLIST_S *, int);
115 void update_passfile_hostlist_auth(char *, char *, STRLIST_S *, int, char *);
116 void free_passfile_cache_work(MMLOGIN_S **);
118 static MMLOGIN_S *passfile_cache = NULL;
119 static int using_passfile = -1;
120 int save_password = 1;
121 #endif /* LOCAL_PASSWD_CACHE */
123 #ifdef PASSFILE
124 char xlate_in(int);
125 char xlate_out(char);
126 int line_get(char *, size_t, char **);
127 #endif /* PASSFILE */
129 #if (WINCRED > 0)
130 void ask_erase_credentials(void);
131 int init_wincred_funcs(void);
132 #endif /* WINCRED */
135 static char *details_cert, *details_host, *details_reason;
137 extern XOAUTH2_INFO_S xoauth_default[];
140 * This is the private information of the client, which is passed to
141 * c-client for processing. Every c-client application must have its
142 * own.
144 OAUTH2_S alpine_oauth2_list[] = {
145 {GMAIL_NAME,
146 {"imap.gmail.com", "smtp.gmail.com", NULL, NULL},
147 {{"client_id", NULL},
148 {"client_secret", NULL},
149 {"tenant", NULL}, /* not used */
150 {"code", NULL}, /* access code from the authorization process */
151 {"refresh_token", NULL},
152 {"scope", "https://mail.google.com/"},
153 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
154 {"grant_type", "authorization_code"},
155 {"grant_type", "refresh_token"},
156 {"response_type", "code"},
157 {"state", NULL},
158 {"device_code", NULL} /* not used */
160 {{"GET", (unsigned char *) "https://accounts.google.com/o/oauth2/auth", /* authorization address, get access code */
161 {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End}},
162 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* Device Info information, not used */
163 {"POST", (unsigned char *) "https://accounts.google.com/o/oauth2/token", /* Address to get refresh token from access code */
164 {OA2_Id, OA2_Secret, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Code, OA2_End, OA2_End}},
165 {"POST", (unsigned char *) "https://accounts.google.com/o/oauth2/token", /* access token from refresh token */
166 {OA2_Id, OA2_Secret, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}
168 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information */
169 NULL, /* access token */
170 NULL, /* special IMAP ID */
171 0, /* do not hide */
172 0, /* expiration time */
173 0, /* first time indicator */
174 1, /* client secret required */
175 0, /* Cancel refresh token */
176 GMAIL_FLAGS /* default flags. For Gmail this should be set to OA2_AUTHORIZE */
178 {OUTLOOK_NAME,
179 {"outlook.office365.com", "smtp.office365.com", NULL, NULL},
180 {{"client_id", NULL},
181 {"client_secret", NULL}, /* not used, but needed */
182 {"tenant", NULL}, /* used */
183 {"code", NULL}, /* not used, not needed */
184 {"refresh_token", NULL},
185 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
186 {"grant_type", "urn:ietf:params:oauth:grant-type:device_code"},
187 {"scope", NULL}, /* not used */
188 {"grant_type", "refresh_token"},
189 {"response_type", "code"}, /* not used */
190 {"state", NULL}, /* not used */
191 {"device_code", NULL} /* only used for frst time set up */
193 {{NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* Get Access Code, Not used */
194 {"POST", (unsigned char *) "https://login.microsoftonline.com/\001/oauth2/v2.0/devicecode", /* first time use and get device code information */
195 {OA2_Id, OA2_Scope, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}},
196 {"POST", (unsigned char *) "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
197 {OA2_Id, OA2_Redirect, OA2_DeviceCode, OA2_End, OA2_End, OA2_End, OA2_End}},
198 {"POST", (unsigned char *) "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
199 {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}
201 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information */
202 NULL, /* access token */
203 NULL, /* special IMAP ID */
204 0, /* do not hide */
205 0, /* expiration time */
206 0, /* first time indicator */
207 0, /* client secret required */
208 0, /* Cancel refresh token */
209 OUTLOOK_FLAGS /* default flags. For OUTLOOK this should be set to OA2_DEVICE */
211 {OUTLOOK_NAME,
212 {"outlook.office365.com", "smtp.office365.com", NULL, NULL},
213 {{"client_id", NULL},
214 {"client_secret", NULL}, /* not used, but needed */
215 {"tenant", NULL}, /* used */
216 {"code", NULL}, /* used during authorization */
217 {"refresh_token", NULL},
218 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
219 {"redirect_uri", "http://localhost"},
220 {"grant_type", "authorization_code"},
221 {"grant_type", "refresh_token"},
222 {"response_type", "code"},
223 {"state", NULL}, /* not used */
224 {"device_code", NULL} /* not used */
226 {{"GET", (unsigned char *) "https://login.microsoftonline.com/\001/oauth2/v2.0/authorize", /* Get Access Code */
227 {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End}},
228 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
229 {"POST", (unsigned char *) "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
230 {OA2_Id, OA2_Redirect, OA2_Scope, OA2_GrantTypeforAccessToken, OA2_Secret, OA2_Code, OA2_End}},
231 {"POST", (unsigned char *) "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
232 {OA2_Id, OA2_RefreshToken, OA2_Scope, OA2_GrantTypefromRefreshToken, OA2_Secret, OA2_End, OA2_End}}
234 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
235 NULL, /* access token */
236 NULL, /* special IMAP ID */
237 0, /* do not hide */
238 0, /* expiration time */
239 0, /* first time indicator */
240 1, /* client secret required */
241 0, /* Cancel refresh token */
242 OUTLOOK_FLAGS /* default flags. For OUTLOOK this should be set to OA2_DEVICE */
244 {YAHOO_NAME,
245 {"imap.mail.yahoo.com", "smtp.mail.yahoo.com", NULL, NULL},
246 {{"client_id", NULL},
247 {"client_secret", NULL}, /* used */
248 {"tenant", NULL}, /* not used */
249 {"code", NULL}, /* used during authorization */
250 {"refresh_token", NULL},
251 {"scope", NULL}, /* not used! */
252 {"redirect_uri", "oob"}, /* https://localhost */
253 {"grant_type", "authorization_code"},
254 {"grant_type", "refresh_token"},
255 {"response_type", "code"},
256 {"state", NULL}, /* used */
257 {"device_code", NULL} /* not used */
259 {{"GET", (unsigned char *) "https://api.login.yahoo.com/oauth2/request_auth", /* Get Access Code */
260 {OA2_Id, OA2_Redirect, OA2_Response, OA2_State, OA2_End, OA2_End, OA2_End}},
261 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
262 {"POST", (unsigned char *) "https://api.login.yahoo.com/oauth2/get_token", /* Get first Refresh Token and Access token */
263 {OA2_Id, OA2_Secret, OA2_Redirect, OA2_Code, OA2_GrantTypeforAccessToken, OA2_End, OA2_End}},
264 {"POST", (unsigned char *) "https://api.login.yahoo.com/oauth2/get_token", /* Get access token from refresh token */
265 {OA2_Id, OA2_Secret, OA2_Redirect, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End}}
267 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
268 NULL, /* access token */
269 "ALPINE_V1", /* special IMAP ID */
270 1, /* hide */
271 0, /* expiration time */
272 0, /* first time indicator */
273 1, /* client secret required */
274 0, /* Cancel refresh token */
275 YAHOO_FLAGS /* default flags. For YAHOO this should be set to OA2_AUTHORIZE */
277 {YANDEX_NAME,
278 {"imap.yandex.com", "smtp.yandex.com", NULL, NULL},
279 {{"client_id", NULL},
280 {"client_secret", NULL}, /* not used, but needed */
281 {"tenant", NULL}, /* not used */
282 {"code", NULL}, /* used during authorization */
283 {"refresh_token", NULL},
284 {"scope", NULL}, /* not needed, so not used */
285 {"redirect_uri", "https://oauth.yandex.ru/verification_code"},
286 {"grant_type", "authorization_code"},
287 {"grant_type", "refresh_token"},
288 {"response_type", "code"},
289 {"state", NULL}, /* not used */
290 {"device_code", NULL} /* not used */
292 {{"GET", (unsigned char *) "https://oauth.yandex.com/authorize", /* Get Access Code */
293 {OA2_Id, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End, OA2_End}},
294 {NULL, NULL, {OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End, OA2_End}}, /* device code, not used */
295 {"POST", (unsigned char *) "https://oauth.yandex.com/token", /* Get first Refresh Token and Access token */
296 {OA2_Id, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Secret, OA2_Code, OA2_End, OA2_End}},
297 {"POST", (unsigned char *) "https://oauth.yandex.com/token", /* Get access token from refresh token */
298 {OA2_Id, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_Secret, OA2_End, OA2_End, OA2_End}}
300 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information, not used */
301 NULL, /* access token */
302 NULL, /* special IMAP ID */
303 0, /* do not hide */
304 0, /* expiration time */
305 0, /* first time indicator */
306 1, /* client secret required */
307 0, /* Cancel refresh token */
308 YANDEX_FLAGS /* default flags. For YANDEX this should be set to OA2_AUTHORIZE */
310 {NULL, /* Name, unsigned char * */
311 {NULL, NULL, NULL, NULL }, /* host and equivalents */
312 {{NULL, NULL}, /* client-id */
313 {NULL, NULL}, /* client-secret */
314 {NULL, NULL}, /* tenant - outlook */
315 {NULL, NULL}, /* access_code, for authorize method */
316 {NULL, NULL}, /* refresh token */
317 {NULL, NULL}, /* scope */
318 {NULL, NULL}, /* redirect */
319 {NULL, NULL}, /* grant type for access token */
320 {NULL, NULL}, /* grant type from refresh token */
321 {NULL, NULL}, /* response */
322 {NULL, NULL}, /* state */
323 {NULL, NULL} /* device code */
325 {{NULL, NULL, {0, 0, 0, 0, 0, 0, 0}}, /* method, server, parameters for authorize */
326 {NULL, NULL, {0, 0, 0, 0, 0, 0, 0}}, /* method, server, parameters for device login */
327 {NULL, NULL, {0, 0, 0, 0, 0, 0, 0}}, /* method, server, parameters for getting refresh token */
328 {NULL, NULL, {0, 0, 0, 0, 0, 0, 0}} /* method, server, parameters for refreshing access token */
330 {NULL, NULL, NULL, 0, 0, NULL}, /* device_code information */
331 NULL, /* access token */
332 NULL, /* special IMAP ID */
333 0, /* Hide this */
334 0, /* expiration time */
335 0, /* first time */
336 0, /* require secret */
337 0, /* cancel refresh token */
338 0 /* flags */
343 xoauth2_flow_tool(struct pine *ps, int cmd, CONF_S **cl, unsigned int flags)
345 int rv = 0;
347 switch(cmd){
348 case MC_CHOICE:
349 *((*cl)->d.xf.selected) = (*cl)->d.xf.pat;
350 rv = simple_exit_cmd(flags);
352 case MC_EXIT:
353 rv = simple_exit_cmd(flags);
354 break;
356 default:
357 rv = -1;
360 if(rv > 0)
361 ps->mangled_body = 1;
363 return rv;
366 OAUTH2_S *
367 oauth2_select_flow(char *host)
369 OAUTH2_S *oa2list, *oa2;
370 int i, rv;
371 char *method;
373 if(ps_global->ttyo){
374 CONF_S *ctmp = NULL, *first_line = NULL;
375 OAUTH2_S *x_sel = NULL;
376 OPT_SCREEN_S screen;
377 char tmp[1024];
379 dprint((9, "xoauth2 select flow"));
380 ps_global->next_screen = SCREEN_FUN_NULL;
382 memset(&screen, 0, sizeof(screen));
384 for(i = 0; i < sizeof(tmp) && i < ps_global->ttyo->screen_cols; i++)
385 tmp[i] = '-';
386 tmp[i] = '\0';
388 new_confline(&ctmp);
389 ctmp->flags |= CF_NOSELECT;
390 ctmp->value = cpystr(tmp);
392 new_confline(&ctmp);
393 ctmp->flags |= CF_NOSELECT;
394 ctmp->value = cpystr(_("Please select below the authorization flow you would like to follow:"));
396 new_confline(&ctmp);
397 ctmp->flags |= CF_NOSELECT;
398 ctmp->value = cpystr(tmp);
400 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
401 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
402 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
403 new_confline(&ctmp);
404 if(!first_line)
405 first_line = ctmp;
406 method = oa2list->server_mthd[0].name ? "Authorize"
407 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
408 sprintf(tmp, "%s (%s)", oa2list->name, method);
409 ctmp->value = cpystr(tmp);
410 ctmp->d.xf.selected = &x_sel;
411 ctmp->d.xf.pat = oa2list;
412 ctmp->keymenu = &xoauth2_id_select_km;
413 ctmp->help = NO_HELP;
414 ctmp->help_title = NULL;
415 ctmp->tool = xoauth2_flow_tool;
416 ctmp->flags = CF_STARTITEM;
417 ctmp->valoffset = 4;
420 (void)conf_scroll_screen(ps_global, &screen, first_line, _("SELECT AUTHORIZATION FLOW"),
421 _("xoauth2"), 0, NULL);
422 oa2 = x_sel;
424 else{
425 char *s;
426 char prompt[1024];
427 char reply[1024];
428 int sel, n = 0, j;
430 for(oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++)
431 n += strlen((char *) oa2list->name) + 5; /* number, parenthesis, space */
432 n += 1024; /* large enough to display lines of 80 characters in UTF-8 */
433 s = fs_get(n*sizeof(char));
434 strcpy(s, _("Please select below the authorization flow you would like to follow:"));
435 sprintf(s + strlen(s), _("Please select the client-id to use from the following list.\n\n"));
436 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
437 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
438 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i])
439 sprintf(s + strlen(s), " %d) %.70s\n", j++, oa2list->name);
441 display_init_err(s, 0);
443 strncpy(prompt, _("Enter your selection number: "), sizeof(prompt));
444 prompt[sizeof(prompt)-1] = '\0';
446 rv = optionally_enter(reply, 0, 0, sizeof(reply), prompt, NULL, NO_HELP, 0);
447 sel = atoi(reply);
448 rv = (sel >= 0 && sel < i) ? 0 : -1;
449 } while (rv != 0);
451 for(j = 1, oa2list = alpine_oauth2_list; oa2list && oa2list->name ;oa2list++){
452 for(i = 0; oa2list && oa2list->host && oa2list->host[i] && strucmp(oa2list->host[i], host); i++);
453 if(oa2list && oa2list->host && i < OAUTH2_TOT_EQUIV && oa2list->host[i]){
454 if(j == sel) break;
455 else j++;
458 oa2 = oa2list;
460 return oa2;
463 typedef struct auth_code_s {
464 char *code;
465 int answer;
466 } AUTH_CODE_S;
469 oauth2device_decode_reply(void *datap, void *replyp)
471 OAUTH2_DEVICEPROC_S *av = (OAUTH2_DEVICEPROC_S *)datap;
472 int reply = *(int *) replyp;
474 return reply < 0 ? av->code_failure : (reply == 0 ? av->code_success : av->code_wait);
478 oauth2_elapsed_done(void *aux_valuep)
480 OAUTH2_S *oauth2 = aux_valuep ? ((OAUTH2_DEVICEPROC_S *) aux_valuep)->xoauth2 : NULL;
481 static time_t savedt = 0, now;
482 int rv = 0;
484 if(aux_valuep == NULL) savedt = 0; /* reset last time we approved */
485 else{
486 now = time(0);
487 if(oauth2->devicecode.interval + now >= savedt)
488 savedt = now;
489 else
490 rv = -1;
492 return rv;
495 void
496 oauth2_set_device_info(OAUTH2_S *oa2, char *method)
498 char tmp[MAILTMPLEN];
499 char *name = (char *) oa2->name;
500 int aux_rv_value;
501 OAUTH2_DEVICECODE_S *deviceinfo = &oa2->devicecode;
502 OAUTH2_DEVICEPROC_S aux_value;
504 if(ps_global->ttyo){
505 SCROLL_S sargs;
506 STORE_S *in_store, *out_store;
507 gf_io_t pc, gc;
508 HANDLE_S *handles = NULL;
509 AUTH_CODE_S user_input;
511 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
512 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
513 goto try_wantto;
515 aux_value.xoauth2 = oa2;
516 aux_value.code_success = 'e';
517 aux_value.code_failure = 'e';
518 aux_value.code_wait = NO_OP_COMMAND;
520 so_puts(in_store, "<HTML><P>");
521 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name);
522 so_puts(in_store, tmp);
523 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name, method),
524 so_puts(in_store, tmp);
526 if(deviceinfo->verification_uri && deviceinfo->user_code){
527 sprintf(tmp,
528 _("</P><P>To sign in, use a web browser to open the page <A HREF=\"%s\">%s</A> and enter the code \"%s\" without the quotes."),
529 deviceinfo->verification_uri, deviceinfo->verification_uri, deviceinfo->user_code);
530 so_puts(in_store, tmp);
532 else{
533 so_puts(in_store, "</P><P>");
534 so_puts(in_store, (char *) deviceinfo->message);
536 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
537 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), name);
538 so_puts(in_store, tmp);
539 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
541 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 "));
542 so_puts(in_store, _("to grant access to Alpine to your data. "));
543 so_puts(in_store, _("</P><P> Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. If you do "));
544 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 "));
545 so_puts(in_store, _("alive at the end of this process, and your connection will proceed from there."));
546 so_puts(in_store, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit"));
547 so_puts(in_store, _("</P></HTML>"));
549 so_seek(in_store, 0L, 0);
550 init_handles(&handles);
551 gf_filter_init();
552 gf_link_filter(gf_html2plain,
553 gf_html2plain_opt(NULL,
554 ps_global->ttyo->screen_cols, non_messageview_margin(),
555 &handles, NULL, GFHP_LOCAL_HANDLES));
556 gf_set_so_readc(&gc, in_store);
557 gf_set_so_writec(&pc, out_store);
558 gf_pipe(gc, pc);
559 gf_clear_so_writec(out_store);
560 gf_clear_so_readc(in_store);
562 memset(&sargs, 0, sizeof(SCROLL_S));
563 sargs.text.handles = handles;
564 sargs.text.text = so_text(out_store);
565 sargs.text.src = CharStar;
566 sargs.text.desc = _("help text");
567 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
568 sargs.proc.tool = oauth2_auth_answer;
569 sargs.proc.data.p = (void *)&user_input;
570 sargs.keys.menu = &oauth2_device_auth_keymenu;
571 /* don't want to re-enter c-client */
572 sargs.quell_newmail = 1;
573 setbitmap(sargs.keys.bitmap);
574 sargs.help.text = h_oauth2_start;
575 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
576 sargs.aux_function = oauth2deviceinfo_get_accesscode;
577 sargs.aux_value = (void *) &aux_value;
578 sargs.aux_condition = oauth2_elapsed_done;
579 sargs.decode_aux_rv_value = oauth2device_decode_reply;
580 sargs.aux_rv_value = (void *) &aux_rv_value;
582 do {
583 scrolltool(&sargs);
584 ps_global->mangled_screen = 1;
585 ps_global->painted_body_on_startup = 0;
586 ps_global->painted_footer_on_startup = 0;
587 } while (user_input.answer != 'e');
589 so_give(&in_store);
590 so_give(&out_store);
591 free_handles(&handles);
592 oauth2_elapsed_done(NULL);
594 else{
596 * If screen hasn't been initialized yet, use want_to.
598 try_wantto:
600 tmp_20k_buf[0] = '\0';
601 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
602 _("Authorizing Alpine Access to %s Email Services\n\n"), name);
603 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
605 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
606 _("Alpine is attempting to log you into your %s account, using the %s method. "), name, method),
607 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
609 if(deviceinfo->verification_uri && deviceinfo->user_code){
610 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
611 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
612 deviceinfo->verification_uri, deviceinfo->user_code);
613 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
615 else{
616 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
617 "%s\n\n", deviceinfo->message);
618 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
621 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
622 _("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);
623 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
625 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
626 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
627 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
629 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
630 "%s", _("to grant access to Alpine to your data.\n\n"));
631 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
633 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
634 "%s", _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
635 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
637 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
638 "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
639 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
641 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
642 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
643 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
645 display_init_err(tmp_20k_buf, 0);
646 memset((void *)tmp, 0, sizeof(tmp));
647 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
648 tmp[sizeof(tmp)-1] = '\0';
650 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
651 int rv;
652 UCS ch;
654 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
655 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
656 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
658 aux_value.xoauth2 = oa2;
659 aux_value.code_success = 'y';
660 aux_value.code_failure = 'n';
661 aux_value.code_wait = 'w';
663 strncpy(tmp, _("Continue waiting"), sizeof(tmp));
664 tmp[sizeof(tmp)-1] = '\0';
665 do {
666 if(oauth2_elapsed_done((void *) &aux_value) == 0)
667 oauth2deviceinfo_get_accesscode((void *) &aux_value, (void *) &rv);
668 ch = oauth2device_decode_reply((void *) &aux_value, (void *) &rv);
669 } while (ch == 'w' || want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y');
670 oauth2_elapsed_done(NULL);
675 char *
676 oauth2_get_access_code(unsigned char *url, char *method, OAUTH2_S *oauth2, int *tryanother)
678 char tmp[MAILTMPLEN];
679 char *code;
681 if(ps_global->ttyo){
682 SCROLL_S sargs;
683 STORE_S *in_store, *out_store;
684 gf_io_t pc, gc;
685 HANDLE_S *handles = NULL;
686 AUTH_CODE_S user_input;
688 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
689 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
690 goto try_wantto;
692 so_puts(in_store, "<HTML><BODY><P>");
693 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2->name);
694 so_puts(in_store, tmp);
695 sprintf(tmp, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
696 so_puts(in_store, tmp);
698 if(strucmp((char *) oauth2->name, (char *) GMAIL_NAME) == 0){
699 so_puts(in_store, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
700 so_puts(in_store, _("</P><P> First you must register Alpine with Google and create a client-id and client-secret. If you already did that, then you can skip to the authorization step, and continue with the process outlined below."));
701 so_puts(in_store, _("<UL> "));
702 so_puts(in_store, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
703 so_puts(in_store, _("and create a project. The name of the project is not important."));
704 so_puts(in_store, _("<LI> Go to the Consent Screen and make your app INTERNAL, if your account is a G-Suite account, or EXTERNAL if it is a personal gmail.com account."));
705 so_puts(in_store, _("<LI> Create OAUTH Credentials."));
706 so_puts(in_store, _("</UL> "));
707 so_puts(in_store, _("<P> As a result of this process, you will get a client-id and a client-secret."));
708 so_puts(in_store, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
709 so_puts(in_store, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
710 so_puts(in_store, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
711 so_puts(in_store, _("</P><P> If you completed these steps successfully, you are ready to move to the second part, where you will authorize Gmail to give access to Alpine to access your email."));
714 so_puts(in_store, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
715 so_puts(in_store,"</P><P>");
716 sprintf(tmp_20k_buf, _("<A HREF=\"%s\">%s</A>"), url, url);
717 so_puts(in_store, tmp_20k_buf);
719 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
720 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2->name);
721 so_puts(in_store, tmp);
722 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
724 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. "));
725 so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page."));
726 so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
727 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. "));
728 so_puts(in_store, _(" Once you have completed this process, Alpine will proceed with authentication."));
729 so_puts(in_store, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
730 so_puts(in_store, _("</P></BODY></HTML>"));
732 so_seek(in_store, 0L, 0);
733 init_handles(&handles);
734 gf_filter_init();
735 gf_link_filter(gf_html2plain,
736 gf_html2plain_opt(NULL,
737 ps_global->ttyo->screen_cols, non_messageview_margin(),
738 &handles, NULL, GFHP_LOCAL_HANDLES));
739 gf_set_so_readc(&gc, in_store);
740 gf_set_so_writec(&pc, out_store);
741 gf_pipe(gc, pc);
742 gf_clear_so_writec(out_store);
743 gf_clear_so_readc(in_store);
745 memset(&sargs, 0, sizeof(SCROLL_S));
746 sargs.text.handles = handles;
747 sargs.text.text = so_text(out_store);
748 sargs.text.src = CharStar;
749 sargs.text.desc = _("help text");
750 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
751 sargs.proc.tool = oauth2_auth_answer;
752 sargs.proc.data.p = (void *)&user_input;
753 sargs.keys.menu = &oauth2_auth_keymenu;
754 /* don't want to re-enter c-client */
755 sargs.quell_newmail = 1;
756 setbitmap(sargs.keys.bitmap);
757 sargs.help.text = h_oauth2_start;
758 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
760 do {
761 scrolltool(&sargs);
762 ps_global->mangled_screen = 1;
763 ps_global->painted_body_on_startup = 0;
764 ps_global->painted_footer_on_startup = 0;
765 } while (user_input.answer != 'e');
767 if(!struncmp(user_input.code, "http://", 7)
768 || !struncmp(user_input.code, "https://", 8)){
769 char *s, *t;
770 s = strstr(user_input.code, "code=");
771 if(s != NULL){
772 t = strchr(s, '&');
773 if(t) *t = '\0';
774 code = cpystr(s+5);
775 if(t) *t = '&';
778 else code = user_input.code ? cpystr(user_input.code) : NULL;
779 if(user_input.code) fs_give((void **) &user_input.code);
781 if(code == NULL) *tryanother = 1;
783 so_give(&in_store);
784 so_give(&out_store);
785 free_handles(&handles);
787 else{
788 int flags, rc, q_line;
789 /* TRANSLATORS: user needs to input an access code from the server */
790 char *accesscodelabel = _("Copy and Paste Access Code");
791 char prompt[MAILTMPLEN], token[MAILTMPLEN];
793 * If screen hasn't been initialized yet, use want_to.
795 try_wantto:
796 tmp_20k_buf[0] = '\0';
797 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
798 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2->name);
799 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
801 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
802 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
803 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
805 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
806 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
807 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
809 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
810 "%s\n\n", url);
811 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
813 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
814 _("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);
815 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
817 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
818 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
819 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
821 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
822 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
823 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
825 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
826 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
827 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
829 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
830 "%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. "));
831 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
833 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
834 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
835 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
837 display_init_err(tmp_20k_buf, 0);
838 memset((void *)tmp, 0, sizeof(tmp));
839 strncpy(tmp, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp));
840 tmp[sizeof(tmp)-1] = '\0';
842 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
843 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
844 flags = OE_APPEND_CURRENT;
845 sprintf(prompt, "%s: ", accesscodelabel);
846 do {
847 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
848 prompt, NULL, NO_HELP, &flags);
849 } while (rc != 0 && rc != 1);
850 if(!struncmp(token, "http://", 7)
851 || !struncmp(token, "https://", 8)){
852 char *s, *t;
853 s = strstr(token, "code=");
854 if(s != NULL){
855 t = strchr(s, '&');
856 if(t) *t = '\0';
857 code = cpystr(s+5);
858 if(t) *t = '&';
861 else code = token[0] ? cpystr(token) : NULL;
865 return code;
868 void mm_login_oauth2(NETMBX *, char *, char *, OAUTH2_S *, long int, char *, char *);
870 /* The purpose of this function is to report to c-client the values of the
871 * different tokens and codes so that c-client can try to log in the user
872 * to the server. This function DOES NOT attempt to get these values for
873 * the user. That is attempted in the c-client side (as best as it can be
874 * done given our circumstances: no http support, no javascript support,
875 * etc.). This is the best we can do:
877 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
878 * as best as we can. Unloaded means that there is no server known to
879 * connect, no access token, etc. Pretty much nothing is known about
880 * how to get access code, access token, etc. We ask the user to fill
881 * it up for us, if they can. If the user fills it up we save those
882 * values.
884 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
885 * save the information that c-client got for us.
887 * 3. When saving this information we use the password caching facilities,
888 * but we must do it in a different format so that old information and
889 * new information are not mixed. In order to accommodate this for new
890 * authentication methods, we save the information in the same fields,
891 * but this time we modify it slightly, so that old functions fail to
892 * understand the new information and so not modify it nor use it. The
893 * modification is simple: Every piece of information that was saved
894 * before is prepended XOAUTH2\001 to indicate the authentication
895 * method for which it works. The character \001 is a separator. New
896 * Alpine will know how to deal with this, but old versions, will not
897 * strip this prefix from the information and fail to get the
898 * information or modify it when needed. Only new versions of Alpine will
899 * know how to process this information.
900 * new_value = authenticator_method separator old_value
901 * authenticator_method = "XOAUTH2_S"
902 * separator = "U+1"
903 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
904 * authenticator is "XOAUTH2\001imap.gmail.com".
905 * In addition, the password field is not used to encode the password
906 * anymore, it is used to save login information needed in a format that
907 * the caller function chooses, but that must be preceded by the
908 * "authenticator_method separator" code as above.
910 void
911 mm_login_oauth2(NETMBX *mb, char *user, char *method,
912 OAUTH2_S *login, long int trial,
913 char *usethisprompt, char *altuserforcache)
915 char *token, tmp[MAILTMPLEN];
916 char prompt[4*MAILTMPLEN];
917 char *OldRefreshToken, *OldAccessToken;
918 char *NewRefreshToken, *NewAccessToken;
919 char *SaveRefreshToken, *SaveAccessToken;
920 /* TRANSLATORS: A label for the hostname that the user is logging in on */
921 char *hostlabel = _("HOST");
922 /* TRANSLATORS: user is logging in as a particular user (a particular
923 login name), this is just labelling that user name. */
924 char *userlabel = _("USER");
925 STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1];
926 int len, q_line, flags, i, j;
927 int save_in_init;
928 int registered;
929 int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
930 OAUTH2_S *oa2list, *oa2;
931 XOAUTH2_INFO_S *x;
933 unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
934 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
935 int preserve_password = -1;
936 #endif
938 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
939 trial, mb->user ? mb->user : "(null)",
940 mb->service ? mb->service : "(null)",
941 mb->port ? " port=" : "",
942 mb->port ? comatose(mb->port) : "",
943 altuserforcache ? " altuserforcache =" : "",
944 altuserforcache ? altuserforcache : ""));
946 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
948 save_in_init = ps_global->in_init_seq;
949 ps_global->in_init_seq = 0;
950 ps_global->no_newmail_check_from_optionally_enter = 1;
952 /* make sure errors are seen */
953 if(ps_global->ttyo && !ps_global->noshow_error)
954 flush_status_messages(0);
956 token = NULL; /* start from scratch */
958 hostlist[0].name = mb->host;
959 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
960 hostlist[0].next = &hostlist[1];
961 hostlist[1].name = mb->orighost;
962 hostlist[1].next = NULL;
964 else
965 hostlist[0].next = NULL;
967 if(hostlist[0].name){
968 dprint((9, "mm_login_oauth2: host=%s\n",
969 hostlist[0].name ? hostlist[0].name : "?"));
970 if(hostlist[0].next && hostlist[1].name){
971 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name));
975 if(trial == 0L && !altuserforcache){
976 if(*mb->user != '\0')
977 strncpy(user, mb->user, NETMAXUSER);
978 else{
979 flags = OE_APPEND_CURRENT;
980 sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
981 optionally_enter(user, q_line, 0, NETMAXUSER, prompt, NULL, NO_HELP, &flags);
983 user[NETMAXUSER-1] = '\0';
987 * We check to see if the server we are going to log in to is already
988 * registered. This gives us a list of servers with the same
989 * credentials, so we use the same credentials for all of them.
992 for(registered = 0, oa2list = alpine_oauth2_list;
993 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
994 oa2list++){
995 for(i = 0; i < OAUTH2_TOT_EQUIV
996 && oa2list->host[i] != NULL
997 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
998 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
999 registered++;
1000 break;
1004 if(registered){
1005 x = oauth2_get_client_info(oa2list->name, user);
1006 if(x && x->flow){
1007 for(oa2list = alpine_oauth2_list;
1008 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
1009 oa2list++){
1010 for(i = 0; i < OAUTH2_TOT_EQUIV
1011 && oa2list->host[i] != NULL
1012 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
1013 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
1014 char *flow = oa2list->server_mthd[0].name ? "Authorize"
1015 : (oa2list->server_mthd[1].name ? "Device" : "Unknown");
1016 if(!strucmp(x->flow, flow)) break; /* found it */
1020 /* else use the one we found earlier, the user has to configure this better */
1023 if(registered){
1024 hostlist2[i = 0].name = mb->host;
1025 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
1026 hostlist2[++i].name = mb->orighost;
1028 for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){
1029 int k;
1030 for(k = 0; k <= i && hostlist2[k].name
1031 && strcmp(hostlist2[k].name, oa2list->host[j]); k++);
1032 if(k == i + 1)
1033 hostlist2[++i].name = oa2list->host[j];
1035 hostlist2[i+1].name = NULL;
1036 hostlist2[i+1].next = NULL;
1037 for(j = i; j >= 0; j--)
1038 hostlist2[j].next = &hostlist2[j+1];
1041 if(registered){ /* redo the app_id, no questions asked */
1042 free_id(&ps_global->id);
1043 ps_global->id = set_alpine_id(oa2list->app_id ? oa2list->app_id : PACKAGE_NAME, PACKAGE_VERSION);
1044 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
1048 * We check if we have a refresh token saved somewhere, if so
1049 * we use it to get a new access token, otherwise we need to
1050 * get an access code so we can get (and save) a refresh token
1051 * and use the access token.
1053 if(trial == 0L && !altuserforcache){
1054 /* Search for a refresh token that is already loaded ... */
1055 if(imap_get_passwd_auth(mm_login_list, &token, user,
1056 registered ? hostlist2 : hostlist,
1057 (mb->sslflag||mb->tlsflag), OA2NAME)){
1058 dprint((9, "mm_login_oauth2: found a refresh token\n"));
1059 ps_global->no_newmail_check_from_optionally_enter = 0;
1060 ps_global->in_init_seq = save_in_init;
1062 #ifdef LOCAL_PASSWD_CACHE
1063 /* or see if we have saved one in the local password cache and load it */
1064 else if(get_passfile_passwd_auth(ps_global->pinerc, &token,
1065 user, registered ? hostlist2 : hostlist,
1066 (mb->sslflag||mb->tlsflag), OA2NAME)){
1067 imap_set_passwd_auth(&mm_login_list, token, user,
1068 hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1069 update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist,
1070 (mb->sslflag||mb->tlsflag), OA2NAME);
1071 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
1072 ps_global->no_newmail_check_from_optionally_enter = 0;
1073 ps_global->in_init_seq = save_in_init;
1075 if(token && *token) preserve_password = 1; /* resave it, no need to ask */
1076 #endif /* LOCAL_PASSWD_CACHE */
1078 user[NETMAXUSER-1] = '\0';
1080 /* The Old* variables is what c_client knows */
1081 OldRefreshToken = login->cancel_refresh_token ? NULL : login->param[OA2_RefreshToken].value;
1082 OldAccessToken = login->access_token;
1083 OldExpirationTime = login->expiration;
1085 /* The New* variables is what Alpine knows */
1086 NewRefreshToken = NewAccessToken = NULL;
1087 NewExpirationTime = 0L;
1088 ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0;
1090 if(token && *token && !login->cancel_refresh_token){
1091 char *s, *t;
1093 s = token;
1094 t = strchr(s, PWDAUTHSEP);
1095 if(t == NULL)
1096 NewRefreshToken = cpystr(s);
1097 else {
1098 *t++ = '\0';
1099 NewRefreshToken = cpystr(s);
1100 s = t;
1101 t = strchr(s, PWDAUTHSEP);
1102 if(t == NULL)
1103 NewAccessToken = cpystr(s);
1104 else {
1105 *t++ = '\0';
1106 NewAccessToken = cpystr(s);
1107 s = t;
1108 NewExpirationTime = strtol(s, &s, 10);
1109 if(NewExpirationTime <= 0 || NewExpirationTime <= time(0))
1110 NewExpirationTime = 0L;
1113 /* check we got good information, and send good information below */
1114 if(NewRefreshToken && !*NewRefreshToken)
1115 fs_give((void **) &NewRefreshToken);
1116 if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken))
1117 fs_give((void **) &NewAccessToken);
1120 if(NewRefreshToken == NULL)
1121 login->first_time++;
1123 if(login->first_time){ /* count how many authorization methods we support */
1124 int nmethods, j;
1126 for(nmethods = 0, oa2 = alpine_oauth2_list; oa2 && oa2->name ; oa2++){
1127 for(j = 0; j < OAUTH2_TOT_EQUIV
1128 && oa2
1129 && oa2->host[j] != NULL
1130 && strucmp(oa2->host[j], mb->orighost) != 0; j++);
1131 if(oa2 && oa2->host && j < OAUTH2_TOT_EQUIV && oa2->host[j]
1132 && ((oa2->server_mthd[0].name && (oa2->flags & OA2_AUTHORIZE))
1133 || (oa2->server_mthd[1].name && (oa2->flags & OA2_DEVICE))))
1134 nmethods++;
1137 if(nmethods > 1)
1138 oa2list = oauth2_select_flow(mb->orighost);
1140 if(!oa2list) registered = 0;
1143 /* Default to saving what we already had saved */
1145 SaveRefreshToken = login->cancel_refresh_token ? NULL : NewRefreshToken;
1146 SaveAccessToken = NewAccessToken;
1147 SaveExpirationTime = NewExpirationTime;
1149 /* Translation of the logic below:
1150 * if (c-client has a refresh token
1151 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1152 forget the Alpine refresh token;
1153 make the Alpine Refresh token = c-client refresh token.;
1154 signal that we changed the refresh token;
1155 In this situation we do not need to clear up the Alpine access token. This will
1156 expire, so we can use it until it expires. We can save the c-client refresh token
1157 together with the Alpine Access Token and the expiration date of the Alpine Access
1158 Token.
1159 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1160 forget the Alpine refresh token;
1161 if Alpine has an access token, forget it;
1162 reset the expiration time
1163 signal that we changed the refresh token; (because the service expired it)
1167 if(OldRefreshToken != NULL
1168 && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){
1169 if(NewRefreshToken) fs_give((void **) &NewRefreshToken);
1170 NewRefreshToken = cpystr(OldRefreshToken);
1171 ChangeRefreshToken++;
1172 SaveRefreshToken = OldRefreshToken;
1173 SaveAccessToken = NewAccessToken;
1174 SaveExpirationTime = NewExpirationTime;
1175 } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){
1176 fs_give((void **) &NewRefreshToken);
1177 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1178 NewExpirationTime = 0L;
1179 ChangeRefreshToken++;
1180 SaveRefreshToken = NULL;
1181 SaveAccessToken = NULL;
1182 SaveExpirationTime = 0L;
1185 if(OldAccessToken != NULL
1186 && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){
1187 if(NewAccessToken) fs_give((void **) &NewAccessToken);
1188 NewAccessToken = cpystr(OldAccessToken);
1189 ChangeAccessToken++;
1190 NewExpirationTime = OldExpirationTime;
1191 SaveRefreshToken = NewRefreshToken;
1192 SaveAccessToken = NewAccessToken;
1193 SaveExpirationTime = NewExpirationTime;
1196 if(!registered){
1197 login->param[OA2_RefreshToken].value = SaveRefreshToken;
1198 login->access_token = SaveAccessToken;
1199 login->expiration = SaveExpirationTime;
1200 } else {
1201 oa2list->param[OA2_RefreshToken].value = SaveRefreshToken;
1202 oa2list->access_token = SaveAccessToken;
1203 oa2list->expiration = SaveExpirationTime;
1204 oa2list->first_time = login->first_time;
1205 oa2list->cancel_refresh_token = login->cancel_refresh_token;
1206 *login = *oa2list; /* load login pointer */
1209 if(!ChangeAccessToken && !ChangeRefreshToken && !login->cancel_refresh_token)
1210 return;
1212 /* get ready to save this information. The format will be
1213 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1214 * (spaces added for clarity, \001 is PWDAUTHSEP)
1216 if(token) fs_give((void **) &token);
1217 sprintf(tmp, "%lu", SaveExpirationTime);
1218 tmp[sizeof(tmp) - 1] = '\0';
1219 len = strlen(SaveRefreshToken ? SaveRefreshToken : "")
1220 + strlen(SaveAccessToken ? SaveAccessToken : "")
1221 + strlen(tmp) + 2;
1222 token = fs_get(len + 1);
1223 sprintf(token, "%s%c%s%c%lu",
1224 SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP,
1225 SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP,
1226 SaveExpirationTime);
1228 /* remember the access information for next time */
1229 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
1230 imap_set_passwd_auth(&mm_login_list, token,
1231 altuserforcache ? altuserforcache : user, hostlist,
1232 (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
1233 #ifdef LOCAL_PASSWD_CACHE
1234 /* if requested, remember it on disk for next session */
1235 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
1236 set_passfile_passwd_auth(ps_global->pinerc, token,
1237 altuserforcache ? altuserforcache : user, hostlist,
1238 (mb->sslflag||mb->tlsflag),
1239 (preserve_password == -1 ? 0
1240 : (preserve_password == 0 ? 2 :1)), OA2NAME);
1241 #endif /* LOCAL_PASSWD_CACHE */
1243 ps_global->no_newmail_check_from_optionally_enter = 0;
1246 IDLIST *
1247 set_alpine_id(char *pname, char *pversion)
1249 IDLIST *id;
1251 if(!pname || !pversion) return NULL;
1253 id = fs_get(sizeof(IDLIST));
1254 id->name = cpystr("name");
1255 id->value = cpystr(pname);
1256 id->next = fs_get(sizeof(IDLIST));
1257 id->next->name = cpystr("version");
1258 id->next->value = cpystr(pversion);
1259 id->next->next = NULL;
1260 return id;
1263 /*----------------------------------------------------------------------
1264 receive notification from IMAP
1266 Args: stream -- Mail stream message is relevant to
1267 string -- The message text
1268 errflg -- Set if it is a serious error
1270 Result: message displayed in status line
1272 The facility is for general notices, such as connection to server;
1273 server shutting down etc... It is used infrequently.
1274 ----------------------------------------------------------------------*/
1275 void
1276 mm_notify(MAILSTREAM *stream, char *string, long int errflg)
1278 time_t now;
1279 struct tm *tm_now;
1281 now = time((time_t *)0);
1282 tm_now = localtime(&now);
1284 /* be sure to log the message... */
1285 #ifdef DEBUG
1286 if(ps_global->debug_imap || ps_global->debugmem)
1287 dprint((errflg == TCPDEBUG || errflg == HTTPDEBUG ? 7 : 2,
1288 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1289 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1290 tm_now->tm_mon+1, tm_now->tm_mday,
1291 (!errflg) ? "babble" :
1292 (errflg == ERROR) ? "error" :
1293 (errflg == WARN) ? "warning" :
1294 (errflg == PARSE) ? "parse" :
1295 (errflg == TCPDEBUG) ? "tcp" :
1296 (errflg == HTTPDEBUG) ? "http" :
1297 (errflg == BYE) ? "bye" : "unknown",
1298 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1299 string ? string : "?"));
1300 #endif
1302 snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s",
1303 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
1304 (int) MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70),
1305 string);
1306 ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols
1307 : sizeof(ps_global->last_error)-1] = '\0';
1310 * Then either set special bits in the pine struct or
1311 * display the message if it's tagged as an "ALERT" or
1312 * its errflg > NIL (i.e., WARN, or ERROR)
1314 if(errflg == BYE)
1316 * We'd like to sp_mark_stream_dead() here but we can't do that because
1317 * that might call mail_close and we are already in a c-client callback.
1318 * So just set the dead bit and clean it up later.
1320 sp_set_dead_stream(stream, 1);
1321 else if(!strncmp(string, "[TRYCREATE]", 11))
1322 ps_global->try_to_create = 1;
1323 else if(!strncmp(string, "[REFERRAL ", 10))
1324 ; /* handled in the imap_referral() callback */
1325 else if(!strncmp(string, "[ALERT]", 7))
1326 q_status_message2(SM_MODAL, 3, 3,
1327 _("Alert received while accessing \"%s\": %s"),
1328 (stream && stream->mailbox)
1329 ? stream->mailbox : "-no folder-",
1330 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000),
1331 SIZEOF_20KBUF-10000, string));
1332 else if(!strncmp(string, "[UNSEEN ", 8)){
1333 char *p;
1334 long n = 0;
1336 for(p = string + 8; isdigit(*p); p++)
1337 n = (n * 10) + (*p - '0');
1339 sp_set_first_unseen(stream, n);
1341 else if(!strncmp(string, "[READ-ONLY]", 11)
1342 && !(stream && stream->mailbox && IS_NEWS(stream)))
1343 q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s",
1344 (stream && stream->mailbox)
1345 ? stream->mailbox : "-no folder-",
1346 string + 11);
1347 else if((errflg && errflg != BYE && errflg != PARSE)
1348 && !ps_global->noshow_error
1349 && !(errflg == WARN
1350 && (ps_global->noshow_warn || (stream && stream->unhealthy))))
1351 q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0),
1352 3, 6, ps_global->last_error);
1356 /*----------------------------------------------------------------------
1357 Queue imap log message for display in the message line
1359 Args: string -- The message
1360 errflg -- flag set to 1 if pertains to an error
1362 Result: Message queued for display
1364 The c-client/imap reports most of it's status and errors here
1365 ---*/
1366 void
1367 mm_log(char *string, long int errflg)
1369 char message[sizeof(ps_global->c_client_error)];
1370 char *occurence;
1371 int was_capitalized;
1372 static char saw_kerberos_init_warning;
1373 time_t now;
1374 struct tm *tm_now;
1376 now = time((time_t *)0);
1377 tm_now = localtime(&now);
1379 dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? 1 :
1380 (errflg == TCPDEBUG) ? 10 :
1381 ((errflg == HTTPDEBUG) && ps_global->debug_http) ? 1 :
1382 (errflg == HTTPDEBUG) ? 10 : 2,
1383 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1384 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
1385 tm_now->tm_mon+1, tm_now->tm_mday,
1386 (!errflg) ? "babble" :
1387 (errflg == ERROR) ? "error" :
1388 (errflg == WARN) ? "warning" :
1389 (errflg == PARSE) ? "parse" :
1390 (errflg == TCPDEBUG) ? "tcp" :
1391 (errflg == HTTPDEBUG) ? "http" :
1392 (errflg == BYE) ? "bye" : "unknown",
1393 string ? string : "?"));
1395 if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){
1396 ps_global->try_to_create = 1;
1397 return;
1399 else if(ps_global->try_to_create
1400 || !strncmp(string, "[CLOSED]", 8)
1401 || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op")))
1403 * Don't display if creating new folder OR
1404 * warning about a dead stream ...
1406 return;
1408 strncpy(message, string, sizeof(message));
1409 message[sizeof(message) - 1] = '\0';
1411 if(errflg == WARN && srchstr(message, "try running kinit") != NULL){
1412 if(saw_kerberos_init_warning)
1413 return;
1415 saw_kerberos_init_warning = 1;
1418 /*---- replace all "mailbox" with "folder" ------*/
1419 occurence = srchstr(message, "mailbox");
1420 while(occurence) {
1421 if(!*(occurence+7)
1422 || isspace((unsigned char) *(occurence+7))
1423 || *(occurence+7) == ':'){
1424 was_capitalized = isupper((unsigned char) *occurence);
1425 rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder"));
1426 if(was_capitalized)
1427 *occurence = (errflg == PARSE ? 'A' : 'F');
1429 else
1430 occurence += 7;
1432 occurence = srchstr(occurence, "mailbox");
1435 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1436 occurence = srchstr(message, "GSSAPI");
1437 while(occurence) {
1438 if(!*(occurence+6)
1439 || isspace((unsigned char) *(occurence+6))
1440 || *(occurence+6) == ':')
1441 rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos");
1442 else
1443 occurence += 6;
1445 occurence = srchstr(occurence, "GSSAPI");
1448 if(errflg == ERROR)
1449 ps_global->mm_log_error = 1;
1451 if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error))
1452 strncpy(ps_global->c_client_error, message,
1453 sizeof(ps_global->c_client_error));
1455 if(ps_global->noshow_error
1456 || (ps_global->noshow_warn && errflg == WARN)
1457 || !(errflg == ERROR || errflg == WARN))
1458 return; /* Only care about errors; don't print when asked not to */
1460 /*---- Display the message ------*/
1461 q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER,
1462 3, 5, message);
1463 strncpy(ps_global->last_error, message, sizeof(ps_global->last_error));
1464 ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
1467 void
1468 mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial,
1469 char *method, char *usethisprompt, char *altuserforcache)
1471 if(method == NULL)
1472 return;
1473 if(strucmp(method, OA2NAME) == 0 || strucmp(method, BEARERNAME) == 0)
1474 mm_login_oauth2(mb, user, method, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache);
1477 void
1478 mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial,
1479 char *usethisprompt, char *altuserforcache)
1481 char tmp[MAILTMPLEN];
1482 char prompt[1000], *last;
1483 char port[20], non_def_port[20], insecure[20];
1484 char defuser[NETMAXUSER];
1485 char hostleadin[80], hostname[200], defubuf[200];
1486 char logleadin[80], pwleadin[50];
1487 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
1488 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1489 that the password will be sent unencrypted over the network. This is
1490 just a warning message that gets added parenthetically when the user
1491 is asked for a password. */
1492 char *insec = _(" (INSECURE)");
1493 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1494 after having already failed at least once. */
1495 char *retry = _("Retrying - ");
1496 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1497 char *hostlabel = _("HOST");
1498 /* TRANSLATORS: user is logging in as a particular user (a particular
1499 login name), this is just labelling that user name. */
1500 char *userlabel = _("USER");
1501 STRLIST_S hostlist[2];
1502 HelpType help ;
1503 int len, rc, q_line, flags;
1504 int oespace, avail, need, save_dont_use;
1505 int save_in_init;
1506 struct servent *sv;
1507 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1508 int preserve_password = -1;
1509 #endif
1511 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1512 trial, mb->user ? mb->user : "(null)",
1513 mb->service ? mb->service : "(null)",
1514 mb->port ? " port=" : "",
1515 mb->port ? comatose(mb->port) : "",
1516 altuserforcache ? " altuserforcache =" : "",
1517 altuserforcache ? altuserforcache : ""));
1518 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
1520 save_in_init = ps_global->in_init_seq;
1521 ps_global->in_init_seq = 0;
1522 ps_global->no_newmail_check_from_optionally_enter = 1;
1524 /* make sure errors are seen */
1525 if(ps_global->ttyo)
1526 flush_status_messages(0);
1528 /* redo app id in case we are logging in to an IMAP server that supports the IMAP ID extension */
1529 free_id(&ps_global->id);
1530 ps_global->id = set_alpine_id(PACKAGE_NAME, PACKAGE_VERSION);
1531 mail_parameters(NULL, SET_IDPARAMS, (void *) ps_global->id);
1534 * Add port number to hostname if going through a tunnel or something
1536 non_def_port[0] = '\0';
1537 if(mb->port && mb->service &&
1538 (sv = getservbyname(mb->service, "tcp")) &&
1539 (mb->port != ntohs(sv->s_port))){
1540 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
1541 non_def_port[sizeof(non_def_port)-1] = '\0';
1542 dprint((9, "mm_login: using non-default port=%s\n", non_def_port));
1546 * set up host list for sybil servers...
1548 if(*non_def_port){
1549 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
1550 hostlist0[sizeof(hostlist0)-1] = '\0';
1551 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
1552 hostlist0[sizeof(hostlist0)-1] = '\0';
1553 hostlist[0].name = hostlist0;
1554 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1555 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
1556 hostlist1[sizeof(hostlist1)-1] = '\0';
1557 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
1558 hostlist1[sizeof(hostlist1)-1] = '\0';
1559 hostlist[0].next = &hostlist[1];
1560 hostlist[1].name = hostlist1;
1561 hostlist[1].next = NULL;
1563 else
1564 hostlist[0].next = NULL;
1566 else{
1567 hostlist[0].name = mb->host;
1568 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1569 hostlist[0].next = &hostlist[1];
1570 hostlist[1].name = mb->orighost;
1571 hostlist[1].next = NULL;
1573 else
1574 hostlist[0].next = NULL;
1577 if(hostlist[0].name){
1578 dprint((9, "mm_login: host=%s\n",
1579 hostlist[0].name ? hostlist[0].name : "?"));
1580 if(hostlist[0].next && hostlist[1].name){
1581 dprint((9, "mm_login: orighost=%s\n", hostlist[1].name));
1586 * Initialize user name with either
1587 * 1) /user= value in the stream being logged into,
1588 * or 2) the user name we're running under.
1590 * Note that VAR_USER_ID is not yet initialized if this login is
1591 * the one to access the remote config file. In that case, the user
1592 * can supply the username in the config file name with /user=.
1594 if(trial == 0L && !altuserforcache){
1595 strncpy(user, (*mb->user) ? mb->user :
1596 ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "",
1597 NETMAXUSER);
1598 user[NETMAXUSER-1] = '\0';
1600 /* try last working password associated with this host. */
1601 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1602 (mb->sslflag||mb->tlsflag))){
1603 dprint((9, "mm_login: found a password to try\n"));
1604 ps_global->no_newmail_check_from_optionally_enter = 0;
1605 ps_global->in_init_seq = save_in_init;
1606 return;
1609 #ifdef LOCAL_PASSWD_CACHE
1610 /* check to see if there's a password left over from last session */
1611 if(get_passfile_passwd(ps_global->pinerc, pwd,
1612 user, hostlist, (mb->sslflag||mb->tlsflag))){
1613 imap_set_passwd(&mm_login_list, *pwd, user,
1614 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1615 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1616 (mb->sslflag||mb->tlsflag));
1617 dprint((9, "mm_login: found a password in passfile to try\n"));
1618 ps_global->no_newmail_check_from_optionally_enter = 0;
1619 ps_global->in_init_seq = save_in_init;
1620 return;
1622 #endif /* LOCAL_PASSWD_CACHE */
1625 * If no explicit user name supplied and we've not logged in
1626 * with our local user name, see if we've visited this
1627 * host before as someone else.
1629 if(!*mb->user &&
1630 ((last = imap_get_user(mm_login_list, hostlist))
1631 #ifdef LOCAL_PASSWD_CACHE
1633 (last = get_passfile_user(ps_global->pinerc, hostlist))
1634 #endif /* LOCAL_PASSWD_CACHE */
1636 strncpy(user, last, NETMAXUSER);
1637 user[NETMAXUSER-1] = '\0';
1638 dprint((9, "mm_login: found user=%s\n",
1639 user ? user : "?"));
1641 /* try last working password associated with this host/user. */
1642 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1643 (mb->sslflag||mb->tlsflag))){
1644 dprint((9,
1645 "mm_login: found a password for user=%s to try\n",
1646 user ? user : "?"));
1647 ps_global->no_newmail_check_from_optionally_enter = 0;
1648 ps_global->in_init_seq = save_in_init;
1649 return;
1652 #ifdef LOCAL_PASSWD_CACHE
1653 /* check to see if there's a password left over from last session */
1654 if(get_passfile_passwd(ps_global->pinerc, pwd,
1655 user, hostlist, (mb->sslflag||mb->tlsflag))){
1656 imap_set_passwd(&mm_login_list, *pwd, user,
1657 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1658 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1659 (mb->sslflag||mb->tlsflag));
1660 dprint((9,
1661 "mm_login: found a password for user=%s in passfile to try\n",
1662 user ? user : "?"));
1663 ps_global->no_newmail_check_from_optionally_enter = 0;
1664 ps_global->in_init_seq = save_in_init;
1665 return;
1667 #endif /* LOCAL_PASSWD_CACHE */
1670 #if !defined(DOS) && !defined(OS2)
1671 if(!*mb->user && !*user &&
1672 (last = (ps_global->ui.login && ps_global->ui.login[0])
1673 ? ps_global->ui.login : NULL)
1675 strncpy(user, last, NETMAXUSER);
1676 user[NETMAXUSER-1] = '\0';
1677 dprint((9, "mm_login: found user=%s\n",
1678 user ? user : "?"));
1680 /* try last working password associated with this host. */
1681 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1682 (mb->sslflag||mb->tlsflag))){
1683 dprint((9, "mm_login:ui: found a password to try\n"));
1684 ps_global->no_newmail_check_from_optionally_enter = 0;
1685 ps_global->in_init_seq = save_in_init;
1686 return;
1689 #ifdef LOCAL_PASSWD_CACHE
1690 /* check to see if there's a password left over from last session */
1691 if(get_passfile_passwd(ps_global->pinerc, pwd,
1692 user, hostlist, (mb->sslflag||mb->tlsflag))){
1693 imap_set_passwd(&mm_login_list, *pwd, user,
1694 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1695 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1696 (mb->sslflag||mb->tlsflag));
1697 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1698 ps_global->no_newmail_check_from_optionally_enter = 0;
1699 ps_global->in_init_seq = save_in_init;
1700 return;
1702 #endif /* LOCAL_PASSWD_CACHE */
1704 #endif
1707 user[NETMAXUSER-1] = '\0';
1709 if(trial == 0)
1710 retry = "";
1713 * Even if we have a user now, user gets a chance to change it.
1715 ps_global->mangled_footer = 1;
1716 if(!*mb->user && !altuserforcache){
1718 help = NO_HELP;
1721 * Instead of offering user with a value that the user can edit,
1722 * we offer [user] as a default so that the user can type CR to
1723 * use it. Otherwise, the user has to type in whole name.
1725 strncpy(defuser, user, sizeof(defuser)-1);
1726 defuser[sizeof(defuser)-1] = '\0';
1727 user[0] = '\0';
1730 * Need space for "Retrying - "
1731 * "+ HOST: "
1732 * hostname
1733 * " (INSECURE)"
1734 * ENTER LOGIN NAME
1735 * " [defuser] : "
1736 * about 15 chars for input
1739 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1740 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1741 hostleadin[sizeof(hostleadin)-1] = '\0';
1743 strncpy(hostname, mb->host, sizeof(hostname)-1);
1744 hostname[sizeof(hostname)-1] = '\0';
1747 * Add port number to hostname if going through a tunnel or something
1749 if(*non_def_port)
1750 strncpy(port, non_def_port, sizeof(port));
1751 else
1752 port[0] = '\0';
1754 insecure[0] = '\0';
1755 /* if not encrypted and SSL/TLS is supported */
1756 if(!(mb->sslflag||mb->tlsflag) &&
1757 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1758 strncpy(insecure, insec, sizeof(insecure));
1760 /* TRANSLATORS: user is being asked to type in their login name */
1761 snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME"));
1763 snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "",
1764 (*defuser) ? defuser : "",
1765 (*defuser) ? "]" : "");
1766 defubuf[sizeof(defubuf)-1] = '\0';
1767 /* space reserved after prompt */
1768 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1770 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1771 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1772 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace;
1774 /* If we're retrying cut the hostname back to the first word. */
1775 if(avail < need && trial > 0){
1776 char *p;
1778 len = strlen(hostname);
1779 if((p = strchr(hostname, '.')) != NULL){
1780 *p = '\0';
1781 need -= (len - strlen(hostname));
1785 if(avail < need){
1786 need -= utf8_width(retry);
1787 retry = "";
1789 if(avail < need){
1791 /* reduce length of logleadin */
1792 len = utf8_width(logleadin);
1793 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1794 longer version doesn't fit on screen */
1795 snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN"));
1796 need -= (len - utf8_width(logleadin));
1798 if(avail < need){
1799 /* get two spaces from hostleadin */
1800 len = utf8_width(hostleadin);
1801 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1802 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1803 hostleadin[sizeof(hostleadin)-1] = '\0';
1804 need -= (len - utf8_width(hostleadin));
1806 /* get rid of port */
1807 if(avail < need && strlen(port) > 0){
1808 need -= strlen(port);
1809 port[0] = '\0';
1812 if(avail < need){
1813 int reduce_to;
1816 * Reduce space for hostname. Best we can do is 6 chars
1817 * with hos...
1819 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1820 len = strlen(hostname);
1821 strncpy(hostname+reduce_to-3, "...", 4);
1822 need -= (len - strlen(hostname));
1824 if(avail < need && strlen(insecure) > 0){
1825 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1826 need -= 3;
1827 insecure[strlen(insecure)-4] = ')';
1828 insecure[strlen(insecure)-3] = '\0';
1830 else{
1831 need -= utf8_width(insecure);
1832 insecure[0] = '\0';
1836 if(avail < need){
1837 if(strlen(defubuf) > 3){
1838 len = strlen(defubuf);
1839 strncpy(defubuf, " [..] :", 9);
1840 need -= (len - strlen(defubuf));
1843 if(avail < need)
1844 strncpy(defubuf, ":", 2);
1847 * If it still doesn't fit, optionally_enter gets
1848 * to worry about it.
1856 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s",
1857 retry, hostleadin, hostname, port, insecure, logleadin, defubuf);
1858 prompt[sizeof(prompt)-1] = '\0';
1860 while(1) {
1861 if(ps_global->ttyo)
1862 mm_login_alt_cue(mb);
1864 flags = OE_APPEND_CURRENT;
1865 save_dont_use = ps_global->dont_use_init_cmds;
1866 ps_global->dont_use_init_cmds = 1;
1867 #ifdef _WINDOWS
1868 if(!*user && *defuser){
1869 strncpy(user, defuser, NETMAXUSER);
1870 user[NETMAXUSER-1] = '\0';
1873 rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD,
1874 #ifdef LOCAL_PASSWD_CACHE
1875 is_using_passfile() ? 1 :
1876 #endif /* LOCAL_PASSWD_CACHE */
1877 0, 0, &preserve_password);
1878 ps_global->dont_use_init_cmds = save_dont_use;
1879 if(rc == 0 && *user && *pwd)
1880 goto nopwpmt;
1881 #else /* !_WINDOWS */
1882 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
1883 prompt, NULL, help, &flags);
1884 #endif /* !_WINDOWS */
1885 ps_global->dont_use_init_cmds = save_dont_use;
1887 if(rc == 3) {
1888 help = help == NO_HELP ? h_oe_login : NO_HELP;
1889 continue;
1892 /* default */
1893 if(rc == 0 && !*user){
1894 strncpy(user, defuser, NETMAXUSER);
1895 user[NETMAXUSER-1] = '\0';
1898 if(rc != 4)
1899 break;
1902 if(rc == 1 || !user[0]) {
1903 ps_global->user_says_cancel = (rc == 1);
1904 user[0] = '\0';
1907 else{
1908 strncpy(user, mb->user, NETMAXUSER);
1909 user[NETMAXUSER-1] = '\0';
1912 user[NETMAXUSER-1] = '\0';
1914 if(!(user[0] || altuserforcache)){
1915 ps_global->no_newmail_check_from_optionally_enter = 0;
1916 ps_global->in_init_seq = save_in_init;
1917 return;
1921 * Now that we have a user, we can check in the cache again to see
1922 * if there is a password there. Try last working password associated
1923 * with this host and user.
1925 if(trial == 0L && !*mb->user && !altuserforcache){
1926 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1927 (mb->sslflag||mb->tlsflag))){
1928 ps_global->no_newmail_check_from_optionally_enter = 0;
1929 ps_global->in_init_seq = save_in_init;
1930 return;
1933 #ifdef LOCAL_PASSWD_CACHE
1934 if(get_passfile_passwd(ps_global->pinerc, pwd,
1935 user, hostlist, (mb->sslflag||mb->tlsflag))){
1936 imap_set_passwd(&mm_login_list, *pwd, user,
1937 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1938 ps_global->no_newmail_check_from_optionally_enter = 0;
1939 ps_global->in_init_seq = save_in_init;
1940 return;
1942 #endif /* LOCAL_PASSWD_CACHE */
1944 else if(trial == 0 && altuserforcache){
1945 if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist,
1946 (mb->sslflag||mb->tlsflag))){
1947 ps_global->no_newmail_check_from_optionally_enter = 0;
1948 ps_global->in_init_seq = save_in_init;
1949 return;
1952 #ifdef LOCAL_PASSWD_CACHE
1953 if(get_passfile_passwd(ps_global->pinerc, pwd,
1954 altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
1955 imap_set_passwd(&mm_login_list, *pwd, altuserforcache,
1956 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1957 ps_global->no_newmail_check_from_optionally_enter = 0;
1958 ps_global->in_init_seq = save_in_init;
1959 return;
1961 #endif /* LOCAL_PASSWD_CACHE */
1965 * Didn't find password in cache or this isn't the first try. Ask user.
1967 help = NO_HELP;
1970 * Need space for "Retrying - "
1971 * "+ HOST: "
1972 * hostname
1973 * " (INSECURE) "
1974 * " USER: "
1975 * user
1976 * " ENTER PASSWORD: "
1977 * about 15 chars for input
1980 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1981 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1983 strncpy(hostname, mb->host, sizeof(hostname)-1);
1984 hostname[sizeof(hostname)-1] = '\0';
1987 * Add port number to hostname if going through a tunnel or something
1989 if(*non_def_port)
1990 strncpy(port, non_def_port, sizeof(port));
1991 else
1992 port[0] = '\0';
1994 insecure[0] = '\0';
1996 /* if not encrypted and SSL/TLS is supported */
1997 if(!(mb->sslflag||mb->tlsflag) &&
1998 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1999 strncpy(insecure, insec, sizeof(insecure));
2001 if(usethisprompt){
2002 strncpy(logleadin, usethisprompt, sizeof(logleadin));
2003 logleadin[sizeof(logleadin)-1] = '\0';
2004 defubuf[0] = '\0';
2005 user[0] = '\0';
2007 else{
2008 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
2010 strncpy(defubuf, user, sizeof(defubuf)-1);
2011 defubuf[sizeof(defubuf)-1] = '\0';
2014 /* TRANSLATORS: user is being asked to type in their password */
2015 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD"));
2017 /* space reserved after prompt */
2018 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
2020 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2021 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
2022 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) +
2023 utf8_width(pwleadin) + oespace;
2025 if(avail < need && trial > 0){
2026 char *p;
2028 len = strlen(hostname);
2029 if((p = strchr(hostname, '.')) != NULL){
2030 *p = '\0';
2031 need -= (len - strlen(hostname));
2035 if(avail < need){
2036 need -= utf8_width(retry);
2037 retry = "";
2039 if(avail < need){
2041 if(!usethisprompt){
2042 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
2043 need--;
2046 rplstr(pwleadin, sizeof(pwleadin), 1, "");
2047 need--;
2049 if(avail < need){
2050 /* get two spaces from hostleadin */
2051 len = utf8_width(hostleadin);
2052 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
2053 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
2054 hostleadin[sizeof(hostleadin)-1] = '\0';
2055 need -= (len - utf8_width(hostleadin));
2057 /* get rid of port */
2058 if(avail < need && strlen(port) > 0){
2059 need -= strlen(port);
2060 port[0] = '\0';
2063 if(avail < need){
2064 len = utf8_width(pwleadin);
2065 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
2066 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD"));
2067 need -= (len - utf8_width(pwleadin));
2071 if(avail < need){
2072 int reduce_to;
2075 * Reduce space for hostname. Best we can do is 6 chars
2076 * with hos...
2078 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
2079 len = strlen(hostname);
2080 strncpy(hostname+reduce_to-3, "...", 4);
2081 need -= (len - strlen(hostname));
2083 if(avail < need && strlen(insecure) > 0){
2084 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
2085 need -= 3;
2086 insecure[strlen(insecure)-4] = ')';
2087 insecure[strlen(insecure)-3] = '\0';
2089 else{
2090 need -= utf8_width(insecure);
2091 insecure[0] = '\0';
2095 if(avail < need){
2096 len = utf8_width(logleadin);
2097 strncpy(logleadin, " ", sizeof(logleadin));
2098 logleadin[sizeof(logleadin)-1] = '\0';
2099 need -= (len - utf8_width(logleadin));
2101 if(avail < need){
2102 reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0;
2103 if(reduce_to)
2104 strncpy(defubuf+reduce_to-3, "...", 4);
2105 else
2106 defubuf[0] = '\0';
2113 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s",
2114 retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
2115 prompt[sizeof(prompt)-1] = '\0';
2117 tmp[0] = '\0';
2118 while(1) {
2119 if(ps_global->ttyo)
2120 mm_login_alt_cue(mb);
2122 save_dont_use = ps_global->dont_use_init_cmds;
2123 ps_global->dont_use_init_cmds = 1;
2124 flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
2125 flags |= OE_KEEP_TRAILING_SPACE;
2126 #ifdef _WINDOWS
2128 char *tmpp;
2129 tmpp = fs_get(NETMAXPASSWD*sizeof(char));
2130 rc = os_login_dialog(mb, user, NETMAXUSER, &tmpp, NETMAXPASSWD, 0, 1,
2131 &preserve_password);
2132 strncpy(tmp, tmpp, sizeof(tmp));
2133 tmp[sizeof(tmp)-1] = '\0';
2134 if(tmpp) fs_give((void **)&tmpp);
2136 #else /* !_WINDOWS */
2137 rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD,
2138 prompt, NULL, help, &flags);
2139 #endif /* !_WINDOWS */
2140 if(rc != 1) *pwd = cpystr(tmp);
2141 ps_global->dont_use_init_cmds = save_dont_use;
2143 if(rc == 3) {
2144 help = help == NO_HELP ? h_oe_passwd : NO_HELP;
2146 else if(rc == 4){
2148 else
2149 break;
2152 if(rc == 1 || !tmp[0]) {
2153 ps_global->user_says_cancel = (rc == 1);
2154 user[0] = '\0';
2155 ps_global->no_newmail_check_from_optionally_enter = 0;
2156 ps_global->in_init_seq = save_in_init;
2157 return;
2160 #ifdef _WINDOWS
2161 nopwpmt:
2162 #endif
2163 /* remember the password for next time */
2164 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
2165 imap_set_passwd(&mm_login_list, *pwd,
2166 altuserforcache ? altuserforcache : user, hostlist,
2167 (mb->sslflag||mb->tlsflag), 0, 0);
2168 #ifdef LOCAL_PASSWD_CACHE
2169 /* if requested, remember it on disk for next session */
2170 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
2171 set_passfile_passwd(ps_global->pinerc, *pwd,
2172 altuserforcache ? altuserforcache : user, hostlist,
2173 (mb->sslflag||mb->tlsflag),
2174 (preserve_password == -1 ? 0
2175 : (preserve_password == 0 ? 2 :1)));
2176 #endif /* LOCAL_PASSWD_CACHE */
2178 ps_global->no_newmail_check_from_optionally_enter = 0;
2182 void
2183 mm_login_alt_cue(NETMBX *mb)
2185 if(ps_global->ttyo){
2186 COLOR_PAIR *lastc;
2188 lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
2189 ps_global->VAR_TITLE_BACK_COLOR,
2190 PSC_REV | PSC_RET);
2192 mark_titlebar_dirty();
2193 PutLine0(0, ps_global->ttyo->screen_cols - 1,
2194 (mb->sslflag||mb->tlsflag) ? "+" : " ");
2196 if(lastc){
2197 (void)pico_set_colorp(lastc, PSC_NONE);
2198 free_color_pair(&lastc);
2201 fflush(stdout);
2206 /*----------------------------------------------------------------------
2207 Receive notification of an error writing to disk
2209 Args: stream -- The stream the error occurred on
2210 errcode -- The system error code (errno)
2211 serious -- Flag indicating error is serious (mail may be lost)
2213 Result: If error is non serious, the stream is marked as having an error
2214 and deletes are disallowed until error clears
2215 If error is serious this goes modal, allowing the user to retry
2216 or get a shell escape to fix the condition. When the condition is
2217 serious it means that mail existing in the mailbox will be lost
2218 if Pine exits without writing, so we try to induce the user to
2219 fix the error, go get someone that can fix the error, or whatever
2220 and don't provide an easy way out.
2221 ----*/
2222 long
2223 mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious)
2225 int i, j;
2226 char *p, *q, *s;
2227 static ESCKEY_S de_opts[] = {
2228 {'r', 'r', "R", "Retry"},
2229 {'f', 'f', "F", "FileBrowser"},
2230 {'s', 's', "S", "ShellPrompt"},
2231 {-1, 0, NULL, NULL}
2233 #define DE_COLS (ps_global->ttyo->screen_cols)
2234 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2236 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2237 #define DE_PMT \
2238 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2239 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2240 #define DE_STR2 \
2241 "The reported error number is %s. The last reported mail error was:"
2242 static char *de_msg[] = {
2243 "Please try to correct the error preventing Alpine from saving your",
2244 "mail folder. For example if the disk is out of space try removing",
2245 "unneeded files. You might also contact your system administrator.",
2247 "Both Alpine's File Browser and an option to enter the system's",
2248 "command prompt are offered to aid in fixing the problem. When",
2249 "you believe the problem is resolved, choose the \"Retry\" option.",
2250 "Be aware that messages may be lost or this folder left in an",
2251 "inaccessible condition if you exit or kill Alpine before the problem",
2252 "is resolved.",
2253 NULL};
2254 static char *de_shell_msg[] = {
2255 "\n\nPlease attempt to correct the error preventing saving of the",
2256 "mail folder. If you do not know how to correct the problem, contact",
2257 "your system administrator. To return to Alpine, type \"exit\".",
2258 NULL};
2260 dprint((0,
2261 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2262 DE_FOLDER(stream), errcode, serious ? "" : "not "));
2263 dprint((0, "***** message: \"%s\"\n\n",
2264 ps_global->last_error ? ps_global->last_error : "?"));
2266 if(!serious) {
2267 sp_set_io_error_on_stream(stream, 1);
2268 return (1) ;
2271 while(1){
2272 /* replace pine's body display with screen full of explanatory text */
2273 ClearLine(2);
2274 PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1)
2275 - strlen(DE_FOLDER(stream)))/2, 0),
2276 DE_STR1, DE_FOLDER(stream));
2277 ClearLine(3);
2278 PutLine1(3, 4, DE_STR2, long2string(errcode));
2280 PutLine0(4, 0, " \"");
2281 removing_leading_white_space(ps_global->last_error);
2282 for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){
2283 for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++)
2284 if(isspace((unsigned char)*q))
2285 s = q;
2287 if(*q && s)
2288 q = s;
2290 while(p < q)
2291 Writechar(*p++, 0);
2293 if(*(p = q)){
2294 ClearLine(++i);
2295 PutLine0(i, 0, " ");
2296 while(*p && isspace((unsigned char)*p))
2297 p++;
2299 else{
2300 Writechar('\"', 0);
2301 CleartoEOLN();
2302 break;
2306 ClearLine(++i);
2307 for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){
2308 ClearLine(i);
2309 PutLine0(i, 0, " ");
2310 Write_to_screen(de_msg[i-j]);
2313 while(i < DE_LINE)
2314 ClearLine(i++);
2316 switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts,
2317 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){
2318 case 'r' : /* Retry! */
2319 ps_global->mangled_screen = 1;
2320 return(0L);
2322 case 'f' : /* File Browser */
2324 char full_filename[MAXPATH+1], filename[MAXPATH+1];
2326 filename[0] = '\0';
2327 build_path(full_filename, ps_global->home_dir, filename,
2328 sizeof(full_filename));
2329 file_lister("DISK ERROR", full_filename, sizeof(full_filename),
2330 filename, sizeof(filename), FALSE, FB_SAVE);
2333 break;
2335 case 's' :
2336 EndInverse();
2337 end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0);
2338 end_tty_driver(ps_global);
2339 for(i = 0; de_shell_msg[i]; i++)
2340 puts(de_shell_msg[i]);
2343 * Don't use our piping mechanism to spawn a subshell here
2344 * since it will the server (thus reentering c-client).
2345 * Bad thing to do.
2347 #ifdef _WINDOWS
2348 #else
2349 system("csh");
2350 #endif
2351 init_tty_driver(ps_global);
2352 init_keyboard(F_ON(F_USE_FK,ps_global));
2353 break;
2356 if(ps_global->redrawer)
2357 (*ps_global->redrawer)();
2362 long
2363 pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host)
2365 long rv = 1L;
2366 char pmt[128];
2368 #ifdef _WINDOWS
2369 mswin_killsplash();
2370 #endif
2372 if(elapsed >= (long)ps_global->tcp_query_timeout){
2373 snprintf(pmt, sizeof(pmt),
2374 _("No reply in %s seconds from server %s. Break connection"),
2375 long2string(elapsed), host);
2376 pmt[sizeof(pmt)-1] = '\0';
2377 if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
2378 ps_global->user_says_cancel = 1;
2379 return(0L);
2383 ps_global->tcptimeout = 0;
2384 return(rv);
2389 * -------------------------------------------------------------
2390 * These are declared in pith/imap.h as mandatory to implement.
2391 * -------------------------------------------------------------
2396 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2398 long
2399 pine_tcptimeout(long int elapsed, long int sincelast, char *host)
2401 long rv = 1L; /* keep trying by default */
2402 unsigned long ch;
2404 ps_global->tcptimeout = 1;
2405 #ifdef DEBUG
2406 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2407 long2string(elapsed), host));
2408 if(debugfile)
2409 fflush(debugfile);
2410 #endif
2412 #ifdef _WINDOWS
2413 mswin_killsplash();
2414 #endif
2416 if(ps_global->noshow_timeout)
2417 return(rv);
2419 if(ps_global->can_interrupt
2420 && ps_global->close_connection_timeout > 0L
2421 && elapsed >= (long)ps_global->tcp_query_timeout
2422 && elapsed >= (long)ps_global->close_connection_timeout){
2423 ps_global->can_interrupt = 0; /* do not return here */
2424 ps_global->read_bail = 0;
2425 ps_global->user_says_cancel = 1;
2426 return 0;
2429 if(!ps_global->ttyo)
2430 return(pine_tcptimeout_noscreen(elapsed, sincelast, host));
2432 suspend_busy_cue();
2435 * Prompt after a minute (since by then things are probably really bad)
2436 * A prompt timeout means "keep trying"...
2438 if(elapsed >= (long)ps_global->tcp_query_timeout){
2439 int clear_inverse;
2441 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2442 if((clear_inverse = !InverseState()) != 0)
2443 StartInverse();
2445 Writechar(BELL, 0);
2447 PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0,
2448 _("No reply in %s seconds from server %s. Break connection?"),
2449 long2string(elapsed), host);
2450 CleartoEOLN();
2451 fflush(stdout);
2452 flush_input();
2453 ch = read_char(7);
2454 if(ps_global->read_bail || ch == 'y' || ch == 'Y'){
2455 ps_global->read_bail = 0;
2456 ps_global->user_says_cancel = 1;
2457 rv = 0L;
2460 if(clear_inverse)
2461 EndInverse();
2463 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
2466 if(rv == 1L){ /* just warn 'em something's up */
2467 q_status_message2(SM_ORDER, 0, 0,
2468 _("No reply in %s seconds from server %s. Still Waiting..."),
2469 long2string(elapsed), host);
2470 flush_status_messages(0); /* make sure it's seen */
2473 mark_status_dirty(); /* make sure it gets cleared */
2475 resume_busy_cue((rv == 1) ? 3 : 0);
2476 ps_global->tcptimeout = 0;
2478 return(rv);
2481 QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota)
2483 QUOTALIST *cquota = NULL;
2485 if(pquota){
2486 cquota = mail_newquotalist();
2487 if (pquota->name && *pquota->name)
2488 cquota->name = cpystr(pquota->name);
2489 cquota->usage = pquota->usage;
2490 cquota->limit = pquota->limit;
2491 if (pquota->next)
2492 cquota->next = pine_quotalist_copy(pquota->next);
2494 return cquota;
2498 /* c-client callback to handle quota */
2500 void
2501 pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
2503 ps_global->quota = pine_quotalist_copy (pquota);
2507 * C-client callback to handle SSL/TLS certificate validation failures
2509 * Returning 0 means error becomes fatal
2510 * Non-zero means certificate problem is ignored and SSL session is
2511 * established
2513 * We remember the answer and won't re-ask for subsequent open attempts to
2514 * the same hostname.
2516 long
2517 pine_sslcertquery(char *reason, char *host, char *cert)
2519 char tmp[500];
2520 char *unknown = "<unknown>";
2521 long rv = 0L;
2522 STRLIST_S hostlist;
2523 int ok_novalidate = 0, warned = 0;
2525 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2526 host ? host : "?", reason ? reason : "?",
2527 cert ? cert : "?"));
2529 hostlist.name = host ? host : "";
2530 hostlist.next = NULL;
2533 * See if we've been asked about this host before.
2535 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2536 /* we were asked before, did we say Yes? */
2537 if(ok_novalidate)
2538 rv++;
2540 if(rv){
2541 dprint((5,
2542 "sslcertificatequery: approved automatically\n"));
2543 return(rv);
2546 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2549 if(ps_global->ttyo){
2550 SCROLL_S sargs;
2551 STORE_S *in_store, *out_store;
2552 gf_io_t pc, gc;
2553 HANDLE_S *handles = NULL;
2554 int the_answer = 'n';
2556 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
2557 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
2558 goto try_wantto;
2560 so_puts(in_store, "<HTML><P>");
2561 so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server"));
2563 so_puts(in_store, "<P><CENTER>");
2564 so_puts(in_store, host ? host : unknown);
2565 so_puts(in_store, "</CENTER>");
2567 so_puts(in_store, "<P>");
2568 so_puts(in_store, _("The reason for the failure was"));
2570 /* squirrel away details */
2571 if(details_host)
2572 fs_give((void **)&details_host);
2573 if(details_reason)
2574 fs_give((void **)&details_reason);
2575 if(details_cert)
2576 fs_give((void **)&details_cert);
2578 details_host = cpystr(host ? host : unknown);
2579 details_reason = cpystr(reason ? reason : unknown);
2580 details_cert = cpystr(cert ? cert : unknown);
2582 so_puts(in_store, "<P><CENTER>");
2583 snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2584 reason ? reason : unknown);
2585 tmp[sizeof(tmp)-1] = '\0';
2587 so_puts(in_store, tmp);
2588 so_puts(in_store, "</CENTER>");
2590 so_puts(in_store, "<P>");
2591 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."));
2593 so_puts(in_store, "<P>");
2594 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"));
2596 so_puts(in_store, "<P><CENTER>");
2597 so_puts(in_store, "/novalidate-cert");
2598 so_puts(in_store, "</CENTER>");
2600 so_puts(in_store, "<P>");
2601 so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2603 so_puts(in_store, "<P><CENTER>");
2604 so_puts(in_store, host ? host : unknown);
2605 so_puts(in_store, "</CENTER>");
2607 so_puts(in_store, "<P>");
2608 so_puts(in_store, _("in your configuration, replace those characters with"));
2610 so_puts(in_store, "<P><CENTER>");
2611 so_puts(in_store, host ? host : unknown);
2612 so_puts(in_store, "/novalidate-cert");
2613 so_puts(in_store, "</CENTER>");
2615 so_puts(in_store, "<P>");
2616 so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2618 so_seek(in_store, 0L, 0);
2619 init_handles(&handles);
2620 gf_filter_init();
2621 gf_link_filter(gf_html2plain,
2622 gf_html2plain_opt(NULL,
2623 ps_global->ttyo->screen_cols, non_messageview_margin(),
2624 &handles, NULL, GFHP_LOCAL_HANDLES));
2625 gf_set_so_readc(&gc, in_store);
2626 gf_set_so_writec(&pc, out_store);
2627 gf_pipe(gc, pc);
2628 gf_clear_so_writec(out_store);
2629 gf_clear_so_readc(in_store);
2631 memset(&sargs, 0, sizeof(SCROLL_S));
2632 sargs.text.handles = handles;
2633 sargs.text.text = so_text(out_store);
2634 sargs.text.src = CharStar;
2635 sargs.text.desc = _("help text");
2636 sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2637 sargs.proc.tool = answer_cert_failure;
2638 sargs.proc.data.p = (void *)&the_answer;
2639 sargs.keys.menu = &ans_certquery_keymenu;
2640 /* don't want to re-enter c-client */
2641 sargs.quell_newmail = 1;
2642 setbitmap(sargs.keys.bitmap);
2643 sargs.help.text = h_tls_validation_failure;
2644 sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
2646 scrolltool(&sargs);
2648 if(the_answer == 'y')
2649 rv++;
2650 else if(the_answer == 'n')
2651 ps_global->user_says_cancel = 1;
2653 ps_global->mangled_screen = 1;
2654 ps_global->painted_body_on_startup = 0;
2655 ps_global->painted_footer_on_startup = 0;
2656 so_give(&in_store);
2657 so_give(&out_store);
2658 free_handles(&handles);
2659 if(details_host)
2660 fs_give((void **)&details_host);
2661 if(details_reason)
2662 fs_give((void **)&details_reason);
2663 if(details_cert)
2664 fs_give((void **)&details_cert);
2666 else{
2668 * If screen hasn't been initialized yet, use want_to.
2670 try_wantto:
2671 memset((void *)tmp, 0, sizeof(tmp));
2672 strncpy(tmp,
2673 reason ? reason : _("SSL/TLS certificate validation failure"),
2674 sizeof(tmp));
2675 tmp[sizeof(tmp)-1] = '\0';
2676 strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
2678 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
2679 rv++;
2682 if(rv == 0)
2683 q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
2684 host ? host : unknown);
2686 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
2688 dprint((5, "sslcertificatequery: %s\n",
2689 rv ? "approved" : "rejected"));
2691 return(rv);
2695 char *
2696 pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
2698 char buf[MAILTMPLEN];
2700 if((can_access(mulname, ACCESS_EXISTS) == 0)
2701 || !(can_access(name, ACCESS_EXISTS) == 0))
2702 return(mulname);
2704 snprintf(buf, sizeof(buf),
2705 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2706 last_cmpnt(name),
2707 strlen(last_cmpnt(name)) > 15 ? "..." : "");
2708 buf[sizeof(buf)-1] = '\0';
2709 if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
2710 rename_file(name, mulname);
2711 return(mulname);
2716 url_local_certdetails(char *url)
2718 if(!struncmp(url, "x-alpine-cert:", 14)){
2719 STORE_S *store;
2720 SCROLL_S sargs;
2721 char *folded;
2723 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
2724 q_status_message(SM_ORDER | SM_DING, 7, 10,
2725 _("Error allocating space for details."));
2726 return(0);
2729 so_puts(store, _("Host given by user:\n\n "));
2730 so_puts(store, details_host);
2731 so_puts(store, _("\n\nReason for failure:\n\n "));
2732 so_puts(store, details_reason);
2733 so_puts(store, _("\n\nCertificate being verified:\n\n"));
2734 folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
2735 so_puts(store, folded);
2736 fs_give((void **)&folded);
2737 so_puts(store, "\n");
2739 memset(&sargs, 0, sizeof(SCROLL_S));
2740 sargs.text.text = so_text(store);
2741 sargs.text.src = CharStar;
2742 sargs.text.desc = _("Details");
2743 sargs.bar.title = _("CERT VALIDATION DETAILS");
2744 sargs.help.text = NO_HELP;
2745 sargs.help.title = NULL;
2746 sargs.quell_newmail = 1;
2747 sargs.help.text = h_tls_failure_details;
2748 sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
2750 scrolltool(&sargs);
2752 so_give(&store); /* free resources associated with store */
2753 ps_global->mangled_screen = 1;
2754 return(1);
2757 return(0);
2762 * C-client callback to handle SSL/TLS certificate validation failures
2764 void
2765 pine_sslfailure(char *host, char *reason, long unsigned int flags)
2767 SCROLL_S sargs;
2768 STORE_S *store;
2769 int the_answer = 'n', indent, len, cols;
2770 char buf[500], buf2[500];
2771 char *folded;
2772 char *hst = host ? host : "<unknown>";
2773 char *rsn = reason ? reason : "<unknown>";
2774 char *notls = "/notls";
2775 STRLIST_S hostlist;
2776 int ok_novalidate = 0, warned = 0;
2779 dprint((1, "sslfailure: host=%s reason=%s\n",
2780 hst ? hst : "?",
2781 rsn ? rsn : "?"));
2783 if(flags & NET_SILENT)
2784 return;
2786 hostlist.name = host ? host : "";
2787 hostlist.next = NULL;
2790 * See if we've been told about this host before.
2792 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2793 /* we were told already */
2794 if(warned){
2795 snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn);
2796 buf[sizeof(buf)-1] = '\0';
2797 mm_log(buf, ERROR);
2798 return;
2802 cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2803 cols--;
2805 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS)))
2806 return;
2808 strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf));
2809 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2810 so_puts(store, folded);
2811 fs_give((void **)&folded);
2812 so_puts(store, "\n");
2814 if((len=strlen(hst)) <= cols){
2815 if((indent=((cols-len)/2)) > 0)
2816 so_puts(store, repeat_char(indent, SPACE));
2818 so_puts(store, hst);
2819 so_puts(store, "\n");
2821 else{
2822 strncpy(buf, hst, sizeof(buf));
2823 buf[sizeof(buf)-1] = '\0';
2824 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2825 so_puts(store, folded);
2826 fs_give((void **)&folded);
2829 so_puts(store, "\n");
2831 strncpy(buf, _("The reason for the failure was"), sizeof(buf));
2832 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2833 so_puts(store, folded);
2834 fs_give((void **)&folded);
2835 so_puts(store, "\n");
2837 if((len=strlen(rsn)) <= cols){
2838 if((indent=((cols-len)/2)) > 0)
2839 so_puts(store, repeat_char(indent, SPACE));
2841 so_puts(store, rsn);
2842 so_puts(store, "\n");
2844 else{
2845 strncpy(buf, rsn, sizeof(buf));
2846 buf[sizeof(buf)-1] = '\0';
2847 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2848 so_puts(store, folded);
2849 fs_give((void **)&folded);
2852 so_puts(store, "\n");
2854 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));
2855 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2856 so_puts(store, folded);
2857 fs_give((void **)&folded);
2858 so_puts(store, "\n");
2860 if((len=strlen(notls)) <= cols){
2861 if((indent=((cols-len)/2)) > 0)
2862 so_puts(store, repeat_char(indent, SPACE));
2864 so_puts(store, notls);
2865 so_puts(store, "\n");
2867 else{
2868 strncpy(buf, notls, sizeof(buf));
2869 buf[sizeof(buf)-1] = '\0';
2870 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2871 so_puts(store, folded);
2872 fs_give((void **)&folded);
2875 so_puts(store, "\n");
2877 strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2878 sizeof(buf));
2879 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2880 so_puts(store, folded);
2881 fs_give((void **)&folded);
2882 so_puts(store, "\n");
2884 if((len=strlen(hst)) <= cols){
2885 if((indent=((cols-len)/2)) > 0)
2886 so_puts(store, repeat_char(indent, SPACE));
2888 so_puts(store, hst);
2889 so_puts(store, "\n");
2891 else{
2892 strncpy(buf, hst, sizeof(buf));
2893 buf[sizeof(buf)-1] = '\0';
2894 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2895 so_puts(store, folded);
2896 fs_give((void **)&folded);
2899 so_puts(store, "\n");
2901 strncpy(buf, _("in your configuration, replace those characters with"),
2902 sizeof(buf));
2903 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2904 so_puts(store, folded);
2905 fs_give((void **)&folded);
2906 so_puts(store, "\n");
2908 snprintf(buf2, sizeof(buf2), "%s%s", hst, notls);
2909 buf2[sizeof(buf2)-1] = '\0';
2910 if((len=strlen(buf2)) <= cols){
2911 if((indent=((cols-len)/2)) > 0)
2912 so_puts(store, repeat_char(indent, SPACE));
2914 so_puts(store, buf2);
2915 so_puts(store, "\n");
2917 else{
2918 strncpy(buf, buf2, sizeof(buf));
2919 buf[sizeof(buf)-1] = '\0';
2920 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2921 so_puts(store, folded);
2922 fs_give((void **)&folded);
2925 so_puts(store, "\n");
2927 if(ps_global->ttyo){
2928 strncpy(buf, _("Type RETURN to continue."), sizeof(buf));
2929 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2930 so_puts(store, folded);
2931 fs_give((void **)&folded);
2934 memset(&sargs, 0, sizeof(SCROLL_S));
2935 sargs.text.text = so_text(store);
2936 sargs.text.src = CharStar;
2937 sargs.text.desc = _("help text");
2938 sargs.bar.title = _("SSL/TLS FAILURE");
2939 sargs.proc.tool = answer_cert_failure;
2940 sargs.proc.data.p = (void *)&the_answer;
2941 sargs.keys.menu = &ans_certfail_keymenu;
2942 setbitmap(sargs.keys.bitmap);
2943 /* don't want to re-enter c-client */
2944 sargs.quell_newmail = 1;
2945 sargs.help.text = h_tls_failure;
2946 sargs.help.title = _("HELP FOR TLS/SSL FAILURE");
2948 if(ps_global->ttyo)
2949 scrolltool(&sargs);
2950 else{
2951 char **q, **qp;
2952 char *p;
2953 unsigned char c;
2954 int cnt = 0;
2957 * The screen isn't initialized yet, which should mean that this
2958 * is the result of a -p argument. Display_args_err knows how to deal
2959 * with the uninitialized screen, so we mess with the data to get it
2960 * in shape for display_args_err. This is pretty hacky.
2963 so_seek(store, 0L, 0); /* rewind */
2964 /* count the lines */
2965 while(so_readc(&c, store))
2966 if(c == '\n')
2967 cnt++;
2969 qp = q = (char **)fs_get((cnt+1) * sizeof(char *));
2970 memset(q, 0, (cnt+1) * sizeof(char *));
2972 so_seek(store, 0L, 0); /* rewind */
2973 p = buf;
2974 while(so_readc(&c, store)){
2975 if(c == '\n'){
2976 *p = '\0';
2977 *qp++ = cpystr(buf);
2978 p = buf;
2980 else
2981 *p++ = c;
2984 display_args_err(NULL, q, 0);
2985 free_list_array(&q);
2988 ps_global->mangled_screen = 1;
2989 ps_global->painted_body_on_startup = 0;
2990 ps_global->painted_footer_on_startup = 0;
2991 so_give(&store);
2993 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1);
2998 answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
3000 int rv = 1;
3002 ps_global->next_screen = SCREEN_FUN_NULL;
3004 switch(cmd){
3005 case MC_YES :
3006 *(int *)(sparms->proc.data.p) = 'y';
3007 break;
3009 case MC_NO :
3010 *(int *)(sparms->proc.data.p) = 'n';
3011 break;
3013 default:
3014 alpine_panic("Unexpected command in answer_cert_failure");
3015 break;
3018 return(rv);
3023 oauth2_auth_answer(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
3025 int rv = 1, rc;
3026 AUTH_CODE_S user;
3027 int q_line, flags;
3028 /* TRANSLATORS: user needs to input an access code from the server */
3029 char *accesscodelabel = _("Copy and Paste Access Code");
3030 char token[MAILTMPLEN], prompt[MAILTMPLEN];
3032 ps_global->next_screen = SCREEN_FUN_NULL;
3034 token[0] = '\0';
3035 switch(cmd){
3036 case MC_YES :
3037 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
3038 flags = OE_APPEND_CURRENT;
3039 sprintf(prompt, "%s: ", accesscodelabel);
3040 do {
3041 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
3042 prompt, NULL, NO_HELP, &flags);
3043 } while (rc != 0 && rc != 1);
3044 user.code = rc == 0 ? cpystr(token) : NULL;
3045 user.answer = 'e';
3046 rv = rc == 1 ? 0 : 1;
3047 break;
3049 case MC_NO :
3050 user.code = NULL;
3051 user.answer = 'e';
3052 break;
3054 default:
3055 alpine_panic("Unexpected command in oauth2_auth_answer");
3056 break;
3058 *(AUTH_CODE_S *) sparms->proc.data.p = user;
3059 return(rv);
3063 /*----------------------------------------------------------------------
3064 This can be used to prevent the flickering of the check_cue char
3065 caused by numerous (5000+) fetches by c-client. Right now, the only
3066 practical use found is newsgroup subsciption.
3068 check_cue_display will check if this global is set, and won't clear
3069 the check_cue_char if set.
3070 ----*/
3071 void
3072 set_read_predicted(int i)
3074 ps_global->read_predicted = i==1;
3075 #ifdef _WINDOWS
3076 if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global))
3077 check_cue_display(" ");
3078 #endif
3082 /*----------------------------------------------------------------------
3083 Exported method to retrieve logged in user name associated with stream
3085 Args: host -- host to find associated login name with.
3087 Result:
3088 ----*/
3089 void *
3090 pine_block_notify(int reason, void *data)
3092 switch(reason){
3093 case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */
3094 break;
3096 case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */
3097 break;
3099 case BLOCK_TCPWRITE: /* blocked on TCP write */
3100 case BLOCK_FILELOCK: /* blocked on file locking */
3101 #ifdef _WINDOWS
3102 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3103 check_cue_display(">");
3105 mswin_setcursor(MSWIN_CURSOR_BUSY);
3106 #endif
3107 break;
3109 case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */
3110 case BLOCK_TCPOPEN: /* blocked on TCP open */
3111 case BLOCK_TCPREAD: /* blocked on TCP read */
3112 case BLOCK_TCPCLOSE: /* blocked on TCP close */
3113 #ifdef _WINDOWS
3114 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3115 check_cue_display("<");
3117 mswin_setcursor(MSWIN_CURSOR_BUSY);
3118 #endif
3119 break;
3121 default :
3122 case BLOCK_NONE: /* not blocked */
3123 #ifdef _WINDOWS
3124 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
3125 check_cue_display(" ");
3126 #endif
3127 break;
3131 return(NULL);
3135 void
3136 mm_expunged_current(long unsigned int rawno)
3138 /* expunged something we're viewing? */
3139 if(!ps_global->expunge_in_progress
3140 && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno))
3141 && (ps_global->prev_screen == mail_view_screen
3142 || ps_global->prev_screen == attachment_screen))){
3143 ps_global->next_screen = mail_index_screen;
3144 q_status_message(SM_ORDER | SM_DING , 3, 3,
3145 "Message you were viewing is gone!");
3150 #ifdef PASSFILE
3153 * Specific functions to support caching username/passwd/host
3154 * triples on disk for use from one session to the next...
3157 #define FIRSTCH 0x20
3158 #define LASTCH 0x7e
3159 #define TABSZ (LASTCH - FIRSTCH + 1)
3161 static int xlate_key;
3165 * xlate_in() - xlate_in the given character
3167 char
3168 xlate_in(int c)
3170 register int eti;
3172 eti = xlate_key;
3173 if((c >= FIRSTCH) && (c <= LASTCH)){
3174 eti += (c - FIRSTCH);
3175 eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0;
3176 return((xlate_key = eti) + FIRSTCH);
3178 else
3179 return(c);
3184 * xlate_out() - xlate_out the given character
3186 char
3187 xlate_out(char c)
3189 register int dti;
3190 register int xch;
3192 if((c >= FIRSTCH) && (c <= LASTCH)){
3193 xch = c - (dti = xlate_key);
3194 xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0;
3195 dti = (xch - FIRSTCH) + dti;
3196 dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0;
3197 xlate_key = dti;
3198 return(xch);
3200 else
3201 return(c);
3203 #endif /* PASSFILE */
3206 #ifdef LOCAL_PASSWD_CACHE
3209 int
3210 line_get(char *tmp, size_t len, char **textp)
3212 char *s;
3214 tmp[0] = '\0';
3215 if (*textp == NULL
3216 || (s = strchr(*textp, '\n')) == NULL
3217 || (s - *textp) > len - 1)
3218 return 0;
3220 *s = '\0';
3221 if(*(s-1) == '\r')
3222 *(s-1) = '\0';
3224 snprintf(tmp, len, "%s\n", *textp);
3225 tmp[len-1] = '\0';
3226 *textp = s+1;
3228 return 1;
3231 typedef struct pwd_s {
3232 char *blob;
3233 char **blobarray;
3234 char *host;
3235 char *user;
3236 char *sflags;
3237 char *passwd;
3238 char *orighost;
3239 } ALPINE_PWD_S;
3241 #define SAME_VALUE(X, Y) ((((X) == NULL && (Y) == NULL) \
3242 || ((X) && (Y) && !strcmp((X), (Y)))) ? 1 : 0)
3245 * For UNIX:
3246 * Passfile lines are
3248 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3250 * In pine4.40 and before there was no orig_hostname, and there still isn't
3251 * if it is the same as hostname.
3253 * else for WINDOWS:
3254 * Use Windows credentials. The TargetName of the credential is
3255 * UWash_Alpine_.partnumber-totalparts_<hostname:port>\tuser\taltflag
3256 * and the blob consists of
3257 * passwd\torighost (if different from host)
3259 * We don't use anything fancy we just copy out all the credentials which
3260 * begin with TNAME and put them into our cache, so we don't lookup based
3261 * on the TargetName or anything like that. That was so we could re-use
3262 * the existing code and so that orighost data could be easily used.
3265 read_passfile(pinerc, l)
3266 char *pinerc;
3267 MMLOGIN_S **l;
3269 #ifdef WINCRED
3270 # if (WINCRED > 0)
3271 LPCTSTR lfilter = TEXT(TNAMESTAR);
3272 DWORD count, k;
3273 PCREDENTIAL *pcred;
3274 char *tmp, *blob, *target = NULL;
3275 ALPINE_PWD_S **pwd;
3276 char *ui[5];
3277 int i, j;
3278 unsigned long m, n, p, loc;
3280 if(using_passfile == 0)
3281 return(using_passfile);
3283 if(!g_CredInited){
3284 if (init_wincred_funcs() != 1) {
3285 using_passfile = 0;
3286 return(using_passfile);
3290 dprint((9, "read_passfile\n"));
3292 using_passfile = 1;
3294 /* this code exists because the XOAUTH2 support makes us save
3295 * access tokens as if they were passwords. However, some servers
3296 * produce extremely long access-tokens that do not fit in the credentials
3297 * and therefore need to be split into several entries.
3299 * The plan is the following:
3300 * step 1: Read and save all the information in the credentials
3301 * step 2: flatten the information into one line
3302 * step 3: process that line.
3304 if (g_CredEnumerateW(lfilter, 0, &count, &pcred)) {
3305 pwd = fs_get((count + 1)*sizeof(ALPINE_PWD_S *));
3306 memset((void *)pwd, 0, (count + 1)*sizeof(ALPINE_PWD_S *));
3307 if (pwd && pcred) {
3308 /* this is step 1 */
3309 for (k = 0; k < count; k++) { /* go through each credential */
3310 target = lptstr_to_utf8(pcred[k]->TargetName);
3311 tmp = srchstr(target, TNAME);
3312 if (tmp) {
3313 tmp += strlen(TNAME);
3314 if (*tmp == '.') {
3315 tmp++;
3316 m = strtoul(tmp, &tmp, 10);
3317 if (*tmp == '-') {
3318 tmp++;
3319 n = strtol(tmp, &tmp, 10);
3320 if (*tmp == '_') tmp++;
3323 else {
3324 m = n = 1;
3327 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3328 for (i = 0, j = 0; tmp[i] && j < 3; j++) {
3329 for (ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3330 ; /* find end of data */
3332 if (tmp[i])
3333 tmp[i++] = '\0'; /* tie off data */
3336 /* improve this. We are trying to find where we saved
3337 * this data, and in general this is fast if there is
3338 * only a few data, which is not unreasonable, but probably
3339 * can be done better.
3341 for (loc = 0; pwd[loc]
3342 && !(SAME_VALUE(ui[0], pwd[loc]->host)
3343 && SAME_VALUE(ui[1], pwd[loc]->user)
3344 && SAME_VALUE(ui[2], pwd[loc]->sflags)); loc++);
3346 if (pwd[loc] == NULL) {
3347 pwd[loc] = fs_get(sizeof(ALPINE_PWD_S));
3348 memset((void *) pwd[loc], 0, sizeof(ALPINE_PWD_S));
3349 pwd[loc]->blobarray = fs_get((n + 1) * sizeof(char*));
3350 memset((void *) pwd[loc]->blobarray, 0, (n + 1) * sizeof(char*));
3353 if (pwd[loc]->host == NULL)
3354 pwd[loc]->host = ui[0] ? cpystr(ui[0]) : NULL;
3355 if (pwd[loc]->user == NULL)
3356 pwd[loc]->user = ui[1] ? cpystr(ui[1]) : NULL;
3357 if (pwd[loc]->sflags == NULL)
3358 pwd[loc]->sflags = ui[2] ? cpystr(ui[2]) : NULL;
3359 blob = (char *) pcred[k]->CredentialBlob;
3360 pwd[loc]->blobarray[m - 1] = blob ? cpystr(blob) : NULL;
3362 if (target) fs_give((void**)&target);
3364 /* step 2 */
3365 for (k = 0; k < count; k++) {
3366 if (pwd[k]) {
3367 for (i = 0, j = 0; pwd[k]->blobarray[j]; j++)
3368 i += strlen(pwd[k]->blobarray[j]);
3369 pwd[k]->blob = fs_get(i + 1);
3370 pwd[k]->blob[0] = '\0';
3371 for (j = 0; pwd[k]->blobarray[j]; j++) {
3372 strcat(pwd[k]->blob, pwd[k]->blobarray[j]);
3373 fs_give((void **) &pwd[k]->blobarray[j]);
3375 fs_give((void **) pwd[k]->blobarray);
3377 else k = count; /* we are done with this step! */
3379 /* step 3 */
3380 for (k = 0; k < count; k++) {
3381 if (pwd[k] && pwd[k]->blob) {
3382 blob = pwd[k]->blob;
3383 for (i = 0, j = 3; blob[i] && j < 5; j++) {
3384 for (ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++)
3385 ; /* find end of data */
3387 if (blob[i])
3388 blob[i++] = '\0'; /* tie off data */
3390 if (pwd[k]->passwd == NULL)
3391 pwd[k]->passwd = ui[3] ? cpystr(ui[3]) : NULL;
3392 if (pwd[k]->orighost == NULL)
3393 pwd[k]->orighost = ui[4] ? cpystr(ui[4]) : NULL;
3394 fs_give((void **) &pwd[k]->blob);
3397 /* now process all lines, and free memory */
3398 for (k = 0; k < count && pwd[k] != NULL; k++){
3399 if (pwd[k]->passwd && pwd[k]->host && pwd[k]->user) { /* valid field? */
3400 STRLIST_S hostlist[2];
3401 int flags;
3403 tmp = pwd[k]->sflags ? strchr(pwd[k]->sflags, PWDAUTHSEP) : NULL;
3404 flags = pwd[k]->sflags ? atoi(tmp ? ++tmp : pwd[k]->sflags) : 0;
3405 hostlist[0].name = pwd[k]->host;
3406 if (pwd[k]->orighost) {
3407 hostlist[0].next = &hostlist[1];
3408 hostlist[1].name = pwd[k]->orighost;
3409 hostlist[1].next = NULL;
3411 else {
3412 hostlist[0].next = NULL;
3414 imap_set_passwd(l, pwd[k]->passwd, pwd[k]->user, hostlist, flags & 0x01, 0, 0);
3416 if (pwd[k]->passwd) fs_give((void **) &pwd[k]->passwd);
3417 if (pwd[k]->user) fs_give((void **) &pwd[k]->user);
3418 if (pwd[k]->host) fs_give((void **) &pwd[k]->host);
3419 if (pwd[k]->sflags) fs_give((void **) &pwd[k]->sflags);
3420 if (pwd[k]->orighost) fs_give((void **) &pwd[k]->orighost);
3421 fs_give((void **) &pwd[k]);
3423 g_CredFree((PVOID)pcred);
3425 fs_give((void **) pwd);
3427 return(1);
3429 # else /* old windows */
3430 using_passfile = 0;
3431 return(0);
3432 # endif
3434 #elif APPLEKEYCHAIN
3436 char target[MAILTMPLEN];
3437 char *tmp, *host, *user, *sflags, *passwd, *orighost;
3438 char *ui[5];
3439 int i, j, k, rc;
3440 SecKeychainAttributeList attrList;
3441 SecKeychainSearchRef searchRef = NULL;
3442 SecKeychainAttribute attrs[] = {
3443 { kSecAccountItemAttr, strlen(TNAME), TNAME }
3446 if(using_passfile == 0)
3447 return(using_passfile);
3449 dprint((9, "read_passfile\n"));
3452 /* search for only our items in the keychain */
3453 attrList.count = 1;
3454 attrList.attr = attrs;
3456 using_passfile = 1;
3457 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
3458 kSecGenericPasswordItemClass,
3459 &attrList,
3460 &searchRef))){
3461 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3462 if(searchRef){
3463 SecKeychainItemRef itemRef = NULL;
3464 SecKeychainAttributeInfo info;
3465 SecKeychainAttributeList *attrList = NULL;
3466 UInt32 blength = 0;
3467 char *blob = NULL;
3468 char *blobcopy = NULL; /* NULL terminated copy */
3470 UInt32 tags[] = {kSecAccountItemAttr,
3471 kSecServiceItemAttr};
3472 UInt32 formats[] = {0,0};
3474 dprint((10, "read_passfile: searchRef not NULL\n"));
3475 info.count = 2;
3476 info.tag = tags;
3477 info.format = formats;
3480 * Go through each item we found and put it
3481 * into our list.
3483 while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){
3484 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3485 rc = SecKeychainItemCopyAttributesAndData(itemRef,
3486 &info, NULL,
3487 &attrList,
3488 &blength,
3489 (void **) &blob);
3490 if(rc == 0 && attrList){
3491 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count));
3493 blobcopy = (char *) fs_get((blength + 1) * sizeof(char));
3494 strncpy(blobcopy, (char *) blob, blength);
3495 blobcopy[blength] = '\0';
3498 * I'm not real clear on how this works. It seems to be
3499 * necessary to combine the attributes from two passes
3500 * (attrList->count == 2) in order to get the full set
3501 * of attributes we inserted into the keychain in the
3502 * first place. So, we reset host...orighost outside of
3503 * the following for loop, not inside.
3505 host = user = sflags = passwd = orighost = NULL;
3506 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3508 for(k = 0; k < attrList->count; k++){
3510 if(attrList->attr[k].length){
3511 strncpy(target,
3512 (char *) attrList->attr[k].data,
3513 MIN(attrList->attr[k].length,sizeof(target)));
3514 target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0';
3517 tmp = target;
3518 for(i = 0, j = 0; tmp[i] && j < 3; j++){
3519 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
3520 ; /* find end of data */
3522 if(tmp[i])
3523 tmp[i++] = '\0'; /* tie off data */
3526 if(ui[0])
3527 host = ui[0];
3529 if(ui[1])
3530 user = ui[1];
3532 if(ui[2])
3533 sflags = ui[2];
3535 for(i = 0, j = 3; blobcopy[i] && j < 5; j++){
3536 for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++)
3537 ; /* find end of data */
3539 if(blobcopy[i])
3540 blobcopy[i++] = '\0'; /* tie off data */
3543 if(ui[3])
3544 passwd = ui[3];
3546 if(ui[4])
3547 orighost = ui[4];
3549 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:""));
3552 if(passwd && host && user){ /* valid field? */
3553 STRLIST_S hostlist[2];
3554 int flags;
3556 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
3557 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
3558 hostlist[0].name = host;
3559 if(orighost){
3560 hostlist[0].next = &hostlist[1];
3561 hostlist[1].name = orighost;
3562 hostlist[1].next = NULL;
3564 else{
3565 hostlist[0].next = NULL;
3568 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
3571 if(blobcopy)
3572 fs_give((void **) & blobcopy);
3574 SecKeychainItemFreeAttributesAndData(attrList, (void *) blob);
3576 else{
3577 using_passfile = 0;
3578 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc));
3581 CFRelease(itemRef);
3582 itemRef = NULL;
3585 CFRelease(searchRef);
3587 else{
3588 using_passfile = 0;
3589 dprint((10, "read_passfile: searchRef NULL\n"));
3592 else{
3593 using_passfile = 0;
3594 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc));
3597 return(using_passfile);
3599 #else /* PASSFILE */
3601 char tmp[MAILTMPLEN], *ui[5];
3602 int i, j, n, rv = 0;
3603 size_t len = 0;
3604 char *tmptext = NULL;
3605 struct stat sbuf;
3606 #ifdef SMIME
3607 char tmp2[MAILTMPLEN];
3608 char *text = NULL, *text2 = NULL;
3609 int encrypted = 0;
3610 #endif /* SMIME */
3611 FILE *fp;
3613 if(using_passfile == 0)
3614 return(using_passfile);
3616 dprint((9, "read_passfile\n"));
3618 /* if there's no password to read, bag it!! */
3619 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){
3620 using_passfile = 0;
3621 return(using_passfile);
3624 #ifndef SMIME
3625 if(our_stat(tmp, &sbuf) == 0)
3626 len = sbuf.st_size;
3627 fp = our_fopen(tmp, "rb"); /* reopen to read data */
3628 #else
3629 /* the next call initializes the key/certificate pair used to
3630 * encrypt and decrypt a password file. The details of how this is
3631 * done is in the file pith/smime.c. During this setup we might call
3632 * smime_init(), but no matter what happens we must call smime_deinit()
3633 * there. The reason why this is so is because we can not assume that
3634 * the .pinerc file has been read by this time, so this code might not
3635 * know about the ps_global->smime structure or any of its components,
3636 * and it shouldn't because it only needs ps_global->pwdcert, so
3637 * do not init smime here, because the .pinerc might not have been
3638 * read and we do not really know where the keys and certificates really
3639 * are.
3640 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3641 * it is called for the first time and there are certificates at all,
3642 * or when it is called after the first time and the user refuses to
3643 * create a self-signed certificate. In this situation we will just
3644 * let the user live in an insecure world, but no more passwords will
3645 * be saved in the password file, and only those found there will be used.
3647 tmp2[0] = '\0';
3648 fgets(tmp2, sizeof(tmp2), fp);
3649 fclose(fp);
3650 if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
3651 /* there is an already existing password file, that is not encrypted
3652 * and there is no key to encrypt it yet, go again through setup_pwdcert
3653 * and encrypt it now.
3655 if(tmp2[0]){ /* not empty, UNencrypted password file */
3656 if(ps_global->pwdcert == NULL)
3657 rv = setup_pwdcert(&ps_global->pwdcert);
3658 if((rv == 0 || rv == -5) && ps_global->pwdcert == NULL)
3659 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3660 if(ps_global->pwdcert == NULL){
3661 q_status_message(SM_ORDER, 3, 3,
3662 " Failed to create private key. Using UNencrypted Password file. ");
3663 save_password = 0;
3665 else{
3666 if(rv == 1){
3667 q_status_message(SM_ORDER, 3, 3,
3668 " Failed to unlock private key. Using UNencrypted Password file. ");
3669 save_password = 0; /* do not save more passwords */
3672 if(ps_global->pwdcert != NULL
3673 && encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
3674 encrypted++;
3677 else {
3678 if(ps_global->pwdcert == NULL)
3679 rv = setup_pwdcert(&ps_global->pwdcert);
3680 encrypted++;
3684 * if password file is encrypted we attempt to decrypt. We ask the
3685 * user for the password to unlock the password file. If the user
3686 * enters the password and it unlocks the file, use it and keep saving
3687 * passwords in it. If the user enters the wrong passwords and does
3688 * not unlock it, we will not see that here, but in decrypt_file, so
3689 * the only other possibility is that the user cancels. In that case
3690 * we will see i == -1. In that case, we will let the user attempt
3691 * manual login to the server they want to login, but passwords will
3692 * not be saved so that the password file will not be saved
3693 * unencrypted and rewritten again.
3695 if(encrypted){
3696 text = text2 = decrypt_file((char *)tmp, &i, (PERSONAL_CERT *)ps_global->pwdcert);
3697 len = text2 ? strlen(text2) : 0;
3698 switch(i){
3699 case -2: using_passfile = 0;
3700 break;
3702 case 1 : save_password = 1;
3703 using_passfile = 1;
3704 break;
3706 case -1: save_password = 0;
3707 using_passfile = 1;
3708 break;
3710 default: break;
3713 #endif /* SMIME */
3715 if(using_passfile == 0){
3716 #ifdef SMIME
3717 if(text) fs_give((void **)&text);
3718 #endif /* SMIME */
3719 return using_passfile;
3722 if(len > 0){
3723 tmptext = fs_get(len + 1);
3724 #ifdef SMIME
3725 for(n = 0; encrypted ? line_get(tmptext, len + 1, &text2)
3726 : (fgets(tmptext, len+1, fp) != NULL); n++){
3727 #else /* SMIME */
3728 for(n = 0; fgets(tmptext, len+1, fp); n++){
3729 #endif /* SMIME */
3730 /*** do any necessary DEcryption here ***/
3731 xlate_key = n;
3732 for(i = 0; tmptext[i]; i++)
3733 tmptext[i] = xlate_out(tmptext[i]);
3735 if(i && tmptext[i-1] == '\n')
3736 tmptext[i-1] = '\0'; /* blast '\n' */
3738 dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
3739 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3740 for(i = 0, j = 0; tmptext[i] && j < 5; j++){
3741 for(ui[j] = &tmptext[i]; tmptext[i] && tmptext[i] != '\t'; i++)
3742 ; /* find end of data */
3744 if(tmptext[i])
3745 tmptext[i++] = '\0'; /* tie off data */
3748 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3749 if(ui[0] && ui[1] && ui[2]){ /* valid field? */
3750 STRLIST_S hostlist[2];
3751 char *s = ui[3] ? strchr(ui[3], PWDAUTHSEP) : NULL;
3752 int flags = ui[3] ? atoi(s ? ++s : ui[3]) : 0;
3754 hostlist[0].name = ui[2];
3755 if(ui[4]){
3756 hostlist[0].next = &hostlist[1];
3757 hostlist[1].name = ui[4];
3758 hostlist[1].next = NULL;
3760 else{
3761 hostlist[0].next = NULL;
3764 imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0);
3769 if (tmptext) fs_give((void **) &tmptext);
3770 #ifdef SMIME
3771 if (text) fs_give((void **)&text);
3772 #else /* SMIME */
3773 fclose(fp);
3774 #endif /* SMIME */
3775 return(1);
3776 #endif /* PASSFILE */
3781 void
3782 write_passfile(pinerc, l)
3783 char *pinerc;
3784 MMLOGIN_S *l;
3786 char *authend, *authtype;
3787 #ifdef WINCRED
3788 # if (WINCRED > 0)
3789 int i, j, k;
3790 char target[10*MAILTMPLEN];
3791 char blob[10 * MAILTMPLEN], blob2[10*MAILTMPLEN], *blobp;
3792 char part[MAILTMPLEN];
3793 CREDENTIAL cred;
3794 LPTSTR ltarget = 0;
3796 if(using_passfile == 0)
3797 return;
3799 dprint((9, "write_passfile\n"));
3801 for(; l; l = l->next){
3802 /* determine how many parts to create first */
3803 snprintf(blob, sizeof(blob), "%s%s%s",
3804 l->passwd ? l->passwd : "",
3805 (l->hosts&& l->hosts->next&& l->hosts->next->name)
3806 ? "\t" : "",
3807 (l->hosts&& l->hosts->next&& l->hosts->next->name)
3808 ? l->hosts->next->name : "");
3809 i = strlen(blob);
3810 blobp = blob;
3811 for (j = 1; i > MAXPWDBUFFERSIZE; j++, i -= PWDBUFFERSIZE);
3812 authtype = l->passwd;
3813 authend = strchr(l->passwd, PWDAUTHSEP);
3814 if (authend != NULL){
3815 *authend = '\0';
3816 sprintf(blob2, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3817 *authend = PWDAUTHSEP;
3819 else
3820 sprintf(blob2, "%d", l->altflag);
3821 for (k = 1, i = strlen(blob), blobp = blob; k <= j; k++) {
3822 snprintf(target, sizeof(target), "%s.%d-%d_%s\t%s\t%s",
3823 TNAME, k, j,
3824 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3825 l->user ? l->user : "",
3826 blob2);
3827 ltarget = utf8_to_lptstr((LPSTR)target);
3828 if (ltarget) {
3829 memset((void*)&cred, 0, sizeof(cred));
3830 cred.Flags = 0;
3831 cred.Type = CRED_TYPE_GENERIC;
3832 cred.TargetName = ltarget;
3833 if (i > MAXPWDBUFFERSIZE) {
3834 strncpy(part, blobp, PWDBUFFERSIZE);
3835 part[PWDBUFFERSIZE] = '\0';
3836 blobp += PWDBUFFERSIZE;
3837 i -= PWDBUFFERSIZE;
3839 else
3840 strcpy(part, blobp);
3841 cred.CredentialBlobSize = strlen(part) + 1;
3842 cred.CredentialBlob = (LPBYTE)&part;
3843 cred.Persist = CRED_PERSIST_ENTERPRISE;
3844 g_CredWriteW(&cred, 0);
3845 fs_give((void**)&ltarget);
3849 #endif /* WINCRED > 0 */
3851 #elif APPLEKEYCHAIN
3852 int rc;
3853 char target[10*MAILTMPLEN];
3854 char blob[10*MAILTMPLEN];
3855 SecKeychainItemRef itemRef = NULL;
3857 if(using_passfile == 0)
3858 return;
3860 dprint((9, "write_passfile\n"));
3862 for(; l; l = l->next){
3863 authtype = l->passwd;
3864 authend = strchr(l->passwd, PWDAUTHSEP);
3865 if(authend != NULL){
3866 *authend = '\0';
3867 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3868 *authend = PWDAUTHSEP;
3870 else
3871 sprintf(blob, "%d", l->altflag);
3873 snprintf(target, sizeof(target), "%s\t%s\t%s",
3874 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3875 l->user ? l->user : "",
3876 blob);
3878 snprintf(blob, sizeof(blob), "%s%s%s",
3879 l->passwd ? l->passwd : "",
3880 (l->hosts && l->hosts->next && l->hosts->next->name)
3881 ? "\t" : "",
3882 (l->hosts && l->hosts->next && l->hosts->next->name)
3883 ? l->hosts->next->name : "");
3885 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob));
3887 rc = SecKeychainAddGenericPassword(NULL,
3888 strlen(target), target,
3889 strlen(TNAME), TNAME,
3890 strlen(blob), blob,
3891 NULL);
3892 if(rc==0){
3893 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3895 else{
3896 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc));
3899 if(rc == errSecDuplicateItem){
3900 /* fix existing entry */
3901 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3902 itemRef = NULL;
3903 if(!(rc=SecKeychainFindGenericPassword(NULL,
3904 strlen(target), target,
3905 strlen(TNAME), TNAME,
3906 NULL, NULL,
3907 &itemRef)) && itemRef){
3909 rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob);
3910 if(!rc){
3911 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc));
3914 else{
3915 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc));
3920 #else /* PASSFILE */
3921 char tmp[10*MAILTMPLEN], blob[10*MAILTMPLEN];
3922 int i, n;
3923 FILE *fp;
3924 #ifdef SMIME
3925 char *text = NULL, tmp2[10*MAILTMPLEN];
3926 int len = 0;
3927 #endif
3929 if(using_passfile == 0)
3930 return;
3932 dprint((9, "write_passfile\n"));
3934 /* if there's no passfile to read, bag it!! */
3935 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "wb"))){
3936 using_passfile = 0;
3937 return;
3940 #ifdef SMIME
3941 strncpy(tmp2, tmp, sizeof(tmp2));
3942 tmp2[sizeof(tmp2)-1] = '\0';
3943 #endif /* SMIME */
3945 for(n = 0; l; l = l->next, n++){
3946 authtype = l->passwd;
3947 authend = strchr(l->passwd, PWDAUTHSEP);
3948 if(authend != NULL){
3949 *authend = '\0';
3950 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3951 *authend = PWDAUTHSEP;
3953 else
3954 sprintf(blob, "%d", l->altflag);
3956 /*** do any necessary ENcryption here ***/
3957 snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%s%s%s\n", l->passwd, l->user,
3958 l->hosts->name, blob,
3959 (l->hosts->next && l->hosts->next->name) ? "\t" : "",
3960 (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
3961 : "");
3962 dprint((10, "write_passfile: %s", tmp ? tmp : "?"));
3963 xlate_key = n;
3964 for(i = 0; tmp[i]; i++)
3965 tmp[i] = xlate_in(tmp[i]);
3967 #ifdef SMIME
3968 fs_resize((void **)&text, (len + strlen(tmp) + 1)*sizeof(char));
3969 text[len] = '\0';
3970 len += strlen(tmp) + 1;
3971 strncat(text, tmp, strlen(tmp));
3972 #else /* SMIME */
3973 fputs(tmp, fp);
3974 #endif /* SMIME */
3977 fclose(fp);
3978 #ifdef SMIME
3979 if(text != NULL){
3980 if(ps_global->pwdcert == NULL){
3981 q_status_message(SM_ORDER, 3, 3, "Attempting to encrypt password file");
3982 i = setup_pwdcert(&ps_global->pwdcert);
3983 if((i == 0 || i == -5) && ps_global->pwdcert == NULL)
3984 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3986 if(ps_global->pwdcert == NULL){ /* we tried but failed */
3987 if(i == -1)
3988 q_status_message1(SM_ORDER, 3, 3, "Error: no directory %s to save certificates", ps_global->pwdcertdir);
3989 else
3990 q_status_message(SM_ORDER, 3, 3, "Refusing to write non-encrypted password file");
3992 else if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0)
3993 q_status_message(SM_ORDER, 3, 3, "Failed to encrypt password file");
3994 fs_give((void **)&text); /* do not save this text */
3996 #endif /* SMIME */
3997 #endif /* PASSFILE */
4000 #endif /* LOCAL_PASSWD_CACHE */
4003 #if (WINCRED > 0)
4004 void
4005 erase_windows_credentials(void)
4007 LPCTSTR lfilter = TEXT(TNAMESTAR);
4008 DWORD count, k;
4009 PCREDENTIAL *pcred;
4011 if(!g_CredInited){
4012 if(init_wincred_funcs() != 1)
4013 return;
4016 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
4017 if(pcred){
4018 for(k = 0; k < count; k++)
4019 g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0);
4021 g_CredFree((PVOID) pcred);
4026 void
4027 ask_erase_credentials(void)
4029 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){
4030 erase_windows_credentials();
4031 q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased");
4033 else
4034 q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased");
4037 #endif /* WINCRED */
4040 #ifdef LOCAL_PASSWD_CACHE
4042 get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
4043 char *pinerc, **passwd, *user;
4044 STRLIST_S *hostlist;
4045 int altflag;
4047 return get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, NULL);
4051 * get_passfile_passwd_auth - return the password contained in the special password
4052 * cache. The file is assumed to be in the same directory
4053 * as the pinerc with the name defined above.
4056 get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, authtype)
4057 char *pinerc, **passwd, *user;
4058 STRLIST_S *hostlist;
4059 int altflag;
4060 char *authtype;
4062 dprint((10, "get_passfile_passwd_auth\n"));
4063 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
4064 ? imap_get_passwd_auth(passfile_cache, passwd,
4065 user, hostlist, altflag, authtype)
4066 : 0);
4069 void
4070 free_passfile_cache_work(MMLOGIN_S **pwdcache)
4072 if(pwdcache == NULL || *pwdcache == NULL)
4073 return;
4075 if((*pwdcache)->user) fs_give((void **)&(*pwdcache)->user);
4076 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
4077 if((*pwdcache)->hosts) free_strlist(&(*pwdcache)->hosts);
4078 free_passfile_cache_work(&(*pwdcache)->next);
4079 fs_give((void **)pwdcache);
4083 void
4084 free_passfile_cache(void)
4086 if(passfile_cache)
4087 free_passfile_cache_work(&passfile_cache);
4091 is_using_passfile(void)
4093 return(using_passfile == 1);
4097 * Just trying to guess the username the user might want to use on this
4098 * host, the user will confirm.
4100 char *
4101 get_passfile_user(pinerc, hostlist)
4102 char *pinerc;
4103 STRLIST_S *hostlist;
4105 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
4106 ? imap_get_user(passfile_cache, hostlist)
4107 : NULL);
4112 preserve_prompt(char *pinerc)
4114 return preserve_prompt_auth(pinerc, NULL);
4118 preserve_prompt_auth(char *pinerc, char *authtype)
4120 #ifdef WINCRED
4121 # if (WINCRED > 0)
4122 #define PROMPT_PWD _("Preserve password for next login")
4123 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4125 * This prompt was going to be able to be turned on and off via a registry
4126 * setting controlled from the config menu. We decided to always use the
4127 * dialog for login, and there the prompt is unobtrusive enough to always
4128 * be in there. As a result, windows should never reach this, but now
4129 * OS X somewhat uses the behavior just described.
4131 if(mswin_store_pass_prompt()
4132 && (want_to(authtype
4133 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4134 : PROMPT_PWD,
4135 'y', 'x', NO_HELP, WT_NORM)
4136 == 'y'))
4137 return(1);
4138 else
4139 return(0);
4140 # else
4141 return(0);
4142 # endif
4144 #elif APPLEKEYCHAIN
4145 #define PROMPT_PWD _("Preserve password for next login")
4146 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4148 int rc;
4149 if((rc = macos_store_pass_prompt()) != 0){
4150 if(want_to(authtype
4151 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4152 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4153 == 'y'){
4154 if(rc == -1){
4155 macos_set_store_pass_prompt(1);
4156 q_status_message(SM_ORDER, 4, 4,
4157 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4159 return(1);
4161 else if(rc == -1){
4162 macos_set_store_pass_prompt(0);
4163 q_status_message(SM_ORDER, 4, 4,
4164 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4166 return(0);
4168 return(0);
4169 #else /* PASSFILE */
4170 #define PROMPT_PWD _("Preserve password on DISK for next login")
4171 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
4173 char tmp[MAILTMPLEN];
4174 struct stat sbuf;
4176 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || our_stat(tmp, &sbuf) < 0)
4177 return 0;
4179 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
4180 return(want_to(authtype
4181 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
4182 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
4183 == 'y');
4184 return(0);
4185 #endif /* PASSFILE */
4188 #endif /* LOCAL_PASSWD_CACHE */
4191 #ifdef APPLEKEYCHAIN
4194 * Returns:
4195 * 1 if store pass prompt is set in the "registry" to on
4196 * 0 if set to off
4197 * -1 if not set to anything
4200 macos_store_pass_prompt(void)
4202 char *data = NULL;
4203 UInt32 len = 0;
4204 int rc = -1;
4205 int val;
4207 if(storepassprompt == -1){
4208 if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL,
4209 strlen(TNAMEPROMPT),
4210 TNAMEPROMPT, &len,
4211 (void **) &data, NULL))){
4212 val = (len == 1 && data && data[0] == '1');
4216 if(storepassprompt == -1 && !rc){
4217 if(val)
4218 storepassprompt = 1;
4219 else
4220 storepassprompt = 0;
4223 return(storepassprompt);
4227 void
4228 macos_set_store_pass_prompt(int val)
4230 storepassprompt = val ? 1 : 0;
4232 SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT),
4233 TNAMEPROMPT, 1, val ? "1" : "0", NULL);
4237 void
4238 macos_erase_keychain(void)
4240 SecKeychainAttributeList attrList;
4241 SecKeychainSearchRef searchRef = NULL;
4242 SecKeychainAttribute attrs1[] = {
4243 { kSecAccountItemAttr, strlen(TNAME), TNAME }
4245 SecKeychainAttribute attrs2[] = {
4246 { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT }
4249 dprint((9, "macos_erase_keychain\n"));
4252 * Seems like we ought to be able to combine attrs1 and attrs2
4253 * into a single array, but I couldn't get it to work.
4256 /* search for only our items in the keychain */
4257 attrList.count = 1;
4258 attrList.attr = attrs1;
4260 if(!SecKeychainSearchCreateFromAttributes(NULL,
4261 kSecGenericPasswordItemClass,
4262 &attrList,
4263 &searchRef)){
4264 if(searchRef){
4265 SecKeychainItemRef itemRef = NULL;
4268 * Go through each item we found and put it
4269 * into our list.
4271 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4272 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4273 SecKeychainItemDelete(itemRef);
4274 CFRelease(itemRef);
4277 CFRelease(searchRef);
4281 attrList.count = 1;
4282 attrList.attr = attrs2;
4284 if(!SecKeychainSearchCreateFromAttributes(NULL,
4285 kSecGenericPasswordItemClass,
4286 &attrList,
4287 &searchRef)){
4288 if(searchRef){
4289 SecKeychainItemRef itemRef = NULL;
4292 * Go through each item we found and put it
4293 * into our list.
4295 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
4296 SecKeychainItemDelete(itemRef);
4297 CFRelease(itemRef);
4300 CFRelease(searchRef);
4305 #endif /* APPLEKEYCHAIN */
4307 #ifdef LOCAL_PASSWD_CACHE
4309 void
4310 set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
4311 char *pinerc, *passwd, *user;
4312 STRLIST_S *hostlist;
4313 int altflag, already_prompted;
4315 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, NULL);
4318 * set_passfile_passwd - set the password file entry associated with
4319 * cache. The file is assumed to be in the same directory
4320 * as the pinerc with the name defined above.
4321 * already_prompted: 0 not prompted
4322 * 1 prompted, answered yes
4323 * 2 prompted, answered no
4325 void
4326 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, authtype)
4327 char *pinerc, *passwd, *user;
4328 STRLIST_S *hostlist;
4329 int altflag, already_prompted;
4330 char *authtype;
4332 dprint((10, "set_passfile_passwd_auth\n"));
4333 if(((already_prompted == 0 && preserve_prompt_auth(pinerc, authtype))
4334 || already_prompted == 1)
4335 && !ps_global->nowrite_password_cache
4336 && (passfile_cache || read_passfile(pinerc, &passfile_cache))){
4337 imap_set_passwd_auth(&passfile_cache, passwd, user, hostlist, altflag, 0, 0, authtype);
4338 write_passfile(pinerc, passfile_cache);
4342 void
4343 update_passfile_hostlist(pinerc, user, hostlist, altflag)
4344 char *pinerc;
4345 char *user;
4346 STRLIST_S *hostlist;
4347 int altflag;
4349 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, NULL);
4353 * Passfile lines are
4355 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4357 * In pine4.40 and before there was no orig_hostname.
4358 * This routine attempts to repair that.
4360 void
4361 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, authtype)
4362 char *pinerc;
4363 char *user;
4364 STRLIST_S *hostlist;
4365 int altflag;
4366 char *authtype;
4368 #ifdef WINCRED
4369 return;
4370 #else /* !WINCRED */
4371 MMLOGIN_S *l;
4372 size_t len = authtype ? strlen(authtype) : 0;
4373 size_t offset = authtype ? 1 : 0;
4375 for(l = passfile_cache; l; l = l->next)
4376 if(imap_same_host_auth(l->hosts, hostlist, authtype)
4377 && *user
4378 && !strcmp(user, l->user + len + offset)
4379 && l->altflag == altflag){
4380 break;
4383 if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
4384 && hostlist->next->name
4385 && !ps_global->nowrite_password_cache){
4386 l->hosts->next = new_strlist_auth(hostlist->next->name, authtype, PWDAUTHSEP);
4387 write_passfile(pinerc, passfile_cache);
4389 #endif /* !WINCRED */
4392 #endif /* LOCAL_PASSWD_CACHE */
4395 #if (WINCRED > 0)
4397 * Load and init the WinCred structure.
4398 * This gives us a way to skip the WinCred code
4399 * if the dll doesn't exist.
4402 init_wincred_funcs(void)
4404 if(!g_CredInited)
4406 HMODULE hmod;
4408 /* Assume the worst. */
4409 g_CredInited = -1;
4411 hmod = LoadLibrary(TEXT("advapi32.dll"));
4412 if(hmod)
4414 FARPROC fpCredWriteW;
4415 FARPROC fpCredEnumerateW;
4416 FARPROC fpCredDeleteW;
4417 FARPROC fpCredFree;
4419 fpCredWriteW = GetProcAddress(hmod, "CredWriteW");
4420 fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW");
4421 fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW");
4422 fpCredFree = GetProcAddress(hmod, "CredFree");
4424 if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree)
4426 g_CredWriteW = (CREDWRITEW *)fpCredWriteW;
4427 g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW;
4428 g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW;
4429 g_CredFree = (CREDFREE *)fpCredFree;
4431 g_CredInited = 1;
4435 mswin_set_erasecreds_callback(ask_erase_credentials);
4438 return g_CredInited;
4441 #endif /* WINCRED */