2 * ========================================================================
3 * Copyright 2013-2021 Eduardo Chappa
4 * Copyright 2006-2009 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 /*======================================================================
17 The call back routines for the c-client/imap
18 - handles error messages and other notification
19 - handles prelimirary notification of new mail and expunged mail
20 - prompting for imap server login and password
39 #include "xoauth2conf.h"
40 #include "confscroll.h"
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_*"
58 #define PWDBUFFERSIZE (250)
59 #define MAXPWDBUFFERSIZE (2*PWDBUFFERSIZE) /* This number must be less than 512 */
62 * WinCred Function prototypes
64 typedef BOOL (WINAPI CREDWRITEW
) ( __in PCREDENTIALW Credential
, __in DWORD Flags
);
65 typedef BOOL (WINAPI CREDENUMERATEW
) ( __in LPCWSTR Filter
, __reserved DWORD Flags
,
66 __out DWORD
*Count
, __deref_out_ecount(*Count
) PCREDENTIALW
**Credential
);
67 typedef BOOL (WINAPI CREDDELETEW
) ( __in LPCWSTR TargetName
, __in DWORD Type
,
68 __reserved DWORD Flags
);
69 typedef VOID (WINAPI CREDFREE
) ( __in PVOID Buffer
);
74 int g_CredInited
= 0; /* 1 for loaded successfully,
75 * -1 for not available.
76 * 0 for not initialized yet.
78 CREDWRITEW
*g_CredWriteW
;
79 CREDENUMERATEW
*g_CredEnumerateW
;
80 CREDDELETEW
*g_CredDeleteW
;
86 #include <Security/SecKeychain.h>
87 #include <Security/SecKeychainItem.h>
88 #include <Security/SecKeychainSearch.h>
89 #define TNAME "UWash_Alpine"
90 #define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
92 int macos_store_pass_prompt(void);
93 void macos_set_store_pass_prompt(int);
95 static int storepassprompt
= -1;
96 #endif /* APPLEKEYCHAIN */
100 * Internal prototypes
102 void mm_login_alt_cue(NETMBX
*);
103 long pine_tcptimeout_noscreen(long, long, char *);
104 int answer_cert_failure(int, MSGNO_S
*, SCROLL_S
*);
105 int oauth2_auth_answer(int, MSGNO_S
*, SCROLL_S
*);
106 OAUTH2_S
*oauth2_select_flow(char *);
107 int xoauth2_flow_tool(struct pine
*, int, CONF_S
**, unsigned int);
108 void cache_method_message(STORE_S
*);
109 void cache_method_message_no_screen(void);
111 #ifdef LOCAL_PASSWD_CACHE
112 int cache_method_was_setup(char *);
113 int read_passfile(char *, MMLOGIN_S
**);
114 void write_passfile(char *, MMLOGIN_S
*);
115 int preserve_prompt(char *);
116 int preserve_prompt_auth(char *, char *authtype
);
117 void update_passfile_hostlist(char *, char *, STRLIST_S
*, int);
118 void update_passfile_hostlist_auth(char *, char *, STRLIST_S
*, int, char *);
119 void free_passfile_cache_work(MMLOGIN_S
**);
121 static MMLOGIN_S
*passfile_cache
= NULL
;
122 static int using_passfile
= -1;
123 int save_password
= 1;
124 #endif /* LOCAL_PASSWD_CACHE */
128 char xlate_out(char);
129 int line_get(char *, size_t, char **);
130 #endif /* PASSFILE */
133 void ask_erase_credentials(void);
134 int init_wincred_funcs(void);
138 static char *details_cert
, *details_host
, *details_reason
;
140 extern XOAUTH2_INFO_S xoauth_default
[];
141 extern OAUTH2_S alpine_oauth2_list
[];
144 cache_method_message(STORE_S
*in_store
)
146 #ifdef LOCAL_PASSWD_CACHE
147 if(cache_method_was_setup(ps_global
->pinerc
)){
148 so_puts(in_store
, _("</P><P> Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. If you do "));
149 so_puts(in_store
, _("not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection to the server will still be "));
150 so_puts(in_store
, _("alive at the end of this process, and your connection will proceed from there."));
153 q_status_message(SM_ORDER
| SM_DING
, 3, 3, _("Create a password file in order to save the login information"));
154 so_puts(in_store
, _("</P><P> Although your version if Alpine was compiled with password file support, this has not been set up yet. "));
155 so_puts(in_store
, _("as a result, the next time you open this folder, you will go through this process once again.\n\n"));
158 so_puts(in_store
, _("</P><P> Your version of Alpine was not built with password file support, as a result you will not be able to save your "));
159 so_puts(in_store
, _("access token, which means that you will have to repeat this process the next time you login to this server. "));
160 so_puts(in_store
, _("You can fix this by either recompiling alpine with password file support or by downloading a version of Alpine to your "));
161 so_puts(in_store
, _("computer that was compiled with password file support."));
162 #endif /* LOCAL_PASSWD_CACHE */
166 cache_method_message_no_screen (void)
168 #ifdef LOCAL_PASSWD_CACHE
169 if(cache_method_was_setup(ps_global
->pinerc
)){
170 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
171 _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
172 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
174 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
175 "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
176 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
178 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
179 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
180 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
183 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
184 _(" Although your version if Alpine was compiled with password file support, this has not been set up yet. "));
185 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
186 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
187 _("as a result, the next time you open this folder, you will go through this process once again.\n\n"));
188 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
191 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
192 _("Your version of Alpine was not built with password file support, as a result you will not be able to save your "));
193 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
194 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
195 _("access token, which means that you will have to repeat this process the next time you login to this server. "));
196 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
197 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
198 _("You can fix this by either recompiling alpine with password file support or by downloading a version of Alpine to your "));
199 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
200 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
201 _("computer that was compiled with password file support.\n\n"));
202 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
203 #endif /* LOCAL_PASSWD_CACHE */
207 xoauth2_flow_tool(struct pine
*ps
, int cmd
, CONF_S
**cl
, unsigned int flags
)
213 *((*cl
)->d
.xf
.selected
) = (*cl
)->d
.xf
.pat
;
214 rv
= simple_exit_cmd(flags
);
217 rv
= simple_exit_cmd(flags
);
225 ps
->mangled_body
= 1;
231 oauth2_select_flow(char *host
)
233 OAUTH2_S
*oa2list
, *oa2
;
238 CONF_S
*ctmp
= NULL
, *first_line
= NULL
;
239 OAUTH2_S
*x_sel
= NULL
;
243 dprint((9, "xoauth2 select flow"));
244 ps_global
->next_screen
= SCREEN_FUN_NULL
;
246 memset(&screen
, 0, sizeof(screen
));
248 for(i
= 0; i
< sizeof(tmp
) && i
< ps_global
->ttyo
->screen_cols
; i
++)
253 ctmp
->flags
|= CF_NOSELECT
;
254 ctmp
->value
= cpystr(tmp
);
257 ctmp
->flags
|= CF_NOSELECT
;
258 ctmp
->value
= cpystr(_("Please select below the authorization flow you would like to follow:"));
261 ctmp
->flags
|= CF_NOSELECT
;
262 ctmp
->value
= cpystr(tmp
);
264 for(oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
265 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
266 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
]){
270 method
= oa2list
->server_mthd
[0].name
? "Authorize"
271 : (oa2list
->server_mthd
[1].name
? "Device" : "Unknown");
272 sprintf(tmp
, "%s (%s)", oa2list
->name
, method
);
273 ctmp
->value
= cpystr(tmp
);
274 ctmp
->d
.xf
.selected
= &x_sel
;
275 ctmp
->d
.xf
.pat
= oa2list
;
276 ctmp
->keymenu
= &xoauth2_id_select_km
;
277 ctmp
->help
= NO_HELP
;
278 ctmp
->help_title
= NULL
;
279 ctmp
->tool
= xoauth2_flow_tool
;
280 ctmp
->flags
= CF_STARTITEM
;
284 (void)conf_scroll_screen(ps_global
, &screen
, first_line
, _("SELECT AUTHORIZATION FLOW"),
285 _("xoauth2"), 0, NULL
);
294 for(oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++)
295 n
+= strlen((char *) oa2list
->name
) + 5; /* number, parenthesis, space */
296 n
+= 1024; /* large enough to display lines of 80 characters in UTF-8 */
297 s
= fs_get(n
*sizeof(char));
298 strcpy(s
, _("Please select below the authorization flow you would like to follow:"));
299 sprintf(s
+ strlen(s
), _("Please select the client-id to use from the following list.\n\n"));
300 for(j
= 1, oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
301 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
302 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
])
303 sprintf(s
+ strlen(s
), " %d) %.70s\n", j
++, oa2list
->name
);
305 display_init_err(s
, 0);
307 strncpy(prompt
, _("Enter your selection number: "), sizeof(prompt
));
308 prompt
[sizeof(prompt
)-1] = '\0';
310 rv
= optionally_enter(reply
, 0, 0, sizeof(reply
), prompt
, NULL
, NO_HELP
, 0);
312 rv
= (sel
>= 0 && sel
< i
) ? 0 : -1;
315 for(j
= 1, oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
316 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
317 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
]){
327 typedef struct auth_code_s
{
333 oauth2device_decode_reply(void *datap
, void *replyp
)
335 OAUTH2_DEVICEPROC_S
*av
= (OAUTH2_DEVICEPROC_S
*)datap
;
336 int reply
= *(int *) replyp
;
338 return reply
< 0 ? av
->code_failure
: (reply
== 0 ? av
->code_success
: av
->code_wait
);
342 oauth2_elapsed_done(void *aux_valuep
)
344 OAUTH2_S
*oauth2
= aux_valuep
? ((OAUTH2_DEVICEPROC_S
*) aux_valuep
)->xoauth2
: NULL
;
345 static time_t savedt
= 0, now
;
348 if(aux_valuep
== NULL
) savedt
= 0; /* reset last time we approved */
351 if(oauth2
->devicecode
.interval
+ now
>= savedt
)
360 oauth2_set_device_info(OAUTH2_S
*oa2
, char *method
)
362 char tmp
[MAILTMPLEN
];
363 char *name
= (char *) oa2
->name
;
365 OAUTH2_DEVICECODE_S
*deviceinfo
= &oa2
->devicecode
;
366 OAUTH2_DEVICEPROC_S aux_value
;
370 STORE_S
*in_store
, *out_store
;
372 HANDLE_S
*handles
= NULL
;
373 AUTH_CODE_S user_input
;
375 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
376 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
379 aux_value
.xoauth2
= oa2
;
380 aux_value
.code_success
= 'e';
381 aux_value
.code_failure
= 'e';
382 aux_value
.code_wait
= NO_OP_COMMAND
;
384 so_puts(in_store
, "<HTML><P>");
385 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name
);
386 so_puts(in_store
, tmp
);
387 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name
, method
),
388 so_puts(in_store
, tmp
);
390 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
392 _("</P><P>To sign in, use a web browser to open the page <A HREF=\"%s\">%s</A> and enter the code \"%s\" without the quotes."),
393 deviceinfo
->verification_uri
, deviceinfo
->verification_uri
, deviceinfo
->user_code
);
394 so_puts(in_store
, tmp
);
397 so_puts(in_store
, "</P><P>");
398 so_puts(in_store
, (char *) deviceinfo
->message
);
400 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
401 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), name
);
402 so_puts(in_store
, tmp
);
403 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
405 so_puts(in_store
, _("</P><P> After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
406 so_puts(in_store
, _("to grant access to Alpine to your data. "));
408 cache_method_message(in_store
);
410 so_puts(in_store
, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit"));
411 so_puts(in_store
, _("</P></HTML>"));
413 so_seek(in_store
, 0L, 0);
414 init_handles(&handles
);
416 gf_link_filter(gf_html2plain
,
417 gf_html2plain_opt(NULL
,
418 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
419 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
420 gf_set_so_readc(&gc
, in_store
);
421 gf_set_so_writec(&pc
, out_store
);
423 gf_clear_so_writec(out_store
);
424 gf_clear_so_readc(in_store
);
426 memset(&sargs
, 0, sizeof(SCROLL_S
));
427 sargs
.text
.handles
= handles
;
428 sargs
.text
.text
= so_text(out_store
);
429 sargs
.text
.src
= CharStar
;
430 sargs
.text
.desc
= _("help text");
431 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
432 sargs
.proc
.tool
= oauth2_auth_answer
;
433 sargs
.proc
.data
.p
= (void *)&user_input
;
434 sargs
.keys
.menu
= &oauth2_device_auth_keymenu
;
435 /* don't want to re-enter c-client */
436 sargs
.quell_newmail
= 1;
437 setbitmap(sargs
.keys
.bitmap
);
438 sargs
.help
.text
= h_oauth2_start
;
439 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
440 sargs
.aux_function
= oauth2deviceinfo_get_accesscode
;
441 sargs
.aux_value
= (void *) &aux_value
;
442 sargs
.aux_condition
= oauth2_elapsed_done
;
443 sargs
.decode_aux_rv_value
= oauth2device_decode_reply
;
444 sargs
.aux_rv_value
= (void *) &aux_rv_value
;
448 ps_global
->mangled_screen
= 1;
449 ps_global
->painted_body_on_startup
= 0;
450 ps_global
->painted_footer_on_startup
= 0;
451 } while (user_input
.answer
!= 'e');
455 free_handles(&handles
);
456 oauth2_elapsed_done(NULL
);
460 * If screen hasn't been initialized yet, use want_to.
464 tmp_20k_buf
[0] = '\0';
465 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
466 _("Authorizing Alpine Access to %s Email Services\n\n"), name
);
467 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
469 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
470 _("Alpine is attempting to log you into your %s account, using the %s method. "), name
, method
),
471 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
473 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
474 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
475 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
476 deviceinfo
->verification_uri
, deviceinfo
->user_code
);
477 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
480 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
481 "%s\n\n", deviceinfo
->message
);
482 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
485 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
486 _("Copy and paste the previous URL into a web browser that supports javascript, to take you to %s's servers to complete this process.\n\n"), name
);
487 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
489 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
490 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
491 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
493 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
494 "%s", _("to grant access to Alpine to your data.\n\n"));
495 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
497 cache_method_message_no_screen();
499 display_init_err(tmp_20k_buf
, 0);
500 memset((void *)tmp
, 0, sizeof(tmp
));
501 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
502 tmp
[sizeof(tmp
)-1] = '\0';
504 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
508 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
509 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
510 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
512 aux_value
.xoauth2
= oa2
;
513 aux_value
.code_success
= 'y';
514 aux_value
.code_failure
= 'n';
515 aux_value
.code_wait
= 'w';
517 strncpy(tmp
, _("Continue waiting"), sizeof(tmp
));
518 tmp
[sizeof(tmp
)-1] = '\0';
520 if(oauth2_elapsed_done((void *) &aux_value
) == 0)
521 oauth2deviceinfo_get_accesscode((void *) &aux_value
, (void *) &rv
);
522 ch
= oauth2device_decode_reply((void *) &aux_value
, (void *) &rv
);
523 } while (ch
== 'w' || want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y');
524 oauth2_elapsed_done(NULL
);
530 oauth2_get_access_code(unsigned char *url
, char *method
, OAUTH2_S
*oauth2
, int *tryanother
)
532 char tmp
[MAILTMPLEN
];
537 STORE_S
*in_store
, *out_store
;
539 HANDLE_S
*handles
= NULL
;
540 AUTH_CODE_S user_input
;
542 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
543 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
546 so_puts(in_store
, "<HTML><BODY><P>");
547 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2
->name
);
548 so_puts(in_store
, tmp
);
549 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
550 so_puts(in_store
, tmp
);
552 if(strucmp((char *) oauth2
->name
, (char *) GMAIL_NAME
) == 0){
553 so_puts(in_store
, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
554 so_puts(in_store
, _("</P><P> First you must register Alpine with Google and create a client-id and client-secret. If you already did that, then you can skip to the authorization step, and continue with the process outlined below."));
555 so_puts(in_store
, _("<UL> "));
556 so_puts(in_store
, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
557 so_puts(in_store
, _("and create a project. The name of the project is not important."));
558 so_puts(in_store
, _("<LI> Go to the Consent Screen and make your app INTERNAL, if your account is a G-Suite account, or EXTERNAL if it is a personal gmail.com account."));
559 so_puts(in_store
, _("<LI> Create OAUTH Credentials."));
560 so_puts(in_store
, _("</UL> "));
561 so_puts(in_store
, _("<P> As a result of this process, you will get a client-id and a client-secret."));
562 so_puts(in_store
, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
563 so_puts(in_store
, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
564 so_puts(in_store
, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
565 so_puts(in_store
, _("</P><P> If you completed these steps successfully, you are ready to move to the second part, where you will authorize Gmail to give access to Alpine to access your email."));
568 so_puts(in_store
, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
569 so_puts(in_store
,"</P><P>");
570 sprintf(tmp_20k_buf
, _("<A HREF=\"%s\">%s</A>"), url
, url
);
571 so_puts(in_store
, tmp_20k_buf
);
573 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
574 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2
->name
);
575 so_puts(in_store
, tmp
);
576 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
578 so_puts(in_store
, _("</P><P> After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine. "));
579 so_puts(in_store
, _(" At the end of this process, you will be given an access code or redirected to a web page."));
580 so_puts(in_store
, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
581 so_puts(in_store
, _(" If you do not see a code, copy the url of the page you were redirected to and again press 'C' and copy and paste it into the prompt. "));
583 cache_method_message(in_store
);
585 so_puts(in_store
, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
586 so_puts(in_store
, _("</P></BODY></HTML>"));
588 so_seek(in_store
, 0L, 0);
589 init_handles(&handles
);
591 gf_link_filter(gf_html2plain
,
592 gf_html2plain_opt(NULL
,
593 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
594 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
595 gf_set_so_readc(&gc
, in_store
);
596 gf_set_so_writec(&pc
, out_store
);
598 gf_clear_so_writec(out_store
);
599 gf_clear_so_readc(in_store
);
601 memset(&sargs
, 0, sizeof(SCROLL_S
));
602 sargs
.text
.handles
= handles
;
603 sargs
.text
.text
= so_text(out_store
);
604 sargs
.text
.src
= CharStar
;
605 sargs
.text
.desc
= _("help text");
606 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
607 sargs
.proc
.tool
= oauth2_auth_answer
;
608 sargs
.proc
.data
.p
= (void *)&user_input
;
609 sargs
.keys
.menu
= &oauth2_auth_keymenu
;
610 /* don't want to re-enter c-client */
611 sargs
.quell_newmail
= 1;
612 setbitmap(sargs
.keys
.bitmap
);
613 sargs
.help
.text
= h_oauth2_start
;
614 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
618 ps_global
->mangled_screen
= 1;
619 ps_global
->painted_body_on_startup
= 0;
620 ps_global
->painted_footer_on_startup
= 0;
621 } while (user_input
.answer
!= 'e');
623 if(!struncmp(user_input
.code
, "http://", 7)
624 || !struncmp(user_input
.code
, "https://", 8)){
626 s
= strstr(user_input
.code
, "code=");
634 else code
= user_input
.code
? cpystr(user_input
.code
) : NULL
;
635 if(user_input
.code
) fs_give((void **) &user_input
.code
);
637 if(code
== NULL
) *tryanother
= 1;
641 free_handles(&handles
);
644 int flags
, rc
, q_line
;
645 /* TRANSLATORS: user needs to input an access code from the server */
646 char *accesscodelabel
= _("Copy and Paste Access Code");
647 char prompt
[MAILTMPLEN
], token
[MAILTMPLEN
];
649 * If screen hasn't been initialized yet, use want_to.
652 tmp_20k_buf
[0] = '\0';
653 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
654 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2
->name
);
655 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
657 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
658 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
659 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
661 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
662 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
663 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
665 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
667 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
669 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
670 _("Copy and paste the previous URL into a web browser that supports javascript, to take you to %s's servers to complete this process.\n\n"), oauth2
->name
);
671 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
673 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
674 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
675 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
677 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
678 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
679 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
681 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
682 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
683 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
685 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
686 "%s", _(" If you do not see a code, copy the url of the page you were redirected to and again press 'C' and copy and paste it into the prompt. "));
687 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
689 cache_method_message_no_screen();
691 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
692 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
693 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
695 display_init_err(tmp_20k_buf
, 0);
696 memset((void *)tmp
, 0, sizeof(tmp
));
697 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
698 tmp
[sizeof(tmp
)-1] = '\0';
700 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
701 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
702 flags
= OE_APPEND_CURRENT
;
703 sprintf(prompt
, "%s: ", accesscodelabel
);
705 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
706 prompt
, NULL
, NO_HELP
, &flags
);
707 } while (rc
!= 0 && rc
!= 1);
708 if(!struncmp(token
, "http://", 7)
709 || !struncmp(token
, "https://", 8)){
711 s
= strstr(token
, "code=");
719 else code
= token
[0] ? cpystr(token
) : NULL
;
726 void mm_login_oauth2(NETMBX
*, char *, char *, OAUTH2_S
*, long int, char *, char *);
728 /* The purpose of this function is to report to c-client the values of the
729 * different tokens and codes so that c-client can try to log in the user
730 * to the server. This function DOES NOT attempt to get these values for
731 * the user. That is attempted in the c-client side (as best as it can be
732 * done given our circumstances: no http support, no javascript support,
733 * etc.). This is the best we can do:
735 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
736 * as best as we can. Unloaded means that there is no server known to
737 * connect, no access token, etc. Pretty much nothing is known about
738 * how to get access code, access token, etc. We ask the user to fill
739 * it up for us, if they can. If the user fills it up we save those
742 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
743 * save the information that c-client got for us.
745 * 3. When saving this information we use the password caching facilities,
746 * but we must do it in a different format so that old information and
747 * new information are not mixed. In order to accommodate this for new
748 * authentication methods, we save the information in the same fields,
749 * but this time we modify it slightly, so that old functions fail to
750 * understand the new information and so not modify it nor use it. The
751 * modification is simple: Every piece of information that was saved
752 * before is prepended XOAUTH2\001 to indicate the authentication
753 * method for which it works. The character \001 is a separator. New
754 * Alpine will know how to deal with this, but old versions, will not
755 * strip this prefix from the information and fail to get the
756 * information or modify it when needed. Only new versions of Alpine will
757 * know how to process this information.
758 * new_value = authenticator_method separator old_value
759 * authenticator_method = "XOAUTH2_S"
761 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
762 * authenticator is "XOAUTH2\001imap.gmail.com".
763 * In addition, the password field is not used to encode the password
764 * anymore, it is used to save login information needed in a format that
765 * the caller function chooses, but that must be preceded by the
766 * "authenticator_method separator" code as above.
769 mm_login_oauth2(NETMBX
*mb
, char *user
, char *method
,
770 OAUTH2_S
*login
, long int trial
,
771 char *usethisprompt
, char *altuserforcache
)
773 char *token
, tmp
[MAILTMPLEN
];
774 char prompt
[4*MAILTMPLEN
];
775 char *OldRefreshToken
, *OldAccessToken
;
776 char *NewRefreshToken
, *NewAccessToken
;
777 char *SaveRefreshToken
, *SaveAccessToken
;
778 /* TRANSLATORS: A label for the hostname that the user is logging in on */
779 char *hostlabel
= _("HOST");
780 /* TRANSLATORS: user is logging in as a particular user (a particular
781 login name), this is just labelling that user name. */
782 char *userlabel
= _("USER");
783 STRLIST_S hostlist
[2], hostlist2
[OAUTH2_TOT_EQUIV
+1];
784 int len
, q_line
, flags
, i
, j
;
787 int ChangeAccessToken
, ChangeRefreshToken
, ChangeExpirationTime
;
788 OAUTH2_S
*oa2list
, *oa2
;
791 unsigned long OldExpirationTime
, NewExpirationTime
, SaveExpirationTime
;
792 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
793 int preserve_password
= -1;
796 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
797 trial
, mb
->user
? mb
->user
: "(null)",
798 mb
->service
? mb
->service
: "(null)",
799 mb
->port
? " port=" : "",
800 mb
->port
? comatose(mb
->port
) : "",
801 altuserforcache
? " altuserforcache =" : "",
802 altuserforcache
? altuserforcache
: ""));
804 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
806 save_in_init
= ps_global
->in_init_seq
;
807 ps_global
->in_init_seq
= 0;
808 ps_global
->no_newmail_check_from_optionally_enter
= 1;
810 /* make sure errors are seen */
811 if(ps_global
->ttyo
&& !ps_global
->noshow_error
)
812 flush_status_messages(0);
814 token
= NULL
; /* start from scratch */
816 hostlist
[0].name
= mb
->host
;
817 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
818 hostlist
[0].next
= &hostlist
[1];
819 hostlist
[1].name
= mb
->orighost
;
820 hostlist
[1].next
= NULL
;
823 hostlist
[0].next
= NULL
;
825 if(hostlist
[0].name
){
826 dprint((9, "mm_login_oauth2: host=%s\n",
827 hostlist
[0].name
? hostlist
[0].name
: "?"));
828 if(hostlist
[0].next
&& hostlist
[1].name
){
829 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist
[1].name
));
833 if(trial
== 0L && !altuserforcache
){
834 if(*mb
->user
!= '\0')
835 strncpy(user
, mb
->user
, NETMAXUSER
);
837 flags
= OE_APPEND_CURRENT
;
838 sprintf(prompt
, "%s: %s - %s: ", hostlabel
, mb
->orighost
, userlabel
);
839 optionally_enter(user
, q_line
, 0, NETMAXUSER
, prompt
, NULL
, NO_HELP
, &flags
);
841 user
[NETMAXUSER
-1] = '\0';
845 * We check to see if the server we are going to log in to is already
846 * registered. This gives us a list of servers with the same
847 * credentials, so we use the same credentials for all of them.
850 for(registered
= 0, oa2list
= alpine_oauth2_list
;
851 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
853 for(i
= 0; i
< OAUTH2_TOT_EQUIV
854 && oa2list
->host
[i
] != NULL
855 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
856 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
863 x
= oauth2_get_client_info(oa2list
->name
, user
);
865 for(oa2list
= alpine_oauth2_list
;
866 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
868 for(i
= 0; i
< OAUTH2_TOT_EQUIV
869 && oa2list
->host
[i
] != NULL
870 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
871 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
872 char *flow
= oa2list
->server_mthd
[0].name
? "Authorize"
873 : (oa2list
->server_mthd
[1].name
? "Device" : "Unknown");
874 if(!strucmp(x
->flow
, flow
)) break; /* found it */
878 /* else use the one we found earlier, the user has to configure this better */
882 hostlist2
[i
= 0].name
= mb
->host
;
883 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
))
884 hostlist2
[++i
].name
= mb
->orighost
;
886 for(j
= 0; j
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[j
] != NULL
; j
++){
888 for(k
= 0; k
<= i
&& hostlist2
[k
].name
889 && strcmp(hostlist2
[k
].name
, oa2list
->host
[j
]); k
++);
891 hostlist2
[++i
].name
= oa2list
->host
[j
];
893 hostlist2
[i
+1].name
= NULL
;
894 hostlist2
[i
+1].next
= NULL
;
895 for(j
= i
; j
>= 0; j
--)
896 hostlist2
[j
].next
= &hostlist2
[j
+1];
899 if(registered
){ /* redo the app_id, no questions asked */
900 free_id(&ps_global
->id
);
901 ps_global
->id
= set_alpine_id(oa2list
->app_id
? oa2list
->app_id
: PACKAGE_NAME
, PACKAGE_VERSION
);
902 mail_parameters(NULL
, SET_IDPARAMS
, (void *) ps_global
->id
);
906 * We check if we have a refresh token saved somewhere, if so
907 * we use it to get a new access token, otherwise we need to
908 * get an access code so we can get (and save) a refresh token
909 * and use the access token.
911 if(trial
== 0L && !altuserforcache
){
912 /* Search for a refresh token that is already loaded ... */
913 if(imap_get_passwd_auth(mm_login_list
, &token
, user
,
914 registered
? hostlist2
: hostlist
,
915 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
916 dprint((9, "mm_login_oauth2: found a refresh token\n"));
917 ps_global
->no_newmail_check_from_optionally_enter
= 0;
918 ps_global
->in_init_seq
= save_in_init
;
920 #ifdef LOCAL_PASSWD_CACHE
921 /* or see if we have saved one in the local password cache and load it */
922 else if(get_passfile_passwd_auth(ps_global
->pinerc
, &token
,
923 user
, registered
? hostlist2
: hostlist
,
924 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
925 imap_set_passwd_auth(&mm_login_list
, token
, user
,
926 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
927 update_passfile_hostlist_auth(ps_global
->pinerc
, user
, hostlist
,
928 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
);
929 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
930 ps_global
->no_newmail_check_from_optionally_enter
= 0;
931 ps_global
->in_init_seq
= save_in_init
;
933 if(token
&& *token
) preserve_password
= 1; /* resave it, no need to ask */
934 #endif /* LOCAL_PASSWD_CACHE */
936 user
[NETMAXUSER
-1] = '\0';
938 /* The Old* variables is what c_client knows */
939 OldRefreshToken
= login
->cancel_refresh_token
? NULL
: login
->param
[OA2_RefreshToken
].value
;
940 OldAccessToken
= login
->access_token
;
941 OldExpirationTime
= login
->expiration
;
943 /* The New* variables is what Alpine knows */
944 NewRefreshToken
= NewAccessToken
= NULL
;
945 NewExpirationTime
= 0L;
946 ChangeAccessToken
= ChangeRefreshToken
= ChangeExpirationTime
= 0;
948 if(token
&& *token
&& !login
->cancel_refresh_token
){
952 t
= strchr(s
, PWDAUTHSEP
);
954 NewRefreshToken
= cpystr(s
);
957 NewRefreshToken
= cpystr(s
);
959 t
= strchr(s
, PWDAUTHSEP
);
961 NewAccessToken
= cpystr(s
);
964 NewAccessToken
= cpystr(s
);
966 NewExpirationTime
= strtol(s
, &s
, 10);
967 if(NewExpirationTime
<= 0 || NewExpirationTime
<= time(0))
968 NewExpirationTime
= 0L;
971 /* check we got good information, and send good information below */
972 if(NewRefreshToken
&& !*NewRefreshToken
)
973 fs_give((void **) &NewRefreshToken
);
974 if(NewAccessToken
&& (NewExpirationTime
== 0L || !*NewAccessToken
))
975 fs_give((void **) &NewAccessToken
);
978 if(NewRefreshToken
== NULL
)
981 if(login
->first_time
){ /* count how many authorization methods we support */
984 for(nmethods
= 0, oa2
= alpine_oauth2_list
; oa2
&& oa2
->name
; oa2
++){
985 for(j
= 0; j
< OAUTH2_TOT_EQUIV
987 && oa2
->host
[j
] != NULL
988 && strucmp(oa2
->host
[j
], mb
->orighost
) != 0; j
++);
989 if(oa2
&& oa2
->host
&& j
< OAUTH2_TOT_EQUIV
&& oa2
->host
[j
]
990 && ((oa2
->server_mthd
[0].name
&& (oa2
->flags
& OA2_AUTHORIZE
))
991 || (oa2
->server_mthd
[1].name
&& (oa2
->flags
& OA2_DEVICE
))))
996 oa2list
= oauth2_select_flow(mb
->orighost
);
998 if(!oa2list
) registered
= 0;
1001 /* Default to saving what we already had saved */
1003 SaveRefreshToken
= login
->cancel_refresh_token
? NULL
: NewRefreshToken
;
1004 SaveAccessToken
= NewAccessToken
;
1005 SaveExpirationTime
= NewExpirationTime
;
1007 /* Translation of the logic below:
1008 * if (c-client has a refresh token
1009 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1010 forget the Alpine refresh token;
1011 make the Alpine Refresh token = c-client refresh token.;
1012 signal that we changed the refresh token;
1013 In this situation we do not need to clear up the Alpine access token. This will
1014 expire, so we can use it until it expires. We can save the c-client refresh token
1015 together with the Alpine Access Token and the expiration date of the Alpine Access
1017 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1018 forget the Alpine refresh token;
1019 if Alpine has an access token, forget it;
1020 reset the expiration time
1021 signal that we changed the refresh token; (because the service expired it)
1025 if(OldRefreshToken
!= NULL
1026 && (NewRefreshToken
== NULL
|| strcmp(OldRefreshToken
, NewRefreshToken
))){
1027 if(NewRefreshToken
) fs_give((void **) &NewRefreshToken
);
1028 NewRefreshToken
= cpystr(OldRefreshToken
);
1029 ChangeRefreshToken
++;
1030 SaveRefreshToken
= OldRefreshToken
;
1031 SaveAccessToken
= NewAccessToken
;
1032 SaveExpirationTime
= NewExpirationTime
;
1033 } else if (OldRefreshToken
== NULL
&& NewRefreshToken
!= NULL
&& trial
> 0){
1034 fs_give((void **) &NewRefreshToken
);
1035 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1036 NewExpirationTime
= 0L;
1037 ChangeRefreshToken
++;
1038 SaveRefreshToken
= NULL
;
1039 SaveAccessToken
= NULL
;
1040 SaveExpirationTime
= 0L;
1043 if(OldAccessToken
!= NULL
1044 && (NewAccessToken
== NULL
|| strcmp(OldAccessToken
, NewAccessToken
))){
1045 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1046 NewAccessToken
= cpystr(OldAccessToken
);
1047 ChangeAccessToken
++;
1048 NewExpirationTime
= OldExpirationTime
;
1049 SaveRefreshToken
= NewRefreshToken
;
1050 SaveAccessToken
= NewAccessToken
;
1051 SaveExpirationTime
= NewExpirationTime
;
1055 login
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1056 login
->access_token
= SaveAccessToken
;
1057 login
->expiration
= SaveExpirationTime
;
1059 oa2list
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1060 oa2list
->access_token
= SaveAccessToken
;
1061 oa2list
->expiration
= SaveExpirationTime
;
1062 oa2list
->first_time
= login
->first_time
;
1063 oa2list
->cancel_refresh_token
= login
->cancel_refresh_token
;
1064 *login
= *oa2list
; /* load login pointer */
1067 if(!ChangeAccessToken
&& !ChangeRefreshToken
&& !login
->cancel_refresh_token
)
1070 /* get ready to save this information. The format will be
1071 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1072 * (spaces added for clarity, \001 is PWDAUTHSEP)
1074 if(token
) fs_give((void **) &token
);
1075 sprintf(tmp
, "%lu", SaveExpirationTime
);
1076 tmp
[sizeof(tmp
) - 1] = '\0';
1077 len
= strlen(SaveRefreshToken
? SaveRefreshToken
: "")
1078 + strlen(SaveAccessToken
? SaveAccessToken
: "")
1080 token
= fs_get(len
+ 1);
1081 sprintf(token
, "%s%c%s%c%lu",
1082 SaveRefreshToken
? SaveRefreshToken
: "", PWDAUTHSEP
,
1083 SaveAccessToken
? SaveAccessToken
: "", PWDAUTHSEP
,
1084 SaveExpirationTime
);
1086 /* remember the access information for next time */
1087 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
1088 imap_set_passwd_auth(&mm_login_list
, token
,
1089 altuserforcache
? altuserforcache
: user
, hostlist
,
1090 (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
1091 #ifdef LOCAL_PASSWD_CACHE
1092 /* if requested, remember it on disk for next session */
1093 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
1094 set_passfile_passwd_auth(ps_global
->pinerc
, token
,
1095 altuserforcache
? altuserforcache
: user
, hostlist
,
1096 (mb
->sslflag
||mb
->tlsflag
),
1097 (preserve_password
== -1 ? 0
1098 : (preserve_password
== 0 ? 2 :1)), OA2NAME
);
1099 #endif /* LOCAL_PASSWD_CACHE */
1101 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1105 set_alpine_id(char *pname
, char *pversion
)
1109 if(!pname
|| !pversion
) return NULL
;
1111 id
= fs_get(sizeof(IDLIST
));
1112 id
->name
= cpystr("name");
1113 id
->value
= cpystr(pname
);
1114 id
->next
= fs_get(sizeof(IDLIST
));
1115 id
->next
->name
= cpystr("version");
1116 id
->next
->value
= cpystr(pversion
);
1117 id
->next
->next
= NULL
;
1121 /*----------------------------------------------------------------------
1122 receive notification from IMAP
1124 Args: stream -- Mail stream message is relevant to
1125 string -- The message text
1126 errflg -- Set if it is a serious error
1128 Result: message displayed in status line
1130 The facility is for general notices, such as connection to server;
1131 server shutting down etc... It is used infrequently.
1132 ----------------------------------------------------------------------*/
1134 mm_notify(MAILSTREAM
*stream
, char *string
, long int errflg
)
1139 now
= time((time_t *)0);
1140 tm_now
= localtime(&now
);
1142 /* be sure to log the message... */
1144 if(ps_global
->debug_imap
|| ps_global
->debugmem
)
1145 dprint((errflg
== TCPDEBUG
|| errflg
== HTTPDEBUG
? 7 : 2,
1146 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1147 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1148 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1149 (!errflg
) ? "babble" :
1150 (errflg
== ERROR
) ? "error" :
1151 (errflg
== WARN
) ? "warning" :
1152 (errflg
== PARSE
) ? "parse" :
1153 (errflg
== TCPDEBUG
) ? "tcp" :
1154 (errflg
== HTTPDEBUG
) ? "http" :
1155 (errflg
== BYE
) ? "bye" : "unknown",
1156 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1157 string
? string
: "?"));
1160 snprintf(ps_global
->last_error
, sizeof(ps_global
->last_error
), "%s : %.*s",
1161 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1162 (int) MIN(MAX_SCREEN_COLS
, sizeof(ps_global
->last_error
)-70),
1164 ps_global
->last_error
[ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1165 : sizeof(ps_global
->last_error
)-1] = '\0';
1168 * Then either set special bits in the pine struct or
1169 * display the message if it's tagged as an "ALERT" or
1170 * its errflg > NIL (i.e., WARN, or ERROR)
1174 * We'd like to sp_mark_stream_dead() here but we can't do that because
1175 * that might call mail_close and we are already in a c-client callback.
1176 * So just set the dead bit and clean it up later.
1178 sp_set_dead_stream(stream
, 1);
1179 else if(!strncmp(string
, "[TRYCREATE]", 11))
1180 ps_global
->try_to_create
= 1;
1181 else if(!strncmp(string
, "[REFERRAL ", 10))
1182 ; /* handled in the imap_referral() callback */
1183 else if(!strncmp(string
, "[ALERT]", 7))
1184 q_status_message2(SM_MODAL
, 3, 3,
1185 _("Alert received while accessing \"%s\": %s"),
1186 (stream
&& stream
->mailbox
)
1187 ? stream
->mailbox
: "-no folder-",
1188 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf
+10000),
1189 SIZEOF_20KBUF
-10000, string
));
1190 else if(!strncmp(string
, "[UNSEEN ", 8)){
1194 for(p
= string
+ 8; isdigit(*p
); p
++)
1195 n
= (n
* 10) + (*p
- '0');
1197 sp_set_first_unseen(stream
, n
);
1199 else if(!strncmp(string
, "[READ-ONLY]", 11)
1200 && !(stream
&& stream
->mailbox
&& IS_NEWS(stream
)))
1201 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, "%s : %s",
1202 (stream
&& stream
->mailbox
)
1203 ? stream
->mailbox
: "-no folder-",
1205 else if((errflg
&& errflg
!= BYE
&& errflg
!= PARSE
)
1206 && !ps_global
->noshow_error
1208 && (ps_global
->noshow_warn
|| (stream
&& stream
->unhealthy
))))
1209 q_status_message(SM_ORDER
| ((errflg
== ERROR
) ? SM_DING
: 0),
1210 3, 6, ps_global
->last_error
);
1214 /*----------------------------------------------------------------------
1215 Queue imap log message for display in the message line
1217 Args: string -- The message
1218 errflg -- flag set to 1 if pertains to an error
1220 Result: Message queued for display
1222 The c-client/imap reports most of it's status and errors here
1225 mm_log(char *string
, long int errflg
)
1227 char message
[sizeof(ps_global
->c_client_error
)];
1229 int was_capitalized
;
1230 static char saw_kerberos_init_warning
;
1234 now
= time((time_t *)0);
1235 tm_now
= localtime(&now
);
1237 dprint((((errflg
== TCPDEBUG
) && ps_global
->debug_tcp
) ? debug
:
1238 (errflg
== TCPDEBUG
) ? 10 :
1239 ((errflg
== HTTPDEBUG
) && ps_global
->debug_http
) ? debug
:
1240 (errflg
== HTTPDEBUG
) ? 10 : 2,
1241 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1242 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1243 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1244 (!errflg
) ? "babble" :
1245 (errflg
== ERROR
) ? "error" :
1246 (errflg
== WARN
) ? "warning" :
1247 (errflg
== PARSE
) ? "parse" :
1248 (errflg
== TCPDEBUG
) ? "tcp" :
1249 (errflg
== HTTPDEBUG
) ? "http" :
1250 (errflg
== BYE
) ? "bye" : "unknown",
1251 string
? string
: "?"));
1253 if(errflg
== ERROR
&& !strncmp(string
, "[TRYCREATE]", 11)){
1254 ps_global
->try_to_create
= 1;
1257 else if(ps_global
->try_to_create
1258 || !strncmp(string
, "[CLOSED]", 8)
1259 || (sp_dead_stream(ps_global
->mail_stream
) && strstr(string
, "No-op")))
1261 * Don't display if creating new folder OR
1262 * warning about a dead stream ...
1266 strncpy(message
, string
, sizeof(message
));
1267 message
[sizeof(message
) - 1] = '\0';
1269 if(errflg
== WARN
&& srchstr(message
, "try running kinit") != NULL
){
1270 if(saw_kerberos_init_warning
)
1273 saw_kerberos_init_warning
= 1;
1276 /*---- replace all "mailbox" with "folder" ------*/
1277 occurence
= srchstr(message
, "mailbox");
1280 || isspace((unsigned char) *(occurence
+7))
1281 || *(occurence
+7) == ':'){
1282 was_capitalized
= isupper((unsigned char) *occurence
);
1283 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 7, (errflg
== PARSE
? "address" : "folder"));
1285 *occurence
= (errflg
== PARSE
? 'A' : 'F');
1290 occurence
= srchstr(occurence
, "mailbox");
1293 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1294 occurence
= srchstr(message
, "GSSAPI");
1297 || isspace((unsigned char) *(occurence
+6))
1298 || *(occurence
+6) == ':')
1299 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 6, "Kerberos");
1303 occurence
= srchstr(occurence
, "GSSAPI");
1307 ps_global
->mm_log_error
= 1;
1309 if(errflg
== PARSE
|| (errflg
== ERROR
&& ps_global
->noshow_error
))
1310 strncpy(ps_global
->c_client_error
, message
,
1311 sizeof(ps_global
->c_client_error
));
1313 if(ps_global
->noshow_error
1314 || (ps_global
->noshow_warn
&& errflg
== WARN
)
1315 || !(errflg
== ERROR
|| errflg
== WARN
))
1316 return; /* Only care about errors; don't print when asked not to */
1318 /*---- Display the message ------*/
1319 q_status_message((errflg
== ERROR
) ? (SM_ORDER
| SM_DING
) : SM_ORDER
,
1321 strncpy(ps_global
->last_error
, message
, sizeof(ps_global
->last_error
));
1322 ps_global
->last_error
[sizeof(ps_global
->last_error
) - 1] = '\0';
1326 mm_login_method_work(NETMBX
*mb
, char *user
, void *login
, long int trial
,
1327 char *method
, char *usethisprompt
, char *altuserforcache
)
1331 if(strucmp(method
, OA2NAME
) == 0 || strucmp(method
, BEARERNAME
) == 0)
1332 mm_login_oauth2(mb
, user
, method
, (OAUTH2_S
*) login
, trial
, usethisprompt
, altuserforcache
);
1336 mm_login_work(NETMBX
*mb
, char *user
, char **pwd
, long int trial
,
1337 char *usethisprompt
, char *altuserforcache
)
1339 char tmp
[MAILTMPLEN
];
1340 char prompt
[1000], *last
;
1341 char port
[20], non_def_port
[20], insecure
[20];
1342 char defuser
[NETMAXUSER
];
1343 char hostleadin
[80], hostname
[200], defubuf
[200];
1344 char logleadin
[80], pwleadin
[50];
1345 char hostlist0
[MAILTMPLEN
], hostlist1
[MAILTMPLEN
];
1346 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1347 that the password will be sent unencrypted over the network. This is
1348 just a warning message that gets added parenthetically when the user
1349 is asked for a password. */
1350 char *insec
= _(" (INSECURE)");
1351 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1352 after having already failed at least once. */
1353 char *retry
= _("Retrying - ");
1354 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1355 char *hostlabel
= _("HOST");
1356 /* TRANSLATORS: user is logging in as a particular user (a particular
1357 login name), this is just labelling that user name. */
1358 char *userlabel
= _("USER");
1359 STRLIST_S hostlist
[2];
1361 int len
, rc
, q_line
, flags
;
1362 int oespace
, avail
, need
, save_dont_use
;
1365 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1366 int preserve_password
= -1;
1369 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1370 trial
, mb
->user
? mb
->user
: "(null)",
1371 mb
->service
? mb
->service
: "(null)",
1372 mb
->port
? " port=" : "",
1373 mb
->port
? comatose(mb
->port
) : "",
1374 altuserforcache
? " altuserforcache =" : "",
1375 altuserforcache
? altuserforcache
: ""));
1376 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
1378 save_in_init
= ps_global
->in_init_seq
;
1379 ps_global
->in_init_seq
= 0;
1380 ps_global
->no_newmail_check_from_optionally_enter
= 1;
1382 /* make sure errors are seen */
1384 flush_status_messages(0);
1386 /* redo app id in case we are logging in to an IMAP server that supports the IMAP ID extension */
1387 free_id(&ps_global
->id
);
1388 ps_global
->id
= set_alpine_id(PACKAGE_NAME
, PACKAGE_VERSION
);
1389 mail_parameters(NULL
, SET_IDPARAMS
, (void *) ps_global
->id
);
1392 * Add port number to hostname if going through a tunnel or something
1394 non_def_port
[0] = '\0';
1395 if(mb
->port
&& mb
->service
&&
1396 (sv
= getservbyname(mb
->service
, "tcp")) &&
1397 (mb
->port
!= ntohs(sv
->s_port
))){
1398 snprintf(non_def_port
, sizeof(non_def_port
), ":%lu", mb
->port
);
1399 non_def_port
[sizeof(non_def_port
)-1] = '\0';
1400 dprint((9, "mm_login: using non-default port=%s\n", non_def_port
));
1404 * set up host list for sybil servers...
1407 strncpy(hostlist0
, mb
->host
, sizeof(hostlist0
)-1);
1408 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1409 strncat(hostlist0
, non_def_port
, sizeof(hostlist0
)-strlen(hostlist0
)-1);
1410 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1411 hostlist
[0].name
= hostlist0
;
1412 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1413 strncpy(hostlist1
, mb
->orighost
, sizeof(hostlist1
)-1);
1414 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1415 strncat(hostlist1
, non_def_port
, sizeof(hostlist1
)-strlen(hostlist1
)-1);
1416 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1417 hostlist
[0].next
= &hostlist
[1];
1418 hostlist
[1].name
= hostlist1
;
1419 hostlist
[1].next
= NULL
;
1422 hostlist
[0].next
= NULL
;
1425 hostlist
[0].name
= mb
->host
;
1426 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1427 hostlist
[0].next
= &hostlist
[1];
1428 hostlist
[1].name
= mb
->orighost
;
1429 hostlist
[1].next
= NULL
;
1432 hostlist
[0].next
= NULL
;
1435 if(hostlist
[0].name
){
1436 dprint((9, "mm_login: host=%s\n",
1437 hostlist
[0].name
? hostlist
[0].name
: "?"));
1438 if(hostlist
[0].next
&& hostlist
[1].name
){
1439 dprint((9, "mm_login: orighost=%s\n", hostlist
[1].name
));
1444 * Initialize user name with either
1445 * 1) /user= value in the stream being logged into,
1446 * or 2) the user name we're running under.
1448 * Note that VAR_USER_ID is not yet initialized if this login is
1449 * the one to access the remote config file. In that case, the user
1450 * can supply the username in the config file name with /user=.
1452 if(trial
== 0L && !altuserforcache
){
1453 strncpy(user
, (*mb
->user
) ? mb
->user
:
1454 ps_global
->VAR_USER_ID
? ps_global
->VAR_USER_ID
: "",
1456 user
[NETMAXUSER
-1] = '\0';
1458 /* try last working password associated with this host. */
1459 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1460 (mb
->sslflag
||mb
->tlsflag
))){
1461 dprint((9, "mm_login: found a password to try\n"));
1462 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1463 ps_global
->in_init_seq
= save_in_init
;
1467 #ifdef LOCAL_PASSWD_CACHE
1468 /* check to see if there's a password left over from last session */
1469 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1470 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1471 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1472 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1473 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1474 (mb
->sslflag
||mb
->tlsflag
));
1475 dprint((9, "mm_login: found a password in passfile to try\n"));
1476 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1477 ps_global
->in_init_seq
= save_in_init
;
1480 #endif /* LOCAL_PASSWD_CACHE */
1483 * If no explicit user name supplied and we've not logged in
1484 * with our local user name, see if we've visited this
1485 * host before as someone else.
1488 ((last
= imap_get_user(mm_login_list
, hostlist
))
1489 #ifdef LOCAL_PASSWD_CACHE
1491 (last
= get_passfile_user(ps_global
->pinerc
, hostlist
))
1492 #endif /* LOCAL_PASSWD_CACHE */
1494 strncpy(user
, last
, NETMAXUSER
);
1495 user
[NETMAXUSER
-1] = '\0';
1496 dprint((9, "mm_login: found user=%s\n",
1497 user
? user
: "?"));
1499 /* try last working password associated with this host/user. */
1500 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1501 (mb
->sslflag
||mb
->tlsflag
))){
1503 "mm_login: found a password for user=%s to try\n",
1504 user
? user
: "?"));
1505 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1506 ps_global
->in_init_seq
= save_in_init
;
1510 #ifdef LOCAL_PASSWD_CACHE
1511 /* check to see if there's a password left over from last session */
1512 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1513 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1514 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1515 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1516 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1517 (mb
->sslflag
||mb
->tlsflag
));
1519 "mm_login: found a password for user=%s in passfile to try\n",
1520 user
? user
: "?"));
1521 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1522 ps_global
->in_init_seq
= save_in_init
;
1525 #endif /* LOCAL_PASSWD_CACHE */
1528 #if !defined(DOS) && !defined(OS2)
1529 if(!*mb
->user
&& !*user
&&
1530 (last
= (ps_global
->ui
.login
&& ps_global
->ui
.login
[0])
1531 ? ps_global
->ui
.login
: NULL
)
1533 strncpy(user
, last
, NETMAXUSER
);
1534 user
[NETMAXUSER
-1] = '\0';
1535 dprint((9, "mm_login: found user=%s\n",
1536 user
? user
: "?"));
1538 /* try last working password associated with this host. */
1539 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1540 (mb
->sslflag
||mb
->tlsflag
))){
1541 dprint((9, "mm_login:ui: found a password to try\n"));
1542 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1543 ps_global
->in_init_seq
= save_in_init
;
1547 #ifdef LOCAL_PASSWD_CACHE
1548 /* check to see if there's a password left over from last session */
1549 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1550 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1551 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1552 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1553 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1554 (mb
->sslflag
||mb
->tlsflag
));
1555 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1556 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1557 ps_global
->in_init_seq
= save_in_init
;
1560 #endif /* LOCAL_PASSWD_CACHE */
1565 user
[NETMAXUSER
-1] = '\0';
1571 * Even if we have a user now, user gets a chance to change it.
1573 ps_global
->mangled_footer
= 1;
1574 if(!*mb
->user
&& !altuserforcache
){
1579 * Instead of offering user with a value that the user can edit,
1580 * we offer [user] as a default so that the user can type CR to
1581 * use it. Otherwise, the user has to type in whole name.
1583 strncpy(defuser
, user
, sizeof(defuser
)-1);
1584 defuser
[sizeof(defuser
)-1] = '\0';
1588 * Need space for "Retrying - "
1594 * about 15 chars for input
1597 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1598 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1599 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1601 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1602 hostname
[sizeof(hostname
)-1] = '\0';
1605 * Add port number to hostname if going through a tunnel or something
1608 strncpy(port
, non_def_port
, sizeof(port
));
1613 /* if not encrypted and SSL/TLS is supported */
1614 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1615 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1616 strncpy(insecure
, insec
, sizeof(insecure
));
1618 /* TRANSLATORS: user is being asked to type in their login name */
1619 snprintf(logleadin
, sizeof(logleadin
), " %s", _("ENTER LOGIN NAME"));
1621 snprintf(defubuf
, sizeof(defubuf
), "%s%s%s : ", (*defuser
) ? " [" : "",
1622 (*defuser
) ? defuser
: "",
1623 (*defuser
) ? "]" : "");
1624 defubuf
[sizeof(defubuf
)-1] = '\0';
1625 /* space reserved after prompt */
1626 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1628 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1629 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1630 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) + oespace
;
1632 /* If we're retrying cut the hostname back to the first word. */
1633 if(avail
< need
&& trial
> 0){
1636 len
= strlen(hostname
);
1637 if((p
= strchr(hostname
, '.')) != NULL
){
1639 need
-= (len
- strlen(hostname
));
1644 need
-= utf8_width(retry
);
1649 /* reduce length of logleadin */
1650 len
= utf8_width(logleadin
);
1651 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1652 longer version doesn't fit on screen */
1653 snprintf(logleadin
, sizeof(logleadin
), " %s", _("LOGIN"));
1654 need
-= (len
- utf8_width(logleadin
));
1657 /* get two spaces from hostleadin */
1658 len
= utf8_width(hostleadin
);
1659 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
1660 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
1661 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1662 need
-= (len
- utf8_width(hostleadin
));
1664 /* get rid of port */
1665 if(avail
< need
&& strlen(port
) > 0){
1666 need
-= strlen(port
);
1674 * Reduce space for hostname. Best we can do is 6 chars
1677 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
1678 len
= strlen(hostname
);
1679 strncpy(hostname
+reduce_to
-3, "...", 4);
1680 need
-= (len
- strlen(hostname
));
1682 if(avail
< need
&& strlen(insecure
) > 0){
1683 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
1685 insecure
[strlen(insecure
)-4] = ')';
1686 insecure
[strlen(insecure
)-3] = '\0';
1689 need
-= utf8_width(insecure
);
1695 if(strlen(defubuf
) > 3){
1696 len
= strlen(defubuf
);
1697 strncpy(defubuf
, " [..] :", 9);
1698 need
-= (len
- strlen(defubuf
));
1702 strncpy(defubuf
, ":", 2);
1705 * If it still doesn't fit, optionally_enter gets
1706 * to worry about it.
1714 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s",
1715 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
);
1716 prompt
[sizeof(prompt
)-1] = '\0';
1720 mm_login_alt_cue(mb
);
1722 flags
= OE_APPEND_CURRENT
;
1723 save_dont_use
= ps_global
->dont_use_init_cmds
;
1724 ps_global
->dont_use_init_cmds
= 1;
1726 if(!*user
&& *defuser
){
1727 strncpy(user
, defuser
, NETMAXUSER
);
1728 user
[NETMAXUSER
-1] = '\0';
1731 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, pwd
, NETMAXPASSWD
,
1732 #ifdef LOCAL_PASSWD_CACHE
1733 is_using_passfile() ? 1 :
1734 #endif /* LOCAL_PASSWD_CACHE */
1735 0, 0, &preserve_password
);
1736 ps_global
->dont_use_init_cmds
= save_dont_use
;
1737 if(rc
== 0 && *user
&& *pwd
)
1739 #else /* !_WINDOWS */
1740 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
1741 prompt
, NULL
, help
, &flags
);
1742 #endif /* !_WINDOWS */
1743 ps_global
->dont_use_init_cmds
= save_dont_use
;
1746 help
= help
== NO_HELP
? h_oe_login
: NO_HELP
;
1751 if(rc
== 0 && !*user
){
1752 strncpy(user
, defuser
, NETMAXUSER
);
1753 user
[NETMAXUSER
-1] = '\0';
1760 if(rc
== 1 || !user
[0]) {
1761 ps_global
->user_says_cancel
= (rc
== 1);
1766 strncpy(user
, mb
->user
, NETMAXUSER
);
1767 user
[NETMAXUSER
-1] = '\0';
1770 user
[NETMAXUSER
-1] = '\0';
1772 if(!(user
[0] || altuserforcache
)){
1773 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1774 ps_global
->in_init_seq
= save_in_init
;
1779 * Now that we have a user, we can check in the cache again to see
1780 * if there is a password there. Try last working password associated
1781 * with this host and user.
1783 if(trial
== 0L && !*mb
->user
&& !altuserforcache
){
1784 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1785 (mb
->sslflag
||mb
->tlsflag
))){
1786 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1787 ps_global
->in_init_seq
= save_in_init
;
1791 #ifdef LOCAL_PASSWD_CACHE
1792 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1793 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1794 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1795 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1796 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1797 ps_global
->in_init_seq
= save_in_init
;
1800 #endif /* LOCAL_PASSWD_CACHE */
1802 else if(trial
== 0 && altuserforcache
){
1803 if(imap_get_passwd(mm_login_list
, pwd
, altuserforcache
, hostlist
,
1804 (mb
->sslflag
||mb
->tlsflag
))){
1805 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1806 ps_global
->in_init_seq
= save_in_init
;
1810 #ifdef LOCAL_PASSWD_CACHE
1811 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1812 altuserforcache
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1813 imap_set_passwd(&mm_login_list
, *pwd
, altuserforcache
,
1814 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1815 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1816 ps_global
->in_init_seq
= save_in_init
;
1819 #endif /* LOCAL_PASSWD_CACHE */
1823 * Didn't find password in cache or this isn't the first try. Ask user.
1828 * Need space for "Retrying - "
1834 * " ENTER PASSWORD: "
1835 * about 15 chars for input
1838 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1839 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1841 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1842 hostname
[sizeof(hostname
)-1] = '\0';
1845 * Add port number to hostname if going through a tunnel or something
1848 strncpy(port
, non_def_port
, sizeof(port
));
1854 /* if not encrypted and SSL/TLS is supported */
1855 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1856 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1857 strncpy(insecure
, insec
, sizeof(insecure
));
1860 strncpy(logleadin
, usethisprompt
, sizeof(logleadin
));
1861 logleadin
[sizeof(logleadin
)-1] = '\0';
1866 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
1868 strncpy(defubuf
, user
, sizeof(defubuf
)-1);
1869 defubuf
[sizeof(defubuf
)-1] = '\0';
1872 /* TRANSLATORS: user is being asked to type in their password */
1873 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("ENTER PASSWORD"));
1875 /* space reserved after prompt */
1876 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1878 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1879 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1880 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) +
1881 utf8_width(pwleadin
) + oespace
;
1883 if(avail
< need
&& trial
> 0){
1886 len
= strlen(hostname
);
1887 if((p
= strchr(hostname
, '.')) != NULL
){
1889 need
-= (len
- strlen(hostname
));
1894 need
-= utf8_width(retry
);
1900 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
1904 rplstr(pwleadin
, sizeof(pwleadin
), 1, "");
1908 /* get two spaces from hostleadin */
1909 len
= utf8_width(hostleadin
);
1910 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
1911 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
1912 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1913 need
-= (len
- utf8_width(hostleadin
));
1915 /* get rid of port */
1916 if(avail
< need
&& strlen(port
) > 0){
1917 need
-= strlen(port
);
1922 len
= utf8_width(pwleadin
);
1923 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
1924 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("PASSWORD"));
1925 need
-= (len
- utf8_width(pwleadin
));
1933 * Reduce space for hostname. Best we can do is 6 chars
1936 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
1937 len
= strlen(hostname
);
1938 strncpy(hostname
+reduce_to
-3, "...", 4);
1939 need
-= (len
- strlen(hostname
));
1941 if(avail
< need
&& strlen(insecure
) > 0){
1942 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
1944 insecure
[strlen(insecure
)-4] = ')';
1945 insecure
[strlen(insecure
)-3] = '\0';
1948 need
-= utf8_width(insecure
);
1954 len
= utf8_width(logleadin
);
1955 strncpy(logleadin
, " ", sizeof(logleadin
));
1956 logleadin
[sizeof(logleadin
)-1] = '\0';
1957 need
-= (len
- utf8_width(logleadin
));
1960 reduce_to
= (need
- avail
< strlen(defubuf
) - 6) ? (strlen(defubuf
)-(need
-avail
)) : 0;
1962 strncpy(defubuf
+reduce_to
-3, "...", 4);
1971 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s%s",
1972 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
, pwleadin
);
1973 prompt
[sizeof(prompt
)-1] = '\0';
1978 mm_login_alt_cue(mb
);
1980 save_dont_use
= ps_global
->dont_use_init_cmds
;
1981 ps_global
->dont_use_init_cmds
= 1;
1982 flags
= F_ON(F_QUELL_ASTERISKS
, ps_global
) ? OE_PASSWD_NOAST
: OE_PASSWD
;
1983 flags
|= OE_KEEP_TRAILING_SPACE
;
1987 tmpp
= fs_get(NETMAXPASSWD
*sizeof(char));
1988 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, &tmpp
, NETMAXPASSWD
, 0, 1,
1989 &preserve_password
);
1990 strncpy(tmp
, tmpp
, sizeof(tmp
));
1991 tmp
[sizeof(tmp
)-1] = '\0';
1992 if(tmpp
) fs_give((void **)&tmpp
);
1994 #else /* !_WINDOWS */
1995 rc
= optionally_enter(tmp
, q_line
, 0, NETMAXPASSWD
,
1996 prompt
, NULL
, help
, &flags
);
1997 #endif /* !_WINDOWS */
1998 if(rc
!= 1) *pwd
= cpystr(tmp
);
1999 ps_global
->dont_use_init_cmds
= save_dont_use
;
2002 help
= help
== NO_HELP
? h_oe_passwd
: NO_HELP
;
2010 if(rc
== 1 || !tmp
[0]) {
2011 ps_global
->user_says_cancel
= (rc
== 1);
2013 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2014 ps_global
->in_init_seq
= save_in_init
;
2021 /* remember the password for next time */
2022 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
2023 imap_set_passwd(&mm_login_list
, *pwd
,
2024 altuserforcache
? altuserforcache
: user
, hostlist
,
2025 (mb
->sslflag
||mb
->tlsflag
), 0, 0);
2026 #ifdef LOCAL_PASSWD_CACHE
2027 /* if requested, remember it on disk for next session */
2028 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
2029 set_passfile_passwd(ps_global
->pinerc
, *pwd
,
2030 altuserforcache
? altuserforcache
: user
, hostlist
,
2031 (mb
->sslflag
||mb
->tlsflag
),
2032 (preserve_password
== -1 ? 0
2033 : (preserve_password
== 0 ? 2 :1)));
2034 #endif /* LOCAL_PASSWD_CACHE */
2036 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2041 mm_login_alt_cue(NETMBX
*mb
)
2043 if(ps_global
->ttyo
){
2046 lastc
= pico_set_colors(ps_global
->VAR_TITLE_FORE_COLOR
,
2047 ps_global
->VAR_TITLE_BACK_COLOR
,
2050 mark_titlebar_dirty();
2051 PutLine0(0, ps_global
->ttyo
->screen_cols
- 1,
2052 (mb
->sslflag
||mb
->tlsflag
) ? "+" : " ");
2055 (void)pico_set_colorp(lastc
, PSC_NONE
);
2056 free_color_pair(&lastc
);
2064 /*----------------------------------------------------------------------
2065 Receive notification of an error writing to disk
2067 Args: stream -- The stream the error occurred on
2068 errcode -- The system error code (errno)
2069 serious -- Flag indicating error is serious (mail may be lost)
2071 Result: If error is non serious, the stream is marked as having an error
2072 and deletes are disallowed until error clears
2073 If error is serious this goes modal, allowing the user to retry
2074 or get a shell escape to fix the condition. When the condition is
2075 serious it means that mail existing in the mailbox will be lost
2076 if Pine exits without writing, so we try to induce the user to
2077 fix the error, go get someone that can fix the error, or whatever
2078 and don't provide an easy way out.
2081 mm_diskerror (MAILSTREAM
*stream
, long int errcode
, long int serious
)
2085 static ESCKEY_S de_opts
[] = {
2086 {'r', 'r', "R", "Retry"},
2087 {'f', 'f', "F", "FileBrowser"},
2088 {'s', 's', "S", "ShellPrompt"},
2091 #define DE_COLS (ps_global->ttyo->screen_cols)
2092 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2094 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2096 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2097 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2099 "The reported error number is %s. The last reported mail error was:"
2100 static char *de_msg
[] = {
2101 "Please try to correct the error preventing Alpine from saving your",
2102 "mail folder. For example if the disk is out of space try removing",
2103 "unneeded files. You might also contact your system administrator.",
2105 "Both Alpine's File Browser and an option to enter the system's",
2106 "command prompt are offered to aid in fixing the problem. When",
2107 "you believe the problem is resolved, choose the \"Retry\" option.",
2108 "Be aware that messages may be lost or this folder left in an",
2109 "inaccessible condition if you exit or kill Alpine before the problem",
2112 static char *de_shell_msg
[] = {
2113 "\n\nPlease attempt to correct the error preventing saving of the",
2114 "mail folder. If you do not know how to correct the problem, contact",
2115 "your system administrator. To return to Alpine, type \"exit\".",
2119 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2120 DE_FOLDER(stream
), errcode
, serious
? "" : "not "));
2121 dprint((0, "***** message: \"%s\"\n\n",
2122 ps_global
->last_error
? ps_global
->last_error
: "?"));
2125 sp_set_io_error_on_stream(stream
, 1);
2130 /* replace pine's body display with screen full of explanatory text */
2132 PutLine1(2, MAX((DE_COLS
- sizeof(DE_STR1
)
2133 - strlen(DE_FOLDER(stream
)))/2, 0),
2134 DE_STR1
, DE_FOLDER(stream
));
2136 PutLine1(3, 4, DE_STR2
, long2string(errcode
));
2138 PutLine0(4, 0, " \"");
2139 removing_leading_white_space(ps_global
->last_error
);
2140 for(i
= 4, p
= ps_global
->last_error
; *p
&& i
< DE_LINE
; ){
2141 for(s
= NULL
, q
= p
; *q
&& q
- p
< DE_COLS
- 16; q
++)
2142 if(isspace((unsigned char)*q
))
2153 PutLine0(i
, 0, " ");
2154 while(*p
&& isspace((unsigned char)*p
))
2165 for(j
= ++i
; i
< DE_LINE
&& de_msg
[i
-j
]; i
++){
2167 PutLine0(i
, 0, " ");
2168 Write_to_screen(de_msg
[i
-j
]);
2174 switch(radio_buttons(DE_PMT
, -FOOTER_ROWS(ps_global
), de_opts
,
2175 'r', 0, NO_HELP
, RB_FLUSH_IN
| RB_NO_NEWMAIL
)){
2176 case 'r' : /* Retry! */
2177 ps_global
->mangled_screen
= 1;
2180 case 'f' : /* File Browser */
2182 char full_filename
[MAXPATH
+1], filename
[MAXPATH
+1];
2185 build_path(full_filename
, ps_global
->home_dir
, filename
,
2186 sizeof(full_filename
));
2187 file_lister("DISK ERROR", full_filename
, sizeof(full_filename
),
2188 filename
, sizeof(filename
), FALSE
, FB_SAVE
);
2195 end_keyboard(ps_global
? F_ON(F_USE_FK
,ps_global
) : 0);
2196 end_tty_driver(ps_global
);
2197 for(i
= 0; de_shell_msg
[i
]; i
++)
2198 puts(de_shell_msg
[i
]);
2201 * Don't use our piping mechanism to spawn a subshell here
2202 * since it will the server (thus reentering c-client).
2209 init_tty_driver(ps_global
);
2210 init_keyboard(F_ON(F_USE_FK
,ps_global
));
2214 if(ps_global
->redrawer
)
2215 (*ps_global
->redrawer
)();
2221 pine_tcptimeout_noscreen(long int elapsed
, long int sincelast
, char *host
)
2230 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2231 snprintf(pmt
, sizeof(pmt
),
2232 _("No reply in %s seconds from server %s. Break connection"),
2233 long2string(elapsed
), host
);
2234 pmt
[sizeof(pmt
)-1] = '\0';
2235 if(want_to(pmt
, 'n', 'n', NO_HELP
, WT_FLUSH_IN
) == 'y'){
2236 ps_global
->user_says_cancel
= 1;
2241 ps_global
->tcptimeout
= 0;
2247 * -------------------------------------------------------------
2248 * These are declared in pith/imap.h as mandatory to implement.
2249 * -------------------------------------------------------------
2254 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2257 pine_tcptimeout(long int elapsed
, long int sincelast
, char *host
)
2259 long rv
= 1L; /* keep trying by default */
2262 ps_global
->tcptimeout
= 1;
2264 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2265 long2string(elapsed
), host
));
2274 if(ps_global
->noshow_timeout
)
2277 if(ps_global
->can_interrupt
2278 && ps_global
->close_connection_timeout
> 0L
2279 && elapsed
>= (long)ps_global
->tcp_query_timeout
2280 && elapsed
>= (long)ps_global
->close_connection_timeout
){
2281 ps_global
->can_interrupt
= 0; /* do not return here */
2282 ps_global
->read_bail
= 0;
2283 ps_global
->user_says_cancel
= 1;
2287 if(!ps_global
->ttyo
)
2288 return(pine_tcptimeout_noscreen(elapsed
, sincelast
, host
));
2293 * Prompt after a minute (since by then things are probably really bad)
2294 * A prompt timeout means "keep trying"...
2296 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2299 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2300 if((clear_inverse
= !InverseState()) != 0)
2305 PutLine2(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
), 0,
2306 _("No reply in %s seconds from server %s. Break connection?"),
2307 long2string(elapsed
), host
);
2312 if(ps_global
->read_bail
|| ch
== 'y' || ch
== 'Y'){
2313 ps_global
->read_bail
= 0;
2314 ps_global
->user_says_cancel
= 1;
2321 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2324 if(rv
== 1L){ /* just warn 'em something's up */
2325 q_status_message2(SM_ORDER
, 0, 0,
2326 _("No reply in %s seconds from server %s. Still Waiting..."),
2327 long2string(elapsed
), host
);
2328 flush_status_messages(0); /* make sure it's seen */
2331 mark_status_dirty(); /* make sure it gets cleared */
2333 resume_busy_cue((rv
== 1) ? 3 : 0);
2334 ps_global
->tcptimeout
= 0;
2339 QUOTALIST
*pine_quotalist_copy (QUOTALIST
*pquota
)
2341 QUOTALIST
*cquota
= NULL
;
2344 cquota
= mail_newquotalist();
2345 if (pquota
->name
&& *pquota
->name
)
2346 cquota
->name
= cpystr(pquota
->name
);
2347 cquota
->usage
= pquota
->usage
;
2348 cquota
->limit
= pquota
->limit
;
2350 cquota
->next
= pine_quotalist_copy(pquota
->next
);
2356 /* c-client callback to handle quota */
2359 pine_parse_quota (MAILSTREAM
*stream
, unsigned char *msg
, QUOTALIST
*pquota
)
2361 ps_global
->quota
= pine_quotalist_copy (pquota
);
2365 * C-client callback to handle SSL/TLS certificate validation failures
2367 * Returning 0 means error becomes fatal
2368 * Non-zero means certificate problem is ignored and SSL session is
2371 * We remember the answer and won't re-ask for subsequent open attempts to
2372 * the same hostname.
2375 pine_sslcertquery(char *reason
, char *host
, char *cert
)
2378 char *unknown
= "<unknown>";
2381 int ok_novalidate
= 0, warned
= 0;
2383 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2384 host
? host
: "?", reason
? reason
: "?",
2385 cert
? cert
: "?"));
2387 hostlist
.name
= host
? host
: "";
2388 hostlist
.next
= NULL
;
2391 * See if we've been asked about this host before.
2393 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2394 /* we were asked before, did we say Yes? */
2400 "sslcertificatequery: approved automatically\n"));
2404 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2407 if(ps_global
->ttyo
){
2409 STORE_S
*in_store
, *out_store
;
2411 HANDLE_S
*handles
= NULL
;
2412 int the_answer
= 'n';
2414 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
2415 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2418 so_puts(in_store
, "<HTML><P>");
2419 so_puts(in_store
, _("There was a failure validating the SSL/TLS certificate for the server"));
2421 so_puts(in_store
, "<P><CENTER>");
2422 so_puts(in_store
, host
? host
: unknown
);
2423 so_puts(in_store
, "</CENTER>");
2425 so_puts(in_store
, "<P>");
2426 so_puts(in_store
, _("The reason for the failure was"));
2428 /* squirrel away details */
2430 fs_give((void **)&details_host
);
2432 fs_give((void **)&details_reason
);
2434 fs_give((void **)&details_cert
);
2436 details_host
= cpystr(host
? host
: unknown
);
2437 details_reason
= cpystr(reason
? reason
: unknown
);
2438 details_cert
= cpystr(cert
? cert
: unknown
);
2440 so_puts(in_store
, "<P><CENTER>");
2441 snprintf(tmp
, sizeof(tmp
), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2442 reason
? reason
: unknown
);
2443 tmp
[sizeof(tmp
)-1] = '\0';
2445 so_puts(in_store
, tmp
);
2446 so_puts(in_store
, "</CENTER>");
2448 so_puts(in_store
, "<P>");
2449 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."));
2451 so_puts(in_store
, "<P>");
2452 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"));
2454 so_puts(in_store
, "<P><CENTER>");
2455 so_puts(in_store
, "/novalidate-cert");
2456 so_puts(in_store
, "</CENTER>");
2458 so_puts(in_store
, "<P>");
2459 so_puts(in_store
, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2461 so_puts(in_store
, "<P><CENTER>");
2462 so_puts(in_store
, host
? host
: unknown
);
2463 so_puts(in_store
, "</CENTER>");
2465 so_puts(in_store
, "<P>");
2466 so_puts(in_store
, _("in your configuration, replace those characters with"));
2468 so_puts(in_store
, "<P><CENTER>");
2469 so_puts(in_store
, host
? host
: unknown
);
2470 so_puts(in_store
, "/novalidate-cert");
2471 so_puts(in_store
, "</CENTER>");
2473 so_puts(in_store
, "<P>");
2474 so_puts(in_store
, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2476 so_seek(in_store
, 0L, 0);
2477 init_handles(&handles
);
2479 gf_link_filter(gf_html2plain
,
2480 gf_html2plain_opt(NULL
,
2481 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
2482 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
2483 gf_set_so_readc(&gc
, in_store
);
2484 gf_set_so_writec(&pc
, out_store
);
2486 gf_clear_so_writec(out_store
);
2487 gf_clear_so_readc(in_store
);
2489 memset(&sargs
, 0, sizeof(SCROLL_S
));
2490 sargs
.text
.handles
= handles
;
2491 sargs
.text
.text
= so_text(out_store
);
2492 sargs
.text
.src
= CharStar
;
2493 sargs
.text
.desc
= _("help text");
2494 sargs
.bar
.title
= _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2495 sargs
.proc
.tool
= answer_cert_failure
;
2496 sargs
.proc
.data
.p
= (void *)&the_answer
;
2497 sargs
.keys
.menu
= &ans_certquery_keymenu
;
2498 /* don't want to re-enter c-client */
2499 sargs
.quell_newmail
= 1;
2500 setbitmap(sargs
.keys
.bitmap
);
2501 sargs
.help
.text
= h_tls_validation_failure
;
2502 sargs
.help
.title
= _("HELP FOR CERT VALIDATION FAILURE");
2506 if(the_answer
== 'y')
2508 else if(the_answer
== 'n')
2509 ps_global
->user_says_cancel
= 1;
2511 ps_global
->mangled_screen
= 1;
2512 ps_global
->painted_body_on_startup
= 0;
2513 ps_global
->painted_footer_on_startup
= 0;
2515 so_give(&out_store
);
2516 free_handles(&handles
);
2518 fs_give((void **)&details_host
);
2520 fs_give((void **)&details_reason
);
2522 fs_give((void **)&details_cert
);
2526 * If screen hasn't been initialized yet, use want_to.
2529 memset((void *)tmp
, 0, sizeof(tmp
));
2531 reason
? reason
: _("SSL/TLS certificate validation failure"),
2533 tmp
[sizeof(tmp
)-1] = '\0';
2534 strncat(tmp
, _(": Continue anyway "), sizeof(tmp
)-strlen(tmp
)-1);
2536 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y')
2541 q_status_message1(SM_ORDER
, 1, 3, _("Open of %s cancelled"),
2542 host
? host
: unknown
);
2544 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, rv
? 1 : 0, 0);
2546 dprint((5, "sslcertificatequery: %s\n",
2547 rv
? "approved" : "rejected"));
2554 pine_newsrcquery(MAILSTREAM
*stream
, char *mulname
, char *name
)
2556 char buf
[MAILTMPLEN
];
2558 if((can_access(mulname
, ACCESS_EXISTS
) == 0)
2559 || !(can_access(name
, ACCESS_EXISTS
) == 0))
2562 snprintf(buf
, sizeof(buf
),
2563 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2565 strlen(last_cmpnt(name
)) > 15 ? "..." : "");
2566 buf
[sizeof(buf
)-1] = '\0';
2567 if(want_to(buf
, 'n', 'n', NO_HELP
, WT_NORM
) == 'y')
2568 rename_file(name
, mulname
);
2574 url_local_certdetails(char *url
)
2576 if(!struncmp(url
, "x-alpine-cert:", 14)){
2581 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
2582 q_status_message(SM_ORDER
| SM_DING
, 7, 10,
2583 _("Error allocating space for details."));
2587 so_puts(store
, _("Host given by user:\n\n "));
2588 so_puts(store
, details_host
);
2589 so_puts(store
, _("\n\nReason for failure:\n\n "));
2590 so_puts(store
, details_reason
);
2591 so_puts(store
, _("\n\nCertificate being verified:\n\n"));
2592 folded
= fold(details_cert
, ps_global
->ttyo
->screen_cols
, ps_global
->ttyo
->screen_cols
, " ", " ", FLD_NONE
);
2593 so_puts(store
, folded
);
2594 fs_give((void **)&folded
);
2595 so_puts(store
, "\n");
2597 memset(&sargs
, 0, sizeof(SCROLL_S
));
2598 sargs
.text
.text
= so_text(store
);
2599 sargs
.text
.src
= CharStar
;
2600 sargs
.text
.desc
= _("Details");
2601 sargs
.bar
.title
= _("CERT VALIDATION DETAILS");
2602 sargs
.help
.text
= NO_HELP
;
2603 sargs
.help
.title
= NULL
;
2604 sargs
.quell_newmail
= 1;
2605 sargs
.help
.text
= h_tls_failure_details
;
2606 sargs
.help
.title
= _("HELP FOR CERT VALIDATION DETAILS");
2610 so_give(&store
); /* free resources associated with store */
2611 ps_global
->mangled_screen
= 1;
2620 * C-client callback to handle SSL/TLS certificate validation failures
2623 pine_sslfailure(char *host
, char *reason
, long unsigned int flags
)
2627 int the_answer
= 'n', indent
, len
, cols
;
2628 char buf
[500], buf2
[500];
2630 char *hst
= host
? host
: "<unknown>";
2631 char *rsn
= reason
? reason
: "<unknown>";
2632 char *notls
= "/notls";
2634 int ok_novalidate
= 0, warned
= 0;
2637 dprint((1, "sslfailure: host=%s reason=%s\n",
2641 if(flags
& NET_SILENT
)
2644 hostlist
.name
= host
? host
: "";
2645 hostlist
.next
= NULL
;
2648 * See if we've been told about this host before.
2650 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2651 /* we were told already */
2653 snprintf(buf
, sizeof(buf
), _("SSL/TLS failure for %s: %s"), hst
, rsn
);
2654 buf
[sizeof(buf
)-1] = '\0';
2660 cols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
2663 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2666 strncpy(buf
, _("There was an SSL/TLS failure for the server"), sizeof(buf
));
2667 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2668 so_puts(store
, folded
);
2669 fs_give((void **)&folded
);
2670 so_puts(store
, "\n");
2672 if((len
=strlen(hst
)) <= cols
){
2673 if((indent
=((cols
-len
)/2)) > 0)
2674 so_puts(store
, repeat_char(indent
, SPACE
));
2676 so_puts(store
, hst
);
2677 so_puts(store
, "\n");
2680 strncpy(buf
, hst
, sizeof(buf
));
2681 buf
[sizeof(buf
)-1] = '\0';
2682 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2683 so_puts(store
, folded
);
2684 fs_give((void **)&folded
);
2687 so_puts(store
, "\n");
2689 strncpy(buf
, _("The reason for the failure was"), sizeof(buf
));
2690 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2691 so_puts(store
, folded
);
2692 fs_give((void **)&folded
);
2693 so_puts(store
, "\n");
2695 if((len
=strlen(rsn
)) <= cols
){
2696 if((indent
=((cols
-len
)/2)) > 0)
2697 so_puts(store
, repeat_char(indent
, SPACE
));
2699 so_puts(store
, rsn
);
2700 so_puts(store
, "\n");
2703 strncpy(buf
, rsn
, sizeof(buf
));
2704 buf
[sizeof(buf
)-1] = '\0';
2705 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2706 so_puts(store
, folded
);
2707 fs_give((void **)&folded
);
2710 so_puts(store
, "\n");
2712 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
));
2713 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2714 so_puts(store
, folded
);
2715 fs_give((void **)&folded
);
2716 so_puts(store
, "\n");
2718 if((len
=strlen(notls
)) <= cols
){
2719 if((indent
=((cols
-len
)/2)) > 0)
2720 so_puts(store
, repeat_char(indent
, SPACE
));
2722 so_puts(store
, notls
);
2723 so_puts(store
, "\n");
2726 strncpy(buf
, notls
, sizeof(buf
));
2727 buf
[sizeof(buf
)-1] = '\0';
2728 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2729 so_puts(store
, folded
);
2730 fs_give((void **)&folded
);
2733 so_puts(store
, "\n");
2735 strncpy(buf
, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2737 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2738 so_puts(store
, folded
);
2739 fs_give((void **)&folded
);
2740 so_puts(store
, "\n");
2742 if((len
=strlen(hst
)) <= cols
){
2743 if((indent
=((cols
-len
)/2)) > 0)
2744 so_puts(store
, repeat_char(indent
, SPACE
));
2746 so_puts(store
, hst
);
2747 so_puts(store
, "\n");
2750 strncpy(buf
, hst
, sizeof(buf
));
2751 buf
[sizeof(buf
)-1] = '\0';
2752 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2753 so_puts(store
, folded
);
2754 fs_give((void **)&folded
);
2757 so_puts(store
, "\n");
2759 strncpy(buf
, _("in your configuration, replace those characters with"),
2761 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2762 so_puts(store
, folded
);
2763 fs_give((void **)&folded
);
2764 so_puts(store
, "\n");
2766 snprintf(buf2
, sizeof(buf2
), "%s%s", hst
, notls
);
2767 buf2
[sizeof(buf2
)-1] = '\0';
2768 if((len
=strlen(buf2
)) <= cols
){
2769 if((indent
=((cols
-len
)/2)) > 0)
2770 so_puts(store
, repeat_char(indent
, SPACE
));
2772 so_puts(store
, buf2
);
2773 so_puts(store
, "\n");
2776 strncpy(buf
, buf2
, sizeof(buf
));
2777 buf
[sizeof(buf
)-1] = '\0';
2778 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2779 so_puts(store
, folded
);
2780 fs_give((void **)&folded
);
2783 so_puts(store
, "\n");
2785 if(ps_global
->ttyo
){
2786 strncpy(buf
, _("Type RETURN to continue."), sizeof(buf
));
2787 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2788 so_puts(store
, folded
);
2789 fs_give((void **)&folded
);
2792 memset(&sargs
, 0, sizeof(SCROLL_S
));
2793 sargs
.text
.text
= so_text(store
);
2794 sargs
.text
.src
= CharStar
;
2795 sargs
.text
.desc
= _("help text");
2796 sargs
.bar
.title
= _("SSL/TLS FAILURE");
2797 sargs
.proc
.tool
= answer_cert_failure
;
2798 sargs
.proc
.data
.p
= (void *)&the_answer
;
2799 sargs
.keys
.menu
= &ans_certfail_keymenu
;
2800 setbitmap(sargs
.keys
.bitmap
);
2801 /* don't want to re-enter c-client */
2802 sargs
.quell_newmail
= 1;
2803 sargs
.help
.text
= h_tls_failure
;
2804 sargs
.help
.title
= _("HELP FOR TLS/SSL FAILURE");
2815 * The screen isn't initialized yet, which should mean that this
2816 * is the result of a -p argument. Display_args_err knows how to deal
2817 * with the uninitialized screen, so we mess with the data to get it
2818 * in shape for display_args_err. This is pretty hacky.
2821 so_seek(store
, 0L, 0); /* rewind */
2822 /* count the lines */
2823 while(so_readc(&c
, store
))
2827 qp
= q
= (char **)fs_get((cnt
+1) * sizeof(char *));
2828 memset(q
, 0, (cnt
+1) * sizeof(char *));
2830 so_seek(store
, 0L, 0); /* rewind */
2832 while(so_readc(&c
, store
)){
2835 *qp
++ = cpystr(buf
);
2842 display_args_err(NULL
, q
, 0);
2843 free_list_array(&q
);
2846 ps_global
->mangled_screen
= 1;
2847 ps_global
->painted_body_on_startup
= 0;
2848 ps_global
->painted_footer_on_startup
= 0;
2851 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, ok_novalidate
, 1);
2856 answer_cert_failure(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2860 ps_global
->next_screen
= SCREEN_FUN_NULL
;
2864 *(int *)(sparms
->proc
.data
.p
) = 'y';
2868 *(int *)(sparms
->proc
.data
.p
) = 'n';
2872 alpine_panic("Unexpected command in answer_cert_failure");
2881 oauth2_auth_answer(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2886 /* TRANSLATORS: user needs to input an access code from the server */
2887 char *accesscodelabel
= _("Copy and Paste Access Code");
2888 char token
[MAILTMPLEN
], prompt
[MAILTMPLEN
];
2890 ps_global
->next_screen
= SCREEN_FUN_NULL
;
2895 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
2896 flags
= OE_APPEND_CURRENT
;
2897 sprintf(prompt
, "%s: ", accesscodelabel
);
2899 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
2900 prompt
, NULL
, NO_HELP
, &flags
);
2901 } while (rc
!= 0 && rc
!= 1);
2902 user
.code
= rc
== 0 ? cpystr(token
) : NULL
;
2904 rv
= rc
== 1 ? 0 : 1;
2913 alpine_panic("Unexpected command in oauth2_auth_answer");
2916 *(AUTH_CODE_S
*) sparms
->proc
.data
.p
= user
;
2921 /*----------------------------------------------------------------------
2922 This can be used to prevent the flickering of the check_cue char
2923 caused by numerous (5000+) fetches by c-client. Right now, the only
2924 practical use found is newsgroup subsciption.
2926 check_cue_display will check if this global is set, and won't clear
2927 the check_cue_char if set.
2930 set_read_predicted(int i
)
2932 ps_global
->read_predicted
= i
==1;
2934 if(!i
&& F_ON(F_SHOW_DELAY_CUE
, ps_global
))
2935 check_cue_display(" ");
2940 /*----------------------------------------------------------------------
2941 Exported method to retrieve logged in user name associated with stream
2943 Args: host -- host to find associated login name with.
2948 pine_block_notify(int reason
, void *data
)
2951 case BLOCK_SENSITIVE
: /* sensitive code, disallow alarms */
2954 case BLOCK_NONSENSITIVE
: /* non-sensitive code, allow alarms */
2957 case BLOCK_TCPWRITE
: /* blocked on TCP write */
2958 case BLOCK_FILELOCK
: /* blocked on file locking */
2960 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
2961 check_cue_display(">");
2963 mswin_setcursor(MSWIN_CURSOR_BUSY
);
2967 case BLOCK_DNSLOOKUP
: /* blocked on DNS lookup */
2968 case BLOCK_TCPOPEN
: /* blocked on TCP open */
2969 case BLOCK_TCPREAD
: /* blocked on TCP read */
2970 case BLOCK_TCPCLOSE
: /* blocked on TCP close */
2972 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
2973 check_cue_display("<");
2975 mswin_setcursor(MSWIN_CURSOR_BUSY
);
2980 case BLOCK_NONE
: /* not blocked */
2982 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
2983 check_cue_display(" ");
2994 mm_expunged_current(long unsigned int rawno
)
2996 /* expunged something we're viewing? */
2997 if(!ps_global
->expunge_in_progress
2998 && (mn_is_cur(ps_global
->msgmap
, mn_raw2m(ps_global
->msgmap
, (long) rawno
))
2999 && (ps_global
->prev_screen
== mail_view_screen
3000 || ps_global
->prev_screen
== attachment_screen
))){
3001 ps_global
->next_screen
= mail_index_screen
;
3002 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3003 "Message you were viewing is gone!");
3011 * Specific functions to support caching username/passwd/host
3012 * triples on disk for use from one session to the next...
3015 #define FIRSTCH 0x20
3017 #define TABSZ (LASTCH - FIRSTCH + 1)
3019 static int xlate_key
;
3023 * xlate_in() - xlate_in the given character
3031 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3032 eti
+= (c
- FIRSTCH
);
3033 eti
-= (eti
>= 2*TABSZ
) ? 2*TABSZ
: (eti
>= TABSZ
) ? TABSZ
: 0;
3034 return((xlate_key
= eti
) + FIRSTCH
);
3042 * xlate_out() - xlate_out the given character
3050 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3051 xch
= c
- (dti
= xlate_key
);
3052 xch
+= (xch
< FIRSTCH
-TABSZ
) ? 2*TABSZ
: (xch
< FIRSTCH
) ? TABSZ
: 0;
3053 dti
= (xch
- FIRSTCH
) + dti
;
3054 dti
-= (dti
>= 2*TABSZ
) ? 2*TABSZ
: (dti
>= TABSZ
) ? TABSZ
: 0;
3061 #endif /* PASSFILE */
3064 #ifdef LOCAL_PASSWD_CACHE
3066 int cache_method_was_setup (char *pinerc
)
3070 char tmp
[MAILTMPLEN
];
3072 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb")))
3074 if(fp
!= NULL
) fclose(fp
);
3075 #endif /* PASSFILE */
3080 line_get(char *tmp
, size_t len
, char **textp
)
3086 || (s
= strchr(*textp
, '\n')) == NULL
3087 || (s
- *textp
) > len
- 1)
3094 snprintf(tmp
, len
, "%s\n", *textp
);
3101 typedef struct pwd_s
{
3111 #define SAME_VALUE(X, Y) ((((X) == NULL && (Y) == NULL) \
3112 || ((X) && (Y) && !strcmp((X), (Y)))) ? 1 : 0)
3116 * Passfile lines are
3118 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3120 * In pine4.40 and before there was no orig_hostname, and there still isn't
3121 * if it is the same as hostname.
3124 * Use Windows credentials. The TargetName of the credential is
3125 * UWash_Alpine_.partnumber-totalparts_<hostname:port>\tuser\taltflag
3126 * and the blob consists of
3127 * passwd\torighost (if different from host)
3129 * We don't use anything fancy we just copy out all the credentials which
3130 * begin with TNAME and put them into our cache, so we don't lookup based
3131 * on the TargetName or anything like that. That was so we could re-use
3132 * the existing code and so that orighost data could be easily used.
3135 read_passfile(pinerc
, l
)
3141 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3144 char *tmp
, *blob
, *target
= NULL
;
3148 unsigned long m
, n
, p
, loc
;
3150 if(using_passfile
== 0)
3151 return(using_passfile
);
3154 if (init_wincred_funcs() != 1) {
3156 return(using_passfile
);
3160 dprint((9, "read_passfile\n"));
3164 /* this code exists because the XOAUTH2 support makes us save
3165 * access tokens as if they were passwords. However, some servers
3166 * produce extremely long access-tokens that do not fit in the credentials
3167 * and therefore need to be split into several entries.
3169 * The plan is the following:
3170 * step 1: Read and save all the information in the credentials
3171 * step 2: flatten the information into one line
3172 * step 3: process that line.
3174 if (g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)) {
3175 pwd
= fs_get((count
+ 1)*sizeof(ALPINE_PWD_S
*));
3176 memset((void *)pwd
, 0, (count
+ 1)*sizeof(ALPINE_PWD_S
*));
3178 /* this is step 1 */
3179 for (k
= 0; k
< count
; k
++) { /* go through each credential */
3180 target
= lptstr_to_utf8(pcred
[k
]->TargetName
);
3181 tmp
= srchstr(target
, TNAME
);
3183 tmp
+= strlen(TNAME
);
3186 m
= strtoul(tmp
, &tmp
, 10);
3189 n
= strtol(tmp
, &tmp
, 10);
3190 if (*tmp
== '_') tmp
++;
3197 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3198 for (i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++) {
3199 for (ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3200 ; /* find end of data */
3203 tmp
[i
++] = '\0'; /* tie off data */
3206 /* improve this. We are trying to find where we saved
3207 * this data, and in general this is fast if there is
3208 * only a few data, which is not unreasonable, but probably
3209 * can be done better.
3211 for (loc
= 0; pwd
[loc
]
3212 && !(SAME_VALUE(ui
[0], pwd
[loc
]->host
)
3213 && SAME_VALUE(ui
[1], pwd
[loc
]->user
)
3214 && SAME_VALUE(ui
[2], pwd
[loc
]->sflags
)); loc
++);
3216 if (pwd
[loc
] == NULL
) {
3217 pwd
[loc
] = fs_get(sizeof(ALPINE_PWD_S
));
3218 memset((void *) pwd
[loc
], 0, sizeof(ALPINE_PWD_S
));
3219 pwd
[loc
]->blobarray
= fs_get((n
+ 1) * sizeof(char*));
3220 memset((void *) pwd
[loc
]->blobarray
, 0, (n
+ 1) * sizeof(char*));
3223 if (pwd
[loc
]->host
== NULL
)
3224 pwd
[loc
]->host
= ui
[0] ? cpystr(ui
[0]) : NULL
;
3225 if (pwd
[loc
]->user
== NULL
)
3226 pwd
[loc
]->user
= ui
[1] ? cpystr(ui
[1]) : NULL
;
3227 if (pwd
[loc
]->sflags
== NULL
)
3228 pwd
[loc
]->sflags
= ui
[2] ? cpystr(ui
[2]) : NULL
;
3229 blob
= (char *) pcred
[k
]->CredentialBlob
;
3230 pwd
[loc
]->blobarray
[m
- 1] = blob
? cpystr(blob
) : NULL
;
3232 if (target
) fs_give((void**)&target
);
3235 for (k
= 0; k
< count
; k
++) {
3237 for (i
= 0, j
= 0; pwd
[k
]->blobarray
[j
]; j
++)
3238 i
+= strlen(pwd
[k
]->blobarray
[j
]);
3239 pwd
[k
]->blob
= fs_get(i
+ 1);
3240 pwd
[k
]->blob
[0] = '\0';
3241 for (j
= 0; pwd
[k
]->blobarray
[j
]; j
++) {
3242 strcat(pwd
[k
]->blob
, pwd
[k
]->blobarray
[j
]);
3243 fs_give((void **) &pwd
[k
]->blobarray
[j
]);
3245 fs_give((void **) pwd
[k
]->blobarray
);
3247 else k
= count
; /* we are done with this step! */
3250 for (k
= 0; k
< count
; k
++) {
3251 if (pwd
[k
] && pwd
[k
]->blob
) {
3252 blob
= pwd
[k
]->blob
;
3253 for (i
= 0, j
= 3; blob
[i
] && j
< 5; j
++) {
3254 for (ui
[j
] = &blob
[i
]; blob
[i
] && blob
[i
] != '\t'; i
++)
3255 ; /* find end of data */
3258 blob
[i
++] = '\0'; /* tie off data */
3260 if (pwd
[k
]->passwd
== NULL
)
3261 pwd
[k
]->passwd
= ui
[3] ? cpystr(ui
[3]) : NULL
;
3262 if (pwd
[k
]->orighost
== NULL
)
3263 pwd
[k
]->orighost
= ui
[4] ? cpystr(ui
[4]) : NULL
;
3264 fs_give((void **) &pwd
[k
]->blob
);
3267 /* now process all lines, and free memory */
3268 for (k
= 0; k
< count
&& pwd
[k
] != NULL
; k
++){
3269 if (pwd
[k
]->passwd
&& pwd
[k
]->host
&& pwd
[k
]->user
) { /* valid field? */
3270 STRLIST_S hostlist
[2];
3273 tmp
= pwd
[k
]->sflags
? strchr(pwd
[k
]->sflags
, PWDAUTHSEP
) : NULL
;
3274 flags
= pwd
[k
]->sflags
? atoi(tmp
? ++tmp
: pwd
[k
]->sflags
) : 0;
3275 hostlist
[0].name
= pwd
[k
]->host
;
3276 if (pwd
[k
]->orighost
) {
3277 hostlist
[0].next
= &hostlist
[1];
3278 hostlist
[1].name
= pwd
[k
]->orighost
;
3279 hostlist
[1].next
= NULL
;
3282 hostlist
[0].next
= NULL
;
3284 imap_set_passwd(l
, pwd
[k
]->passwd
, pwd
[k
]->user
, hostlist
, flags
& 0x01, 0, 0);
3286 if (pwd
[k
]->passwd
) fs_give((void **) &pwd
[k
]->passwd
);
3287 if (pwd
[k
]->user
) fs_give((void **) &pwd
[k
]->user
);
3288 if (pwd
[k
]->host
) fs_give((void **) &pwd
[k
]->host
);
3289 if (pwd
[k
]->sflags
) fs_give((void **) &pwd
[k
]->sflags
);
3290 if (pwd
[k
]->orighost
) fs_give((void **) &pwd
[k
]->orighost
);
3291 fs_give((void **) &pwd
[k
]);
3293 g_CredFree((PVOID
)pcred
);
3295 fs_give((void **) pwd
);
3299 # else /* old windows */
3306 char target
[MAILTMPLEN
];
3307 char *tmp
, *host
, *user
, *sflags
, *passwd
, *orighost
;
3310 SecKeychainAttributeList attrList
;
3311 SecKeychainSearchRef searchRef
= NULL
;
3312 SecKeychainAttribute attrs
[] = {
3313 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
3316 if(using_passfile
== 0)
3317 return(using_passfile
);
3319 dprint((9, "read_passfile\n"));
3322 /* search for only our items in the keychain */
3324 attrList
.attr
= attrs
;
3327 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
3328 kSecGenericPasswordItemClass
,
3331 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3333 SecKeychainItemRef itemRef
= NULL
;
3334 SecKeychainAttributeInfo info
;
3335 SecKeychainAttributeList
*attrList
= NULL
;
3338 char *blobcopy
= NULL
; /* NULL terminated copy */
3340 UInt32 tags
[] = {kSecAccountItemAttr
,
3341 kSecServiceItemAttr
};
3342 UInt32 formats
[] = {0,0};
3344 dprint((10, "read_passfile: searchRef not NULL\n"));
3347 info
.format
= formats
;
3350 * Go through each item we found and put it
3353 while(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
)) && itemRef
){
3354 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3355 rc
= SecKeychainItemCopyAttributesAndData(itemRef
,
3360 if(rc
== 0 && attrList
){
3361 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList
->count
));
3363 blobcopy
= (char *) fs_get((blength
+ 1) * sizeof(char));
3364 strncpy(blobcopy
, (char *) blob
, blength
);
3365 blobcopy
[blength
] = '\0';
3368 * I'm not real clear on how this works. It seems to be
3369 * necessary to combine the attributes from two passes
3370 * (attrList->count == 2) in order to get the full set
3371 * of attributes we inserted into the keychain in the
3372 * first place. So, we reset host...orighost outside of
3373 * the following for loop, not inside.
3375 host
= user
= sflags
= passwd
= orighost
= NULL
;
3376 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3378 for(k
= 0; k
< attrList
->count
; k
++){
3380 if(attrList
->attr
[k
].length
){
3382 (char *) attrList
->attr
[k
].data
,
3383 MIN(attrList
->attr
[k
].length
,sizeof(target
)));
3384 target
[MIN(attrList
->attr
[k
].length
,sizeof(target
)-1)] = '\0';
3388 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
3389 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3390 ; /* find end of data */
3393 tmp
[i
++] = '\0'; /* tie off data */
3405 for(i
= 0, j
= 3; blobcopy
[i
] && j
< 5; j
++){
3406 for(ui
[j
] = &blobcopy
[i
]; blobcopy
[i
] && blobcopy
[i
] != '\t'; i
++)
3407 ; /* find end of data */
3410 blobcopy
[i
++] = '\0'; /* tie off data */
3419 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
:""));
3422 if(passwd
&& host
&& user
){ /* valid field? */
3423 STRLIST_S hostlist
[2];
3426 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
3427 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
3428 hostlist
[0].name
= host
;
3430 hostlist
[0].next
= &hostlist
[1];
3431 hostlist
[1].name
= orighost
;
3432 hostlist
[1].next
= NULL
;
3435 hostlist
[0].next
= NULL
;
3438 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
3442 fs_give((void **) & blobcopy
);
3444 SecKeychainItemFreeAttributesAndData(attrList
, (void *) blob
);
3448 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc
));
3455 CFRelease(searchRef
);
3459 dprint((10, "read_passfile: searchRef NULL\n"));
3464 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc
));
3467 return(using_passfile
);
3469 #else /* PASSFILE */
3471 char tmp
[MAILTMPLEN
], *ui
[5];
3472 int i
, j
, n
, rv
= 0;
3474 char *tmptext
= NULL
;
3477 char tmp2
[MAILTMPLEN
];
3478 char *text
= NULL
, *text2
= NULL
;
3483 if(using_passfile
== 0)
3484 return(using_passfile
);
3486 dprint((9, "read_passfile\n"));
3488 /* if there's no password to read, bag it!! */
3489 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb"))){
3491 return(using_passfile
);
3495 if(our_stat(tmp
, &sbuf
) == 0)
3497 fp
= our_fopen(tmp
, "rb"); /* reopen to read data */
3499 /* the next call initializes the key/certificate pair used to
3500 * encrypt and decrypt a password file. The details of how this is
3501 * done is in the file pith/smime.c. During this setup we might call
3502 * smime_init(), but no matter what happens we must call smime_deinit()
3503 * there. The reason why this is so is because we can not assume that
3504 * the .pinerc file has been read by this time, so this code might not
3505 * know about the ps_global->smime structure or any of its components,
3506 * and it shouldn't because it only needs ps_global->pwdcert, so
3507 * do not init smime here, because the .pinerc might not have been
3508 * read and we do not really know where the keys and certificates really
3510 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3511 * it is called for the first time and there are certificates at all,
3512 * or when it is called after the first time and the user refuses to
3513 * create a self-signed certificate. In this situation we will just
3514 * let the user live in an insecure world, but no more passwords will
3515 * be saved in the password file, and only those found there will be used.
3518 fgets(tmp2
, sizeof(tmp2
), fp
);
3520 if(strcmp(tmp2
, "-----BEGIN PKCS7-----\n")){
3521 /* there is an already existing password file, that is not encrypted
3522 * and there is no key to encrypt it yet, go again through setup_pwdcert
3523 * and encrypt it now.
3525 if(tmp2
[0]){ /* not empty, UNencrypted password file */
3526 if(ps_global
->pwdcert
== NULL
)
3527 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3528 if((rv
== 0 || rv
== -5) && ps_global
->pwdcert
== NULL
)
3529 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3530 if(ps_global
->pwdcert
== NULL
){
3531 q_status_message(SM_ORDER
, 3, 3,
3532 " Failed to create private key. Using UNencrypted Password file. ");
3537 q_status_message(SM_ORDER
, 3, 3,
3538 " Failed to unlock private key. Using UNencrypted Password file. ");
3539 save_password
= 0; /* do not save more passwords */
3542 if(ps_global
->pwdcert
!= NULL
3543 && encrypt_file((char *)tmp
, NULL
, (PERSONAL_CERT
*)ps_global
->pwdcert
))
3548 if(ps_global
->pwdcert
== NULL
)
3549 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3554 * if password file is encrypted we attempt to decrypt. We ask the
3555 * user for the password to unlock the password file. If the user
3556 * enters the password and it unlocks the file, use it and keep saving
3557 * passwords in it. If the user enters the wrong passwords and does
3558 * not unlock it, we will not see that here, but in decrypt_file, so
3559 * the only other possibility is that the user cancels. In that case
3560 * we will see i == -1. In that case, we will let the user attempt
3561 * manual login to the server they want to login, but passwords will
3562 * not be saved so that the password file will not be saved
3563 * unencrypted and rewritten again.
3566 text
= text2
= decrypt_file((char *)tmp
, &i
, (PERSONAL_CERT
*)ps_global
->pwdcert
);
3567 len
= text2
? strlen(text2
) : 0;
3569 case -2: using_passfile
= 0;
3572 case 1 : save_password
= 1;
3576 case -1: save_password
= 0;
3585 if(using_passfile
== 0){
3587 if(text
) fs_give((void **)&text
);
3589 return using_passfile
;
3593 tmptext
= fs_get(len
+ 1);
3595 for(n
= 0; encrypted
? line_get(tmptext
, len
+ 1, &text2
)
3596 : (fgets(tmptext
, len
+1, fp
) != NULL
); n
++){
3598 for(n
= 0; fgets(tmptext
, len
+1, fp
); n
++){
3600 /*** do any necessary DEcryption here ***/
3602 for(i
= 0; tmptext
[i
]; i
++)
3603 tmptext
[i
] = xlate_out(tmptext
[i
]);
3605 if(i
&& tmptext
[i
-1] == '\n')
3606 tmptext
[i
-1] = '\0'; /* blast '\n' */
3608 dprint((10, "read_passfile: %s\n", tmp
? tmp
: "?"));
3609 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3610 for(i
= 0, j
= 0; tmptext
[i
] && j
< 5; j
++){
3611 for(ui
[j
] = &tmptext
[i
]; tmptext
[i
] && tmptext
[i
] != '\t'; i
++)
3612 ; /* find end of data */
3615 tmptext
[i
++] = '\0'; /* tie off data */
3618 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3619 if(ui
[0] && ui
[1] && ui
[2]){ /* valid field? */
3620 STRLIST_S hostlist
[2];
3621 char *s
= ui
[3] ? strchr(ui
[3], PWDAUTHSEP
) : NULL
;
3622 int flags
= ui
[3] ? atoi(s
? ++s
: ui
[3]) : 0;
3624 hostlist
[0].name
= ui
[2];
3626 hostlist
[0].next
= &hostlist
[1];
3627 hostlist
[1].name
= ui
[4];
3628 hostlist
[1].next
= NULL
;
3631 hostlist
[0].next
= NULL
;
3634 imap_set_passwd(l
, ui
[0], ui
[1], hostlist
, flags
& 0x01, 0, 0);
3639 if (tmptext
) fs_give((void **) &tmptext
);
3641 if (text
) fs_give((void **)&text
);
3646 #endif /* PASSFILE */
3652 write_passfile(pinerc
, l
)
3656 char *authend
, *authtype
;
3660 char target
[10*MAILTMPLEN
];
3661 char blob
[10 * MAILTMPLEN
], blob2
[10*MAILTMPLEN
], *blobp
;
3662 char part
[MAILTMPLEN
];
3666 if(using_passfile
== 0)
3669 dprint((9, "write_passfile\n"));
3671 for(; l
; l
= l
->next
){
3672 /* determine how many parts to create first */
3673 snprintf(blob
, sizeof(blob
), "%s%s%s",
3674 l
->passwd
? l
->passwd
: "",
3675 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3677 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3678 ? l
->hosts
->next
->name
: "");
3681 for (j
= 1; i
> MAXPWDBUFFERSIZE
; j
++, i
-= PWDBUFFERSIZE
);
3682 authtype
= l
->passwd
;
3683 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3684 if (authend
!= NULL
){
3686 sprintf(blob2
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3687 *authend
= PWDAUTHSEP
;
3690 sprintf(blob2
, "%d", l
->altflag
);
3691 for (k
= 1, i
= strlen(blob
), blobp
= blob
; k
<= j
; k
++) {
3692 snprintf(target
, sizeof(target
), "%s.%d-%d_%s\t%s\t%s",
3694 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3695 l
->user
? l
->user
: "",
3697 ltarget
= utf8_to_lptstr((LPSTR
)target
);
3699 memset((void*)&cred
, 0, sizeof(cred
));
3701 cred
.Type
= CRED_TYPE_GENERIC
;
3702 cred
.TargetName
= ltarget
;
3703 if (i
> MAXPWDBUFFERSIZE
) {
3704 strncpy(part
, blobp
, PWDBUFFERSIZE
);
3705 part
[PWDBUFFERSIZE
] = '\0';
3706 blobp
+= PWDBUFFERSIZE
;
3710 strcpy(part
, blobp
);
3711 cred
.CredentialBlobSize
= strlen(part
) + 1;
3712 cred
.CredentialBlob
= (LPBYTE
)&part
;
3713 cred
.Persist
= CRED_PERSIST_ENTERPRISE
;
3714 g_CredWriteW(&cred
, 0);
3715 fs_give((void**)<arget
);
3719 #endif /* WINCRED > 0 */
3723 char target
[10*MAILTMPLEN
];
3724 char blob
[10*MAILTMPLEN
];
3725 SecKeychainItemRef itemRef
= NULL
;
3727 if(using_passfile
== 0)
3730 dprint((9, "write_passfile\n"));
3732 for(; l
; l
= l
->next
){
3733 authtype
= l
->passwd
;
3734 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3735 if(authend
!= NULL
){
3737 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3738 *authend
= PWDAUTHSEP
;
3741 sprintf(blob
, "%d", l
->altflag
);
3743 snprintf(target
, sizeof(target
), "%s\t%s\t%s",
3744 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3745 l
->user
? l
->user
: "",
3748 snprintf(blob
, sizeof(blob
), "%s%s%s",
3749 l
->passwd
? l
->passwd
: "",
3750 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3752 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3753 ? l
->hosts
->next
->name
: "");
3755 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target
), target
, strlen(TNAME
), TNAME
, strlen(blob
), blob
));
3757 rc
= SecKeychainAddGenericPassword(NULL
,
3758 strlen(target
), target
,
3759 strlen(TNAME
), TNAME
,
3763 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3766 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc
));
3769 if(rc
== errSecDuplicateItem
){
3770 /* fix existing entry */
3771 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3773 if(!(rc
=SecKeychainFindGenericPassword(NULL
,
3774 strlen(target
), target
,
3775 strlen(TNAME
), TNAME
,
3777 &itemRef
)) && itemRef
){
3779 rc
= SecKeychainItemModifyAttributesAndData(itemRef
, NULL
, strlen(blob
), blob
);
3781 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc
));
3785 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc
));
3790 #else /* PASSFILE */
3791 char tmp
[10*MAILTMPLEN
], blob
[10*MAILTMPLEN
];
3795 char *text
= NULL
, tmp2
[10*MAILTMPLEN
];
3799 if(using_passfile
== 0)
3802 dprint((9, "write_passfile\n"));
3804 /* if there's no passfile to read, bag it!! */
3805 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "wb"))){
3811 strncpy(tmp2
, tmp
, sizeof(tmp2
));
3812 tmp2
[sizeof(tmp2
)-1] = '\0';
3815 for(n
= 0; l
; l
= l
->next
, n
++){
3816 authtype
= l
->passwd
;
3817 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3818 if(authend
!= NULL
){
3820 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3821 *authend
= PWDAUTHSEP
;
3824 sprintf(blob
, "%d", l
->altflag
);
3826 /*** do any necessary ENcryption here ***/
3827 snprintf(tmp
, sizeof(tmp
), "%s\t%s\t%s\t%s%s%s\n", l
->passwd
, l
->user
,
3828 l
->hosts
->name
, blob
,
3829 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "",
3830 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
3832 dprint((10, "write_passfile: %s", tmp
? tmp
: "?"));
3834 for(i
= 0; tmp
[i
]; i
++)
3835 tmp
[i
] = xlate_in(tmp
[i
]);
3838 fs_resize((void **)&text
, (len
+ strlen(tmp
) + 1)*sizeof(char));
3840 len
+= strlen(tmp
) + 1;
3841 strncat(text
, tmp
, strlen(tmp
));
3850 i
= 0; /* to quell gcc */
3851 if(ps_global
->pwdcert
== NULL
){
3852 q_status_message(SM_ORDER
, 3, 3, "Attempting to encrypt password file");
3853 i
= setup_pwdcert(&ps_global
->pwdcert
);
3854 if((i
== 0 || i
== -5) && ps_global
->pwdcert
== NULL
)
3855 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3857 if(ps_global
->pwdcert
== NULL
){ /* we tried but failed */
3859 q_status_message1(SM_ORDER
, 3, 3, "Error: no directory %s to save certificates", ps_global
->pwdcertdir
);
3861 q_status_message(SM_ORDER
, 3, 3, "Refusing to write non-encrypted password file");
3863 else if(encrypt_file((char *)tmp2
, text
, ps_global
->pwdcert
) == 0)
3864 q_status_message(SM_ORDER
, 3, 3, "Failed to encrypt password file");
3865 fs_give((void **)&text
); /* do not save this text */
3868 #endif /* PASSFILE */
3871 #endif /* LOCAL_PASSWD_CACHE */
3876 erase_windows_credentials(void)
3878 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3883 if(init_wincred_funcs() != 1)
3887 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
3889 for(k
= 0; k
< count
; k
++)
3890 g_CredDeleteW(pcred
[k
]->TargetName
, CRED_TYPE_GENERIC
, 0);
3892 g_CredFree((PVOID
) pcred
);
3898 ask_erase_credentials(void)
3900 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y'){
3901 erase_windows_credentials();
3902 q_status_message(SM_ORDER
, 3, 3, "All preserved passwords have been erased");
3905 q_status_message(SM_ORDER
, 3, 3, "Previously preserved passwords will not be erased");
3908 #endif /* WINCRED */
3911 #ifdef LOCAL_PASSWD_CACHE
3913 get_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
)
3914 char *pinerc
, **passwd
, *user
;
3915 STRLIST_S
*hostlist
;
3918 return get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, NULL
);
3922 * get_passfile_passwd_auth - return the password contained in the special password
3923 * cache. The file is assumed to be in the same directory
3924 * as the pinerc with the name defined above.
3927 get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, authtype
)
3928 char *pinerc
, **passwd
, *user
;
3929 STRLIST_S
*hostlist
;
3933 dprint((10, "get_passfile_passwd_auth\n"));
3934 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
3935 ? imap_get_passwd_auth(passfile_cache
, passwd
,
3936 user
, hostlist
, altflag
, authtype
)
3941 free_passfile_cache_work(MMLOGIN_S
**pwdcache
)
3943 if(pwdcache
== NULL
|| *pwdcache
== NULL
)
3946 if((*pwdcache
)->user
) fs_give((void **)&(*pwdcache
)->user
);
3947 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
3948 if((*pwdcache
)->hosts
) free_strlist(&(*pwdcache
)->hosts
);
3949 free_passfile_cache_work(&(*pwdcache
)->next
);
3950 fs_give((void **)pwdcache
);
3955 free_passfile_cache(void)
3958 free_passfile_cache_work(&passfile_cache
);
3962 is_using_passfile(void)
3964 return(using_passfile
== 1);
3968 * Just trying to guess the username the user might want to use on this
3969 * host, the user will confirm.
3972 get_passfile_user(pinerc
, hostlist
)
3974 STRLIST_S
*hostlist
;
3976 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
3977 ? imap_get_user(passfile_cache
, hostlist
)
3983 preserve_prompt(char *pinerc
)
3985 return preserve_prompt_auth(pinerc
, NULL
);
3989 preserve_prompt_auth(char *pinerc
, char *authtype
)
3993 #define PROMPT_PWD _("Preserve password for next login")
3994 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3996 * This prompt was going to be able to be turned on and off via a registry
3997 * setting controlled from the config menu. We decided to always use the
3998 * dialog for login, and there the prompt is unobtrusive enough to always
3999 * be in there. As a result, windows should never reach this, but now
4000 * OS X somewhat uses the behavior just described.
4002 if(mswin_store_pass_prompt()
4003 && (want_to(authtype
4004 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4006 'y', 'x', NO_HELP
, WT_NORM
)
4016 #define PROMPT_PWD _("Preserve password for next login")
4017 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4020 if((rc
= macos_store_pass_prompt()) != 0){
4022 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4023 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
4026 macos_set_store_pass_prompt(1);
4027 q_status_message(SM_ORDER
, 4, 4,
4028 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4033 macos_set_store_pass_prompt(0);
4034 q_status_message(SM_ORDER
, 4, 4,
4035 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4040 #else /* PASSFILE */
4041 #define PROMPT_PWD _("Preserve password on DISK for next login")
4042 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
4044 char tmp
[MAILTMPLEN
];
4047 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || our_stat(tmp
, &sbuf
) < 0)
4050 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
4051 return(want_to(authtype
4052 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4053 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
4056 #endif /* PASSFILE */
4059 #endif /* LOCAL_PASSWD_CACHE */
4062 #ifdef APPLEKEYCHAIN
4066 * 1 if store pass prompt is set in the "registry" to on
4068 * -1 if not set to anything
4071 macos_store_pass_prompt(void)
4078 if(storepassprompt
== -1){
4079 if(!(rc
=SecKeychainFindGenericPassword(NULL
, 0, NULL
,
4080 strlen(TNAMEPROMPT
),
4082 (void **) &data
, NULL
))){
4083 val
= (len
== 1 && data
&& data
[0] == '1');
4087 if(storepassprompt
== -1 && !rc
){
4089 storepassprompt
= 1;
4091 storepassprompt
= 0;
4094 return(storepassprompt
);
4099 macos_set_store_pass_prompt(int val
)
4101 storepassprompt
= val
? 1 : 0;
4103 SecKeychainAddGenericPassword(NULL
, 0, NULL
, strlen(TNAMEPROMPT
),
4104 TNAMEPROMPT
, 1, val
? "1" : "0", NULL
);
4109 macos_erase_keychain(void)
4111 SecKeychainAttributeList attrList
;
4112 SecKeychainSearchRef searchRef
= NULL
;
4113 SecKeychainAttribute attrs1
[] = {
4114 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
4116 SecKeychainAttribute attrs2
[] = {
4117 { kSecAccountItemAttr
, strlen(TNAMEPROMPT
), TNAMEPROMPT
}
4120 dprint((9, "macos_erase_keychain\n"));
4123 * Seems like we ought to be able to combine attrs1 and attrs2
4124 * into a single array, but I couldn't get it to work.
4127 /* search for only our items in the keychain */
4129 attrList
.attr
= attrs1
;
4131 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4132 kSecGenericPasswordItemClass
,
4136 SecKeychainItemRef itemRef
= NULL
;
4139 * Go through each item we found and put it
4142 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4143 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4144 SecKeychainItemDelete(itemRef
);
4148 CFRelease(searchRef
);
4153 attrList
.attr
= attrs2
;
4155 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4156 kSecGenericPasswordItemClass
,
4160 SecKeychainItemRef itemRef
= NULL
;
4163 * Go through each item we found and put it
4166 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4167 SecKeychainItemDelete(itemRef
);
4171 CFRelease(searchRef
);
4176 #endif /* APPLEKEYCHAIN */
4178 #ifdef LOCAL_PASSWD_CACHE
4181 set_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
)
4182 char *pinerc
, *passwd
, *user
;
4183 STRLIST_S
*hostlist
;
4184 int altflag
, already_prompted
;
4186 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, NULL
);
4189 * set_passfile_passwd - set the password file entry associated with
4190 * cache. The file is assumed to be in the same directory
4191 * as the pinerc with the name defined above.
4192 * already_prompted: 0 not prompted
4193 * 1 prompted, answered yes
4194 * 2 prompted, answered no
4197 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, authtype
)
4198 char *pinerc
, *passwd
, *user
;
4199 STRLIST_S
*hostlist
;
4200 int altflag
, already_prompted
;
4203 dprint((10, "set_passfile_passwd_auth\n"));
4204 if(((already_prompted
== 0 && preserve_prompt_auth(pinerc
, authtype
))
4205 || already_prompted
== 1)
4206 && !ps_global
->nowrite_password_cache
4207 && (passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))){
4208 imap_set_passwd_auth(&passfile_cache
, passwd
, user
, hostlist
, altflag
, 0, 0, authtype
);
4209 write_passfile(pinerc
, passfile_cache
);
4214 update_passfile_hostlist(pinerc
, user
, hostlist
, altflag
)
4217 STRLIST_S
*hostlist
;
4220 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, NULL
);
4224 * Passfile lines are
4226 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4228 * In pine4.40 and before there was no orig_hostname.
4229 * This routine attempts to repair that.
4232 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, authtype
)
4235 STRLIST_S
*hostlist
;
4241 #else /* !WINCRED */
4243 size_t len
= authtype
? strlen(authtype
) : 0;
4244 size_t offset
= authtype
? 1 : 0;
4246 for(l
= passfile_cache
; l
; l
= l
->next
)
4247 if(imap_same_host_auth(l
->hosts
, hostlist
, authtype
)
4249 && !strcmp(user
, l
->user
+ len
+ offset
)
4250 && l
->altflag
== altflag
){
4254 if(l
&& l
->hosts
&& hostlist
&& !l
->hosts
->next
&& hostlist
->next
4255 && hostlist
->next
->name
4256 && !ps_global
->nowrite_password_cache
){
4257 l
->hosts
->next
= new_strlist_auth(hostlist
->next
->name
, authtype
, PWDAUTHSEP
);
4258 write_passfile(pinerc
, passfile_cache
);
4260 #endif /* !WINCRED */
4263 #endif /* LOCAL_PASSWD_CACHE */
4268 * Load and init the WinCred structure.
4269 * This gives us a way to skip the WinCred code
4270 * if the dll doesn't exist.
4273 init_wincred_funcs(void)
4279 /* Assume the worst. */
4282 hmod
= LoadLibrary(TEXT("advapi32.dll"));
4285 FARPROC fpCredWriteW
;
4286 FARPROC fpCredEnumerateW
;
4287 FARPROC fpCredDeleteW
;
4290 fpCredWriteW
= GetProcAddress(hmod
, "CredWriteW");
4291 fpCredEnumerateW
= GetProcAddress(hmod
, "CredEnumerateW");
4292 fpCredDeleteW
= GetProcAddress(hmod
, "CredDeleteW");
4293 fpCredFree
= GetProcAddress(hmod
, "CredFree");
4295 if(fpCredWriteW
&& fpCredEnumerateW
&& fpCredDeleteW
&& fpCredFree
)
4297 g_CredWriteW
= (CREDWRITEW
*)fpCredWriteW
;
4298 g_CredEnumerateW
= (CREDENUMERATEW
*)fpCredEnumerateW
;
4299 g_CredDeleteW
= (CREDDELETEW
*)fpCredDeleteW
;
4300 g_CredFree
= (CREDFREE
*)fpCredFree
;
4306 mswin_set_erasecreds_callback(ask_erase_credentials
);
4309 return g_CredInited
;
4312 #endif /* WINCRED */