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-2018 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
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
42 #include "../pith/state.h"
43 #include "../pith/conf.h"
44 #include "../pith/msgno.h"
45 #include "../pith/filter.h"
46 #include "../pith/news.h"
47 #include "../pith/util.h"
48 #include "../pith/list.h"
49 #include "../pith/margin.h"
51 #include "../pith/smime.h"
56 #define TNAME "UWash_Alpine_"
57 #define TNAMESTAR "UWash_Alpine_*"
60 * WinCred Function prototypes
62 typedef BOOL (WINAPI CREDWRITEW
) ( __in PCREDENTIALW Credential
, __in DWORD Flags
);
63 typedef BOOL (WINAPI CREDENUMERATEW
) ( __in LPCWSTR Filter
, __reserved DWORD Flags
,
64 __out DWORD
*Count
, __deref_out_ecount(*Count
) PCREDENTIALW
**Credential
);
65 typedef BOOL (WINAPI CREDDELETEW
) ( __in LPCWSTR TargetName
, __in DWORD Type
,
66 __reserved DWORD Flags
);
67 typedef VOID (WINAPI CREDFREE
) ( __in PVOID Buffer
);
72 int g_CredInited
= 0; /* 1 for loaded successfully,
73 * -1 for not available.
74 * 0 for not initialized yet.
76 CREDWRITEW
*g_CredWriteW
;
77 CREDENUMERATEW
*g_CredEnumerateW
;
78 CREDDELETEW
*g_CredDeleteW
;
84 #include <Security/SecKeychain.h>
85 #include <Security/SecKeychainItem.h>
86 #include <Security/SecKeychainSearch.h>
87 #define TNAME "UWash_Alpine"
88 #define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
90 int macos_store_pass_prompt(void);
91 void macos_set_store_pass_prompt(int);
93 static int storepassprompt
= -1;
94 #endif /* APPLEKEYCHAIN */
100 void mm_login_alt_cue(NETMBX
*);
101 long pine_tcptimeout_noscreen(long, long, char *);
102 int answer_cert_failure(int, MSGNO_S
*, SCROLL_S
*);
104 #ifdef LOCAL_PASSWD_CACHE
105 int read_passfile(char *, MMLOGIN_S
**);
106 void write_passfile(char *, MMLOGIN_S
*);
107 int preserve_prompt(char *);
108 void update_passfile_hostlist(char *, char *, STRLIST_S
*, int);
109 void free_passfile_cache_work(MMLOGIN_S
**);
111 static MMLOGIN_S
*passfile_cache
= NULL
;
112 static int using_passfile
= -1;
113 int save_password
= 1;
114 #endif /* LOCAL_PASSWD_CACHE */
118 char xlate_out(char);
119 int line_get(char *, size_t, char **);
120 #endif /* PASSFILE */
123 void ask_erase_credentials(void);
124 int init_wincred_funcs(void);
128 static char *details_cert
, *details_host
, *details_reason
;
131 /*----------------------------------------------------------------------
132 recieve notification from IMAP
134 Args: stream -- Mail stream message is relavant to
135 string -- The message text
136 errflg -- Set if it is a serious error
138 Result: message displayed in status line
140 The facility is for general notices, such as connection to server;
141 server shutting down etc... It is used infrequently.
142 ----------------------------------------------------------------------*/
144 mm_notify(MAILSTREAM
*stream
, char *string
, long int errflg
)
149 now
= time((time_t *)0);
150 tm_now
= localtime(&now
);
152 /* be sure to log the message... */
154 if(ps_global
->debug_imap
|| ps_global
->debugmem
)
155 dprint((errflg
== TCPDEBUG
? 7 : 2,
156 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
157 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
158 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
159 (!errflg
) ? "babble" :
160 (errflg
== ERROR
) ? "error" :
161 (errflg
== WARN
) ? "warning" :
162 (errflg
== PARSE
) ? "parse" :
163 (errflg
== TCPDEBUG
) ? "tcp" :
164 (errflg
== BYE
) ? "bye" : "unknown",
165 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
166 string
? string
: "?"));
169 snprintf(ps_global
->last_error
, sizeof(ps_global
->last_error
), "%s : %.*s",
170 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
171 (int) MIN(MAX_SCREEN_COLS
, sizeof(ps_global
->last_error
)-70),
173 ps_global
->last_error
[ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
174 : sizeof(ps_global
->last_error
)-1] = '\0';
177 * Then either set special bits in the pine struct or
178 * display the message if it's tagged as an "ALERT" or
179 * its errflg > NIL (i.e., WARN, or ERROR)
183 * We'd like to sp_mark_stream_dead() here but we can't do that because
184 * that might call mail_close and we are already in a c-client callback.
185 * So just set the dead bit and clean it up later.
187 sp_set_dead_stream(stream
, 1);
188 else if(!strncmp(string
, "[TRYCREATE]", 11))
189 ps_global
->try_to_create
= 1;
190 else if(!strncmp(string
, "[REFERRAL ", 10))
191 ; /* handled in the imap_referral() callback */
192 else if(!strncmp(string
, "[ALERT]", 7))
193 q_status_message2(SM_MODAL
, 3, 3,
194 _("Alert received while accessing \"%s\": %s"),
195 (stream
&& stream
->mailbox
)
196 ? stream
->mailbox
: "-no folder-",
197 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf
+10000),
198 SIZEOF_20KBUF
-10000, string
));
199 else if(!strncmp(string
, "[UNSEEN ", 8)){
203 for(p
= string
+ 8; isdigit(*p
); p
++)
204 n
= (n
* 10) + (*p
- '0');
206 sp_set_first_unseen(stream
, n
);
208 else if(!strncmp(string
, "[READ-ONLY]", 11)
209 && !(stream
&& stream
->mailbox
&& IS_NEWS(stream
)))
210 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, "%s : %s",
211 (stream
&& stream
->mailbox
)
212 ? stream
->mailbox
: "-no folder-",
214 else if((errflg
&& errflg
!= BYE
&& errflg
!= PARSE
)
215 && !ps_global
->noshow_error
217 && (ps_global
->noshow_warn
|| (stream
&& stream
->unhealthy
))))
218 q_status_message(SM_ORDER
| ((errflg
== ERROR
) ? SM_DING
: 0),
219 3, 6, ps_global
->last_error
);
223 /*----------------------------------------------------------------------
224 Queue imap log message for display in the message line
226 Args: string -- The message
227 errflg -- flag set to 1 if pertains to an error
229 Result: Message queued for display
231 The c-client/imap reports most of it's status and errors here
234 mm_log(char *string
, long int errflg
)
236 char message
[sizeof(ps_global
->c_client_error
)];
239 static char saw_kerberos_init_warning
;
243 now
= time((time_t *)0);
244 tm_now
= localtime(&now
);
246 dprint((((errflg
== TCPDEBUG
) && ps_global
->debug_tcp
) ? 1 :
247 (errflg
== TCPDEBUG
) ? 10 : 2,
248 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
249 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
250 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
251 (!errflg
) ? "babble" :
252 (errflg
== ERROR
) ? "error" :
253 (errflg
== WARN
) ? "warning" :
254 (errflg
== PARSE
) ? "parse" :
255 (errflg
== TCPDEBUG
) ? "tcp" :
256 (errflg
== BYE
) ? "bye" : "unknown",
257 string
? string
: "?"));
259 if(errflg
== ERROR
&& !strncmp(string
, "[TRYCREATE]", 11)){
260 ps_global
->try_to_create
= 1;
263 else if(ps_global
->try_to_create
264 || !strncmp(string
, "[CLOSED]", 8)
265 || (sp_dead_stream(ps_global
->mail_stream
) && strstr(string
, "No-op")))
267 * Don't display if creating new folder OR
268 * warning about a dead stream ...
272 strncpy(message
, string
, sizeof(message
));
273 message
[sizeof(message
) - 1] = '\0';
275 if(errflg
== WARN
&& srchstr(message
, "try running kinit") != NULL
){
276 if(saw_kerberos_init_warning
)
279 saw_kerberos_init_warning
= 1;
282 /*---- replace all "mailbox" with "folder" ------*/
283 occurence
= srchstr(message
, "mailbox");
286 || isspace((unsigned char) *(occurence
+7))
287 || *(occurence
+7) == ':'){
288 was_capitalized
= isupper((unsigned char) *occurence
);
289 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 7, (errflg
== PARSE
? "address" : "folder"));
291 *occurence
= (errflg
== PARSE
? 'A' : 'F');
296 occurence
= srchstr(occurence
, "mailbox");
299 /*---- replace all "GSSAPI" with "Kerberos" ------*/
300 occurence
= srchstr(message
, "GSSAPI");
303 || isspace((unsigned char) *(occurence
+6))
304 || *(occurence
+6) == ':')
305 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 6, "Kerberos");
309 occurence
= srchstr(occurence
, "GSSAPI");
313 ps_global
->mm_log_error
= 1;
315 if(errflg
== PARSE
|| (errflg
== ERROR
&& ps_global
->noshow_error
))
316 strncpy(ps_global
->c_client_error
, message
,
317 sizeof(ps_global
->c_client_error
));
319 if(ps_global
->noshow_error
320 || (ps_global
->noshow_warn
&& errflg
== WARN
)
321 || !(errflg
== ERROR
|| errflg
== WARN
))
322 return; /* Only care about errors; don't print when asked not to */
324 /*---- Display the message ------*/
325 q_status_message((errflg
== ERROR
) ? (SM_ORDER
| SM_DING
) : SM_ORDER
,
327 strncpy(ps_global
->last_error
, message
, sizeof(ps_global
->last_error
));
328 ps_global
->last_error
[sizeof(ps_global
->last_error
) - 1] = '\0';
333 mm_login_work(NETMBX
*mb
, char *user
, char *pwd
, long int trial
,
334 char *usethisprompt
, char *altuserforcache
)
336 char prompt
[1000], *last
;
337 char port
[20], non_def_port
[20], insecure
[20];
338 char defuser
[NETMAXUSER
];
339 char hostleadin
[80], hostname
[200], defubuf
[200];
340 char logleadin
[80], pwleadin
[50];
341 char hostlist0
[MAILTMPLEN
], hostlist1
[MAILTMPLEN
];
342 /* TRANSLATORS: when logging in, this text is added to the prompt to show
343 that the password will be sent unencrypted over the network. This is
344 just a warning message that gets added parenthetically when the user
345 is asked for a password. */
346 char *insec
= _(" (INSECURE)");
347 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
348 after having already failed at least once. */
349 char *retry
= _("Retrying - ");
350 /* TRANSLATORS: A label for the hostname that the user is logging in on */
351 char *hostlabel
= _("HOST");
352 /* TRANSLATORS: user is logging in as a particular user (a particular
353 login name), this is just labelling that user name. */
354 char *userlabel
= _("USER");
355 STRLIST_S hostlist
[2];
357 int len
, rc
, q_line
, flags
;
358 int oespace
, avail
, need
, save_dont_use
;
361 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
362 int preserve_password
= -1;
365 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
366 trial
, mb
->user
? mb
->user
: "(null)",
367 mb
->service
? mb
->service
: "(null)",
368 mb
->port
? " port=" : "",
369 mb
->port
? comatose(mb
->port
) : "",
370 altuserforcache
? " altuserforcache =" : "",
371 altuserforcache
? altuserforcache
: ""));
372 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
374 save_in_init
= ps_global
->in_init_seq
;
375 ps_global
->in_init_seq
= 0;
376 ps_global
->no_newmail_check_from_optionally_enter
= 1;
378 /* make sure errors are seen */
380 flush_status_messages(0);
383 * Add port number to hostname if going through a tunnel or something
385 non_def_port
[0] = '\0';
386 if(mb
->port
&& mb
->service
&&
387 (sv
= getservbyname(mb
->service
, "tcp")) &&
388 (mb
->port
!= ntohs(sv
->s_port
))){
389 snprintf(non_def_port
, sizeof(non_def_port
), ":%lu", mb
->port
);
390 non_def_port
[sizeof(non_def_port
)-1] = '\0';
391 dprint((9, "mm_login: using non-default port=%s\n",
392 non_def_port
? non_def_port
: "?"));
396 * set up host list for sybil servers...
399 strncpy(hostlist0
, mb
->host
, sizeof(hostlist0
)-1);
400 hostlist0
[sizeof(hostlist0
)-1] = '\0';
401 strncat(hostlist0
, non_def_port
, sizeof(hostlist0
)-strlen(hostlist0
)-1);
402 hostlist0
[sizeof(hostlist0
)-1] = '\0';
403 hostlist
[0].name
= hostlist0
;
404 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
405 strncpy(hostlist1
, mb
->orighost
, sizeof(hostlist1
)-1);
406 hostlist1
[sizeof(hostlist1
)-1] = '\0';
407 strncat(hostlist1
, non_def_port
, sizeof(hostlist1
)-strlen(hostlist1
)-1);
408 hostlist1
[sizeof(hostlist1
)-1] = '\0';
409 hostlist
[0].next
= &hostlist
[1];
410 hostlist
[1].name
= hostlist1
;
411 hostlist
[1].next
= NULL
;
414 hostlist
[0].next
= NULL
;
417 hostlist
[0].name
= mb
->host
;
418 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
419 hostlist
[0].next
= &hostlist
[1];
420 hostlist
[1].name
= mb
->orighost
;
421 hostlist
[1].next
= NULL
;
424 hostlist
[0].next
= NULL
;
427 if(hostlist
[0].name
){
428 dprint((9, "mm_login: host=%s\n",
429 hostlist
[0].name
? hostlist
[0].name
: "?"));
430 if(hostlist
[0].next
&& hostlist
[1].name
){
431 dprint((9, "mm_login: orighost=%s\n", hostlist
[1].name
));
436 * Initialize user name with either
437 * 1) /user= value in the stream being logged into,
438 * or 2) the user name we're running under.
440 * Note that VAR_USER_ID is not yet initialized if this login is
441 * the one to access the remote config file. In that case, the user
442 * can supply the username in the config file name with /user=.
444 if(trial
== 0L && !altuserforcache
){
445 strncpy(user
, (*mb
->user
) ? mb
->user
:
446 ps_global
->VAR_USER_ID
? ps_global
->VAR_USER_ID
: "",
448 user
[NETMAXUSER
-1] = '\0';
450 /* try last working password associated with this host. */
451 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
452 (mb
->sslflag
||mb
->tlsflag
))){
453 dprint((9, "mm_login: found a password to try\n"));
454 ps_global
->no_newmail_check_from_optionally_enter
= 0;
455 ps_global
->in_init_seq
= save_in_init
;
459 #ifdef LOCAL_PASSWD_CACHE
460 /* check to see if there's a password left over from last session */
461 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
462 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
463 imap_set_passwd(&mm_login_list
, pwd
, user
,
464 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
465 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
466 (mb
->sslflag
||mb
->tlsflag
));
467 dprint((9, "mm_login: found a password in passfile to try\n"));
468 ps_global
->no_newmail_check_from_optionally_enter
= 0;
469 ps_global
->in_init_seq
= save_in_init
;
472 #endif /* LOCAL_PASSWD_CACHE */
475 * If no explicit user name supplied and we've not logged in
476 * with our local user name, see if we've visited this
477 * host before as someone else.
480 ((last
= imap_get_user(mm_login_list
, hostlist
))
481 #ifdef LOCAL_PASSWD_CACHE
483 (last
= get_passfile_user(ps_global
->pinerc
, hostlist
))
484 #endif /* LOCAL_PASSWD_CACHE */
486 strncpy(user
, last
, NETMAXUSER
);
487 user
[NETMAXUSER
-1] = '\0';
488 dprint((9, "mm_login: found user=%s\n",
491 /* try last working password associated with this host/user. */
492 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
493 (mb
->sslflag
||mb
->tlsflag
))){
495 "mm_login: found a password for user=%s to try\n",
497 ps_global
->no_newmail_check_from_optionally_enter
= 0;
498 ps_global
->in_init_seq
= save_in_init
;
502 #ifdef LOCAL_PASSWD_CACHE
503 /* check to see if there's a password left over from last session */
504 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
505 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
506 imap_set_passwd(&mm_login_list
, pwd
, user
,
507 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
508 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
509 (mb
->sslflag
||mb
->tlsflag
));
511 "mm_login: found a password for user=%s in passfile to try\n",
513 ps_global
->no_newmail_check_from_optionally_enter
= 0;
514 ps_global
->in_init_seq
= save_in_init
;
517 #endif /* LOCAL_PASSWD_CACHE */
520 #if !defined(DOS) && !defined(OS2)
521 if(!*mb
->user
&& !*user
&&
522 (last
= (ps_global
->ui
.login
&& ps_global
->ui
.login
[0])
523 ? ps_global
->ui
.login
: NULL
)
525 strncpy(user
, last
, NETMAXUSER
);
526 user
[NETMAXUSER
-1] = '\0';
527 dprint((9, "mm_login: found user=%s\n",
530 /* try last working password associated with this host. */
531 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
532 (mb
->sslflag
||mb
->tlsflag
))){
533 dprint((9, "mm_login:ui: found a password to try\n"));
534 ps_global
->no_newmail_check_from_optionally_enter
= 0;
535 ps_global
->in_init_seq
= save_in_init
;
539 #ifdef LOCAL_PASSWD_CACHE
540 /* check to see if there's a password left over from last session */
541 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
542 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
543 imap_set_passwd(&mm_login_list
, pwd
, user
,
544 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
545 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
546 (mb
->sslflag
||mb
->tlsflag
));
547 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
548 ps_global
->no_newmail_check_from_optionally_enter
= 0;
549 ps_global
->in_init_seq
= save_in_init
;
552 #endif /* LOCAL_PASSWD_CACHE */
557 user
[NETMAXUSER
-1] = '\0';
563 * Even if we have a user now, user gets a chance to change it.
565 ps_global
->mangled_footer
= 1;
566 if(!*mb
->user
&& !altuserforcache
){
571 * Instead of offering user with a value that the user can edit,
572 * we offer [user] as a default so that the user can type CR to
573 * use it. Otherwise, the user has to type in whole name.
575 strncpy(defuser
, user
, sizeof(defuser
)-1);
576 defuser
[sizeof(defuser
)-1] = '\0';
580 * Need space for "Retrying - "
586 * about 15 chars for input
589 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
590 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
591 hostleadin
[sizeof(hostleadin
)-1] = '\0';
593 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
594 hostname
[sizeof(hostname
)-1] = '\0';
597 * Add port number to hostname if going through a tunnel or something
600 strncpy(port
, non_def_port
, sizeof(port
));
605 /* if not encrypted and SSL/TLS is supported */
606 if(!(mb
->sslflag
||mb
->tlsflag
) &&
607 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
608 strncpy(insecure
, insec
, sizeof(insecure
));
610 /* TRANSLATORS: user is being asked to type in their login name */
611 snprintf(logleadin
, sizeof(logleadin
), " %s", _("ENTER LOGIN NAME"));
613 snprintf(defubuf
, sizeof(defubuf
), "%s%s%s : ", (*defuser
) ? " [" : "",
614 (*defuser
) ? defuser
: "",
615 (*defuser
) ? "]" : "");
616 defubuf
[sizeof(defubuf
)-1] = '\0';
617 /* space reserved after prompt */
618 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
620 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
621 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
622 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) + oespace
;
624 /* If we're retrying cut the hostname back to the first word. */
625 if(avail
< need
&& trial
> 0){
628 len
= strlen(hostname
);
629 if((p
= strchr(hostname
, '.')) != NULL
){
631 need
-= (len
- strlen(hostname
));
636 need
-= utf8_width(retry
);
641 /* reduce length of logleadin */
642 len
= utf8_width(logleadin
);
643 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
644 longer version doesn't fit on screen */
645 snprintf(logleadin
, sizeof(logleadin
), " %s", _("LOGIN"));
646 need
-= (len
- utf8_width(logleadin
));
649 /* get two spaces from hostleadin */
650 len
= utf8_width(hostleadin
);
651 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
652 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
653 hostleadin
[sizeof(hostleadin
)-1] = '\0';
654 need
-= (len
- utf8_width(hostleadin
));
656 /* get rid of port */
657 if(avail
< need
&& strlen(port
) > 0){
658 need
-= strlen(port
);
666 * Reduce space for hostname. Best we can do is 6 chars
669 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
670 len
= strlen(hostname
);
671 strncpy(hostname
+reduce_to
-3, "...", 4);
672 need
-= (len
- strlen(hostname
));
674 if(avail
< need
&& strlen(insecure
) > 0){
675 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
677 insecure
[strlen(insecure
)-4] = ')';
678 insecure
[strlen(insecure
)-3] = '\0';
681 need
-= utf8_width(insecure
);
687 if(strlen(defubuf
) > 3){
688 len
= strlen(defubuf
);
689 strncpy(defubuf
, " [..] :", 9);
690 need
-= (len
- strlen(defubuf
));
694 strncpy(defubuf
, ":", 2);
697 * If it still doesn't fit, optionally_enter gets
706 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s",
707 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
);
708 prompt
[sizeof(prompt
)-1] = '\0';
712 mm_login_alt_cue(mb
);
714 flags
= OE_APPEND_CURRENT
;
715 save_dont_use
= ps_global
->dont_use_init_cmds
;
716 ps_global
->dont_use_init_cmds
= 1;
718 if(!*user
&& *defuser
){
719 strncpy(user
, defuser
, NETMAXUSER
);
720 user
[NETMAXUSER
-1] = '\0';
723 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, pwd
, NETMAXPASSWD
,
724 #ifdef LOCAL_PASSWD_CACHE
725 is_using_passfile() ? 1 :
726 #endif /* LOCAL_PASSWD_CACHE */
727 0, 0, &preserve_password
);
728 ps_global
->dont_use_init_cmds
= save_dont_use
;
729 if(rc
== 0 && *user
&& *pwd
)
731 #else /* !_WINDOWS */
732 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
733 prompt
, NULL
, help
, &flags
);
734 #endif /* !_WINDOWS */
735 ps_global
->dont_use_init_cmds
= save_dont_use
;
738 help
= help
== NO_HELP
? h_oe_login
: NO_HELP
;
743 if(rc
== 0 && !*user
){
744 strncpy(user
, defuser
, NETMAXUSER
);
745 user
[NETMAXUSER
-1] = '\0';
752 if(rc
== 1 || !user
[0]) {
753 ps_global
->user_says_cancel
= (rc
== 1);
759 strncpy(user
, mb
->user
, NETMAXUSER
);
760 user
[NETMAXUSER
-1] = '\0';
763 user
[NETMAXUSER
-1] = '\0';
764 pwd
[NETMAXPASSWD
-1] = '\0';
766 if(!(user
[0] || altuserforcache
)){
767 ps_global
->no_newmail_check_from_optionally_enter
= 0;
768 ps_global
->in_init_seq
= save_in_init
;
773 * Now that we have a user, we can check in the cache again to see
774 * if there is a password there. Try last working password associated
775 * with this host and user.
777 if(trial
== 0L && !*mb
->user
&& !altuserforcache
){
778 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
779 (mb
->sslflag
||mb
->tlsflag
))){
780 ps_global
->no_newmail_check_from_optionally_enter
= 0;
781 ps_global
->in_init_seq
= save_in_init
;
785 #ifdef LOCAL_PASSWD_CACHE
786 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
787 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
788 imap_set_passwd(&mm_login_list
, pwd
, user
,
789 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
790 ps_global
->no_newmail_check_from_optionally_enter
= 0;
791 ps_global
->in_init_seq
= save_in_init
;
794 #endif /* LOCAL_PASSWD_CACHE */
796 else if(trial
== 0 && altuserforcache
){
797 if(imap_get_passwd(mm_login_list
, pwd
, altuserforcache
, hostlist
,
798 (mb
->sslflag
||mb
->tlsflag
))){
799 ps_global
->no_newmail_check_from_optionally_enter
= 0;
800 ps_global
->in_init_seq
= save_in_init
;
804 #ifdef LOCAL_PASSWD_CACHE
805 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
806 altuserforcache
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
807 imap_set_passwd(&mm_login_list
, pwd
, altuserforcache
,
808 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
809 ps_global
->no_newmail_check_from_optionally_enter
= 0;
810 ps_global
->in_init_seq
= save_in_init
;
813 #endif /* LOCAL_PASSWD_CACHE */
817 * Didn't find password in cache or this isn't the first try. Ask user.
822 * Need space for "Retrying - "
828 * " ENTER PASSWORD: "
829 * about 15 chars for input
832 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
833 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
835 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
836 hostname
[sizeof(hostname
)-1] = '\0';
839 * Add port number to hostname if going through a tunnel or something
842 strncpy(port
, non_def_port
, sizeof(port
));
848 /* if not encrypted and SSL/TLS is supported */
849 if(!(mb
->sslflag
||mb
->tlsflag
) &&
850 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
851 strncpy(insecure
, insec
, sizeof(insecure
));
854 strncpy(logleadin
, usethisprompt
, sizeof(logleadin
));
855 logleadin
[sizeof(logleadin
)-1] = '\0';
860 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
862 strncpy(defubuf
, user
, sizeof(defubuf
)-1);
863 defubuf
[sizeof(defubuf
)-1] = '\0';
866 /* TRANSLATORS: user is being asked to type in their password */
867 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("ENTER PASSWORD"));
869 /* space reserved after prompt */
870 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
872 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
873 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
874 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) +
875 utf8_width(pwleadin
) + oespace
;
877 if(avail
< need
&& trial
> 0){
880 len
= strlen(hostname
);
881 if((p
= strchr(hostname
, '.')) != NULL
){
883 need
-= (len
- strlen(hostname
));
888 need
-= utf8_width(retry
);
894 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
898 rplstr(pwleadin
, sizeof(pwleadin
), 1, "");
902 /* get two spaces from hostleadin */
903 len
= utf8_width(hostleadin
);
904 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
905 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
906 hostleadin
[sizeof(hostleadin
)-1] = '\0';
907 need
-= (len
- utf8_width(hostleadin
));
909 /* get rid of port */
910 if(avail
< need
&& strlen(port
) > 0){
911 need
-= strlen(port
);
916 len
= utf8_width(pwleadin
);
917 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
918 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("PASSWORD"));
919 need
-= (len
- utf8_width(pwleadin
));
927 * Reduce space for hostname. Best we can do is 6 chars
930 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
931 len
= strlen(hostname
);
932 strncpy(hostname
+reduce_to
-3, "...", 4);
933 need
-= (len
- strlen(hostname
));
935 if(avail
< need
&& strlen(insecure
) > 0){
936 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
938 insecure
[strlen(insecure
)-4] = ')';
939 insecure
[strlen(insecure
)-3] = '\0';
942 need
-= utf8_width(insecure
);
948 len
= utf8_width(logleadin
);
949 strncpy(logleadin
, " ", sizeof(logleadin
));
950 logleadin
[sizeof(logleadin
)-1] = '\0';
951 need
-= (len
- utf8_width(logleadin
));
954 reduce_to
= (need
- avail
< strlen(defubuf
) - 6) ? (strlen(defubuf
)-(need
-avail
)) : 0;
956 strncpy(defubuf
+reduce_to
-3, "...", 4);
965 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s%s",
966 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
, pwleadin
);
967 prompt
[sizeof(prompt
)-1] = '\0';
972 mm_login_alt_cue(mb
);
974 save_dont_use
= ps_global
->dont_use_init_cmds
;
975 ps_global
->dont_use_init_cmds
= 1;
976 flags
= F_ON(F_QUELL_ASTERISKS
, ps_global
) ? OE_PASSWD_NOAST
: OE_PASSWD
;
978 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, pwd
, NETMAXPASSWD
, 0, 1,
980 #else /* !_WINDOWS */
981 rc
= optionally_enter(pwd
, q_line
, 0, NETMAXPASSWD
,
982 prompt
, NULL
, help
, &flags
);
983 #endif /* !_WINDOWS */
984 ps_global
->dont_use_init_cmds
= save_dont_use
;
987 help
= help
== NO_HELP
? h_oe_passwd
: NO_HELP
;
995 if(rc
== 1 || !pwd
[0]) {
996 ps_global
->user_says_cancel
= (rc
== 1);
997 user
[0] = pwd
[0] = '\0';
998 ps_global
->no_newmail_check_from_optionally_enter
= 0;
999 ps_global
->in_init_seq
= save_in_init
;
1006 /* remember the password for next time */
1007 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
1008 imap_set_passwd(&mm_login_list
, pwd
,
1009 altuserforcache
? altuserforcache
: user
, hostlist
,
1010 (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1011 #ifdef LOCAL_PASSWD_CACHE
1012 /* if requested, remember it on disk for next session */
1013 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
1014 set_passfile_passwd(ps_global
->pinerc
, pwd
,
1015 altuserforcache
? altuserforcache
: user
, hostlist
,
1016 (mb
->sslflag
||mb
->tlsflag
),
1017 (preserve_password
== -1 ? 0
1018 : (preserve_password
== 0 ? 2 :1)));
1019 #endif /* LOCAL_PASSWD_CACHE */
1021 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1026 mm_login_alt_cue(NETMBX
*mb
)
1028 if(ps_global
->ttyo
){
1031 lastc
= pico_set_colors(ps_global
->VAR_TITLE_FORE_COLOR
,
1032 ps_global
->VAR_TITLE_BACK_COLOR
,
1035 mark_titlebar_dirty();
1036 PutLine0(0, ps_global
->ttyo
->screen_cols
- 1,
1037 (mb
->sslflag
||mb
->tlsflag
) ? "+" : " ");
1040 (void)pico_set_colorp(lastc
, PSC_NONE
);
1041 free_color_pair(&lastc
);
1049 /*----------------------------------------------------------------------
1050 Receive notification of an error writing to disk
1052 Args: stream -- The stream the error occured on
1053 errcode -- The system error code (errno)
1054 serious -- Flag indicating error is serious (mail may be lost)
1056 Result: If error is non serious, the stream is marked as having an error
1057 and deletes are disallowed until error clears
1058 If error is serious this goes modal, allowing the user to retry
1059 or get a shell escape to fix the condition. When the condition is
1060 serious it means that mail existing in the mailbox will be lost
1061 if Pine exits without writing, so we try to induce the user to
1062 fix the error, go get someone that can fix the error, or whatever
1063 and don't provide an easy way out.
1066 mm_diskerror (MAILSTREAM
*stream
, long int errcode
, long int serious
)
1070 static ESCKEY_S de_opts
[] = {
1071 {'r', 'r', "R", "Retry"},
1072 {'f', 'f', "F", "FileBrowser"},
1073 {'s', 's', "S", "ShellPrompt"},
1076 #define DE_COLS (ps_global->ttyo->screen_cols)
1077 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
1079 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
1081 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
1082 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
1084 "The reported error number is %s. The last reported mail error was:"
1085 static char *de_msg
[] = {
1086 "Please try to correct the error preventing Alpine from saving your",
1087 "mail folder. For example if the disk is out of space try removing",
1088 "unneeded files. You might also contact your system administrator.",
1090 "Both Alpine's File Browser and an option to enter the system's",
1091 "command prompt are offered to aid in fixing the problem. When",
1092 "you believe the problem is resolved, choose the \"Retry\" option.",
1093 "Be aware that messages may be lost or this folder left in an",
1094 "inaccessible condition if you exit or kill Alpine before the problem",
1097 static char *de_shell_msg
[] = {
1098 "\n\nPlease attempt to correct the error preventing saving of the",
1099 "mail folder. If you do not know how to correct the problem, contact",
1100 "your system administrator. To return to Alpine, type \"exit\".",
1104 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
1105 DE_FOLDER(stream
), errcode
, serious
? "" : "not "));
1106 dprint((0, "***** message: \"%s\"\n\n",
1107 ps_global
->last_error
? ps_global
->last_error
: "?"));
1110 sp_set_io_error_on_stream(stream
, 1);
1115 /* replace pine's body display with screen full of explanatory text */
1117 PutLine1(2, MAX((DE_COLS
- sizeof(DE_STR1
)
1118 - strlen(DE_FOLDER(stream
)))/2, 0),
1119 DE_STR1
, DE_FOLDER(stream
));
1121 PutLine1(3, 4, DE_STR2
, long2string(errcode
));
1123 PutLine0(4, 0, " \"");
1124 removing_leading_white_space(ps_global
->last_error
);
1125 for(i
= 4, p
= ps_global
->last_error
; *p
&& i
< DE_LINE
; ){
1126 for(s
= NULL
, q
= p
; *q
&& q
- p
< DE_COLS
- 16; q
++)
1127 if(isspace((unsigned char)*q
))
1138 PutLine0(i
, 0, " ");
1139 while(*p
&& isspace((unsigned char)*p
))
1150 for(j
= ++i
; i
< DE_LINE
&& de_msg
[i
-j
]; i
++){
1152 PutLine0(i
, 0, " ");
1153 Write_to_screen(de_msg
[i
-j
]);
1159 switch(radio_buttons(DE_PMT
, -FOOTER_ROWS(ps_global
), de_opts
,
1160 'r', 0, NO_HELP
, RB_FLUSH_IN
| RB_NO_NEWMAIL
)){
1161 case 'r' : /* Retry! */
1162 ps_global
->mangled_screen
= 1;
1165 case 'f' : /* File Browser */
1167 char full_filename
[MAXPATH
+1], filename
[MAXPATH
+1];
1170 build_path(full_filename
, ps_global
->home_dir
, filename
,
1171 sizeof(full_filename
));
1172 file_lister("DISK ERROR", full_filename
, sizeof(full_filename
),
1173 filename
, sizeof(filename
), FALSE
, FB_SAVE
);
1180 end_keyboard(ps_global
? F_ON(F_USE_FK
,ps_global
) : 0);
1181 end_tty_driver(ps_global
);
1182 for(i
= 0; de_shell_msg
[i
]; i
++)
1183 puts(de_shell_msg
[i
]);
1186 * Don't use our piping mechanism to spawn a subshell here
1187 * since it will the server (thus reentering c-client).
1194 init_tty_driver(ps_global
);
1195 init_keyboard(F_ON(F_USE_FK
,ps_global
));
1199 if(ps_global
->redrawer
)
1200 (*ps_global
->redrawer
)();
1206 pine_tcptimeout_noscreen(long int elapsed
, long int sincelast
, char *host
)
1215 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
1216 snprintf(pmt
, sizeof(pmt
),
1217 _("No reply in %s seconds from server %s. Break connection"),
1218 long2string(elapsed
), host
);
1219 pmt
[sizeof(pmt
)-1] = '\0';
1220 if(want_to(pmt
, 'n', 'n', NO_HELP
, WT_FLUSH_IN
) == 'y'){
1221 ps_global
->user_says_cancel
= 1;
1226 ps_global
->tcptimeout
= 0;
1232 * -------------------------------------------------------------
1233 * These are declared in pith/imap.h as mandatory to implement.
1234 * -------------------------------------------------------------
1239 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
1242 pine_tcptimeout(long int elapsed
, long int sincelast
, char *host
)
1244 long rv
= 1L; /* keep trying by default */
1247 ps_global
->tcptimeout
= 1;
1249 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
1250 long2string(elapsed
), host
));
1259 if(ps_global
->noshow_timeout
)
1262 if(ps_global
->can_interrupt
1263 && ps_global
->close_connection_timeout
> 0L
1264 && elapsed
>= (long)ps_global
->tcp_query_timeout
1265 && elapsed
>= (long)ps_global
->close_connection_timeout
){
1266 ps_global
->read_bail
= 0;
1267 ps_global
->user_says_cancel
= 1;
1271 if(!ps_global
->ttyo
)
1272 return(pine_tcptimeout_noscreen(elapsed
, sincelast
, host
));
1277 * Prompt after a minute (since by then things are probably really bad)
1278 * A prompt timeout means "keep trying"...
1280 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
1283 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
1284 if((clear_inverse
= !InverseState()) != 0)
1289 PutLine2(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
), 0,
1290 _("No reply in %s seconds from server %s. Break connection?"),
1291 long2string(elapsed
), host
);
1296 if(ps_global
->read_bail
|| ch
== 'y' || ch
== 'Y'){
1297 ps_global
->read_bail
= 0;
1298 ps_global
->user_says_cancel
= 1;
1305 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
1308 if(rv
== 1L){ /* just warn 'em something's up */
1309 q_status_message2(SM_ORDER
, 0, 0,
1310 _("No reply in %s seconds from server %s. Still Waiting..."),
1311 long2string(elapsed
), host
);
1312 flush_status_messages(0); /* make sure it's seen */
1315 mark_status_dirty(); /* make sure it get's cleared */
1317 resume_busy_cue((rv
== 1) ? 3 : 0);
1318 ps_global
->tcptimeout
= 0;
1323 QUOTALIST
*pine_quotalist_copy (QUOTALIST
*pquota
)
1325 QUOTALIST
*cquota
= NULL
;
1328 cquota
= mail_newquotalist();
1329 if (pquota
->name
&& *pquota
->name
)
1330 cquota
->name
= cpystr(pquota
->name
);
1331 cquota
->usage
= pquota
->usage
;
1332 cquota
->limit
= pquota
->limit
;
1334 cquota
->next
= pine_quotalist_copy(pquota
->next
);
1340 /* c-client callback to handle quota */
1343 pine_parse_quota (MAILSTREAM
*stream
, unsigned char *msg
, QUOTALIST
*pquota
)
1345 ps_global
->quota
= pine_quotalist_copy (pquota
);
1349 * C-client callback to handle SSL/TLS certificate validation failures
1351 * Returning 0 means error becomes fatal
1352 * Non-zero means certificate problem is ignored and SSL session is
1355 * We remember the answer and won't re-ask for subsequent open attempts to
1356 * the same hostname.
1359 pine_sslcertquery(char *reason
, char *host
, char *cert
)
1362 char *unknown
= "<unknown>";
1365 int ok_novalidate
= 0, warned
= 0;
1367 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
1368 host
? host
: "?", reason
? reason
: "?",
1369 cert
? cert
: "?"));
1371 hostlist
.name
= host
? host
: "";
1372 hostlist
.next
= NULL
;
1375 * See if we've been asked about this host before.
1377 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
1378 /* we were asked before, did we say Yes? */
1384 "sslcertificatequery: approved automatically\n"));
1388 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
1391 if(ps_global
->ttyo
){
1393 STORE_S
*in_store
, *out_store
;
1395 HANDLE_S
*handles
= NULL
;
1396 int the_answer
= 'n';
1398 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
1399 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
1402 so_puts(in_store
, "<HTML><P>");
1403 so_puts(in_store
, _("There was a failure validating the SSL/TLS certificate for the server"));
1405 so_puts(in_store
, "<P><CENTER>");
1406 so_puts(in_store
, host
? host
: unknown
);
1407 so_puts(in_store
, "</CENTER>");
1409 so_puts(in_store
, "<P>");
1410 so_puts(in_store
, _("The reason for the failure was"));
1412 /* squirrel away details */
1414 fs_give((void **)&details_host
);
1416 fs_give((void **)&details_reason
);
1418 fs_give((void **)&details_cert
);
1420 details_host
= cpystr(host
? host
: unknown
);
1421 details_reason
= cpystr(reason
? reason
: unknown
);
1422 details_cert
= cpystr(cert
? cert
: unknown
);
1424 so_puts(in_store
, "<P><CENTER>");
1425 snprintf(tmp
, sizeof(tmp
), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
1426 reason
? reason
: unknown
);
1427 tmp
[sizeof(tmp
)-1] = '\0';
1429 so_puts(in_store
, tmp
);
1430 so_puts(in_store
, "</CENTER>");
1432 so_puts(in_store
, "<P>");
1433 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."));
1435 so_puts(in_store
, "<P>");
1436 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"));
1438 so_puts(in_store
, "<P><CENTER>");
1439 so_puts(in_store
, "/novalidate-cert");
1440 so_puts(in_store
, "</CENTER>");
1442 so_puts(in_store
, "<P>");
1443 so_puts(in_store
, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
1445 so_puts(in_store
, "<P><CENTER>");
1446 so_puts(in_store
, host
? host
: unknown
);
1447 so_puts(in_store
, "</CENTER>");
1449 so_puts(in_store
, "<P>");
1450 so_puts(in_store
, _("in your configuration, replace those characters with"));
1452 so_puts(in_store
, "<P><CENTER>");
1453 so_puts(in_store
, host
? host
: unknown
);
1454 so_puts(in_store
, "/novalidate-cert");
1455 so_puts(in_store
, "</CENTER>");
1457 so_puts(in_store
, "<P>");
1458 so_puts(in_store
, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
1460 so_seek(in_store
, 0L, 0);
1461 init_handles(&handles
);
1463 gf_link_filter(gf_html2plain
,
1464 gf_html2plain_opt(NULL
,
1465 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
1466 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
1467 gf_set_so_readc(&gc
, in_store
);
1468 gf_set_so_writec(&pc
, out_store
);
1470 gf_clear_so_writec(out_store
);
1471 gf_clear_so_readc(in_store
);
1473 memset(&sargs
, 0, sizeof(SCROLL_S
));
1474 sargs
.text
.handles
= handles
;
1475 sargs
.text
.text
= so_text(out_store
);
1476 sargs
.text
.src
= CharStar
;
1477 sargs
.text
.desc
= _("help text");
1478 sargs
.bar
.title
= _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
1479 sargs
.proc
.tool
= answer_cert_failure
;
1480 sargs
.proc
.data
.p
= (void *)&the_answer
;
1481 sargs
.keys
.menu
= &ans_certquery_keymenu
;
1482 /* don't want to re-enter c-client */
1483 sargs
.quell_newmail
= 1;
1484 setbitmap(sargs
.keys
.bitmap
);
1485 sargs
.help
.text
= h_tls_validation_failure
;
1486 sargs
.help
.title
= _("HELP FOR CERT VALIDATION FAILURE");
1490 if(the_answer
== 'y')
1493 ps_global
->mangled_screen
= 1;
1494 ps_global
->painted_body_on_startup
= 0;
1495 ps_global
->painted_footer_on_startup
= 0;
1497 so_give(&out_store
);
1498 free_handles(&handles
);
1500 fs_give((void **)&details_host
);
1502 fs_give((void **)&details_reason
);
1504 fs_give((void **)&details_cert
);
1508 * If screen hasn't been initialized yet, use want_to.
1511 memset((void *)tmp
, 0, sizeof(tmp
));
1513 reason
? reason
: _("SSL/TLS certificate validation failure"),
1515 tmp
[sizeof(tmp
)-1] = '\0';
1516 strncat(tmp
, _(": Continue anyway "), sizeof(tmp
)-strlen(tmp
)-1);
1518 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y')
1523 q_status_message1(SM_ORDER
, 1, 3, _("Open of %s cancelled"),
1524 host
? host
: unknown
);
1526 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, rv
? 1 : 0, 0);
1528 dprint((5, "sslcertificatequery: %s\n",
1529 rv
? "approved" : "rejected"));
1536 pine_newsrcquery(MAILSTREAM
*stream
, char *mulname
, char *name
)
1538 char buf
[MAILTMPLEN
];
1540 if((can_access(mulname
, ACCESS_EXISTS
) == 0)
1541 || !(can_access(name
, ACCESS_EXISTS
) == 0))
1544 snprintf(buf
, sizeof(buf
),
1545 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
1547 strlen(last_cmpnt(name
)) > 15 ? "..." : "");
1548 buf
[sizeof(buf
)-1] = '\0';
1549 if(want_to(buf
, 'n', 'n', NO_HELP
, WT_NORM
) == 'y')
1550 rename_file(name
, mulname
);
1556 url_local_certdetails(char *url
)
1558 if(!struncmp(url
, "x-alpine-cert:", 14)){
1563 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
1564 q_status_message(SM_ORDER
| SM_DING
, 7, 10,
1565 _("Error allocating space for details."));
1569 so_puts(store
, _("Host given by user:\n\n "));
1570 so_puts(store
, details_host
);
1571 so_puts(store
, _("\n\nReason for failure:\n\n "));
1572 so_puts(store
, details_reason
);
1573 so_puts(store
, _("\n\nCertificate being verified:\n\n"));
1574 folded
= fold(details_cert
, ps_global
->ttyo
->screen_cols
, ps_global
->ttyo
->screen_cols
, " ", " ", FLD_NONE
);
1575 so_puts(store
, folded
);
1576 fs_give((void **)&folded
);
1577 so_puts(store
, "\n");
1579 memset(&sargs
, 0, sizeof(SCROLL_S
));
1580 sargs
.text
.text
= so_text(store
);
1581 sargs
.text
.src
= CharStar
;
1582 sargs
.text
.desc
= _("Details");
1583 sargs
.bar
.title
= _("CERT VALIDATION DETAILS");
1584 sargs
.help
.text
= NO_HELP
;
1585 sargs
.help
.title
= NULL
;
1586 sargs
.quell_newmail
= 1;
1587 sargs
.help
.text
= h_tls_failure_details
;
1588 sargs
.help
.title
= _("HELP FOR CERT VALIDATION DETAILS");
1592 so_give(&store
); /* free resources associated with store */
1593 ps_global
->mangled_screen
= 1;
1602 * C-client callback to handle SSL/TLS certificate validation failures
1605 pine_sslfailure(char *host
, char *reason
, long unsigned int flags
)
1609 int the_answer
= 'n', indent
, len
, cols
;
1610 char buf
[500], buf2
[500];
1612 char *hst
= host
? host
: "<unknown>";
1613 char *rsn
= reason
? reason
: "<unknown>";
1614 char *notls
= "/notls";
1616 int ok_novalidate
= 0, warned
= 0;
1619 dprint((1, "sslfailure: host=%s reason=%s\n",
1623 if(flags
& NET_SILENT
)
1626 hostlist
.name
= host
? host
: "";
1627 hostlist
.next
= NULL
;
1630 * See if we've been told about this host before.
1632 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
1633 /* we were told already */
1635 snprintf(buf
, sizeof(buf
), _("SSL/TLS failure for %s: %s"), hst
, rsn
);
1636 buf
[sizeof(buf
)-1] = '\0';
1642 cols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1645 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
1648 strncpy(buf
, _("There was an SSL/TLS failure for the server"), sizeof(buf
));
1649 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1650 so_puts(store
, folded
);
1651 fs_give((void **)&folded
);
1652 so_puts(store
, "\n");
1654 if((len
=strlen(hst
)) <= cols
){
1655 if((indent
=((cols
-len
)/2)) > 0)
1656 so_puts(store
, repeat_char(indent
, SPACE
));
1658 so_puts(store
, hst
);
1659 so_puts(store
, "\n");
1662 strncpy(buf
, hst
, sizeof(buf
));
1663 buf
[sizeof(buf
)-1] = '\0';
1664 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1665 so_puts(store
, folded
);
1666 fs_give((void **)&folded
);
1669 so_puts(store
, "\n");
1671 strncpy(buf
, _("The reason for the failure was"), sizeof(buf
));
1672 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1673 so_puts(store
, folded
);
1674 fs_give((void **)&folded
);
1675 so_puts(store
, "\n");
1677 if((len
=strlen(rsn
)) <= cols
){
1678 if((indent
=((cols
-len
)/2)) > 0)
1679 so_puts(store
, repeat_char(indent
, SPACE
));
1681 so_puts(store
, rsn
);
1682 so_puts(store
, "\n");
1685 strncpy(buf
, rsn
, sizeof(buf
));
1686 buf
[sizeof(buf
)-1] = '\0';
1687 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1688 so_puts(store
, folded
);
1689 fs_give((void **)&folded
);
1692 so_puts(store
, "\n");
1694 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
));
1695 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1696 so_puts(store
, folded
);
1697 fs_give((void **)&folded
);
1698 so_puts(store
, "\n");
1700 if((len
=strlen(notls
)) <= cols
){
1701 if((indent
=((cols
-len
)/2)) > 0)
1702 so_puts(store
, repeat_char(indent
, SPACE
));
1704 so_puts(store
, notls
);
1705 so_puts(store
, "\n");
1708 strncpy(buf
, notls
, sizeof(buf
));
1709 buf
[sizeof(buf
)-1] = '\0';
1710 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1711 so_puts(store
, folded
);
1712 fs_give((void **)&folded
);
1715 so_puts(store
, "\n");
1717 strncpy(buf
, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
1719 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1720 so_puts(store
, folded
);
1721 fs_give((void **)&folded
);
1722 so_puts(store
, "\n");
1724 if((len
=strlen(hst
)) <= cols
){
1725 if((indent
=((cols
-len
)/2)) > 0)
1726 so_puts(store
, repeat_char(indent
, SPACE
));
1728 so_puts(store
, hst
);
1729 so_puts(store
, "\n");
1732 strncpy(buf
, hst
, sizeof(buf
));
1733 buf
[sizeof(buf
)-1] = '\0';
1734 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1735 so_puts(store
, folded
);
1736 fs_give((void **)&folded
);
1739 so_puts(store
, "\n");
1741 strncpy(buf
, _("in your configuration, replace those characters with"),
1743 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1744 so_puts(store
, folded
);
1745 fs_give((void **)&folded
);
1746 so_puts(store
, "\n");
1748 snprintf(buf2
, sizeof(buf2
), "%s%s", hst
, notls
);
1749 buf2
[sizeof(buf2
)-1] = '\0';
1750 if((len
=strlen(buf2
)) <= cols
){
1751 if((indent
=((cols
-len
)/2)) > 0)
1752 so_puts(store
, repeat_char(indent
, SPACE
));
1754 so_puts(store
, buf2
);
1755 so_puts(store
, "\n");
1758 strncpy(buf
, buf2
, sizeof(buf
));
1759 buf
[sizeof(buf
)-1] = '\0';
1760 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1761 so_puts(store
, folded
);
1762 fs_give((void **)&folded
);
1765 so_puts(store
, "\n");
1767 if(ps_global
->ttyo
){
1768 strncpy(buf
, _("Type RETURN to continue."), sizeof(buf
));
1769 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1770 so_puts(store
, folded
);
1771 fs_give((void **)&folded
);
1774 memset(&sargs
, 0, sizeof(SCROLL_S
));
1775 sargs
.text
.text
= so_text(store
);
1776 sargs
.text
.src
= CharStar
;
1777 sargs
.text
.desc
= _("help text");
1778 sargs
.bar
.title
= _("SSL/TLS FAILURE");
1779 sargs
.proc
.tool
= answer_cert_failure
;
1780 sargs
.proc
.data
.p
= (void *)&the_answer
;
1781 sargs
.keys
.menu
= &ans_certfail_keymenu
;
1782 setbitmap(sargs
.keys
.bitmap
);
1783 /* don't want to re-enter c-client */
1784 sargs
.quell_newmail
= 1;
1785 sargs
.help
.text
= h_tls_failure
;
1786 sargs
.help
.title
= _("HELP FOR TLS/SSL FAILURE");
1797 * The screen isn't initialized yet, which should mean that this
1798 * is the result of a -p argument. Display_args_err knows how to deal
1799 * with the uninitialized screen, so we mess with the data to get it
1800 * in shape for display_args_err. This is pretty hacky.
1803 so_seek(store
, 0L, 0); /* rewind */
1804 /* count the lines */
1805 while(so_readc(&c
, store
))
1809 qp
= q
= (char **)fs_get((cnt
+1) * sizeof(char *));
1810 memset(q
, 0, (cnt
+1) * sizeof(char *));
1812 so_seek(store
, 0L, 0); /* rewind */
1814 while(so_readc(&c
, store
)){
1817 *qp
++ = cpystr(buf
);
1824 display_args_err(NULL
, q
, 0);
1825 free_list_array(&q
);
1828 ps_global
->mangled_screen
= 1;
1829 ps_global
->painted_body_on_startup
= 0;
1830 ps_global
->painted_footer_on_startup
= 0;
1833 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, ok_novalidate
, 1);
1838 answer_cert_failure(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
1842 ps_global
->next_screen
= SCREEN_FUN_NULL
;
1846 *(int *)(sparms
->proc
.data
.p
) = 'y';
1850 *(int *)(sparms
->proc
.data
.p
) = 'n';
1854 alpine_panic("Unexpected command in answer_cert_failure");
1862 /*----------------------------------------------------------------------
1863 This can be used to prevent the flickering of the check_cue char
1864 caused by numerous (5000+) fetches by c-client. Right now, the only
1865 practical use found is newsgroup subsciption.
1867 check_cue_display will check if this global is set, and won't clear
1868 the check_cue_char if set.
1871 set_read_predicted(int i
)
1873 ps_global
->read_predicted
= i
==1;
1875 if(!i
&& F_ON(F_SHOW_DELAY_CUE
, ps_global
))
1876 check_cue_display(" ");
1881 /*----------------------------------------------------------------------
1882 Exported method to retrieve logged in user name associated with stream
1884 Args: host -- host to find associated login name with.
1889 pine_block_notify(int reason
, void *data
)
1892 case BLOCK_SENSITIVE
: /* sensitive code, disallow alarms */
1895 case BLOCK_NONSENSITIVE
: /* non-sensitive code, allow alarms */
1898 case BLOCK_TCPWRITE
: /* blocked on TCP write */
1899 case BLOCK_FILELOCK
: /* blocked on file locking */
1901 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
1902 check_cue_display(">");
1904 mswin_setcursor(MSWIN_CURSOR_BUSY
);
1908 case BLOCK_DNSLOOKUP
: /* blocked on DNS lookup */
1909 case BLOCK_TCPOPEN
: /* blocked on TCP open */
1910 case BLOCK_TCPREAD
: /* blocked on TCP read */
1911 case BLOCK_TCPCLOSE
: /* blocked on TCP close */
1913 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
1914 check_cue_display("<");
1916 mswin_setcursor(MSWIN_CURSOR_BUSY
);
1921 case BLOCK_NONE
: /* not blocked */
1923 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
1924 check_cue_display(" ");
1935 mm_expunged_current(long unsigned int rawno
)
1937 /* expunged something we're viewing? */
1938 if(!ps_global
->expunge_in_progress
1939 && (mn_is_cur(ps_global
->msgmap
, mn_raw2m(ps_global
->msgmap
, (long) rawno
))
1940 && (ps_global
->prev_screen
== mail_view_screen
1941 || ps_global
->prev_screen
== attachment_screen
))){
1942 ps_global
->next_screen
= mail_index_screen
;
1943 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
1944 "Message you were viewing is gone!");
1952 * Specific functions to support caching username/passwd/host
1953 * triples on disk for use from one session to the next...
1956 #define FIRSTCH 0x20
1958 #define TABSZ (LASTCH - FIRSTCH + 1)
1960 static int xlate_key
;
1964 * xlate_in() - xlate_in the given character
1972 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
1973 eti
+= (c
- FIRSTCH
);
1974 eti
-= (eti
>= 2*TABSZ
) ? 2*TABSZ
: (eti
>= TABSZ
) ? TABSZ
: 0;
1975 return((xlate_key
= eti
) + FIRSTCH
);
1983 * xlate_out() - xlate_out the given character
1991 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
1992 xch
= c
- (dti
= xlate_key
);
1993 xch
+= (xch
< FIRSTCH
-TABSZ
) ? 2*TABSZ
: (xch
< FIRSTCH
) ? TABSZ
: 0;
1994 dti
= (xch
- FIRSTCH
) + dti
;
1995 dti
-= (dti
>= 2*TABSZ
) ? 2*TABSZ
: (dti
>= TABSZ
) ? TABSZ
: 0;
2002 #endif /* PASSFILE */
2005 #ifdef LOCAL_PASSWD_CACHE
2009 line_get(char *tmp
, size_t len
, char **textp
)
2015 || (s
= strchr(*textp
, '\n')) == NULL
2016 || (s
- *textp
) > len
- 1)
2023 snprintf(tmp
, len
, "%s\n", *textp
);
2031 * Passfile lines are
2033 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
2035 * In pine4.40 and before there was no orig_hostname, and there still isn't
2036 * if it is the same as hostname.
2039 * Use Windows credentials. The TargetName of the credential is
2040 * UWash_Alpine_<hostname:port>\tuser\taltflag
2041 * and the blob consists of
2042 * passwd\torighost (if different from host)
2044 * We don't use anything fancy we just copy out all the credentials which
2045 * begin with TNAME and put them into our cache, so we don't lookup based
2046 * on the TargetName or anything like that. That was so we could re-use
2047 * the existing code and so that orighost data could be easily used.
2050 read_passfile(pinerc
, l
)
2056 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
2059 char *tmp
, *blob
, *target
= NULL
;
2060 char *host
, *user
, *sflags
, *passwd
, *orighost
;
2064 if(using_passfile
== 0)
2065 return(using_passfile
);
2068 if(init_wincred_funcs() != 1){
2070 return(using_passfile
);
2074 dprint((9, "read_passfile\n"));
2078 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
2080 for(k
= 0; k
< count
; k
++){
2082 host
= user
= sflags
= passwd
= orighost
= NULL
;
2083 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
2085 target
= lptstr_to_utf8(pcred
[k
]->TargetName
);
2086 tmp
= srchstr(target
, TNAME
);
2089 tmp
+= strlen(TNAME
);
2090 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
2091 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
2092 ; /* find end of data */
2095 tmp
[i
++] = '\0'; /* tie off data */
2103 blob
= (char *) pcred
[k
]->CredentialBlob
;
2105 for(i
= 0, j
= 3; blob
[i
] && j
< 5; j
++){
2106 for(ui
[j
] = &blob
[i
]; blob
[i
] && blob
[i
] != '\t'; i
++)
2107 ; /* find end of data */
2110 blob
[i
++] = '\0'; /* tie off data */
2117 if(passwd
&& host
&& user
){ /* valid field? */
2118 STRLIST_S hostlist
[2];
2119 int flags
= sflags
? atoi(sflags
) : 0;
2121 hostlist
[0].name
= host
;
2123 hostlist
[0].next
= &hostlist
[1];
2124 hostlist
[1].name
= orighost
;
2125 hostlist
[1].next
= NULL
;
2128 hostlist
[0].next
= NULL
;
2131 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
2135 fs_give((void **) &target
);
2138 g_CredFree((PVOID
) pcred
);
2144 # else /* old windows */
2151 char target
[MAILTMPLEN
];
2152 char *tmp
, *host
, *user
, *sflags
, *passwd
, *orighost
;
2155 SecKeychainAttributeList attrList
;
2156 SecKeychainSearchRef searchRef
= NULL
;
2157 SecKeychainAttribute attrs
[] = {
2158 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
2161 if(using_passfile
== 0)
2162 return(using_passfile
);
2164 dprint((9, "read_passfile\n"));
2167 /* search for only our items in the keychain */
2169 attrList
.attr
= attrs
;
2172 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
2173 kSecGenericPasswordItemClass
,
2176 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
2178 SecKeychainItemRef itemRef
= NULL
;
2179 SecKeychainAttributeInfo info
;
2180 SecKeychainAttributeList
*attrList
= NULL
;
2183 char *blobcopy
= NULL
; /* NULL terminated copy */
2185 UInt32 tags
[] = {kSecAccountItemAttr
,
2186 kSecServiceItemAttr
};
2187 UInt32 formats
[] = {0,0};
2189 dprint((10, "read_passfile: searchRef not NULL\n"));
2192 info
.format
= formats
;
2195 * Go through each item we found and put it
2198 while(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
)) && itemRef
){
2199 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
2200 rc
= SecKeychainItemCopyAttributesAndData(itemRef
,
2205 if(rc
== 0 && attrList
){
2206 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList
->count
));
2208 blobcopy
= (char *) fs_get((blength
+ 1) * sizeof(char));
2209 strncpy(blobcopy
, (char *) blob
, blength
);
2210 blobcopy
[blength
] = '\0';
2213 * I'm not real clear on how this works. It seems to be
2214 * necessary to combine the attributes from two passes
2215 * (attrList->count == 2) in order to get the full set
2216 * of attributes we inserted into the keychain in the
2217 * first place. So, we reset host...orighost outside of
2218 * the following for loop, not inside.
2220 host
= user
= sflags
= passwd
= orighost
= NULL
;
2221 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
2223 for(k
= 0; k
< attrList
->count
; k
++){
2225 if(attrList
->attr
[k
].length
){
2227 (char *) attrList
->attr
[k
].data
,
2228 MIN(attrList
->attr
[k
].length
,sizeof(target
)));
2229 target
[MIN(attrList
->attr
[k
].length
,sizeof(target
)-1)] = '\0';
2233 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
2234 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
2235 ; /* find end of data */
2238 tmp
[i
++] = '\0'; /* tie off data */
2250 for(i
= 0, j
= 3; blobcopy
[i
] && j
< 5; j
++){
2251 for(ui
[j
] = &blobcopy
[i
]; blobcopy
[i
] && blobcopy
[i
] != '\t'; i
++)
2252 ; /* find end of data */
2255 blobcopy
[i
++] = '\0'; /* tie off data */
2264 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
:""));
2267 if(passwd
&& host
&& user
){ /* valid field? */
2268 STRLIST_S hostlist
[2];
2269 int flags
= sflags
? atoi(sflags
) : 0;
2271 hostlist
[0].name
= host
;
2273 hostlist
[0].next
= &hostlist
[1];
2274 hostlist
[1].name
= orighost
;
2275 hostlist
[1].next
= NULL
;
2278 hostlist
[0].next
= NULL
;
2281 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
2285 fs_give((void **) & blobcopy
);
2287 SecKeychainItemFreeAttributesAndData(attrList
, (void *) blob
);
2291 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc
));
2298 CFRelease(searchRef
);
2302 dprint((10, "read_passfile: searchRef NULL\n"));
2307 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc
));
2310 return(using_passfile
);
2312 #else /* PASSFILE */
2314 char tmp
[MAILTMPLEN
], *ui
[5];
2315 int i
, j
, n
, rv
= 0;
2317 char tmp2
[MAILTMPLEN
];
2318 char *text
= NULL
, *text2
= NULL
;
2323 if(using_passfile
== 0)
2324 return(using_passfile
);
2326 dprint((9, "read_passfile\n"));
2328 /* if there's no password to read, bag it!! */
2329 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb"))){
2331 return(using_passfile
);
2335 /* the next call initializes the key/certificate pair used to
2336 * encrypt and decrypt a password file. The details of how this is
2337 * done is in the file pith/smime.c. During this setup we might call
2338 * smime_init(), but no matter what happens we must call smime_deinit()
2339 * there. The reason why this is so is because we can not assume that
2340 * the .pinerc file has been read by this time, so this code might not
2341 * know about the ps_global->smime structure or any of its components,
2342 * and it shouldn't because it only needs ps_global->pwdcert, so
2343 * do not init smime here, because the .pinerc might not have been
2344 * read and we do not really know where the keys and certificates really
2346 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
2347 * it is called for the first time and there are certificates at all,
2348 * or when it is called after the first time and the user refuses to
2349 * create a self-signed certificate. In this situation we will just
2350 * let the user live in an insecure world, but no more passwords will
2351 * be saved in the password file, and only those found there will be used.
2354 fgets(tmp2
, sizeof(tmp2
), fp
);
2356 if(strcmp(tmp2
, "-----BEGIN PKCS7-----\n")){
2357 /* there is an already existing password file, that is not encrypted
2358 * and there is no key to encrypt it yet, go again through setup_pwdcert
2359 * and encrypt it now.
2361 if(tmp2
[0]){ /* not empty, UNencrypted password file */
2362 if(ps_global
->pwdcert
== NULL
)
2363 rv
= setup_pwdcert(&ps_global
->pwdcert
);
2364 if((rv
== 0 || rv
== -5) && ps_global
->pwdcert
== NULL
)
2365 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
2366 if(ps_global
->pwdcert
== NULL
){
2367 q_status_message(SM_ORDER
, 3, 3,
2368 " Failed to create private key. Using UNencrypted Password file. ");
2373 q_status_message(SM_ORDER
, 3, 3,
2374 " Failed to unlock private key. Using UNencrypted Password file. ");
2375 save_password
= 0; /* do not save more passwords */
2378 if(ps_global
->pwdcert
!= NULL
2379 && encrypt_file((char *)tmp
, NULL
, (PERSONAL_CERT
*)ps_global
->pwdcert
))
2384 if(ps_global
->pwdcert
== NULL
)
2385 rv
= setup_pwdcert(&ps_global
->pwdcert
);
2390 * if password file is encrypted we attemtp to decrypt. We ask the
2391 * user for the password to unlock the password file. If the user
2392 * enters the password and it unlocks the file, use it and keep saving
2393 * passwords in it. If the user enters the wrong passwords and does
2394 * not unlock it, we will not see that here, but in decrypt_file, so
2395 * the only other possibility is that the user cancels. In that case
2396 * we will see i == -1. In that case, we will let the user attempt
2397 * manual login to the server they want to login, but passwords will
2398 * not be saved so that the password file will not be saved
2399 * unencrypted and rewritten again.
2402 text
= text2
= decrypt_file((char *)tmp
, &i
, (PERSONAL_CERT
*)ps_global
->pwdcert
);
2404 case -2: using_passfile
= 0;
2407 case 1 : save_password
= 1;
2411 case -1: save_password
= 0;
2419 fp
= our_fopen(tmp
, "rb"); /* reopen to read data */
2422 if(using_passfile
== 0){
2424 if(text
) fs_give((void **)&text
);
2426 return using_passfile
;
2430 for(n
= 0; encrypted
? line_get(tmp
, sizeof(tmp
), &text2
)
2431 : (fgets(tmp
, sizeof(tmp
), fp
) != NULL
); n
++){
2433 for(n
= 0; fgets(tmp
, sizeof(tmp
), fp
); n
++){
2435 /*** do any necessary DEcryption here ***/
2437 for(i
= 0; tmp
[i
]; i
++)
2438 tmp
[i
] = xlate_out(tmp
[i
]);
2440 if(i
&& tmp
[i
-1] == '\n')
2441 tmp
[i
-1] = '\0'; /* blast '\n' */
2443 dprint((10, "read_passfile: %s\n", tmp
? tmp
: "?"));
2444 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
2445 for(i
= 0, j
= 0; tmp
[i
] && j
< 5; j
++){
2446 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
2447 ; /* find end of data */
2450 tmp
[i
++] = '\0'; /* tie off data */
2453 dprint((10, "read_passfile: calling imap_set_passwd\n"));
2454 if(ui
[0] && ui
[1] && ui
[2]){ /* valid field? */
2455 STRLIST_S hostlist
[2];
2456 int flags
= ui
[3] ? atoi(ui
[3]) : 0;
2458 hostlist
[0].name
= ui
[2];
2460 hostlist
[0].next
= &hostlist
[1];
2461 hostlist
[1].name
= ui
[4];
2462 hostlist
[1].next
= NULL
;
2465 hostlist
[0].next
= NULL
;
2468 imap_set_passwd(l
, ui
[0], ui
[1], hostlist
, flags
& 0x01, 0, 0);
2473 if (text
) fs_give((void **)&text
);
2478 #endif /* PASSFILE */
2484 write_passfile(pinerc
, l
)
2490 char target
[MAILTMPLEN
];
2491 char blob
[MAILTMPLEN
];
2495 if(using_passfile
== 0)
2498 dprint((9, "write_passfile\n"));
2500 for(; l
; l
= l
->next
){
2501 snprintf(target
, sizeof(target
), "%s%s\t%s\t%d",
2503 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
2504 l
->user
? l
->user
: "",
2506 ltarget
= utf8_to_lptstr((LPSTR
) target
);
2509 snprintf(blob
, sizeof(blob
), "%s%s%s",
2510 l
->passwd
? l
->passwd
: "",
2511 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2513 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2514 ? l
->hosts
->next
->name
: "");
2515 memset((void *) &cred
, 0, sizeof(cred
));
2517 cred
.Type
= CRED_TYPE_GENERIC
;
2518 cred
.TargetName
= ltarget
;
2519 cred
.CredentialBlobSize
= strlen(blob
)+1;
2520 cred
.CredentialBlob
= (LPBYTE
) &blob
;
2521 cred
.Persist
= CRED_PERSIST_ENTERPRISE
;
2522 g_CredWriteW(&cred
, 0);
2524 fs_give((void **) <arget
);
2527 #endif /* WINCRED > 0 */
2532 char target
[MAILTMPLEN
];
2533 char blob
[MAILTMPLEN
];
2534 SecKeychainItemRef itemRef
= NULL
;
2536 if(using_passfile
== 0)
2539 dprint((9, "write_passfile\n"));
2541 for(; l
; l
= l
->next
){
2542 snprintf(target
, sizeof(target
), "%s\t%s\t%d",
2543 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
2544 l
->user
? l
->user
: "",
2547 snprintf(blob
, sizeof(blob
), "%s%s%s",
2548 l
->passwd
? l
->passwd
: "",
2549 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2551 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2552 ? l
->hosts
->next
->name
: "");
2554 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target
), target
, strlen(TNAME
), TNAME
, strlen(blob
), blob
));
2556 rc
= SecKeychainAddGenericPassword(NULL
,
2557 strlen(target
), target
,
2558 strlen(TNAME
), TNAME
,
2562 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
2565 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc
));
2568 if(rc
== errSecDuplicateItem
){
2569 /* fix existing entry */
2570 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
2572 if(!(rc
=SecKeychainFindGenericPassword(NULL
,
2573 strlen(target
), target
,
2574 strlen(TNAME
), TNAME
,
2576 &itemRef
)) && itemRef
){
2578 rc
= SecKeychainItemModifyAttributesAndData(itemRef
, NULL
, strlen(blob
), blob
);
2580 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc
));
2584 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc
));
2589 #else /* PASSFILE */
2591 char tmp
[MAILTMPLEN
];
2595 char *text
= NULL
, tmp2
[MAILTMPLEN
];
2599 if(using_passfile
== 0)
2602 dprint((9, "write_passfile\n"));
2604 /* if there's no passfile to read, bag it!! */
2605 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "wb"))){
2611 strncpy(tmp2
, tmp
, sizeof(tmp2
));
2612 tmp2
[sizeof(tmp2
)-1] = '\0';
2615 for(n
= 0; l
; l
= l
->next
, n
++){
2616 /*** do any necessary ENcryption here ***/
2617 snprintf(tmp
, sizeof(tmp
), "%s\t%s\t%s\t%d%s%s\n", l
->passwd
, l
->user
,
2618 l
->hosts
->name
, l
->altflag
,
2619 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "",
2620 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
2622 dprint((10, "write_passfile: %s", tmp
? tmp
: "?"));
2624 for(i
= 0; tmp
[i
]; i
++)
2625 tmp
[i
] = xlate_in(tmp
[i
]);
2629 len
= strlen(tmp
) + 1;
2630 text
= fs_get(len
*sizeof(char));
2633 if(strlen(text
) + strlen(tmp
) > len
){
2634 len
= strlen(text
) + strlen(tmp
) + 1;
2635 fs_resize((void **)&text
, len
*sizeof(char));
2637 strncat(text
, tmp
, strlen(tmp
));
2646 if(ps_global
->pwdcert
== NULL
){
2647 q_status_message(SM_ORDER
, 3, 3, "Attempting to encrypt password file");
2648 i
= setup_pwdcert(&ps_global
->pwdcert
);
2649 if((i
== 0 || i
== -5) && ps_global
->pwdcert
== NULL
)
2650 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
2652 if(ps_global
->pwdcert
== NULL
){ /* we tried but failed */
2654 q_status_message1(SM_ORDER
, 3, 3, "Error: no directory %s to save certificates", ps_global
->pwdcertdir
);
2656 q_status_message(SM_ORDER
, 3, 3, "Refusing to write non-encrypted password file");
2658 else if(encrypt_file((char *)tmp2
, text
, ps_global
->pwdcert
) == 0)
2659 q_status_message(SM_ORDER
, 3, 3, "Failed to encrypt password file");
2660 fs_give((void **)&text
); /* do not save this text */
2663 #endif /* PASSFILE */
2666 #endif /* LOCAL_PASSWD_CACHE */
2671 erase_windows_credentials(void)
2673 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
2678 if(init_wincred_funcs() != 1)
2682 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
2684 for(k
= 0; k
< count
; k
++)
2685 g_CredDeleteW(pcred
[k
]->TargetName
, CRED_TYPE_GENERIC
, 0);
2687 g_CredFree((PVOID
) pcred
);
2693 ask_erase_credentials(void)
2695 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y'){
2696 erase_windows_credentials();
2697 q_status_message(SM_ORDER
, 3, 3, "All preserved passwords have been erased");
2700 q_status_message(SM_ORDER
, 3, 3, "Previously preserved passwords will not be erased");
2703 #endif /* WINCRED */
2706 #ifdef LOCAL_PASSWD_CACHE
2709 * get_passfile_passwd - return the password contained in the special passord
2710 * cache. The file is assumed to be in the same directory
2711 * as the pinerc with the name defined above.
2714 get_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
)
2715 char *pinerc
, *passwd
, *user
;
2716 STRLIST_S
*hostlist
;
2719 dprint((10, "get_passfile_passwd\n"));
2720 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
2721 ? imap_get_passwd(passfile_cache
, passwd
,
2722 user
, hostlist
, altflag
)
2727 free_passfile_cache_work(MMLOGIN_S
**pwdcache
)
2729 if(pwdcache
== NULL
|| *pwdcache
== NULL
)
2732 if((*pwdcache
)->user
) fs_give((void **)&(*pwdcache
)->user
);
2733 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
2734 if((*pwdcache
)->hosts
) free_strlist(&(*pwdcache
)->hosts
);
2735 free_passfile_cache_work(&(*pwdcache
)->next
);
2736 fs_give((void **)pwdcache
);
2741 free_passfile_cache(void)
2744 free_passfile_cache_work(&passfile_cache
);
2748 is_using_passfile(void)
2750 return(using_passfile
== 1);
2754 * Just trying to guess the username the user might want to use on this
2755 * host, the user will confirm.
2758 get_passfile_user(pinerc
, hostlist
)
2760 STRLIST_S
*hostlist
;
2762 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
2763 ? imap_get_user(passfile_cache
, hostlist
)
2769 preserve_prompt(char *pinerc
)
2774 * This prompt was going to be able to be turned on and off via a registry
2775 * setting controlled from the config menu. We decided to always use the
2776 * dialog for login, and there the prompt is unobtrusive enough to always
2777 * be in there. As a result, windows should never reach this, but now
2778 * OS X somewhat uses the behavior just described.
2780 if(mswin_store_pass_prompt()
2781 && (want_to(_("Preserve password for next login"),
2782 'y', 'x', NO_HELP
, WT_NORM
)
2794 if((rc
= macos_store_pass_prompt()) != 0){
2795 if(want_to(_("Preserve password for next login"),
2796 'y', 'x', NO_HELP
, WT_NORM
)
2799 macos_set_store_pass_prompt(1);
2800 q_status_message(SM_ORDER
, 4, 4,
2801 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
2806 macos_set_store_pass_prompt(0);
2807 q_status_message(SM_ORDER
, 4, 4,
2808 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
2813 #else /* PASSFILE */
2814 char tmp
[MAILTMPLEN
];
2817 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || our_stat(tmp
, &sbuf
) < 0)
2820 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
2821 return(want_to(_("Preserve password on DISK for next login"),
2822 'y', 'x', NO_HELP
, WT_NORM
)
2825 #endif /* PASSFILE */
2828 #endif /* LOCAL_PASSWD_CACHE */
2831 #ifdef APPLEKEYCHAIN
2835 * 1 if store pass prompt is set in the "registry" to on
2837 * -1 if not set to anything
2840 macos_store_pass_prompt(void)
2847 if(storepassprompt
== -1){
2848 if(!(rc
=SecKeychainFindGenericPassword(NULL
, 0, NULL
,
2849 strlen(TNAMEPROMPT
),
2851 (void **) &data
, NULL
))){
2852 val
= (len
== 1 && data
&& data
[0] == '1');
2856 if(storepassprompt
== -1 && !rc
){
2858 storepassprompt
= 1;
2860 storepassprompt
= 0;
2863 return(storepassprompt
);
2868 macos_set_store_pass_prompt(int val
)
2870 storepassprompt
= val
? 1 : 0;
2872 SecKeychainAddGenericPassword(NULL
, 0, NULL
, strlen(TNAMEPROMPT
),
2873 TNAMEPROMPT
, 1, val
? "1" : "0", NULL
);
2878 macos_erase_keychain(void)
2880 SecKeychainAttributeList attrList
;
2881 SecKeychainSearchRef searchRef
= NULL
;
2882 SecKeychainAttribute attrs1
[] = {
2883 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
2885 SecKeychainAttribute attrs2
[] = {
2886 { kSecAccountItemAttr
, strlen(TNAMEPROMPT
), TNAMEPROMPT
}
2889 dprint((9, "macos_erase_keychain\n"));
2892 * Seems like we ought to be able to combine attrs1 and attrs2
2893 * into a single array, but I couldn't get it to work.
2896 /* search for only our items in the keychain */
2898 attrList
.attr
= attrs1
;
2900 if(!SecKeychainSearchCreateFromAttributes(NULL
,
2901 kSecGenericPasswordItemClass
,
2905 SecKeychainItemRef itemRef
= NULL
;
2908 * Go through each item we found and put it
2911 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
2912 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
2913 SecKeychainItemDelete(itemRef
);
2917 CFRelease(searchRef
);
2922 attrList
.attr
= attrs2
;
2924 if(!SecKeychainSearchCreateFromAttributes(NULL
,
2925 kSecGenericPasswordItemClass
,
2929 SecKeychainItemRef itemRef
= NULL
;
2932 * Go through each item we found and put it
2935 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
2936 SecKeychainItemDelete(itemRef
);
2940 CFRelease(searchRef
);
2945 #endif /* APPLEKEYCHAIN */
2947 #ifdef LOCAL_PASSWD_CACHE
2950 * set_passfile_passwd - set the password file entry associated with
2951 * cache. The file is assumed to be in the same directory
2952 * as the pinerc with the name defined above.
2953 * already_prompted: 0 not prompted
2954 * 1 prompted, answered yes
2955 * 2 prompted, answered no
2958 set_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
)
2959 char *pinerc
, *passwd
, *user
;
2960 STRLIST_S
*hostlist
;
2961 int altflag
, already_prompted
;
2963 dprint((10, "set_passfile_passwd\n"));
2964 if(((already_prompted
== 0 && preserve_prompt(pinerc
))
2965 || already_prompted
== 1)
2966 && !ps_global
->nowrite_password_cache
2967 && (passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))){
2968 imap_set_passwd(&passfile_cache
, passwd
, user
, hostlist
, altflag
, 0, 0);
2969 write_passfile(pinerc
, passfile_cache
);
2975 * Passfile lines are
2977 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
2979 * In pine4.40 and before there was no orig_hostname.
2980 * This routine attempts to repair that.
2983 update_passfile_hostlist(pinerc
, user
, hostlist
, altflag
)
2986 STRLIST_S
*hostlist
;
2991 #else /* !WINCRED */
2994 for(l
= passfile_cache
; l
; l
= l
->next
)
2995 if(imap_same_host(l
->hosts
, hostlist
)
2997 && !strcmp(user
, l
->user
)
2998 && l
->altflag
== altflag
){
3002 if(l
&& l
->hosts
&& hostlist
&& !l
->hosts
->next
&& hostlist
->next
3003 && hostlist
->next
->name
3004 && !ps_global
->nowrite_password_cache
){
3005 l
->hosts
->next
= new_strlist(hostlist
->next
->name
);
3006 write_passfile(pinerc
, passfile_cache
);
3008 #endif /* !WINCRED */
3011 #endif /* LOCAL_PASSWD_CACHE */
3016 * Load and init the WinCred structure.
3017 * This gives us a way to skip the WinCred code
3018 * if the dll doesn't exist.
3021 init_wincred_funcs(void)
3027 /* Assume the worst. */
3030 hmod
= LoadLibrary(TEXT("advapi32.dll"));
3033 FARPROC fpCredWriteW
;
3034 FARPROC fpCredEnumerateW
;
3035 FARPROC fpCredDeleteW
;
3038 fpCredWriteW
= GetProcAddress(hmod
, "CredWriteW");
3039 fpCredEnumerateW
= GetProcAddress(hmod
, "CredEnumerateW");
3040 fpCredDeleteW
= GetProcAddress(hmod
, "CredDeleteW");
3041 fpCredFree
= GetProcAddress(hmod
, "CredFree");
3043 if(fpCredWriteW
&& fpCredEnumerateW
&& fpCredDeleteW
&& fpCredFree
)
3045 g_CredWriteW
= (CREDWRITEW
*)fpCredWriteW
;
3046 g_CredEnumerateW
= (CREDENUMERATEW
*)fpCredEnumerateW
;
3047 g_CredDeleteW
= (CREDDELETEW
*)fpCredDeleteW
;
3048 g_CredFree
= (CREDFREE
*)fpCredFree
;
3054 mswin_set_erasecreds_callback(ask_erase_credentials
);
3057 return g_CredInited
;
3060 #endif /* WINCRED */