* Few improvements in the password management code.
[alpine.git] / alpine / imap.c
blobf4bf396561462681165865755f02028e6b67e89b
1 #if !defined(lint) && !defined(DOS)
2 static char rcsid[] = "$Id: imap.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
3 #endif
5 /*
6 * ========================================================================
7 * Copyright 2013-2018 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
20 imap.c
21 The call back routines for the c-client/imap
22 - handles error messages and other notification
23 - handles prelimirary notification of new mail and expunged mail
24 - prompting for imap server login and password
26 ====*/
28 #include "headers.h"
29 #include "alpine.h"
30 #include "imap.h"
31 #include "status.h"
32 #include "mailview.h"
33 #include "mailcmd.h"
34 #include "radio.h"
35 #include "keymenu.h"
36 #include "signal.h"
37 #include "mailpart.h"
38 #include "mailindx.h"
39 #include "arg.h"
40 #include "busy.h"
41 #include "titlebar.h"
42 #include "xoauth2.h"
43 #include "../pith/state.h"
44 #include "../pith/conf.h"
45 #include "../pith/msgno.h"
46 #include "../pith/filter.h"
47 #include "../pith/news.h"
48 #include "../pith/util.h"
49 #include "../pith/list.h"
50 #include "../pith/margin.h"
51 #ifdef SMIME
52 #include "../pith/smime.h"
53 #endif /* SMIME */
55 #if (WINCRED > 0)
56 #include <wincred.h>
57 #define TNAME "UWash_Alpine_"
58 #define TNAMESTAR "UWash_Alpine_*"
61 * WinCred Function prototypes
63 typedef BOOL (WINAPI CREDWRITEW) ( __in PCREDENTIALW Credential, __in DWORD Flags );
64 typedef BOOL (WINAPI CREDENUMERATEW) ( __in LPCWSTR Filter, __reserved DWORD Flags,
65 __out DWORD *Count, __deref_out_ecount(*Count) PCREDENTIALW **Credential );
66 typedef BOOL (WINAPI CREDDELETEW) ( __in LPCWSTR TargetName, __in DWORD Type,
67 __reserved DWORD Flags );
68 typedef VOID (WINAPI CREDFREE) ( __in PVOID Buffer );
71 * WinCred functions
73 int g_CredInited = 0; /* 1 for loaded successfully,
74 * -1 for not available.
75 * 0 for not initialized yet.
77 CREDWRITEW *g_CredWriteW;
78 CREDENUMERATEW *g_CredEnumerateW;
79 CREDDELETEW *g_CredDeleteW;
80 CREDFREE *g_CredFree;
82 #endif /* WINCRED */
84 #ifdef APPLEKEYCHAIN
85 #include <Security/SecKeychain.h>
86 #include <Security/SecKeychainItem.h>
87 #include <Security/SecKeychainSearch.h>
88 #define TNAME "UWash_Alpine"
89 #define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
91 int macos_store_pass_prompt(void);
92 void macos_set_store_pass_prompt(int);
94 static int storepassprompt = -1;
95 #endif /* APPLEKEYCHAIN */
99 * Internal prototypes
101 void mm_login_alt_cue(NETMBX *);
102 long pine_tcptimeout_noscreen(long, long, char *);
103 int answer_cert_failure(int, MSGNO_S *, SCROLL_S *);
104 int oauth2_auth_answer(int, MSGNO_S *, SCROLL_S *);
106 #ifdef LOCAL_PASSWD_CACHE
107 int read_passfile(char *, MMLOGIN_S **);
108 void write_passfile(char *, MMLOGIN_S *);
109 int preserve_prompt(char *);
110 int preserve_prompt_auth(char *, char *authtype);
111 void update_passfile_hostlist(char *, char *, STRLIST_S *, int);
112 void update_passfile_hostlist_auth(char *, char *, STRLIST_S *, int, char *);
113 void free_passfile_cache_work(MMLOGIN_S **);
115 static MMLOGIN_S *passfile_cache = NULL;
116 static int using_passfile = -1;
117 int save_password = 1;
118 #endif /* LOCAL_PASSWD_CACHE */
120 #ifdef PASSFILE
121 char xlate_in(int);
122 char xlate_out(char);
123 int line_get(char *, size_t, char **);
124 #endif /* PASSFILE */
126 #if (WINCRED > 0)
127 void ask_erase_credentials(void);
128 int init_wincred_funcs(void);
129 #endif /* WINCRED */
132 static char *details_cert, *details_host, *details_reason;
134 typedef struct auth_code_s {
135 char *code;
136 int answer;
137 } AUTH_CODE_S;
139 char *
140 oauth2_get_access_code(char *url, OAUTH2_S *oauth2, int *tryanother)
142 char tmp[MAILTMPLEN];
143 char *code;
145 if(ps_global->ttyo){
146 SCROLL_S sargs;
147 STORE_S *in_store, *out_store;
148 gf_io_t pc, gc;
149 HANDLE_S *handles = NULL;
150 AUTH_CODE_S user_input;
152 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
153 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
154 goto try_wantto;
156 so_puts(in_store, "<HTML><P>");
157 sprintf(tmp, _("<CENTER>Auhtorizing Alpine Access to %s Email Services</CENTER>"), oauth2->name);
158 so_puts(in_store, tmp);
159 sprintf(tmp, _("</P><P>Alpine is attempting to log you into your %s account, using the XOAUTH2 method."), oauth2->name),
160 so_puts(in_store, tmp);
162 so_puts(in_store, _(" In order to do that, Alpine needs to open the following URL:"));
163 so_puts(in_store,"</P><P>");
164 sprintf(tmp_20k_buf, _("<A HREF=\"%s\">%s.</A>"), url, url);
165 so_puts(in_store, tmp_20k_buf);
167 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
168 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2->name);
169 so_puts(in_store, tmp);
170 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
172 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. "));
173 so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page."));
174 so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
175 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. "));
176 so_puts(in_store, _(" Once you have completed this process, Alpine will proceed with authentication."));
177 so_puts(in_store, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
178 so_puts(in_store, _("</P></HTML>"));
180 so_seek(in_store, 0L, 0);
181 init_handles(&handles);
182 gf_filter_init();
183 gf_link_filter(gf_html2plain,
184 gf_html2plain_opt(NULL,
185 ps_global->ttyo->screen_cols, non_messageview_margin(),
186 &handles, NULL, GFHP_LOCAL_HANDLES));
187 gf_set_so_readc(&gc, in_store);
188 gf_set_so_writec(&pc, out_store);
189 gf_pipe(gc, pc);
190 gf_clear_so_writec(out_store);
191 gf_clear_so_readc(in_store);
193 memset(&sargs, 0, sizeof(SCROLL_S));
194 sargs.text.handles = handles;
195 sargs.text.text = so_text(out_store);
196 sargs.text.src = CharStar;
197 sargs.text.desc = _("help text");
198 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
199 sargs.proc.tool = oauth2_auth_answer;
200 sargs.proc.data.p = (void *)&user_input;
201 sargs.keys.menu = &oauth2_auth_keymenu;
202 /* don't want to re-enter c-client */
203 sargs.quell_newmail = 1;
204 setbitmap(sargs.keys.bitmap);
205 sargs.help.text = h_oauth2_start;
206 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
208 do {
209 scrolltool(&sargs);
210 ps_global->mangled_screen = 1;
211 ps_global->painted_body_on_startup = 0;
212 ps_global->painted_footer_on_startup = 0;
213 } while (user_input.answer != 'e');
215 if(!struncmp(user_input.code, "https://", 8)){
216 char *s, *t;
217 s = strstr(user_input.code, "code=");
218 if(s != NULL){
219 t = strchr(s, '&');
220 if(t) *t = '\0';
221 code = cpystr(s+5);
222 if(t) *t = '&';
225 else code = user_input.code ? cpystr(user_input.code) : NULL;
226 if(user_input.code) fs_give((void **) &user_input.code);
228 if(code == NULL) *tryanother = 1;
230 so_give(&in_store);
231 so_give(&out_store);
232 free_handles(&handles);
234 else{
235 int flags, rc, q_line;
236 /* TRANSLATORS: user needs to input an access code from the server */
237 char *accesscodelabel = _("Copy and Paste Access Code");
238 char prompt[MAILTMPLEN], token[MAILTMPLEN];
240 * If screen hasn't been initialized yet, use want_to.
242 try_wantto:
243 memset((void *)tmp, 0, sizeof(tmp));
244 strncpy(tmp, _("Alpine would like to get authorization to access your email: "), sizeof(tmp));
245 tmp[sizeof(tmp)-1] = '\0';
246 strncat(tmp, _(": Proceed "), sizeof(tmp)-strlen(tmp)-1);
248 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
249 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
250 flags = OE_APPEND_CURRENT;
251 sprintf(prompt, "%s: ", accesscodelabel);
252 do {
253 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
254 prompt, NULL, NO_HELP, &flags);
255 } while (rc != 0 && rc != 1);
256 if(!struncmp(token, "https://", 8)){
257 char *s, *t;
258 s = strstr(token, "code=");
259 if(s != NULL){
260 t = strchr(s, '&');
261 if(t) *t = '\0';
262 code = cpystr(s+5);
263 if(t) *t = '&';
266 else code = token[0] ? cpystr(token) : NULL;
270 return code;
273 void mm_login_oauth2(NETMBX *, char *, OAUTH2_S *, long int, char *, char *);
275 /* The purpose of this function is to report to c-client the values of the
276 * different tokens and codes so that c-client can try to log in the user
277 * to the server. This function DOES NOT attempt to get these values for
278 * the user. That is attempted in the c-client side (as best as it can be
279 * done given our circumstances: no http support, no javascript support,
280 * etc.). This is the best we can do:
282 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
283 * as best as we can. Unloaded means that there is no server known to
284 * connect, no access token, etc. Pretty much nothing is known about
285 * how to get access code, access token, etc. We ask the user to fill
286 * it up for us, if they can. If the user fills it up we save those
287 * values.
289 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
290 * save the information that c-client got for us.
292 * 3. When saving this information we use the password caching facilities,
293 * but we must do it in a different format so that old information and
294 * new information are not mixed. In order to accomodate this for new
295 * authentication methods, we save the information in the same fields,
296 * but this time we modify it slightly, so that old functions fail to
297 * understand the new information and so not modify it nor use it. The
298 * modification is simple: Every piece of information that was saved
299 * before is prepended XOAUTH2\001 to indicate the authentication
300 * method for which it works. The character \001 is a separator. New
301 * Alpine will know how to deal with this, but old versions, will not
302 * strip this prefix from the information and fail to get the
303 * information or modify it when needed. Only new versions of Alpine will
304 * know how to process this information.
305 * new_value = authenticator_method separator old_value
306 * authenticator_method = "XOAUTH2_S"
307 * separator = "U+1"
308 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
309 * authenticator is "XOAUTH2\001imap.gmail.com".
310 * In addition, the password field is not used to encode the password
311 * anymore, it is used to save login information needed in a format that
312 * the caller function chooses, but that must be preceeded by the
313 * "authenticator_method separator" code as above.
315 void
316 mm_login_oauth2(NETMBX *mb, char *user, OAUTH2_S *login, long int trial,
317 char *usethisprompt, char *altuserforcache)
319 char *token, tmp[MAILTMPLEN];
320 char prompt[4*MAILTMPLEN], value[4*MAILTMPLEN], *last;
321 char defuser[NETMAXUSER];
322 char hostleadin[80], hostname[200], defubuf[200];
323 char logleadin[80], pwleadin[50];
324 char *url_oauth2;
325 char *tool = NULL;
326 char *OldRefreshToken, *OldAccessToken;
327 char *NewRefreshToken, *NewAccessToken;
328 char *SaveRefreshToken, *SaveAccessToken;
329 /* TRANSLATORS: A label for the hostname that the user is logging in on */
330 char *hostlabel = _("HOST");
331 /* TRANSLATORS: user is logging in as a particular user (a particular
332 login name), this is just labelling that user name. */
333 char *userlabel = _("USER");
334 STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1];
335 HelpType help ;
336 int len, rc, q_line, flags, i, j;
337 int oespace, avail, need, save_dont_use;
338 int save_in_init;
339 int registered;
340 int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
341 OAUTH2_S *oa2list;
342 unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
343 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
344 int preserve_password = -1;
345 #endif
347 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
348 trial, mb->user ? mb->user : "(null)",
349 mb->service ? mb->service : "(null)",
350 mb->port ? " port=" : "",
351 mb->port ? comatose(mb->port) : "",
352 altuserforcache ? " altuserforcache =" : "",
353 altuserforcache ? altuserforcache : ""));
355 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
357 save_in_init = ps_global->in_init_seq;
358 ps_global->in_init_seq = 0;
359 ps_global->no_newmail_check_from_optionally_enter = 1;
361 /* make sure errors are seen */
362 if(ps_global->ttyo)
363 flush_status_messages(0);
365 token = NULL; /* start from scratch */
367 hostlist[0].name = mb->host;
368 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
369 hostlist[0].next = &hostlist[1];
370 hostlist[1].name = mb->orighost;
371 hostlist[1].next = NULL;
373 else
374 hostlist[0].next = NULL;
376 if(hostlist[0].name){
377 dprint((9, "mm_login_oauth2: host=%s\n",
378 hostlist[0].name ? hostlist[0].name : "?"));
379 if(hostlist[0].next && hostlist[1].name){
380 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name));
385 * We check to see if the server we are going to log in to is already
386 * registered. This gives us a list of servers with the same
387 * credentials, so we use the same credentials for all of them.
390 for(registered = 0, oa2list = alpine_oauth2_list;
391 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
392 oa2list++){
393 for(i = 0; i < OAUTH2_TOT_EQUIV
394 && oa2list->host[i] != NULL
395 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
396 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
397 registered++;
398 break;
402 if(registered){
403 hostlist2[i = 0].name = mb->host;
404 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
405 hostlist2[++i].name = mb->orighost;
407 for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){
408 int k;
409 for(k = 0; k <= i && hostlist2[k].name
410 && strcmp(hostlist2[k].name, oa2list->host[j]); k++);
411 if(k == i + 1)
412 hostlist2[++i].name = oa2list->host[j];
414 hostlist2[i+1].name = NULL;
415 hostlist2[i+1].next = NULL;
416 for(j = i; j >= 0; j--)
417 hostlist2[j].next = &hostlist2[j+1];
421 * We check if we have a refresh token saved somewhere, if so
422 * we use it to get a new access token, otherwise we need to
423 * get an access code so we can get (and save) a refresh token
424 * and use the access token.
426 if(trial == 0L && !altuserforcache){
427 int code;
429 if(*mb->user != '\0')
430 strncpy(user, mb->user, NETMAXUSER);
431 else{
432 flags = OE_APPEND_CURRENT;
433 sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
434 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
435 prompt, NULL, NO_HELP, &flags);
437 user[NETMAXUSER-1] = '\0';
439 /* Search for a refresh token that is already loaded ... */
440 if(imap_get_passwd_auth(mm_login_list, &token, user,
441 registered ? hostlist2 : hostlist,
442 (mb->sslflag||mb->tlsflag), OA2NAME)){
443 dprint((9, "mm_login_oauth2: found a refresh token\n"));
444 ps_global->no_newmail_check_from_optionally_enter = 0;
445 ps_global->in_init_seq = save_in_init;
447 #ifdef LOCAL_PASSWD_CACHE
448 /* or see if we have saved one in the local password cache and load it */
449 else if(get_passfile_passwd_auth(ps_global->pinerc, &token,
450 user, registered ? hostlist2 : hostlist,
451 (mb->sslflag||mb->tlsflag), OA2NAME)){
452 imap_set_passwd_auth(&mm_login_list, token, user,
453 hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
454 update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist,
455 (mb->sslflag||mb->tlsflag), OA2NAME);
456 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
457 ps_global->no_newmail_check_from_optionally_enter = 0;
458 ps_global->in_init_seq = save_in_init;
460 if(token && *token) preserve_password = 1; /* resave it, no need to ask */
461 #endif /* LOCAL_PASSWD_CACHE */
463 user[NETMAXUSER-1] = '\0';
465 /* The Old* variables is what c_client knows */
466 OldRefreshToken = login->param[OA2_RefreshToken].value;
467 OldAccessToken = login->access_token;
468 OldExpirationTime = login->expiration;
470 /* The New* variables is what Alpine knows */
471 NewRefreshToken = NewAccessToken = NULL;
472 NewExpirationTime = 0L;
473 ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0;
475 if(token && *token){
476 char *s, *t;
478 s = token;
479 t = strchr(s, PWDAUTHSEP);
480 if(t == NULL)
481 NewRefreshToken = cpystr(s);
482 else {
483 *t++ = '\0';
484 NewRefreshToken = cpystr(s);
485 s = t;
486 t = strchr(s, PWDAUTHSEP);
487 if(t == NULL)
488 NewAccessToken = cpystr(s);
489 else {
490 *t++ = '\0';
491 NewAccessToken = cpystr(s);
492 s = t;
493 NewExpirationTime = strtol(s, &s, 10);
494 if(NewExpirationTime <= 0 || NewExpirationTime <= time(0))
495 NewExpirationTime = 0L;
498 /* check we got good information, and send good information below */
499 if(NewRefreshToken && !*NewRefreshToken)
500 fs_give((void **) &NewRefreshToken);
501 if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken))
502 fs_give((void **) &NewAccessToken);
505 /* Default to saving what we already had saved */
507 SaveRefreshToken = NewRefreshToken;
508 SaveAccessToken = NewAccessToken;
509 SaveExpirationTime = NewExpirationTime;
511 /* Translation of the logic below:
512 * if (c-client has a refresh token
513 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
514 forget the Alpine refresh token;
515 make the Alpine Refresh token = c-client refresh token.;
516 signal that we changed the refresh token;
517 In this situation we do not need to clear up the Alpine access token. This will
518 expire, so we can use it until it expires. We can save the c-client refresh token
519 together with the Alpine Access Token and the expiration date of the Alpine Access
520 Token.
521 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
522 forget the Alpine refresh token;
523 if Alpine has an access token, forget it;
524 reset the expiration time
525 signal that we changed the refresh token; (because the service expired it)
529 if(OldRefreshToken != NULL
530 && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){
531 if(NewRefreshToken) fs_give((void **) &NewRefreshToken);
532 NewRefreshToken = cpystr(OldRefreshToken);
533 ChangeRefreshToken++;
534 SaveRefreshToken = OldRefreshToken;
535 SaveAccessToken = NewAccessToken;
536 SaveExpirationTime = NewExpirationTime;
537 } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){
538 fs_give((void **) &NewRefreshToken);
539 if(NewAccessToken) fs_give((void **) &NewAccessToken);
540 NewExpirationTime = 0L;
541 ChangeRefreshToken++;
542 SaveRefreshToken = NULL;
543 SaveAccessToken = NULL;
544 SaveExpirationTime = 0L;
547 if(OldAccessToken != NULL
548 && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){
549 if(NewAccessToken) fs_give((void **) &NewAccessToken);
550 NewAccessToken = cpystr(OldAccessToken);
551 ChangeAccessToken++;
552 NewExpirationTime = OldExpirationTime;
553 SaveRefreshToken = NewRefreshToken;
554 SaveAccessToken = NewAccessToken;
555 SaveExpirationTime = NewExpirationTime;
558 if(!registered){
559 login->param[OA2_RefreshToken].value = SaveRefreshToken;
560 login->access_token = SaveAccessToken;
561 login->expiration = SaveExpirationTime;
562 } else {
563 oa2list->param[OA2_RefreshToken].value = SaveRefreshToken;
564 oa2list->access_token = SaveAccessToken;
565 oa2list->expiration = SaveExpirationTime;
566 *login = *oa2list; /* load login pointer */
569 if(!ChangeAccessToken && !ChangeRefreshToken)
570 return;
572 /* get ready to save this information. The format will be
573 * RefreshToken \001 LastAccessToken \001 ExpirationTime
574 * (spaces added for clarity, \001 is PWDAUTHSEP)
576 if(token) fs_give((void **) &token);
577 sprintf(tmp, "%lu", SaveExpirationTime);
578 tmp[sizeof(tmp) - 1] = '\0';
579 len = strlen(SaveRefreshToken ? SaveRefreshToken : "")
580 + strlen(SaveAccessToken ? SaveAccessToken : "")
581 + strlen(tmp) + 2;
582 token = fs_get(len + 1);
583 sprintf(token, "%s%c%s%c%lu",
584 SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP,
585 SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP,
586 SaveExpirationTime);
588 /* remember the access information for next time */
589 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
590 imap_set_passwd_auth(&mm_login_list, token,
591 altuserforcache ? altuserforcache : user, hostlist,
592 (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
593 #ifdef LOCAL_PASSWD_CACHE
594 /* if requested, remember it on disk for next session */
595 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
596 set_passfile_passwd_auth(ps_global->pinerc, token,
597 altuserforcache ? altuserforcache : user, hostlist,
598 (mb->sslflag||mb->tlsflag),
599 (preserve_password == -1 ? 0
600 : (preserve_password == 0 ? 2 :1)), OA2NAME);
601 #endif /* LOCAL_PASSWD_CACHE */
603 ps_global->no_newmail_check_from_optionally_enter = 0;
606 /*----------------------------------------------------------------------
607 recieve notification from IMAP
609 Args: stream -- Mail stream message is relavant to
610 string -- The message text
611 errflg -- Set if it is a serious error
613 Result: message displayed in status line
615 The facility is for general notices, such as connection to server;
616 server shutting down etc... It is used infrequently.
617 ----------------------------------------------------------------------*/
618 void
619 mm_notify(MAILSTREAM *stream, char *string, long int errflg)
621 time_t now;
622 struct tm *tm_now;
624 now = time((time_t *)0);
625 tm_now = localtime(&now);
627 /* be sure to log the message... */
628 #ifdef DEBUG
629 if(ps_global->debug_imap || ps_global->debugmem)
630 dprint((errflg == TCPDEBUG ? 7 : 2,
631 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
632 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
633 tm_now->tm_mon+1, tm_now->tm_mday,
634 (!errflg) ? "babble" :
635 (errflg == ERROR) ? "error" :
636 (errflg == WARN) ? "warning" :
637 (errflg == PARSE) ? "parse" :
638 (errflg == TCPDEBUG) ? "tcp" :
639 (errflg == BYE) ? "bye" : "unknown",
640 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
641 string ? string : "?"));
642 #endif
644 snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s",
645 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
646 (int) MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70),
647 string);
648 ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols
649 : sizeof(ps_global->last_error)-1] = '\0';
652 * Then either set special bits in the pine struct or
653 * display the message if it's tagged as an "ALERT" or
654 * its errflg > NIL (i.e., WARN, or ERROR)
656 if(errflg == BYE)
658 * We'd like to sp_mark_stream_dead() here but we can't do that because
659 * that might call mail_close and we are already in a c-client callback.
660 * So just set the dead bit and clean it up later.
662 sp_set_dead_stream(stream, 1);
663 else if(!strncmp(string, "[TRYCREATE]", 11))
664 ps_global->try_to_create = 1;
665 else if(!strncmp(string, "[REFERRAL ", 10))
666 ; /* handled in the imap_referral() callback */
667 else if(!strncmp(string, "[ALERT]", 7))
668 q_status_message2(SM_MODAL, 3, 3,
669 _("Alert received while accessing \"%s\": %s"),
670 (stream && stream->mailbox)
671 ? stream->mailbox : "-no folder-",
672 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000),
673 SIZEOF_20KBUF-10000, string));
674 else if(!strncmp(string, "[UNSEEN ", 8)){
675 char *p;
676 long n = 0;
678 for(p = string + 8; isdigit(*p); p++)
679 n = (n * 10) + (*p - '0');
681 sp_set_first_unseen(stream, n);
683 else if(!strncmp(string, "[READ-ONLY]", 11)
684 && !(stream && stream->mailbox && IS_NEWS(stream)))
685 q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s",
686 (stream && stream->mailbox)
687 ? stream->mailbox : "-no folder-",
688 string + 11);
689 else if((errflg && errflg != BYE && errflg != PARSE)
690 && !ps_global->noshow_error
691 && !(errflg == WARN
692 && (ps_global->noshow_warn || (stream && stream->unhealthy))))
693 q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0),
694 3, 6, ps_global->last_error);
698 /*----------------------------------------------------------------------
699 Queue imap log message for display in the message line
701 Args: string -- The message
702 errflg -- flag set to 1 if pertains to an error
704 Result: Message queued for display
706 The c-client/imap reports most of it's status and errors here
707 ---*/
708 void
709 mm_log(char *string, long int errflg)
711 char message[sizeof(ps_global->c_client_error)];
712 char *occurence;
713 int was_capitalized;
714 static char saw_kerberos_init_warning;
715 time_t now;
716 struct tm *tm_now;
718 now = time((time_t *)0);
719 tm_now = localtime(&now);
721 dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? 1 :
722 (errflg == TCPDEBUG) ? 10 : 2,
723 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
724 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
725 tm_now->tm_mon+1, tm_now->tm_mday,
726 (!errflg) ? "babble" :
727 (errflg == ERROR) ? "error" :
728 (errflg == WARN) ? "warning" :
729 (errflg == PARSE) ? "parse" :
730 (errflg == TCPDEBUG) ? "tcp" :
731 (errflg == BYE) ? "bye" : "unknown",
732 string ? string : "?"));
734 if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){
735 ps_global->try_to_create = 1;
736 return;
738 else if(ps_global->try_to_create
739 || !strncmp(string, "[CLOSED]", 8)
740 || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op")))
742 * Don't display if creating new folder OR
743 * warning about a dead stream ...
745 return;
747 strncpy(message, string, sizeof(message));
748 message[sizeof(message) - 1] = '\0';
750 if(errflg == WARN && srchstr(message, "try running kinit") != NULL){
751 if(saw_kerberos_init_warning)
752 return;
754 saw_kerberos_init_warning = 1;
757 /*---- replace all "mailbox" with "folder" ------*/
758 occurence = srchstr(message, "mailbox");
759 while(occurence) {
760 if(!*(occurence+7)
761 || isspace((unsigned char) *(occurence+7))
762 || *(occurence+7) == ':'){
763 was_capitalized = isupper((unsigned char) *occurence);
764 rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder"));
765 if(was_capitalized)
766 *occurence = (errflg == PARSE ? 'A' : 'F');
768 else
769 occurence += 7;
771 occurence = srchstr(occurence, "mailbox");
774 /*---- replace all "GSSAPI" with "Kerberos" ------*/
775 occurence = srchstr(message, "GSSAPI");
776 while(occurence) {
777 if(!*(occurence+6)
778 || isspace((unsigned char) *(occurence+6))
779 || *(occurence+6) == ':')
780 rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos");
781 else
782 occurence += 6;
784 occurence = srchstr(occurence, "GSSAPI");
787 if(errflg == ERROR)
788 ps_global->mm_log_error = 1;
790 if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error))
791 strncpy(ps_global->c_client_error, message,
792 sizeof(ps_global->c_client_error));
794 if(ps_global->noshow_error
795 || (ps_global->noshow_warn && errflg == WARN)
796 || !(errflg == ERROR || errflg == WARN))
797 return; /* Only care about errors; don't print when asked not to */
799 /*---- Display the message ------*/
800 q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER,
801 3, 5, message);
802 strncpy(ps_global->last_error, message, sizeof(ps_global->last_error));
803 ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
806 void
807 mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial,
808 char *method, char *usethisprompt, char *altuserforcache)
810 if(method == NULL)
811 return;
812 if(strucmp(method, OA2NAME) == 0)
813 mm_login_oauth2(mb, user, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache);
816 void
817 mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial,
818 char *usethisprompt, char *altuserforcache)
820 char tmp[MAILTMPLEN];
821 char prompt[1000], *last;
822 char port[20], non_def_port[20], insecure[20];
823 char defuser[NETMAXUSER];
824 char hostleadin[80], hostname[200], defubuf[200];
825 char logleadin[80], pwleadin[50];
826 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
827 /* TRANSLATORS: when logging in, this text is added to the prompt to show
828 that the password will be sent unencrypted over the network. This is
829 just a warning message that gets added parenthetically when the user
830 is asked for a password. */
831 char *insec = _(" (INSECURE)");
832 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
833 after having already failed at least once. */
834 char *retry = _("Retrying - ");
835 /* TRANSLATORS: A label for the hostname that the user is logging in on */
836 char *hostlabel = _("HOST");
837 /* TRANSLATORS: user is logging in as a particular user (a particular
838 login name), this is just labelling that user name. */
839 char *userlabel = _("USER");
840 STRLIST_S hostlist[2];
841 HelpType help ;
842 int len, rc, q_line, flags;
843 int oespace, avail, need, save_dont_use;
844 int save_in_init;
845 struct servent *sv;
846 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
847 int preserve_password = -1;
848 #endif
850 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
851 trial, mb->user ? mb->user : "(null)",
852 mb->service ? mb->service : "(null)",
853 mb->port ? " port=" : "",
854 mb->port ? comatose(mb->port) : "",
855 altuserforcache ? " altuserforcache =" : "",
856 altuserforcache ? altuserforcache : ""));
857 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
859 save_in_init = ps_global->in_init_seq;
860 ps_global->in_init_seq = 0;
861 ps_global->no_newmail_check_from_optionally_enter = 1;
863 /* make sure errors are seen */
864 if(ps_global->ttyo)
865 flush_status_messages(0);
868 * Add port number to hostname if going through a tunnel or something
870 non_def_port[0] = '\0';
871 if(mb->port && mb->service &&
872 (sv = getservbyname(mb->service, "tcp")) &&
873 (mb->port != ntohs(sv->s_port))){
874 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
875 non_def_port[sizeof(non_def_port)-1] = '\0';
876 dprint((9, "mm_login: using non-default port=%s\n",
877 non_def_port ? non_def_port : "?"));
881 * set up host list for sybil servers...
883 if(*non_def_port){
884 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
885 hostlist0[sizeof(hostlist0)-1] = '\0';
886 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
887 hostlist0[sizeof(hostlist0)-1] = '\0';
888 hostlist[0].name = hostlist0;
889 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
890 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
891 hostlist1[sizeof(hostlist1)-1] = '\0';
892 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
893 hostlist1[sizeof(hostlist1)-1] = '\0';
894 hostlist[0].next = &hostlist[1];
895 hostlist[1].name = hostlist1;
896 hostlist[1].next = NULL;
898 else
899 hostlist[0].next = NULL;
901 else{
902 hostlist[0].name = mb->host;
903 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
904 hostlist[0].next = &hostlist[1];
905 hostlist[1].name = mb->orighost;
906 hostlist[1].next = NULL;
908 else
909 hostlist[0].next = NULL;
912 if(hostlist[0].name){
913 dprint((9, "mm_login: host=%s\n",
914 hostlist[0].name ? hostlist[0].name : "?"));
915 if(hostlist[0].next && hostlist[1].name){
916 dprint((9, "mm_login: orighost=%s\n", hostlist[1].name));
921 * Initialize user name with either
922 * 1) /user= value in the stream being logged into,
923 * or 2) the user name we're running under.
925 * Note that VAR_USER_ID is not yet initialized if this login is
926 * the one to access the remote config file. In that case, the user
927 * can supply the username in the config file name with /user=.
929 if(trial == 0L && !altuserforcache){
930 strncpy(user, (*mb->user) ? mb->user :
931 ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "",
932 NETMAXUSER);
933 user[NETMAXUSER-1] = '\0';
935 /* try last working password associated with this host. */
936 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
937 (mb->sslflag||mb->tlsflag))){
938 dprint((9, "mm_login: found a password to try\n"));
939 ps_global->no_newmail_check_from_optionally_enter = 0;
940 ps_global->in_init_seq = save_in_init;
941 return;
944 #ifdef LOCAL_PASSWD_CACHE
945 /* check to see if there's a password left over from last session */
946 if(get_passfile_passwd(ps_global->pinerc, pwd,
947 user, hostlist, (mb->sslflag||mb->tlsflag))){
948 imap_set_passwd(&mm_login_list, *pwd, user,
949 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
950 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
951 (mb->sslflag||mb->tlsflag));
952 dprint((9, "mm_login: found a password in passfile to try\n"));
953 ps_global->no_newmail_check_from_optionally_enter = 0;
954 ps_global->in_init_seq = save_in_init;
955 return;
957 #endif /* LOCAL_PASSWD_CACHE */
960 * If no explicit user name supplied and we've not logged in
961 * with our local user name, see if we've visited this
962 * host before as someone else.
964 if(!*mb->user &&
965 ((last = imap_get_user(mm_login_list, hostlist))
966 #ifdef LOCAL_PASSWD_CACHE
968 (last = get_passfile_user(ps_global->pinerc, hostlist))
969 #endif /* LOCAL_PASSWD_CACHE */
971 strncpy(user, last, NETMAXUSER);
972 user[NETMAXUSER-1] = '\0';
973 dprint((9, "mm_login: found user=%s\n",
974 user ? user : "?"));
976 /* try last working password associated with this host/user. */
977 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
978 (mb->sslflag||mb->tlsflag))){
979 dprint((9,
980 "mm_login: found a password for user=%s to try\n",
981 user ? user : "?"));
982 ps_global->no_newmail_check_from_optionally_enter = 0;
983 ps_global->in_init_seq = save_in_init;
984 return;
987 #ifdef LOCAL_PASSWD_CACHE
988 /* check to see if there's a password left over from last session */
989 if(get_passfile_passwd(ps_global->pinerc, pwd,
990 user, hostlist, (mb->sslflag||mb->tlsflag))){
991 imap_set_passwd(&mm_login_list, *pwd, user,
992 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
993 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
994 (mb->sslflag||mb->tlsflag));
995 dprint((9,
996 "mm_login: found a password for user=%s in passfile to try\n",
997 user ? user : "?"));
998 ps_global->no_newmail_check_from_optionally_enter = 0;
999 ps_global->in_init_seq = save_in_init;
1000 return;
1002 #endif /* LOCAL_PASSWD_CACHE */
1005 #if !defined(DOS) && !defined(OS2)
1006 if(!*mb->user && !*user &&
1007 (last = (ps_global->ui.login && ps_global->ui.login[0])
1008 ? ps_global->ui.login : NULL)
1010 strncpy(user, last, NETMAXUSER);
1011 user[NETMAXUSER-1] = '\0';
1012 dprint((9, "mm_login: found user=%s\n",
1013 user ? user : "?"));
1015 /* try last working password associated with this host. */
1016 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1017 (mb->sslflag||mb->tlsflag))){
1018 dprint((9, "mm_login:ui: found a password to try\n"));
1019 ps_global->no_newmail_check_from_optionally_enter = 0;
1020 ps_global->in_init_seq = save_in_init;
1021 return;
1024 #ifdef LOCAL_PASSWD_CACHE
1025 /* check to see if there's a password left over from last session */
1026 if(get_passfile_passwd(ps_global->pinerc, pwd,
1027 user, hostlist, (mb->sslflag||mb->tlsflag))){
1028 imap_set_passwd(&mm_login_list, *pwd, user,
1029 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1030 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1031 (mb->sslflag||mb->tlsflag));
1032 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1033 ps_global->no_newmail_check_from_optionally_enter = 0;
1034 ps_global->in_init_seq = save_in_init;
1035 return;
1037 #endif /* LOCAL_PASSWD_CACHE */
1039 #endif
1042 user[NETMAXUSER-1] = '\0';
1044 if(trial == 0)
1045 retry = "";
1048 * Even if we have a user now, user gets a chance to change it.
1050 ps_global->mangled_footer = 1;
1051 if(!*mb->user && !altuserforcache){
1053 help = NO_HELP;
1056 * Instead of offering user with a value that the user can edit,
1057 * we offer [user] as a default so that the user can type CR to
1058 * use it. Otherwise, the user has to type in whole name.
1060 strncpy(defuser, user, sizeof(defuser)-1);
1061 defuser[sizeof(defuser)-1] = '\0';
1062 user[0] = '\0';
1065 * Need space for "Retrying - "
1066 * "+ HOST: "
1067 * hostname
1068 * " (INSECURE)"
1069 * ENTER LOGIN NAME
1070 * " [defuser] : "
1071 * about 15 chars for input
1074 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1075 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1076 hostleadin[sizeof(hostleadin)-1] = '\0';
1078 strncpy(hostname, mb->host, sizeof(hostname)-1);
1079 hostname[sizeof(hostname)-1] = '\0';
1082 * Add port number to hostname if going through a tunnel or something
1084 if(*non_def_port)
1085 strncpy(port, non_def_port, sizeof(port));
1086 else
1087 port[0] = '\0';
1089 insecure[0] = '\0';
1090 /* if not encrypted and SSL/TLS is supported */
1091 if(!(mb->sslflag||mb->tlsflag) &&
1092 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1093 strncpy(insecure, insec, sizeof(insecure));
1095 /* TRANSLATORS: user is being asked to type in their login name */
1096 snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME"));
1098 snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "",
1099 (*defuser) ? defuser : "",
1100 (*defuser) ? "]" : "");
1101 defubuf[sizeof(defubuf)-1] = '\0';
1102 /* space reserved after prompt */
1103 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1105 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1106 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1107 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace;
1109 /* If we're retrying cut the hostname back to the first word. */
1110 if(avail < need && trial > 0){
1111 char *p;
1113 len = strlen(hostname);
1114 if((p = strchr(hostname, '.')) != NULL){
1115 *p = '\0';
1116 need -= (len - strlen(hostname));
1120 if(avail < need){
1121 need -= utf8_width(retry);
1122 retry = "";
1124 if(avail < need){
1126 /* reduce length of logleadin */
1127 len = utf8_width(logleadin);
1128 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1129 longer version doesn't fit on screen */
1130 snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN"));
1131 need -= (len - utf8_width(logleadin));
1133 if(avail < need){
1134 /* get two spaces from hostleadin */
1135 len = utf8_width(hostleadin);
1136 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1137 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1138 hostleadin[sizeof(hostleadin)-1] = '\0';
1139 need -= (len - utf8_width(hostleadin));
1141 /* get rid of port */
1142 if(avail < need && strlen(port) > 0){
1143 need -= strlen(port);
1144 port[0] = '\0';
1147 if(avail < need){
1148 int reduce_to;
1151 * Reduce space for hostname. Best we can do is 6 chars
1152 * with hos...
1154 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1155 len = strlen(hostname);
1156 strncpy(hostname+reduce_to-3, "...", 4);
1157 need -= (len - strlen(hostname));
1159 if(avail < need && strlen(insecure) > 0){
1160 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1161 need -= 3;
1162 insecure[strlen(insecure)-4] = ')';
1163 insecure[strlen(insecure)-3] = '\0';
1165 else{
1166 need -= utf8_width(insecure);
1167 insecure[0] = '\0';
1171 if(avail < need){
1172 if(strlen(defubuf) > 3){
1173 len = strlen(defubuf);
1174 strncpy(defubuf, " [..] :", 9);
1175 need -= (len - strlen(defubuf));
1178 if(avail < need)
1179 strncpy(defubuf, ":", 2);
1182 * If it still doesn't fit, optionally_enter gets
1183 * to worry about it.
1191 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s",
1192 retry, hostleadin, hostname, port, insecure, logleadin, defubuf);
1193 prompt[sizeof(prompt)-1] = '\0';
1195 while(1) {
1196 if(ps_global->ttyo)
1197 mm_login_alt_cue(mb);
1199 flags = OE_APPEND_CURRENT;
1200 save_dont_use = ps_global->dont_use_init_cmds;
1201 ps_global->dont_use_init_cmds = 1;
1202 #ifdef _WINDOWS
1203 if(!*user && *defuser){
1204 strncpy(user, defuser, NETMAXUSER);
1205 user[NETMAXUSER-1] = '\0';
1208 rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD,
1209 #ifdef LOCAL_PASSWD_CACHE
1210 is_using_passfile() ? 1 :
1211 #endif /* LOCAL_PASSWD_CACHE */
1212 0, 0, &preserve_password);
1213 ps_global->dont_use_init_cmds = save_dont_use;
1214 if(rc == 0 && *user && *pwd)
1215 goto nopwpmt;
1216 #else /* !_WINDOWS */
1217 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
1218 prompt, NULL, help, &flags);
1219 #endif /* !_WINDOWS */
1220 ps_global->dont_use_init_cmds = save_dont_use;
1222 if(rc == 3) {
1223 help = help == NO_HELP ? h_oe_login : NO_HELP;
1224 continue;
1227 /* default */
1228 if(rc == 0 && !*user){
1229 strncpy(user, defuser, NETMAXUSER);
1230 user[NETMAXUSER-1] = '\0';
1233 if(rc != 4)
1234 break;
1237 if(rc == 1 || !user[0]) {
1238 ps_global->user_says_cancel = (rc == 1);
1239 user[0] = '\0';
1240 pwd[0] = '\0';
1243 else{
1244 strncpy(user, mb->user, NETMAXUSER);
1245 user[NETMAXUSER-1] = '\0';
1248 user[NETMAXUSER-1] = '\0';
1249 pwd[NETMAXPASSWD-1] = '\0';
1251 if(!(user[0] || altuserforcache)){
1252 ps_global->no_newmail_check_from_optionally_enter = 0;
1253 ps_global->in_init_seq = save_in_init;
1254 return;
1258 * Now that we have a user, we can check in the cache again to see
1259 * if there is a password there. Try last working password associated
1260 * with this host and user.
1262 if(trial == 0L && !*mb->user && !altuserforcache){
1263 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1264 (mb->sslflag||mb->tlsflag))){
1265 ps_global->no_newmail_check_from_optionally_enter = 0;
1266 ps_global->in_init_seq = save_in_init;
1267 return;
1270 #ifdef LOCAL_PASSWD_CACHE
1271 if(get_passfile_passwd(ps_global->pinerc, pwd,
1272 user, hostlist, (mb->sslflag||mb->tlsflag))){
1273 imap_set_passwd(&mm_login_list, *pwd, user,
1274 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1275 ps_global->no_newmail_check_from_optionally_enter = 0;
1276 ps_global->in_init_seq = save_in_init;
1277 return;
1279 #endif /* LOCAL_PASSWD_CACHE */
1281 else if(trial == 0 && altuserforcache){
1282 if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist,
1283 (mb->sslflag||mb->tlsflag))){
1284 ps_global->no_newmail_check_from_optionally_enter = 0;
1285 ps_global->in_init_seq = save_in_init;
1286 return;
1289 #ifdef LOCAL_PASSWD_CACHE
1290 if(get_passfile_passwd(ps_global->pinerc, pwd,
1291 altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
1292 imap_set_passwd(&mm_login_list, *pwd, altuserforcache,
1293 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1294 ps_global->no_newmail_check_from_optionally_enter = 0;
1295 ps_global->in_init_seq = save_in_init;
1296 return;
1298 #endif /* LOCAL_PASSWD_CACHE */
1302 * Didn't find password in cache or this isn't the first try. Ask user.
1304 help = NO_HELP;
1307 * Need space for "Retrying - "
1308 * "+ HOST: "
1309 * hostname
1310 * " (INSECURE) "
1311 * " USER: "
1312 * user
1313 * " ENTER PASSWORD: "
1314 * about 15 chars for input
1317 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1318 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1320 strncpy(hostname, mb->host, sizeof(hostname)-1);
1321 hostname[sizeof(hostname)-1] = '\0';
1324 * Add port number to hostname if going through a tunnel or something
1326 if(*non_def_port)
1327 strncpy(port, non_def_port, sizeof(port));
1328 else
1329 port[0] = '\0';
1331 insecure[0] = '\0';
1333 /* if not encrypted and SSL/TLS is supported */
1334 if(!(mb->sslflag||mb->tlsflag) &&
1335 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1336 strncpy(insecure, insec, sizeof(insecure));
1338 if(usethisprompt){
1339 strncpy(logleadin, usethisprompt, sizeof(logleadin));
1340 logleadin[sizeof(logleadin)-1] = '\0';
1341 defubuf[0] = '\0';
1342 user[0] = '\0';
1344 else{
1345 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1347 strncpy(defubuf, user, sizeof(defubuf)-1);
1348 defubuf[sizeof(defubuf)-1] = '\0';
1351 /* TRANSLATORS: user is being asked to type in their password */
1352 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD"));
1354 /* space reserved after prompt */
1355 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1357 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1358 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1359 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) +
1360 utf8_width(pwleadin) + oespace;
1362 if(avail < need && trial > 0){
1363 char *p;
1365 len = strlen(hostname);
1366 if((p = strchr(hostname, '.')) != NULL){
1367 *p = '\0';
1368 need -= (len - strlen(hostname));
1372 if(avail < need){
1373 need -= utf8_width(retry);
1374 retry = "";
1376 if(avail < need){
1378 if(!usethisprompt){
1379 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1380 need--;
1383 rplstr(pwleadin, sizeof(pwleadin), 1, "");
1384 need--;
1386 if(avail < need){
1387 /* get two spaces from hostleadin */
1388 len = utf8_width(hostleadin);
1389 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1390 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1391 hostleadin[sizeof(hostleadin)-1] = '\0';
1392 need -= (len - utf8_width(hostleadin));
1394 /* get rid of port */
1395 if(avail < need && strlen(port) > 0){
1396 need -= strlen(port);
1397 port[0] = '\0';
1400 if(avail < need){
1401 len = utf8_width(pwleadin);
1402 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
1403 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD"));
1404 need -= (len - utf8_width(pwleadin));
1408 if(avail < need){
1409 int reduce_to;
1412 * Reduce space for hostname. Best we can do is 6 chars
1413 * with hos...
1415 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1416 len = strlen(hostname);
1417 strncpy(hostname+reduce_to-3, "...", 4);
1418 need -= (len - strlen(hostname));
1420 if(avail < need && strlen(insecure) > 0){
1421 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1422 need -= 3;
1423 insecure[strlen(insecure)-4] = ')';
1424 insecure[strlen(insecure)-3] = '\0';
1426 else{
1427 need -= utf8_width(insecure);
1428 insecure[0] = '\0';
1432 if(avail < need){
1433 len = utf8_width(logleadin);
1434 strncpy(logleadin, " ", sizeof(logleadin));
1435 logleadin[sizeof(logleadin)-1] = '\0';
1436 need -= (len - utf8_width(logleadin));
1438 if(avail < need){
1439 reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0;
1440 if(reduce_to)
1441 strncpy(defubuf+reduce_to-3, "...", 4);
1442 else
1443 defubuf[0] = '\0';
1450 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s",
1451 retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
1452 prompt[sizeof(prompt)-1] = '\0';
1454 tmp[0] = '\0';
1455 while(1) {
1456 if(ps_global->ttyo)
1457 mm_login_alt_cue(mb);
1459 save_dont_use = ps_global->dont_use_init_cmds;
1460 ps_global->dont_use_init_cmds = 1;
1461 flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
1462 #ifdef _WINDOWS
1463 rc = os_login_dialog(mb, user, NETMAXUSER, tmp, NETMAXPASSWD, 0, 1,
1464 &preserve_password);
1465 #else /* !_WINDOWS */
1466 rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD,
1467 prompt, NULL, help, &flags);
1468 #endif /* !_WINDOWS */
1469 *pwd = cpystr(tmp);
1470 ps_global->dont_use_init_cmds = save_dont_use;
1472 if(rc == 3) {
1473 help = help == NO_HELP ? h_oe_passwd : NO_HELP;
1475 else if(rc == 4){
1477 else
1478 break;
1481 if(rc == 1 || !tmp[0]) {
1482 ps_global->user_says_cancel = (rc == 1);
1483 user[0] = '\0';
1484 ps_global->no_newmail_check_from_optionally_enter = 0;
1485 ps_global->in_init_seq = save_in_init;
1486 return;
1489 #ifdef _WINDOWS
1490 nopwpmt:
1491 #endif
1492 /* remember the password for next time */
1493 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
1494 imap_set_passwd(&mm_login_list, *pwd,
1495 altuserforcache ? altuserforcache : user, hostlist,
1496 (mb->sslflag||mb->tlsflag), 0, 0);
1497 #ifdef LOCAL_PASSWD_CACHE
1498 /* if requested, remember it on disk for next session */
1499 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
1500 set_passfile_passwd(ps_global->pinerc, *pwd,
1501 altuserforcache ? altuserforcache : user, hostlist,
1502 (mb->sslflag||mb->tlsflag),
1503 (preserve_password == -1 ? 0
1504 : (preserve_password == 0 ? 2 :1)));
1505 #endif /* LOCAL_PASSWD_CACHE */
1507 ps_global->no_newmail_check_from_optionally_enter = 0;
1511 void
1512 mm_login_alt_cue(NETMBX *mb)
1514 if(ps_global->ttyo){
1515 COLOR_PAIR *lastc;
1517 lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
1518 ps_global->VAR_TITLE_BACK_COLOR,
1519 PSC_REV | PSC_RET);
1521 mark_titlebar_dirty();
1522 PutLine0(0, ps_global->ttyo->screen_cols - 1,
1523 (mb->sslflag||mb->tlsflag) ? "+" : " ");
1525 if(lastc){
1526 (void)pico_set_colorp(lastc, PSC_NONE);
1527 free_color_pair(&lastc);
1530 fflush(stdout);
1535 /*----------------------------------------------------------------------
1536 Receive notification of an error writing to disk
1538 Args: stream -- The stream the error occured on
1539 errcode -- The system error code (errno)
1540 serious -- Flag indicating error is serious (mail may be lost)
1542 Result: If error is non serious, the stream is marked as having an error
1543 and deletes are disallowed until error clears
1544 If error is serious this goes modal, allowing the user to retry
1545 or get a shell escape to fix the condition. When the condition is
1546 serious it means that mail existing in the mailbox will be lost
1547 if Pine exits without writing, so we try to induce the user to
1548 fix the error, go get someone that can fix the error, or whatever
1549 and don't provide an easy way out.
1550 ----*/
1551 long
1552 mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious)
1554 int i, j;
1555 char *p, *q, *s;
1556 static ESCKEY_S de_opts[] = {
1557 {'r', 'r', "R", "Retry"},
1558 {'f', 'f', "F", "FileBrowser"},
1559 {'s', 's', "S", "ShellPrompt"},
1560 {-1, 0, NULL, NULL}
1562 #define DE_COLS (ps_global->ttyo->screen_cols)
1563 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
1565 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
1566 #define DE_PMT \
1567 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
1568 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
1569 #define DE_STR2 \
1570 "The reported error number is %s. The last reported mail error was:"
1571 static char *de_msg[] = {
1572 "Please try to correct the error preventing Alpine from saving your",
1573 "mail folder. For example if the disk is out of space try removing",
1574 "unneeded files. You might also contact your system administrator.",
1576 "Both Alpine's File Browser and an option to enter the system's",
1577 "command prompt are offered to aid in fixing the problem. When",
1578 "you believe the problem is resolved, choose the \"Retry\" option.",
1579 "Be aware that messages may be lost or this folder left in an",
1580 "inaccessible condition if you exit or kill Alpine before the problem",
1581 "is resolved.",
1582 NULL};
1583 static char *de_shell_msg[] = {
1584 "\n\nPlease attempt to correct the error preventing saving of the",
1585 "mail folder. If you do not know how to correct the problem, contact",
1586 "your system administrator. To return to Alpine, type \"exit\".",
1587 NULL};
1589 dprint((0,
1590 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
1591 DE_FOLDER(stream), errcode, serious ? "" : "not "));
1592 dprint((0, "***** message: \"%s\"\n\n",
1593 ps_global->last_error ? ps_global->last_error : "?"));
1595 if(!serious) {
1596 sp_set_io_error_on_stream(stream, 1);
1597 return (1) ;
1600 while(1){
1601 /* replace pine's body display with screen full of explanatory text */
1602 ClearLine(2);
1603 PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1)
1604 - strlen(DE_FOLDER(stream)))/2, 0),
1605 DE_STR1, DE_FOLDER(stream));
1606 ClearLine(3);
1607 PutLine1(3, 4, DE_STR2, long2string(errcode));
1609 PutLine0(4, 0, " \"");
1610 removing_leading_white_space(ps_global->last_error);
1611 for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){
1612 for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++)
1613 if(isspace((unsigned char)*q))
1614 s = q;
1616 if(*q && s)
1617 q = s;
1619 while(p < q)
1620 Writechar(*p++, 0);
1622 if(*(p = q)){
1623 ClearLine(++i);
1624 PutLine0(i, 0, " ");
1625 while(*p && isspace((unsigned char)*p))
1626 p++;
1628 else{
1629 Writechar('\"', 0);
1630 CleartoEOLN();
1631 break;
1635 ClearLine(++i);
1636 for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){
1637 ClearLine(i);
1638 PutLine0(i, 0, " ");
1639 Write_to_screen(de_msg[i-j]);
1642 while(i < DE_LINE)
1643 ClearLine(i++);
1645 switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts,
1646 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){
1647 case 'r' : /* Retry! */
1648 ps_global->mangled_screen = 1;
1649 return(0L);
1651 case 'f' : /* File Browser */
1653 char full_filename[MAXPATH+1], filename[MAXPATH+1];
1655 filename[0] = '\0';
1656 build_path(full_filename, ps_global->home_dir, filename,
1657 sizeof(full_filename));
1658 file_lister("DISK ERROR", full_filename, sizeof(full_filename),
1659 filename, sizeof(filename), FALSE, FB_SAVE);
1662 break;
1664 case 's' :
1665 EndInverse();
1666 end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0);
1667 end_tty_driver(ps_global);
1668 for(i = 0; de_shell_msg[i]; i++)
1669 puts(de_shell_msg[i]);
1672 * Don't use our piping mechanism to spawn a subshell here
1673 * since it will the server (thus reentering c-client).
1674 * Bad thing to do.
1676 #ifdef _WINDOWS
1677 #else
1678 system("csh");
1679 #endif
1680 init_tty_driver(ps_global);
1681 init_keyboard(F_ON(F_USE_FK,ps_global));
1682 break;
1685 if(ps_global->redrawer)
1686 (*ps_global->redrawer)();
1691 long
1692 pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host)
1694 long rv = 1L;
1695 char pmt[128];
1697 #ifdef _WINDOWS
1698 mswin_killsplash();
1699 #endif
1701 if(elapsed >= (long)ps_global->tcp_query_timeout){
1702 snprintf(pmt, sizeof(pmt),
1703 _("No reply in %s seconds from server %s. Break connection"),
1704 long2string(elapsed), host);
1705 pmt[sizeof(pmt)-1] = '\0';
1706 if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
1707 ps_global->user_says_cancel = 1;
1708 return(0L);
1712 ps_global->tcptimeout = 0;
1713 return(rv);
1718 * -------------------------------------------------------------
1719 * These are declared in pith/imap.h as mandatory to implement.
1720 * -------------------------------------------------------------
1725 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
1727 long
1728 pine_tcptimeout(long int elapsed, long int sincelast, char *host)
1730 long rv = 1L; /* keep trying by default */
1731 unsigned long ch;
1733 ps_global->tcptimeout = 1;
1734 #ifdef DEBUG
1735 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
1736 long2string(elapsed), host));
1737 if(debugfile)
1738 fflush(debugfile);
1739 #endif
1741 #ifdef _WINDOWS
1742 mswin_killsplash();
1743 #endif
1745 if(ps_global->noshow_timeout)
1746 return(rv);
1748 if(ps_global->can_interrupt
1749 && ps_global->close_connection_timeout > 0L
1750 && elapsed >= (long)ps_global->tcp_query_timeout
1751 && elapsed >= (long)ps_global->close_connection_timeout){
1752 ps_global->can_interrupt = 0; /* do not return here */
1753 ps_global->read_bail = 0;
1754 ps_global->user_says_cancel = 1;
1755 return 0;
1758 if(!ps_global->ttyo)
1759 return(pine_tcptimeout_noscreen(elapsed, sincelast, host));
1761 suspend_busy_cue();
1764 * Prompt after a minute (since by then things are probably really bad)
1765 * A prompt timeout means "keep trying"...
1767 if(elapsed >= (long)ps_global->tcp_query_timeout){
1768 int clear_inverse;
1770 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
1771 if((clear_inverse = !InverseState()) != 0)
1772 StartInverse();
1774 Writechar(BELL, 0);
1776 PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0,
1777 _("No reply in %s seconds from server %s. Break connection?"),
1778 long2string(elapsed), host);
1779 CleartoEOLN();
1780 fflush(stdout);
1781 flush_input();
1782 ch = read_char(7);
1783 if(ps_global->read_bail || ch == 'y' || ch == 'Y'){
1784 ps_global->read_bail = 0;
1785 ps_global->user_says_cancel = 1;
1786 rv = 0L;
1789 if(clear_inverse)
1790 EndInverse();
1792 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
1795 if(rv == 1L){ /* just warn 'em something's up */
1796 q_status_message2(SM_ORDER, 0, 0,
1797 _("No reply in %s seconds from server %s. Still Waiting..."),
1798 long2string(elapsed), host);
1799 flush_status_messages(0); /* make sure it's seen */
1802 mark_status_dirty(); /* make sure it get's cleared */
1804 resume_busy_cue((rv == 1) ? 3 : 0);
1805 ps_global->tcptimeout = 0;
1807 return(rv);
1810 QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota)
1812 QUOTALIST *cquota = NULL;
1814 if(pquota){
1815 cquota = mail_newquotalist();
1816 if (pquota->name && *pquota->name)
1817 cquota->name = cpystr(pquota->name);
1818 cquota->usage = pquota->usage;
1819 cquota->limit = pquota->limit;
1820 if (pquota->next)
1821 cquota->next = pine_quotalist_copy(pquota->next);
1823 return cquota;
1827 /* c-client callback to handle quota */
1829 void
1830 pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
1832 ps_global->quota = pine_quotalist_copy (pquota);
1836 * C-client callback to handle SSL/TLS certificate validation failures
1838 * Returning 0 means error becomes fatal
1839 * Non-zero means certificate problem is ignored and SSL session is
1840 * established
1842 * We remember the answer and won't re-ask for subsequent open attempts to
1843 * the same hostname.
1845 long
1846 pine_sslcertquery(char *reason, char *host, char *cert)
1848 char tmp[500];
1849 char *unknown = "<unknown>";
1850 long rv = 0L;
1851 STRLIST_S hostlist;
1852 int ok_novalidate = 0, warned = 0;
1854 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
1855 host ? host : "?", reason ? reason : "?",
1856 cert ? cert : "?"));
1858 hostlist.name = host ? host : "";
1859 hostlist.next = NULL;
1862 * See if we've been asked about this host before.
1864 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
1865 /* we were asked before, did we say Yes? */
1866 if(ok_novalidate)
1867 rv++;
1869 if(rv){
1870 dprint((5,
1871 "sslcertificatequery: approved automatically\n"));
1872 return(rv);
1875 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
1878 if(ps_global->ttyo){
1879 SCROLL_S sargs;
1880 STORE_S *in_store, *out_store;
1881 gf_io_t pc, gc;
1882 HANDLE_S *handles = NULL;
1883 int the_answer = 'n';
1885 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
1886 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
1887 goto try_wantto;
1889 so_puts(in_store, "<HTML><P>");
1890 so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server"));
1892 so_puts(in_store, "<P><CENTER>");
1893 so_puts(in_store, host ? host : unknown);
1894 so_puts(in_store, "</CENTER>");
1896 so_puts(in_store, "<P>");
1897 so_puts(in_store, _("The reason for the failure was"));
1899 /* squirrel away details */
1900 if(details_host)
1901 fs_give((void **)&details_host);
1902 if(details_reason)
1903 fs_give((void **)&details_reason);
1904 if(details_cert)
1905 fs_give((void **)&details_cert);
1907 details_host = cpystr(host ? host : unknown);
1908 details_reason = cpystr(reason ? reason : unknown);
1909 details_cert = cpystr(cert ? cert : unknown);
1911 so_puts(in_store, "<P><CENTER>");
1912 snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
1913 reason ? reason : unknown);
1914 tmp[sizeof(tmp)-1] = '\0';
1916 so_puts(in_store, tmp);
1917 so_puts(in_store, "</CENTER>");
1919 so_puts(in_store, "<P>");
1920 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."));
1922 so_puts(in_store, "<P>");
1923 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"));
1925 so_puts(in_store, "<P><CENTER>");
1926 so_puts(in_store, "/novalidate-cert");
1927 so_puts(in_store, "</CENTER>");
1929 so_puts(in_store, "<P>");
1930 so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
1932 so_puts(in_store, "<P><CENTER>");
1933 so_puts(in_store, host ? host : unknown);
1934 so_puts(in_store, "</CENTER>");
1936 so_puts(in_store, "<P>");
1937 so_puts(in_store, _("in your configuration, replace those characters with"));
1939 so_puts(in_store, "<P><CENTER>");
1940 so_puts(in_store, host ? host : unknown);
1941 so_puts(in_store, "/novalidate-cert");
1942 so_puts(in_store, "</CENTER>");
1944 so_puts(in_store, "<P>");
1945 so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
1947 so_seek(in_store, 0L, 0);
1948 init_handles(&handles);
1949 gf_filter_init();
1950 gf_link_filter(gf_html2plain,
1951 gf_html2plain_opt(NULL,
1952 ps_global->ttyo->screen_cols, non_messageview_margin(),
1953 &handles, NULL, GFHP_LOCAL_HANDLES));
1954 gf_set_so_readc(&gc, in_store);
1955 gf_set_so_writec(&pc, out_store);
1956 gf_pipe(gc, pc);
1957 gf_clear_so_writec(out_store);
1958 gf_clear_so_readc(in_store);
1960 memset(&sargs, 0, sizeof(SCROLL_S));
1961 sargs.text.handles = handles;
1962 sargs.text.text = so_text(out_store);
1963 sargs.text.src = CharStar;
1964 sargs.text.desc = _("help text");
1965 sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
1966 sargs.proc.tool = answer_cert_failure;
1967 sargs.proc.data.p = (void *)&the_answer;
1968 sargs.keys.menu = &ans_certquery_keymenu;
1969 /* don't want to re-enter c-client */
1970 sargs.quell_newmail = 1;
1971 setbitmap(sargs.keys.bitmap);
1972 sargs.help.text = h_tls_validation_failure;
1973 sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
1975 scrolltool(&sargs);
1977 if(the_answer == 'y')
1978 rv++;
1980 ps_global->mangled_screen = 1;
1981 ps_global->painted_body_on_startup = 0;
1982 ps_global->painted_footer_on_startup = 0;
1983 so_give(&in_store);
1984 so_give(&out_store);
1985 free_handles(&handles);
1986 if(details_host)
1987 fs_give((void **)&details_host);
1988 if(details_reason)
1989 fs_give((void **)&details_reason);
1990 if(details_cert)
1991 fs_give((void **)&details_cert);
1993 else{
1995 * If screen hasn't been initialized yet, use want_to.
1997 try_wantto:
1998 memset((void *)tmp, 0, sizeof(tmp));
1999 strncpy(tmp,
2000 reason ? reason : _("SSL/TLS certificate validation failure"),
2001 sizeof(tmp));
2002 tmp[sizeof(tmp)-1] = '\0';
2003 strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
2005 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
2006 rv++;
2009 if(rv == 0)
2010 q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
2011 host ? host : unknown);
2013 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
2015 dprint((5, "sslcertificatequery: %s\n",
2016 rv ? "approved" : "rejected"));
2018 return(rv);
2022 char *
2023 pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
2025 char buf[MAILTMPLEN];
2027 if((can_access(mulname, ACCESS_EXISTS) == 0)
2028 || !(can_access(name, ACCESS_EXISTS) == 0))
2029 return(mulname);
2031 snprintf(buf, sizeof(buf),
2032 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2033 last_cmpnt(name),
2034 strlen(last_cmpnt(name)) > 15 ? "..." : "");
2035 buf[sizeof(buf)-1] = '\0';
2036 if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
2037 rename_file(name, mulname);
2038 return(mulname);
2043 url_local_certdetails(char *url)
2045 if(!struncmp(url, "x-alpine-cert:", 14)){
2046 STORE_S *store;
2047 SCROLL_S sargs;
2048 char *folded;
2050 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
2051 q_status_message(SM_ORDER | SM_DING, 7, 10,
2052 _("Error allocating space for details."));
2053 return(0);
2056 so_puts(store, _("Host given by user:\n\n "));
2057 so_puts(store, details_host);
2058 so_puts(store, _("\n\nReason for failure:\n\n "));
2059 so_puts(store, details_reason);
2060 so_puts(store, _("\n\nCertificate being verified:\n\n"));
2061 folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
2062 so_puts(store, folded);
2063 fs_give((void **)&folded);
2064 so_puts(store, "\n");
2066 memset(&sargs, 0, sizeof(SCROLL_S));
2067 sargs.text.text = so_text(store);
2068 sargs.text.src = CharStar;
2069 sargs.text.desc = _("Details");
2070 sargs.bar.title = _("CERT VALIDATION DETAILS");
2071 sargs.help.text = NO_HELP;
2072 sargs.help.title = NULL;
2073 sargs.quell_newmail = 1;
2074 sargs.help.text = h_tls_failure_details;
2075 sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
2077 scrolltool(&sargs);
2079 so_give(&store); /* free resources associated with store */
2080 ps_global->mangled_screen = 1;
2081 return(1);
2084 return(0);
2089 * C-client callback to handle SSL/TLS certificate validation failures
2091 void
2092 pine_sslfailure(char *host, char *reason, long unsigned int flags)
2094 SCROLL_S sargs;
2095 STORE_S *store;
2096 int the_answer = 'n', indent, len, cols;
2097 char buf[500], buf2[500];
2098 char *folded;
2099 char *hst = host ? host : "<unknown>";
2100 char *rsn = reason ? reason : "<unknown>";
2101 char *notls = "/notls";
2102 STRLIST_S hostlist;
2103 int ok_novalidate = 0, warned = 0;
2106 dprint((1, "sslfailure: host=%s reason=%s\n",
2107 hst ? hst : "?",
2108 rsn ? rsn : "?"));
2110 if(flags & NET_SILENT)
2111 return;
2113 hostlist.name = host ? host : "";
2114 hostlist.next = NULL;
2117 * See if we've been told about this host before.
2119 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2120 /* we were told already */
2121 if(warned){
2122 snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn);
2123 buf[sizeof(buf)-1] = '\0';
2124 mm_log(buf, ERROR);
2125 return;
2129 cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2130 cols--;
2132 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS)))
2133 return;
2135 strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf));
2136 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2137 so_puts(store, folded);
2138 fs_give((void **)&folded);
2139 so_puts(store, "\n");
2141 if((len=strlen(hst)) <= cols){
2142 if((indent=((cols-len)/2)) > 0)
2143 so_puts(store, repeat_char(indent, SPACE));
2145 so_puts(store, hst);
2146 so_puts(store, "\n");
2148 else{
2149 strncpy(buf, hst, sizeof(buf));
2150 buf[sizeof(buf)-1] = '\0';
2151 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2152 so_puts(store, folded);
2153 fs_give((void **)&folded);
2156 so_puts(store, "\n");
2158 strncpy(buf, _("The reason for the failure was"), sizeof(buf));
2159 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2160 so_puts(store, folded);
2161 fs_give((void **)&folded);
2162 so_puts(store, "\n");
2164 if((len=strlen(rsn)) <= cols){
2165 if((indent=((cols-len)/2)) > 0)
2166 so_puts(store, repeat_char(indent, SPACE));
2168 so_puts(store, rsn);
2169 so_puts(store, "\n");
2171 else{
2172 strncpy(buf, rsn, sizeof(buf));
2173 buf[sizeof(buf)-1] = '\0';
2174 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2175 so_puts(store, folded);
2176 fs_give((void **)&folded);
2179 so_puts(store, "\n");
2181 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));
2182 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2183 so_puts(store, folded);
2184 fs_give((void **)&folded);
2185 so_puts(store, "\n");
2187 if((len=strlen(notls)) <= cols){
2188 if((indent=((cols-len)/2)) > 0)
2189 so_puts(store, repeat_char(indent, SPACE));
2191 so_puts(store, notls);
2192 so_puts(store, "\n");
2194 else{
2195 strncpy(buf, notls, sizeof(buf));
2196 buf[sizeof(buf)-1] = '\0';
2197 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2198 so_puts(store, folded);
2199 fs_give((void **)&folded);
2202 so_puts(store, "\n");
2204 strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2205 sizeof(buf));
2206 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2207 so_puts(store, folded);
2208 fs_give((void **)&folded);
2209 so_puts(store, "\n");
2211 if((len=strlen(hst)) <= cols){
2212 if((indent=((cols-len)/2)) > 0)
2213 so_puts(store, repeat_char(indent, SPACE));
2215 so_puts(store, hst);
2216 so_puts(store, "\n");
2218 else{
2219 strncpy(buf, hst, sizeof(buf));
2220 buf[sizeof(buf)-1] = '\0';
2221 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2222 so_puts(store, folded);
2223 fs_give((void **)&folded);
2226 so_puts(store, "\n");
2228 strncpy(buf, _("in your configuration, replace those characters with"),
2229 sizeof(buf));
2230 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2231 so_puts(store, folded);
2232 fs_give((void **)&folded);
2233 so_puts(store, "\n");
2235 snprintf(buf2, sizeof(buf2), "%s%s", hst, notls);
2236 buf2[sizeof(buf2)-1] = '\0';
2237 if((len=strlen(buf2)) <= cols){
2238 if((indent=((cols-len)/2)) > 0)
2239 so_puts(store, repeat_char(indent, SPACE));
2241 so_puts(store, buf2);
2242 so_puts(store, "\n");
2244 else{
2245 strncpy(buf, buf2, sizeof(buf));
2246 buf[sizeof(buf)-1] = '\0';
2247 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2248 so_puts(store, folded);
2249 fs_give((void **)&folded);
2252 so_puts(store, "\n");
2254 if(ps_global->ttyo){
2255 strncpy(buf, _("Type RETURN to continue."), sizeof(buf));
2256 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2257 so_puts(store, folded);
2258 fs_give((void **)&folded);
2261 memset(&sargs, 0, sizeof(SCROLL_S));
2262 sargs.text.text = so_text(store);
2263 sargs.text.src = CharStar;
2264 sargs.text.desc = _("help text");
2265 sargs.bar.title = _("SSL/TLS FAILURE");
2266 sargs.proc.tool = answer_cert_failure;
2267 sargs.proc.data.p = (void *)&the_answer;
2268 sargs.keys.menu = &ans_certfail_keymenu;
2269 setbitmap(sargs.keys.bitmap);
2270 /* don't want to re-enter c-client */
2271 sargs.quell_newmail = 1;
2272 sargs.help.text = h_tls_failure;
2273 sargs.help.title = _("HELP FOR TLS/SSL FAILURE");
2275 if(ps_global->ttyo)
2276 scrolltool(&sargs);
2277 else{
2278 char **q, **qp;
2279 char *p;
2280 unsigned char c;
2281 int cnt = 0;
2284 * The screen isn't initialized yet, which should mean that this
2285 * is the result of a -p argument. Display_args_err knows how to deal
2286 * with the uninitialized screen, so we mess with the data to get it
2287 * in shape for display_args_err. This is pretty hacky.
2290 so_seek(store, 0L, 0); /* rewind */
2291 /* count the lines */
2292 while(so_readc(&c, store))
2293 if(c == '\n')
2294 cnt++;
2296 qp = q = (char **)fs_get((cnt+1) * sizeof(char *));
2297 memset(q, 0, (cnt+1) * sizeof(char *));
2299 so_seek(store, 0L, 0); /* rewind */
2300 p = buf;
2301 while(so_readc(&c, store)){
2302 if(c == '\n'){
2303 *p = '\0';
2304 *qp++ = cpystr(buf);
2305 p = buf;
2307 else
2308 *p++ = c;
2311 display_args_err(NULL, q, 0);
2312 free_list_array(&q);
2315 ps_global->mangled_screen = 1;
2316 ps_global->painted_body_on_startup = 0;
2317 ps_global->painted_footer_on_startup = 0;
2318 so_give(&store);
2320 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1);
2325 answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2327 int rv = 1;
2329 ps_global->next_screen = SCREEN_FUN_NULL;
2331 switch(cmd){
2332 case MC_YES :
2333 *(int *)(sparms->proc.data.p) = 'y';
2334 break;
2336 case MC_NO :
2337 *(int *)(sparms->proc.data.p) = 'n';
2338 break;
2340 default:
2341 alpine_panic("Unexpected command in answer_cert_failure");
2342 break;
2345 return(rv);
2350 oauth2_auth_answer(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2352 int rv = 1, rc;
2353 AUTH_CODE_S user;
2354 int q_line, flags;
2355 /* TRANSLATORS: user needs to input an access code from the server */
2356 char *accesscodelabel = _("Copy and Paste Access Code");
2357 char token[MAILTMPLEN], prompt[MAILTMPLEN];
2359 ps_global->next_screen = SCREEN_FUN_NULL;
2361 token[0] = '\0';
2362 switch(cmd){
2363 case MC_YES :
2364 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
2365 flags = OE_APPEND_CURRENT;
2366 sprintf(prompt, "%s: ", accesscodelabel);
2367 do {
2368 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
2369 prompt, NULL, NO_HELP, &flags);
2370 } while (rc != 0 && rc != 1);
2371 user.code = rc == 0 ? cpystr(token) : NULL;
2372 user.answer = 'e';
2373 rv = rc == 1 ? 0 : 1;
2374 break;
2376 case MC_NO :
2377 user.code = NULL;
2378 user.answer = 'e';
2379 break;
2381 default:
2382 alpine_panic("Unexpected command in oauth2_auth_answer");
2383 break;
2385 *(AUTH_CODE_S *) sparms->proc.data.p = user;
2386 return(rv);
2390 /*----------------------------------------------------------------------
2391 This can be used to prevent the flickering of the check_cue char
2392 caused by numerous (5000+) fetches by c-client. Right now, the only
2393 practical use found is newsgroup subsciption.
2395 check_cue_display will check if this global is set, and won't clear
2396 the check_cue_char if set.
2397 ----*/
2398 void
2399 set_read_predicted(int i)
2401 ps_global->read_predicted = i==1;
2402 #ifdef _WINDOWS
2403 if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global))
2404 check_cue_display(" ");
2405 #endif
2409 /*----------------------------------------------------------------------
2410 Exported method to retrieve logged in user name associated with stream
2412 Args: host -- host to find associated login name with.
2414 Result:
2415 ----*/
2416 void *
2417 pine_block_notify(int reason, void *data)
2419 switch(reason){
2420 case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */
2421 break;
2423 case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */
2424 break;
2426 case BLOCK_TCPWRITE: /* blocked on TCP write */
2427 case BLOCK_FILELOCK: /* blocked on file locking */
2428 #ifdef _WINDOWS
2429 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
2430 check_cue_display(">");
2432 mswin_setcursor(MSWIN_CURSOR_BUSY);
2433 #endif
2434 break;
2436 case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */
2437 case BLOCK_TCPOPEN: /* blocked on TCP open */
2438 case BLOCK_TCPREAD: /* blocked on TCP read */
2439 case BLOCK_TCPCLOSE: /* blocked on TCP close */
2440 #ifdef _WINDOWS
2441 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
2442 check_cue_display("<");
2444 mswin_setcursor(MSWIN_CURSOR_BUSY);
2445 #endif
2446 break;
2448 default :
2449 case BLOCK_NONE: /* not blocked */
2450 #ifdef _WINDOWS
2451 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
2452 check_cue_display(" ");
2453 #endif
2454 break;
2458 return(NULL);
2462 void
2463 mm_expunged_current(long unsigned int rawno)
2465 /* expunged something we're viewing? */
2466 if(!ps_global->expunge_in_progress
2467 && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno))
2468 && (ps_global->prev_screen == mail_view_screen
2469 || ps_global->prev_screen == attachment_screen))){
2470 ps_global->next_screen = mail_index_screen;
2471 q_status_message(SM_ORDER | SM_DING , 3, 3,
2472 "Message you were viewing is gone!");
2477 #ifdef PASSFILE
2480 * Specific functions to support caching username/passwd/host
2481 * triples on disk for use from one session to the next...
2484 #define FIRSTCH 0x20
2485 #define LASTCH 0x7e
2486 #define TABSZ (LASTCH - FIRSTCH + 1)
2488 static int xlate_key;
2492 * xlate_in() - xlate_in the given character
2494 char
2495 xlate_in(int c)
2497 register int eti;
2499 eti = xlate_key;
2500 if((c >= FIRSTCH) && (c <= LASTCH)){
2501 eti += (c - FIRSTCH);
2502 eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0;
2503 return((xlate_key = eti) + FIRSTCH);
2505 else
2506 return(c);
2511 * xlate_out() - xlate_out the given character
2513 char
2514 xlate_out(char c)
2516 register int dti;
2517 register int xch;
2519 if((c >= FIRSTCH) && (c <= LASTCH)){
2520 xch = c - (dti = xlate_key);
2521 xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0;
2522 dti = (xch - FIRSTCH) + dti;
2523 dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0;
2524 xlate_key = dti;
2525 return(xch);
2527 else
2528 return(c);
2530 #endif /* PASSFILE */
2533 #ifdef LOCAL_PASSWD_CACHE
2536 int
2537 line_get(char *tmp, size_t len, char **textp)
2539 char *s;
2541 tmp[0] = '\0';
2542 if (*textp == NULL
2543 || (s = strchr(*textp, '\n')) == NULL
2544 || (s - *textp) > len - 1)
2545 return 0;
2547 *s = '\0';
2548 if(*(s-1) == '\r')
2549 *(s-1) = '\0';
2551 snprintf(tmp, len, "%s\n", *textp);
2552 tmp[len-1] = '\0';
2553 *textp = s+1;
2555 return 1;
2558 * For UNIX:
2559 * Passfile lines are
2561 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
2563 * In pine4.40 and before there was no orig_hostname, and there still isn't
2564 * if it is the same as hostname.
2566 * else for WINDOWS:
2567 * Use Windows credentials. The TargetName of the credential is
2568 * UWash_Alpine_<hostname:port>\tuser\taltflag
2569 * and the blob consists of
2570 * passwd\torighost (if different from host)
2572 * We don't use anything fancy we just copy out all the credentials which
2573 * begin with TNAME and put them into our cache, so we don't lookup based
2574 * on the TargetName or anything like that. That was so we could re-use
2575 * the existing code and so that orighost data could be easily used.
2578 read_passfile(pinerc, l)
2579 char *pinerc;
2580 MMLOGIN_S **l;
2582 #ifdef WINCRED
2583 # if (WINCRED > 0)
2584 LPCTSTR lfilter = TEXT(TNAMESTAR);
2585 DWORD count, k;
2586 PCREDENTIAL *pcred;
2587 char *tmp, *blob, *target = NULL;
2588 char *host, *user, *sflags, *passwd, *orighost;
2589 char *ui[5];
2590 int i, j;
2592 if(using_passfile == 0)
2593 return(using_passfile);
2595 if(!g_CredInited){
2596 if(init_wincred_funcs() != 1){
2597 using_passfile = 0;
2598 return(using_passfile);
2602 dprint((9, "read_passfile\n"));
2604 using_passfile = 1;
2606 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
2607 if(pcred){
2608 for(k = 0; k < count; k++){
2610 host = user = sflags = passwd = orighost = NULL;
2611 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
2613 target = lptstr_to_utf8(pcred[k]->TargetName);
2614 tmp = srchstr(target, TNAME);
2616 if(tmp){
2617 tmp += strlen(TNAME);
2618 for(i = 0, j = 0; tmp[i] && j < 3; j++){
2619 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
2620 ; /* find end of data */
2622 if(tmp[i])
2623 tmp[i++] = '\0'; /* tie off data */
2626 host = ui[0];
2627 user = ui[1];
2628 sflags = ui[2];
2631 blob = (char *) pcred[k]->CredentialBlob;
2632 if(blob){
2633 for(i = 0, j = 3; blob[i] && j < 5; j++){
2634 for(ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++)
2635 ; /* find end of data */
2637 if(blob[i])
2638 blob[i++] = '\0'; /* tie off data */
2641 passwd = ui[3];
2642 orighost = ui[4];
2645 if(passwd && host && user){ /* valid field? */
2646 STRLIST_S hostlist[2];
2647 int flags;
2649 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
2650 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
2651 hostlist[0].name = host;
2652 if(orighost){
2653 hostlist[0].next = &hostlist[1];
2654 hostlist[1].name = orighost;
2655 hostlist[1].next = NULL;
2657 else{
2658 hostlist[0].next = NULL;
2661 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
2664 if(target)
2665 fs_give((void **) &target);
2668 g_CredFree((PVOID) pcred);
2672 return(1);
2674 # else /* old windows */
2675 using_passfile = 0;
2676 return(0);
2677 # endif
2679 #elif APPLEKEYCHAIN
2681 char target[MAILTMPLEN];
2682 char *tmp, *host, *user, *sflags, *passwd, *orighost;
2683 char *ui[5];
2684 int i, j, k, rc;
2685 SecKeychainAttributeList attrList;
2686 SecKeychainSearchRef searchRef = NULL;
2687 SecKeychainAttribute attrs[] = {
2688 { kSecAccountItemAttr, strlen(TNAME), TNAME }
2691 if(using_passfile == 0)
2692 return(using_passfile);
2694 dprint((9, "read_passfile\n"));
2697 /* search for only our items in the keychain */
2698 attrList.count = 1;
2699 attrList.attr = attrs;
2701 using_passfile = 1;
2702 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
2703 kSecGenericPasswordItemClass,
2704 &attrList,
2705 &searchRef))){
2706 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
2707 if(searchRef){
2708 SecKeychainItemRef itemRef = NULL;
2709 SecKeychainAttributeInfo info;
2710 SecKeychainAttributeList *attrList = NULL;
2711 UInt32 blength = 0;
2712 char *blob = NULL;
2713 char *blobcopy = NULL; /* NULL terminated copy */
2715 UInt32 tags[] = {kSecAccountItemAttr,
2716 kSecServiceItemAttr};
2717 UInt32 formats[] = {0,0};
2719 dprint((10, "read_passfile: searchRef not NULL\n"));
2720 info.count = 2;
2721 info.tag = tags;
2722 info.format = formats;
2725 * Go through each item we found and put it
2726 * into our list.
2728 while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){
2729 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
2730 rc = SecKeychainItemCopyAttributesAndData(itemRef,
2731 &info, NULL,
2732 &attrList,
2733 &blength,
2734 (void **) &blob);
2735 if(rc == 0 && attrList){
2736 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count));
2738 blobcopy = (char *) fs_get((blength + 1) * sizeof(char));
2739 strncpy(blobcopy, (char *) blob, blength);
2740 blobcopy[blength] = '\0';
2743 * I'm not real clear on how this works. It seems to be
2744 * necessary to combine the attributes from two passes
2745 * (attrList->count == 2) in order to get the full set
2746 * of attributes we inserted into the keychain in the
2747 * first place. So, we reset host...orighost outside of
2748 * the following for loop, not inside.
2750 host = user = sflags = passwd = orighost = NULL;
2751 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
2753 for(k = 0; k < attrList->count; k++){
2755 if(attrList->attr[k].length){
2756 strncpy(target,
2757 (char *) attrList->attr[k].data,
2758 MIN(attrList->attr[k].length,sizeof(target)));
2759 target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0';
2762 tmp = target;
2763 for(i = 0, j = 0; tmp[i] && j < 3; j++){
2764 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
2765 ; /* find end of data */
2767 if(tmp[i])
2768 tmp[i++] = '\0'; /* tie off data */
2771 if(ui[0])
2772 host = ui[0];
2774 if(ui[1])
2775 user = ui[1];
2777 if(ui[2])
2778 sflags = ui[2];
2780 for(i = 0, j = 3; blobcopy[i] && j < 5; j++){
2781 for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++)
2782 ; /* find end of data */
2784 if(blobcopy[i])
2785 blobcopy[i++] = '\0'; /* tie off data */
2788 if(ui[3])
2789 passwd = ui[3];
2791 if(ui[4])
2792 orighost = ui[4];
2794 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:""));
2797 if(passwd && host && user){ /* valid field? */
2798 STRLIST_S hostlist[2];
2799 int flags;
2801 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
2802 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
2803 hostlist[0].name = host;
2804 if(orighost){
2805 hostlist[0].next = &hostlist[1];
2806 hostlist[1].name = orighost;
2807 hostlist[1].next = NULL;
2809 else{
2810 hostlist[0].next = NULL;
2813 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
2816 if(blobcopy)
2817 fs_give((void **) & blobcopy);
2819 SecKeychainItemFreeAttributesAndData(attrList, (void *) blob);
2821 else{
2822 using_passfile = 0;
2823 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc));
2826 CFRelease(itemRef);
2827 itemRef = NULL;
2830 CFRelease(searchRef);
2832 else{
2833 using_passfile = 0;
2834 dprint((10, "read_passfile: searchRef NULL\n"));
2837 else{
2838 using_passfile = 0;
2839 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc));
2842 return(using_passfile);
2844 #else /* PASSFILE */
2846 char tmp[MAILTMPLEN], *ui[5];
2847 int i, j, n, rv = 0;
2848 size_t len;
2849 char *tmptext = NULL;
2850 #ifdef SMIME
2851 char tmp2[MAILTMPLEN];
2852 char *text = NULL, *text2 = NULL;
2853 int encrypted = 0;
2854 #endif /* SMIME */
2855 FILE *fp;
2857 if(using_passfile == 0)
2858 return(using_passfile);
2860 dprint((9, "read_passfile\n"));
2862 /* if there's no password to read, bag it!! */
2863 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){
2864 using_passfile = 0;
2865 return(using_passfile);
2868 #ifdef SMIME
2869 /* the next call initializes the key/certificate pair used to
2870 * encrypt and decrypt a password file. The details of how this is
2871 * done is in the file pith/smime.c. During this setup we might call
2872 * smime_init(), but no matter what happens we must call smime_deinit()
2873 * there. The reason why this is so is because we can not assume that
2874 * the .pinerc file has been read by this time, so this code might not
2875 * know about the ps_global->smime structure or any of its components,
2876 * and it shouldn't because it only needs ps_global->pwdcert, so
2877 * do not init smime here, because the .pinerc might not have been
2878 * read and we do not really know where the keys and certificates really
2879 * are.
2880 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
2881 * it is called for the first time and there are certificates at all,
2882 * or when it is called after the first time and the user refuses to
2883 * create a self-signed certificate. In this situation we will just
2884 * let the user live in an insecure world, but no more passwords will
2885 * be saved in the password file, and only those found there will be used.
2887 tmp2[0] = '\0';
2888 fgets(tmp2, sizeof(tmp2), fp);
2889 fclose(fp);
2890 if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
2891 /* there is an already existing password file, that is not encrypted
2892 * and there is no key to encrypt it yet, go again through setup_pwdcert
2893 * and encrypt it now.
2895 if(tmp2[0]){ /* not empty, UNencrypted password file */
2896 if(ps_global->pwdcert == NULL)
2897 rv = setup_pwdcert(&ps_global->pwdcert);
2898 if((rv == 0 || rv == -5) && ps_global->pwdcert == NULL)
2899 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
2900 if(ps_global->pwdcert == NULL){
2901 q_status_message(SM_ORDER, 3, 3,
2902 " Failed to create private key. Using UNencrypted Password file. ");
2903 save_password = 0;
2905 else{
2906 if(rv == 1){
2907 q_status_message(SM_ORDER, 3, 3,
2908 " Failed to unlock private key. Using UNencrypted Password file. ");
2909 save_password = 0; /* do not save more passwords */
2912 if(ps_global->pwdcert != NULL
2913 && encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
2914 encrypted++;
2917 else {
2918 if(ps_global->pwdcert == NULL)
2919 rv = setup_pwdcert(&ps_global->pwdcert);
2920 encrypted++;
2924 * if password file is encrypted we attemtp to decrypt. We ask the
2925 * user for the password to unlock the password file. If the user
2926 * enters the password and it unlocks the file, use it and keep saving
2927 * passwords in it. If the user enters the wrong passwords and does
2928 * not unlock it, we will not see that here, but in decrypt_file, so
2929 * the only other possibility is that the user cancels. In that case
2930 * we will see i == -1. In that case, we will let the user attempt
2931 * manual login to the server they want to login, but passwords will
2932 * not be saved so that the password file will not be saved
2933 * unencrypted and rewritten again.
2935 if(encrypted){
2936 text = text2 = decrypt_file((char *)tmp, &i, (PERSONAL_CERT *)ps_global->pwdcert);
2937 len = text2 ? strlen(text2) : 0;
2938 switch(i){
2939 case -2: using_passfile = 0;
2940 break;
2942 case 1 : save_password = 1;
2943 using_passfile = 1;
2944 break;
2946 case -1: save_password = 0;
2947 using_passfile = 1;
2948 break;
2950 default: break;
2953 else{
2954 struct stat sbuf;
2955 if(our_stat(tmp, &sbuf) == 0)
2956 len = sbuf.st_size;
2957 else
2958 len = 0;
2959 fp = our_fopen(tmp, "rb"); /* reopen to read data */
2961 #endif /* SMIME */
2963 if(using_passfile == 0){
2964 #ifdef SMIME
2965 if(text) fs_give((void **)&text);
2966 #endif /* SMIME */
2967 return using_passfile;
2970 if(len > 0){
2971 tmptext = fs_get(len + 1);
2972 #ifdef SMIME
2973 for(n = 0; encrypted ? line_get(tmptext, len + 1, &text2)
2974 : (fgets(tmptext, len+1, fp) != NULL); n++){
2975 #else /* SMIME */
2976 for(n = 0; fgets(tmptext, len+1, fp); n++){
2977 #endif /* SMIME */
2978 /*** do any necessary DEcryption here ***/
2979 xlate_key = n;
2980 for(i = 0; tmptext[i]; i++)
2981 tmptext[i] = xlate_out(tmptext[i]);
2983 if(i && tmptext[i-1] == '\n')
2984 tmptext[i-1] = '\0'; /* blast '\n' */
2986 dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
2987 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
2988 for(i = 0, j = 0; tmptext[i] && j < 5; j++){
2989 for(ui[j] = &tmptext[i]; tmptext[i] && tmptext[i] != '\t'; i++)
2990 ; /* find end of data */
2992 if(tmptext[i])
2993 tmptext[i++] = '\0'; /* tie off data */
2996 dprint((10, "read_passfile: calling imap_set_passwd\n"));
2997 if(ui[0] && ui[1] && ui[2]){ /* valid field? */
2998 STRLIST_S hostlist[2];
2999 char *s = ui[3] ? strchr(ui[3], PWDAUTHSEP) : NULL;
3000 int flags = ui[3] ? atoi(s ? ++s : ui[3]) : 0;
3002 hostlist[0].name = ui[2];
3003 if(ui[4]){
3004 hostlist[0].next = &hostlist[1];
3005 hostlist[1].name = ui[4];
3006 hostlist[1].next = NULL;
3008 else{
3009 hostlist[0].next = NULL;
3012 imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0);
3017 if (tmptext) fs_give((void **) &tmptext);
3018 #ifdef SMIME
3019 if (text) fs_give((void **)&text);
3020 #else /* SMIME */
3021 fclose(fp);
3022 #endif /* SMIME */
3023 return(1);
3024 #endif /* PASSFILE */
3029 void
3030 write_passfile(pinerc, l)
3031 char *pinerc;
3032 MMLOGIN_S *l;
3034 char *authend, *authtype;
3035 #ifdef WINCRED
3036 # if (WINCRED > 0)
3037 char target[MAILTMPLEN];
3038 char blob[MAILTMPLEN];
3039 CREDENTIAL cred;
3040 LPTSTR ltarget = 0;
3042 if(using_passfile == 0)
3043 return;
3045 dprint((9, "write_passfile\n"));
3047 for(; l; l = l->next){
3048 authtype = l->passwd;
3049 authend = strchr(l->passwd, PWDAUTHSEP);
3050 if(authend != NULL){
3051 *authend = '\0';
3052 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3053 *authend = PWDAUTHSEP;
3055 else
3056 sprintf(blob, "%d", l->altflag);
3058 snprintf(target, sizeof(target), "%s%s\t%s\t%s",
3059 TNAME,
3060 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3061 l->user ? l->user : "",
3062 blob);
3063 ltarget = utf8_to_lptstr((LPSTR) target);
3065 if(ltarget){
3066 snprintf(blob, sizeof(blob), "%s%s%s",
3067 l->passwd ? l->passwd : "",
3068 (l->hosts && l->hosts->next && l->hosts->next->name)
3069 ? "\t" : "",
3070 (l->hosts && l->hosts->next && l->hosts->next->name)
3071 ? l->hosts->next->name : "");
3072 memset((void *) &cred, 0, sizeof(cred));
3073 cred.Flags = 0;
3074 cred.Type = CRED_TYPE_GENERIC;
3075 cred.TargetName = ltarget;
3076 cred.CredentialBlobSize = strlen(blob)+1;
3077 cred.CredentialBlob = (LPBYTE) &blob;
3078 cred.Persist = CRED_PERSIST_ENTERPRISE;
3079 g_CredWriteW(&cred, 0);
3081 fs_give((void **) &ltarget);
3084 #endif /* WINCRED > 0 */
3086 #elif APPLEKEYCHAIN
3087 int rc;
3088 char target[MAILTMPLEN];
3089 char blob[MAILTMPLEN];
3090 SecKeychainItemRef itemRef = NULL;
3092 if(using_passfile == 0)
3093 return;
3095 dprint((9, "write_passfile\n"));
3097 for(; l; l = l->next){
3098 authtype = l->passwd;
3099 authend = strchr(l->passwd, PWDAUTHSEP);
3100 if(authend != NULL){
3101 *authend = '\0';
3102 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3103 *authend = PWDAUTHSEP;
3105 else
3106 sprintf(blob, "%d", l->altflag);
3108 snprintf(target, sizeof(target), "%s\t%s\t%s",
3109 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3110 l->user ? l->user : "",
3111 blob);
3113 snprintf(blob, sizeof(blob), "%s%s%s",
3114 l->passwd ? l->passwd : "",
3115 (l->hosts && l->hosts->next && l->hosts->next->name)
3116 ? "\t" : "",
3117 (l->hosts && l->hosts->next && l->hosts->next->name)
3118 ? l->hosts->next->name : "");
3120 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob));
3122 rc = SecKeychainAddGenericPassword(NULL,
3123 strlen(target), target,
3124 strlen(TNAME), TNAME,
3125 strlen(blob), blob,
3126 NULL);
3127 if(rc==0){
3128 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3130 else{
3131 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc));
3134 if(rc == errSecDuplicateItem){
3135 /* fix existing entry */
3136 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3137 itemRef = NULL;
3138 if(!(rc=SecKeychainFindGenericPassword(NULL,
3139 strlen(target), target,
3140 strlen(TNAME), TNAME,
3141 NULL, NULL,
3142 &itemRef)) && itemRef){
3144 rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob);
3145 if(!rc){
3146 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc));
3149 else{
3150 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc));
3155 #else /* PASSFILE */
3156 char tmp[4*MAILTMPLEN], blob[4*MAILTMPLEN];
3157 int i, n;
3158 FILE *fp;
3159 #ifdef SMIME
3160 char *text = NULL, tmp2[4*MAILTMPLEN];
3161 int len = 0;
3162 #endif
3164 if(using_passfile == 0)
3165 return;
3167 dprint((9, "write_passfile\n"));
3169 /* if there's no passfile to read, bag it!! */
3170 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "wb"))){
3171 using_passfile = 0;
3172 return;
3175 #ifdef SMIME
3176 strncpy(tmp2, tmp, sizeof(tmp2));
3177 tmp2[sizeof(tmp2)-1] = '\0';
3178 #endif /* SMIME */
3180 for(n = 0; l; l = l->next, n++){
3181 authtype = l->passwd;
3182 authend = strchr(l->passwd, PWDAUTHSEP);
3183 if(authend != NULL){
3184 *authend = '\0';
3185 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3186 *authend = PWDAUTHSEP;
3188 else
3189 sprintf(blob, "%d", l->altflag);
3191 /*** do any necessary ENcryption here ***/
3192 snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%s%s%s\n", l->passwd, l->user,
3193 l->hosts->name, blob,
3194 (l->hosts->next && l->hosts->next->name) ? "\t" : "",
3195 (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
3196 : "");
3197 dprint((10, "write_passfile: %s", tmp ? tmp : "?"));
3198 xlate_key = n;
3199 for(i = 0; tmp[i]; i++)
3200 tmp[i] = xlate_in(tmp[i]);
3202 #ifdef SMIME
3203 fs_resize((void **)&text, (len + strlen(tmp) + 1)*sizeof(char));
3204 text[len] = '\0';
3205 len += strlen(tmp) + 1;
3206 strncat(text, tmp, strlen(tmp));
3207 #else /* SMIME */
3208 fputs(tmp, fp);
3209 #endif /* SMIME */
3212 fclose(fp);
3213 #ifdef SMIME
3214 if(text != NULL){
3215 if(ps_global->pwdcert == NULL){
3216 q_status_message(SM_ORDER, 3, 3, "Attempting to encrypt password file");
3217 i = setup_pwdcert(&ps_global->pwdcert);
3218 if((i == 0 || i == -5) && ps_global->pwdcert == NULL)
3219 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3221 if(ps_global->pwdcert == NULL){ /* we tried but failed */
3222 if(i == -1)
3223 q_status_message1(SM_ORDER, 3, 3, "Error: no directory %s to save certificates", ps_global->pwdcertdir);
3224 else
3225 q_status_message(SM_ORDER, 3, 3, "Refusing to write non-encrypted password file");
3227 else if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0)
3228 q_status_message(SM_ORDER, 3, 3, "Failed to encrypt password file");
3229 fs_give((void **)&text); /* do not save this text */
3231 #endif /* SMIME */
3232 #endif /* PASSFILE */
3235 #endif /* LOCAL_PASSWD_CACHE */
3238 #if (WINCRED > 0)
3239 void
3240 erase_windows_credentials(void)
3242 LPCTSTR lfilter = TEXT(TNAMESTAR);
3243 DWORD count, k;
3244 PCREDENTIAL *pcred;
3246 if(!g_CredInited){
3247 if(init_wincred_funcs() != 1)
3248 return;
3251 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
3252 if(pcred){
3253 for(k = 0; k < count; k++)
3254 g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0);
3256 g_CredFree((PVOID) pcred);
3261 void
3262 ask_erase_credentials(void)
3264 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){
3265 erase_windows_credentials();
3266 q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased");
3268 else
3269 q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased");
3272 #endif /* WINCRED */
3275 #ifdef LOCAL_PASSWD_CACHE
3277 get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
3278 char *pinerc, **passwd, *user;
3279 STRLIST_S *hostlist;
3280 int altflag;
3282 return get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, NULL);
3286 * get_passfile_passwd_auth - return the password contained in the special passord
3287 * cache. The file is assumed to be in the same directory
3288 * as the pinerc with the name defined above.
3291 get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, authtype)
3292 char *pinerc, **passwd, *user;
3293 STRLIST_S *hostlist;
3294 int altflag;
3295 char *authtype;
3297 dprint((10, "get_passfile_passwd_auth\n"));
3298 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3299 ? imap_get_passwd_auth(passfile_cache, passwd,
3300 user, hostlist, altflag, authtype)
3301 : 0);
3304 void
3305 free_passfile_cache_work(MMLOGIN_S **pwdcache)
3307 if(pwdcache == NULL || *pwdcache == NULL)
3308 return;
3310 if((*pwdcache)->user) fs_give((void **)&(*pwdcache)->user);
3311 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
3312 if((*pwdcache)->hosts) free_strlist(&(*pwdcache)->hosts);
3313 free_passfile_cache_work(&(*pwdcache)->next);
3314 fs_give((void **)pwdcache);
3318 void
3319 free_passfile_cache(void)
3321 if(passfile_cache)
3322 free_passfile_cache_work(&passfile_cache);
3326 is_using_passfile(void)
3328 return(using_passfile == 1);
3332 * Just trying to guess the username the user might want to use on this
3333 * host, the user will confirm.
3335 char *
3336 get_passfile_user(pinerc, hostlist)
3337 char *pinerc;
3338 STRLIST_S *hostlist;
3340 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3341 ? imap_get_user(passfile_cache, hostlist)
3342 : NULL);
3347 preserve_prompt(char *pinerc)
3349 return preserve_prompt_auth(pinerc, NULL);
3353 preserve_prompt_auth(char *pinerc, char *authtype)
3355 #ifdef WINCRED
3356 # if (WINCRED > 0)
3357 #define PROMPT_PWD _("Preserve password for next login")
3358 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3360 * This prompt was going to be able to be turned on and off via a registry
3361 * setting controlled from the config menu. We decided to always use the
3362 * dialog for login, and there the prompt is unobtrusive enough to always
3363 * be in there. As a result, windows should never reach this, but now
3364 * OS X somewhat uses the behavior just described.
3366 if(mswin_store_pass_prompt()
3367 && (want_to(authtype
3368 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3369 : PROMPT_PWD,
3370 'y', 'x', NO_HELP, WT_NORM)
3371 == 'y'))
3372 return(1);
3373 else
3374 return(0);
3375 # else
3376 return(0);
3377 # endif
3379 #elif APPLEKEYCHAIN
3380 #define PROMPT_PWD _("Preserve password for next login")
3381 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3383 int rc;
3384 if((rc = macos_store_pass_prompt()) != 0){
3385 if(want_to(authtype
3386 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3387 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
3388 == 'y'){
3389 if(rc == -1){
3390 macos_set_store_pass_prompt(1);
3391 q_status_message(SM_ORDER, 4, 4,
3392 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3394 return(1);
3396 else if(rc == -1){
3397 macos_set_store_pass_prompt(0);
3398 q_status_message(SM_ORDER, 4, 4,
3399 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3401 return(0);
3403 return(0);
3404 #else /* PASSFILE */
3405 #define PROMPT_PWD _("Preserve password on DISK for next login")
3406 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
3408 char tmp[MAILTMPLEN];
3409 struct stat sbuf;
3411 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || our_stat(tmp, &sbuf) < 0)
3412 return 0;
3414 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
3415 return(want_to(authtype
3416 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3417 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
3418 == 'y');
3419 return(0);
3420 #endif /* PASSFILE */
3423 #endif /* LOCAL_PASSWD_CACHE */
3426 #ifdef APPLEKEYCHAIN
3429 * Returns:
3430 * 1 if store pass prompt is set in the "registry" to on
3431 * 0 if set to off
3432 * -1 if not set to anything
3435 macos_store_pass_prompt(void)
3437 char *data = NULL;
3438 UInt32 len = 0;
3439 int rc = -1;
3440 int val;
3442 if(storepassprompt == -1){
3443 if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL,
3444 strlen(TNAMEPROMPT),
3445 TNAMEPROMPT, &len,
3446 (void **) &data, NULL))){
3447 val = (len == 1 && data && data[0] == '1');
3451 if(storepassprompt == -1 && !rc){
3452 if(val)
3453 storepassprompt = 1;
3454 else
3455 storepassprompt = 0;
3458 return(storepassprompt);
3462 void
3463 macos_set_store_pass_prompt(int val)
3465 storepassprompt = val ? 1 : 0;
3467 SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT),
3468 TNAMEPROMPT, 1, val ? "1" : "0", NULL);
3472 void
3473 macos_erase_keychain(void)
3475 SecKeychainAttributeList attrList;
3476 SecKeychainSearchRef searchRef = NULL;
3477 SecKeychainAttribute attrs1[] = {
3478 { kSecAccountItemAttr, strlen(TNAME), TNAME }
3480 SecKeychainAttribute attrs2[] = {
3481 { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT }
3484 dprint((9, "macos_erase_keychain\n"));
3487 * Seems like we ought to be able to combine attrs1 and attrs2
3488 * into a single array, but I couldn't get it to work.
3491 /* search for only our items in the keychain */
3492 attrList.count = 1;
3493 attrList.attr = attrs1;
3495 if(!SecKeychainSearchCreateFromAttributes(NULL,
3496 kSecGenericPasswordItemClass,
3497 &attrList,
3498 &searchRef)){
3499 if(searchRef){
3500 SecKeychainItemRef itemRef = NULL;
3503 * Go through each item we found and put it
3504 * into our list.
3506 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
3507 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3508 SecKeychainItemDelete(itemRef);
3509 CFRelease(itemRef);
3512 CFRelease(searchRef);
3516 attrList.count = 1;
3517 attrList.attr = attrs2;
3519 if(!SecKeychainSearchCreateFromAttributes(NULL,
3520 kSecGenericPasswordItemClass,
3521 &attrList,
3522 &searchRef)){
3523 if(searchRef){
3524 SecKeychainItemRef itemRef = NULL;
3527 * Go through each item we found and put it
3528 * into our list.
3530 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
3531 SecKeychainItemDelete(itemRef);
3532 CFRelease(itemRef);
3535 CFRelease(searchRef);
3540 #endif /* APPLEKEYCHAIN */
3542 #ifdef LOCAL_PASSWD_CACHE
3544 void
3545 set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
3546 char *pinerc, *passwd, *user;
3547 STRLIST_S *hostlist;
3548 int altflag, already_prompted;
3550 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, NULL);
3553 * set_passfile_passwd - set the password file entry associated with
3554 * cache. The file is assumed to be in the same directory
3555 * as the pinerc with the name defined above.
3556 * already_prompted: 0 not prompted
3557 * 1 prompted, answered yes
3558 * 2 prompted, answered no
3560 void
3561 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, authtype)
3562 char *pinerc, *passwd, *user;
3563 STRLIST_S *hostlist;
3564 int altflag, already_prompted;
3565 char *authtype;
3567 dprint((10, "set_passfile_passwd_auth\n"));
3568 if(((already_prompted == 0 && preserve_prompt_auth(pinerc, authtype))
3569 || already_prompted == 1)
3570 && !ps_global->nowrite_password_cache
3571 && (passfile_cache || read_passfile(pinerc, &passfile_cache))){
3572 imap_set_passwd_auth(&passfile_cache, passwd, user, hostlist, altflag, 0, 0, authtype);
3573 write_passfile(pinerc, passfile_cache);
3577 void
3578 update_passfile_hostlist(pinerc, user, hostlist, altflag)
3579 char *pinerc;
3580 char *user;
3581 STRLIST_S *hostlist;
3582 int altflag;
3584 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, NULL);
3588 * Passfile lines are
3590 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3592 * In pine4.40 and before there was no orig_hostname.
3593 * This routine attempts to repair that.
3595 void
3596 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, authtype)
3597 char *pinerc;
3598 char *user;
3599 STRLIST_S *hostlist;
3600 int altflag;
3601 char *authtype;
3603 #ifdef WINCRED
3604 return;
3605 #else /* !WINCRED */
3606 MMLOGIN_S *l;
3607 size_t len = authtype ? strlen(authtype) : 0;
3608 size_t offset = authtype ? 1 : 0;
3610 for(l = passfile_cache; l; l = l->next)
3611 if(imap_same_host_auth(l->hosts, hostlist, authtype)
3612 && *user
3613 && !strcmp(user, l->user + len + offset)
3614 && l->altflag == altflag){
3615 break;
3618 if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
3619 && hostlist->next->name
3620 && !ps_global->nowrite_password_cache){
3621 l->hosts->next = new_strlist_auth(hostlist->next->name, authtype, PWDAUTHSEP);
3622 write_passfile(pinerc, passfile_cache);
3624 #endif /* !WINCRED */
3627 #endif /* LOCAL_PASSWD_CACHE */
3630 #if (WINCRED > 0)
3632 * Load and init the WinCred structure.
3633 * This gives us a way to skip the WinCred code
3634 * if the dll doesn't exist.
3637 init_wincred_funcs(void)
3639 if(!g_CredInited)
3641 HMODULE hmod;
3643 /* Assume the worst. */
3644 g_CredInited = -1;
3646 hmod = LoadLibrary(TEXT("advapi32.dll"));
3647 if(hmod)
3649 FARPROC fpCredWriteW;
3650 FARPROC fpCredEnumerateW;
3651 FARPROC fpCredDeleteW;
3652 FARPROC fpCredFree;
3654 fpCredWriteW = GetProcAddress(hmod, "CredWriteW");
3655 fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW");
3656 fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW");
3657 fpCredFree = GetProcAddress(hmod, "CredFree");
3659 if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree)
3661 g_CredWriteW = (CREDWRITEW *)fpCredWriteW;
3662 g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW;
3663 g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW;
3664 g_CredFree = (CREDFREE *)fpCredFree;
3666 g_CredInited = 1;
3670 mswin_set_erasecreds_callback(ask_erase_credentials);
3673 return g_CredInited;
3676 #endif /* WINCRED */