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];
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
2337 if(ps_global
->pwdcert
== NULL
)
2338 setup_pwdcert(&ps_global
->pwdcert
);
2340 fgets(tmp2
, sizeof(tmp2
), fp
);
2342 if(strcmp(tmp2
, "-----BEGIN PKCS7-----\n")){
2343 if(encrypt_file((char *)tmp
, NULL
, (PERSONAL_CERT
*)ps_global
->pwdcert
))
2350 * if password file is encrypted we attemtp to decrypt. We ask the
2351 * user for the password to unlock the password file. If the user
2352 * enters the password and it unlocks the file, use it and keep saving
2353 * passwords in it. If the user enters the wrong passwords and does
2354 * not unlock it, we will not see that here, but in decrypt_file, so
2355 * the only other possibility is that the user cancels. In that case
2356 * we will see i == -1. In that case, we will let the user attempt
2357 * manual login to the server they want to login, but passwords will
2358 * not be saved so that the password file will not be saved
2359 * unencrypted and rewritten again.
2362 text
= text2
= decrypt_file((char *)tmp
, &i
, (PERSONAL_CERT
*)ps_global
->pwdcert
);
2364 case -2: using_passfile
= 0;
2367 case 1 : save_password
= 1;
2371 case -1: save_password
= 0;
2379 fp
= our_fopen(tmp
, "rb"); /* reopen to read data */
2382 if(using_passfile
== 0){
2384 if(text
) fs_give((void **)&text
);
2386 return using_passfile
;
2390 for(n
= 0; encrypted
? line_get(tmp
, sizeof(tmp
), &text2
)
2391 : (fgets(tmp
, sizeof(tmp
), fp
) != NULL
); n
++){
2393 for(n
= 0; fgets(tmp
, sizeof(tmp
), fp
); n
++){
2395 /*** do any necessary DEcryption here ***/
2397 for(i
= 0; tmp
[i
]; i
++)
2398 tmp
[i
] = xlate_out(tmp
[i
]);
2400 if(i
&& tmp
[i
-1] == '\n')
2401 tmp
[i
-1] = '\0'; /* blast '\n' */
2403 dprint((10, "read_passfile: %s\n", tmp
? tmp
: "?"));
2404 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
2405 for(i
= 0, j
= 0; tmp
[i
] && j
< 5; j
++){
2406 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
2407 ; /* find end of data */
2410 tmp
[i
++] = '\0'; /* tie off data */
2413 dprint((10, "read_passfile: calling imap_set_passwd\n"));
2414 if(ui
[0] && ui
[1] && ui
[2]){ /* valid field? */
2415 STRLIST_S hostlist
[2];
2416 int flags
= ui
[3] ? atoi(ui
[3]) : 0;
2418 hostlist
[0].name
= ui
[2];
2420 hostlist
[0].next
= &hostlist
[1];
2421 hostlist
[1].name
= ui
[4];
2422 hostlist
[1].next
= NULL
;
2425 hostlist
[0].next
= NULL
;
2428 imap_set_passwd(l
, ui
[0], ui
[1], hostlist
, flags
& 0x01, 0, 0);
2433 if (text
) fs_give((void **)&text
);
2438 #endif /* PASSFILE */
2444 write_passfile(pinerc
, l
)
2450 char target
[MAILTMPLEN
];
2451 char blob
[MAILTMPLEN
];
2455 if(using_passfile
== 0)
2458 dprint((9, "write_passfile\n"));
2460 for(; l
; l
= l
->next
){
2461 snprintf(target
, sizeof(target
), "%s%s\t%s\t%d",
2463 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
2464 l
->user
? l
->user
: "",
2466 ltarget
= utf8_to_lptstr((LPSTR
) target
);
2469 snprintf(blob
, sizeof(blob
), "%s%s%s",
2470 l
->passwd
? l
->passwd
: "",
2471 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2473 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2474 ? l
->hosts
->next
->name
: "");
2475 memset((void *) &cred
, 0, sizeof(cred
));
2477 cred
.Type
= CRED_TYPE_GENERIC
;
2478 cred
.TargetName
= ltarget
;
2479 cred
.CredentialBlobSize
= strlen(blob
)+1;
2480 cred
.CredentialBlob
= (LPBYTE
) &blob
;
2481 cred
.Persist
= CRED_PERSIST_ENTERPRISE
;
2482 g_CredWriteW(&cred
, 0);
2484 fs_give((void **) <arget
);
2487 #endif /* WINCRED > 0 */
2492 char target
[MAILTMPLEN
];
2493 char blob
[MAILTMPLEN
];
2494 SecKeychainItemRef itemRef
= NULL
;
2496 if(using_passfile
== 0)
2499 dprint((9, "write_passfile\n"));
2501 for(; l
; l
= l
->next
){
2502 snprintf(target
, sizeof(target
), "%s\t%s\t%d",
2503 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
2504 l
->user
? l
->user
: "",
2507 snprintf(blob
, sizeof(blob
), "%s%s%s",
2508 l
->passwd
? l
->passwd
: "",
2509 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2511 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
2512 ? l
->hosts
->next
->name
: "");
2514 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target
), target
, strlen(TNAME
), TNAME
, strlen(blob
), blob
));
2516 rc
= SecKeychainAddGenericPassword(NULL
,
2517 strlen(target
), target
,
2518 strlen(TNAME
), TNAME
,
2522 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
2525 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc
));
2528 if(rc
== errSecDuplicateItem
){
2529 /* fix existing entry */
2530 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
2532 if(!(rc
=SecKeychainFindGenericPassword(NULL
,
2533 strlen(target
), target
,
2534 strlen(TNAME
), TNAME
,
2536 &itemRef
)) && itemRef
){
2538 rc
= SecKeychainItemModifyAttributesAndData(itemRef
, NULL
, strlen(blob
), blob
);
2540 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc
));
2544 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc
));
2549 #else /* PASSFILE */
2551 char tmp
[MAILTMPLEN
];
2555 char *text
= NULL
, tmp2
[MAILTMPLEN
];
2559 if(using_passfile
== 0)
2562 dprint((9, "write_passfile\n"));
2564 /* if there's no passfile to read, bag it!! */
2565 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "wb"))){
2571 strncpy(tmp2
, tmp
, sizeof(tmp2
)-1);
2572 tmp2
[sizeof(tmp2
)-1] = '\0';
2575 for(n
= 0; l
; l
= l
->next
, n
++){
2576 /*** do any necessary ENcryption here ***/
2577 snprintf(tmp
, sizeof(tmp
), "%s\t%s\t%s\t%d%s%s\n", l
->passwd
, l
->user
,
2578 l
->hosts
->name
, l
->altflag
,
2579 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "",
2580 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
2582 dprint((10, "write_passfile: %s", tmp
? tmp
: "?"));
2584 for(i
= 0; tmp
[i
]; i
++)
2585 tmp
[i
] = xlate_in(tmp
[i
]);
2589 len
= strlen(tmp
) + 1;
2590 text
= fs_get(len
*sizeof(char));
2593 if(strlen(text
) + strlen(tmp
) > len
){
2594 len
= strlen(text
) + strlen(tmp
) + 1;
2595 fs_resize((void **)&text
, len
*sizeof(char));
2597 strncat(text
, tmp
, strlen(tmp
));
2606 if(encrypt_file((char *)tmp2
, text
, ps_global
->pwdcert
) == 0){
2607 if((fp
= our_fopen(tmp2
, "wb")) != NULL
){
2612 fs_give((void **)&text
);
2615 #endif /* PASSFILE */
2618 #endif /* LOCAL_PASSWD_CACHE */
2623 erase_windows_credentials(void)
2625 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
2630 if(init_wincred_funcs() != 1)
2634 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
2636 for(k
= 0; k
< count
; k
++)
2637 g_CredDeleteW(pcred
[k
]->TargetName
, CRED_TYPE_GENERIC
, 0);
2639 g_CredFree((PVOID
) pcred
);
2645 ask_erase_credentials(void)
2647 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y'){
2648 erase_windows_credentials();
2649 q_status_message(SM_ORDER
, 3, 3, "All preserved passwords have been erased");
2652 q_status_message(SM_ORDER
, 3, 3, "Previously preserved passwords will not be erased");
2655 #endif /* WINCRED */
2658 #ifdef LOCAL_PASSWD_CACHE
2661 * get_passfile_passwd - return the password contained in the special passord
2662 * cache. The file is assumed to be in the same directory
2663 * as the pinerc with the name defined above.
2666 get_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
)
2667 char *pinerc
, *passwd
, *user
;
2668 STRLIST_S
*hostlist
;
2671 dprint((10, "get_passfile_passwd\n"));
2672 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
2673 ? imap_get_passwd(passfile_cache
, passwd
,
2674 user
, hostlist
, altflag
)
2681 return(using_passfile
== 1);
2685 * Just trying to guess the username the user might want to use on this
2686 * host, the user will confirm.
2689 get_passfile_user(pinerc
, hostlist
)
2691 STRLIST_S
*hostlist
;
2693 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
2694 ? imap_get_user(passfile_cache
, hostlist
)
2700 preserve_prompt(char *pinerc
)
2705 * This prompt was going to be able to be turned on and off via a registry
2706 * setting controlled from the config menu. We decided to always use the
2707 * dialog for login, and there the prompt is unobtrusive enough to always
2708 * be in there. As a result, windows should never reach this, but now
2709 * OS X somewhat uses the behavior just described.
2711 if(mswin_store_pass_prompt()
2712 && (want_to(_("Preserve password for next login"),
2713 'y', 'x', NO_HELP
, WT_NORM
)
2725 if((rc
= macos_store_pass_prompt()) != 0){
2726 if(want_to(_("Preserve password for next login"),
2727 'y', 'x', NO_HELP
, WT_NORM
)
2730 macos_set_store_pass_prompt(1);
2731 q_status_message(SM_ORDER
, 4, 4,
2732 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
2737 macos_set_store_pass_prompt(0);
2738 q_status_message(SM_ORDER
, 4, 4,
2739 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
2744 #else /* PASSFILE */
2745 char tmp
[MAILTMPLEN
];
2748 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || our_stat(tmp
, &sbuf
) < 0)
2751 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
2752 return(want_to(_("Preserve password on DISK for next login"),
2753 'y', 'x', NO_HELP
, WT_NORM
)
2756 #endif /* PASSFILE */
2759 #endif /* LOCAL_PASSWD_CACHE */
2762 #ifdef APPLEKEYCHAIN
2766 * 1 if store pass prompt is set in the "registry" to on
2768 * -1 if not set to anything
2771 macos_store_pass_prompt(void)
2778 if(storepassprompt
== -1){
2779 if(!(rc
=SecKeychainFindGenericPassword(NULL
, 0, NULL
,
2780 strlen(TNAMEPROMPT
),
2782 (void **) &data
, NULL
))){
2783 val
= (len
== 1 && data
&& data
[0] == '1');
2787 if(storepassprompt
== -1 && !rc
){
2789 storepassprompt
= 1;
2791 storepassprompt
= 0;
2794 return(storepassprompt
);
2799 macos_set_store_pass_prompt(int val
)
2801 storepassprompt
= val
? 1 : 0;
2803 SecKeychainAddGenericPassword(NULL
, 0, NULL
, strlen(TNAMEPROMPT
),
2804 TNAMEPROMPT
, 1, val
? "1" : "0", NULL
);
2809 macos_erase_keychain(void)
2811 SecKeychainAttributeList attrList
;
2812 SecKeychainSearchRef searchRef
= NULL
;
2813 SecKeychainAttribute attrs1
[] = {
2814 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
2816 SecKeychainAttribute attrs2
[] = {
2817 { kSecAccountItemAttr
, strlen(TNAMEPROMPT
), TNAMEPROMPT
}
2820 dprint((9, "macos_erase_keychain\n"));
2823 * Seems like we ought to be able to combine attrs1 and attrs2
2824 * into a single array, but I couldn't get it to work.
2827 /* search for only our items in the keychain */
2829 attrList
.attr
= attrs1
;
2831 if(!SecKeychainSearchCreateFromAttributes(NULL
,
2832 kSecGenericPasswordItemClass
,
2836 SecKeychainItemRef itemRef
= NULL
;
2839 * Go through each item we found and put it
2842 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
2843 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
2844 SecKeychainItemDelete(itemRef
);
2848 CFRelease(searchRef
);
2853 attrList
.attr
= attrs2
;
2855 if(!SecKeychainSearchCreateFromAttributes(NULL
,
2856 kSecGenericPasswordItemClass
,
2860 SecKeychainItemRef itemRef
= NULL
;
2863 * Go through each item we found and put it
2866 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
2867 SecKeychainItemDelete(itemRef
);
2871 CFRelease(searchRef
);
2876 #endif /* APPLEKEYCHAIN */
2878 #ifdef LOCAL_PASSWD_CACHE
2881 * set_passfile_passwd - set the password file entry associated with
2882 * cache. The file is assumed to be in the same directory
2883 * as the pinerc with the name defined above.
2884 * already_prompted: 0 not prompted
2885 * 1 prompted, answered yes
2886 * 2 prompted, answered no
2889 set_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
)
2890 char *pinerc
, *passwd
, *user
;
2891 STRLIST_S
*hostlist
;
2892 int altflag
, already_prompted
;
2894 dprint((10, "set_passfile_passwd\n"));
2895 if(((already_prompted
== 0 && preserve_prompt(pinerc
))
2896 || already_prompted
== 1)
2897 && !ps_global
->nowrite_password_cache
2898 && (passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))){
2899 imap_set_passwd(&passfile_cache
, passwd
, user
, hostlist
, altflag
, 0, 0);
2900 write_passfile(pinerc
, passfile_cache
);
2906 * Passfile lines are
2908 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
2910 * In pine4.40 and before there was no orig_hostname.
2911 * This routine attempts to repair that.
2914 update_passfile_hostlist(pinerc
, user
, hostlist
, altflag
)
2917 STRLIST_S
*hostlist
;
2922 #else /* !WINCRED */
2925 for(l
= passfile_cache
; l
; l
= l
->next
)
2926 if(imap_same_host(l
->hosts
, hostlist
)
2928 && !strcmp(user
, l
->user
)
2929 && l
->altflag
== altflag
){
2933 if(l
&& l
->hosts
&& hostlist
&& !l
->hosts
->next
&& hostlist
->next
2934 && hostlist
->next
->name
2935 && !ps_global
->nowrite_password_cache
){
2936 l
->hosts
->next
= new_strlist(hostlist
->next
->name
);
2937 write_passfile(pinerc
, passfile_cache
);
2939 #endif /* !WINCRED */
2942 #endif /* LOCAL_PASSWD_CACHE */
2947 * Load and init the WinCred structure.
2948 * This gives us a way to skip the WinCred code
2949 * if the dll doesn't exist.
2952 init_wincred_funcs(void)
2958 /* Assume the worst. */
2961 hmod
= LoadLibrary(TEXT("advapi32.dll"));
2964 FARPROC fpCredWriteW
;
2965 FARPROC fpCredEnumerateW
;
2966 FARPROC fpCredDeleteW
;
2969 fpCredWriteW
= GetProcAddress(hmod
, "CredWriteW");
2970 fpCredEnumerateW
= GetProcAddress(hmod
, "CredEnumerateW");
2971 fpCredDeleteW
= GetProcAddress(hmod
, "CredDeleteW");
2972 fpCredFree
= GetProcAddress(hmod
, "CredFree");
2974 if(fpCredWriteW
&& fpCredEnumerateW
&& fpCredDeleteW
&& fpCredFree
)
2976 g_CredWriteW
= (CREDWRITEW
*)fpCredWriteW
;
2977 g_CredEnumerateW
= (CREDENUMERATEW
*)fpCredEnumerateW
;
2978 g_CredDeleteW
= (CREDDELETEW
*)fpCredDeleteW
;
2979 g_CredFree
= (CREDFREE
*)fpCredFree
;
2985 mswin_set_erasecreds_callback(ask_erase_credentials
);
2988 return g_CredInited
;
2991 #endif /* WINCRED */