1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: imap.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
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 /*======================================================================
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
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"
53 #include "../pith/smime.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
);
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
;
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 */
123 char xlate_out(char);
124 int line_get(char *, size_t, char **);
125 #endif /* PASSFILE */
128 void ask_erase_credentials(void);
129 int init_wincred_funcs(void);
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
142 OAUTH2_S alpine_oauth2_list
[] =
145 {"imap.gmail.com", "smtp.gmail.com", NULL
, NULL
},
146 {{"client_id", NULL
},
147 {"client_secret", 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"},
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
}}
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
},
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"},
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
}}
194 { NULL
, NULL
, NULL
, NULL
, NULL
, 0},
197 typedef struct auth_code_s
{
203 oauth2_get_access_code(char *url
, char *method
, OAUTH2_S
*oauth2
, int *tryanother
)
205 char tmp
[MAILTMPLEN
];
210 STORE_S
*in_store
, *out_store
;
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
)))
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
);
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
);
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");
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)){
296 s
= strstr(user_input
.code
, "code=");
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;
311 free_handles(&handles
);
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.
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
),
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
);
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)){
379 s
= strstr(token
, "code=");
387 else code
= token
[0] ? cpystr(token
) : NULL
;
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
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"
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.
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];
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];
458 int len
, rc
, q_line
, flags
, i
, j
;
459 int oespace
, avail
, need
, save_dont_use
;
462 int ChangeAccessToken
, ChangeRefreshToken
, ChangeExpirationTime
;
464 unsigned long OldExpirationTime
, NewExpirationTime
, SaveExpirationTime
;
465 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
466 int preserve_password
= -1;
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 */
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
;
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
;
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
){
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
++){
531 for(k
= 0; k
<= i
&& hostlist2
[k
].name
532 && strcmp(hostlist2
[k
].name
, oa2list
->host
[j
]); k
++);
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
){
551 if(*mb
->user
!= '\0')
552 strncpy(user
, mb
->user
, NETMAXUSER
);
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;
601 t
= strchr(s
, PWDAUTHSEP
);
603 NewRefreshToken
= cpystr(s
);
606 NewRefreshToken
= cpystr(s
);
608 t
= strchr(s
, PWDAUTHSEP
);
610 NewAccessToken
= cpystr(s
);
613 NewAccessToken
= cpystr(s
);
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
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
);
674 NewExpirationTime
= OldExpirationTime
;
675 SaveRefreshToken
= NewRefreshToken
;
676 SaveAccessToken
= NewAccessToken
;
677 SaveExpirationTime
= NewExpirationTime
;
681 login
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
682 login
->access_token
= SaveAccessToken
;
683 login
->expiration
= SaveExpirationTime
;
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
)
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
: "")
704 token
= fs_get(len
+ 1);
705 sprintf(token
, "%s%c%s%c%lu",
706 SaveRefreshToken
? SaveRefreshToken
: "", PWDAUTHSEP
,
707 SaveAccessToken
? SaveAccessToken
: "", PWDAUTHSEP
,
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 ----------------------------------------------------------------------*/
741 mm_notify(MAILSTREAM
*stream
, char *string
, long int errflg
)
746 now
= time((time_t *)0);
747 tm_now
= localtime(&now
);
749 /* be sure to log the message... */
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
: "?"));
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),
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)
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)){
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-",
811 else if((errflg
&& errflg
!= BYE
&& errflg
!= PARSE
)
812 && !ps_global
->noshow_error
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
831 mm_log(char *string
, long int errflg
)
833 char message
[sizeof(ps_global
->c_client_error
)];
836 static char saw_kerberos_init_warning
;
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;
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 ...
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
)
876 saw_kerberos_init_warning
= 1;
879 /*---- replace all "mailbox" with "folder" ------*/
880 occurence
= srchstr(message
, "mailbox");
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"));
888 *occurence
= (errflg
== PARSE
? 'A' : 'F');
893 occurence
= srchstr(occurence
, "mailbox");
896 /*---- replace all "GSSAPI" with "Kerberos" ------*/
897 occurence
= srchstr(message
, "GSSAPI");
900 || isspace((unsigned char) *(occurence
+6))
901 || *(occurence
+6) == ':')
902 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 6, "Kerberos");
906 occurence
= srchstr(occurence
, "GSSAPI");
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
,
924 strncpy(ps_global
->last_error
, message
, sizeof(ps_global
->last_error
));
925 ps_global
->last_error
[sizeof(ps_global
->last_error
) - 1] = '\0';
929 mm_login_method_work(NETMBX
*mb
, char *user
, void *login
, long int trial
,
930 char *method
, char *usethisprompt
, char *altuserforcache
)
934 if(strucmp(method
, OA2NAME
) == 0 || strucmp(method
, BEARERNAME
) == 0)
935 mm_login_oauth2(mb
, user
, method
, (OAUTH2_S
*) login
, trial
, usethisprompt
, altuserforcache
);
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];
964 int len
, rc
, q_line
, flags
;
965 int oespace
, avail
, need
, save_dont_use
;
968 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
969 int preserve_password
= -1;
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 */
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...
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
;
1021 hostlist
[0].next
= NULL
;
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
;
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
: "",
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
;
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
;
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.
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
))){
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
;
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
));
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
;
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
;
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
;
1159 #endif /* LOCAL_PASSWD_CACHE */
1164 user
[NETMAXUSER
-1] = '\0';
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
){
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';
1187 * Need space for "Retrying - "
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
1207 strncpy(port
, non_def_port
, sizeof(port
));
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){
1235 len
= strlen(hostname
);
1236 if((p
= strchr(hostname
, '.')) != NULL
){
1238 need
-= (len
- strlen(hostname
));
1243 need
-= utf8_width(retry
);
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
));
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
);
1273 * Reduce space for hostname. Best we can do is 6 chars
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)")){
1284 insecure
[strlen(insecure
)-4] = ')';
1285 insecure
[strlen(insecure
)-3] = '\0';
1288 need
-= utf8_width(insecure
);
1294 if(strlen(defubuf
) > 3){
1295 len
= strlen(defubuf
);
1296 strncpy(defubuf
, " [..] :", 9);
1297 need
-= (len
- strlen(defubuf
));
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';
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;
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
)
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
;
1345 help
= help
== NO_HELP
? h_oe_login
: NO_HELP
;
1350 if(rc
== 0 && !*user
){
1351 strncpy(user
, defuser
, NETMAXUSER
);
1352 user
[NETMAXUSER
-1] = '\0';
1359 if(rc
== 1 || !user
[0]) {
1360 ps_global
->user_says_cancel
= (rc
== 1);
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
;
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
;
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
;
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
;
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
;
1418 #endif /* LOCAL_PASSWD_CACHE */
1422 * Didn't find password in cache or this isn't the first try. Ask user.
1427 * Need space for "Retrying - "
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
1447 strncpy(port
, non_def_port
, sizeof(port
));
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
));
1459 strncpy(logleadin
, usethisprompt
, sizeof(logleadin
));
1460 logleadin
[sizeof(logleadin
)-1] = '\0';
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){
1485 len
= strlen(hostname
);
1486 if((p
= strchr(hostname
, '.')) != NULL
){
1488 need
-= (len
- strlen(hostname
));
1493 need
-= utf8_width(retry
);
1499 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
1503 rplstr(pwleadin
, sizeof(pwleadin
), 1, "");
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
);
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
));
1532 * Reduce space for hostname. Best we can do is 6 chars
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)")){
1543 insecure
[strlen(insecure
)-4] = ')';
1544 insecure
[strlen(insecure
)-3] = '\0';
1547 need
-= utf8_width(insecure
);
1553 len
= utf8_width(logleadin
);
1554 strncpy(logleadin
, " ", sizeof(logleadin
));
1555 logleadin
[sizeof(logleadin
)-1] = '\0';
1556 need
-= (len
- utf8_width(logleadin
));
1559 reduce_to
= (need
- avail
< strlen(defubuf
) - 6) ? (strlen(defubuf
)-(need
-avail
)) : 0;
1561 strncpy(defubuf
+reduce_to
-3, "...", 4);
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';
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
;
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
;
1594 help
= help
== NO_HELP
? h_oe_passwd
: NO_HELP
;
1602 if(rc
== 1 || !tmp
[0]) {
1603 ps_global
->user_says_cancel
= (rc
== 1);
1605 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1606 ps_global
->in_init_seq
= save_in_init
;
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;
1633 mm_login_alt_cue(NETMBX
*mb
)
1635 if(ps_global
->ttyo
){
1638 lastc
= pico_set_colors(ps_global
->VAR_TITLE_FORE_COLOR
,
1639 ps_global
->VAR_TITLE_BACK_COLOR
,
1642 mark_titlebar_dirty();
1643 PutLine0(0, ps_global
->ttyo
->screen_cols
- 1,
1644 (mb
->sslflag
||mb
->tlsflag
) ? "+" : " ");
1647 (void)pico_set_colorp(lastc
, PSC_NONE
);
1648 free_color_pair(&lastc
);
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.
1673 mm_diskerror (MAILSTREAM
*stream
, long int errcode
, long int serious
)
1677 static ESCKEY_S de_opts
[] = {
1678 {'r', 'r', "R", "Retry"},
1679 {'f', 'f', "F", "FileBrowser"},
1680 {'s', 's', "S", "ShellPrompt"},
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>")
1688 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
1689 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
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",
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\".",
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
: "?"));
1717 sp_set_io_error_on_stream(stream
, 1);
1722 /* replace pine's body display with screen full of explanatory text */
1724 PutLine1(2, MAX((DE_COLS
- sizeof(DE_STR1
)
1725 - strlen(DE_FOLDER(stream
)))/2, 0),
1726 DE_STR1
, DE_FOLDER(stream
));
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
))
1745 PutLine0(i
, 0, " ");
1746 while(*p
&& isspace((unsigned char)*p
))
1757 for(j
= ++i
; i
< DE_LINE
&& de_msg
[i
-j
]; i
++){
1759 PutLine0(i
, 0, " ");
1760 Write_to_screen(de_msg
[i
-j
]);
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;
1772 case 'f' : /* File Browser */
1774 char full_filename
[MAXPATH
+1], filename
[MAXPATH
+1];
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
);
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).
1801 init_tty_driver(ps_global
);
1802 init_keyboard(F_ON(F_USE_FK
,ps_global
));
1806 if(ps_global
->redrawer
)
1807 (*ps_global
->redrawer
)();
1813 pine_tcptimeout_noscreen(long int elapsed
, long int sincelast
, char *host
)
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;
1833 ps_global
->tcptimeout
= 0;
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.
1849 pine_tcptimeout(long int elapsed
, long int sincelast
, char *host
)
1851 long rv
= 1L; /* keep trying by default */
1854 ps_global
->tcptimeout
= 1;
1856 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
1857 long2string(elapsed
), host
));
1866 if(ps_global
->noshow_timeout
)
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;
1879 if(!ps_global
->ttyo
)
1880 return(pine_tcptimeout_noscreen(elapsed
, sincelast
, host
));
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
){
1891 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
1892 if((clear_inverse
= !InverseState()) != 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
);
1904 if(ps_global
->read_bail
|| ch
== 'y' || ch
== 'Y'){
1905 ps_global
->read_bail
= 0;
1906 ps_global
->user_says_cancel
= 1;
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;
1931 QUOTALIST
*pine_quotalist_copy (QUOTALIST
*pquota
)
1933 QUOTALIST
*cquota
= NULL
;
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
;
1942 cquota
->next
= pine_quotalist_copy(pquota
->next
);
1948 /* c-client callback to handle quota */
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
1963 * We remember the answer and won't re-ask for subsequent open attempts to
1964 * the same hostname.
1967 pine_sslcertquery(char *reason
, char *host
, char *cert
)
1970 char *unknown
= "<unknown>";
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? */
1992 "sslcertificatequery: approved automatically\n"));
1996 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
1999 if(ps_global
->ttyo
){
2001 STORE_S
*in_store
, *out_store
;
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
)))
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 */
2022 fs_give((void **)&details_host
);
2024 fs_give((void **)&details_reason
);
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
);
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
);
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");
2098 if(the_answer
== 'y')
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;
2107 so_give(&out_store
);
2108 free_handles(&handles
);
2110 fs_give((void **)&details_host
);
2112 fs_give((void **)&details_reason
);
2114 fs_give((void **)&details_cert
);
2118 * If screen hasn't been initialized yet, use want_to.
2121 memset((void *)tmp
, 0, sizeof(tmp
));
2123 reason
? reason
: _("SSL/TLS certificate validation failure"),
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')
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"));
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))
2154 snprintf(buf
, sizeof(buf
),
2155 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
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
);
2166 url_local_certdetails(char *url
)
2168 if(!struncmp(url
, "x-alpine-cert:", 14)){
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."));
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");
2202 so_give(&store
); /* free resources associated with store */
2203 ps_global
->mangled_screen
= 1;
2212 * C-client callback to handle SSL/TLS certificate validation failures
2215 pine_sslfailure(char *host
, char *reason
, long unsigned int flags
)
2219 int the_answer
= 'n', indent
, len
, cols
;
2220 char buf
[500], buf2
[500];
2222 char *hst
= host
? host
: "<unknown>";
2223 char *rsn
= reason
? reason
: "<unknown>";
2224 char *notls
= "/notls";
2226 int ok_novalidate
= 0, warned
= 0;
2229 dprint((1, "sslfailure: host=%s reason=%s\n",
2233 if(flags
& NET_SILENT
)
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 */
2245 snprintf(buf
, sizeof(buf
), _("SSL/TLS failure for %s: %s"), hst
, rsn
);
2246 buf
[sizeof(buf
)-1] = '\0';
2252 cols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
2255 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
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");
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");
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");
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"),
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");
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"),
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");
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");
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
))
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 */
2424 while(so_readc(&c
, store
)){
2427 *qp
++ = cpystr(buf
);
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;
2443 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, ok_novalidate
, 1);
2448 answer_cert_failure(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2452 ps_global
->next_screen
= SCREEN_FUN_NULL
;
2456 *(int *)(sparms
->proc
.data
.p
) = 'y';
2460 *(int *)(sparms
->proc
.data
.p
) = 'n';
2464 alpine_panic("Unexpected command in answer_cert_failure");
2473 oauth2_auth_answer(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
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
;
2487 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
2488 flags
= OE_APPEND_CURRENT
;
2489 sprintf(prompt
, "%s: ", accesscodelabel
);
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
;
2496 rv
= rc
== 1 ? 0 : 1;
2505 alpine_panic("Unexpected command in oauth2_auth_answer");
2508 *(AUTH_CODE_S
*) sparms
->proc
.data
.p
= user
;
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.
2522 set_read_predicted(int i
)
2524 ps_global
->read_predicted
= i
==1;
2526 if(!i
&& F_ON(F_SHOW_DELAY_CUE
, ps_global
))
2527 check_cue_display(" ");
2532 /*----------------------------------------------------------------------
2533 Exported method to retrieve logged in user name associated with stream
2535 Args: host -- host to find associated login name with.
2540 pine_block_notify(int reason
, void *data
)
2543 case BLOCK_SENSITIVE
: /* sensitive code, disallow alarms */
2546 case BLOCK_NONSENSITIVE
: /* non-sensitive code, allow alarms */
2549 case BLOCK_TCPWRITE
: /* blocked on TCP write */
2550 case BLOCK_FILELOCK
: /* blocked on file locking */
2552 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
2553 check_cue_display(">");
2555 mswin_setcursor(MSWIN_CURSOR_BUSY
);
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 */
2564 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
2565 check_cue_display("<");
2567 mswin_setcursor(MSWIN_CURSOR_BUSY
);
2572 case BLOCK_NONE
: /* not blocked */
2574 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
2575 check_cue_display(" ");
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!");
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
2609 #define TABSZ (LASTCH - FIRSTCH + 1)
2611 static int xlate_key
;
2615 * xlate_in() - xlate_in the given character
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
);
2634 * xlate_out() - xlate_out the given character
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;
2653 #endif /* PASSFILE */
2656 #ifdef LOCAL_PASSWD_CACHE
2660 line_get(char *tmp
, size_t len
, char **textp
)
2666 || (s
= strchr(*textp
, '\n')) == NULL
2667 || (s
- *textp
) > len
- 1)
2674 snprintf(tmp
, len
, "%s\n", *textp
);
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.
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
)
2707 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
2710 char *tmp
, *blob
, *target
= NULL
;
2711 char *host
, *user
, *sflags
, *passwd
, *orighost
;
2715 if(using_passfile
== 0)
2716 return(using_passfile
);
2719 if(init_wincred_funcs() != 1){
2721 return(using_passfile
);
2725 dprint((9, "read_passfile\n"));
2729 if(g_CredEnumerateW(lfilter
, 0, &count
, &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
);
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 */
2746 tmp
[i
++] = '\0'; /* tie off data */
2754 blob
= (char *) pcred
[k
]->CredentialBlob
;
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 */
2761 blob
[i
++] = '\0'; /* tie off data */
2768 if(passwd
&& host
&& user
){ /* valid field? */
2769 STRLIST_S hostlist
[2];
2772 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
2773 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
2774 hostlist
[0].name
= host
;
2776 hostlist
[0].next
= &hostlist
[1];
2777 hostlist
[1].name
= orighost
;
2778 hostlist
[1].next
= NULL
;
2781 hostlist
[0].next
= NULL
;
2784 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
2788 fs_give((void **) &target
);
2791 g_CredFree((PVOID
) pcred
);
2797 # else /* old windows */
2804 char target
[MAILTMPLEN
];
2805 char *tmp
, *host
, *user
, *sflags
, *passwd
, *orighost
;
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 */
2822 attrList
.attr
= attrs
;
2825 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
2826 kSecGenericPasswordItemClass
,
2829 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
2831 SecKeychainItemRef itemRef
= NULL
;
2832 SecKeychainAttributeInfo info
;
2833 SecKeychainAttributeList
*attrList
= 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"));
2845 info
.format
= formats
;
2848 * Go through each item we found and put it
2851 while(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
)) && itemRef
){
2852 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
2853 rc
= SecKeychainItemCopyAttributesAndData(itemRef
,
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
){
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';
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 */
2891 tmp
[i
++] = '\0'; /* tie off data */
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 */
2908 blobcopy
[i
++] = '\0'; /* tie off data */
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];
2924 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
2925 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
2926 hostlist
[0].name
= host
;
2928 hostlist
[0].next
= &hostlist
[1];
2929 hostlist
[1].name
= orighost
;
2930 hostlist
[1].next
= NULL
;
2933 hostlist
[0].next
= NULL
;
2936 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
2940 fs_give((void **) & blobcopy
);
2942 SecKeychainItemFreeAttributesAndData(attrList
, (void *) blob
);
2946 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc
));
2953 CFRelease(searchRef
);
2957 dprint((10, "read_passfile: searchRef NULL\n"));
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;
2972 char *tmptext
= NULL
;
2974 char tmp2
[MAILTMPLEN
];
2975 char *text
= NULL
, *text2
= NULL
;
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"))){
2988 return(using_passfile
);
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
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.
3011 fgets(tmp2
, sizeof(tmp2
), 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. ");
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
))
3041 if(ps_global
->pwdcert
== NULL
)
3042 rv
= setup_pwdcert(&ps_global
->pwdcert
);
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.
3059 text
= text2
= decrypt_file((char *)tmp
, &i
, (PERSONAL_CERT
*)ps_global
->pwdcert
);
3060 len
= text2
? strlen(text2
) : 0;
3062 case -2: using_passfile
= 0;
3065 case 1 : save_password
= 1;
3069 case -1: save_password
= 0;
3078 if(our_stat(tmp
, &sbuf
) == 0)
3082 fp
= our_fopen(tmp
, "rb"); /* reopen to read data */
3086 if(using_passfile
== 0){
3088 if(text
) fs_give((void **)&text
);
3090 return using_passfile
;
3094 tmptext
= fs_get(len
+ 1);
3096 for(n
= 0; encrypted
? line_get(tmptext
, len
+ 1, &text2
)
3097 : (fgets(tmptext
, len
+1, fp
) != NULL
); n
++){
3099 for(n
= 0; fgets(tmptext
, len
+1, fp
); n
++){
3101 /*** do any necessary DEcryption here ***/
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 */
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];
3127 hostlist
[0].next
= &hostlist
[1];
3128 hostlist
[1].name
= ui
[4];
3129 hostlist
[1].next
= NULL
;
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
);
3142 if (text
) fs_give((void **)&text
);
3147 #endif /* PASSFILE */
3153 write_passfile(pinerc
, l
)
3157 char *authend
, *authtype
;
3160 char target
[MAILTMPLEN
];
3161 char blob
[MAILTMPLEN
];
3165 if(using_passfile
== 0)
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
){
3175 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3176 *authend
= PWDAUTHSEP
;
3179 sprintf(blob
, "%d", l
->altflag
);
3181 snprintf(target
, sizeof(target
), "%s%s\t%s\t%s",
3183 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3184 l
->user
? l
->user
: "",
3186 ltarget
= utf8_to_lptstr((LPSTR
) target
);
3189 snprintf(blob
, sizeof(blob
), "%s%s%s",
3190 l
->passwd
? l
->passwd
: "",
3191 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3193 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3194 ? l
->hosts
->next
->name
: "");
3195 memset((void *) &cred
, 0, sizeof(cred
));
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 **) <arget
);
3207 #endif /* WINCRED > 0 */
3211 char target
[MAILTMPLEN
];
3212 char blob
[MAILTMPLEN
];
3213 SecKeychainItemRef itemRef
= NULL
;
3215 if(using_passfile
== 0)
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
){
3225 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3226 *authend
= PWDAUTHSEP
;
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
: "",
3236 snprintf(blob
, sizeof(blob
), "%s%s%s",
3237 l
->passwd
? l
->passwd
: "",
3238 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
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
,
3251 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
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"));
3261 if(!(rc
=SecKeychainFindGenericPassword(NULL
,
3262 strlen(target
), target
,
3263 strlen(TNAME
), TNAME
,
3265 &itemRef
)) && itemRef
){
3267 rc
= SecKeychainItemModifyAttributesAndData(itemRef
, NULL
, strlen(blob
), blob
);
3269 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc
));
3273 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc
));
3278 #else /* PASSFILE */
3279 char tmp
[4*MAILTMPLEN
], blob
[4*MAILTMPLEN
];
3283 char *text
= NULL
, tmp2
[4*MAILTMPLEN
];
3287 if(using_passfile
== 0)
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"))){
3299 strncpy(tmp2
, tmp
, sizeof(tmp2
));
3300 tmp2
[sizeof(tmp2
)-1] = '\0';
3303 for(n
= 0; l
; l
= l
->next
, n
++){
3304 authtype
= l
->passwd
;
3305 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3306 if(authend
!= NULL
){
3308 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3309 *authend
= PWDAUTHSEP
;
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
3320 dprint((10, "write_passfile: %s", tmp
? tmp
: "?"));
3322 for(i
= 0; tmp
[i
]; i
++)
3323 tmp
[i
] = xlate_in(tmp
[i
]);
3326 fs_resize((void **)&text
, (len
+ strlen(tmp
) + 1)*sizeof(char));
3328 len
+= strlen(tmp
) + 1;
3329 strncat(text
, tmp
, strlen(tmp
));
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 */
3346 q_status_message1(SM_ORDER
, 3, 3, "Error: no directory %s to save certificates", ps_global
->pwdcertdir
);
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 */
3355 #endif /* PASSFILE */
3358 #endif /* LOCAL_PASSWD_CACHE */
3363 erase_windows_credentials(void)
3365 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3370 if(init_wincred_funcs() != 1)
3374 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
3376 for(k
= 0; k
< count
; k
++)
3377 g_CredDeleteW(pcred
[k
]->TargetName
, CRED_TYPE_GENERIC
, 0);
3379 g_CredFree((PVOID
) pcred
);
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");
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
;
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
;
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
)
3428 free_passfile_cache_work(MMLOGIN_S
**pwdcache
)
3430 if(pwdcache
== NULL
|| *pwdcache
== NULL
)
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
);
3442 free_passfile_cache(void)
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.
3459 get_passfile_user(pinerc
, hostlist
)
3461 STRLIST_S
*hostlist
;
3463 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
3464 ? imap_get_user(passfile_cache
, hostlist
)
3470 preserve_prompt(char *pinerc
)
3472 return preserve_prompt_auth(pinerc
, NULL
);
3476 preserve_prompt_auth(char *pinerc
, char *authtype
)
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
)
3493 'y', 'x', NO_HELP
, WT_NORM
)
3503 #define PROMPT_PWD _("Preserve password for next login")
3504 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3507 if((rc
= macos_store_pass_prompt()) != 0){
3509 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
3510 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
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"));
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"));
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
];
3534 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || our_stat(tmp
, &sbuf
) < 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
)
3543 #endif /* PASSFILE */
3546 #endif /* LOCAL_PASSWD_CACHE */
3549 #ifdef APPLEKEYCHAIN
3553 * 1 if store pass prompt is set in the "registry" to on
3555 * -1 if not set to anything
3558 macos_store_pass_prompt(void)
3565 if(storepassprompt
== -1){
3566 if(!(rc
=SecKeychainFindGenericPassword(NULL
, 0, NULL
,
3567 strlen(TNAMEPROMPT
),
3569 (void **) &data
, NULL
))){
3570 val
= (len
== 1 && data
&& data
[0] == '1');
3574 if(storepassprompt
== -1 && !rc
){
3576 storepassprompt
= 1;
3578 storepassprompt
= 0;
3581 return(storepassprompt
);
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
);
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 */
3616 attrList
.attr
= attrs1
;
3618 if(!SecKeychainSearchCreateFromAttributes(NULL
,
3619 kSecGenericPasswordItemClass
,
3623 SecKeychainItemRef itemRef
= NULL
;
3626 * Go through each item we found and put it
3629 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
3630 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3631 SecKeychainItemDelete(itemRef
);
3635 CFRelease(searchRef
);
3640 attrList
.attr
= attrs2
;
3642 if(!SecKeychainSearchCreateFromAttributes(NULL
,
3643 kSecGenericPasswordItemClass
,
3647 SecKeychainItemRef itemRef
= NULL
;
3650 * Go through each item we found and put it
3653 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
3654 SecKeychainItemDelete(itemRef
);
3658 CFRelease(searchRef
);
3663 #endif /* APPLEKEYCHAIN */
3665 #ifdef LOCAL_PASSWD_CACHE
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
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
;
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
);
3701 update_passfile_hostlist(pinerc
, user
, hostlist
, altflag
)
3704 STRLIST_S
*hostlist
;
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.
3719 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, authtype
)
3722 STRLIST_S
*hostlist
;
3728 #else /* !WINCRED */
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
)
3736 && !strcmp(user
, l
->user
+ len
+ offset
)
3737 && l
->altflag
== altflag
){
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 */
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)
3766 /* Assume the worst. */
3769 hmod
= LoadLibrary(TEXT("advapi32.dll"));
3772 FARPROC fpCredWriteW
;
3773 FARPROC fpCredEnumerateW
;
3774 FARPROC fpCredDeleteW
;
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
;
3793 mswin_set_erasecreds_callback(ask_erase_credentials
);
3796 return g_CredInited
;
3799 #endif /* WINCRED */