* A few improvements to the http code, which make the http_* functions not return...
[alpine.git] / alpine / imap.c
blob556ad4018f12e59c28d4feed4c33957180503220
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-2020 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 "init.h"
44 #include "../pith/state.h"
45 #include "../pith/conf.h"
46 #include "../pith/msgno.h"
47 #include "../pith/filter.h"
48 #include "../pith/news.h"
49 #include "../pith/util.h"
50 #include "../pith/list.h"
51 #include "../pith/margin.h"
52 #ifdef SMIME
53 #include "../pith/smime.h"
54 #endif /* SMIME */
56 #if (WINCRED > 0)
57 #include <wincred.h>
58 #define TNAME "UWash_Alpine_"
59 #define TNAMESTAR "UWash_Alpine_*"
62 * WinCred Function prototypes
64 typedef BOOL (WINAPI CREDWRITEW) ( __in PCREDENTIALW Credential, __in DWORD Flags );
65 typedef BOOL (WINAPI CREDENUMERATEW) ( __in LPCWSTR Filter, __reserved DWORD Flags,
66 __out DWORD *Count, __deref_out_ecount(*Count) PCREDENTIALW **Credential );
67 typedef BOOL (WINAPI CREDDELETEW) ( __in LPCWSTR TargetName, __in DWORD Type,
68 __reserved DWORD Flags );
69 typedef VOID (WINAPI CREDFREE) ( __in PVOID Buffer );
72 * WinCred functions
74 int g_CredInited = 0; /* 1 for loaded successfully,
75 * -1 for not available.
76 * 0 for not initialized yet.
78 CREDWRITEW *g_CredWriteW;
79 CREDENUMERATEW *g_CredEnumerateW;
80 CREDDELETEW *g_CredDeleteW;
81 CREDFREE *g_CredFree;
83 #endif /* WINCRED */
85 #ifdef APPLEKEYCHAIN
86 #include <Security/SecKeychain.h>
87 #include <Security/SecKeychainItem.h>
88 #include <Security/SecKeychainSearch.h>
89 #define TNAME "UWash_Alpine"
90 #define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
92 int macos_store_pass_prompt(void);
93 void macos_set_store_pass_prompt(int);
95 static int storepassprompt = -1;
96 #endif /* APPLEKEYCHAIN */
100 * Internal prototypes
102 void mm_login_alt_cue(NETMBX *);
103 long pine_tcptimeout_noscreen(long, long, char *);
104 int answer_cert_failure(int, MSGNO_S *, SCROLL_S *);
105 int oauth2_auth_answer(int, MSGNO_S *, SCROLL_S *);
107 #ifdef LOCAL_PASSWD_CACHE
108 int read_passfile(char *, MMLOGIN_S **);
109 void write_passfile(char *, MMLOGIN_S *);
110 int preserve_prompt(char *);
111 int preserve_prompt_auth(char *, char *authtype);
112 void update_passfile_hostlist(char *, char *, STRLIST_S *, int);
113 void update_passfile_hostlist_auth(char *, char *, STRLIST_S *, int, char *);
114 void free_passfile_cache_work(MMLOGIN_S **);
116 static MMLOGIN_S *passfile_cache = NULL;
117 static int using_passfile = -1;
118 int save_password = 1;
119 #endif /* LOCAL_PASSWD_CACHE */
121 #ifdef PASSFILE
122 char xlate_in(int);
123 char xlate_out(char);
124 int line_get(char *, size_t, char **);
125 #endif /* PASSFILE */
127 #if (WINCRED > 0)
128 void ask_erase_credentials(void);
129 int init_wincred_funcs(void);
130 #endif /* WINCRED */
133 static char *details_cert, *details_host, *details_reason;
135 extern XOAUTH2_INFO_S xoauth_default[];
138 * This is the private information of the client, which is passed to
139 * c-client for processing. Every c-client application must have its
140 * own.
142 OAUTH2_S alpine_oauth2_list[] =
144 {GMAIL_NAME,
145 {"imap.gmail.com", "smtp.gmail.com", NULL, NULL},
146 {{"client_id", NULL},
147 {"client_secret", NULL},
148 {"code", NULL},
149 {"refresh_token", NULL},
150 {"scope", "https://mail.google.com/"},
151 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
152 {"grant_type", "authorization_code"},
153 {"grant_type", "refresh_token"},
154 {"response_type", "code"},
155 {"state", NULL},
156 {"prompt", NULL}
158 {{"GET", "https://accounts.google.com/o/oauth2/auth",
159 {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_End, OA2_End, OA2_End}},
160 {"POST", "https://accounts.google.com/o/oauth2/token",
161 {OA2_Id, OA2_Secret, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Code, OA2_End, OA2_End}},
162 {"POST", "https://accounts.google.com/o/oauth2/token",
163 {OA2_Id, OA2_Secret, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}
165 NULL, 0
167 #if 0
168 {"Outlook",
169 {"outlook.office365.com", "smtp.gmail.com", NULL, NULL},
170 // {{"client_id", "2d681b88-9675-4ff0-b033-4de97dcb7a04"},
171 // {"client_secret", "FHLY770;@%fmrzxbnEKG44!"},
172 {{"client_id", NULL},
173 {"client_secret", NULL},
174 {"code", NULL},
175 {"refresh_token", NULL},
176 {"scope", "openid offline_access profile https://outlook.office.com/mail.readwrite https://outlook.office.com/mail.readwrite.shared https://outlook.office.com/mail.send https://outlook.office.com/mail.send.shared https://outlook.office.com/calendars.readwrite https://outlook.office.com/calendars.readwrite.shared https://outlook.office.com/contacts.readwrite https://outlook.office.com/contacts.readwrite.shared https://outlook.office.com/tasks.readwrite https://outlook.office.com/tasks.readwrite.shared https://outlook.office.com/mailboxsettings.readwrite https://outlook.office.com/people.read https://outlook.office.com/user.readbasic.all"},
177 {"redirect_uri", "https://login.microsoftonline.com/common/oauth2/nativeclient"},
178 {"grant_type", "authorization_code"},
179 {"grant_type", "refresh_token"},
180 {"response_type", "code"},
181 {"state", NULL},
182 {"prompt", "login"}
184 {{"GET", "https://login.microsoftonline.com/common/oauth2/authorize",
185 {OA2_Id, OA2_Scope, OA2_Redirect, OA2_Response, OA2_State, OA2_Prompt, OA2_End}},
186 {"POST", "https://login.microsoftonline.com/common/oauth2/token",
187 {OA2_Id, OA2_Secret, OA2_Redirect, OA2_GrantTypeforAccessToken, OA2_Code, OA2_Scope, OA2_End}},
188 {"POST", "https://login.microsoftonline.com/common/oauth2/token",
189 {OA2_Id, OA2_Secret, OA2_RefreshToken, OA2_GrantTypefromRefreshToken, OA2_End, OA2_End, OA2_End}}
191 NULL, 0
193 #endif
194 { NULL, NULL, NULL, NULL, NULL, 0},
197 typedef struct auth_code_s {
198 char *code;
199 int answer;
200 } AUTH_CODE_S;
202 char *
203 oauth2_get_access_code(char *url, char *method, OAUTH2_S *oauth2, int *tryanother)
205 char tmp[MAILTMPLEN];
206 char *code;
208 if(ps_global->ttyo){
209 SCROLL_S sargs;
210 STORE_S *in_store, *out_store;
211 gf_io_t pc, gc;
212 HANDLE_S *handles = NULL;
213 AUTH_CODE_S user_input;
215 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
216 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
217 goto try_wantto;
219 so_puts(in_store, "<HTML><P>");
220 sprintf(tmp, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2->name);
221 so_puts(in_store, tmp);
222 sprintf(tmp, _("</P><P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
223 so_puts(in_store, tmp);
225 if(strucmp(oauth2->name, "Gmail") == 0){
226 so_puts(in_store, _(" If this is your first time setting up this type of authentication and you have a G-Suite account, please follow the steps below. "));
227 so_puts(in_store, _("</P><P> First you must register Alpine with Google and create a client-id and client-secret. The steps below explain how to do this. If you already did that, then you can skip to the <A HREF=\"#secondpart\">second part</A> to continue with the setup process."));
228 so_puts(in_store, _("<UL> "));
229 so_puts(in_store, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A>,"));
230 so_puts(in_store, _("and create a project. The name of the project is not important."));
231 so_puts(in_store, _("<LI> Go to the Consent Screen and make your app INTERNAL."));
232 so_puts(in_store, _("<LI> Create OAUTH Credentials."));
233 so_puts(in_store, _("</UL> "));
234 so_puts(in_store, _("<P> As a result of this process, you will get a client-id and a client-secret."));
235 so_puts(in_store, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
236 so_puts(in_store, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
237 so_puts(in_store, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
238 so_puts(in_store, _("</P><P> If you completed these steps successfully, you are ready to move to the second part, where you will authorize Gmail to give access to Alpine to access your email."));
241 so_puts(in_store, _("</P><P><A NAME=\"secondpart\">In order</A> to authrorize Alpine to access your email, Alpine needs to open the following URL:"));
242 so_puts(in_store,"</P><P>");
243 sprintf(tmp_20k_buf, _("<A HREF=\"%s\">%s</A>"), url, url);
244 so_puts(in_store, tmp_20k_buf);
246 so_puts(in_store, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
247 sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2->name);
248 so_puts(in_store, tmp);
249 so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
251 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. "));
252 so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page."));
253 so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
254 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. "));
255 so_puts(in_store, _(" Once you have completed this process, Alpine will proceed with authentication."));
256 so_puts(in_store, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
257 so_puts(in_store, _("</P></HTML>"));
259 so_seek(in_store, 0L, 0);
260 init_handles(&handles);
261 gf_filter_init();
262 gf_link_filter(gf_html2plain,
263 gf_html2plain_opt(NULL,
264 ps_global->ttyo->screen_cols, non_messageview_margin(),
265 &handles, NULL, GFHP_LOCAL_HANDLES));
266 gf_set_so_readc(&gc, in_store);
267 gf_set_so_writec(&pc, out_store);
268 gf_pipe(gc, pc);
269 gf_clear_so_writec(out_store);
270 gf_clear_so_readc(in_store);
272 memset(&sargs, 0, sizeof(SCROLL_S));
273 sargs.text.handles = handles;
274 sargs.text.text = so_text(out_store);
275 sargs.text.src = CharStar;
276 sargs.text.desc = _("help text");
277 sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION");
278 sargs.proc.tool = oauth2_auth_answer;
279 sargs.proc.data.p = (void *)&user_input;
280 sargs.keys.menu = &oauth2_auth_keymenu;
281 /* don't want to re-enter c-client */
282 sargs.quell_newmail = 1;
283 setbitmap(sargs.keys.bitmap);
284 sargs.help.text = h_oauth2_start;
285 sargs.help.title = _("HELP FOR SETTING UP XOAUTH2");
287 do {
288 scrolltool(&sargs);
289 ps_global->mangled_screen = 1;
290 ps_global->painted_body_on_startup = 0;
291 ps_global->painted_footer_on_startup = 0;
292 } while (user_input.answer != 'e');
294 if(!struncmp(user_input.code, "https://", 8)){
295 char *s, *t;
296 s = strstr(user_input.code, "code=");
297 if(s != NULL){
298 t = strchr(s, '&');
299 if(t) *t = '\0';
300 code = cpystr(s+5);
301 if(t) *t = '&';
304 else code = user_input.code ? cpystr(user_input.code) : NULL;
305 if(user_input.code) fs_give((void **) &user_input.code);
307 if(code == NULL) *tryanother = 1;
309 so_give(&in_store);
310 so_give(&out_store);
311 free_handles(&handles);
313 else{
314 int flags, rc, q_line;
315 /* TRANSLATORS: user needs to input an access code from the server */
316 char *accesscodelabel = _("Copy and Paste Access Code");
317 char prompt[MAILTMPLEN], token[MAILTMPLEN];
319 * If screen hasn't been initialized yet, use want_to.
321 try_wantto:
322 tmp_20k_buf[0] = '\0';
323 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
324 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2->name);
325 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
327 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
328 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2->name, method),
329 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
331 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
332 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
333 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
335 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
336 "%s\n\n", url);
337 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
339 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
340 _("Copy and paste the previous URL into a web browser that supports javascript, to take you to %s's servers to complete this process.\n\n"), oauth2->name);
341 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
343 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
344 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
345 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
347 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
348 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
349 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
351 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
352 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
353 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
355 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
356 "%s", _(" If you do not see a code, copy the url of the page you were redirected to and again press 'C' and copy and paste it into the prompt. "));
357 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
359 snprintf(tmp_20k_buf+strlen(tmp_20k_buf), SIZEOF_20KBUF-strlen(tmp_20k_buf),
360 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
361 tmp_20k_buf[SIZEOF_20KBUF-1] = '\0';
363 display_init_err(tmp_20k_buf, 0);
364 memset((void *)tmp, 0, sizeof(tmp));
365 strncpy(tmp, _("Alpine would like to get authorization to access your email: "), sizeof(tmp));
366 tmp[sizeof(tmp)-1] = '\0';
367 strncat(tmp, _(": Proceed "), sizeof(tmp)-strlen(tmp)-1);
369 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){
370 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
371 flags = OE_APPEND_CURRENT;
372 sprintf(prompt, "%s: ", accesscodelabel);
373 do {
374 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
375 prompt, NULL, NO_HELP, &flags);
376 } while (rc != 0 && rc != 1);
377 if(!struncmp(token, "https://", 8)){
378 char *s, *t;
379 s = strstr(token, "code=");
380 if(s != NULL){
381 t = strchr(s, '&');
382 if(t) *t = '\0';
383 code = cpystr(s+5);
384 if(t) *t = '&';
387 else code = token[0] ? cpystr(token) : NULL;
391 return code;
394 void mm_login_oauth2(NETMBX *, char *, char *, OAUTH2_S *, long int, char *, char *);
396 /* The purpose of this function is to report to c-client the values of the
397 * different tokens and codes so that c-client can try to log in the user
398 * to the server. This function DOES NOT attempt to get these values for
399 * the user. That is attempted in the c-client side (as best as it can be
400 * done given our circumstances: no http support, no javascript support,
401 * etc.). This is the best we can do:
403 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
404 * as best as we can. Unloaded means that there is no server known to
405 * connect, no access token, etc. Pretty much nothing is known about
406 * how to get access code, access token, etc. We ask the user to fill
407 * it up for us, if they can. If the user fills it up we save those
408 * values.
410 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
411 * save the information that c-client got for us.
413 * 3. When saving this information we use the password caching facilities,
414 * but we must do it in a different format so that old information and
415 * new information are not mixed. In order to accommodate this for new
416 * authentication methods, we save the information in the same fields,
417 * but this time we modify it slightly, so that old functions fail to
418 * understand the new information and so not modify it nor use it. The
419 * modification is simple: Every piece of information that was saved
420 * before is prepended XOAUTH2\001 to indicate the authentication
421 * method for which it works. The character \001 is a separator. New
422 * Alpine will know how to deal with this, but old versions, will not
423 * strip this prefix from the information and fail to get the
424 * information or modify it when needed. Only new versions of Alpine will
425 * know how to process this information.
426 * new_value = authenticator_method separator old_value
427 * authenticator_method = "XOAUTH2_S"
428 * separator = "U+1"
429 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
430 * authenticator is "XOAUTH2\001imap.gmail.com".
431 * In addition, the password field is not used to encode the password
432 * anymore, it is used to save login information needed in a format that
433 * the caller function chooses, but that must be preceded by the
434 * "authenticator_method separator" code as above.
436 void
437 mm_login_oauth2(NETMBX *mb, char *user, char *method,
438 OAUTH2_S *login, long int trial,
439 char *usethisprompt, char *altuserforcache)
441 char *token, tmp[MAILTMPLEN];
442 char prompt[4*MAILTMPLEN], value[4*MAILTMPLEN], *last;
443 char defuser[NETMAXUSER];
444 char hostleadin[80], hostname[200], defubuf[200];
445 char logleadin[80], pwleadin[50];
446 char *url_oauth2;
447 char *tool = NULL;
448 char *OldRefreshToken, *OldAccessToken;
449 char *NewRefreshToken, *NewAccessToken;
450 char *SaveRefreshToken, *SaveAccessToken;
451 /* TRANSLATORS: A label for the hostname that the user is logging in on */
452 char *hostlabel = _("HOST");
453 /* TRANSLATORS: user is logging in as a particular user (a particular
454 login name), this is just labelling that user name. */
455 char *userlabel = _("USER");
456 STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1];
457 HelpType help ;
458 int len, rc, q_line, flags, i, j;
459 int oespace, avail, need, save_dont_use;
460 int save_in_init;
461 int registered;
462 int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime;
463 OAUTH2_S *oa2list;
464 unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime;
465 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
466 int preserve_password = -1;
467 #endif
469 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
470 trial, mb->user ? mb->user : "(null)",
471 mb->service ? mb->service : "(null)",
472 mb->port ? " port=" : "",
473 mb->port ? comatose(mb->port) : "",
474 altuserforcache ? " altuserforcache =" : "",
475 altuserforcache ? altuserforcache : ""));
477 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
479 save_in_init = ps_global->in_init_seq;
480 ps_global->in_init_seq = 0;
481 ps_global->no_newmail_check_from_optionally_enter = 1;
483 /* make sure errors are seen */
484 if(ps_global->ttyo)
485 flush_status_messages(0);
487 token = NULL; /* start from scratch */
489 hostlist[0].name = mb->host;
490 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
491 hostlist[0].next = &hostlist[1];
492 hostlist[1].name = mb->orighost;
493 hostlist[1].next = NULL;
495 else
496 hostlist[0].next = NULL;
498 if(hostlist[0].name){
499 dprint((9, "mm_login_oauth2: host=%s\n",
500 hostlist[0].name ? hostlist[0].name : "?"));
501 if(hostlist[0].next && hostlist[1].name){
502 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name));
507 * We check to see if the server we are going to log in to is already
508 * registered. This gives us a list of servers with the same
509 * credentials, so we use the same credentials for all of them.
512 for(registered = 0, oa2list = alpine_oauth2_list;
513 oa2list && oa2list->host != NULL && oa2list->host[0] != NULL;
514 oa2list++){
515 for(i = 0; i < OAUTH2_TOT_EQUIV
516 && oa2list->host[i] != NULL
517 && strucmp(oa2list->host[i], mb->orighost) != 0; i++);
518 if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){
519 registered++;
520 break;
524 if(registered){
525 hostlist2[i = 0].name = mb->host;
526 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost))
527 hostlist2[++i].name = mb->orighost;
529 for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){
530 int k;
531 for(k = 0; k <= i && hostlist2[k].name
532 && strcmp(hostlist2[k].name, oa2list->host[j]); k++);
533 if(k == i + 1)
534 hostlist2[++i].name = oa2list->host[j];
536 hostlist2[i+1].name = NULL;
537 hostlist2[i+1].next = NULL;
538 for(j = i; j >= 0; j--)
539 hostlist2[j].next = &hostlist2[j+1];
543 * We check if we have a refresh token saved somewhere, if so
544 * we use it to get a new access token, otherwise we need to
545 * get an access code so we can get (and save) a refresh token
546 * and use the access token.
548 if(trial == 0L && !altuserforcache){
549 int code;
551 if(*mb->user != '\0')
552 strncpy(user, mb->user, NETMAXUSER);
553 else{
554 flags = OE_APPEND_CURRENT;
555 sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel);
556 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
557 prompt, NULL, NO_HELP, &flags);
559 user[NETMAXUSER-1] = '\0';
561 /* Search for a refresh token that is already loaded ... */
562 if(imap_get_passwd_auth(mm_login_list, &token, user,
563 registered ? hostlist2 : hostlist,
564 (mb->sslflag||mb->tlsflag), OA2NAME)){
565 dprint((9, "mm_login_oauth2: found a refresh token\n"));
566 ps_global->no_newmail_check_from_optionally_enter = 0;
567 ps_global->in_init_seq = save_in_init;
569 #ifdef LOCAL_PASSWD_CACHE
570 /* or see if we have saved one in the local password cache and load it */
571 else if(get_passfile_passwd_auth(ps_global->pinerc, &token,
572 user, registered ? hostlist2 : hostlist,
573 (mb->sslflag||mb->tlsflag), OA2NAME)){
574 imap_set_passwd_auth(&mm_login_list, token, user,
575 hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
576 update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist,
577 (mb->sslflag||mb->tlsflag), OA2NAME);
578 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
579 ps_global->no_newmail_check_from_optionally_enter = 0;
580 ps_global->in_init_seq = save_in_init;
582 if(token && *token) preserve_password = 1; /* resave it, no need to ask */
583 #endif /* LOCAL_PASSWD_CACHE */
585 user[NETMAXUSER-1] = '\0';
587 /* The Old* variables is what c_client knows */
588 OldRefreshToken = login->param[OA2_RefreshToken].value;
589 OldAccessToken = login->access_token;
590 OldExpirationTime = login->expiration;
592 /* The New* variables is what Alpine knows */
593 NewRefreshToken = NewAccessToken = NULL;
594 NewExpirationTime = 0L;
595 ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0;
597 if(token && *token){
598 char *s, *t;
600 s = token;
601 t = strchr(s, PWDAUTHSEP);
602 if(t == NULL)
603 NewRefreshToken = cpystr(s);
604 else {
605 *t++ = '\0';
606 NewRefreshToken = cpystr(s);
607 s = t;
608 t = strchr(s, PWDAUTHSEP);
609 if(t == NULL)
610 NewAccessToken = cpystr(s);
611 else {
612 *t++ = '\0';
613 NewAccessToken = cpystr(s);
614 s = t;
615 NewExpirationTime = strtol(s, &s, 10);
616 if(NewExpirationTime <= 0 || NewExpirationTime <= time(0))
617 NewExpirationTime = 0L;
620 /* check we got good information, and send good information below */
621 if(NewRefreshToken && !*NewRefreshToken)
622 fs_give((void **) &NewRefreshToken);
623 if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken))
624 fs_give((void **) &NewAccessToken);
627 /* Default to saving what we already had saved */
629 SaveRefreshToken = NewRefreshToken;
630 SaveAccessToken = NewAccessToken;
631 SaveExpirationTime = NewExpirationTime;
633 /* Translation of the logic below:
634 * if (c-client has a refresh token
635 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
636 forget the Alpine refresh token;
637 make the Alpine Refresh token = c-client refresh token.;
638 signal that we changed the refresh token;
639 In this situation we do not need to clear up the Alpine access token. This will
640 expire, so we can use it until it expires. We can save the c-client refresh token
641 together with the Alpine Access Token and the expiration date of the Alpine Access
642 Token.
643 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
644 forget the Alpine refresh token;
645 if Alpine has an access token, forget it;
646 reset the expiration time
647 signal that we changed the refresh token; (because the service expired it)
651 if(OldRefreshToken != NULL
652 && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){
653 if(NewRefreshToken) fs_give((void **) &NewRefreshToken);
654 NewRefreshToken = cpystr(OldRefreshToken);
655 ChangeRefreshToken++;
656 SaveRefreshToken = OldRefreshToken;
657 SaveAccessToken = NewAccessToken;
658 SaveExpirationTime = NewExpirationTime;
659 } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){
660 fs_give((void **) &NewRefreshToken);
661 if(NewAccessToken) fs_give((void **) &NewAccessToken);
662 NewExpirationTime = 0L;
663 ChangeRefreshToken++;
664 SaveRefreshToken = NULL;
665 SaveAccessToken = NULL;
666 SaveExpirationTime = 0L;
669 if(OldAccessToken != NULL
670 && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){
671 if(NewAccessToken) fs_give((void **) &NewAccessToken);
672 NewAccessToken = cpystr(OldAccessToken);
673 ChangeAccessToken++;
674 NewExpirationTime = OldExpirationTime;
675 SaveRefreshToken = NewRefreshToken;
676 SaveAccessToken = NewAccessToken;
677 SaveExpirationTime = NewExpirationTime;
680 if(!registered){
681 login->param[OA2_RefreshToken].value = SaveRefreshToken;
682 login->access_token = SaveAccessToken;
683 login->expiration = SaveExpirationTime;
684 } else {
685 oa2list->param[OA2_RefreshToken].value = SaveRefreshToken;
686 oa2list->access_token = SaveAccessToken;
687 oa2list->expiration = SaveExpirationTime;
688 *login = *oa2list; /* load login pointer */
691 if(!ChangeAccessToken && !ChangeRefreshToken)
692 return;
694 /* get ready to save this information. The format will be
695 * RefreshToken \001 LastAccessToken \001 ExpirationTime
696 * (spaces added for clarity, \001 is PWDAUTHSEP)
698 if(token) fs_give((void **) &token);
699 sprintf(tmp, "%lu", SaveExpirationTime);
700 tmp[sizeof(tmp) - 1] = '\0';
701 len = strlen(SaveRefreshToken ? SaveRefreshToken : "")
702 + strlen(SaveAccessToken ? SaveAccessToken : "")
703 + strlen(tmp) + 2;
704 token = fs_get(len + 1);
705 sprintf(token, "%s%c%s%c%lu",
706 SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP,
707 SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP,
708 SaveExpirationTime);
710 /* remember the access information for next time */
711 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
712 imap_set_passwd_auth(&mm_login_list, token,
713 altuserforcache ? altuserforcache : user, hostlist,
714 (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME);
715 #ifdef LOCAL_PASSWD_CACHE
716 /* if requested, remember it on disk for next session */
717 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
718 set_passfile_passwd_auth(ps_global->pinerc, token,
719 altuserforcache ? altuserforcache : user, hostlist,
720 (mb->sslflag||mb->tlsflag),
721 (preserve_password == -1 ? 0
722 : (preserve_password == 0 ? 2 :1)), OA2NAME);
723 #endif /* LOCAL_PASSWD_CACHE */
725 ps_global->no_newmail_check_from_optionally_enter = 0;
728 /*----------------------------------------------------------------------
729 receive notification from IMAP
731 Args: stream -- Mail stream message is relevant to
732 string -- The message text
733 errflg -- Set if it is a serious error
735 Result: message displayed in status line
737 The facility is for general notices, such as connection to server;
738 server shutting down etc... It is used infrequently.
739 ----------------------------------------------------------------------*/
740 void
741 mm_notify(MAILSTREAM *stream, char *string, long int errflg)
743 time_t now;
744 struct tm *tm_now;
746 now = time((time_t *)0);
747 tm_now = localtime(&now);
749 /* be sure to log the message... */
750 #ifdef DEBUG
751 if(ps_global->debug_imap || ps_global->debugmem)
752 dprint((errflg == TCPDEBUG ? 7 : 2,
753 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
754 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
755 tm_now->tm_mon+1, tm_now->tm_mday,
756 (!errflg) ? "babble" :
757 (errflg == ERROR) ? "error" :
758 (errflg == WARN) ? "warning" :
759 (errflg == PARSE) ? "parse" :
760 (errflg == TCPDEBUG) ? "tcp" :
761 (errflg == BYE) ? "bye" : "unknown",
762 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
763 string ? string : "?"));
764 #endif
766 snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s",
767 (stream && stream->mailbox) ? stream->mailbox : "-no folder-",
768 (int) MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70),
769 string);
770 ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols
771 : sizeof(ps_global->last_error)-1] = '\0';
774 * Then either set special bits in the pine struct or
775 * display the message if it's tagged as an "ALERT" or
776 * its errflg > NIL (i.e., WARN, or ERROR)
778 if(errflg == BYE)
780 * We'd like to sp_mark_stream_dead() here but we can't do that because
781 * that might call mail_close and we are already in a c-client callback.
782 * So just set the dead bit and clean it up later.
784 sp_set_dead_stream(stream, 1);
785 else if(!strncmp(string, "[TRYCREATE]", 11))
786 ps_global->try_to_create = 1;
787 else if(!strncmp(string, "[REFERRAL ", 10))
788 ; /* handled in the imap_referral() callback */
789 else if(!strncmp(string, "[ALERT]", 7))
790 q_status_message2(SM_MODAL, 3, 3,
791 _("Alert received while accessing \"%s\": %s"),
792 (stream && stream->mailbox)
793 ? stream->mailbox : "-no folder-",
794 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000),
795 SIZEOF_20KBUF-10000, string));
796 else if(!strncmp(string, "[UNSEEN ", 8)){
797 char *p;
798 long n = 0;
800 for(p = string + 8; isdigit(*p); p++)
801 n = (n * 10) + (*p - '0');
803 sp_set_first_unseen(stream, n);
805 else if(!strncmp(string, "[READ-ONLY]", 11)
806 && !(stream && stream->mailbox && IS_NEWS(stream)))
807 q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s",
808 (stream && stream->mailbox)
809 ? stream->mailbox : "-no folder-",
810 string + 11);
811 else if((errflg && errflg != BYE && errflg != PARSE)
812 && !ps_global->noshow_error
813 && !(errflg == WARN
814 && (ps_global->noshow_warn || (stream && stream->unhealthy))))
815 q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0),
816 3, 6, ps_global->last_error);
820 /*----------------------------------------------------------------------
821 Queue imap log message for display in the message line
823 Args: string -- The message
824 errflg -- flag set to 1 if pertains to an error
826 Result: Message queued for display
828 The c-client/imap reports most of it's status and errors here
829 ---*/
830 void
831 mm_log(char *string, long int errflg)
833 char message[sizeof(ps_global->c_client_error)];
834 char *occurence;
835 int was_capitalized;
836 static char saw_kerberos_init_warning;
837 time_t now;
838 struct tm *tm_now;
840 now = time((time_t *)0);
841 tm_now = localtime(&now);
843 dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? 1 :
844 (errflg == TCPDEBUG) ? 10 : 2,
845 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
846 tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec,
847 tm_now->tm_mon+1, tm_now->tm_mday,
848 (!errflg) ? "babble" :
849 (errflg == ERROR) ? "error" :
850 (errflg == WARN) ? "warning" :
851 (errflg == PARSE) ? "parse" :
852 (errflg == TCPDEBUG) ? "tcp" :
853 (errflg == BYE) ? "bye" : "unknown",
854 string ? string : "?"));
856 if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){
857 ps_global->try_to_create = 1;
858 return;
860 else if(ps_global->try_to_create
861 || !strncmp(string, "[CLOSED]", 8)
862 || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op")))
864 * Don't display if creating new folder OR
865 * warning about a dead stream ...
867 return;
869 strncpy(message, string, sizeof(message));
870 message[sizeof(message) - 1] = '\0';
872 if(errflg == WARN && srchstr(message, "try running kinit") != NULL){
873 if(saw_kerberos_init_warning)
874 return;
876 saw_kerberos_init_warning = 1;
879 /*---- replace all "mailbox" with "folder" ------*/
880 occurence = srchstr(message, "mailbox");
881 while(occurence) {
882 if(!*(occurence+7)
883 || isspace((unsigned char) *(occurence+7))
884 || *(occurence+7) == ':'){
885 was_capitalized = isupper((unsigned char) *occurence);
886 rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder"));
887 if(was_capitalized)
888 *occurence = (errflg == PARSE ? 'A' : 'F');
890 else
891 occurence += 7;
893 occurence = srchstr(occurence, "mailbox");
896 /*---- replace all "GSSAPI" with "Kerberos" ------*/
897 occurence = srchstr(message, "GSSAPI");
898 while(occurence) {
899 if(!*(occurence+6)
900 || isspace((unsigned char) *(occurence+6))
901 || *(occurence+6) == ':')
902 rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos");
903 else
904 occurence += 6;
906 occurence = srchstr(occurence, "GSSAPI");
909 if(errflg == ERROR)
910 ps_global->mm_log_error = 1;
912 if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error))
913 strncpy(ps_global->c_client_error, message,
914 sizeof(ps_global->c_client_error));
916 if(ps_global->noshow_error
917 || (ps_global->noshow_warn && errflg == WARN)
918 || !(errflg == ERROR || errflg == WARN))
919 return; /* Only care about errors; don't print when asked not to */
921 /*---- Display the message ------*/
922 q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER,
923 3, 5, message);
924 strncpy(ps_global->last_error, message, sizeof(ps_global->last_error));
925 ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0';
928 void
929 mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial,
930 char *method, char *usethisprompt, char *altuserforcache)
932 if(method == NULL)
933 return;
934 if(strucmp(method, OA2NAME) == 0 || strucmp(method, BEARERNAME) == 0)
935 mm_login_oauth2(mb, user, method, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache);
938 void
939 mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial,
940 char *usethisprompt, char *altuserforcache)
942 char tmp[MAILTMPLEN];
943 char prompt[1000], *last;
944 char port[20], non_def_port[20], insecure[20];
945 char defuser[NETMAXUSER];
946 char hostleadin[80], hostname[200], defubuf[200];
947 char logleadin[80], pwleadin[50];
948 char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN];
949 /* TRANSLATORS: when logging in, this text is added to the prompt to show
950 that the password will be sent unencrypted over the network. This is
951 just a warning message that gets added parenthetically when the user
952 is asked for a password. */
953 char *insec = _(" (INSECURE)");
954 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
955 after having already failed at least once. */
956 char *retry = _("Retrying - ");
957 /* TRANSLATORS: A label for the hostname that the user is logging in on */
958 char *hostlabel = _("HOST");
959 /* TRANSLATORS: user is logging in as a particular user (a particular
960 login name), this is just labelling that user name. */
961 char *userlabel = _("USER");
962 STRLIST_S hostlist[2];
963 HelpType help ;
964 int len, rc, q_line, flags;
965 int oespace, avail, need, save_dont_use;
966 int save_in_init;
967 struct servent *sv;
968 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
969 int preserve_password = -1;
970 #endif
972 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
973 trial, mb->user ? mb->user : "(null)",
974 mb->service ? mb->service : "(null)",
975 mb->port ? " port=" : "",
976 mb->port ? comatose(mb->port) : "",
977 altuserforcache ? " altuserforcache =" : "",
978 altuserforcache ? altuserforcache : ""));
979 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
981 save_in_init = ps_global->in_init_seq;
982 ps_global->in_init_seq = 0;
983 ps_global->no_newmail_check_from_optionally_enter = 1;
985 /* make sure errors are seen */
986 if(ps_global->ttyo)
987 flush_status_messages(0);
990 * Add port number to hostname if going through a tunnel or something
992 non_def_port[0] = '\0';
993 if(mb->port && mb->service &&
994 (sv = getservbyname(mb->service, "tcp")) &&
995 (mb->port != ntohs(sv->s_port))){
996 snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port);
997 non_def_port[sizeof(non_def_port)-1] = '\0';
998 dprint((9, "mm_login: using non-default port=%s\n",
999 non_def_port ? non_def_port : "?"));
1003 * set up host list for sybil servers...
1005 if(*non_def_port){
1006 strncpy(hostlist0, mb->host, sizeof(hostlist0)-1);
1007 hostlist0[sizeof(hostlist0)-1] = '\0';
1008 strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1);
1009 hostlist0[sizeof(hostlist0)-1] = '\0';
1010 hostlist[0].name = hostlist0;
1011 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1012 strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1);
1013 hostlist1[sizeof(hostlist1)-1] = '\0';
1014 strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1);
1015 hostlist1[sizeof(hostlist1)-1] = '\0';
1016 hostlist[0].next = &hostlist[1];
1017 hostlist[1].name = hostlist1;
1018 hostlist[1].next = NULL;
1020 else
1021 hostlist[0].next = NULL;
1023 else{
1024 hostlist[0].name = mb->host;
1025 if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){
1026 hostlist[0].next = &hostlist[1];
1027 hostlist[1].name = mb->orighost;
1028 hostlist[1].next = NULL;
1030 else
1031 hostlist[0].next = NULL;
1034 if(hostlist[0].name){
1035 dprint((9, "mm_login: host=%s\n",
1036 hostlist[0].name ? hostlist[0].name : "?"));
1037 if(hostlist[0].next && hostlist[1].name){
1038 dprint((9, "mm_login: orighost=%s\n", hostlist[1].name));
1043 * Initialize user name with either
1044 * 1) /user= value in the stream being logged into,
1045 * or 2) the user name we're running under.
1047 * Note that VAR_USER_ID is not yet initialized if this login is
1048 * the one to access the remote config file. In that case, the user
1049 * can supply the username in the config file name with /user=.
1051 if(trial == 0L && !altuserforcache){
1052 strncpy(user, (*mb->user) ? mb->user :
1053 ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "",
1054 NETMAXUSER);
1055 user[NETMAXUSER-1] = '\0';
1057 /* try last working password associated with this host. */
1058 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1059 (mb->sslflag||mb->tlsflag))){
1060 dprint((9, "mm_login: found a password to try\n"));
1061 ps_global->no_newmail_check_from_optionally_enter = 0;
1062 ps_global->in_init_seq = save_in_init;
1063 return;
1066 #ifdef LOCAL_PASSWD_CACHE
1067 /* check to see if there's a password left over from last session */
1068 if(get_passfile_passwd(ps_global->pinerc, pwd,
1069 user, hostlist, (mb->sslflag||mb->tlsflag))){
1070 imap_set_passwd(&mm_login_list, *pwd, user,
1071 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1072 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1073 (mb->sslflag||mb->tlsflag));
1074 dprint((9, "mm_login: found a password in passfile to try\n"));
1075 ps_global->no_newmail_check_from_optionally_enter = 0;
1076 ps_global->in_init_seq = save_in_init;
1077 return;
1079 #endif /* LOCAL_PASSWD_CACHE */
1082 * If no explicit user name supplied and we've not logged in
1083 * with our local user name, see if we've visited this
1084 * host before as someone else.
1086 if(!*mb->user &&
1087 ((last = imap_get_user(mm_login_list, hostlist))
1088 #ifdef LOCAL_PASSWD_CACHE
1090 (last = get_passfile_user(ps_global->pinerc, hostlist))
1091 #endif /* LOCAL_PASSWD_CACHE */
1093 strncpy(user, last, NETMAXUSER);
1094 user[NETMAXUSER-1] = '\0';
1095 dprint((9, "mm_login: found user=%s\n",
1096 user ? user : "?"));
1098 /* try last working password associated with this host/user. */
1099 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1100 (mb->sslflag||mb->tlsflag))){
1101 dprint((9,
1102 "mm_login: found a password for user=%s to try\n",
1103 user ? user : "?"));
1104 ps_global->no_newmail_check_from_optionally_enter = 0;
1105 ps_global->in_init_seq = save_in_init;
1106 return;
1109 #ifdef LOCAL_PASSWD_CACHE
1110 /* check to see if there's a password left over from last session */
1111 if(get_passfile_passwd(ps_global->pinerc, pwd,
1112 user, hostlist, (mb->sslflag||mb->tlsflag))){
1113 imap_set_passwd(&mm_login_list, *pwd, user,
1114 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1115 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1116 (mb->sslflag||mb->tlsflag));
1117 dprint((9,
1118 "mm_login: found a password for user=%s in passfile to try\n",
1119 user ? user : "?"));
1120 ps_global->no_newmail_check_from_optionally_enter = 0;
1121 ps_global->in_init_seq = save_in_init;
1122 return;
1124 #endif /* LOCAL_PASSWD_CACHE */
1127 #if !defined(DOS) && !defined(OS2)
1128 if(!*mb->user && !*user &&
1129 (last = (ps_global->ui.login && ps_global->ui.login[0])
1130 ? ps_global->ui.login : NULL)
1132 strncpy(user, last, NETMAXUSER);
1133 user[NETMAXUSER-1] = '\0';
1134 dprint((9, "mm_login: found user=%s\n",
1135 user ? user : "?"));
1137 /* try last working password associated with this host. */
1138 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1139 (mb->sslflag||mb->tlsflag))){
1140 dprint((9, "mm_login:ui: found a password to try\n"));
1141 ps_global->no_newmail_check_from_optionally_enter = 0;
1142 ps_global->in_init_seq = save_in_init;
1143 return;
1146 #ifdef LOCAL_PASSWD_CACHE
1147 /* check to see if there's a password left over from last session */
1148 if(get_passfile_passwd(ps_global->pinerc, pwd,
1149 user, hostlist, (mb->sslflag||mb->tlsflag))){
1150 imap_set_passwd(&mm_login_list, *pwd, user,
1151 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1152 update_passfile_hostlist(ps_global->pinerc, user, hostlist,
1153 (mb->sslflag||mb->tlsflag));
1154 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1155 ps_global->no_newmail_check_from_optionally_enter = 0;
1156 ps_global->in_init_seq = save_in_init;
1157 return;
1159 #endif /* LOCAL_PASSWD_CACHE */
1161 #endif
1164 user[NETMAXUSER-1] = '\0';
1166 if(trial == 0)
1167 retry = "";
1170 * Even if we have a user now, user gets a chance to change it.
1172 ps_global->mangled_footer = 1;
1173 if(!*mb->user && !altuserforcache){
1175 help = NO_HELP;
1178 * Instead of offering user with a value that the user can edit,
1179 * we offer [user] as a default so that the user can type CR to
1180 * use it. Otherwise, the user has to type in whole name.
1182 strncpy(defuser, user, sizeof(defuser)-1);
1183 defuser[sizeof(defuser)-1] = '\0';
1184 user[0] = '\0';
1187 * Need space for "Retrying - "
1188 * "+ HOST: "
1189 * hostname
1190 * " (INSECURE)"
1191 * ENTER LOGIN NAME
1192 * " [defuser] : "
1193 * about 15 chars for input
1196 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1197 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1198 hostleadin[sizeof(hostleadin)-1] = '\0';
1200 strncpy(hostname, mb->host, sizeof(hostname)-1);
1201 hostname[sizeof(hostname)-1] = '\0';
1204 * Add port number to hostname if going through a tunnel or something
1206 if(*non_def_port)
1207 strncpy(port, non_def_port, sizeof(port));
1208 else
1209 port[0] = '\0';
1211 insecure[0] = '\0';
1212 /* if not encrypted and SSL/TLS is supported */
1213 if(!(mb->sslflag||mb->tlsflag) &&
1214 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1215 strncpy(insecure, insec, sizeof(insecure));
1217 /* TRANSLATORS: user is being asked to type in their login name */
1218 snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME"));
1220 snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "",
1221 (*defuser) ? defuser : "",
1222 (*defuser) ? "]" : "");
1223 defubuf[sizeof(defubuf)-1] = '\0';
1224 /* space reserved after prompt */
1225 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1227 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1228 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1229 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace;
1231 /* If we're retrying cut the hostname back to the first word. */
1232 if(avail < need && trial > 0){
1233 char *p;
1235 len = strlen(hostname);
1236 if((p = strchr(hostname, '.')) != NULL){
1237 *p = '\0';
1238 need -= (len - strlen(hostname));
1242 if(avail < need){
1243 need -= utf8_width(retry);
1244 retry = "";
1246 if(avail < need){
1248 /* reduce length of logleadin */
1249 len = utf8_width(logleadin);
1250 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1251 longer version doesn't fit on screen */
1252 snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN"));
1253 need -= (len - utf8_width(logleadin));
1255 if(avail < need){
1256 /* get two spaces from hostleadin */
1257 len = utf8_width(hostleadin);
1258 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1259 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1260 hostleadin[sizeof(hostleadin)-1] = '\0';
1261 need -= (len - utf8_width(hostleadin));
1263 /* get rid of port */
1264 if(avail < need && strlen(port) > 0){
1265 need -= strlen(port);
1266 port[0] = '\0';
1269 if(avail < need){
1270 int reduce_to;
1273 * Reduce space for hostname. Best we can do is 6 chars
1274 * with hos...
1276 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1277 len = strlen(hostname);
1278 strncpy(hostname+reduce_to-3, "...", 4);
1279 need -= (len - strlen(hostname));
1281 if(avail < need && strlen(insecure) > 0){
1282 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1283 need -= 3;
1284 insecure[strlen(insecure)-4] = ')';
1285 insecure[strlen(insecure)-3] = '\0';
1287 else{
1288 need -= utf8_width(insecure);
1289 insecure[0] = '\0';
1293 if(avail < need){
1294 if(strlen(defubuf) > 3){
1295 len = strlen(defubuf);
1296 strncpy(defubuf, " [..] :", 9);
1297 need -= (len - strlen(defubuf));
1300 if(avail < need)
1301 strncpy(defubuf, ":", 2);
1304 * If it still doesn't fit, optionally_enter gets
1305 * to worry about it.
1313 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s",
1314 retry, hostleadin, hostname, port, insecure, logleadin, defubuf);
1315 prompt[sizeof(prompt)-1] = '\0';
1317 while(1) {
1318 if(ps_global->ttyo)
1319 mm_login_alt_cue(mb);
1321 flags = OE_APPEND_CURRENT;
1322 save_dont_use = ps_global->dont_use_init_cmds;
1323 ps_global->dont_use_init_cmds = 1;
1324 #ifdef _WINDOWS
1325 if(!*user && *defuser){
1326 strncpy(user, defuser, NETMAXUSER);
1327 user[NETMAXUSER-1] = '\0';
1330 rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD,
1331 #ifdef LOCAL_PASSWD_CACHE
1332 is_using_passfile() ? 1 :
1333 #endif /* LOCAL_PASSWD_CACHE */
1334 0, 0, &preserve_password);
1335 ps_global->dont_use_init_cmds = save_dont_use;
1336 if(rc == 0 && *user && *pwd)
1337 goto nopwpmt;
1338 #else /* !_WINDOWS */
1339 rc = optionally_enter(user, q_line, 0, NETMAXUSER,
1340 prompt, NULL, help, &flags);
1341 #endif /* !_WINDOWS */
1342 ps_global->dont_use_init_cmds = save_dont_use;
1344 if(rc == 3) {
1345 help = help == NO_HELP ? h_oe_login : NO_HELP;
1346 continue;
1349 /* default */
1350 if(rc == 0 && !*user){
1351 strncpy(user, defuser, NETMAXUSER);
1352 user[NETMAXUSER-1] = '\0';
1355 if(rc != 4)
1356 break;
1359 if(rc == 1 || !user[0]) {
1360 ps_global->user_says_cancel = (rc == 1);
1361 user[0] = '\0';
1364 else{
1365 strncpy(user, mb->user, NETMAXUSER);
1366 user[NETMAXUSER-1] = '\0';
1369 user[NETMAXUSER-1] = '\0';
1371 if(!(user[0] || altuserforcache)){
1372 ps_global->no_newmail_check_from_optionally_enter = 0;
1373 ps_global->in_init_seq = save_in_init;
1374 return;
1378 * Now that we have a user, we can check in the cache again to see
1379 * if there is a password there. Try last working password associated
1380 * with this host and user.
1382 if(trial == 0L && !*mb->user && !altuserforcache){
1383 if(imap_get_passwd(mm_login_list, pwd, user, hostlist,
1384 (mb->sslflag||mb->tlsflag))){
1385 ps_global->no_newmail_check_from_optionally_enter = 0;
1386 ps_global->in_init_seq = save_in_init;
1387 return;
1390 #ifdef LOCAL_PASSWD_CACHE
1391 if(get_passfile_passwd(ps_global->pinerc, pwd,
1392 user, hostlist, (mb->sslflag||mb->tlsflag))){
1393 imap_set_passwd(&mm_login_list, *pwd, user,
1394 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1395 ps_global->no_newmail_check_from_optionally_enter = 0;
1396 ps_global->in_init_seq = save_in_init;
1397 return;
1399 #endif /* LOCAL_PASSWD_CACHE */
1401 else if(trial == 0 && altuserforcache){
1402 if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist,
1403 (mb->sslflag||mb->tlsflag))){
1404 ps_global->no_newmail_check_from_optionally_enter = 0;
1405 ps_global->in_init_seq = save_in_init;
1406 return;
1409 #ifdef LOCAL_PASSWD_CACHE
1410 if(get_passfile_passwd(ps_global->pinerc, pwd,
1411 altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){
1412 imap_set_passwd(&mm_login_list, *pwd, altuserforcache,
1413 hostlist, (mb->sslflag||mb->tlsflag), 0, 0);
1414 ps_global->no_newmail_check_from_optionally_enter = 0;
1415 ps_global->in_init_seq = save_in_init;
1416 return;
1418 #endif /* LOCAL_PASSWD_CACHE */
1422 * Didn't find password in cache or this isn't the first try. Ask user.
1424 help = NO_HELP;
1427 * Need space for "Retrying - "
1428 * "+ HOST: "
1429 * hostname
1430 * " (INSECURE) "
1431 * " USER: "
1432 * user
1433 * " ENTER PASSWORD: "
1434 * about 15 chars for input
1437 snprintf(hostleadin, sizeof(hostleadin), "%s%s: ",
1438 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel);
1440 strncpy(hostname, mb->host, sizeof(hostname)-1);
1441 hostname[sizeof(hostname)-1] = '\0';
1444 * Add port number to hostname if going through a tunnel or something
1446 if(*non_def_port)
1447 strncpy(port, non_def_port, sizeof(port));
1448 else
1449 port[0] = '\0';
1451 insecure[0] = '\0';
1453 /* if not encrypted and SSL/TLS is supported */
1454 if(!(mb->sslflag||mb->tlsflag) &&
1455 mail_parameters(NIL, GET_SSLDRIVER, NIL))
1456 strncpy(insecure, insec, sizeof(insecure));
1458 if(usethisprompt){
1459 strncpy(logleadin, usethisprompt, sizeof(logleadin));
1460 logleadin[sizeof(logleadin)-1] = '\0';
1461 defubuf[0] = '\0';
1462 user[0] = '\0';
1464 else{
1465 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1467 strncpy(defubuf, user, sizeof(defubuf)-1);
1468 defubuf[sizeof(defubuf)-1] = '\0';
1471 /* TRANSLATORS: user is being asked to type in their password */
1472 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD"));
1474 /* space reserved after prompt */
1475 oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6);
1477 avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
1478 need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) +
1479 utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) +
1480 utf8_width(pwleadin) + oespace;
1482 if(avail < need && trial > 0){
1483 char *p;
1485 len = strlen(hostname);
1486 if((p = strchr(hostname, '.')) != NULL){
1487 *p = '\0';
1488 need -= (len - strlen(hostname));
1492 if(avail < need){
1493 need -= utf8_width(retry);
1494 retry = "";
1496 if(avail < need){
1498 if(!usethisprompt){
1499 snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel);
1500 need--;
1503 rplstr(pwleadin, sizeof(pwleadin), 1, "");
1504 need--;
1506 if(avail < need){
1507 /* get two spaces from hostleadin */
1508 len = utf8_width(hostleadin);
1509 snprintf(hostleadin, sizeof(hostleadin), "%s%s:",
1510 (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel);
1511 hostleadin[sizeof(hostleadin)-1] = '\0';
1512 need -= (len - utf8_width(hostleadin));
1514 /* get rid of port */
1515 if(avail < need && strlen(port) > 0){
1516 need -= strlen(port);
1517 port[0] = '\0';
1520 if(avail < need){
1521 len = utf8_width(pwleadin);
1522 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
1523 snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD"));
1524 need -= (len - utf8_width(pwleadin));
1528 if(avail < need){
1529 int reduce_to;
1532 * Reduce space for hostname. Best we can do is 6 chars
1533 * with hos...
1535 reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6;
1536 len = strlen(hostname);
1537 strncpy(hostname+reduce_to-3, "...", 4);
1538 need -= (len - strlen(hostname));
1540 if(avail < need && strlen(insecure) > 0){
1541 if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){
1542 need -= 3;
1543 insecure[strlen(insecure)-4] = ')';
1544 insecure[strlen(insecure)-3] = '\0';
1546 else{
1547 need -= utf8_width(insecure);
1548 insecure[0] = '\0';
1552 if(avail < need){
1553 len = utf8_width(logleadin);
1554 strncpy(logleadin, " ", sizeof(logleadin));
1555 logleadin[sizeof(logleadin)-1] = '\0';
1556 need -= (len - utf8_width(logleadin));
1558 if(avail < need){
1559 reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0;
1560 if(reduce_to)
1561 strncpy(defubuf+reduce_to-3, "...", 4);
1562 else
1563 defubuf[0] = '\0';
1570 snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s",
1571 retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin);
1572 prompt[sizeof(prompt)-1] = '\0';
1574 tmp[0] = '\0';
1575 while(1) {
1576 if(ps_global->ttyo)
1577 mm_login_alt_cue(mb);
1579 save_dont_use = ps_global->dont_use_init_cmds;
1580 ps_global->dont_use_init_cmds = 1;
1581 flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD;
1582 flags |= OE_KEEP_TRAILING_SPACE;
1583 #ifdef _WINDOWS
1584 rc = os_login_dialog(mb, user, NETMAXUSER, tmp, NETMAXPASSWD, 0, 1,
1585 &preserve_password);
1586 #else /* !_WINDOWS */
1587 rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD,
1588 prompt, NULL, help, &flags);
1589 #endif /* !_WINDOWS */
1590 if(rc != 1) *pwd = cpystr(tmp);
1591 ps_global->dont_use_init_cmds = save_dont_use;
1593 if(rc == 3) {
1594 help = help == NO_HELP ? h_oe_passwd : NO_HELP;
1596 else if(rc == 4){
1598 else
1599 break;
1602 if(rc == 1 || !tmp[0]) {
1603 ps_global->user_says_cancel = (rc == 1);
1604 user[0] = '\0';
1605 ps_global->no_newmail_check_from_optionally_enter = 0;
1606 ps_global->in_init_seq = save_in_init;
1607 return;
1610 #ifdef _WINDOWS
1611 nopwpmt:
1612 #endif
1613 /* remember the password for next time */
1614 if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global))
1615 imap_set_passwd(&mm_login_list, *pwd,
1616 altuserforcache ? altuserforcache : user, hostlist,
1617 (mb->sslflag||mb->tlsflag), 0, 0);
1618 #ifdef LOCAL_PASSWD_CACHE
1619 /* if requested, remember it on disk for next session */
1620 if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
1621 set_passfile_passwd(ps_global->pinerc, *pwd,
1622 altuserforcache ? altuserforcache : user, hostlist,
1623 (mb->sslflag||mb->tlsflag),
1624 (preserve_password == -1 ? 0
1625 : (preserve_password == 0 ? 2 :1)));
1626 #endif /* LOCAL_PASSWD_CACHE */
1628 ps_global->no_newmail_check_from_optionally_enter = 0;
1632 void
1633 mm_login_alt_cue(NETMBX *mb)
1635 if(ps_global->ttyo){
1636 COLOR_PAIR *lastc;
1638 lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR,
1639 ps_global->VAR_TITLE_BACK_COLOR,
1640 PSC_REV | PSC_RET);
1642 mark_titlebar_dirty();
1643 PutLine0(0, ps_global->ttyo->screen_cols - 1,
1644 (mb->sslflag||mb->tlsflag) ? "+" : " ");
1646 if(lastc){
1647 (void)pico_set_colorp(lastc, PSC_NONE);
1648 free_color_pair(&lastc);
1651 fflush(stdout);
1656 /*----------------------------------------------------------------------
1657 Receive notification of an error writing to disk
1659 Args: stream -- The stream the error occurred on
1660 errcode -- The system error code (errno)
1661 serious -- Flag indicating error is serious (mail may be lost)
1663 Result: If error is non serious, the stream is marked as having an error
1664 and deletes are disallowed until error clears
1665 If error is serious this goes modal, allowing the user to retry
1666 or get a shell escape to fix the condition. When the condition is
1667 serious it means that mail existing in the mailbox will be lost
1668 if Pine exits without writing, so we try to induce the user to
1669 fix the error, go get someone that can fix the error, or whatever
1670 and don't provide an easy way out.
1671 ----*/
1672 long
1673 mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious)
1675 int i, j;
1676 char *p, *q, *s;
1677 static ESCKEY_S de_opts[] = {
1678 {'r', 'r', "R", "Retry"},
1679 {'f', 'f', "F", "FileBrowser"},
1680 {'s', 's', "S", "ShellPrompt"},
1681 {-1, 0, NULL, NULL}
1683 #define DE_COLS (ps_global->ttyo->screen_cols)
1684 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
1686 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
1687 #define DE_PMT \
1688 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
1689 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
1690 #define DE_STR2 \
1691 "The reported error number is %s. The last reported mail error was:"
1692 static char *de_msg[] = {
1693 "Please try to correct the error preventing Alpine from saving your",
1694 "mail folder. For example if the disk is out of space try removing",
1695 "unneeded files. You might also contact your system administrator.",
1697 "Both Alpine's File Browser and an option to enter the system's",
1698 "command prompt are offered to aid in fixing the problem. When",
1699 "you believe the problem is resolved, choose the \"Retry\" option.",
1700 "Be aware that messages may be lost or this folder left in an",
1701 "inaccessible condition if you exit or kill Alpine before the problem",
1702 "is resolved.",
1703 NULL};
1704 static char *de_shell_msg[] = {
1705 "\n\nPlease attempt to correct the error preventing saving of the",
1706 "mail folder. If you do not know how to correct the problem, contact",
1707 "your system administrator. To return to Alpine, type \"exit\".",
1708 NULL};
1710 dprint((0,
1711 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
1712 DE_FOLDER(stream), errcode, serious ? "" : "not "));
1713 dprint((0, "***** message: \"%s\"\n\n",
1714 ps_global->last_error ? ps_global->last_error : "?"));
1716 if(!serious) {
1717 sp_set_io_error_on_stream(stream, 1);
1718 return (1) ;
1721 while(1){
1722 /* replace pine's body display with screen full of explanatory text */
1723 ClearLine(2);
1724 PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1)
1725 - strlen(DE_FOLDER(stream)))/2, 0),
1726 DE_STR1, DE_FOLDER(stream));
1727 ClearLine(3);
1728 PutLine1(3, 4, DE_STR2, long2string(errcode));
1730 PutLine0(4, 0, " \"");
1731 removing_leading_white_space(ps_global->last_error);
1732 for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){
1733 for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++)
1734 if(isspace((unsigned char)*q))
1735 s = q;
1737 if(*q && s)
1738 q = s;
1740 while(p < q)
1741 Writechar(*p++, 0);
1743 if(*(p = q)){
1744 ClearLine(++i);
1745 PutLine0(i, 0, " ");
1746 while(*p && isspace((unsigned char)*p))
1747 p++;
1749 else{
1750 Writechar('\"', 0);
1751 CleartoEOLN();
1752 break;
1756 ClearLine(++i);
1757 for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){
1758 ClearLine(i);
1759 PutLine0(i, 0, " ");
1760 Write_to_screen(de_msg[i-j]);
1763 while(i < DE_LINE)
1764 ClearLine(i++);
1766 switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts,
1767 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){
1768 case 'r' : /* Retry! */
1769 ps_global->mangled_screen = 1;
1770 return(0L);
1772 case 'f' : /* File Browser */
1774 char full_filename[MAXPATH+1], filename[MAXPATH+1];
1776 filename[0] = '\0';
1777 build_path(full_filename, ps_global->home_dir, filename,
1778 sizeof(full_filename));
1779 file_lister("DISK ERROR", full_filename, sizeof(full_filename),
1780 filename, sizeof(filename), FALSE, FB_SAVE);
1783 break;
1785 case 's' :
1786 EndInverse();
1787 end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0);
1788 end_tty_driver(ps_global);
1789 for(i = 0; de_shell_msg[i]; i++)
1790 puts(de_shell_msg[i]);
1793 * Don't use our piping mechanism to spawn a subshell here
1794 * since it will the server (thus reentering c-client).
1795 * Bad thing to do.
1797 #ifdef _WINDOWS
1798 #else
1799 system("csh");
1800 #endif
1801 init_tty_driver(ps_global);
1802 init_keyboard(F_ON(F_USE_FK,ps_global));
1803 break;
1806 if(ps_global->redrawer)
1807 (*ps_global->redrawer)();
1812 long
1813 pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host)
1815 long rv = 1L;
1816 char pmt[128];
1818 #ifdef _WINDOWS
1819 mswin_killsplash();
1820 #endif
1822 if(elapsed >= (long)ps_global->tcp_query_timeout){
1823 snprintf(pmt, sizeof(pmt),
1824 _("No reply in %s seconds from server %s. Break connection"),
1825 long2string(elapsed), host);
1826 pmt[sizeof(pmt)-1] = '\0';
1827 if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){
1828 ps_global->user_says_cancel = 1;
1829 return(0L);
1833 ps_global->tcptimeout = 0;
1834 return(rv);
1839 * -------------------------------------------------------------
1840 * These are declared in pith/imap.h as mandatory to implement.
1841 * -------------------------------------------------------------
1846 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
1848 long
1849 pine_tcptimeout(long int elapsed, long int sincelast, char *host)
1851 long rv = 1L; /* keep trying by default */
1852 unsigned long ch;
1854 ps_global->tcptimeout = 1;
1855 #ifdef DEBUG
1856 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
1857 long2string(elapsed), host));
1858 if(debugfile)
1859 fflush(debugfile);
1860 #endif
1862 #ifdef _WINDOWS
1863 mswin_killsplash();
1864 #endif
1866 if(ps_global->noshow_timeout)
1867 return(rv);
1869 if(ps_global->can_interrupt
1870 && ps_global->close_connection_timeout > 0L
1871 && elapsed >= (long)ps_global->tcp_query_timeout
1872 && elapsed >= (long)ps_global->close_connection_timeout){
1873 ps_global->can_interrupt = 0; /* do not return here */
1874 ps_global->read_bail = 0;
1875 ps_global->user_says_cancel = 1;
1876 return 0;
1879 if(!ps_global->ttyo)
1880 return(pine_tcptimeout_noscreen(elapsed, sincelast, host));
1882 suspend_busy_cue();
1885 * Prompt after a minute (since by then things are probably really bad)
1886 * A prompt timeout means "keep trying"...
1888 if(elapsed >= (long)ps_global->tcp_query_timeout){
1889 int clear_inverse;
1891 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
1892 if((clear_inverse = !InverseState()) != 0)
1893 StartInverse();
1895 Writechar(BELL, 0);
1897 PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0,
1898 _("No reply in %s seconds from server %s. Break connection?"),
1899 long2string(elapsed), host);
1900 CleartoEOLN();
1901 fflush(stdout);
1902 flush_input();
1903 ch = read_char(7);
1904 if(ps_global->read_bail || ch == 'y' || ch == 'Y'){
1905 ps_global->read_bail = 0;
1906 ps_global->user_says_cancel = 1;
1907 rv = 0L;
1910 if(clear_inverse)
1911 EndInverse();
1913 ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global));
1916 if(rv == 1L){ /* just warn 'em something's up */
1917 q_status_message2(SM_ORDER, 0, 0,
1918 _("No reply in %s seconds from server %s. Still Waiting..."),
1919 long2string(elapsed), host);
1920 flush_status_messages(0); /* make sure it's seen */
1923 mark_status_dirty(); /* make sure it gets cleared */
1925 resume_busy_cue((rv == 1) ? 3 : 0);
1926 ps_global->tcptimeout = 0;
1928 return(rv);
1931 QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota)
1933 QUOTALIST *cquota = NULL;
1935 if(pquota){
1936 cquota = mail_newquotalist();
1937 if (pquota->name && *pquota->name)
1938 cquota->name = cpystr(pquota->name);
1939 cquota->usage = pquota->usage;
1940 cquota->limit = pquota->limit;
1941 if (pquota->next)
1942 cquota->next = pine_quotalist_copy(pquota->next);
1944 return cquota;
1948 /* c-client callback to handle quota */
1950 void
1951 pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota)
1953 ps_global->quota = pine_quotalist_copy (pquota);
1957 * C-client callback to handle SSL/TLS certificate validation failures
1959 * Returning 0 means error becomes fatal
1960 * Non-zero means certificate problem is ignored and SSL session is
1961 * established
1963 * We remember the answer and won't re-ask for subsequent open attempts to
1964 * the same hostname.
1966 long
1967 pine_sslcertquery(char *reason, char *host, char *cert)
1969 char tmp[500];
1970 char *unknown = "<unknown>";
1971 long rv = 0L;
1972 STRLIST_S hostlist;
1973 int ok_novalidate = 0, warned = 0;
1975 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
1976 host ? host : "?", reason ? reason : "?",
1977 cert ? cert : "?"));
1979 hostlist.name = host ? host : "";
1980 hostlist.next = NULL;
1983 * See if we've been asked about this host before.
1985 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
1986 /* we were asked before, did we say Yes? */
1987 if(ok_novalidate)
1988 rv++;
1990 if(rv){
1991 dprint((5,
1992 "sslcertificatequery: approved automatically\n"));
1993 return(rv);
1996 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
1999 if(ps_global->ttyo){
2000 SCROLL_S sargs;
2001 STORE_S *in_store, *out_store;
2002 gf_io_t pc, gc;
2003 HANDLE_S *handles = NULL;
2004 int the_answer = 'n';
2006 if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) ||
2007 !(out_store = so_get(CharStar, NULL, EDIT_ACCESS)))
2008 goto try_wantto;
2010 so_puts(in_store, "<HTML><P>");
2011 so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server"));
2013 so_puts(in_store, "<P><CENTER>");
2014 so_puts(in_store, host ? host : unknown);
2015 so_puts(in_store, "</CENTER>");
2017 so_puts(in_store, "<P>");
2018 so_puts(in_store, _("The reason for the failure was"));
2020 /* squirrel away details */
2021 if(details_host)
2022 fs_give((void **)&details_host);
2023 if(details_reason)
2024 fs_give((void **)&details_reason);
2025 if(details_cert)
2026 fs_give((void **)&details_cert);
2028 details_host = cpystr(host ? host : unknown);
2029 details_reason = cpystr(reason ? reason : unknown);
2030 details_cert = cpystr(cert ? cert : unknown);
2032 so_puts(in_store, "<P><CENTER>");
2033 snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2034 reason ? reason : unknown);
2035 tmp[sizeof(tmp)-1] = '\0';
2037 so_puts(in_store, tmp);
2038 so_puts(in_store, "</CENTER>");
2040 so_puts(in_store, "<P>");
2041 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."));
2043 so_puts(in_store, "<P>");
2044 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"));
2046 so_puts(in_store, "<P><CENTER>");
2047 so_puts(in_store, "/novalidate-cert");
2048 so_puts(in_store, "</CENTER>");
2050 so_puts(in_store, "<P>");
2051 so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2053 so_puts(in_store, "<P><CENTER>");
2054 so_puts(in_store, host ? host : unknown);
2055 so_puts(in_store, "</CENTER>");
2057 so_puts(in_store, "<P>");
2058 so_puts(in_store, _("in your configuration, replace those characters with"));
2060 so_puts(in_store, "<P><CENTER>");
2061 so_puts(in_store, host ? host : unknown);
2062 so_puts(in_store, "/novalidate-cert");
2063 so_puts(in_store, "</CENTER>");
2065 so_puts(in_store, "<P>");
2066 so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2068 so_seek(in_store, 0L, 0);
2069 init_handles(&handles);
2070 gf_filter_init();
2071 gf_link_filter(gf_html2plain,
2072 gf_html2plain_opt(NULL,
2073 ps_global->ttyo->screen_cols, non_messageview_margin(),
2074 &handles, NULL, GFHP_LOCAL_HANDLES));
2075 gf_set_so_readc(&gc, in_store);
2076 gf_set_so_writec(&pc, out_store);
2077 gf_pipe(gc, pc);
2078 gf_clear_so_writec(out_store);
2079 gf_clear_so_readc(in_store);
2081 memset(&sargs, 0, sizeof(SCROLL_S));
2082 sargs.text.handles = handles;
2083 sargs.text.text = so_text(out_store);
2084 sargs.text.src = CharStar;
2085 sargs.text.desc = _("help text");
2086 sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2087 sargs.proc.tool = answer_cert_failure;
2088 sargs.proc.data.p = (void *)&the_answer;
2089 sargs.keys.menu = &ans_certquery_keymenu;
2090 /* don't want to re-enter c-client */
2091 sargs.quell_newmail = 1;
2092 setbitmap(sargs.keys.bitmap);
2093 sargs.help.text = h_tls_validation_failure;
2094 sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
2096 scrolltool(&sargs);
2098 if(the_answer == 'y')
2099 rv++;
2100 else if(the_answer == 'n')
2101 ps_global->user_says_cancel = 1;
2103 ps_global->mangled_screen = 1;
2104 ps_global->painted_body_on_startup = 0;
2105 ps_global->painted_footer_on_startup = 0;
2106 so_give(&in_store);
2107 so_give(&out_store);
2108 free_handles(&handles);
2109 if(details_host)
2110 fs_give((void **)&details_host);
2111 if(details_reason)
2112 fs_give((void **)&details_reason);
2113 if(details_cert)
2114 fs_give((void **)&details_cert);
2116 else{
2118 * If screen hasn't been initialized yet, use want_to.
2120 try_wantto:
2121 memset((void *)tmp, 0, sizeof(tmp));
2122 strncpy(tmp,
2123 reason ? reason : _("SSL/TLS certificate validation failure"),
2124 sizeof(tmp));
2125 tmp[sizeof(tmp)-1] = '\0';
2126 strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
2128 if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
2129 rv++;
2132 if(rv == 0)
2133 q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
2134 host ? host : unknown);
2136 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
2138 dprint((5, "sslcertificatequery: %s\n",
2139 rv ? "approved" : "rejected"));
2141 return(rv);
2145 char *
2146 pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
2148 char buf[MAILTMPLEN];
2150 if((can_access(mulname, ACCESS_EXISTS) == 0)
2151 || !(can_access(name, ACCESS_EXISTS) == 0))
2152 return(mulname);
2154 snprintf(buf, sizeof(buf),
2155 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2156 last_cmpnt(name),
2157 strlen(last_cmpnt(name)) > 15 ? "..." : "");
2158 buf[sizeof(buf)-1] = '\0';
2159 if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
2160 rename_file(name, mulname);
2161 return(mulname);
2166 url_local_certdetails(char *url)
2168 if(!struncmp(url, "x-alpine-cert:", 14)){
2169 STORE_S *store;
2170 SCROLL_S sargs;
2171 char *folded;
2173 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
2174 q_status_message(SM_ORDER | SM_DING, 7, 10,
2175 _("Error allocating space for details."));
2176 return(0);
2179 so_puts(store, _("Host given by user:\n\n "));
2180 so_puts(store, details_host);
2181 so_puts(store, _("\n\nReason for failure:\n\n "));
2182 so_puts(store, details_reason);
2183 so_puts(store, _("\n\nCertificate being verified:\n\n"));
2184 folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
2185 so_puts(store, folded);
2186 fs_give((void **)&folded);
2187 so_puts(store, "\n");
2189 memset(&sargs, 0, sizeof(SCROLL_S));
2190 sargs.text.text = so_text(store);
2191 sargs.text.src = CharStar;
2192 sargs.text.desc = _("Details");
2193 sargs.bar.title = _("CERT VALIDATION DETAILS");
2194 sargs.help.text = NO_HELP;
2195 sargs.help.title = NULL;
2196 sargs.quell_newmail = 1;
2197 sargs.help.text = h_tls_failure_details;
2198 sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
2200 scrolltool(&sargs);
2202 so_give(&store); /* free resources associated with store */
2203 ps_global->mangled_screen = 1;
2204 return(1);
2207 return(0);
2212 * C-client callback to handle SSL/TLS certificate validation failures
2214 void
2215 pine_sslfailure(char *host, char *reason, long unsigned int flags)
2217 SCROLL_S sargs;
2218 STORE_S *store;
2219 int the_answer = 'n', indent, len, cols;
2220 char buf[500], buf2[500];
2221 char *folded;
2222 char *hst = host ? host : "<unknown>";
2223 char *rsn = reason ? reason : "<unknown>";
2224 char *notls = "/notls";
2225 STRLIST_S hostlist;
2226 int ok_novalidate = 0, warned = 0;
2229 dprint((1, "sslfailure: host=%s reason=%s\n",
2230 hst ? hst : "?",
2231 rsn ? rsn : "?"));
2233 if(flags & NET_SILENT)
2234 return;
2236 hostlist.name = host ? host : "";
2237 hostlist.next = NULL;
2240 * See if we've been told about this host before.
2242 if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){
2243 /* we were told already */
2244 if(warned){
2245 snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn);
2246 buf[sizeof(buf)-1] = '\0';
2247 mm_log(buf, ERROR);
2248 return;
2252 cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80;
2253 cols--;
2255 if(!(store = so_get(CharStar, NULL, EDIT_ACCESS)))
2256 return;
2258 strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf));
2259 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2260 so_puts(store, folded);
2261 fs_give((void **)&folded);
2262 so_puts(store, "\n");
2264 if((len=strlen(hst)) <= cols){
2265 if((indent=((cols-len)/2)) > 0)
2266 so_puts(store, repeat_char(indent, SPACE));
2268 so_puts(store, hst);
2269 so_puts(store, "\n");
2271 else{
2272 strncpy(buf, hst, sizeof(buf));
2273 buf[sizeof(buf)-1] = '\0';
2274 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2275 so_puts(store, folded);
2276 fs_give((void **)&folded);
2279 so_puts(store, "\n");
2281 strncpy(buf, _("The reason for the failure was"), sizeof(buf));
2282 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2283 so_puts(store, folded);
2284 fs_give((void **)&folded);
2285 so_puts(store, "\n");
2287 if((len=strlen(rsn)) <= cols){
2288 if((indent=((cols-len)/2)) > 0)
2289 so_puts(store, repeat_char(indent, SPACE));
2291 so_puts(store, rsn);
2292 so_puts(store, "\n");
2294 else{
2295 strncpy(buf, rsn, sizeof(buf));
2296 buf[sizeof(buf)-1] = '\0';
2297 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2298 so_puts(store, folded);
2299 fs_give((void **)&folded);
2302 so_puts(store, "\n");
2304 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));
2305 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2306 so_puts(store, folded);
2307 fs_give((void **)&folded);
2308 so_puts(store, "\n");
2310 if((len=strlen(notls)) <= cols){
2311 if((indent=((cols-len)/2)) > 0)
2312 so_puts(store, repeat_char(indent, SPACE));
2314 so_puts(store, notls);
2315 so_puts(store, "\n");
2317 else{
2318 strncpy(buf, notls, sizeof(buf));
2319 buf[sizeof(buf)-1] = '\0';
2320 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2321 so_puts(store, folded);
2322 fs_give((void **)&folded);
2325 so_puts(store, "\n");
2327 strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2328 sizeof(buf));
2329 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2330 so_puts(store, folded);
2331 fs_give((void **)&folded);
2332 so_puts(store, "\n");
2334 if((len=strlen(hst)) <= cols){
2335 if((indent=((cols-len)/2)) > 0)
2336 so_puts(store, repeat_char(indent, SPACE));
2338 so_puts(store, hst);
2339 so_puts(store, "\n");
2341 else{
2342 strncpy(buf, hst, sizeof(buf));
2343 buf[sizeof(buf)-1] = '\0';
2344 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2345 so_puts(store, folded);
2346 fs_give((void **)&folded);
2349 so_puts(store, "\n");
2351 strncpy(buf, _("in your configuration, replace those characters with"),
2352 sizeof(buf));
2353 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2354 so_puts(store, folded);
2355 fs_give((void **)&folded);
2356 so_puts(store, "\n");
2358 snprintf(buf2, sizeof(buf2), "%s%s", hst, notls);
2359 buf2[sizeof(buf2)-1] = '\0';
2360 if((len=strlen(buf2)) <= cols){
2361 if((indent=((cols-len)/2)) > 0)
2362 so_puts(store, repeat_char(indent, SPACE));
2364 so_puts(store, buf2);
2365 so_puts(store, "\n");
2367 else{
2368 strncpy(buf, buf2, sizeof(buf));
2369 buf[sizeof(buf)-1] = '\0';
2370 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2371 so_puts(store, folded);
2372 fs_give((void **)&folded);
2375 so_puts(store, "\n");
2377 if(ps_global->ttyo){
2378 strncpy(buf, _("Type RETURN to continue."), sizeof(buf));
2379 folded = fold(buf, cols, cols, "", "", FLD_NONE);
2380 so_puts(store, folded);
2381 fs_give((void **)&folded);
2384 memset(&sargs, 0, sizeof(SCROLL_S));
2385 sargs.text.text = so_text(store);
2386 sargs.text.src = CharStar;
2387 sargs.text.desc = _("help text");
2388 sargs.bar.title = _("SSL/TLS FAILURE");
2389 sargs.proc.tool = answer_cert_failure;
2390 sargs.proc.data.p = (void *)&the_answer;
2391 sargs.keys.menu = &ans_certfail_keymenu;
2392 setbitmap(sargs.keys.bitmap);
2393 /* don't want to re-enter c-client */
2394 sargs.quell_newmail = 1;
2395 sargs.help.text = h_tls_failure;
2396 sargs.help.title = _("HELP FOR TLS/SSL FAILURE");
2398 if(ps_global->ttyo)
2399 scrolltool(&sargs);
2400 else{
2401 char **q, **qp;
2402 char *p;
2403 unsigned char c;
2404 int cnt = 0;
2407 * The screen isn't initialized yet, which should mean that this
2408 * is the result of a -p argument. Display_args_err knows how to deal
2409 * with the uninitialized screen, so we mess with the data to get it
2410 * in shape for display_args_err. This is pretty hacky.
2413 so_seek(store, 0L, 0); /* rewind */
2414 /* count the lines */
2415 while(so_readc(&c, store))
2416 if(c == '\n')
2417 cnt++;
2419 qp = q = (char **)fs_get((cnt+1) * sizeof(char *));
2420 memset(q, 0, (cnt+1) * sizeof(char *));
2422 so_seek(store, 0L, 0); /* rewind */
2423 p = buf;
2424 while(so_readc(&c, store)){
2425 if(c == '\n'){
2426 *p = '\0';
2427 *qp++ = cpystr(buf);
2428 p = buf;
2430 else
2431 *p++ = c;
2434 display_args_err(NULL, q, 0);
2435 free_list_array(&q);
2438 ps_global->mangled_screen = 1;
2439 ps_global->painted_body_on_startup = 0;
2440 ps_global->painted_footer_on_startup = 0;
2441 so_give(&store);
2443 imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1);
2448 answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2450 int rv = 1;
2452 ps_global->next_screen = SCREEN_FUN_NULL;
2454 switch(cmd){
2455 case MC_YES :
2456 *(int *)(sparms->proc.data.p) = 'y';
2457 break;
2459 case MC_NO :
2460 *(int *)(sparms->proc.data.p) = 'n';
2461 break;
2463 default:
2464 alpine_panic("Unexpected command in answer_cert_failure");
2465 break;
2468 return(rv);
2473 oauth2_auth_answer(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms)
2475 int rv = 1, rc;
2476 AUTH_CODE_S user;
2477 int q_line, flags;
2478 /* TRANSLATORS: user needs to input an access code from the server */
2479 char *accesscodelabel = _("Copy and Paste Access Code");
2480 char token[MAILTMPLEN], prompt[MAILTMPLEN];
2482 ps_global->next_screen = SCREEN_FUN_NULL;
2484 token[0] = '\0';
2485 switch(cmd){
2486 case MC_YES :
2487 q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3);
2488 flags = OE_APPEND_CURRENT;
2489 sprintf(prompt, "%s: ", accesscodelabel);
2490 do {
2491 rc = optionally_enter(token, q_line, 0, MAILTMPLEN,
2492 prompt, NULL, NO_HELP, &flags);
2493 } while (rc != 0 && rc != 1);
2494 user.code = rc == 0 ? cpystr(token) : NULL;
2495 user.answer = 'e';
2496 rv = rc == 1 ? 0 : 1;
2497 break;
2499 case MC_NO :
2500 user.code = NULL;
2501 user.answer = 'e';
2502 break;
2504 default:
2505 alpine_panic("Unexpected command in oauth2_auth_answer");
2506 break;
2508 *(AUTH_CODE_S *) sparms->proc.data.p = user;
2509 return(rv);
2513 /*----------------------------------------------------------------------
2514 This can be used to prevent the flickering of the check_cue char
2515 caused by numerous (5000+) fetches by c-client. Right now, the only
2516 practical use found is newsgroup subsciption.
2518 check_cue_display will check if this global is set, and won't clear
2519 the check_cue_char if set.
2520 ----*/
2521 void
2522 set_read_predicted(int i)
2524 ps_global->read_predicted = i==1;
2525 #ifdef _WINDOWS
2526 if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global))
2527 check_cue_display(" ");
2528 #endif
2532 /*----------------------------------------------------------------------
2533 Exported method to retrieve logged in user name associated with stream
2535 Args: host -- host to find associated login name with.
2537 Result:
2538 ----*/
2539 void *
2540 pine_block_notify(int reason, void *data)
2542 switch(reason){
2543 case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */
2544 break;
2546 case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */
2547 break;
2549 case BLOCK_TCPWRITE: /* blocked on TCP write */
2550 case BLOCK_FILELOCK: /* blocked on file locking */
2551 #ifdef _WINDOWS
2552 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
2553 check_cue_display(">");
2555 mswin_setcursor(MSWIN_CURSOR_BUSY);
2556 #endif
2557 break;
2559 case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */
2560 case BLOCK_TCPOPEN: /* blocked on TCP open */
2561 case BLOCK_TCPREAD: /* blocked on TCP read */
2562 case BLOCK_TCPCLOSE: /* blocked on TCP close */
2563 #ifdef _WINDOWS
2564 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
2565 check_cue_display("<");
2567 mswin_setcursor(MSWIN_CURSOR_BUSY);
2568 #endif
2569 break;
2571 default :
2572 case BLOCK_NONE: /* not blocked */
2573 #ifdef _WINDOWS
2574 if(F_ON(F_SHOW_DELAY_CUE, ps_global))
2575 check_cue_display(" ");
2576 #endif
2577 break;
2581 return(NULL);
2585 void
2586 mm_expunged_current(long unsigned int rawno)
2588 /* expunged something we're viewing? */
2589 if(!ps_global->expunge_in_progress
2590 && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno))
2591 && (ps_global->prev_screen == mail_view_screen
2592 || ps_global->prev_screen == attachment_screen))){
2593 ps_global->next_screen = mail_index_screen;
2594 q_status_message(SM_ORDER | SM_DING , 3, 3,
2595 "Message you were viewing is gone!");
2600 #ifdef PASSFILE
2603 * Specific functions to support caching username/passwd/host
2604 * triples on disk for use from one session to the next...
2607 #define FIRSTCH 0x20
2608 #define LASTCH 0x7e
2609 #define TABSZ (LASTCH - FIRSTCH + 1)
2611 static int xlate_key;
2615 * xlate_in() - xlate_in the given character
2617 char
2618 xlate_in(int c)
2620 register int eti;
2622 eti = xlate_key;
2623 if((c >= FIRSTCH) && (c <= LASTCH)){
2624 eti += (c - FIRSTCH);
2625 eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0;
2626 return((xlate_key = eti) + FIRSTCH);
2628 else
2629 return(c);
2634 * xlate_out() - xlate_out the given character
2636 char
2637 xlate_out(char c)
2639 register int dti;
2640 register int xch;
2642 if((c >= FIRSTCH) && (c <= LASTCH)){
2643 xch = c - (dti = xlate_key);
2644 xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0;
2645 dti = (xch - FIRSTCH) + dti;
2646 dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0;
2647 xlate_key = dti;
2648 return(xch);
2650 else
2651 return(c);
2653 #endif /* PASSFILE */
2656 #ifdef LOCAL_PASSWD_CACHE
2659 int
2660 line_get(char *tmp, size_t len, char **textp)
2662 char *s;
2664 tmp[0] = '\0';
2665 if (*textp == NULL
2666 || (s = strchr(*textp, '\n')) == NULL
2667 || (s - *textp) > len - 1)
2668 return 0;
2670 *s = '\0';
2671 if(*(s-1) == '\r')
2672 *(s-1) = '\0';
2674 snprintf(tmp, len, "%s\n", *textp);
2675 tmp[len-1] = '\0';
2676 *textp = s+1;
2678 return 1;
2681 * For UNIX:
2682 * Passfile lines are
2684 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
2686 * In pine4.40 and before there was no orig_hostname, and there still isn't
2687 * if it is the same as hostname.
2689 * else for WINDOWS:
2690 * Use Windows credentials. The TargetName of the credential is
2691 * UWash_Alpine_<hostname:port>\tuser\taltflag
2692 * and the blob consists of
2693 * passwd\torighost (if different from host)
2695 * We don't use anything fancy we just copy out all the credentials which
2696 * begin with TNAME and put them into our cache, so we don't lookup based
2697 * on the TargetName or anything like that. That was so we could re-use
2698 * the existing code and so that orighost data could be easily used.
2701 read_passfile(pinerc, l)
2702 char *pinerc;
2703 MMLOGIN_S **l;
2705 #ifdef WINCRED
2706 # if (WINCRED > 0)
2707 LPCTSTR lfilter = TEXT(TNAMESTAR);
2708 DWORD count, k;
2709 PCREDENTIAL *pcred;
2710 char *tmp, *blob, *target = NULL;
2711 char *host, *user, *sflags, *passwd, *orighost;
2712 char *ui[5];
2713 int i, j;
2715 if(using_passfile == 0)
2716 return(using_passfile);
2718 if(!g_CredInited){
2719 if(init_wincred_funcs() != 1){
2720 using_passfile = 0;
2721 return(using_passfile);
2725 dprint((9, "read_passfile\n"));
2727 using_passfile = 1;
2729 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
2730 if(pcred){
2731 for(k = 0; k < count; k++){
2733 host = user = sflags = passwd = orighost = NULL;
2734 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
2736 target = lptstr_to_utf8(pcred[k]->TargetName);
2737 tmp = srchstr(target, TNAME);
2739 if(tmp){
2740 tmp += strlen(TNAME);
2741 for(i = 0, j = 0; tmp[i] && j < 3; j++){
2742 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
2743 ; /* find end of data */
2745 if(tmp[i])
2746 tmp[i++] = '\0'; /* tie off data */
2749 host = ui[0];
2750 user = ui[1];
2751 sflags = ui[2];
2754 blob = (char *) pcred[k]->CredentialBlob;
2755 if(blob){
2756 for(i = 0, j = 3; blob[i] && j < 5; j++){
2757 for(ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++)
2758 ; /* find end of data */
2760 if(blob[i])
2761 blob[i++] = '\0'; /* tie off data */
2764 passwd = ui[3];
2765 orighost = ui[4];
2768 if(passwd && host && user){ /* valid field? */
2769 STRLIST_S hostlist[2];
2770 int flags;
2772 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
2773 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
2774 hostlist[0].name = host;
2775 if(orighost){
2776 hostlist[0].next = &hostlist[1];
2777 hostlist[1].name = orighost;
2778 hostlist[1].next = NULL;
2780 else{
2781 hostlist[0].next = NULL;
2784 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
2787 if(target)
2788 fs_give((void **) &target);
2791 g_CredFree((PVOID) pcred);
2795 return(1);
2797 # else /* old windows */
2798 using_passfile = 0;
2799 return(0);
2800 # endif
2802 #elif APPLEKEYCHAIN
2804 char target[MAILTMPLEN];
2805 char *tmp, *host, *user, *sflags, *passwd, *orighost;
2806 char *ui[5];
2807 int i, j, k, rc;
2808 SecKeychainAttributeList attrList;
2809 SecKeychainSearchRef searchRef = NULL;
2810 SecKeychainAttribute attrs[] = {
2811 { kSecAccountItemAttr, strlen(TNAME), TNAME }
2814 if(using_passfile == 0)
2815 return(using_passfile);
2817 dprint((9, "read_passfile\n"));
2820 /* search for only our items in the keychain */
2821 attrList.count = 1;
2822 attrList.attr = attrs;
2824 using_passfile = 1;
2825 if(!(rc=SecKeychainSearchCreateFromAttributes(NULL,
2826 kSecGenericPasswordItemClass,
2827 &attrList,
2828 &searchRef))){
2829 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
2830 if(searchRef){
2831 SecKeychainItemRef itemRef = NULL;
2832 SecKeychainAttributeInfo info;
2833 SecKeychainAttributeList *attrList = NULL;
2834 UInt32 blength = 0;
2835 char *blob = NULL;
2836 char *blobcopy = NULL; /* NULL terminated copy */
2838 UInt32 tags[] = {kSecAccountItemAttr,
2839 kSecServiceItemAttr};
2840 UInt32 formats[] = {0,0};
2842 dprint((10, "read_passfile: searchRef not NULL\n"));
2843 info.count = 2;
2844 info.tag = tags;
2845 info.format = formats;
2848 * Go through each item we found and put it
2849 * into our list.
2851 while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){
2852 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
2853 rc = SecKeychainItemCopyAttributesAndData(itemRef,
2854 &info, NULL,
2855 &attrList,
2856 &blength,
2857 (void **) &blob);
2858 if(rc == 0 && attrList){
2859 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count));
2861 blobcopy = (char *) fs_get((blength + 1) * sizeof(char));
2862 strncpy(blobcopy, (char *) blob, blength);
2863 blobcopy[blength] = '\0';
2866 * I'm not real clear on how this works. It seems to be
2867 * necessary to combine the attributes from two passes
2868 * (attrList->count == 2) in order to get the full set
2869 * of attributes we inserted into the keychain in the
2870 * first place. So, we reset host...orighost outside of
2871 * the following for loop, not inside.
2873 host = user = sflags = passwd = orighost = NULL;
2874 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
2876 for(k = 0; k < attrList->count; k++){
2878 if(attrList->attr[k].length){
2879 strncpy(target,
2880 (char *) attrList->attr[k].data,
2881 MIN(attrList->attr[k].length,sizeof(target)));
2882 target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0';
2885 tmp = target;
2886 for(i = 0, j = 0; tmp[i] && j < 3; j++){
2887 for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++)
2888 ; /* find end of data */
2890 if(tmp[i])
2891 tmp[i++] = '\0'; /* tie off data */
2894 if(ui[0])
2895 host = ui[0];
2897 if(ui[1])
2898 user = ui[1];
2900 if(ui[2])
2901 sflags = ui[2];
2903 for(i = 0, j = 3; blobcopy[i] && j < 5; j++){
2904 for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++)
2905 ; /* find end of data */
2907 if(blobcopy[i])
2908 blobcopy[i++] = '\0'; /* tie off data */
2911 if(ui[3])
2912 passwd = ui[3];
2914 if(ui[4])
2915 orighost = ui[4];
2917 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:""));
2920 if(passwd && host && user){ /* valid field? */
2921 STRLIST_S hostlist[2];
2922 int flags;
2924 tmp = sflags ? strchr(sflags, PWDAUTHSEP) : NULL;
2925 flags = sflags ? atoi(tmp ? ++tmp : sflags) : 0;
2926 hostlist[0].name = host;
2927 if(orighost){
2928 hostlist[0].next = &hostlist[1];
2929 hostlist[1].name = orighost;
2930 hostlist[1].next = NULL;
2932 else{
2933 hostlist[0].next = NULL;
2936 imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0);
2939 if(blobcopy)
2940 fs_give((void **) & blobcopy);
2942 SecKeychainItemFreeAttributesAndData(attrList, (void *) blob);
2944 else{
2945 using_passfile = 0;
2946 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc));
2949 CFRelease(itemRef);
2950 itemRef = NULL;
2953 CFRelease(searchRef);
2955 else{
2956 using_passfile = 0;
2957 dprint((10, "read_passfile: searchRef NULL\n"));
2960 else{
2961 using_passfile = 0;
2962 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc));
2965 return(using_passfile);
2967 #else /* PASSFILE */
2969 char tmp[MAILTMPLEN], *ui[5];
2970 int i, j, n, rv = 0;
2971 size_t len;
2972 char *tmptext = NULL;
2973 #ifdef SMIME
2974 char tmp2[MAILTMPLEN];
2975 char *text = NULL, *text2 = NULL;
2976 int encrypted = 0;
2977 #endif /* SMIME */
2978 FILE *fp;
2980 if(using_passfile == 0)
2981 return(using_passfile);
2983 dprint((9, "read_passfile\n"));
2985 /* if there's no password to read, bag it!! */
2986 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){
2987 using_passfile = 0;
2988 return(using_passfile);
2991 #ifdef SMIME
2992 /* the next call initializes the key/certificate pair used to
2993 * encrypt and decrypt a password file. The details of how this is
2994 * done is in the file pith/smime.c. During this setup we might call
2995 * smime_init(), but no matter what happens we must call smime_deinit()
2996 * there. The reason why this is so is because we can not assume that
2997 * the .pinerc file has been read by this time, so this code might not
2998 * know about the ps_global->smime structure or any of its components,
2999 * and it shouldn't because it only needs ps_global->pwdcert, so
3000 * do not init smime here, because the .pinerc might not have been
3001 * read and we do not really know where the keys and certificates really
3002 * are.
3003 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3004 * it is called for the first time and there are certificates at all,
3005 * or when it is called after the first time and the user refuses to
3006 * create a self-signed certificate. In this situation we will just
3007 * let the user live in an insecure world, but no more passwords will
3008 * be saved in the password file, and only those found there will be used.
3010 tmp2[0] = '\0';
3011 fgets(tmp2, sizeof(tmp2), fp);
3012 fclose(fp);
3013 if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){
3014 /* there is an already existing password file, that is not encrypted
3015 * and there is no key to encrypt it yet, go again through setup_pwdcert
3016 * and encrypt it now.
3018 if(tmp2[0]){ /* not empty, UNencrypted password file */
3019 if(ps_global->pwdcert == NULL)
3020 rv = setup_pwdcert(&ps_global->pwdcert);
3021 if((rv == 0 || rv == -5) && ps_global->pwdcert == NULL)
3022 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3023 if(ps_global->pwdcert == NULL){
3024 q_status_message(SM_ORDER, 3, 3,
3025 " Failed to create private key. Using UNencrypted Password file. ");
3026 save_password = 0;
3028 else{
3029 if(rv == 1){
3030 q_status_message(SM_ORDER, 3, 3,
3031 " Failed to unlock private key. Using UNencrypted Password file. ");
3032 save_password = 0; /* do not save more passwords */
3035 if(ps_global->pwdcert != NULL
3036 && encrypt_file((char *)tmp, NULL, (PERSONAL_CERT *)ps_global->pwdcert))
3037 encrypted++;
3040 else {
3041 if(ps_global->pwdcert == NULL)
3042 rv = setup_pwdcert(&ps_global->pwdcert);
3043 encrypted++;
3047 * if password file is encrypted we attempt to decrypt. We ask the
3048 * user for the password to unlock the password file. If the user
3049 * enters the password and it unlocks the file, use it and keep saving
3050 * passwords in it. If the user enters the wrong passwords and does
3051 * not unlock it, we will not see that here, but in decrypt_file, so
3052 * the only other possibility is that the user cancels. In that case
3053 * we will see i == -1. In that case, we will let the user attempt
3054 * manual login to the server they want to login, but passwords will
3055 * not be saved so that the password file will not be saved
3056 * unencrypted and rewritten again.
3058 if(encrypted){
3059 text = text2 = decrypt_file((char *)tmp, &i, (PERSONAL_CERT *)ps_global->pwdcert);
3060 len = text2 ? strlen(text2) : 0;
3061 switch(i){
3062 case -2: using_passfile = 0;
3063 break;
3065 case 1 : save_password = 1;
3066 using_passfile = 1;
3067 break;
3069 case -1: save_password = 0;
3070 using_passfile = 1;
3071 break;
3073 default: break;
3076 else{
3077 struct stat sbuf;
3078 if(our_stat(tmp, &sbuf) == 0)
3079 len = sbuf.st_size;
3080 else
3081 len = 0;
3082 fp = our_fopen(tmp, "rb"); /* reopen to read data */
3084 #endif /* SMIME */
3086 if(using_passfile == 0){
3087 #ifdef SMIME
3088 if(text) fs_give((void **)&text);
3089 #endif /* SMIME */
3090 return using_passfile;
3093 if(len > 0){
3094 tmptext = fs_get(len + 1);
3095 #ifdef SMIME
3096 for(n = 0; encrypted ? line_get(tmptext, len + 1, &text2)
3097 : (fgets(tmptext, len+1, fp) != NULL); n++){
3098 #else /* SMIME */
3099 for(n = 0; fgets(tmptext, len+1, fp); n++){
3100 #endif /* SMIME */
3101 /*** do any necessary DEcryption here ***/
3102 xlate_key = n;
3103 for(i = 0; tmptext[i]; i++)
3104 tmptext[i] = xlate_out(tmptext[i]);
3106 if(i && tmptext[i-1] == '\n')
3107 tmptext[i-1] = '\0'; /* blast '\n' */
3109 dprint((10, "read_passfile: %s\n", tmp ? tmp : "?"));
3110 ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL;
3111 for(i = 0, j = 0; tmptext[i] && j < 5; j++){
3112 for(ui[j] = &tmptext[i]; tmptext[i] && tmptext[i] != '\t'; i++)
3113 ; /* find end of data */
3115 if(tmptext[i])
3116 tmptext[i++] = '\0'; /* tie off data */
3119 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3120 if(ui[0] && ui[1] && ui[2]){ /* valid field? */
3121 STRLIST_S hostlist[2];
3122 char *s = ui[3] ? strchr(ui[3], PWDAUTHSEP) : NULL;
3123 int flags = ui[3] ? atoi(s ? ++s : ui[3]) : 0;
3125 hostlist[0].name = ui[2];
3126 if(ui[4]){
3127 hostlist[0].next = &hostlist[1];
3128 hostlist[1].name = ui[4];
3129 hostlist[1].next = NULL;
3131 else{
3132 hostlist[0].next = NULL;
3135 imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0);
3140 if (tmptext) fs_give((void **) &tmptext);
3141 #ifdef SMIME
3142 if (text) fs_give((void **)&text);
3143 #else /* SMIME */
3144 fclose(fp);
3145 #endif /* SMIME */
3146 return(1);
3147 #endif /* PASSFILE */
3152 void
3153 write_passfile(pinerc, l)
3154 char *pinerc;
3155 MMLOGIN_S *l;
3157 char *authend, *authtype;
3158 #ifdef WINCRED
3159 # if (WINCRED > 0)
3160 char target[MAILTMPLEN];
3161 char blob[MAILTMPLEN];
3162 CREDENTIAL cred;
3163 LPTSTR ltarget = 0;
3165 if(using_passfile == 0)
3166 return;
3168 dprint((9, "write_passfile\n"));
3170 for(; l; l = l->next){
3171 authtype = l->passwd;
3172 authend = strchr(l->passwd, PWDAUTHSEP);
3173 if(authend != NULL){
3174 *authend = '\0';
3175 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3176 *authend = PWDAUTHSEP;
3178 else
3179 sprintf(blob, "%d", l->altflag);
3181 snprintf(target, sizeof(target), "%s%s\t%s\t%s",
3182 TNAME,
3183 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3184 l->user ? l->user : "",
3185 blob);
3186 ltarget = utf8_to_lptstr((LPSTR) target);
3188 if(ltarget){
3189 snprintf(blob, sizeof(blob), "%s%s%s",
3190 l->passwd ? l->passwd : "",
3191 (l->hosts && l->hosts->next && l->hosts->next->name)
3192 ? "\t" : "",
3193 (l->hosts && l->hosts->next && l->hosts->next->name)
3194 ? l->hosts->next->name : "");
3195 memset((void *) &cred, 0, sizeof(cred));
3196 cred.Flags = 0;
3197 cred.Type = CRED_TYPE_GENERIC;
3198 cred.TargetName = ltarget;
3199 cred.CredentialBlobSize = strlen(blob)+1;
3200 cred.CredentialBlob = (LPBYTE) &blob;
3201 cred.Persist = CRED_PERSIST_ENTERPRISE;
3202 g_CredWriteW(&cred, 0);
3204 fs_give((void **) &ltarget);
3207 #endif /* WINCRED > 0 */
3209 #elif APPLEKEYCHAIN
3210 int rc;
3211 char target[MAILTMPLEN];
3212 char blob[MAILTMPLEN];
3213 SecKeychainItemRef itemRef = NULL;
3215 if(using_passfile == 0)
3216 return;
3218 dprint((9, "write_passfile\n"));
3220 for(; l; l = l->next){
3221 authtype = l->passwd;
3222 authend = strchr(l->passwd, PWDAUTHSEP);
3223 if(authend != NULL){
3224 *authend = '\0';
3225 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3226 *authend = PWDAUTHSEP;
3228 else
3229 sprintf(blob, "%d", l->altflag);
3231 snprintf(target, sizeof(target), "%s\t%s\t%s",
3232 (l->hosts && l->hosts->name) ? l->hosts->name : "",
3233 l->user ? l->user : "",
3234 blob);
3236 snprintf(blob, sizeof(blob), "%s%s%s",
3237 l->passwd ? l->passwd : "",
3238 (l->hosts && l->hosts->next && l->hosts->next->name)
3239 ? "\t" : "",
3240 (l->hosts && l->hosts->next && l->hosts->next->name)
3241 ? l->hosts->next->name : "");
3243 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob));
3245 rc = SecKeychainAddGenericPassword(NULL,
3246 strlen(target), target,
3247 strlen(TNAME), TNAME,
3248 strlen(blob), blob,
3249 NULL);
3250 if(rc==0){
3251 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3253 else{
3254 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc));
3257 if(rc == errSecDuplicateItem){
3258 /* fix existing entry */
3259 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3260 itemRef = NULL;
3261 if(!(rc=SecKeychainFindGenericPassword(NULL,
3262 strlen(target), target,
3263 strlen(TNAME), TNAME,
3264 NULL, NULL,
3265 &itemRef)) && itemRef){
3267 rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob);
3268 if(!rc){
3269 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc));
3272 else{
3273 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc));
3278 #else /* PASSFILE */
3279 char tmp[4*MAILTMPLEN], blob[4*MAILTMPLEN];
3280 int i, n;
3281 FILE *fp;
3282 #ifdef SMIME
3283 char *text = NULL, tmp2[4*MAILTMPLEN];
3284 int len = 0;
3285 #endif
3287 if(using_passfile == 0)
3288 return;
3290 dprint((9, "write_passfile\n"));
3292 /* if there's no passfile to read, bag it!! */
3293 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "wb"))){
3294 using_passfile = 0;
3295 return;
3298 #ifdef SMIME
3299 strncpy(tmp2, tmp, sizeof(tmp2));
3300 tmp2[sizeof(tmp2)-1] = '\0';
3301 #endif /* SMIME */
3303 for(n = 0; l; l = l->next, n++){
3304 authtype = l->passwd;
3305 authend = strchr(l->passwd, PWDAUTHSEP);
3306 if(authend != NULL){
3307 *authend = '\0';
3308 sprintf(blob, "%s%c%d", authtype, PWDAUTHSEP, l->altflag);
3309 *authend = PWDAUTHSEP;
3311 else
3312 sprintf(blob, "%d", l->altflag);
3314 /*** do any necessary ENcryption here ***/
3315 snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%s%s%s\n", l->passwd, l->user,
3316 l->hosts->name, blob,
3317 (l->hosts->next && l->hosts->next->name) ? "\t" : "",
3318 (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name
3319 : "");
3320 dprint((10, "write_passfile: %s", tmp ? tmp : "?"));
3321 xlate_key = n;
3322 for(i = 0; tmp[i]; i++)
3323 tmp[i] = xlate_in(tmp[i]);
3325 #ifdef SMIME
3326 fs_resize((void **)&text, (len + strlen(tmp) + 1)*sizeof(char));
3327 text[len] = '\0';
3328 len += strlen(tmp) + 1;
3329 strncat(text, tmp, strlen(tmp));
3330 #else /* SMIME */
3331 fputs(tmp, fp);
3332 #endif /* SMIME */
3335 fclose(fp);
3336 #ifdef SMIME
3337 if(text != NULL){
3338 if(ps_global->pwdcert == NULL){
3339 q_status_message(SM_ORDER, 3, 3, "Attempting to encrypt password file");
3340 i = setup_pwdcert(&ps_global->pwdcert);
3341 if((i == 0 || i == -5) && ps_global->pwdcert == NULL)
3342 ps_global->pwdcert = (void *) ALPINE_self_signed_certificate(NULL, 0, ps_global->pwdcertdir, MASTERNAME);
3344 if(ps_global->pwdcert == NULL){ /* we tried but failed */
3345 if(i == -1)
3346 q_status_message1(SM_ORDER, 3, 3, "Error: no directory %s to save certificates", ps_global->pwdcertdir);
3347 else
3348 q_status_message(SM_ORDER, 3, 3, "Refusing to write non-encrypted password file");
3350 else if(encrypt_file((char *)tmp2, text, ps_global->pwdcert) == 0)
3351 q_status_message(SM_ORDER, 3, 3, "Failed to encrypt password file");
3352 fs_give((void **)&text); /* do not save this text */
3354 #endif /* SMIME */
3355 #endif /* PASSFILE */
3358 #endif /* LOCAL_PASSWD_CACHE */
3361 #if (WINCRED > 0)
3362 void
3363 erase_windows_credentials(void)
3365 LPCTSTR lfilter = TEXT(TNAMESTAR);
3366 DWORD count, k;
3367 PCREDENTIAL *pcred;
3369 if(!g_CredInited){
3370 if(init_wincred_funcs() != 1)
3371 return;
3374 if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){
3375 if(pcred){
3376 for(k = 0; k < count; k++)
3377 g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0);
3379 g_CredFree((PVOID) pcred);
3384 void
3385 ask_erase_credentials(void)
3387 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){
3388 erase_windows_credentials();
3389 q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased");
3391 else
3392 q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased");
3395 #endif /* WINCRED */
3398 #ifdef LOCAL_PASSWD_CACHE
3400 get_passfile_passwd(pinerc, passwd, user, hostlist, altflag)
3401 char *pinerc, **passwd, *user;
3402 STRLIST_S *hostlist;
3403 int altflag;
3405 return get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, NULL);
3409 * get_passfile_passwd_auth - return the password contained in the special passord
3410 * cache. The file is assumed to be in the same directory
3411 * as the pinerc with the name defined above.
3414 get_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, authtype)
3415 char *pinerc, **passwd, *user;
3416 STRLIST_S *hostlist;
3417 int altflag;
3418 char *authtype;
3420 dprint((10, "get_passfile_passwd_auth\n"));
3421 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3422 ? imap_get_passwd_auth(passfile_cache, passwd,
3423 user, hostlist, altflag, authtype)
3424 : 0);
3427 void
3428 free_passfile_cache_work(MMLOGIN_S **pwdcache)
3430 if(pwdcache == NULL || *pwdcache == NULL)
3431 return;
3433 if((*pwdcache)->user) fs_give((void **)&(*pwdcache)->user);
3434 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
3435 if((*pwdcache)->hosts) free_strlist(&(*pwdcache)->hosts);
3436 free_passfile_cache_work(&(*pwdcache)->next);
3437 fs_give((void **)pwdcache);
3441 void
3442 free_passfile_cache(void)
3444 if(passfile_cache)
3445 free_passfile_cache_work(&passfile_cache);
3449 is_using_passfile(void)
3451 return(using_passfile == 1);
3455 * Just trying to guess the username the user might want to use on this
3456 * host, the user will confirm.
3458 char *
3459 get_passfile_user(pinerc, hostlist)
3460 char *pinerc;
3461 STRLIST_S *hostlist;
3463 return((passfile_cache || read_passfile(pinerc, &passfile_cache))
3464 ? imap_get_user(passfile_cache, hostlist)
3465 : NULL);
3470 preserve_prompt(char *pinerc)
3472 return preserve_prompt_auth(pinerc, NULL);
3476 preserve_prompt_auth(char *pinerc, char *authtype)
3478 #ifdef WINCRED
3479 # if (WINCRED > 0)
3480 #define PROMPT_PWD _("Preserve password for next login")
3481 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3483 * This prompt was going to be able to be turned on and off via a registry
3484 * setting controlled from the config menu. We decided to always use the
3485 * dialog for login, and there the prompt is unobtrusive enough to always
3486 * be in there. As a result, windows should never reach this, but now
3487 * OS X somewhat uses the behavior just described.
3489 if(mswin_store_pass_prompt()
3490 && (want_to(authtype
3491 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3492 : PROMPT_PWD,
3493 'y', 'x', NO_HELP, WT_NORM)
3494 == 'y'))
3495 return(1);
3496 else
3497 return(0);
3498 # else
3499 return(0);
3500 # endif
3502 #elif APPLEKEYCHAIN
3503 #define PROMPT_PWD _("Preserve password for next login")
3504 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3506 int rc;
3507 if((rc = macos_store_pass_prompt()) != 0){
3508 if(want_to(authtype
3509 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3510 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
3511 == 'y'){
3512 if(rc == -1){
3513 macos_set_store_pass_prompt(1);
3514 q_status_message(SM_ORDER, 4, 4,
3515 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3517 return(1);
3519 else if(rc == -1){
3520 macos_set_store_pass_prompt(0);
3521 q_status_message(SM_ORDER, 4, 4,
3522 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3524 return(0);
3526 return(0);
3527 #else /* PASSFILE */
3528 #define PROMPT_PWD _("Preserve password on DISK for next login")
3529 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
3531 char tmp[MAILTMPLEN];
3532 struct stat sbuf;
3534 if(!passfile_name(pinerc, tmp, sizeof(tmp)) || our_stat(tmp, &sbuf) < 0)
3535 return 0;
3537 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global))
3538 return(want_to(authtype
3539 ? (strcmp(authtype, OA2NAME) ? PROMPT_PWD : PROMPT_OA2)
3540 : PROMPT_PWD, 'y', 'x', NO_HELP, WT_NORM)
3541 == 'y');
3542 return(0);
3543 #endif /* PASSFILE */
3546 #endif /* LOCAL_PASSWD_CACHE */
3549 #ifdef APPLEKEYCHAIN
3552 * Returns:
3553 * 1 if store pass prompt is set in the "registry" to on
3554 * 0 if set to off
3555 * -1 if not set to anything
3558 macos_store_pass_prompt(void)
3560 char *data = NULL;
3561 UInt32 len = 0;
3562 int rc = -1;
3563 int val;
3565 if(storepassprompt == -1){
3566 if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL,
3567 strlen(TNAMEPROMPT),
3568 TNAMEPROMPT, &len,
3569 (void **) &data, NULL))){
3570 val = (len == 1 && data && data[0] == '1');
3574 if(storepassprompt == -1 && !rc){
3575 if(val)
3576 storepassprompt = 1;
3577 else
3578 storepassprompt = 0;
3581 return(storepassprompt);
3585 void
3586 macos_set_store_pass_prompt(int val)
3588 storepassprompt = val ? 1 : 0;
3590 SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT),
3591 TNAMEPROMPT, 1, val ? "1" : "0", NULL);
3595 void
3596 macos_erase_keychain(void)
3598 SecKeychainAttributeList attrList;
3599 SecKeychainSearchRef searchRef = NULL;
3600 SecKeychainAttribute attrs1[] = {
3601 { kSecAccountItemAttr, strlen(TNAME), TNAME }
3603 SecKeychainAttribute attrs2[] = {
3604 { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT }
3607 dprint((9, "macos_erase_keychain\n"));
3610 * Seems like we ought to be able to combine attrs1 and attrs2
3611 * into a single array, but I couldn't get it to work.
3614 /* search for only our items in the keychain */
3615 attrList.count = 1;
3616 attrList.attr = attrs1;
3618 if(!SecKeychainSearchCreateFromAttributes(NULL,
3619 kSecGenericPasswordItemClass,
3620 &attrList,
3621 &searchRef)){
3622 if(searchRef){
3623 SecKeychainItemRef itemRef = NULL;
3626 * Go through each item we found and put it
3627 * into our list.
3629 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
3630 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3631 SecKeychainItemDelete(itemRef);
3632 CFRelease(itemRef);
3635 CFRelease(searchRef);
3639 attrList.count = 1;
3640 attrList.attr = attrs2;
3642 if(!SecKeychainSearchCreateFromAttributes(NULL,
3643 kSecGenericPasswordItemClass,
3644 &attrList,
3645 &searchRef)){
3646 if(searchRef){
3647 SecKeychainItemRef itemRef = NULL;
3650 * Go through each item we found and put it
3651 * into our list.
3653 while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){
3654 SecKeychainItemDelete(itemRef);
3655 CFRelease(itemRef);
3658 CFRelease(searchRef);
3663 #endif /* APPLEKEYCHAIN */
3665 #ifdef LOCAL_PASSWD_CACHE
3667 void
3668 set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted)
3669 char *pinerc, *passwd, *user;
3670 STRLIST_S *hostlist;
3671 int altflag, already_prompted;
3673 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, NULL);
3676 * set_passfile_passwd - set the password file entry associated with
3677 * cache. The file is assumed to be in the same directory
3678 * as the pinerc with the name defined above.
3679 * already_prompted: 0 not prompted
3680 * 1 prompted, answered yes
3681 * 2 prompted, answered no
3683 void
3684 set_passfile_passwd_auth(pinerc, passwd, user, hostlist, altflag, already_prompted, authtype)
3685 char *pinerc, *passwd, *user;
3686 STRLIST_S *hostlist;
3687 int altflag, already_prompted;
3688 char *authtype;
3690 dprint((10, "set_passfile_passwd_auth\n"));
3691 if(((already_prompted == 0 && preserve_prompt_auth(pinerc, authtype))
3692 || already_prompted == 1)
3693 && !ps_global->nowrite_password_cache
3694 && (passfile_cache || read_passfile(pinerc, &passfile_cache))){
3695 imap_set_passwd_auth(&passfile_cache, passwd, user, hostlist, altflag, 0, 0, authtype);
3696 write_passfile(pinerc, passfile_cache);
3700 void
3701 update_passfile_hostlist(pinerc, user, hostlist, altflag)
3702 char *pinerc;
3703 char *user;
3704 STRLIST_S *hostlist;
3705 int altflag;
3707 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, NULL);
3711 * Passfile lines are
3713 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3715 * In pine4.40 and before there was no orig_hostname.
3716 * This routine attempts to repair that.
3718 void
3719 update_passfile_hostlist_auth(pinerc, user, hostlist, altflag, authtype)
3720 char *pinerc;
3721 char *user;
3722 STRLIST_S *hostlist;
3723 int altflag;
3724 char *authtype;
3726 #ifdef WINCRED
3727 return;
3728 #else /* !WINCRED */
3729 MMLOGIN_S *l;
3730 size_t len = authtype ? strlen(authtype) : 0;
3731 size_t offset = authtype ? 1 : 0;
3733 for(l = passfile_cache; l; l = l->next)
3734 if(imap_same_host_auth(l->hosts, hostlist, authtype)
3735 && *user
3736 && !strcmp(user, l->user + len + offset)
3737 && l->altflag == altflag){
3738 break;
3741 if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next
3742 && hostlist->next->name
3743 && !ps_global->nowrite_password_cache){
3744 l->hosts->next = new_strlist_auth(hostlist->next->name, authtype, PWDAUTHSEP);
3745 write_passfile(pinerc, passfile_cache);
3747 #endif /* !WINCRED */
3750 #endif /* LOCAL_PASSWD_CACHE */
3753 #if (WINCRED > 0)
3755 * Load and init the WinCred structure.
3756 * This gives us a way to skip the WinCred code
3757 * if the dll doesn't exist.
3760 init_wincred_funcs(void)
3762 if(!g_CredInited)
3764 HMODULE hmod;
3766 /* Assume the worst. */
3767 g_CredInited = -1;
3769 hmod = LoadLibrary(TEXT("advapi32.dll"));
3770 if(hmod)
3772 FARPROC fpCredWriteW;
3773 FARPROC fpCredEnumerateW;
3774 FARPROC fpCredDeleteW;
3775 FARPROC fpCredFree;
3777 fpCredWriteW = GetProcAddress(hmod, "CredWriteW");
3778 fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW");
3779 fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW");
3780 fpCredFree = GetProcAddress(hmod, "CredFree");
3782 if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree)
3784 g_CredWriteW = (CREDWRITEW *)fpCredWriteW;
3785 g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW;
3786 g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW;
3787 g_CredFree = (CREDFREE *)fpCredFree;
3789 g_CredInited = 1;
3793 mswin_set_erasecreds_callback(ask_erase_credentials);
3796 return g_CredInited;
3799 #endif /* WINCRED */