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-2016 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);
110 static MMLOGIN_S
*passfile_cache
= NULL
;
111 static int using_passfile
= -1;
112 int save_password
= 1;
113 #endif /* LOCAL_PASSWD_CACHE */
117 char xlate_out(char);
118 int line_get(char *, size_t, char **);
119 #endif /* PASSFILE */
122 void ask_erase_credentials(void);
123 int init_wincred_funcs(void);
127 static char *details_cert
, *details_host
, *details_reason
;
130 /*----------------------------------------------------------------------
131 recieve notification from IMAP
133 Args: stream -- Mail stream message is relavant to
134 string -- The message text
135 errflg -- Set if it is a serious error
137 Result: message displayed in status line
139 The facility is for general notices, such as connection to server;
140 server shutting down etc... It is used infrequently.
141 ----------------------------------------------------------------------*/
143 mm_notify(MAILSTREAM
*stream
, char *string
, long int errflg
)
148 now
= time((time_t *)0);
149 tm_now
= localtime(&now
);
151 /* be sure to log the message... */
153 if(ps_global
->debug_imap
|| ps_global
->debugmem
)
154 dprint((errflg
== TCPDEBUG
? 7 : 2,
155 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
156 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
157 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
158 (!errflg
) ? "babble" :
159 (errflg
== ERROR
) ? "error" :
160 (errflg
== WARN
) ? "warning" :
161 (errflg
== PARSE
) ? "parse" :
162 (errflg
== TCPDEBUG
) ? "tcp" :
163 (errflg
== BYE
) ? "bye" : "unknown",
164 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
165 string
? string
: "?"));
168 snprintf(ps_global
->last_error
, sizeof(ps_global
->last_error
), "%s : %.*s",
169 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
170 (int) MIN(MAX_SCREEN_COLS
, sizeof(ps_global
->last_error
)-70),
172 ps_global
->last_error
[ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
173 : sizeof(ps_global
->last_error
)-1] = '\0';
176 * Then either set special bits in the pine struct or
177 * display the message if it's tagged as an "ALERT" or
178 * its errflg > NIL (i.e., WARN, or ERROR)
182 * We'd like to sp_mark_stream_dead() here but we can't do that because
183 * that might call mail_close and we are already in a c-client callback.
184 * So just set the dead bit and clean it up later.
186 sp_set_dead_stream(stream
, 1);
187 else if(!strncmp(string
, "[TRYCREATE]", 11))
188 ps_global
->try_to_create
= 1;
189 else if(!strncmp(string
, "[REFERRAL ", 10))
190 ; /* handled in the imap_referral() callback */
191 else if(!strncmp(string
, "[ALERT]", 7))
192 q_status_message2(SM_MODAL
, 3, 3,
193 _("Alert received while accessing \"%s\": %s"),
194 (stream
&& stream
->mailbox
)
195 ? stream
->mailbox
: "-no folder-",
196 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf
+10000),
197 SIZEOF_20KBUF
-10000, string
));
198 else if(!strncmp(string
, "[UNSEEN ", 8)){
202 for(p
= string
+ 8; isdigit(*p
); p
++)
203 n
= (n
* 10) + (*p
- '0');
205 sp_set_first_unseen(stream
, n
);
207 else if(!strncmp(string
, "[READ-ONLY]", 11)
208 && !(stream
&& stream
->mailbox
&& IS_NEWS(stream
)))
209 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, "%s : %s",
210 (stream
&& stream
->mailbox
)
211 ? stream
->mailbox
: "-no folder-",
213 else if((errflg
&& errflg
!= BYE
&& errflg
!= PARSE
)
214 && !ps_global
->noshow_error
216 && (ps_global
->noshow_warn
|| (stream
&& stream
->unhealthy
))))
217 q_status_message(SM_ORDER
| ((errflg
== ERROR
) ? SM_DING
: 0),
218 3, 6, ps_global
->last_error
);
222 /*----------------------------------------------------------------------
223 Queue imap log message for display in the message line
225 Args: string -- The message
226 errflg -- flag set to 1 if pertains to an error
228 Result: Message queued for display
230 The c-client/imap reports most of it's status and errors here
233 mm_log(char *string
, long int errflg
)
235 char message
[sizeof(ps_global
->c_client_error
)];
238 static char saw_kerberos_init_warning
;
242 now
= time((time_t *)0);
243 tm_now
= localtime(&now
);
245 dprint((((errflg
== TCPDEBUG
) && ps_global
->debug_tcp
) ? 1 :
246 (errflg
== TCPDEBUG
) ? 10 : 2,
247 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
248 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
249 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
250 (!errflg
) ? "babble" :
251 (errflg
== ERROR
) ? "error" :
252 (errflg
== WARN
) ? "warning" :
253 (errflg
== PARSE
) ? "parse" :
254 (errflg
== TCPDEBUG
) ? "tcp" :
255 (errflg
== BYE
) ? "bye" : "unknown",
256 string
? string
: "?"));
258 if(errflg
== ERROR
&& !strncmp(string
, "[TRYCREATE]", 11)){
259 ps_global
->try_to_create
= 1;
262 else if(ps_global
->try_to_create
263 || !strncmp(string
, "[CLOSED]", 8)
264 || (sp_dead_stream(ps_global
->mail_stream
) && strstr(string
, "No-op")))
266 * Don't display if creating new folder OR
267 * warning about a dead stream ...
271 strncpy(message
, string
, sizeof(message
));
272 message
[sizeof(message
) - 1] = '\0';
274 if(errflg
== WARN
&& srchstr(message
, "try running kinit") != NULL
){
275 if(saw_kerberos_init_warning
)
278 saw_kerberos_init_warning
= 1;
281 /*---- replace all "mailbox" with "folder" ------*/
282 occurence
= srchstr(message
, "mailbox");
285 || isspace((unsigned char) *(occurence
+7))
286 || *(occurence
+7) == ':'){
287 was_capitalized
= isupper((unsigned char) *occurence
);
288 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 7, (errflg
== PARSE
? "address" : "folder"));
290 *occurence
= (errflg
== PARSE
? 'A' : 'F');
295 occurence
= srchstr(occurence
, "mailbox");
298 /*---- replace all "GSSAPI" with "Kerberos" ------*/
299 occurence
= srchstr(message
, "GSSAPI");
302 || isspace((unsigned char) *(occurence
+6))
303 || *(occurence
+6) == ':')
304 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 6, "Kerberos");
308 occurence
= srchstr(occurence
, "GSSAPI");
312 ps_global
->mm_log_error
= 1;
314 if(errflg
== PARSE
|| (errflg
== ERROR
&& ps_global
->noshow_error
))
315 strncpy(ps_global
->c_client_error
, message
,
316 sizeof(ps_global
->c_client_error
));
318 if(ps_global
->noshow_error
319 || (ps_global
->noshow_warn
&& errflg
== WARN
)
320 || !(errflg
== ERROR
|| errflg
== WARN
))
321 return; /* Only care about errors; don't print when asked not to */
323 /*---- Display the message ------*/
324 q_status_message((errflg
== ERROR
) ? (SM_ORDER
| SM_DING
) : SM_ORDER
,
326 strncpy(ps_global
->last_error
, message
, sizeof(ps_global
->last_error
));
327 ps_global
->last_error
[sizeof(ps_global
->last_error
) - 1] = '\0';
332 mm_login_work(NETMBX
*mb
, char *user
, char *pwd
, long int trial
,
333 char *usethisprompt
, char *altuserforcache
)
335 char prompt
[1000], *last
;
336 char port
[20], non_def_port
[20], insecure
[20];
337 char defuser
[NETMAXUSER
];
338 char hostleadin
[80], hostname
[200], defubuf
[200];
339 char logleadin
[80], pwleadin
[50];
340 char hostlist0
[MAILTMPLEN
], hostlist1
[MAILTMPLEN
];
341 /* TRANSLATORS: when logging in, this text is added to the prompt to show
342 that the password will be sent unencrypted over the network. This is
343 just a warning message that gets added parenthetically when the user
344 is asked for a password. */
345 char *insec
= _(" (INSECURE)");
346 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
347 after having already failed at least once. */
348 char *retry
= _("Retrying - ");
349 /* TRANSLATORS: A label for the hostname that the user is logging in on */
350 char *hostlabel
= _("HOST");
351 /* TRANSLATORS: user is logging in as a particular user (a particular
352 login name), this is just labelling that user name. */
353 char *userlabel
= _("USER");
354 STRLIST_S hostlist
[2];
356 int len
, rc
, q_line
, flags
;
357 int oespace
, avail
, need
, save_dont_use
;
360 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
361 int preserve_password
= -1;
364 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
365 trial
, mb
->user
? mb
->user
: "(null)",
366 mb
->service
? mb
->service
: "(null)",
367 mb
->port
? " port=" : "",
368 mb
->port
? comatose(mb
->port
) : "",
369 altuserforcache
? " altuserforcache =" : "",
370 altuserforcache
? altuserforcache
: ""));
371 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
373 save_in_init
= ps_global
->in_init_seq
;
374 ps_global
->in_init_seq
= 0;
375 ps_global
->no_newmail_check_from_optionally_enter
= 1;
377 /* make sure errors are seen */
379 flush_status_messages(0);
382 * Add port number to hostname if going through a tunnel or something
384 non_def_port
[0] = '\0';
385 if(mb
->port
&& mb
->service
&&
386 (sv
= getservbyname(mb
->service
, "tcp")) &&
387 (mb
->port
!= ntohs(sv
->s_port
))){
388 snprintf(non_def_port
, sizeof(non_def_port
), ":%lu", mb
->port
);
389 non_def_port
[sizeof(non_def_port
)-1] = '\0';
390 dprint((9, "mm_login: using non-default port=%s\n",
391 non_def_port
? non_def_port
: "?"));
395 * set up host list for sybil servers...
398 strncpy(hostlist0
, mb
->host
, sizeof(hostlist0
)-1);
399 hostlist0
[sizeof(hostlist0
)-1] = '\0';
400 strncat(hostlist0
, non_def_port
, sizeof(hostlist0
)-strlen(hostlist0
)-1);
401 hostlist0
[sizeof(hostlist0
)-1] = '\0';
402 hostlist
[0].name
= hostlist0
;
403 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
404 strncpy(hostlist1
, mb
->orighost
, sizeof(hostlist1
)-1);
405 hostlist1
[sizeof(hostlist1
)-1] = '\0';
406 strncat(hostlist1
, non_def_port
, sizeof(hostlist1
)-strlen(hostlist1
)-1);
407 hostlist1
[sizeof(hostlist1
)-1] = '\0';
408 hostlist
[0].next
= &hostlist
[1];
409 hostlist
[1].name
= hostlist1
;
410 hostlist
[1].next
= NULL
;
413 hostlist
[0].next
= NULL
;
416 hostlist
[0].name
= mb
->host
;
417 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
418 hostlist
[0].next
= &hostlist
[1];
419 hostlist
[1].name
= mb
->orighost
;
420 hostlist
[1].next
= NULL
;
423 hostlist
[0].next
= NULL
;
426 if(hostlist
[0].name
){
427 dprint((9, "mm_login: host=%s\n",
428 hostlist
[0].name
? hostlist
[0].name
: "?"));
429 if(hostlist
[0].next
&& hostlist
[1].name
){
430 dprint((9, "mm_login: orighost=%s\n", hostlist
[1].name
));
435 * Initialize user name with either
436 * 1) /user= value in the stream being logged into,
437 * or 2) the user name we're running under.
439 * Note that VAR_USER_ID is not yet initialized if this login is
440 * the one to access the remote config file. In that case, the user
441 * can supply the username in the config file name with /user=.
443 if(trial
== 0L && !altuserforcache
){
444 strncpy(user
, (*mb
->user
) ? mb
->user
:
445 ps_global
->VAR_USER_ID
? ps_global
->VAR_USER_ID
: "",
447 user
[NETMAXUSER
-1] = '\0';
449 /* try last working password associated with this host. */
450 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
451 (mb
->sslflag
||mb
->tlsflag
))){
452 dprint((9, "mm_login: found a password to try\n"));
453 ps_global
->no_newmail_check_from_optionally_enter
= 0;
454 ps_global
->in_init_seq
= save_in_init
;
458 #ifdef LOCAL_PASSWD_CACHE
459 /* check to see if there's a password left over from last session */
460 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
461 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
462 imap_set_passwd(&mm_login_list
, pwd
, user
,
463 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
464 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
465 (mb
->sslflag
||mb
->tlsflag
));
466 dprint((9, "mm_login: found a password in passfile to try\n"));
467 ps_global
->no_newmail_check_from_optionally_enter
= 0;
468 ps_global
->in_init_seq
= save_in_init
;
471 #endif /* LOCAL_PASSWD_CACHE */
474 * If no explicit user name supplied and we've not logged in
475 * with our local user name, see if we've visited this
476 * host before as someone else.
479 ((last
= imap_get_user(mm_login_list
, hostlist
))
480 #ifdef LOCAL_PASSWD_CACHE
482 (last
= get_passfile_user(ps_global
->pinerc
, hostlist
))
483 #endif /* LOCAL_PASSWD_CACHE */
485 strncpy(user
, last
, NETMAXUSER
);
486 user
[NETMAXUSER
-1] = '\0';
487 dprint((9, "mm_login: found user=%s\n",
490 /* try last working password associated with this host/user. */
491 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
492 (mb
->sslflag
||mb
->tlsflag
))){
494 "mm_login: found a password for user=%s to try\n",
496 ps_global
->no_newmail_check_from_optionally_enter
= 0;
497 ps_global
->in_init_seq
= save_in_init
;
501 #ifdef LOCAL_PASSWD_CACHE
502 /* check to see if there's a password left over from last session */
503 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
504 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
505 imap_set_passwd(&mm_login_list
, pwd
, user
,
506 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
507 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
508 (mb
->sslflag
||mb
->tlsflag
));
510 "mm_login: found a password for user=%s in passfile to try\n",
512 ps_global
->no_newmail_check_from_optionally_enter
= 0;
513 ps_global
->in_init_seq
= save_in_init
;
516 #endif /* LOCAL_PASSWD_CACHE */
519 #if !defined(DOS) && !defined(OS2)
520 if(!*mb
->user
&& !*user
&&
521 (last
= (ps_global
->ui
.login
&& ps_global
->ui
.login
[0])
522 ? ps_global
->ui
.login
: NULL
)
524 strncpy(user
, last
, NETMAXUSER
);
525 user
[NETMAXUSER
-1] = '\0';
526 dprint((9, "mm_login: found user=%s\n",
529 /* try last working password associated with this host. */
530 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
531 (mb
->sslflag
||mb
->tlsflag
))){
532 dprint((9, "mm_login:ui: found a password to try\n"));
533 ps_global
->no_newmail_check_from_optionally_enter
= 0;
534 ps_global
->in_init_seq
= save_in_init
;
538 #ifdef LOCAL_PASSWD_CACHE
539 /* check to see if there's a password left over from last session */
540 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
541 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
542 imap_set_passwd(&mm_login_list
, pwd
, user
,
543 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
544 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
545 (mb
->sslflag
||mb
->tlsflag
));
546 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
547 ps_global
->no_newmail_check_from_optionally_enter
= 0;
548 ps_global
->in_init_seq
= save_in_init
;
551 #endif /* LOCAL_PASSWD_CACHE */
556 user
[NETMAXUSER
-1] = '\0';
562 * Even if we have a user now, user gets a chance to change it.
564 ps_global
->mangled_footer
= 1;
565 if(!*mb
->user
&& !altuserforcache
){
570 * Instead of offering user with a value that the user can edit,
571 * we offer [user] as a default so that the user can type CR to
572 * use it. Otherwise, the user has to type in whole name.
574 strncpy(defuser
, user
, sizeof(defuser
)-1);
575 defuser
[sizeof(defuser
)-1] = '\0';
579 * Need space for "Retrying - "
585 * about 15 chars for input
588 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
589 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
590 hostleadin
[sizeof(hostleadin
)-1] = '\0';
592 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
593 hostname
[sizeof(hostname
)-1] = '\0';
596 * Add port number to hostname if going through a tunnel or something
599 strncpy(port
, non_def_port
, sizeof(port
));
604 /* if not encrypted and SSL/TLS is supported */
605 if(!(mb
->sslflag
||mb
->tlsflag
) &&
606 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
607 strncpy(insecure
, insec
, sizeof(insecure
));
609 /* TRANSLATORS: user is being asked to type in their login name */
610 snprintf(logleadin
, sizeof(logleadin
), " %s", _("ENTER LOGIN NAME"));
612 snprintf(defubuf
, sizeof(defubuf
), "%s%s%s : ", (*defuser
) ? " [" : "",
613 (*defuser
) ? defuser
: "",
614 (*defuser
) ? "]" : "");
615 defubuf
[sizeof(defubuf
)-1] = '\0';
616 /* space reserved after prompt */
617 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
619 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
620 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
621 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) + oespace
;
623 /* If we're retrying cut the hostname back to the first word. */
624 if(avail
< need
&& trial
> 0){
627 len
= strlen(hostname
);
628 if((p
= strchr(hostname
, '.')) != NULL
){
630 need
-= (len
- strlen(hostname
));
635 need
-= utf8_width(retry
);
640 /* reduce length of logleadin */
641 len
= utf8_width(logleadin
);
642 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
643 longer version doesn't fit on screen */
644 snprintf(logleadin
, sizeof(logleadin
), " %s", _("LOGIN"));
645 need
-= (len
- utf8_width(logleadin
));
648 /* get two spaces from hostleadin */
649 len
= utf8_width(hostleadin
);
650 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
651 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
652 hostleadin
[sizeof(hostleadin
)-1] = '\0';
653 need
-= (len
- utf8_width(hostleadin
));
655 /* get rid of port */
656 if(avail
< need
&& strlen(port
) > 0){
657 need
-= strlen(port
);
665 * Reduce space for hostname. Best we can do is 6 chars
668 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
669 len
= strlen(hostname
);
670 strncpy(hostname
+reduce_to
-3, "...", 4);
671 need
-= (len
- strlen(hostname
));
673 if(avail
< need
&& strlen(insecure
) > 0){
674 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
676 insecure
[strlen(insecure
)-4] = ')';
677 insecure
[strlen(insecure
)-3] = '\0';
680 need
-= utf8_width(insecure
);
686 if(strlen(defubuf
) > 3){
687 len
= strlen(defubuf
);
688 strncpy(defubuf
, " [..] :", 9);
689 need
-= (len
- strlen(defubuf
));
693 strncpy(defubuf
, ":", 2);
696 * If it still doesn't fit, optionally_enter gets
705 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s",
706 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
);
707 prompt
[sizeof(prompt
)-1] = '\0';
711 mm_login_alt_cue(mb
);
713 flags
= OE_APPEND_CURRENT
;
714 save_dont_use
= ps_global
->dont_use_init_cmds
;
715 ps_global
->dont_use_init_cmds
= 1;
717 if(!*user
&& *defuser
){
718 strncpy(user
, defuser
, NETMAXUSER
);
719 user
[NETMAXUSER
-1] = '\0';
722 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, pwd
, NETMAXPASSWD
,
723 #ifdef LOCAL_PASSWD_CACHE
724 is_using_passfile() ? 1 :
725 #endif /* LOCAL_PASSWD_CACHE */
726 0, 0, &preserve_password
);
727 ps_global
->dont_use_init_cmds
= save_dont_use
;
728 if(rc
== 0 && *user
&& *pwd
)
730 #else /* !_WINDOWS */
731 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
732 prompt
, NULL
, help
, &flags
);
733 #endif /* !_WINDOWS */
734 ps_global
->dont_use_init_cmds
= save_dont_use
;
737 help
= help
== NO_HELP
? h_oe_login
: NO_HELP
;
742 if(rc
== 0 && !*user
){
743 strncpy(user
, defuser
, NETMAXUSER
);
744 user
[NETMAXUSER
-1] = '\0';
751 if(rc
== 1 || !user
[0]) {
752 ps_global
->user_says_cancel
= (rc
== 1);
758 strncpy(user
, mb
->user
, NETMAXUSER
);
759 user
[NETMAXUSER
-1] = '\0';
762 user
[NETMAXUSER
-1] = '\0';
763 pwd
[NETMAXPASSWD
-1] = '\0';
765 if(!(user
[0] || altuserforcache
)){
766 ps_global
->no_newmail_check_from_optionally_enter
= 0;
767 ps_global
->in_init_seq
= save_in_init
;
772 * Now that we have a user, we can check in the cache again to see
773 * if there is a password there. Try last working password associated
774 * with this host and user.
776 if(trial
== 0L && !*mb
->user
&& !altuserforcache
){
777 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
778 (mb
->sslflag
||mb
->tlsflag
))){
779 ps_global
->no_newmail_check_from_optionally_enter
= 0;
780 ps_global
->in_init_seq
= save_in_init
;
784 #ifdef LOCAL_PASSWD_CACHE
785 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
786 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
787 imap_set_passwd(&mm_login_list
, pwd
, user
,
788 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
789 ps_global
->no_newmail_check_from_optionally_enter
= 0;
790 ps_global
->in_init_seq
= save_in_init
;
793 #endif /* LOCAL_PASSWD_CACHE */
795 else if(trial
== 0 && altuserforcache
){
796 if(imap_get_passwd(mm_login_list
, pwd
, altuserforcache
, hostlist
,
797 (mb
->sslflag
||mb
->tlsflag
))){
798 ps_global
->no_newmail_check_from_optionally_enter
= 0;
799 ps_global
->in_init_seq
= save_in_init
;
803 #ifdef LOCAL_PASSWD_CACHE
804 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
805 altuserforcache
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
806 imap_set_passwd(&mm_login_list
, pwd
, altuserforcache
,
807 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
808 ps_global
->no_newmail_check_from_optionally_enter
= 0;
809 ps_global
->in_init_seq
= save_in_init
;
812 #endif /* LOCAL_PASSWD_CACHE */
816 * Didn't find password in cache or this isn't the first try. Ask user.
821 * Need space for "Retrying - "
827 * " ENTER PASSWORD: "
828 * about 15 chars for input
831 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
832 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
834 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
835 hostname
[sizeof(hostname
)-1] = '\0';
838 * Add port number to hostname if going through a tunnel or something
841 strncpy(port
, non_def_port
, sizeof(port
));
847 /* if not encrypted and SSL/TLS is supported */
848 if(!(mb
->sslflag
||mb
->tlsflag
) &&
849 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
850 strncpy(insecure
, insec
, sizeof(insecure
));
853 strncpy(logleadin
, usethisprompt
, sizeof(logleadin
));
854 logleadin
[sizeof(logleadin
)-1] = '\0';
859 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
861 strncpy(defubuf
, user
, sizeof(defubuf
)-1);
862 defubuf
[sizeof(defubuf
)-1] = '\0';
865 /* TRANSLATORS: user is being asked to type in their password */
866 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("ENTER PASSWORD"));
868 /* space reserved after prompt */
869 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
871 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
872 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
873 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) +
874 utf8_width(pwleadin
) + oespace
;
876 if(avail
< need
&& trial
> 0){
879 len
= strlen(hostname
);
880 if((p
= strchr(hostname
, '.')) != NULL
){
882 need
-= (len
- strlen(hostname
));
887 need
-= utf8_width(retry
);
893 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
897 rplstr(pwleadin
, sizeof(pwleadin
), 1, "");
901 /* get two spaces from hostleadin */
902 len
= utf8_width(hostleadin
);
903 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
904 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
905 hostleadin
[sizeof(hostleadin
)-1] = '\0';
906 need
-= (len
- utf8_width(hostleadin
));
908 /* get rid of port */
909 if(avail
< need
&& strlen(port
) > 0){
910 need
-= strlen(port
);
915 len
= utf8_width(pwleadin
);
916 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
917 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("PASSWORD"));
918 need
-= (len
- utf8_width(pwleadin
));
926 * Reduce space for hostname. Best we can do is 6 chars
929 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
930 len
= strlen(hostname
);
931 strncpy(hostname
+reduce_to
-3, "...", 4);
932 need
-= (len
- strlen(hostname
));
934 if(avail
< need
&& strlen(insecure
) > 0){
935 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
937 insecure
[strlen(insecure
)-4] = ')';
938 insecure
[strlen(insecure
)-3] = '\0';
941 need
-= utf8_width(insecure
);
947 len
= utf8_width(logleadin
);
948 strncpy(logleadin
, " ", sizeof(logleadin
));
949 logleadin
[sizeof(logleadin
)-1] = '\0';
950 need
-= (len
- utf8_width(logleadin
));
953 reduce_to
= (need
- avail
< strlen(defubuf
) - 6) ? (strlen(defubuf
)-(need
-avail
)) : 0;
955 strncpy(defubuf
+reduce_to
-3, "...", 4);
964 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s%s",
965 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
, pwleadin
);
966 prompt
[sizeof(prompt
)-1] = '\0';
971 mm_login_alt_cue(mb
);
973 save_dont_use
= ps_global
->dont_use_init_cmds
;
974 ps_global
->dont_use_init_cmds
= 1;
975 flags
= F_ON(F_QUELL_ASTERISKS
, ps_global
) ? OE_PASSWD_NOAST
: OE_PASSWD
;
977 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, pwd
, NETMAXPASSWD
, 0, 1,
979 #else /* !_WINDOWS */
980 rc
= optionally_enter(pwd
, q_line
, 0, NETMAXPASSWD
,
981 prompt
, NULL
, help
, &flags
);
982 #endif /* !_WINDOWS */
983 ps_global
->dont_use_init_cmds
= save_dont_use
;
986 help
= help
== NO_HELP
? h_oe_passwd
: NO_HELP
;
994 if(rc
== 1 || !pwd
[0]) {
995 ps_global
->user_says_cancel
= (rc
== 1);
996 user
[0] = pwd
[0] = '\0';
997 ps_global
->no_newmail_check_from_optionally_enter
= 0;
998 ps_global
->in_init_seq
= save_in_init
;
1005 /* remember the password for next time */
1006 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
1007 imap_set_passwd(&mm_login_list
, pwd
,
1008 altuserforcache
? altuserforcache
: user
, hostlist
,
1009 (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1010 #ifdef LOCAL_PASSWD_CACHE
1011 /* if requested, remember it on disk for next session */
1012 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
1013 set_passfile_passwd(ps_global
->pinerc
, pwd
,
1014 altuserforcache
? altuserforcache
: user
, hostlist
,
1015 (mb
->sslflag
||mb
->tlsflag
),
1016 (preserve_password
== -1 ? 0
1017 : (preserve_password
== 0 ? 2 :1)));
1018 #endif /* LOCAL_PASSWD_CACHE */
1020 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1025 mm_login_alt_cue(NETMBX
*mb
)
1027 if(ps_global
->ttyo
){
1030 lastc
= pico_set_colors(ps_global
->VAR_TITLE_FORE_COLOR
,
1031 ps_global
->VAR_TITLE_BACK_COLOR
,
1034 mark_titlebar_dirty();
1035 PutLine0(0, ps_global
->ttyo
->screen_cols
- 1,
1036 (mb
->sslflag
||mb
->tlsflag
) ? "+" : " ");
1039 (void)pico_set_colorp(lastc
, PSC_NONE
);
1040 free_color_pair(&lastc
);
1048 /*----------------------------------------------------------------------
1049 Receive notification of an error writing to disk
1051 Args: stream -- The stream the error occured on
1052 errcode -- The system error code (errno)
1053 serious -- Flag indicating error is serious (mail may be lost)
1055 Result: If error is non serious, the stream is marked as having an error
1056 and deletes are disallowed until error clears
1057 If error is serious this goes modal, allowing the user to retry
1058 or get a shell escape to fix the condition. When the condition is
1059 serious it means that mail existing in the mailbox will be lost
1060 if Pine exits without writing, so we try to induce the user to
1061 fix the error, go get someone that can fix the error, or whatever
1062 and don't provide an easy way out.
1065 mm_diskerror (MAILSTREAM
*stream
, long int errcode
, long int serious
)
1069 static ESCKEY_S de_opts
[] = {
1070 {'r', 'r', "R", "Retry"},
1071 {'f', 'f', "F", "FileBrowser"},
1072 {'s', 's', "S", "ShellPrompt"},
1075 #define DE_COLS (ps_global->ttyo->screen_cols)
1076 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
1078 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
1080 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
1081 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
1083 "The reported error number is %s. The last reported mail error was:"
1084 static char *de_msg
[] = {
1085 "Please try to correct the error preventing Alpine from saving your",
1086 "mail folder. For example if the disk is out of space try removing",
1087 "unneeded files. You might also contact your system administrator.",
1089 "Both Alpine's File Browser and an option to enter the system's",
1090 "command prompt are offered to aid in fixing the problem. When",
1091 "you believe the problem is resolved, choose the \"Retry\" option.",
1092 "Be aware that messages may be lost or this folder left in an",
1093 "inaccessible condition if you exit or kill Alpine before the problem",
1096 static char *de_shell_msg
[] = {
1097 "\n\nPlease attempt to correct the error preventing saving of the",
1098 "mail folder. If you do not know how to correct the problem, contact",
1099 "your system administrator. To return to Alpine, type \"exit\".",
1103 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
1104 DE_FOLDER(stream
), errcode
, serious
? "" : "not "));
1105 dprint((0, "***** message: \"%s\"\n\n",
1106 ps_global
->last_error
? ps_global
->last_error
: "?"));
1109 sp_set_io_error_on_stream(stream
, 1);
1114 /* replace pine's body display with screen full of explanatory text */
1116 PutLine1(2, MAX((DE_COLS
- sizeof(DE_STR1
)
1117 - strlen(DE_FOLDER(stream
)))/2, 0),
1118 DE_STR1
, DE_FOLDER(stream
));
1120 PutLine1(3, 4, DE_STR2
, long2string(errcode
));
1122 PutLine0(4, 0, " \"");
1123 removing_leading_white_space(ps_global
->last_error
);
1124 for(i
= 4, p
= ps_global
->last_error
; *p
&& i
< DE_LINE
; ){
1125 for(s
= NULL
, q
= p
; *q
&& q
- p
< DE_COLS
- 16; q
++)
1126 if(isspace((unsigned char)*q
))
1137 PutLine0(i
, 0, " ");
1138 while(*p
&& isspace((unsigned char)*p
))
1149 for(j
= ++i
; i
< DE_LINE
&& de_msg
[i
-j
]; i
++){
1151 PutLine0(i
, 0, " ");
1152 Write_to_screen(de_msg
[i
-j
]);
1158 switch(radio_buttons(DE_PMT
, -FOOTER_ROWS(ps_global
), de_opts
,
1159 'r', 0, NO_HELP
, RB_FLUSH_IN
| RB_NO_NEWMAIL
)){
1160 case 'r' : /* Retry! */
1161 ps_global
->mangled_screen
= 1;
1164 case 'f' : /* File Browser */
1166 char full_filename
[MAXPATH
+1], filename
[MAXPATH
+1];
1169 build_path(full_filename
, ps_global
->home_dir
, filename
,
1170 sizeof(full_filename
));
1171 file_lister("DISK ERROR", full_filename
, sizeof(full_filename
),
1172 filename
, sizeof(filename
), FALSE
, FB_SAVE
);
1179 end_keyboard(ps_global
? F_ON(F_USE_FK
,ps_global
) : 0);
1180 end_tty_driver(ps_global
);
1181 for(i
= 0; de_shell_msg
[i
]; i
++)
1182 puts(de_shell_msg
[i
]);
1185 * Don't use our piping mechanism to spawn a subshell here
1186 * since it will the server (thus reentering c-client).
1193 init_tty_driver(ps_global
);
1194 init_keyboard(F_ON(F_USE_FK
,ps_global
));
1198 if(ps_global
->redrawer
)
1199 (*ps_global
->redrawer
)();
1205 pine_tcptimeout_noscreen(long int elapsed
, long int sincelast
, char *host
)
1214 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
1215 snprintf(pmt
, sizeof(pmt
),
1216 _("No reply in %s seconds from server %s. Break connection"),
1217 long2string(elapsed
), host
);
1218 pmt
[sizeof(pmt
)-1] = '\0';
1219 if(want_to(pmt
, 'n', 'n', NO_HELP
, WT_FLUSH_IN
) == 'y'){
1220 ps_global
->user_says_cancel
= 1;
1225 ps_global
->tcptimeout
= 0;
1231 * -------------------------------------------------------------
1232 * These are declared in pith/imap.h as mandatory to implement.
1233 * -------------------------------------------------------------
1238 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
1241 pine_tcptimeout(long int elapsed
, long int sincelast
, char *host
)
1243 long rv
= 1L; /* keep trying by default */
1246 ps_global
->tcptimeout
= 1;
1248 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
1249 long2string(elapsed
), host
));
1258 if(ps_global
->noshow_timeout
)
1261 if(!ps_global
->ttyo
)
1262 return(pine_tcptimeout_noscreen(elapsed
, sincelast
, host
));
1267 * Prompt after a minute (since by then things are probably really bad)
1268 * A prompt timeout means "keep trying"...
1270 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
1273 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
1274 if((clear_inverse
= !InverseState()) != 0)
1279 PutLine2(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
), 0,
1280 _("No reply in %s seconds from server %s. Break connection?"),
1281 long2string(elapsed
), host
);
1286 if(ps_global
->read_bail
|| ch
== 'y' || ch
== 'Y'){
1287 ps_global
->read_bail
= 0;
1288 ps_global
->user_says_cancel
= 1;
1295 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
1298 if(rv
== 1L){ /* just warn 'em something's up */
1299 q_status_message2(SM_ORDER
, 0, 0,
1300 _("No reply in %s seconds from server %s. Still Waiting..."),
1301 long2string(elapsed
), host
);
1302 flush_status_messages(0); /* make sure it's seen */
1305 mark_status_dirty(); /* make sure it get's cleared */
1307 resume_busy_cue((rv
== 1) ? 3 : 0);
1308 ps_global
->tcptimeout
= 0;
1313 QUOTALIST
*pine_quotalist_copy (QUOTALIST
*pquota
)
1315 QUOTALIST
*cquota
= NULL
;
1318 cquota
= mail_newquotalist();
1319 if (pquota
->name
&& *pquota
->name
)
1320 cquota
->name
= cpystr(pquota
->name
);
1321 cquota
->usage
= pquota
->usage
;
1322 cquota
->limit
= pquota
->limit
;
1324 cquota
->next
= pine_quotalist_copy(pquota
->next
);
1330 /* c-client callback to handle quota */
1333 pine_parse_quota (MAILSTREAM
*stream
, unsigned char *msg
, QUOTALIST
*pquota
)
1335 ps_global
->quota
= pine_quotalist_copy (pquota
);
1339 * C-client callback to handle SSL/TLS certificate validation failures
1341 * Returning 0 means error becomes fatal
1342 * Non-zero means certificate problem is ignored and SSL session is
1345 * We remember the answer and won't re-ask for subsequent open attempts to
1346 * the same hostname.
1349 pine_sslcertquery(char *reason
, char *host
, char *cert
)
1352 char *unknown
= "<unknown>";
1355 int ok_novalidate
= 0, warned
= 0;
1357 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
1358 host
? host
: "?", reason
? reason
: "?",
1359 cert
? cert
: "?"));
1361 hostlist
.name
= host
? host
: "";
1362 hostlist
.next
= NULL
;
1365 * See if we've been asked about this host before.
1367 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
1368 /* we were asked before, did we say Yes? */
1374 "sslcertificatequery: approved automatically\n"));
1378 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
1381 if(ps_global
->ttyo
){
1383 STORE_S
*in_store
, *out_store
;
1385 HANDLE_S
*handles
= NULL
;
1386 int the_answer
= 'n';
1388 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
1389 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
1392 so_puts(in_store
, "<HTML><P>");
1393 so_puts(in_store
, _("There was a failure validating the SSL/TLS certificate for the server"));
1395 so_puts(in_store
, "<P><CENTER>");
1396 so_puts(in_store
, host
? host
: unknown
);
1397 so_puts(in_store
, "</CENTER>");
1399 so_puts(in_store
, "<P>");
1400 so_puts(in_store
, _("The reason for the failure was"));
1402 /* squirrel away details */
1404 fs_give((void **)&details_host
);
1406 fs_give((void **)&details_reason
);
1408 fs_give((void **)&details_cert
);
1410 details_host
= cpystr(host
? host
: unknown
);
1411 details_reason
= cpystr(reason
? reason
: unknown
);
1412 details_cert
= cpystr(cert
? cert
: unknown
);
1414 so_puts(in_store
, "<P><CENTER>");
1415 snprintf(tmp
, sizeof(tmp
), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
1416 reason
? reason
: unknown
);
1417 tmp
[sizeof(tmp
)-1] = '\0';
1419 so_puts(in_store
, tmp
);
1420 so_puts(in_store
, "</CENTER>");
1422 so_puts(in_store
, "<P>");
1423 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."));
1425 so_puts(in_store
, "<P>");
1426 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"));
1428 so_puts(in_store
, "<P><CENTER>");
1429 so_puts(in_store
, "/novalidate-cert");
1430 so_puts(in_store
, "</CENTER>");
1432 so_puts(in_store
, "<P>");
1433 so_puts(in_store
, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
1435 so_puts(in_store
, "<P><CENTER>");
1436 so_puts(in_store
, host
? host
: unknown
);
1437 so_puts(in_store
, "</CENTER>");
1439 so_puts(in_store
, "<P>");
1440 so_puts(in_store
, _("in your configuration, replace those characters with"));
1442 so_puts(in_store
, "<P><CENTER>");
1443 so_puts(in_store
, host
? host
: unknown
);
1444 so_puts(in_store
, "/novalidate-cert");
1445 so_puts(in_store
, "</CENTER>");
1447 so_puts(in_store
, "<P>");
1448 so_puts(in_store
, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
1450 so_seek(in_store
, 0L, 0);
1451 init_handles(&handles
);
1453 gf_link_filter(gf_html2plain
,
1454 gf_html2plain_opt(NULL
,
1455 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
1456 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
1457 gf_set_so_readc(&gc
, in_store
);
1458 gf_set_so_writec(&pc
, out_store
);
1460 gf_clear_so_writec(out_store
);
1461 gf_clear_so_readc(in_store
);
1463 memset(&sargs
, 0, sizeof(SCROLL_S
));
1464 sargs
.text
.handles
= handles
;
1465 sargs
.text
.text
= so_text(out_store
);
1466 sargs
.text
.src
= CharStar
;
1467 sargs
.text
.desc
= _("help text");
1468 sargs
.bar
.title
= _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
1469 sargs
.proc
.tool
= answer_cert_failure
;
1470 sargs
.proc
.data
.p
= (void *)&the_answer
;
1471 sargs
.keys
.menu
= &ans_certquery_keymenu
;
1472 /* don't want to re-enter c-client */
1473 sargs
.quell_newmail
= 1;
1474 setbitmap(sargs
.keys
.bitmap
);
1475 sargs
.help
.text
= h_tls_validation_failure
;
1476 sargs
.help
.title
= _("HELP FOR CERT VALIDATION FAILURE");
1480 if(the_answer
== 'y')
1483 ps_global
->mangled_screen
= 1;
1484 ps_global
->painted_body_on_startup
= 0;
1485 ps_global
->painted_footer_on_startup
= 0;
1487 so_give(&out_store
);
1488 free_handles(&handles
);
1490 fs_give((void **)&details_host
);
1492 fs_give((void **)&details_reason
);
1494 fs_give((void **)&details_cert
);
1498 * If screen hasn't been initialized yet, use want_to.
1501 memset((void *)tmp
, 0, sizeof(tmp
));
1503 reason
? reason
: _("SSL/TLS certificate validation failure"),
1505 tmp
[sizeof(tmp
)-1] = '\0';
1506 strncat(tmp
, _(": Continue anyway "), sizeof(tmp
)-strlen(tmp
)-1);
1508 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y')
1513 q_status_message1(SM_ORDER
, 1, 3, _("Open of %s cancelled"),
1514 host
? host
: unknown
);
1516 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, rv
? 1 : 0, 0);
1518 dprint((5, "sslcertificatequery: %s\n",
1519 rv
? "approved" : "rejected"));
1526 pine_newsrcquery(MAILSTREAM
*stream
, char *mulname
, char *name
)
1528 char buf
[MAILTMPLEN
];
1530 if((can_access(mulname
, ACCESS_EXISTS
) == 0)
1531 || !(can_access(name
, ACCESS_EXISTS
) == 0))
1534 snprintf(buf
, sizeof(buf
),
1535 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
1537 strlen(last_cmpnt(name
)) > 15 ? "..." : "");
1538 buf
[sizeof(buf
)-1] = '\0';
1539 if(want_to(buf
, 'n', 'n', NO_HELP
, WT_NORM
) == 'y')
1540 rename_file(name
, mulname
);
1546 url_local_certdetails(char *url
)
1548 if(!struncmp(url
, "x-alpine-cert:", 14)){
1553 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
1554 q_status_message(SM_ORDER
| SM_DING
, 7, 10,
1555 _("Error allocating space for details."));
1559 so_puts(store
, _("Host given by user:\n\n "));
1560 so_puts(store
, details_host
);
1561 so_puts(store
, _("\n\nReason for failure:\n\n "));
1562 so_puts(store
, details_reason
);
1563 so_puts(store
, _("\n\nCertificate being verified:\n\n"));
1564 folded
= fold(details_cert
, ps_global
->ttyo
->screen_cols
, ps_global
->ttyo
->screen_cols
, " ", " ", FLD_NONE
);
1565 so_puts(store
, folded
);
1566 fs_give((void **)&folded
);
1567 so_puts(store
, "\n");
1569 memset(&sargs
, 0, sizeof(SCROLL_S
));
1570 sargs
.text
.text
= so_text(store
);
1571 sargs
.text
.src
= CharStar
;
1572 sargs
.text
.desc
= _("Details");
1573 sargs
.bar
.title
= _("CERT VALIDATION DETAILS");
1574 sargs
.help
.text
= NO_HELP
;
1575 sargs
.help
.title
= NULL
;
1576 sargs
.quell_newmail
= 1;
1577 sargs
.help
.text
= h_tls_failure_details
;
1578 sargs
.help
.title
= _("HELP FOR CERT VALIDATION DETAILS");
1582 so_give(&store
); /* free resources associated with store */
1583 ps_global
->mangled_screen
= 1;
1592 * C-client callback to handle SSL/TLS certificate validation failures
1595 pine_sslfailure(char *host
, char *reason
, long unsigned int flags
)
1599 int the_answer
= 'n', indent
, len
, cols
;
1600 char buf
[500], buf2
[500];
1602 char *hst
= host
? host
: "<unknown>";
1603 char *rsn
= reason
? reason
: "<unknown>";
1604 char *notls
= "/notls";
1606 int ok_novalidate
= 0, warned
= 0;
1609 dprint((1, "sslfailure: host=%s reason=%s\n",
1613 if(flags
& NET_SILENT
)
1616 hostlist
.name
= host
? host
: "";
1617 hostlist
.next
= NULL
;
1620 * See if we've been told about this host before.
1622 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
1623 /* we were told already */
1625 snprintf(buf
, sizeof(buf
), _("SSL/TLS failure for %s: %s"), hst
, rsn
);
1626 buf
[sizeof(buf
)-1] = '\0';
1632 cols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1635 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
1638 strncpy(buf
, _("There was an SSL/TLS failure for the server"), sizeof(buf
));
1639 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1640 so_puts(store
, folded
);
1641 fs_give((void **)&folded
);
1642 so_puts(store
, "\n");
1644 if((len
=strlen(hst
)) <= cols
){
1645 if((indent
=((cols
-len
)/2)) > 0)
1646 so_puts(store
, repeat_char(indent
, SPACE
));
1648 so_puts(store
, hst
);
1649 so_puts(store
, "\n");
1652 strncpy(buf
, hst
, sizeof(buf
));
1653 buf
[sizeof(buf
)-1] = '\0';
1654 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1655 so_puts(store
, folded
);
1656 fs_give((void **)&folded
);
1659 so_puts(store
, "\n");
1661 strncpy(buf
, _("The reason for the failure was"), sizeof(buf
));
1662 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1663 so_puts(store
, folded
);
1664 fs_give((void **)&folded
);
1665 so_puts(store
, "\n");
1667 if((len
=strlen(rsn
)) <= cols
){
1668 if((indent
=((cols
-len
)/2)) > 0)
1669 so_puts(store
, repeat_char(indent
, SPACE
));
1671 so_puts(store
, rsn
);
1672 so_puts(store
, "\n");
1675 strncpy(buf
, rsn
, sizeof(buf
));
1676 buf
[sizeof(buf
)-1] = '\0';
1677 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1678 so_puts(store
, folded
);
1679 fs_give((void **)&folded
);
1682 so_puts(store
, "\n");
1684 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
));
1685 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1686 so_puts(store
, folded
);
1687 fs_give((void **)&folded
);
1688 so_puts(store
, "\n");
1690 if((len
=strlen(notls
)) <= cols
){
1691 if((indent
=((cols
-len
)/2)) > 0)
1692 so_puts(store
, repeat_char(indent
, SPACE
));
1694 so_puts(store
, notls
);
1695 so_puts(store
, "\n");
1698 strncpy(buf
, notls
, sizeof(buf
));
1699 buf
[sizeof(buf
)-1] = '\0';
1700 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1701 so_puts(store
, folded
);
1702 fs_give((void **)&folded
);
1705 so_puts(store
, "\n");
1707 strncpy(buf
, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
1709 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1710 so_puts(store
, folded
);
1711 fs_give((void **)&folded
);
1712 so_puts(store
, "\n");
1714 if((len
=strlen(hst
)) <= cols
){
1715 if((indent
=((cols
-len
)/2)) > 0)
1716 so_puts(store
, repeat_char(indent
, SPACE
));
1718 so_puts(store
, hst
);
1719 so_puts(store
, "\n");
1722 strncpy(buf
, hst
, sizeof(buf
));
1723 buf
[sizeof(buf
)-1] = '\0';
1724 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1725 so_puts(store
, folded
);
1726 fs_give((void **)&folded
);
1729 so_puts(store
, "\n");
1731 strncpy(buf
, _("in your configuration, replace those characters with"),
1733 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1734 so_puts(store
, folded
);
1735 fs_give((void **)&folded
);
1736 so_puts(store
, "\n");
1738 snprintf(buf2
, sizeof(buf2
), "%s%s", hst
, notls
);
1739 buf2
[sizeof(buf2
)-1] = '\0';
1740 if((len
=strlen(buf2
)) <= cols
){
1741 if((indent
=((cols
-len
)/2)) > 0)
1742 so_puts(store
, repeat_char(indent
, SPACE
));
1744 so_puts(store
, buf2
);
1745 so_puts(store
, "\n");
1748 strncpy(buf
, buf2
, sizeof(buf
));
1749 buf
[sizeof(buf
)-1] = '\0';
1750 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1751 so_puts(store
, folded
);
1752 fs_give((void **)&folded
);
1755 so_puts(store
, "\n");
1757 if(ps_global
->ttyo
){
1758 strncpy(buf
, _("Type RETURN to continue."), sizeof(buf
));
1759 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
1760 so_puts(store
, folded
);
1761 fs_give((void **)&folded
);
1764 memset(&sargs
, 0, sizeof(SCROLL_S
));
1765 sargs
.text
.text
= so_text(store
);
1766 sargs
.text
.src
= CharStar
;
1767 sargs
.text
.desc
= _("help text");
1768 sargs
.bar
.title
= _("SSL/TLS FAILURE");
1769 sargs
.proc
.tool
= answer_cert_failure
;
1770 sargs
.proc
.data
.p
= (void *)&the_answer
;
1771 sargs
.keys
.menu
= &ans_certfail_keymenu
;
1772 setbitmap(sargs
.keys
.bitmap
);
1773 /* don't want to re-enter c-client */
1774 sargs
.quell_newmail
= 1;
1775 sargs
.help
.text
= h_tls_failure
;
1776 sargs
.help
.title
= _("HELP FOR TLS/SSL FAILURE");
1787 * The screen isn't initialized yet, which should mean that this
1788 * is the result of a -p argument. Display_args_err knows how to deal
1789 * with the uninitialized screen, so we mess with the data to get it
1790 * in shape for display_args_err. This is pretty hacky.
1793 so_seek(store
, 0L, 0); /* rewind */
1794 /* count the lines */
1795 while(so_readc(&c
, store
))
1799 qp
= q
= (char **)fs_get((cnt
+1) * sizeof(char *));
1800 memset(q
, 0, (cnt
+1) * sizeof(char *));
1802 so_seek(store
, 0L, 0); /* rewind */
1804 while(so_readc(&c
, store
)){
1807 *qp
++ = cpystr(buf
);
1814 display_args_err(NULL
, q
, 0);
1815 free_list_array(&q
);
1818 ps_global
->mangled_screen
= 1;
1819 ps_global
->painted_body_on_startup
= 0;
1820 ps_global
->painted_footer_on_startup
= 0;
1823 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, ok_novalidate
, 1);
1828 answer_cert_failure(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
1832 ps_global
->next_screen
= SCREEN_FUN_NULL
;
1836 *(int *)(sparms
->proc
.data
.p
) = 'y';
1840 *(int *)(sparms
->proc
.data
.p
) = 'n';
1844 alpine_panic("Unexpected command in answer_cert_failure");
1852 /*----------------------------------------------------------------------
1853 This can be used to prevent the flickering of the check_cue char
1854 caused by numerous (5000+) fetches by c-client. Right now, the only
1855 practical use found is newsgroup subsciption.
1857 check_cue_display will check if this global is set, and won't clear
1858 the check_cue_char if set.
1861 set_read_predicted(int i
)
1863 ps_global
->read_predicted
= i
==1;
1865 if(!i
&& F_ON(F_SHOW_DELAY_CUE
, ps_global
))
1866 check_cue_display(" ");
1871 /*----------------------------------------------------------------------
1872 Exported method to retrieve logged in user name associated with stream
1874 Args: host -- host to find associated login name with.
1879 pine_block_notify(int reason
, void *data
)
1882 case BLOCK_SENSITIVE
: /* sensitive code, disallow alarms */
1885 case BLOCK_NONSENSITIVE
: /* non-sensitive code, allow alarms */
1888 case BLOCK_TCPWRITE
: /* blocked on TCP write */
1889 case BLOCK_FILELOCK
: /* blocked on file locking */
1891 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
1892 check_cue_display(">");
1894 mswin_setcursor(MSWIN_CURSOR_BUSY
);
1898 case BLOCK_DNSLOOKUP
: /* blocked on DNS lookup */
1899 case BLOCK_TCPOPEN
: /* blocked on TCP open */
1900 case BLOCK_TCPREAD
: /* blocked on TCP read */
1901 case BLOCK_TCPCLOSE
: /* blocked on TCP close */
1903 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
1904 check_cue_display("<");
1906 mswin_setcursor(MSWIN_CURSOR_BUSY
);
1911 case BLOCK_NONE
: /* not blocked */
1913 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
1914 check_cue_display(" ");
1925 mm_expunged_current(long unsigned int rawno
)
1927 /* expunged something we're viewing? */
1928 if(!ps_global
->expunge_in_progress
1929 && (mn_is_cur(ps_global
->msgmap
, mn_raw2m(ps_global
->msgmap
, (long) rawno
))
1930 && (ps_global
->prev_screen
== mail_view_screen
1931 || ps_global
->prev_screen
== attachment_screen
))){
1932 ps_global
->next_screen
= mail_index_screen
;
1933 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
1934 "Message you were viewing is gone!");
1942 * Specific functions to support caching username/passwd/host
1943 * triples on disk for use from one session to the next...
1946 #define FIRSTCH 0x20
1948 #define TABSZ (LASTCH - FIRSTCH + 1)
1950 static int xlate_key
;
1954 * xlate_in() - xlate_in the given character
1962 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
1963 eti
+= (c
- FIRSTCH
);
1964 eti
-= (eti
>= 2*TABSZ
) ? 2*TABSZ
: (eti
>= TABSZ
) ? TABSZ
: 0;
1965 return((xlate_key
= eti
) + FIRSTCH
);
1973 * xlate_out() - xlate_out the given character
1981 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
1982 xch
= c
- (dti
= xlate_key
);
1983 xch
+= (xch
< FIRSTCH
-TABSZ
) ? 2*TABSZ
: (xch
< FIRSTCH
) ? TABSZ
: 0;
1984 dti
= (xch
- FIRSTCH
) + dti
;
1985 dti
-= (dti
>= 2*TABSZ
) ? 2*TABSZ
: (dti
>= TABSZ
) ? TABSZ
: 0;
1992 #endif /* PASSFILE */
1995 #ifdef LOCAL_PASSWD_CACHE
1999 line_get(char *tmp
, size_t len
, char **textp
)
2005 || (s
= strchr(*textp
, '\n')) == NULL
2006 || (s
- *textp
) > len
- 1)
2013 snprintf(tmp
, len
, "%s\n", *textp
);
2021 * Passfile lines are
2023 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
2025 * In pine4.40 and before there was no orig_hostname, and there still isn't
2026 * if it is the same as hostname.
2029 * Use Windows credentials. The TargetName of the credential is
2030 * UWash_Alpine_<hostname:port>\tuser\taltflag
2031 * and the blob consists of
2032 * passwd\torighost (if different from host)
2034 * We don't use anything fancy we just copy out all the credentials which
2035 * begin with TNAME and put them into our cache, so we don't lookup based
2036 * on the TargetName or anything like that. That was so we could re-use
2037 * the existing code and so that orighost data could be easily used.
2040 read_passfile(pinerc
, l
)
2046 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
2049 char *tmp
, *blob
, *target
= NULL
;
2050 char *host
, *user
, *sflags
, *passwd
, *orighost
;
2054 if(using_passfile
== 0)
2055 return(using_passfile
);
2058 if(init_wincred_funcs() != 1){
2060 return(using_passfile
);
2064 dprint((9, "read_passfile\n"));
2068 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
2070 for(k
= 0; k
< count
; k
++){
2072 host
= user
= sflags
= passwd
= orighost
= NULL
;
2073 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
2075 target
= lptstr_to_utf8(pcred
[k
]->TargetName
);
2076 tmp
= srchstr(target
, TNAME
);
2079 tmp
+= strlen(TNAME
);
2080 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
2081 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
2082 ; /* find end of data */
2085 tmp
[i
++] = '\0'; /* tie off data */
2093 blob
= (char *) pcred
[k
]->CredentialBlob
;
2095 for(i
= 0, j
= 3; blob
[i
] && j
< 5; j
++){
2096 for(ui
[j
] = &blob
[i
]; blob
[i
] && blob
[i
] != '\t'; i
++)
2097 ; /* find end of data */
2100 blob
[i
++] = '\0'; /* tie off data */
2107 if(passwd
&& host
&& user
){ /* valid field? */
2108 STRLIST_S hostlist
[2];
2109 int flags
= sflags
? atoi(sflags
) : 0;
2111 hostlist
[0].name
= host
;
2113 hostlist
[0].next
= &hostlist
[1];
2114 hostlist
[1].name
= orighost
;
2115 hostlist
[1].next
= NULL
;
2118 hostlist
[0].next
= NULL
;
2121 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
2125 fs_give((void **) &target
);
2128 g_CredFree((PVOID
) pcred
);
2134 # else /* old windows */
2141 char target
[MAILTMPLEN
];
2142 char *tmp
, *host
, *user
, *sflags
, *passwd
, *orighost
;
2145 SecKeychainAttributeList attrList
;
2146 SecKeychainSearchRef searchRef
= NULL
;
2147 SecKeychainAttribute attrs
[] = {
2148 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
2151 if(using_passfile
== 0)
2152 return(using_passfile
);
2154 dprint((9, "read_passfile\n"));
2157 /* search for only our items in the keychain */
2159 attrList
.attr
= attrs
;
2162 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
2163 kSecGenericPasswordItemClass
,
2166 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
2168 SecKeychainItemRef itemRef
= NULL
;
2169 SecKeychainAttributeInfo info
;
2170 SecKeychainAttributeList
*attrList
= NULL
;
2173 char *blobcopy
= NULL
; /* NULL terminated copy */
2175 UInt32 tags
[] = {kSecAccountItemAttr
,
2176 kSecServiceItemAttr
};
2177 UInt32 formats
[] = {0,0};
2179 dprint((10, "read_passfile: searchRef not NULL\n"));
2182 info
.format
= formats
;
2185 * Go through each item we found and put it
2188 while(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
)) && itemRef
){
2189 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
2190 rc
= SecKeychainItemCopyAttributesAndData(itemRef
,
2195 if(rc
== 0 && attrList
){
2196 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList
->count
));
2198 blobcopy
= (char *) fs_get((blength
+ 1) * sizeof(char));
2199 strncpy(blobcopy
, (char *) blob
, blength
);
2200 blobcopy
[blength
] = '\0';
2203 * I'm not real clear on how this works. It seems to be
2204 * necessary to combine the attributes from two passes
2205 * (attrList->count == 2) in order to get the full set
2206 * of attributes we inserted into the keychain in the
2207 * first place. So, we reset host...orighost outside of
2208 * the following for loop, not inside.
2210 host
= user
= sflags
= passwd
= orighost
= NULL
;
2211 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
2213 for(k
= 0; k
< attrList
->count
; k
++){
2215 if(attrList
->attr
[k
].length
){
2217 (char *) attrList
->attr
[k
].data
,
2218 MIN(attrList
->attr
[k
].length
,sizeof(target
)));
2219 target
[MIN(attrList
->attr
[k
].length
,sizeof(target
)-1)] = '\0';
2223 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
2224 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
2225 ; /* find end of data */
2228 tmp
[i
++] = '\0'; /* tie off data */
2240 for(i
= 0, j
= 3; blobcopy
[i
] && j
< 5; j
++){
2241 for(ui
[j
] = &blobcopy
[i
]; blobcopy
[i
] && blobcopy
[i
] != '\t'; i
++)
2242 ; /* find end of data */
2245 blobcopy
[i
++] = '\0'; /* tie off data */
2254 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
:""));
2257 if(passwd
&& host
&& user
){ /* valid field? */
2258 STRLIST_S hostlist
[2];
2259 int flags
= sflags
? atoi(sflags
) : 0;
2261 hostlist
[0].name
= host
;
2263 hostlist
[0].next
= &hostlist
[1];
2264 hostlist
[1].name
= orighost
;
2265 hostlist
[1].next
= NULL
;
2268 hostlist
[0].next
= NULL
;
2271 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
2275 fs_give((void **) & blobcopy
);
2277 SecKeychainItemFreeAttributesAndData(attrList
, (void *) blob
);
2281 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc
));
2288 CFRelease(searchRef
);
2292 dprint((10, "read_passfile: searchRef NULL\n"));
2297 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc
));
2300 return(using_passfile
);
2302 #else /* PASSFILE */
2304 char tmp
[MAILTMPLEN
], *ui
[5];
2305 int i
, j
, n
, rv
= 0;
2307 char tmp2
[MAILTMPLEN
];
2308 char *text
= NULL
, *text2
= NULL
;
2313 if(using_passfile
== 0)
2314 return(using_passfile
);
2316 dprint((9, "read_passfile\n"));
2318 /* if there's no password to read, bag it!! */
2319 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb"))){
2321 return(using_passfile
);
2325 /* the next call initializes the key/certificate pair used to
2326 * encrypt and decrypt a password file. The details of how this is
2327 * done is in the file pith/smime.c. During this setup we might call
2328 * smime_init(), but no matter what happens we must call smime_deinit()
2329 * there. The reason why this is so is because we can not assume that
2330 * the .pinerc file has been read by this time, so this code might not
2331 * know about the ps_global->smime structure or any of its components,
2332 * and it shouldn't because it only needs ps_global->pwdcert, so
2333 * do not init smime here, because the .pinerc might not have been
2334 * read and we do not really know where the keys and certificates really
2336 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
2337 * it is called for the first time and there are certificates at all,
2338 * or when it is called after the first time and the user refuses to
2339 * create a self-signed certificate. In this situation we will just
2340 * let the user live in an insecure world, but no more passwords will
2341 * be saved in the password file, and only those found there will be used.
2344 fgets(tmp2
, sizeof(tmp2
), fp
);
2346 if(strcmp(tmp2
, "-----BEGIN PKCS7-----\n")){
2347 /* there is an already existing password file, that is not encrypted
2348 * and there is no key to encrypt it yet, go again through setup_pwdcert
2349 * and encrypt it now.
2351 if(tmp2
[0]){ /* not empty, UNencrypted password file */
2352 if(ps_global
->pwdcert
== NULL
)
2353 rv
= setup_pwdcert(&ps_global
->pwdcert
);
2354 if((rv
== 0 || rv
== -5) && ps_global
->pwdcert
== NULL
)
2355 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
2356 if(ps_global
->pwdcert
== NULL
){
2357 q_status_message(SM_ORDER
, 3, 3,
2358 " Failed to create private key. Using UNencrypted Password file. ");
2363 q_status_message(SM_ORDER
, 3, 3,
2364 " Failed to unlock private key. Using UNencrypted Password file. ");
2365 save_password
= 0; /* do not save more passwords */
2368 if(ps_global
->pwdcert
!= NULL
2369 && encrypt_file((char *)tmp
, NULL
, (PERSONAL_CERT
*)ps_global
->pwdcert
))
2374 if(ps_global
->pwdcert
== NULL
)
2375 rv
= setup_pwdcert(&ps_global
->pwdcert
);
2380 * if password file is encrypted we attemtp to decrypt. We ask the
2381 * user for the password to unlock the password file. If the user
2382 * enters the password and it unlocks the file, use it and keep saving
2383 * passwords in it. If the user enters the wrong passwords and does
2384 * not unlock it, we will not see that here, but in decrypt_file, so
2385 * the only other possibility is that the user cancels. In that case
2386 * we will see i == -1. In that case, we will let the user attempt
2387 * manual login to the server they want to login, but passwords will
2388 * not be saved so that the password file will not be saved
2389 * unencrypted and rewritten again.
2392 text
= text2
= decrypt_file((char *)tmp
, &i
, (PERSONAL_CERT
*)ps_global
->pwdcert
);
2394 case -2: using_passfile
= 0;
2397 case 1 : save_password
= 1;
2401 case -1: save_password
= 0;
2409 fp
= our_fopen(tmp
, "rb"); /* reopen to read data */
2412 if(using_passfile
== 0){
2414 if(text
) fs_give((void **)&text
);
2416 return using_passfile
;
2420 for(n
= 0; encrypted
? line_get(tmp
, sizeof(tmp
), &text2
)
2421 : (fgets(tmp
, sizeof(tmp
), fp
) != NULL
); n
++){
2423 for(n
= 0; fgets(tmp
, sizeof(tmp
), fp
); n
++){
2425 /*** do any necessary DEcryption here ***/
2427 for(i
= 0; tmp
[i
]; i
++)
2428 tmp
[i
] = xlate_out(tmp
[i
]);
2430 if(i
&& tmp
[i
-1] == '\n')
2431 tmp
[i
-1] = '\0'; /* blast '\n' */
2433 dprint((10, "read_passfile: %s\n", tmp
? tmp
: "?"));
2434 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
2435 for(i
= 0, j
= 0; tmp
[i
] && j
< 5; j
++){
2436 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
2437 ; /* find end of data */
2440 tmp
[i
++] = '\0'; /* tie off data */
2443 dprint((10, "read_passfile: calling imap_set_passwd\n"));
2444 if(ui
[0] && ui
[1] && ui
[2]){ /* valid field? */
2445 STRLIST_S hostlist
[2];
2446 int flags
= ui
[3] ? atoi(ui
[3]) : 0;
2448 hostlist
[0].name
= ui
[2];
2450 hostlist
[0].next
= &hostlist
[1];
2451 hostlist
[1].name
= ui
[4];
2452 hostlist
[1].next
= NULL
;
2455 hostlist
[0].next
= NULL
;
2458 imap_set_passwd(l
, ui
[0], ui
[1], hostlist
, flags
& 0x01, 0, 0);
2463 if (text
) fs_give((void **)&text
);
2468 #endif /* PASSFILE */
2474 write_passfile(pinerc
, l
)
2480 char target
[MAILTMPLEN
];
2481 char blob
[MAILTMPLEN
];
2485 if(using_passfile
== 0)
2488 dprint((9, "write_passfile\n"));
2490 for(; l
; l
= l
->next
){
2491 snprintf(target
, sizeof(target
), "%s%s\t%s\t%d",
2493 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
2494 l
->user
? l
->user
: "",
2496 ltarget
= utf8_to_lptstr((LPSTR
) target
);
2499 snprintf(blob
, sizeof(blob
), "%s%s%s",
2500 l
->passwd
? l
->passwd
: "",
2501 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2503 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2504 ? l
->hosts
->next
->name
: "");
2505 memset((void *) &cred
, 0, sizeof(cred
));
2507 cred
.Type
= CRED_TYPE_GENERIC
;
2508 cred
.TargetName
= ltarget
;
2509 cred
.CredentialBlobSize
= strlen(blob
)+1;
2510 cred
.CredentialBlob
= (LPBYTE
) &blob
;
2511 cred
.Persist
= CRED_PERSIST_ENTERPRISE
;
2512 g_CredWriteW(&cred
, 0);
2514 fs_give((void **) <arget
);
2517 #endif /* WINCRED > 0 */
2522 char target
[MAILTMPLEN
];
2523 char blob
[MAILTMPLEN
];
2524 SecKeychainItemRef itemRef
= NULL
;
2526 if(using_passfile
== 0)
2529 dprint((9, "write_passfile\n"));
2531 for(; l
; l
= l
->next
){
2532 snprintf(target
, sizeof(target
), "%s\t%s\t%d",
2533 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
2534 l
->user
? l
->user
: "",
2537 snprintf(blob
, sizeof(blob
), "%s%s%s",
2538 l
->passwd
? l
->passwd
: "",
2539 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2541 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2542 ? l
->hosts
->next
->name
: "");
2544 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target
), target
, strlen(TNAME
), TNAME
, strlen(blob
), blob
));
2546 rc
= SecKeychainAddGenericPassword(NULL
,
2547 strlen(target
), target
,
2548 strlen(TNAME
), TNAME
,
2552 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
2555 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc
));
2558 if(rc
== errSecDuplicateItem
){
2559 /* fix existing entry */
2560 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
2562 if(!(rc
=SecKeychainFindGenericPassword(NULL
,
2563 strlen(target
), target
,
2564 strlen(TNAME
), TNAME
,
2566 &itemRef
)) && itemRef
){
2568 rc
= SecKeychainItemModifyAttributesAndData(itemRef
, NULL
, strlen(blob
), blob
);
2570 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc
));
2574 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc
));
2579 #else /* PASSFILE */
2581 char tmp
[MAILTMPLEN
];
2585 char *text
= NULL
, tmp2
[MAILTMPLEN
];
2589 if(using_passfile
== 0)
2592 dprint((9, "write_passfile\n"));
2594 /* if there's no passfile to read, bag it!! */
2595 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "wb"))){
2601 strncpy(tmp2
, tmp
, sizeof(tmp2
));
2602 tmp2
[sizeof(tmp2
)-1] = '\0';
2605 for(n
= 0; l
; l
= l
->next
, n
++){
2606 /*** do any necessary ENcryption here ***/
2607 snprintf(tmp
, sizeof(tmp
), "%s\t%s\t%s\t%d%s%s\n", l
->passwd
, l
->user
,
2608 l
->hosts
->name
, l
->altflag
,
2609 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "",
2610 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
2612 dprint((10, "write_passfile: %s", tmp
? tmp
: "?"));
2614 for(i
= 0; tmp
[i
]; i
++)
2615 tmp
[i
] = xlate_in(tmp
[i
]);
2619 len
= strlen(tmp
) + 1;
2620 text
= fs_get(len
*sizeof(char));
2623 if(strlen(text
) + strlen(tmp
) > len
){
2624 len
= strlen(text
) + strlen(tmp
) + 1;
2625 fs_resize((void **)&text
, len
*sizeof(char));
2627 strncat(text
, tmp
, strlen(tmp
));
2636 if(ps_global
->pwdcert
== NULL
){
2637 q_status_message(SM_ORDER
, 3, 3, "Attempting to encrypt password file");
2638 i
= setup_pwdcert(&ps_global
->pwdcert
);
2639 if(i
== 0 && ps_global
->pwdcert
== NULL
)
2640 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
2642 if(ps_global
->pwdcert
== NULL
){ /* we tried but failed */
2644 q_status_message1(SM_ORDER
, 3, 3, "Error: no directory %s to save certificates", ps_global
->pwdcertdir
);
2646 q_status_message(SM_ORDER
, 3, 3, "Refusing to write non-encrypted password file");
2648 else if(encrypt_file((char *)tmp2
, text
, ps_global
->pwdcert
) == 0)
2649 q_status_message(SM_ORDER
, 3, 3, "Failed to encrypt password file");
2650 fs_give((void **)&text
); /* do not save this text */
2653 #endif /* PASSFILE */
2656 #endif /* LOCAL_PASSWD_CACHE */
2661 erase_windows_credentials(void)
2663 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
2668 if(init_wincred_funcs() != 1)
2672 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
2674 for(k
= 0; k
< count
; k
++)
2675 g_CredDeleteW(pcred
[k
]->TargetName
, CRED_TYPE_GENERIC
, 0);
2677 g_CredFree((PVOID
) pcred
);
2683 ask_erase_credentials(void)
2685 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y'){
2686 erase_windows_credentials();
2687 q_status_message(SM_ORDER
, 3, 3, "All preserved passwords have been erased");
2690 q_status_message(SM_ORDER
, 3, 3, "Previously preserved passwords will not be erased");
2693 #endif /* WINCRED */
2696 #ifdef LOCAL_PASSWD_CACHE
2699 * get_passfile_passwd - return the password contained in the special passord
2700 * cache. The file is assumed to be in the same directory
2701 * as the pinerc with the name defined above.
2704 get_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
)
2705 char *pinerc
, *passwd
, *user
;
2706 STRLIST_S
*hostlist
;
2709 dprint((10, "get_passfile_passwd\n"));
2710 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
2711 ? imap_get_passwd(passfile_cache
, passwd
,
2712 user
, hostlist
, altflag
)
2719 return(using_passfile
== 1);
2723 * Just trying to guess the username the user might want to use on this
2724 * host, the user will confirm.
2727 get_passfile_user(pinerc
, hostlist
)
2729 STRLIST_S
*hostlist
;
2731 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
2732 ? imap_get_user(passfile_cache
, hostlist
)
2738 preserve_prompt(char *pinerc
)
2743 * This prompt was going to be able to be turned on and off via a registry
2744 * setting controlled from the config menu. We decided to always use the
2745 * dialog for login, and there the prompt is unobtrusive enough to always
2746 * be in there. As a result, windows should never reach this, but now
2747 * OS X somewhat uses the behavior just described.
2749 if(mswin_store_pass_prompt()
2750 && (want_to(_("Preserve password for next login"),
2751 'y', 'x', NO_HELP
, WT_NORM
)
2763 if((rc
= macos_store_pass_prompt()) != 0){
2764 if(want_to(_("Preserve password for next login"),
2765 'y', 'x', NO_HELP
, WT_NORM
)
2768 macos_set_store_pass_prompt(1);
2769 q_status_message(SM_ORDER
, 4, 4,
2770 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
2775 macos_set_store_pass_prompt(0);
2776 q_status_message(SM_ORDER
, 4, 4,
2777 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
2782 #else /* PASSFILE */
2783 char tmp
[MAILTMPLEN
];
2786 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || our_stat(tmp
, &sbuf
) < 0)
2789 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
2790 return(want_to(_("Preserve password on DISK for next login"),
2791 'y', 'x', NO_HELP
, WT_NORM
)
2794 #endif /* PASSFILE */
2797 #endif /* LOCAL_PASSWD_CACHE */
2800 #ifdef APPLEKEYCHAIN
2804 * 1 if store pass prompt is set in the "registry" to on
2806 * -1 if not set to anything
2809 macos_store_pass_prompt(void)
2816 if(storepassprompt
== -1){
2817 if(!(rc
=SecKeychainFindGenericPassword(NULL
, 0, NULL
,
2818 strlen(TNAMEPROMPT
),
2820 (void **) &data
, NULL
))){
2821 val
= (len
== 1 && data
&& data
[0] == '1');
2825 if(storepassprompt
== -1 && !rc
){
2827 storepassprompt
= 1;
2829 storepassprompt
= 0;
2832 return(storepassprompt
);
2837 macos_set_store_pass_prompt(int val
)
2839 storepassprompt
= val
? 1 : 0;
2841 SecKeychainAddGenericPassword(NULL
, 0, NULL
, strlen(TNAMEPROMPT
),
2842 TNAMEPROMPT
, 1, val
? "1" : "0", NULL
);
2847 macos_erase_keychain(void)
2849 SecKeychainAttributeList attrList
;
2850 SecKeychainSearchRef searchRef
= NULL
;
2851 SecKeychainAttribute attrs1
[] = {
2852 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
2854 SecKeychainAttribute attrs2
[] = {
2855 { kSecAccountItemAttr
, strlen(TNAMEPROMPT
), TNAMEPROMPT
}
2858 dprint((9, "macos_erase_keychain\n"));
2861 * Seems like we ought to be able to combine attrs1 and attrs2
2862 * into a single array, but I couldn't get it to work.
2865 /* search for only our items in the keychain */
2867 attrList
.attr
= attrs1
;
2869 if(!SecKeychainSearchCreateFromAttributes(NULL
,
2870 kSecGenericPasswordItemClass
,
2874 SecKeychainItemRef itemRef
= NULL
;
2877 * Go through each item we found and put it
2880 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
2881 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
2882 SecKeychainItemDelete(itemRef
);
2886 CFRelease(searchRef
);
2891 attrList
.attr
= attrs2
;
2893 if(!SecKeychainSearchCreateFromAttributes(NULL
,
2894 kSecGenericPasswordItemClass
,
2898 SecKeychainItemRef itemRef
= NULL
;
2901 * Go through each item we found and put it
2904 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
2905 SecKeychainItemDelete(itemRef
);
2909 CFRelease(searchRef
);
2914 #endif /* APPLEKEYCHAIN */
2916 #ifdef LOCAL_PASSWD_CACHE
2919 * set_passfile_passwd - set the password file entry associated with
2920 * cache. The file is assumed to be in the same directory
2921 * as the pinerc with the name defined above.
2922 * already_prompted: 0 not prompted
2923 * 1 prompted, answered yes
2924 * 2 prompted, answered no
2927 set_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
)
2928 char *pinerc
, *passwd
, *user
;
2929 STRLIST_S
*hostlist
;
2930 int altflag
, already_prompted
;
2932 dprint((10, "set_passfile_passwd\n"));
2933 if(((already_prompted
== 0 && preserve_prompt(pinerc
))
2934 || already_prompted
== 1)
2935 && !ps_global
->nowrite_password_cache
2936 && (passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))){
2937 imap_set_passwd(&passfile_cache
, passwd
, user
, hostlist
, altflag
, 0, 0);
2938 write_passfile(pinerc
, passfile_cache
);
2944 * Passfile lines are
2946 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
2948 * In pine4.40 and before there was no orig_hostname.
2949 * This routine attempts to repair that.
2952 update_passfile_hostlist(pinerc
, user
, hostlist
, altflag
)
2955 STRLIST_S
*hostlist
;
2960 #else /* !WINCRED */
2963 for(l
= passfile_cache
; l
; l
= l
->next
)
2964 if(imap_same_host(l
->hosts
, hostlist
)
2966 && !strcmp(user
, l
->user
)
2967 && l
->altflag
== altflag
){
2971 if(l
&& l
->hosts
&& hostlist
&& !l
->hosts
->next
&& hostlist
->next
2972 && hostlist
->next
->name
2973 && !ps_global
->nowrite_password_cache
){
2974 l
->hosts
->next
= new_strlist(hostlist
->next
->name
);
2975 write_passfile(pinerc
, passfile_cache
);
2977 #endif /* !WINCRED */
2980 #endif /* LOCAL_PASSWD_CACHE */
2985 * Load and init the WinCred structure.
2986 * This gives us a way to skip the WinCred code
2987 * if the dll doesn't exist.
2990 init_wincred_funcs(void)
2996 /* Assume the worst. */
2999 hmod
= LoadLibrary(TEXT("advapi32.dll"));
3002 FARPROC fpCredWriteW
;
3003 FARPROC fpCredEnumerateW
;
3004 FARPROC fpCredDeleteW
;
3007 fpCredWriteW
= GetProcAddress(hmod
, "CredWriteW");
3008 fpCredEnumerateW
= GetProcAddress(hmod
, "CredEnumerateW");
3009 fpCredDeleteW
= GetProcAddress(hmod
, "CredDeleteW");
3010 fpCredFree
= GetProcAddress(hmod
, "CredFree");
3012 if(fpCredWriteW
&& fpCredEnumerateW
&& fpCredDeleteW
&& fpCredFree
)
3014 g_CredWriteW
= (CREDWRITEW
*)fpCredWriteW
;
3015 g_CredEnumerateW
= (CREDENUMERATEW
*)fpCredEnumerateW
;
3016 g_CredDeleteW
= (CREDDELETEW
*)fpCredDeleteW
;
3017 g_CredFree
= (CREDFREE
*)fpCredFree
;
3023 mswin_set_erasecreds_callback(ask_erase_credentials
);
3026 return g_CredInited
;
3029 #endif /* WINCRED */