2 * ========================================================================
3 * Copyright 2013-2022 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 (240)
59 #define MAXPWDBUFFERSIZE (2*PWDBUFFERSIZE) /* This number must be less than 512 with some room to TNAME and other extra characters */
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 *);
120 static int using_passfile
= -1;
121 int save_password
= 1;
122 #endif /* LOCAL_PASSWD_CACHE */
126 char xlate_out(char);
127 int line_get(char *, size_t, char **);
128 #endif /* PASSFILE */
131 void ask_erase_credentials(void);
132 int init_wincred_funcs(void);
136 static char *details_cert
, *details_host
, *details_reason
;
138 extern XOAUTH2_INFO_S xoauth_default
[];
139 extern OAUTH2_S alpine_oauth2_list
[];
142 cache_method_message(STORE_S
*in_store
)
144 #ifdef LOCAL_PASSWD_CACHE
145 if(cache_method_was_setup(ps_global
->pinerc
)){
146 so_puts(in_store
, _("</P><P> Once you have authorized Alpine, Alpine will ask you if you want to preserve the Refresh Token and Access Code. If you do "));
147 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 "));
148 so_puts(in_store
, _("alive at the end of this process, and your connection will proceed from there."));
151 q_status_message(SM_ORDER
| SM_DING
, 3, 3, _("Create a password file in order to save the login information"));
152 so_puts(in_store
, _("</P><P> Although your version of Alpine was compiled with password file support, this has not been set up yet. "));
153 so_puts(in_store
, _("as a result, the next time you open this folder, you will go through this process once again.\n\n"));
156 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 "));
157 so_puts(in_store
, _("access token, which means that you will have to repeat this process the next time you login to this server. "));
158 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 "));
159 so_puts(in_store
, _("computer that was compiled with password file support."));
160 #endif /* LOCAL_PASSWD_CACHE */
164 cache_method_message_no_screen (void)
166 #ifdef LOCAL_PASSWD_CACHE
167 if(cache_method_was_setup(ps_global
->pinerc
)){
168 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
169 _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
170 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
172 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
173 "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
174 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
176 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
177 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
178 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
181 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
182 _(" Although your version of Alpine was compiled with password file support, this has not been set up yet. "));
183 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
184 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
185 _("as a result, the next time you open this folder, you will go through this process once again.\n\n"));
186 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
189 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
190 _("Your version of Alpine was not built with password file support, as a result you will not be able to save your "));
191 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
192 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
193 _("access token, which means that you will have to repeat this process the next time you login to this server. "));
194 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
195 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
196 _("You can fix this by either recompiling alpine with password file support or by downloading a version of Alpine to your "));
197 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
198 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
), "%s",
199 _("computer that was compiled with password file support.\n\n"));
200 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
201 #endif /* LOCAL_PASSWD_CACHE */
205 xoauth2_flow_tool(struct pine
*ps
, int cmd
, CONF_S
**cl
, unsigned int flags
)
211 *((*cl
)->d
.xf
.selected
) = (*cl
)->d
.xf
.pat
;
212 rv
= simple_exit_cmd(flags
);
215 rv
= simple_exit_cmd(flags
);
223 ps
->mangled_body
= 1;
229 oauth2_select_flow(char *host
)
231 OAUTH2_S
*oa2list
, *oa2
;
235 dprint((2, "-- oauth2_select_flow()\n"));
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
;
368 dprint((2, "-- oauth2_set_device_info\n"));
369 ps_global
->in_xoauth2_auth
= 1;
372 STORE_S
*in_store
, *out_store
;
374 HANDLE_S
*handles
= NULL
;
375 AUTH_CODE_S user_input
;
377 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
378 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
381 aux_value
.xoauth2
= oa2
;
382 aux_value
.code_success
= 'e';
383 aux_value
.code_failure
= 'e';
384 aux_value
.code_wait
= NO_OP_COMMAND
;
386 so_puts(in_store
, "<HTML><P>");
387 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name
);
388 so_puts(in_store
, tmp
);
389 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name
, method
),
390 so_puts(in_store
, tmp
);
392 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
394 _("</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."),
395 deviceinfo
->verification_uri
, deviceinfo
->verification_uri
, deviceinfo
->user_code
);
396 so_puts(in_store
, tmp
);
399 so_puts(in_store
, "</P><P>");
400 so_puts(in_store
, (char *) deviceinfo
->message
);
402 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
403 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), name
);
404 so_puts(in_store
, tmp
);
405 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
407 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 "));
408 so_puts(in_store
, _("to grant access to Alpine to your data. "));
410 cache_method_message(in_store
);
412 so_puts(in_store
, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit."));
413 so_puts(in_store
, _("</P></HTML>"));
415 so_seek(in_store
, 0L, 0);
416 init_handles(&handles
);
418 gf_link_filter(gf_html2plain
,
419 gf_html2plain_opt(NULL
,
420 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
421 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
422 gf_set_so_readc(&gc
, in_store
);
423 gf_set_so_writec(&pc
, out_store
);
425 gf_clear_so_writec(out_store
);
426 gf_clear_so_readc(in_store
);
428 memset(&sargs
, 0, sizeof(SCROLL_S
));
429 sargs
.text
.handles
= handles
;
430 sargs
.text
.text
= so_text(out_store
);
431 sargs
.text
.src
= CharStar
;
432 sargs
.text
.desc
= _("help text");
433 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
434 sargs
.proc
.tool
= oauth2_auth_answer
;
435 sargs
.proc
.data
.p
= (void *)&user_input
;
436 sargs
.keys
.menu
= &oauth2_device_auth_keymenu
;
437 /* don't want to re-enter c-client */
438 sargs
.quell_newmail
= 1;
439 setbitmap(sargs
.keys
.bitmap
);
440 sargs
.help
.text
= h_oauth2_start_device
;
441 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
442 sargs
.aux_function
= oauth2deviceinfo_get_accesscode
;
443 sargs
.aux_value
= (void *) &aux_value
;
444 sargs
.aux_condition
= oauth2_elapsed_done
;
445 sargs
.decode_aux_rv_value
= oauth2device_decode_reply
;
446 sargs
.aux_rv_value
= (void *) &aux_rv_value
;
449 if(scrolltool(&sargs
) == MC_NO
)
450 ps_global
->user_says_cancel
= 1;
451 ps_global
->mangled_screen
= 1;
452 ps_global
->painted_body_on_startup
= 0;
453 ps_global
->painted_footer_on_startup
= 0;
454 } while (user_input
.answer
!= 'e');
458 free_handles(&handles
);
459 oauth2_elapsed_done(NULL
);
463 * If screen hasn't been initialized yet, use want_to.
467 tmp_20k_buf
[0] = '\0';
468 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
469 _("Authorizing Alpine Access to %s Email Services\n\n"), name
);
470 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
472 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
473 _("Alpine is attempting to log you into your %s account, using the %s method. "), name
, method
),
474 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
476 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
477 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
478 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
479 deviceinfo
->verification_uri
, deviceinfo
->user_code
);
480 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
483 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
484 "%s\n\n", deviceinfo
->message
);
485 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
488 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
489 _("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
);
490 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
492 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
493 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
494 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
496 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
497 "%s", _("to grant access to Alpine to your data.\n\n"));
498 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
500 cache_method_message_no_screen();
502 display_init_err(tmp_20k_buf
, 0);
503 memset((void *)tmp
, 0, sizeof(tmp
));
504 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
505 tmp
[sizeof(tmp
)-1] = '\0';
507 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
511 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
512 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
513 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
515 aux_value
.xoauth2
= oa2
;
516 aux_value
.code_success
= 'y';
517 aux_value
.code_failure
= 'n';
518 aux_value
.code_wait
= 'w';
520 strncpy(tmp
, _("Continue waiting"), sizeof(tmp
));
521 tmp
[sizeof(tmp
)-1] = '\0';
523 if(oauth2_elapsed_done((void *) &aux_value
) == 0)
524 oauth2deviceinfo_get_accesscode((void *) &aux_value
, (void *) &rv
);
525 ch
= oauth2device_decode_reply((void *) &aux_value
, (void *) &rv
);
526 } while (ch
== 'w' || want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y');
527 oauth2_elapsed_done(NULL
);
533 oauth2_get_access_code(unsigned char *url
, char *method
, OAUTH2_S
*oauth2
, int *tryanother
)
535 char tmp
[MAILTMPLEN
];
538 dprint((2, "-- oauth2_get_access_code\n"));
539 ps_global
->in_xoauth2_auth
= 1;
542 STORE_S
*in_store
, *out_store
;
544 HANDLE_S
*handles
= NULL
;
545 AUTH_CODE_S user_input
;
547 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
548 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
551 so_puts(in_store
, "<HTML><BODY><P>");
552 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2
->name
);
553 so_puts(in_store
, tmp
);
554 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
555 so_puts(in_store
, tmp
);
557 if(strucmp((char *) oauth2
->name
, (char *) GMAIL_NAME
) == 0 && strstr(url
, (char *) GMAIL_ID
) != NULL
){
558 so_puts(in_store
, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
559 so_puts(in_store
, _("</P><P> First you must register Alpine with Google and create a client-id and client-secret.</P>"));
560 so_puts(in_store
, _("<UL> "));
561 so_puts(in_store
, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
562 so_puts(in_store
, _("and create a project. The name of the project is not important."));
563 so_puts(in_store
, _("<LI> Go to the OAuth 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."));
564 so_puts(in_store
, _("<LI> This will take you to several screens where you must input the required information. You can always use your email address for developer and contact information. Do not add scopes when you get to the scopes screen and add your email address to the screen to add Test Users."));
565 so_puts(in_store
, _("<LI> Create OAUTH Credentials."));
566 so_puts(in_store
, _("</UL> "));
567 so_puts(in_store
, _("<P> As a result of this process, you will get a client-id and a client-secret."));
568 so_puts(in_store
, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently,"));
569 so_puts(in_store
, _(" then retry login into Gmail's server."));
570 so_puts(in_store
, _(" More detailed and up to date information on how to configure Alpine for Gmail can be found at the following <A href=\"https://alpine.x10host.com/alpine/alpine-info/misc/RegisteringAlpineinGmail.html\">link</A>."));
573 so_puts(in_store
, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
574 so_puts(in_store
,"</P><P>");
575 sprintf(tmp_20k_buf
, _("<A HREF=\"%s\">%s</A>"), url
, url
);
576 so_puts(in_store
, tmp_20k_buf
);
578 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
579 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2
->name
);
580 so_puts(in_store
, tmp
);
581 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
583 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. "));
584 so_puts(in_store
, _(" At the end of this process, you will be given an access code or redirected to a web page."));
585 so_puts(in_store
, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
586 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. "));
588 cache_method_message(in_store
);
591 so_puts(in_store
, _("</P><P> If you do not wish to proceed, cancel by pressing 'E' to exit."));
592 so_puts(in_store
, _("</P></BODY></HTML>"));
594 so_seek(in_store
, 0L, 0);
595 init_handles(&handles
);
597 gf_link_filter(gf_html2plain
,
598 gf_html2plain_opt(NULL
,
599 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
600 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
601 gf_set_so_readc(&gc
, in_store
);
602 gf_set_so_writec(&pc
, out_store
);
604 gf_clear_so_writec(out_store
);
605 gf_clear_so_readc(in_store
);
607 memset(&sargs
, 0, sizeof(SCROLL_S
));
608 sargs
.text
.handles
= handles
;
609 sargs
.text
.text
= so_text(out_store
);
610 sargs
.text
.src
= CharStar
;
611 sargs
.text
.desc
= _("help text");
612 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
613 sargs
.proc
.tool
= oauth2_auth_answer
;
614 sargs
.proc
.data
.p
= (void *)&user_input
;
615 sargs
.keys
.menu
= &oauth2_auth_keymenu
;
616 /* don't want to re-enter c-client */
617 sargs
.quell_newmail
= 1;
618 setbitmap(sargs
.keys
.bitmap
);
619 sargs
.help
.text
= h_oauth2_start
;
620 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
623 if(scrolltool(&sargs
) == MC_NO
)
624 ps_global
->user_says_cancel
= 1;
625 ps_global
->mangled_screen
= 1;
626 ps_global
->painted_body_on_startup
= 0;
627 ps_global
->painted_footer_on_startup
= 0;
628 } while (user_input
.answer
!= 'e');
630 if(!struncmp(user_input
.code
, "http://", 7)
631 || !struncmp(user_input
.code
, "https://", 8)){
633 s
= strstr(user_input
.code
, "code=");
641 else code
= user_input
.code
? cpystr(user_input
.code
) : NULL
;
642 if(user_input
.code
) fs_give((void **) &user_input
.code
);
644 if(code
== NULL
) *tryanother
= 1;
648 free_handles(&handles
);
651 int flags
, rc
, q_line
;
652 /* TRANSLATORS: user needs to input an access code from the server */
653 char *accesscodelabel
= _("Copy and Paste Access Code");
654 char prompt
[MAILTMPLEN
], token
[MAILTMPLEN
];
656 * If screen hasn't been initialized yet, use want_to.
659 tmp_20k_buf
[0] = '\0';
660 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
661 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2
->name
);
662 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
664 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
665 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
666 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
668 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
669 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
670 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
672 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
674 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
676 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
677 _("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
);
678 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
680 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
681 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
682 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
684 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
685 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
686 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
688 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
689 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
690 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
692 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
693 "%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. "));
694 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
696 cache_method_message_no_screen();
698 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
699 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
700 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
702 display_init_err(tmp_20k_buf
, 0);
703 memset((void *)tmp
, 0, sizeof(tmp
));
704 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
705 tmp
[sizeof(tmp
)-1] = '\0';
707 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
708 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
709 flags
= OE_APPEND_CURRENT
;
710 sprintf(prompt
, "%s: ", accesscodelabel
);
712 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
713 prompt
, NULL
, NO_HELP
, &flags
);
714 } while (rc
!= 0 && rc
!= 1);
715 if(!struncmp(token
, "http://", 7)
716 || !struncmp(token
, "https://", 8)){
718 s
= strstr(token
, "code=");
726 else code
= token
[0] ? cpystr(token
) : NULL
;
733 void mm_login_oauth2(NETMBX
*, char *, char *, OAUTH2_S
*, long int, char *, char *);
735 /* The purpose of this function is to report to c-client the values of the
736 * different tokens and codes so that c-client can try to log in the user
737 * to the server. This function DOES NOT attempt to get these values for
738 * the user. That is attempted in the c-client side (as best as it can be
739 * done given our circumstances: no http support, no javascript support,
740 * etc.). This is the best we can do:
742 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
743 * as best as we can. Unloaded means that there is no server known to
744 * connect, no access token, etc. Pretty much nothing is known about
745 * how to get access code, access token, etc. We ask the user to fill
746 * it up for us, if they can. If the user fills it up we save those
749 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
750 * save the information that c-client got for us.
752 * 3. When saving this information we use the password caching facilities,
753 * but we must do it in a different format so that old information and
754 * new information are not mixed. In order to accommodate this for new
755 * authentication methods, we save the information in the same fields,
756 * but this time we modify it slightly, so that old functions fail to
757 * understand the new information and so not modify it nor use it. The
758 * modification is simple: Every piece of information that was saved
759 * before is prepended XOAUTH2\001 to indicate the authentication
760 * method for which it works. The character \001 is a separator. New
761 * Alpine will know how to deal with this, but old versions, will not
762 * strip this prefix from the information and fail to get the
763 * information or modify it when needed. Only new versions of Alpine will
764 * know how to process this information.
765 * new_value = authenticator_method separator old_value
766 * authenticator_method = "XOAUTH2_S"
768 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
769 * authenticator is "XOAUTH2\001imap.gmail.com".
770 * In addition, the password field is not used to encode the password
771 * anymore, it is used to save login information needed in a format that
772 * the caller function chooses, but that must be preceded by the
773 * "authenticator_method separator" code as above.
776 mm_login_oauth2(NETMBX
*mb
, char *user
, char *method
,
777 OAUTH2_S
*login
, long int trial
,
778 char *usethisprompt
, char *altuserforcache
)
780 char *token
, tmp
[MAILTMPLEN
];
781 char prompt
[4*MAILTMPLEN
];
782 char *OldRefreshToken
, *OldAccessToken
;
783 char *NewRefreshToken
, *NewAccessToken
;
784 char *SaveRefreshToken
, *SaveAccessToken
;
785 /* TRANSLATORS: A label for the hostname that the user is logging in on */
786 char *hostlabel
= _("HOST");
787 /* TRANSLATORS: user is logging in as a particular user (a particular
788 login name), this is just labelling that user name. */
789 char *userlabel
= _("USER");
790 STRLIST_S hostlist
[2], hostlist2
[OAUTH2_TOT_EQUIV
+1];
791 int len
, q_line
, flags
, i
, j
;
794 int ChangeAccessToken
, ChangeRefreshToken
, ChangeExpirationTime
;
795 OAUTH2_S
*oa2list
, *oa2
;
797 unsigned long OldExpirationTime
, NewExpirationTime
, SaveExpirationTime
;
798 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
799 int preserve_password
= -1;
802 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
803 trial
, mb
->user
? mb
->user
: "(null)",
804 mb
->service
? mb
->service
: "(null)",
805 mb
->port
? " port=" : "",
806 mb
->port
? comatose(mb
->port
) : "",
807 altuserforcache
? " altuserforcache =" : "",
808 altuserforcache
? altuserforcache
: ""));
810 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
812 save_in_init
= ps_global
->in_init_seq
;
813 ps_global
->in_init_seq
= 0;
814 ps_global
->no_newmail_check_from_optionally_enter
= 1;
816 /* make sure errors are seen */
817 if(ps_global
->ttyo
&& !ps_global
->noshow_error
818 && login
&& (login
->flags
& OA2_OPENSTREAM
))
819 flush_status_messages(0);
821 if(login
&& (login
->flags
& OA2_OPENSTREAM
))
822 login
->flags
|= ~OA2_OPENSTREAM
;
824 token
= NULL
; /* start from scratch */
826 hostlist
[0].name
= mb
->host
;
827 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
828 hostlist
[0].next
= &hostlist
[1];
829 hostlist
[1].name
= mb
->orighost
;
830 hostlist
[1].next
= NULL
;
833 hostlist
[0].next
= NULL
;
835 if(hostlist
[0].name
){
836 dprint((9, "mm_login_oauth2: host=%s\n",
837 hostlist
[0].name
? hostlist
[0].name
: "?"));
838 if(hostlist
[0].next
&& hostlist
[1].name
){
839 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist
[1].name
));
843 if(trial
== 0L && !altuserforcache
){
844 if(*mb
->user
!= '\0')
845 strncpy(user
, mb
->user
, NETMAXUSER
);
847 flags
= OE_APPEND_CURRENT
;
848 sprintf(prompt
, "%s: %s - %s: ", hostlabel
, mb
->orighost
, userlabel
);
849 optionally_enter(user
, q_line
, 0, NETMAXUSER
, prompt
, NULL
, NO_HELP
, &flags
);
851 user
[NETMAXUSER
-1] = '\0';
855 * We check to see if the server we are going to log in to is already
856 * registered. This gives us a list of servers with the same
857 * credentials, so we use the same credentials for all of them.
860 for(registered
= 0, oa2list
= alpine_oauth2_list
;
861 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
863 for(i
= 0; i
< OAUTH2_TOT_EQUIV
864 && oa2list
->host
[i
] != NULL
865 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
866 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
873 x
= oauth2_get_client_info(oa2list
->name
, user
);
874 if(!x
) return; /* user cancelled, let's get out of here */
876 int authorize
= 0, device
= 0;
877 for(oa2list
= alpine_oauth2_list
;
878 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
880 for(i
= 0; i
< OAUTH2_TOT_EQUIV
881 && oa2list
->host
[i
] != NULL
882 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
883 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
884 char *flow
= oa2list
->server_mthd
[0].name
? "Authorize"
885 : (oa2list
->server_mthd
[1].name
? "Device" : NULL
);
886 authorize
+= oa2list
->server_mthd
[0].name
? 1 : 0;
887 device
+= oa2list
->server_mthd
[1].name
? 1 : 0;
888 if(flow
&& !strucmp(x
->flow
, flow
)) break; /* found it */
891 if(!oa2list
|| !oa2list
->host
|| !oa2list
->host
[0]){
892 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
893 _("%s does not support or recognize flow type \"%s\". Use %s%s%s"),
896 authorize
? "\"Authorize\"" : "",
897 authorize
? (device
? " or " : "") : "",
898 device
? "\"Device\"" : "");
899 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
900 q_status_message(SM_ORDER
| SM_DING
, 3, 5, tmp_20k_buf
);
901 free_xoauth2_info(&x
);
905 free_xoauth2_info(&x
);
909 hostlist2
[i
= 0].name
= mb
->host
;
910 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
))
911 hostlist2
[++i
].name
= mb
->orighost
;
913 for(j
= 0; j
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[j
] != NULL
; j
++){
915 for(k
= 0; k
<= i
&& hostlist2
[k
].name
916 && strcmp(hostlist2
[k
].name
, oa2list
->host
[j
]); k
++);
918 hostlist2
[++i
].name
= oa2list
->host
[j
];
920 hostlist2
[i
+1].name
= NULL
;
921 hostlist2
[i
+1].next
= NULL
;
922 for(j
= i
; j
>= 0; j
--)
923 hostlist2
[j
].next
= &hostlist2
[j
+1];
926 if(registered
){ /* redo the app_id, no questions asked */
927 free_id(&ps_global
->id
);
928 ps_global
->id
= set_alpine_id(oa2list
->app_id
? oa2list
->app_id
: PACKAGE_NAME
, PACKAGE_VERSION
);
929 mail_parameters(NULL
, SET_IDPARAMS
, (void *) ps_global
->id
);
933 oa2list
->param
[OA2_State
].value
= login
->param
[OA2_State
].value
;
935 if(login
->cancel_refresh_token
){
936 imap_delete_passwd_auth(&mm_login_list
, user
,
937 registered
? hostlist2
: hostlist
,
938 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
);
939 #ifdef LOCAL_PASSWD_CACHE
940 write_passfile(ps_global
->pinerc
, mm_login_list
);
941 #endif /* LOCAL_PASSWD_CACHE */
945 * We check if we have a refresh token saved somewhere, if so
946 * we use it to get a new access token, otherwise we need to
947 * get an access code so we can get (and save) a refresh token
948 * and use the access token.
950 if(trial
== 0L && !altuserforcache
){
951 /* Search for a refresh token that is already loaded ... */
952 if(imap_get_passwd_auth(mm_login_list
, &token
, user
,
953 registered
? hostlist2
: hostlist
,
954 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
955 dprint((9, "mm_login_oauth2: found a refresh token\n"));
956 ps_global
->no_newmail_check_from_optionally_enter
= 0;
957 ps_global
->in_init_seq
= save_in_init
;
959 #ifdef LOCAL_PASSWD_CACHE
960 /* or see if we have saved one in the local password cache and load it */
961 else if(get_passfile_passwd_auth(ps_global
->pinerc
, &token
,
962 user
, registered
? hostlist2
: hostlist
,
963 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
964 imap_set_passwd_auth(&mm_login_list
, token
, user
,
965 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
966 update_passfile_hostlist_auth(ps_global
->pinerc
, user
, hostlist
,
967 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
);
968 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
969 ps_global
->no_newmail_check_from_optionally_enter
= 0;
970 ps_global
->in_init_seq
= save_in_init
;
972 if(token
&& *token
) preserve_password
= 1; /* resave it, no need to ask */
973 #endif /* LOCAL_PASSWD_CACHE */
975 user
[NETMAXUSER
-1] = '\0';
977 /* The Old* variables is what c_client knows */
978 OldRefreshToken
= login
->cancel_refresh_token
? NULL
: login
->param
[OA2_RefreshToken
].value
;
979 OldAccessToken
= login
->access_token
;
980 OldExpirationTime
= login
->expiration
;
982 /* The New* variables is what Alpine knows */
983 NewRefreshToken
= NewAccessToken
= NULL
;
984 NewExpirationTime
= 0L;
985 ChangeAccessToken
= ChangeRefreshToken
= ChangeExpirationTime
= 0;
987 /* We have done all steps that cancellation requires us by this time */
988 if(login
->cancel_refresh_token
)
989 login
->cancel_refresh_token
= 0;
995 t
= strchr(s
, PWDAUTHSEP
);
997 NewRefreshToken
= cpystr(s
);
1000 NewRefreshToken
= cpystr(s
);
1002 t
= strchr(s
, PWDAUTHSEP
);
1004 NewAccessToken
= cpystr(s
);
1007 NewAccessToken
= cpystr(s
);
1009 NewExpirationTime
= strtol(s
, &s
, 10);
1010 if(NewExpirationTime
<= 0 || NewExpirationTime
<= time(0))
1011 NewExpirationTime
= 0L;
1014 /* check we got good information, and send good information below */
1015 if(NewRefreshToken
&& !*NewRefreshToken
)
1016 fs_give((void **) &NewRefreshToken
);
1017 if(NewAccessToken
&& (NewExpirationTime
== 0L || !*NewAccessToken
))
1018 fs_give((void **) &NewAccessToken
);
1021 if(NewRefreshToken
== NULL
)
1022 login
->first_time
++;
1024 if(login
->first_time
){ /* count how many authorization methods we support */
1027 for(nmethods
= 0, oa2
= alpine_oauth2_list
; oa2
&& oa2
->name
; oa2
++){
1028 for(j
= 0; j
< OAUTH2_TOT_EQUIV
1030 && oa2
->host
[j
] != NULL
1031 && strucmp(oa2
->host
[j
], mb
->orighost
) != 0; j
++);
1032 if(oa2
&& oa2
->host
&& j
< OAUTH2_TOT_EQUIV
&& oa2
->host
[j
]
1033 && ((oa2
->server_mthd
[0].name
&& (oa2
->flags
& OA2_AUTHORIZE
))
1034 || (oa2
->server_mthd
[1].name
&& (oa2
->flags
& OA2_DEVICE
))))
1039 oa2list
= oauth2_select_flow(mb
->orighost
);
1041 if(!oa2list
) registered
= 0;
1044 /* Default to saving what we already had saved */
1046 SaveRefreshToken
= NewRefreshToken
;
1047 SaveAccessToken
= NewAccessToken
;
1048 SaveExpirationTime
= NewExpirationTime
;
1050 /* Translation of the logic below:
1051 * if (c-client has a refresh token
1052 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1053 forget the Alpine refresh token;
1054 make the Alpine Refresh token = c-client refresh token.;
1055 signal that we changed the refresh token;
1056 In this situation we do not need to clear up the Alpine access token. This will
1057 expire, so we can use it until it expires. We can save the c-client refresh token
1058 together with the Alpine Access Token and the expiration date of the Alpine Access
1060 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1061 forget the Alpine refresh token;
1062 if Alpine has an access token, forget it;
1063 reset the expiration time
1064 signal that we changed the refresh token; (because the service expired it)
1068 if(OldRefreshToken
!= NULL
1069 && (NewRefreshToken
== NULL
|| strcmp(OldRefreshToken
, NewRefreshToken
))){
1070 if(NewRefreshToken
) fs_give((void **) &NewRefreshToken
);
1071 NewRefreshToken
= cpystr(OldRefreshToken
);
1072 ChangeRefreshToken
++;
1073 SaveRefreshToken
= OldRefreshToken
;
1074 SaveAccessToken
= NewAccessToken
;
1075 SaveExpirationTime
= NewExpirationTime
;
1076 } else if (OldRefreshToken
== NULL
&& NewRefreshToken
!= NULL
&& trial
> 0){
1077 fs_give((void **) &NewRefreshToken
);
1078 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1079 NewExpirationTime
= 0L;
1080 ChangeRefreshToken
++;
1081 SaveRefreshToken
= NULL
;
1082 SaveAccessToken
= NULL
;
1083 SaveExpirationTime
= 0L;
1086 if(OldAccessToken
!= NULL
1087 && (NewAccessToken
== NULL
|| strcmp(OldAccessToken
, NewAccessToken
))){
1088 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1089 NewAccessToken
= cpystr(OldAccessToken
);
1090 NewAccessToken
= OldAccessToken
;
1091 ChangeAccessToken
++;
1092 NewExpirationTime
= OldExpirationTime
;
1093 SaveRefreshToken
= NewRefreshToken
;
1094 SaveAccessToken
= NewAccessToken
;
1095 SaveExpirationTime
= NewExpirationTime
;
1099 login
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1100 login
->access_token
= SaveAccessToken
;
1101 login
->expiration
= SaveExpirationTime
;
1103 oa2list
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1104 oa2list
->access_token
= SaveAccessToken
;
1105 oa2list
->expiration
= SaveExpirationTime
;
1106 oa2list
->first_time
= login
->first_time
;
1107 oa2list
->cancel_refresh_token
= login
->cancel_refresh_token
;
1108 *login
= *oa2list
; /* load login pointer */
1110 if(token
) fs_give((void **) &token
);
1112 if(!ChangeAccessToken
&& !ChangeRefreshToken
)
1115 /* get ready to save this information. The format will be
1116 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1117 * (spaces added for clarity, \001 is PWDAUTHSEP)
1119 sprintf(tmp
, "%lu", SaveExpirationTime
);
1120 tmp
[sizeof(tmp
) - 1] = '\0';
1121 len
= strlen(SaveRefreshToken
? SaveRefreshToken
: "")
1122 + strlen(SaveAccessToken
? SaveAccessToken
: "")
1124 token
= fs_get(len
+ 1);
1125 sprintf(token
, "%s%c%s%c%lu",
1126 SaveRefreshToken
? SaveRefreshToken
: "", PWDAUTHSEP
,
1127 SaveAccessToken
? SaveAccessToken
: "", PWDAUTHSEP
,
1128 SaveExpirationTime
);
1130 /* remember the access information for next time */
1131 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
1132 imap_set_passwd_auth(&mm_login_list
, token
,
1133 altuserforcache
? altuserforcache
: user
, hostlist
,
1134 (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
1135 #ifdef LOCAL_PASSWD_CACHE
1136 /* if requested, remember it on disk for next session */
1137 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
1138 set_passfile_passwd_auth(ps_global
->pinerc
, token
,
1139 altuserforcache
? altuserforcache
: user
, hostlist
,
1140 (mb
->sslflag
||mb
->tlsflag
),
1141 (preserve_password
== -1 ? 0
1142 : (preserve_password
== 0 ? 2 :1)), OA2NAME
);
1143 #endif /* LOCAL_PASSWD_CACHE */
1144 if (token
) fs_give((void **) &token
);
1145 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1149 set_alpine_id(char *pname
, char *pversion
)
1153 if(!pname
|| !pversion
) return NULL
;
1155 id
= fs_get(sizeof(IDLIST
));
1156 id
->name
= cpystr("name");
1157 id
->value
= cpystr(pname
);
1158 id
->next
= fs_get(sizeof(IDLIST
));
1159 id
->next
->name
= cpystr("version");
1160 id
->next
->value
= cpystr(pversion
);
1161 id
->next
->next
= NULL
;
1166 pine_delete_pwd(NETMBX
*mb
, char *user
)
1168 char hostlist0
[MAILTMPLEN
], hostlist1
[MAILTMPLEN
];
1169 char port
[20], non_def_port
[20];
1170 STRLIST_S hostlist
[2];
1173 /* do not invalidate password on cancel */
1174 if(ps_global
->user_says_cancel
!= 0)
1177 dprint((9, "pine_delete_pwd\n"));
1179 /* setup hostlist */
1180 non_def_port
[0] = '\0';
1181 if(mb
->port
&& mb
->service
&&
1182 (sv
= getservbyname(mb
->service
, "tcp")) &&
1183 (mb
->port
!= ntohs(sv
->s_port
))){
1184 snprintf(non_def_port
, sizeof(non_def_port
), ":%lu", mb
->port
);
1185 non_def_port
[sizeof(non_def_port
)-1] = '\0';
1186 dprint((9, "mm_login: using non-default port=%s\n",
1187 non_def_port
? non_def_port
: "?"));
1191 strncpy(hostlist0
, mb
->host
, sizeof(hostlist0
)-1);
1192 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1193 strncat(hostlist0
, non_def_port
, sizeof(hostlist0
)-strlen(hostlist0
)-1);
1194 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1195 hostlist
[0].name
= hostlist0
;
1196 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1197 strncpy(hostlist1
, mb
->orighost
, sizeof(hostlist1
)-1);
1198 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1199 strncat(hostlist1
, non_def_port
, sizeof(hostlist1
)-strlen(hostlist1
)-1);
1200 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1201 hostlist
[0].next
= &hostlist
[1];
1202 hostlist
[1].name
= hostlist1
;
1203 hostlist
[1].next
= NULL
;
1206 hostlist
[0].next
= NULL
;
1209 hostlist
[0].name
= mb
->host
;
1210 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1211 hostlist
[0].next
= &hostlist
[1];
1212 hostlist
[1].name
= mb
->orighost
;
1213 hostlist
[1].next
= NULL
;
1216 hostlist
[0].next
= NULL
;
1218 imap_delete_passwd(&mm_login_list
, user
, hostlist
, mb
->sslflag
||mb
->tlsflag
);
1219 #ifdef LOCAL_PASSWD_CACHE
1220 write_passfile(ps_global
->pinerc
, mm_login_list
);
1221 #endif /* LOCAL_PASSWD_CACHE */
1224 /*----------------------------------------------------------------------
1225 receive notification from IMAP
1227 Args: stream -- Mail stream message is relevant to
1228 string -- The message text
1229 errflg -- Set if it is a serious error
1231 Result: message displayed in status line
1233 The facility is for general notices, such as connection to server;
1234 server shutting down etc... It is used infrequently.
1235 ----------------------------------------------------------------------*/
1237 mm_notify(MAILSTREAM
*stream
, char *string
, long int errflg
)
1242 now
= time((time_t *)0);
1243 tm_now
= localtime(&now
);
1245 /* be sure to log the message... */
1247 if(ps_global
->debug_imap
|| ps_global
->debugmem
)
1248 dprint((errflg
== TCPDEBUG
|| errflg
== HTTPDEBUG
? 7 : 2,
1249 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1250 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1251 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1252 (!errflg
) ? "babble" :
1253 (errflg
== ERROR
) ? "error" :
1254 (errflg
== WARN
) ? "warning" :
1255 (errflg
== PARSE
) ? "parse" :
1256 (errflg
== TCPDEBUG
) ? "tcp" :
1257 (errflg
== HTTPDEBUG
) ? "http" :
1258 (errflg
== BYE
) ? "bye" : "unknown",
1259 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1260 string
? string
: "?"));
1263 snprintf(ps_global
->last_error
, sizeof(ps_global
->last_error
), "%s : %.*s",
1264 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1265 (int) MIN(MAX_SCREEN_COLS
, sizeof(ps_global
->last_error
)-70),
1267 ps_global
->last_error
[ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1268 : sizeof(ps_global
->last_error
)-1] = '\0';
1271 * Then either set special bits in the pine struct or
1272 * display the message if it's tagged as an "ALERT" or
1273 * its errflg > NIL (i.e., WARN, or ERROR)
1277 * We'd like to sp_mark_stream_dead() here but we can't do that because
1278 * that might call mail_close and we are already in a c-client callback.
1279 * So just set the dead bit and clean it up later.
1281 sp_set_dead_stream(stream
, 1);
1282 else if(!strncmp(string
, "[TRYCREATE]", 11))
1283 ps_global
->try_to_create
= 1;
1284 else if(!strncmp(string
, "[REFERRAL ", 10))
1285 ; /* handled in the imap_referral() callback */
1286 else if(!strncmp(string
, "[ALERT]", 7))
1287 q_status_message2(SM_MODAL
, 3, 3,
1288 _("Alert received while accessing \"%s\": %s"),
1289 (stream
&& stream
->mailbox
)
1290 ? stream
->mailbox
: "-no folder-",
1291 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf
+10000),
1292 SIZEOF_20KBUF
-10000, string
));
1293 else if(!strncmp(string
, "[UNSEEN ", 8)){
1297 for(p
= string
+ 8; isdigit(*p
); p
++)
1298 n
= (n
* 10) + (*p
- '0');
1300 sp_set_first_unseen(stream
, n
);
1302 else if(!strncmp(string
, "[READ-ONLY]", 11)
1303 && !(stream
&& stream
->mailbox
&& IS_NEWS(stream
)))
1304 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, "%s : %s",
1305 (stream
&& stream
->mailbox
)
1306 ? stream
->mailbox
: "-no folder-",
1308 else if((errflg
&& errflg
!= BYE
&& errflg
!= PARSE
)
1309 && !ps_global
->noshow_error
1311 && (ps_global
->noshow_warn
|| (stream
&& stream
->unhealthy
))))
1312 q_status_message(SM_ORDER
| ((errflg
== ERROR
) ? SM_DING
: 0),
1313 3, 6, ps_global
->last_error
);
1317 /*----------------------------------------------------------------------
1318 Queue imap log message for display in the message line
1320 Args: string -- The message
1321 errflg -- flag set to 1 if pertains to an error
1323 Result: Message queued for display
1325 The c-client/imap reports most of it's status and errors here
1328 mm_log(char *string
, long int errflg
)
1330 char message
[sizeof(ps_global
->c_client_error
)];
1332 int was_capitalized
;
1333 static char saw_kerberos_init_warning
;
1337 now
= time((time_t *)0);
1338 tm_now
= localtime(&now
);
1340 dprint((((errflg
== TCPDEBUG
) && ps_global
->debug_tcp
) ? debug
:
1341 (errflg
== TCPDEBUG
) ? 10 :
1342 ((errflg
== HTTPDEBUG
) && ps_global
->debug_http
) ? debug
:
1343 (errflg
== HTTPDEBUG
) ? 10 : 2,
1344 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1345 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1346 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1347 (!errflg
) ? "babble" :
1348 (errflg
== ERROR
) ? "error" :
1349 (errflg
== WARN
) ? "warning" :
1350 (errflg
== PARSE
) ? "parse" :
1351 (errflg
== TCPDEBUG
) ? "tcp" :
1352 (errflg
== HTTPDEBUG
) ? "http" :
1353 (errflg
== BYE
) ? "bye" : "unknown",
1354 string
? string
: "?"));
1356 if(errflg
== ERROR
&& !strncmp(string
, "[TRYCREATE]", 11)){
1357 ps_global
->try_to_create
= 1;
1360 else if(ps_global
->try_to_create
1361 || !strncmp(string
, "[CLOSED]", 8)
1362 || (sp_dead_stream(ps_global
->mail_stream
) && strstr(string
, "No-op")))
1364 * Don't display if creating new folder OR
1365 * warning about a dead stream ...
1369 /* if we took too long to authenticate, ignore this error */
1370 if(ps_global
->in_xoauth2_auth
&& strstr(string
, "[CLOSED]"))
1373 strncpy(message
, string
, sizeof(message
));
1374 message
[sizeof(message
) - 1] = '\0';
1376 if(errflg
== WARN
&& srchstr(message
, "try running kinit") != NULL
){
1377 if(saw_kerberos_init_warning
)
1380 saw_kerberos_init_warning
= 1;
1383 /*---- replace all "mailbox" with "folder" ------*/
1384 occurence
= srchstr(message
, "mailbox");
1387 || isspace((unsigned char) *(occurence
+7))
1388 || *(occurence
+7) == ':'){
1389 was_capitalized
= isupper((unsigned char) *occurence
);
1390 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 7, (errflg
== PARSE
? "address" : "folder"));
1392 *occurence
= (errflg
== PARSE
? 'A' : 'F');
1397 occurence
= srchstr(occurence
, "mailbox");
1400 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1401 occurence
= srchstr(message
, "GSSAPI");
1404 || isspace((unsigned char) *(occurence
+6))
1405 || *(occurence
+6) == ':')
1406 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 6, "Kerberos");
1410 occurence
= srchstr(occurence
, "GSSAPI");
1414 ps_global
->mm_log_error
= 1;
1416 if(errflg
== PARSE
|| (errflg
== ERROR
&& ps_global
->noshow_error
))
1417 strncpy(ps_global
->c_client_error
, message
,
1418 sizeof(ps_global
->c_client_error
));
1420 if(ps_global
->noshow_error
1421 || (ps_global
->noshow_warn
&& errflg
== WARN
)
1422 || !(errflg
== ERROR
|| errflg
== WARN
))
1423 return; /* Only care about errors; don't print when asked not to */
1425 /*---- Display the message ------*/
1426 q_status_message((errflg
== ERROR
) ? (SM_ORDER
| SM_DING
) : SM_ORDER
,
1428 strncpy(ps_global
->last_error
, message
, sizeof(ps_global
->last_error
));
1429 ps_global
->last_error
[sizeof(ps_global
->last_error
) - 1] = '\0';
1433 mm_login_method_work(NETMBX
*mb
, char *user
, void *login
, long int trial
,
1434 char *method
, char *usethisprompt
, char *altuserforcache
)
1438 if(strucmp(method
, OA2NAME
) == 0 || strucmp(method
, BEARERNAME
) == 0)
1439 mm_login_oauth2(mb
, user
, method
, (OAUTH2_S
*) login
, trial
, usethisprompt
, altuserforcache
);
1443 mm_login_work(NETMBX
*mb
, char *user
, char **pwd
, long int trial
,
1444 char *usethisprompt
, char *altuserforcache
)
1446 char tmp
[MAILTMPLEN
];
1447 char prompt
[1000], *last
;
1448 char port
[20], non_def_port
[20], insecure
[20];
1449 char defuser
[NETMAXUSER
];
1450 char hostleadin
[80], hostname
[200], defubuf
[200];
1451 char logleadin
[80], pwleadin
[50];
1452 char hostlist0
[MAILTMPLEN
], hostlist1
[MAILTMPLEN
];
1453 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1454 that the password will be sent unencrypted over the network. This is
1455 just a warning message that gets added parenthetically when the user
1456 is asked for a password. */
1457 char *insec
= _(" (INSECURE)");
1458 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1459 after having already failed at least once. */
1460 char *retry
= _("Retrying - ");
1461 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1462 char *hostlabel
= _("HOST");
1463 /* TRANSLATORS: user is logging in as a particular user (a particular
1464 login name), this is just labelling that user name. */
1465 char *userlabel
= _("USER");
1466 STRLIST_S hostlist
[2];
1468 int len
, rc
, q_line
, flags
;
1469 int oespace
, avail
, need
, save_dont_use
;
1472 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1473 int preserve_password
= -1;
1476 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1477 trial
, mb
->user
? mb
->user
: "(null)",
1478 mb
->service
? mb
->service
: "(null)",
1479 mb
->port
? " port=" : "",
1480 mb
->port
? comatose(mb
->port
) : "",
1481 altuserforcache
? " altuserforcache =" : "",
1482 altuserforcache
? altuserforcache
: ""));
1483 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
1485 save_in_init
= ps_global
->in_init_seq
;
1486 ps_global
->in_init_seq
= 0;
1487 ps_global
->no_newmail_check_from_optionally_enter
= 1;
1489 /* make sure errors are seen */
1491 flush_status_messages(0);
1493 /* redo app id in case we are logging in to an IMAP server that supports the IMAP ID extension */
1494 free_id(&ps_global
->id
);
1495 ps_global
->id
= set_alpine_id(PACKAGE_NAME
, PACKAGE_VERSION
);
1496 mail_parameters(NULL
, SET_IDPARAMS
, (void *) ps_global
->id
);
1499 * Add port number to hostname if going through a tunnel or something
1501 non_def_port
[0] = '\0';
1502 if(mb
->port
&& mb
->service
&&
1503 (sv
= getservbyname(mb
->service
, "tcp")) &&
1504 (mb
->port
!= ntohs(sv
->s_port
))){
1505 snprintf(non_def_port
, sizeof(non_def_port
), ":%lu", mb
->port
);
1506 non_def_port
[sizeof(non_def_port
)-1] = '\0';
1507 dprint((9, "mm_login: using non-default port=%s\n", non_def_port
));
1511 * set up host list for sybil servers...
1514 strncpy(hostlist0
, mb
->host
, sizeof(hostlist0
)-1);
1515 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1516 strncat(hostlist0
, non_def_port
, sizeof(hostlist0
)-strlen(hostlist0
)-1);
1517 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1518 hostlist
[0].name
= hostlist0
;
1519 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1520 strncpy(hostlist1
, mb
->orighost
, sizeof(hostlist1
)-1);
1521 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1522 strncat(hostlist1
, non_def_port
, sizeof(hostlist1
)-strlen(hostlist1
)-1);
1523 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1524 hostlist
[0].next
= &hostlist
[1];
1525 hostlist
[1].name
= hostlist1
;
1526 hostlist
[1].next
= NULL
;
1529 hostlist
[0].next
= NULL
;
1532 hostlist
[0].name
= mb
->host
;
1533 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1534 hostlist
[0].next
= &hostlist
[1];
1535 hostlist
[1].name
= mb
->orighost
;
1536 hostlist
[1].next
= NULL
;
1539 hostlist
[0].next
= NULL
;
1542 if(hostlist
[0].name
){
1543 dprint((9, "mm_login: host=%s\n",
1544 hostlist
[0].name
? hostlist
[0].name
: "?"));
1545 if(hostlist
[0].next
&& hostlist
[1].name
){
1546 dprint((9, "mm_login: orighost=%s\n", hostlist
[1].name
));
1551 * Initialize user name with either
1552 * 1) /user= value in the stream being logged into,
1553 * or 2) the user name we're running under.
1555 * Note that VAR_USER_ID is not yet initialized if this login is
1556 * the one to access the remote config file. In that case, the user
1557 * can supply the username in the config file name with /user=.
1559 if(trial
== 0L && !altuserforcache
){
1560 strncpy(user
, (*mb
->user
) ? mb
->user
:
1561 ps_global
->VAR_USER_ID
? ps_global
->VAR_USER_ID
: "",
1563 user
[NETMAXUSER
-1] = '\0';
1565 /* try last working password associated with this host. */
1566 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1567 (mb
->sslflag
||mb
->tlsflag
))){
1568 dprint((9, "mm_login: found a password to try\n"));
1569 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1570 ps_global
->in_init_seq
= save_in_init
;
1574 #ifdef LOCAL_PASSWD_CACHE
1575 /* check to see if there's a password left over from last session */
1576 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1577 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1578 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1579 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1580 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1581 (mb
->sslflag
||mb
->tlsflag
));
1582 dprint((9, "mm_login: found a password in passfile to try\n"));
1583 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1584 ps_global
->in_init_seq
= save_in_init
;
1587 #endif /* LOCAL_PASSWD_CACHE */
1590 * If no explicit user name supplied and we've not logged in
1591 * with our local user name, see if we've visited this
1592 * host before as someone else.
1595 ((last
= imap_get_user(mm_login_list
, hostlist
))
1596 #ifdef LOCAL_PASSWD_CACHE
1598 (last
= get_passfile_user(ps_global
->pinerc
, hostlist
))
1599 #endif /* LOCAL_PASSWD_CACHE */
1601 strncpy(user
, last
, NETMAXUSER
);
1602 user
[NETMAXUSER
-1] = '\0';
1603 dprint((9, "mm_login: found user=%s\n",
1604 user
? user
: "?"));
1606 /* try last working password associated with this host/user. */
1607 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1608 (mb
->sslflag
||mb
->tlsflag
))){
1610 "mm_login: found a password for user=%s to try\n",
1611 user
? user
: "?"));
1612 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1613 ps_global
->in_init_seq
= save_in_init
;
1617 #ifdef LOCAL_PASSWD_CACHE
1618 /* check to see if there's a password left over from last session */
1619 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1620 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1621 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1622 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1623 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1624 (mb
->sslflag
||mb
->tlsflag
));
1626 "mm_login: found a password for user=%s in passfile to try\n",
1627 user
? user
: "?"));
1628 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1629 ps_global
->in_init_seq
= save_in_init
;
1632 #endif /* LOCAL_PASSWD_CACHE */
1635 #if !defined(DOS) && !defined(OS2)
1636 if(!*mb
->user
&& !*user
&&
1637 (last
= (ps_global
->ui
.login
&& ps_global
->ui
.login
[0])
1638 ? ps_global
->ui
.login
: NULL
)
1640 strncpy(user
, last
, NETMAXUSER
);
1641 user
[NETMAXUSER
-1] = '\0';
1642 dprint((9, "mm_login: found user=%s\n",
1643 user
? user
: "?"));
1645 /* try last working password associated with this host. */
1646 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1647 (mb
->sslflag
||mb
->tlsflag
))){
1648 dprint((9, "mm_login:ui: found a password to try\n"));
1649 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1650 ps_global
->in_init_seq
= save_in_init
;
1654 #ifdef LOCAL_PASSWD_CACHE
1655 /* check to see if there's a password left over from last session */
1656 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1657 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1658 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1659 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1660 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1661 (mb
->sslflag
||mb
->tlsflag
));
1662 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1663 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1664 ps_global
->in_init_seq
= save_in_init
;
1667 #endif /* LOCAL_PASSWD_CACHE */
1672 user
[NETMAXUSER
-1] = '\0';
1678 * Even if we have a user now, user gets a chance to change it.
1680 ps_global
->mangled_footer
= 1;
1681 if(!*mb
->user
&& !altuserforcache
){
1686 * Instead of offering user with a value that the user can edit,
1687 * we offer [user] as a default so that the user can type CR to
1688 * use it. Otherwise, the user has to type in whole name.
1690 strncpy(defuser
, user
, sizeof(defuser
)-1);
1691 defuser
[sizeof(defuser
)-1] = '\0';
1695 * Need space for "Retrying - "
1701 * about 15 chars for input
1704 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1705 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1706 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1708 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1709 hostname
[sizeof(hostname
)-1] = '\0';
1712 * Add port number to hostname if going through a tunnel or something
1715 strncpy(port
, non_def_port
, sizeof(port
));
1720 /* if not encrypted and SSL/TLS is supported */
1721 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1722 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1723 strncpy(insecure
, insec
, sizeof(insecure
));
1725 /* TRANSLATORS: user is being asked to type in their login name */
1726 snprintf(logleadin
, sizeof(logleadin
), " %s", _("ENTER LOGIN NAME"));
1728 snprintf(defubuf
, sizeof(defubuf
), "%s%s%s : ", (*defuser
) ? " [" : "",
1729 (*defuser
) ? defuser
: "",
1730 (*defuser
) ? "]" : "");
1731 defubuf
[sizeof(defubuf
)-1] = '\0';
1732 /* space reserved after prompt */
1733 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1735 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1736 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1737 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) + oespace
;
1739 /* If we're retrying cut the hostname back to the first word. */
1740 if(avail
< need
&& trial
> 0){
1743 len
= strlen(hostname
);
1744 if((p
= strchr(hostname
, '.')) != NULL
){
1746 need
-= (len
- strlen(hostname
));
1751 need
-= utf8_width(retry
);
1756 /* reduce length of logleadin */
1757 len
= utf8_width(logleadin
);
1758 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1759 longer version doesn't fit on screen */
1760 snprintf(logleadin
, sizeof(logleadin
), " %s", _("LOGIN"));
1761 need
-= (len
- utf8_width(logleadin
));
1764 /* get two spaces from hostleadin */
1765 len
= utf8_width(hostleadin
);
1766 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
1767 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
1768 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1769 need
-= (len
- utf8_width(hostleadin
));
1771 /* get rid of port */
1772 if(avail
< need
&& strlen(port
) > 0){
1773 need
-= strlen(port
);
1781 * Reduce space for hostname. Best we can do is 6 chars
1784 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
1785 len
= strlen(hostname
);
1786 strncpy(hostname
+reduce_to
-3, "...", 4);
1787 need
-= (len
- strlen(hostname
));
1789 if(avail
< need
&& strlen(insecure
) > 0){
1790 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
1792 insecure
[strlen(insecure
)-4] = ')';
1793 insecure
[strlen(insecure
)-3] = '\0';
1796 need
-= utf8_width(insecure
);
1802 if(strlen(defubuf
) > 3){
1803 len
= strlen(defubuf
);
1804 strncpy(defubuf
, " [..] :", 9);
1805 need
-= (len
- strlen(defubuf
));
1809 strncpy(defubuf
, ":", 2);
1812 * If it still doesn't fit, optionally_enter gets
1813 * to worry about it.
1821 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s",
1822 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
);
1823 prompt
[sizeof(prompt
)-1] = '\0';
1827 mm_login_alt_cue(mb
);
1829 flags
= OE_APPEND_CURRENT
;
1830 save_dont_use
= ps_global
->dont_use_init_cmds
;
1831 ps_global
->dont_use_init_cmds
= 1;
1833 if(!*user
&& *defuser
){
1834 strncpy(user
, defuser
, NETMAXUSER
);
1835 user
[NETMAXUSER
-1] = '\0';
1838 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, pwd
, NETMAXPASSWD
,
1839 #ifdef LOCAL_PASSWD_CACHE
1840 is_using_passfile() ? 1 :
1841 #endif /* LOCAL_PASSWD_CACHE */
1842 0, 0, &preserve_password
);
1843 ps_global
->dont_use_init_cmds
= save_dont_use
;
1844 if(rc
== 0 && *user
&& *pwd
)
1846 #else /* !_WINDOWS */
1847 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
1848 prompt
, NULL
, help
, &flags
);
1849 #endif /* !_WINDOWS */
1850 ps_global
->dont_use_init_cmds
= save_dont_use
;
1853 help
= help
== NO_HELP
? h_oe_login
: NO_HELP
;
1858 if(rc
== 0 && !*user
){
1859 strncpy(user
, defuser
, NETMAXUSER
);
1860 user
[NETMAXUSER
-1] = '\0';
1867 if(rc
== 1 || !user
[0]) {
1868 ps_global
->user_says_cancel
= (rc
== 1);
1873 strncpy(user
, mb
->user
, NETMAXUSER
);
1874 user
[NETMAXUSER
-1] = '\0';
1877 user
[NETMAXUSER
-1] = '\0';
1879 if(!(user
[0] || altuserforcache
)){
1880 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1881 ps_global
->in_init_seq
= save_in_init
;
1886 * Now that we have a user, we can check in the cache again to see
1887 * if there is a password there. Try last working password associated
1888 * with this host and user.
1890 if(trial
== 0L && !*mb
->user
&& !altuserforcache
){
1891 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1892 (mb
->sslflag
||mb
->tlsflag
))){
1893 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1894 ps_global
->in_init_seq
= save_in_init
;
1898 #ifdef LOCAL_PASSWD_CACHE
1899 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1900 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1901 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1902 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1903 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1904 ps_global
->in_init_seq
= save_in_init
;
1907 #endif /* LOCAL_PASSWD_CACHE */
1909 else if(trial
== 0 && altuserforcache
){
1910 if(imap_get_passwd(mm_login_list
, pwd
, altuserforcache
, hostlist
,
1911 (mb
->sslflag
||mb
->tlsflag
))){
1912 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1913 ps_global
->in_init_seq
= save_in_init
;
1917 #ifdef LOCAL_PASSWD_CACHE
1918 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1919 altuserforcache
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1920 imap_set_passwd(&mm_login_list
, *pwd
, altuserforcache
,
1921 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1922 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1923 ps_global
->in_init_seq
= save_in_init
;
1926 #endif /* LOCAL_PASSWD_CACHE */
1930 * Didn't find password in cache or this isn't the first try. Ask user.
1935 * Need space for "Retrying - "
1941 * " ENTER PASSWORD: "
1942 * about 15 chars for input
1945 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1946 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1948 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1949 hostname
[sizeof(hostname
)-1] = '\0';
1952 * Add port number to hostname if going through a tunnel or something
1955 strncpy(port
, non_def_port
, sizeof(port
));
1961 /* if not encrypted and SSL/TLS is supported */
1962 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1963 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1964 strncpy(insecure
, insec
, sizeof(insecure
));
1967 strncpy(logleadin
, usethisprompt
, sizeof(logleadin
));
1968 logleadin
[sizeof(logleadin
)-1] = '\0';
1973 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
1975 strncpy(defubuf
, user
, sizeof(defubuf
)-1);
1976 defubuf
[sizeof(defubuf
)-1] = '\0';
1979 /* TRANSLATORS: user is being asked to type in their password */
1980 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("ENTER PASSWORD"));
1982 /* space reserved after prompt */
1983 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1985 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1986 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1987 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) +
1988 utf8_width(pwleadin
) + oespace
;
1990 if(avail
< need
&& trial
> 0){
1993 len
= strlen(hostname
);
1994 if((p
= strchr(hostname
, '.')) != NULL
){
1996 need
-= (len
- strlen(hostname
));
2001 need
-= utf8_width(retry
);
2007 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
2011 rplstr(pwleadin
, sizeof(pwleadin
), 1, "");
2015 /* get two spaces from hostleadin */
2016 len
= utf8_width(hostleadin
);
2017 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
2018 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
2019 hostleadin
[sizeof(hostleadin
)-1] = '\0';
2020 need
-= (len
- utf8_width(hostleadin
));
2022 /* get rid of port */
2023 if(avail
< need
&& strlen(port
) > 0){
2024 need
-= strlen(port
);
2029 len
= utf8_width(pwleadin
);
2030 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
2031 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("PASSWORD"));
2032 need
-= (len
- utf8_width(pwleadin
));
2040 * Reduce space for hostname. Best we can do is 6 chars
2043 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
2044 len
= strlen(hostname
);
2045 strncpy(hostname
+reduce_to
-3, "...", 4);
2046 need
-= (len
- strlen(hostname
));
2048 if(avail
< need
&& strlen(insecure
) > 0){
2049 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
2051 insecure
[strlen(insecure
)-4] = ')';
2052 insecure
[strlen(insecure
)-3] = '\0';
2055 need
-= utf8_width(insecure
);
2061 len
= utf8_width(logleadin
);
2062 strncpy(logleadin
, " ", sizeof(logleadin
));
2063 logleadin
[sizeof(logleadin
)-1] = '\0';
2064 need
-= (len
- utf8_width(logleadin
));
2067 reduce_to
= (need
- avail
< strlen(defubuf
) - 6) ? (strlen(defubuf
)-(need
-avail
)) : 0;
2069 strncpy(defubuf
+reduce_to
-3, "...", 4);
2078 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s%s",
2079 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
, pwleadin
);
2080 prompt
[sizeof(prompt
)-1] = '\0';
2085 mm_login_alt_cue(mb
);
2087 save_dont_use
= ps_global
->dont_use_init_cmds
;
2088 ps_global
->dont_use_init_cmds
= 1;
2089 flags
= F_ON(F_QUELL_ASTERISKS
, ps_global
) ? OE_PASSWD_NOAST
: OE_PASSWD
;
2090 flags
|= OE_KEEP_TRAILING_SPACE
;
2094 tmpp
= fs_get(NETMAXPASSWD
*sizeof(char));
2095 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, &tmpp
, NETMAXPASSWD
, 0, 1,
2096 &preserve_password
);
2097 strncpy(tmp
, tmpp
, sizeof(tmp
));
2098 tmp
[sizeof(tmp
)-1] = '\0';
2099 if(tmpp
) fs_give((void **)&tmpp
);
2101 #else /* !_WINDOWS */
2102 rc
= optionally_enter(tmp
, q_line
, 0, NETMAXPASSWD
,
2103 prompt
, NULL
, help
, &flags
);
2104 #endif /* !_WINDOWS */
2105 if(rc
!= 1) *pwd
= cpystr(tmp
);
2106 ps_global
->dont_use_init_cmds
= save_dont_use
;
2109 help
= help
== NO_HELP
? h_oe_passwd
: NO_HELP
;
2117 if(rc
== 1 || !tmp
[0]) {
2118 ps_global
->user_says_cancel
= (rc
== 1);
2120 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2121 ps_global
->in_init_seq
= save_in_init
;
2128 /* remember the password for next time */
2129 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
2130 imap_set_passwd(&mm_login_list
, *pwd
,
2131 altuserforcache
? altuserforcache
: user
, hostlist
,
2132 (mb
->sslflag
||mb
->tlsflag
), 0, 0);
2133 #ifdef LOCAL_PASSWD_CACHE
2134 /* if requested, remember it on disk for next session */
2135 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
2136 set_passfile_passwd(ps_global
->pinerc
, *pwd
,
2137 altuserforcache
? altuserforcache
: user
, hostlist
,
2138 (mb
->sslflag
||mb
->tlsflag
),
2139 (preserve_password
== -1 ? 0
2140 : (preserve_password
== 0 ? 2 :1)));
2141 #endif /* LOCAL_PASSWD_CACHE */
2143 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2148 mm_login_alt_cue(NETMBX
*mb
)
2150 if(ps_global
->ttyo
){
2153 lastc
= pico_set_colors(ps_global
->VAR_TITLE_FORE_COLOR
,
2154 ps_global
->VAR_TITLE_BACK_COLOR
,
2157 mark_titlebar_dirty();
2158 PutLine0(0, ps_global
->ttyo
->screen_cols
- 1,
2159 (mb
->sslflag
||mb
->tlsflag
) ? "+" : " ");
2162 (void)pico_set_colorp(lastc
, PSC_NONE
);
2163 free_color_pair(&lastc
);
2171 /*----------------------------------------------------------------------
2172 Receive notification of an error writing to disk
2174 Args: stream -- The stream the error occurred on
2175 errcode -- The system error code (errno)
2176 serious -- Flag indicating error is serious (mail may be lost)
2178 Result: If error is non serious, the stream is marked as having an error
2179 and deletes are disallowed until error clears
2180 If error is serious this goes modal, allowing the user to retry
2181 or get a shell escape to fix the condition. When the condition is
2182 serious it means that mail existing in the mailbox will be lost
2183 if Pine exits without writing, so we try to induce the user to
2184 fix the error, go get someone that can fix the error, or whatever
2185 and don't provide an easy way out.
2188 mm_diskerror (MAILSTREAM
*stream
, long int errcode
, long int serious
)
2192 static ESCKEY_S de_opts
[] = {
2193 {'r', 'r', "R", "Retry"},
2194 {'f', 'f', "F", "FileBrowser"},
2195 {'s', 's', "S", "ShellPrompt"},
2198 #define DE_COLS (ps_global->ttyo->screen_cols)
2199 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2201 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2203 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2204 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2206 "The reported error number is %s. The last reported mail error was:"
2207 static char *de_msg
[] = {
2208 "Please try to correct the error preventing Alpine from saving your",
2209 "mail folder. For example if the disk is out of space try removing",
2210 "unneeded files. You might also contact your system administrator.",
2212 "Both Alpine's File Browser and an option to enter the system's",
2213 "command prompt are offered to aid in fixing the problem. When",
2214 "you believe the problem is resolved, choose the \"Retry\" option.",
2215 "Be aware that messages may be lost or this folder left in an",
2216 "inaccessible condition if you exit or kill Alpine before the problem",
2219 static char *de_shell_msg
[] = {
2220 "\n\nPlease attempt to correct the error preventing saving of the",
2221 "mail folder. If you do not know how to correct the problem, contact",
2222 "your system administrator. To return to Alpine, type \"exit\".",
2226 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2227 DE_FOLDER(stream
), errcode
, serious
? "" : "not "));
2228 dprint((0, "***** message: \"%s\"\n\n",
2229 ps_global
->last_error
? ps_global
->last_error
: "?"));
2232 sp_set_io_error_on_stream(stream
, 1);
2237 /* replace pine's body display with screen full of explanatory text */
2239 PutLine1(2, MAX((DE_COLS
- sizeof(DE_STR1
)
2240 - strlen(DE_FOLDER(stream
)))/2, 0),
2241 DE_STR1
, DE_FOLDER(stream
));
2243 PutLine1(3, 4, DE_STR2
, long2string(errcode
));
2245 PutLine0(4, 0, " \"");
2246 removing_leading_white_space(ps_global
->last_error
);
2247 for(i
= 4, p
= ps_global
->last_error
; *p
&& i
< DE_LINE
; ){
2248 for(s
= NULL
, q
= p
; *q
&& q
- p
< DE_COLS
- 16; q
++)
2249 if(isspace((unsigned char)*q
))
2260 PutLine0(i
, 0, " ");
2261 while(*p
&& isspace((unsigned char)*p
))
2272 for(j
= ++i
; i
< DE_LINE
&& de_msg
[i
-j
]; i
++){
2274 PutLine0(i
, 0, " ");
2275 Write_to_screen(de_msg
[i
-j
]);
2281 switch(radio_buttons(DE_PMT
, -FOOTER_ROWS(ps_global
), de_opts
,
2282 'r', 0, NO_HELP
, RB_FLUSH_IN
| RB_NO_NEWMAIL
)){
2283 case 'r' : /* Retry! */
2284 ps_global
->mangled_screen
= 1;
2287 case 'f' : /* File Browser */
2289 char full_filename
[MAXPATH
+1], filename
[MAXPATH
+1];
2292 build_path(full_filename
, ps_global
->home_dir
, filename
,
2293 sizeof(full_filename
));
2294 file_lister("DISK ERROR", full_filename
, sizeof(full_filename
),
2295 filename
, sizeof(filename
), FALSE
, FB_SAVE
);
2302 end_keyboard(ps_global
? F_ON(F_USE_FK
,ps_global
) : 0);
2303 end_tty_driver(ps_global
);
2304 for(i
= 0; de_shell_msg
[i
]; i
++)
2305 puts(de_shell_msg
[i
]);
2308 * Don't use our piping mechanism to spawn a subshell here
2309 * since it will the server (thus reentering c-client).
2316 init_tty_driver(ps_global
);
2317 init_keyboard(F_ON(F_USE_FK
,ps_global
));
2321 if(ps_global
->redrawer
)
2322 (*ps_global
->redrawer
)();
2328 pine_tcptimeout_noscreen(long int elapsed
, long int sincelast
, char *host
)
2337 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2338 snprintf(pmt
, sizeof(pmt
),
2339 _("No reply in %s seconds from server %s. Break connection"),
2340 long2string(elapsed
), host
);
2341 pmt
[sizeof(pmt
)-1] = '\0';
2342 if(want_to(pmt
, 'n', 'n', NO_HELP
, WT_FLUSH_IN
) == 'y'){
2343 ps_global
->user_says_cancel
= 1;
2348 ps_global
->tcptimeout
= 0;
2354 * -------------------------------------------------------------
2355 * These are declared in pith/imap.h as mandatory to implement.
2356 * -------------------------------------------------------------
2361 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2364 pine_tcptimeout(long int elapsed
, long int sincelast
, char *host
)
2366 long rv
= 1L; /* keep trying by default */
2369 ps_global
->tcptimeout
= 1;
2371 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2372 long2string(elapsed
), host
));
2381 if(ps_global
->noshow_timeout
)
2384 if(ps_global
->can_interrupt
2385 && ps_global
->close_connection_timeout
> 0L
2386 && elapsed
>= (long)ps_global
->tcp_query_timeout
2387 && elapsed
>= (long)ps_global
->close_connection_timeout
){
2388 ps_global
->can_interrupt
= 0; /* do not return here */
2389 ps_global
->read_bail
= 0;
2390 ps_global
->user_says_cancel
= 1;
2394 if(!ps_global
->ttyo
)
2395 return(pine_tcptimeout_noscreen(elapsed
, sincelast
, host
));
2400 * Prompt after a minute (since by then things are probably really bad)
2401 * A prompt timeout means "keep trying"...
2403 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2406 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2407 if((clear_inverse
= !InverseState()) != 0)
2412 PutLine2(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
), 0,
2413 _("No reply in %s seconds from server %s. Break connection?"),
2414 long2string(elapsed
), host
);
2419 if(ps_global
->read_bail
|| ch
== 'y' || ch
== 'Y'){
2420 ps_global
->read_bail
= 0;
2421 ps_global
->user_says_cancel
= 1;
2428 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2431 if(rv
== 1L){ /* just warn 'em something's up */
2432 q_status_message2(SM_ORDER
, 0, 0,
2433 _("No reply in %s seconds from server %s. Still Waiting..."),
2434 long2string(elapsed
), host
);
2435 flush_status_messages(0); /* make sure it's seen */
2438 mark_status_dirty(); /* make sure it gets cleared */
2440 resume_busy_cue((rv
== 1) ? 3 : 0);
2441 ps_global
->tcptimeout
= 0;
2446 QUOTALIST
*pine_quotalist_copy (QUOTALIST
*pquota
)
2448 QUOTALIST
*cquota
= NULL
;
2451 cquota
= mail_newquotalist();
2452 if (pquota
->name
&& *pquota
->name
)
2453 cquota
->name
= cpystr(pquota
->name
);
2454 cquota
->usage
= pquota
->usage
;
2455 cquota
->limit
= pquota
->limit
;
2457 cquota
->next
= pine_quotalist_copy(pquota
->next
);
2463 /* c-client callback to handle quota */
2466 pine_parse_quota (MAILSTREAM
*stream
, unsigned char *msg
, QUOTALIST
*pquota
)
2468 ps_global
->quota
= pine_quotalist_copy (pquota
);
2472 * C-client callback to handle SSL/TLS certificate validation failures
2474 * Returning 0 means error becomes fatal
2475 * Non-zero means certificate problem is ignored and SSL session is
2478 * We remember the answer and won't re-ask for subsequent open attempts to
2479 * the same hostname.
2482 pine_sslcertquery(char *reason
, char *host
, char *cert
)
2485 char *unknown
= "<unknown>";
2488 int ok_novalidate
= 0, warned
= 0;
2490 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2491 host
? host
: "?", reason
? reason
: "?",
2492 cert
? cert
: "?"));
2494 hostlist
.name
= host
? host
: "";
2495 hostlist
.next
= NULL
;
2498 * See if we've been asked about this host before.
2500 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2501 /* we were asked before, did we say Yes? */
2507 "sslcertificatequery: approved automatically\n"));
2511 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2514 if(ps_global
->ttyo
){
2516 STORE_S
*in_store
, *out_store
;
2518 HANDLE_S
*handles
= NULL
;
2519 int the_answer
= 'n';
2521 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
2522 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2525 so_puts(in_store
, "<HTML><P>");
2526 so_puts(in_store
, _("There was a failure validating the SSL/TLS certificate for the server"));
2528 so_puts(in_store
, "<P><CENTER>");
2529 so_puts(in_store
, host
? host
: unknown
);
2530 so_puts(in_store
, "</CENTER>");
2532 so_puts(in_store
, "<P>");
2533 so_puts(in_store
, _("The reason for the failure was"));
2535 /* squirrel away details */
2537 fs_give((void **)&details_host
);
2539 fs_give((void **)&details_reason
);
2541 fs_give((void **)&details_cert
);
2543 details_host
= cpystr(host
? host
: unknown
);
2544 details_reason
= cpystr(reason
? reason
: unknown
);
2545 details_cert
= cpystr(cert
? cert
: unknown
);
2547 so_puts(in_store
, "<P><CENTER>");
2548 snprintf(tmp
, sizeof(tmp
), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2549 reason
? reason
: unknown
);
2550 tmp
[sizeof(tmp
)-1] = '\0';
2552 so_puts(in_store
, tmp
);
2553 so_puts(in_store
, "</CENTER>");
2555 so_puts(in_store
, "<P>");
2556 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."));
2558 so_puts(in_store
, "<P>");
2559 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"));
2561 so_puts(in_store
, "<P><CENTER>");
2562 so_puts(in_store
, "/novalidate-cert");
2563 so_puts(in_store
, "</CENTER>");
2565 so_puts(in_store
, "<P>");
2566 so_puts(in_store
, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2568 so_puts(in_store
, "<P><CENTER>");
2569 so_puts(in_store
, host
? host
: unknown
);
2570 so_puts(in_store
, "</CENTER>");
2572 so_puts(in_store
, "<P>");
2573 so_puts(in_store
, _("in your configuration, replace those characters with"));
2575 so_puts(in_store
, "<P><CENTER>");
2576 so_puts(in_store
, host
? host
: unknown
);
2577 so_puts(in_store
, "/novalidate-cert");
2578 so_puts(in_store
, "</CENTER>");
2580 so_puts(in_store
, "<P>");
2581 so_puts(in_store
, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2583 so_seek(in_store
, 0L, 0);
2584 init_handles(&handles
);
2586 gf_link_filter(gf_html2plain
,
2587 gf_html2plain_opt(NULL
,
2588 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
2589 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
2590 gf_set_so_readc(&gc
, in_store
);
2591 gf_set_so_writec(&pc
, out_store
);
2593 gf_clear_so_writec(out_store
);
2594 gf_clear_so_readc(in_store
);
2596 memset(&sargs
, 0, sizeof(SCROLL_S
));
2597 sargs
.text
.handles
= handles
;
2598 sargs
.text
.text
= so_text(out_store
);
2599 sargs
.text
.src
= CharStar
;
2600 sargs
.text
.desc
= _("help text");
2601 sargs
.bar
.title
= _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2602 sargs
.proc
.tool
= answer_cert_failure
;
2603 sargs
.proc
.data
.p
= (void *)&the_answer
;
2604 sargs
.keys
.menu
= &ans_certquery_keymenu
;
2605 /* don't want to re-enter c-client */
2606 sargs
.quell_newmail
= 1;
2607 setbitmap(sargs
.keys
.bitmap
);
2608 sargs
.help
.text
= h_tls_validation_failure
;
2609 sargs
.help
.title
= _("HELP FOR CERT VALIDATION FAILURE");
2613 if(the_answer
== 'y')
2615 else if(the_answer
== 'n')
2616 ps_global
->user_says_cancel
= 1;
2618 ps_global
->mangled_screen
= 1;
2619 ps_global
->painted_body_on_startup
= 0;
2620 ps_global
->painted_footer_on_startup
= 0;
2622 so_give(&out_store
);
2623 free_handles(&handles
);
2625 fs_give((void **)&details_host
);
2627 fs_give((void **)&details_reason
);
2629 fs_give((void **)&details_cert
);
2633 * If screen hasn't been initialized yet, use want_to.
2636 memset((void *)tmp
, 0, sizeof(tmp
));
2638 reason
? reason
: _("SSL/TLS certificate validation failure"),
2640 tmp
[sizeof(tmp
)-1] = '\0';
2641 strncat(tmp
, _(": Continue anyway "), sizeof(tmp
)-strlen(tmp
)-1);
2643 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y')
2648 q_status_message1(SM_ORDER
, 1, 3, _("Open of %s cancelled"),
2649 host
? host
: unknown
);
2651 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, rv
? 1 : 0, 0);
2653 dprint((5, "sslcertificatequery: %s\n",
2654 rv
? "approved" : "rejected"));
2661 pine_newsrcquery(MAILSTREAM
*stream
, char *mulname
, char *name
)
2663 char buf
[MAILTMPLEN
];
2665 if((can_access(mulname
, ACCESS_EXISTS
) == 0)
2666 || !(can_access(name
, ACCESS_EXISTS
) == 0))
2669 snprintf(buf
, sizeof(buf
),
2670 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2672 strlen(last_cmpnt(name
)) > 15 ? "..." : "");
2673 buf
[sizeof(buf
)-1] = '\0';
2674 if(want_to(buf
, 'n', 'n', NO_HELP
, WT_NORM
) == 'y')
2675 rename_file(name
, mulname
);
2681 url_local_certdetails(char *url
)
2683 if(!struncmp(url
, "x-alpine-cert:", 14)){
2688 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
2689 q_status_message(SM_ORDER
| SM_DING
, 7, 10,
2690 _("Error allocating space for details."));
2694 so_puts(store
, _("Host given by user:\n\n "));
2695 so_puts(store
, details_host
);
2696 so_puts(store
, _("\n\nReason for failure:\n\n "));
2697 so_puts(store
, details_reason
);
2698 so_puts(store
, _("\n\nCertificate being verified:\n\n"));
2699 folded
= fold(details_cert
, ps_global
->ttyo
->screen_cols
, ps_global
->ttyo
->screen_cols
, " ", " ", FLD_NONE
);
2700 so_puts(store
, folded
);
2701 fs_give((void **)&folded
);
2702 so_puts(store
, "\n");
2704 memset(&sargs
, 0, sizeof(SCROLL_S
));
2705 sargs
.text
.text
= so_text(store
);
2706 sargs
.text
.src
= CharStar
;
2707 sargs
.text
.desc
= _("Details");
2708 sargs
.bar
.title
= _("CERT VALIDATION DETAILS");
2709 sargs
.help
.text
= NO_HELP
;
2710 sargs
.help
.title
= NULL
;
2711 sargs
.quell_newmail
= 1;
2712 sargs
.help
.text
= h_tls_failure_details
;
2713 sargs
.help
.title
= _("HELP FOR CERT VALIDATION DETAILS");
2717 so_give(&store
); /* free resources associated with store */
2718 ps_global
->mangled_screen
= 1;
2727 * C-client callback to handle SSL/TLS certificate validation failures
2730 pine_sslfailure(char *host
, char *reason
, long unsigned int flags
)
2734 int the_answer
= 'n', indent
, len
, cols
;
2735 char buf
[500], buf2
[500];
2737 char *hst
= host
? host
: "<unknown>";
2738 char *rsn
= reason
? reason
: "<unknown>";
2739 char *notls
= "/notls";
2741 int ok_novalidate
= 0, warned
= 0;
2744 dprint((1, "sslfailure: host=%s reason=%s\n",
2748 if(flags
& NET_SILENT
)
2751 hostlist
.name
= host
? host
: "";
2752 hostlist
.next
= NULL
;
2755 * See if we've been told about this host before.
2757 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2758 /* we were told already */
2760 snprintf(buf
, sizeof(buf
), _("SSL/TLS failure for %s: %s"), hst
, rsn
);
2761 buf
[sizeof(buf
)-1] = '\0';
2767 cols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
2770 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2773 strncpy(buf
, _("There was an SSL/TLS failure for the server"), sizeof(buf
));
2774 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2775 so_puts(store
, folded
);
2776 fs_give((void **)&folded
);
2777 so_puts(store
, "\n");
2779 if((len
=strlen(hst
)) <= cols
){
2780 if((indent
=((cols
-len
)/2)) > 0)
2781 so_puts(store
, repeat_char(indent
, SPACE
));
2783 so_puts(store
, hst
);
2784 so_puts(store
, "\n");
2787 strncpy(buf
, hst
, sizeof(buf
));
2788 buf
[sizeof(buf
)-1] = '\0';
2789 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2790 so_puts(store
, folded
);
2791 fs_give((void **)&folded
);
2794 so_puts(store
, "\n");
2796 strncpy(buf
, _("The reason for the failure was"), sizeof(buf
));
2797 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2798 so_puts(store
, folded
);
2799 fs_give((void **)&folded
);
2800 so_puts(store
, "\n");
2802 if((len
=strlen(rsn
)) <= cols
){
2803 if((indent
=((cols
-len
)/2)) > 0)
2804 so_puts(store
, repeat_char(indent
, SPACE
));
2806 so_puts(store
, rsn
);
2807 so_puts(store
, "\n");
2810 strncpy(buf
, rsn
, sizeof(buf
));
2811 buf
[sizeof(buf
)-1] = '\0';
2812 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2813 so_puts(store
, folded
);
2814 fs_give((void **)&folded
);
2817 so_puts(store
, "\n");
2819 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
));
2820 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2821 so_puts(store
, folded
);
2822 fs_give((void **)&folded
);
2823 so_puts(store
, "\n");
2825 if((len
=strlen(notls
)) <= cols
){
2826 if((indent
=((cols
-len
)/2)) > 0)
2827 so_puts(store
, repeat_char(indent
, SPACE
));
2829 so_puts(store
, notls
);
2830 so_puts(store
, "\n");
2833 strncpy(buf
, notls
, sizeof(buf
));
2834 buf
[sizeof(buf
)-1] = '\0';
2835 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2836 so_puts(store
, folded
);
2837 fs_give((void **)&folded
);
2840 so_puts(store
, "\n");
2842 strncpy(buf
, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2844 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2845 so_puts(store
, folded
);
2846 fs_give((void **)&folded
);
2847 so_puts(store
, "\n");
2849 if((len
=strlen(hst
)) <= cols
){
2850 if((indent
=((cols
-len
)/2)) > 0)
2851 so_puts(store
, repeat_char(indent
, SPACE
));
2853 so_puts(store
, hst
);
2854 so_puts(store
, "\n");
2857 strncpy(buf
, hst
, sizeof(buf
));
2858 buf
[sizeof(buf
)-1] = '\0';
2859 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2860 so_puts(store
, folded
);
2861 fs_give((void **)&folded
);
2864 so_puts(store
, "\n");
2866 strncpy(buf
, _("in your configuration, replace those characters with"),
2868 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2869 so_puts(store
, folded
);
2870 fs_give((void **)&folded
);
2871 so_puts(store
, "\n");
2873 snprintf(buf2
, sizeof(buf2
), "%s%s", hst
, notls
);
2874 buf2
[sizeof(buf2
)-1] = '\0';
2875 if((len
=strlen(buf2
)) <= cols
){
2876 if((indent
=((cols
-len
)/2)) > 0)
2877 so_puts(store
, repeat_char(indent
, SPACE
));
2879 so_puts(store
, buf2
);
2880 so_puts(store
, "\n");
2883 strncpy(buf
, buf2
, sizeof(buf
));
2884 buf
[sizeof(buf
)-1] = '\0';
2885 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2886 so_puts(store
, folded
);
2887 fs_give((void **)&folded
);
2890 so_puts(store
, "\n");
2892 if(ps_global
->ttyo
){
2893 strncpy(buf
, _("Type RETURN to continue."), sizeof(buf
));
2894 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2895 so_puts(store
, folded
);
2896 fs_give((void **)&folded
);
2899 memset(&sargs
, 0, sizeof(SCROLL_S
));
2900 sargs
.text
.text
= so_text(store
);
2901 sargs
.text
.src
= CharStar
;
2902 sargs
.text
.desc
= _("help text");
2903 sargs
.bar
.title
= _("SSL/TLS FAILURE");
2904 sargs
.proc
.tool
= answer_cert_failure
;
2905 sargs
.proc
.data
.p
= (void *)&the_answer
;
2906 sargs
.keys
.menu
= &ans_certfail_keymenu
;
2907 setbitmap(sargs
.keys
.bitmap
);
2908 /* don't want to re-enter c-client */
2909 sargs
.quell_newmail
= 1;
2910 sargs
.help
.text
= h_tls_failure
;
2911 sargs
.help
.title
= _("HELP FOR TLS/SSL FAILURE");
2922 * The screen isn't initialized yet, which should mean that this
2923 * is the result of a -p argument. Display_args_err knows how to deal
2924 * with the uninitialized screen, so we mess with the data to get it
2925 * in shape for display_args_err. This is pretty hacky.
2928 so_seek(store
, 0L, 0); /* rewind */
2929 /* count the lines */
2930 while(so_readc(&c
, store
))
2934 qp
= q
= (char **)fs_get((cnt
+1) * sizeof(char *));
2935 memset(q
, 0, (cnt
+1) * sizeof(char *));
2937 so_seek(store
, 0L, 0); /* rewind */
2939 while(so_readc(&c
, store
)){
2942 *qp
++ = cpystr(buf
);
2949 display_args_err(NULL
, q
, 0);
2950 free_list_array(&q
);
2953 ps_global
->mangled_screen
= 1;
2954 ps_global
->painted_body_on_startup
= 0;
2955 ps_global
->painted_footer_on_startup
= 0;
2958 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, ok_novalidate
, 1);
2963 answer_cert_failure(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2967 ps_global
->next_screen
= SCREEN_FUN_NULL
;
2971 *(int *)(sparms
->proc
.data
.p
) = 'y';
2975 *(int *)(sparms
->proc
.data
.p
) = 'n';
2979 alpine_panic("Unexpected command in answer_cert_failure");
2988 oauth2_auth_answer(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2993 /* TRANSLATORS: user needs to input an access code from the server */
2994 char *accesscodelabel
= _("Copy and Paste Access Code");
2995 char token
[MAILTMPLEN
], prompt
[MAILTMPLEN
];
2997 ps_global
->next_screen
= SCREEN_FUN_NULL
;
3002 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
3003 flags
= OE_APPEND_CURRENT
;
3004 sprintf(prompt
, "%s: ", accesscodelabel
);
3006 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
3007 prompt
, NULL
, NO_HELP
, &flags
);
3008 } while (rc
!= 0 && rc
!= 1);
3009 user
.code
= rc
== 0 ? cpystr(token
) : NULL
;
3011 rv
= rc
== 1 ? 0 : 1;
3020 alpine_panic("Unexpected command in oauth2_auth_answer");
3023 *(AUTH_CODE_S
*) sparms
->proc
.data
.p
= user
;
3028 /*----------------------------------------------------------------------
3029 This can be used to prevent the flickering of the check_cue char
3030 caused by numerous (5000+) fetches by c-client. Right now, the only
3031 practical use found is newsgroup subsciption.
3033 check_cue_display will check if this global is set, and won't clear
3034 the check_cue_char if set.
3037 set_read_predicted(int i
)
3039 ps_global
->read_predicted
= i
==1;
3041 if(!i
&& F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3042 check_cue_display(" ");
3047 /*----------------------------------------------------------------------
3048 Exported method to retrieve logged in user name associated with stream
3050 Args: host -- host to find associated login name with.
3055 pine_block_notify(int reason
, void *data
)
3058 case BLOCK_SENSITIVE
: /* sensitive code, disallow alarms */
3061 case BLOCK_NONSENSITIVE
: /* non-sensitive code, allow alarms */
3064 case BLOCK_TCPWRITE
: /* blocked on TCP write */
3065 case BLOCK_FILELOCK
: /* blocked on file locking */
3067 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3068 check_cue_display(">");
3070 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3074 case BLOCK_DNSLOOKUP
: /* blocked on DNS lookup */
3075 case BLOCK_TCPOPEN
: /* blocked on TCP open */
3076 case BLOCK_TCPREAD
: /* blocked on TCP read */
3077 case BLOCK_TCPCLOSE
: /* blocked on TCP close */
3079 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3080 check_cue_display("<");
3082 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3087 case BLOCK_NONE
: /* not blocked */
3089 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3090 check_cue_display(" ");
3101 mm_expunged_current(long unsigned int rawno
)
3103 /* expunged something we're viewing? */
3104 if(!ps_global
->expunge_in_progress
3105 && (mn_is_cur(ps_global
->msgmap
, mn_raw2m(ps_global
->msgmap
, (long) rawno
))
3106 && (ps_global
->prev_screen
== mail_view_screen
3107 || ps_global
->prev_screen
== attachment_screen
))){
3108 ps_global
->next_screen
= mail_index_screen
;
3109 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3110 "Message you were viewing is gone!");
3118 * Specific functions to support caching username/passwd/host
3119 * triples on disk for use from one session to the next...
3122 #define FIRSTCH 0x20
3124 #define TABSZ (LASTCH - FIRSTCH + 1)
3126 static int xlate_key
;
3130 * xlate_in() - xlate_in the given character
3138 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3139 eti
+= (c
- FIRSTCH
);
3140 eti
-= (eti
>= 2*TABSZ
) ? 2*TABSZ
: (eti
>= TABSZ
) ? TABSZ
: 0;
3141 return((xlate_key
= eti
) + FIRSTCH
);
3149 * xlate_out() - xlate_out the given character
3157 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3158 xch
= c
- (dti
= xlate_key
);
3159 xch
+= (xch
< FIRSTCH
-TABSZ
) ? 2*TABSZ
: (xch
< FIRSTCH
) ? TABSZ
: 0;
3160 dti
= (xch
- FIRSTCH
) + dti
;
3161 dti
-= (dti
>= 2*TABSZ
) ? 2*TABSZ
: (dti
>= TABSZ
) ? TABSZ
: 0;
3168 #endif /* PASSFILE */
3171 #ifdef LOCAL_PASSWD_CACHE
3173 int cache_method_was_setup (char *pinerc
)
3177 char tmp
[MAILTMPLEN
];
3179 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb")))
3181 if(fp
!= NULL
) fclose(fp
);
3182 #endif /* PASSFILE */
3187 line_get(char *tmp
, size_t len
, char **textp
)
3193 || (s
= strchr(*textp
, '\n')) == NULL
3194 || (s
- *textp
) > len
- 1)
3201 snprintf(tmp
, len
, "%s\n", *textp
);
3208 typedef struct pwd_s
{
3219 #define SAME_VALUE(X, Y) ((((X) == NULL && (Y) == NULL) \
3220 || ((X) && (Y) && !strcmp((X), (Y)))) ? 1 : 0)
3224 * Passfile lines are
3226 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3228 * In pine4.40 and before there was no orig_hostname, and there still isn't
3229 * if it is the same as hostname.
3232 * Use Windows credentials. The TargetName of the credential is
3233 * UWash_Alpine_.partnumber-totalparts_<hostname:port>\tuser\taltflag
3234 * and the blob consists of
3235 * passwd\torighost (if different from host)
3237 * We don't use anything fancy we just copy out all the credentials which
3238 * begin with TNAME and put them into our cache, so we don't lookup based
3239 * on the TargetName or anything like that. That was so we could re-use
3240 * the existing code and so that orighost data could be easily used.
3243 read_passfile(pinerc
, l
)
3249 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3252 char *tmp
, *blob
, *target
= NULL
;
3253 ALPINE_PWD_S
**pwd
= NULL
;
3254 int uid
, rewrite_passfile
= 0;
3257 unsigned long m
, n
, p
, loc
;
3259 if(using_passfile
== 0)
3260 return(using_passfile
);
3263 if (init_wincred_funcs() != 1) {
3265 return(using_passfile
);
3269 dprint((9, "read_passfile\n"));
3273 /* this code exists because the XOAUTH2 support makes us save
3274 * access tokens as if they were passwords. However, some servers
3275 * produce extremely long access-tokens that do not fit in the credentials
3276 * and therefore need to be split into several entries.
3278 * The plan is the following:
3279 * step 1: Read and save all the information in the credentials
3280 * step 2: flatten the information into one line
3281 * step 3: process that line.
3283 if (g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)) {
3284 pwd
= fs_get((count
+ 1)*sizeof(ALPINE_PWD_S
*));
3285 memset((void *)pwd
, 0, (count
+ 1)*sizeof(ALPINE_PWD_S
*));
3287 /* this is step 1 */
3288 for (k
= 0; k
< count
; k
++) { /* go through each credential */
3289 target
= lptstr_to_utf8(pcred
[k
]->TargetName
);
3290 tmp
= srchstr(target
, TNAME
);
3292 tmp
+= strlen(TNAME
);
3295 m
= strtoul(tmp
, &tmp
, 10);
3299 m
= strtoul(tmp
, &tmp
, 10);
3302 uid
= 0; /* impossible value, uid >= 1, old format! */
3307 n
= strtol(tmp
, &tmp
, 10);
3308 if (*tmp
== '_') tmp
++;
3312 for(s
= tmp
; *s
&& *s
!= '_'; s
++);
3313 if(s
&& *s
) tmp
= s
;
3320 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3321 for (i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++) {
3322 for (ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3323 ; /* find end of data */
3326 tmp
[i
++] = '\0'; /* tie off data */
3329 /* improve this. We are trying to find where we saved
3330 * this data, and in general this is fast if there is
3331 * only a few data, which is not unreasonable, but probably
3332 * can be done better.
3334 for (loc
= 0; pwd
[loc
]
3335 && !(uid
== pwd
[loc
]->uid
3336 && SAME_VALUE(ui
[0], pwd
[loc
]->host
)
3337 && SAME_VALUE(ui
[1], pwd
[loc
]->user
)
3338 && SAME_VALUE(ui
[2], pwd
[loc
]->sflags
)); loc
++);
3340 if (pwd
[loc
] == NULL
) {
3341 pwd
[loc
] = fs_get(sizeof(ALPINE_PWD_S
));
3342 memset((void *) pwd
[loc
], 0, sizeof(ALPINE_PWD_S
));
3343 pwd
[loc
]->blobarray
= fs_get((n
+ 1) * sizeof(char*));
3344 memset((void *) pwd
[loc
]->blobarray
, 0, (n
+ 1) * sizeof(char*));
3347 pwd
[loc
]->uid
= uid
;
3348 if (pwd
[loc
]->host
== NULL
)
3349 pwd
[loc
]->host
= ui
[0] ? cpystr(ui
[0]) : NULL
;
3350 if (pwd
[loc
]->user
== NULL
)
3351 pwd
[loc
]->user
= ui
[1] ? cpystr(ui
[1]) : NULL
;
3352 if (pwd
[loc
]->sflags
== NULL
)
3353 pwd
[loc
]->sflags
= ui
[2] ? cpystr(ui
[2]) : NULL
;
3354 blob
= (char *) pcred
[k
]->CredentialBlob
;
3355 pwd
[loc
]->blobarray
[m
- 1] = blob
? cpystr(blob
) : NULL
;
3357 if (target
) fs_give((void**)&target
);
3360 for (k
= 0; k
< count
; k
++) {
3362 for (i
= 0, j
= 0; pwd
[k
]->blobarray
[j
]; j
++)
3363 i
+= strlen(pwd
[k
]->blobarray
[j
]);
3364 pwd
[k
]->blob
= fs_get(i
+ 1);
3365 pwd
[k
]->blob
[0] = '\0';
3366 for (j
= 0; pwd
[k
]->blobarray
[j
]; j
++) {
3367 strcat(pwd
[k
]->blob
, pwd
[k
]->blobarray
[j
]);
3368 fs_give((void **) &pwd
[k
]->blobarray
[j
]);
3370 fs_give((void **) &pwd
[k
]->blobarray
);
3372 else k
= count
; /* we are done with this step! */
3375 for (k
= 0; k
< count
; k
++) {
3376 if (pwd
[k
] && pwd
[k
]->blob
) {
3377 blob
= pwd
[k
]->blob
;
3378 for (i
= 0, j
= 3; blob
[i
] && j
< 5; j
++) {
3379 for (ui
[j
] = &blob
[i
]; blob
[i
] && blob
[i
] != '\t'; i
++)
3380 ; /* find end of data */
3383 blob
[i
++] = '\0'; /* tie off data */
3385 if (pwd
[k
]->passwd
== NULL
)
3386 pwd
[k
]->passwd
= ui
[3] ? cpystr(ui
[3]) : NULL
;
3387 if (pwd
[k
]->orighost
== NULL
)
3388 pwd
[k
]->orighost
= ui
[4] ? cpystr(ui
[4]) : NULL
;
3389 fs_give((void **) &pwd
[k
]->blob
);
3392 /* now process all lines, and free memory */
3393 for (k
= 0; k
< count
&& pwd
[k
] != NULL
; k
++){
3394 if (pwd
[k
]->passwd
&& pwd
[k
]->host
&& pwd
[k
]->user
) { /* valid field? */
3395 STRLIST_S hostlist
[2];
3398 tmp
= pwd
[k
]->sflags
? strchr(pwd
[k
]->sflags
, PWDAUTHSEP
) : NULL
;
3399 flags
= pwd
[k
]->sflags
? atoi(tmp
? ++tmp
: pwd
[k
]->sflags
) : 0;
3400 hostlist
[0].name
= pwd
[k
]->host
;
3401 if (pwd
[k
]->orighost
) {
3402 hostlist
[0].next
= &hostlist
[1];
3403 hostlist
[1].name
= pwd
[k
]->orighost
;
3404 hostlist
[1].next
= NULL
;
3407 hostlist
[0].next
= NULL
;
3409 imap_set_passwd(l
, pwd
[k
]->passwd
, pwd
[k
]->user
, hostlist
, flags
& 0x01, 0, 0);
3411 if (pwd
[k
]->passwd
) fs_give((void **) &pwd
[k
]->passwd
);
3412 if (pwd
[k
]->user
) fs_give((void **) &pwd
[k
]->user
);
3413 if (pwd
[k
]->host
) fs_give((void **) &pwd
[k
]->host
);
3414 if (pwd
[k
]->sflags
) fs_give((void **) &pwd
[k
]->sflags
);
3415 if (pwd
[k
]->orighost
) fs_give((void **) &pwd
[k
]->orighost
);
3416 fs_give((void **) &pwd
[k
]);
3418 g_CredFree((PVOID
)pcred
);
3420 fs_give((void **) &pwd
);
3421 if(rewrite_passfile
) write_passfile(pinerc
, *l
);
3425 # else /* old windows */
3432 char target
[MAILTMPLEN
];
3433 char *tmp
, *host
, *user
, *sflags
, *passwd
, *orighost
;
3436 SecKeychainAttributeList attrList
;
3437 SecKeychainSearchRef searchRef
= NULL
;
3438 SecKeychainAttribute attrs
[] = {
3439 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
3442 if(using_passfile
== 0)
3443 return(using_passfile
);
3445 dprint((9, "read_passfile\n"));
3448 /* search for only our items in the keychain */
3450 attrList
.attr
= attrs
;
3453 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
3454 kSecGenericPasswordItemClass
,
3457 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3459 SecKeychainItemRef itemRef
= NULL
;
3460 SecKeychainAttributeInfo info
;
3461 SecKeychainAttributeList
*attrList
= NULL
;
3464 char *blobcopy
= NULL
; /* NULL terminated copy */
3466 UInt32 tags
[] = {kSecAccountItemAttr
,
3467 kSecServiceItemAttr
};
3468 UInt32 formats
[] = {0,0};
3470 dprint((10, "read_passfile: searchRef not NULL\n"));
3473 info
.format
= formats
;
3476 * Go through each item we found and put it
3479 while(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
)) && itemRef
){
3480 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3481 rc
= SecKeychainItemCopyAttributesAndData(itemRef
,
3486 if(rc
== 0 && attrList
){
3487 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList
->count
));
3489 blobcopy
= (char *) fs_get((blength
+ 1) * sizeof(char));
3490 strncpy(blobcopy
, (char *) blob
, blength
);
3491 blobcopy
[blength
] = '\0';
3494 * I'm not real clear on how this works. It seems to be
3495 * necessary to combine the attributes from two passes
3496 * (attrList->count == 2) in order to get the full set
3497 * of attributes we inserted into the keychain in the
3498 * first place. So, we reset host...orighost outside of
3499 * the following for loop, not inside.
3501 host
= user
= sflags
= passwd
= orighost
= NULL
;
3502 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3504 for(k
= 0; k
< attrList
->count
; k
++){
3506 if(attrList
->attr
[k
].length
){
3508 (char *) attrList
->attr
[k
].data
,
3509 MIN(attrList
->attr
[k
].length
,sizeof(target
)));
3510 target
[MIN(attrList
->attr
[k
].length
,sizeof(target
)-1)] = '\0';
3514 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
3515 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3516 ; /* find end of data */
3519 tmp
[i
++] = '\0'; /* tie off data */
3531 for(i
= 0, j
= 3; blobcopy
[i
] && j
< 5; j
++){
3532 for(ui
[j
] = &blobcopy
[i
]; blobcopy
[i
] && blobcopy
[i
] != '\t'; i
++)
3533 ; /* find end of data */
3536 blobcopy
[i
++] = '\0'; /* tie off data */
3545 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
:""));
3548 if(passwd
&& host
&& user
){ /* valid field? */
3549 STRLIST_S hostlist
[2];
3552 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
3553 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
3554 hostlist
[0].name
= host
;
3556 hostlist
[0].next
= &hostlist
[1];
3557 hostlist
[1].name
= orighost
;
3558 hostlist
[1].next
= NULL
;
3561 hostlist
[0].next
= NULL
;
3564 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
3568 fs_give((void **) & blobcopy
);
3570 SecKeychainItemFreeAttributesAndData(attrList
, (void *) blob
);
3574 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc
));
3581 CFRelease(searchRef
);
3585 dprint((10, "read_passfile: searchRef NULL\n"));
3590 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc
));
3593 return(using_passfile
);
3595 #else /* PASSFILE */
3597 char tmp
[MAILTMPLEN
], *ui
[5];
3598 int i
, j
, n
, rv
= 0, error
= 0;
3600 char *tmptext
= NULL
;
3603 char tmp2
[MAILTMPLEN
];
3604 char *text
= NULL
, *text2
= NULL
;
3609 if(using_passfile
== 0)
3610 return(using_passfile
);
3612 dprint((9, "read_passfile\n"));
3614 /* if there's no password to read, create it if we can encrypt it,
3615 * or else let the user create it and bail out of here.
3618 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb"))){
3620 i
= our_creat(tmp
, 0600);
3623 if(!(fp
= our_fopen(tmp
, "rb")))
3634 return(using_passfile
);
3638 if(our_stat(tmp
, &sbuf
) == 0)
3640 fp
= our_fopen(tmp
, "rb"); /* reopen to read data */
3642 /* the next call initializes the key/certificate pair used to
3643 * encrypt and decrypt a password file. The details of how this is
3644 * done is in the file pith/smime.c. During this setup we might call
3645 * smime_init(), but no matter what happens we must call smime_deinit()
3646 * there. The reason why this is so is because we can not assume that
3647 * the .pinerc file has been read by this time, so this code might not
3648 * know about the ps_global->smime structure or any of its components,
3649 * and it shouldn't because it only needs ps_global->pwdcert, so
3650 * do not init smime here, because the .pinerc might not have been
3651 * read and we do not really know where the keys and certificates really
3653 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3654 * it is called for the first time and there are certificates at all,
3655 * or when it is called after the first time and the user refuses to
3656 * create a self-signed certificate. In this situation we will just
3657 * let the user live in an insecure world, but no more passwords will
3658 * be saved in the password file, and only those found there will be used.
3661 fgets(tmp2
, sizeof(tmp2
), fp
);
3663 if(strcmp(tmp2
, "-----BEGIN PKCS7-----\n")){
3664 /* there is an already existing password file, that is not encrypted
3665 * and there is no key to encrypt it yet, go again through setup_pwdcert
3666 * and encrypt it now.
3668 if(tmp2
[0]){ /* not empty, UNencrypted password file */
3669 if(ps_global
->pwdcert
== NULL
)
3670 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3671 if((rv
== 0 || rv
== -5) && ps_global
->pwdcert
== NULL
)
3672 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3673 if(ps_global
->pwdcert
== NULL
){
3674 q_status_message(SM_ORDER
, 3, 3,
3675 " Failed to create private key. Using UNencrypted Password file. ");
3680 q_status_message(SM_ORDER
, 3, 3,
3681 " Failed to unlock private key. Using UNencrypted Password file. ");
3682 save_password
= 0; /* do not save more passwords */
3685 if(ps_global
->pwdcert
!= NULL
3686 && encrypt_file((char *)tmp
, NULL
, (PERSONAL_CERT
*)ps_global
->pwdcert
))
3691 if(ps_global
->pwdcert
== NULL
)
3692 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3697 * if password file is encrypted we attempt to decrypt. We ask the
3698 * user for the password to unlock the password file. If the user
3699 * enters the password and it unlocks the file, use it and keep saving
3700 * passwords in it. If the user enters the wrong passwords and does
3701 * not unlock it, we will not see that here, but in decrypt_file, so
3702 * the only other possibility is that the user cancels. In that case
3703 * we will see i == -1. In that case, we will let the user attempt
3704 * manual login to the server they want to login, but passwords will
3705 * not be saved so that the password file will not be saved
3706 * unencrypted and rewritten again.
3709 text
= text2
= decrypt_file((char *)tmp
, &i
, (PERSONAL_CERT
*)ps_global
->pwdcert
);
3710 len
= text2
? strlen(text2
) : 0;
3712 case -2: using_passfile
= 0;
3715 case 1 : save_password
= 1;
3719 case -1: save_password
= 0;
3728 if(using_passfile
== 0){
3730 if(text
) fs_give((void **)&text
);
3732 return using_passfile
;
3736 tmptext
= fs_get(len
+ 1);
3738 for(n
= 0; encrypted
? line_get(tmptext
, len
+ 1, &text2
)
3739 : (fgets(tmptext
, len
+1, fp
) != NULL
); n
++){
3741 for(n
= 0; fgets(tmptext
, len
+1, fp
); n
++){
3743 /*** do any necessary DEcryption here ***/
3745 for(i
= 0; tmptext
[i
]; i
++)
3746 tmptext
[i
] = xlate_out(tmptext
[i
]);
3748 if(i
&& tmptext
[i
-1] == '\n')
3749 tmptext
[i
-1] = '\0'; /* blast '\n' */
3751 dprint((10, "read_passfile: %s\n", tmp
? tmp
: "?"));
3752 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3753 for(i
= 0, j
= 0; tmptext
[i
] && j
< 5; j
++){
3754 for(ui
[j
] = &tmptext
[i
]; tmptext
[i
] && tmptext
[i
] != '\t'; i
++)
3755 ; /* find end of data */
3758 tmptext
[i
++] = '\0'; /* tie off data */
3761 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3762 if(ui
[0] && ui
[1] && ui
[2]){ /* valid field? */
3763 STRLIST_S hostlist
[2];
3764 char *s
= ui
[3] ? strchr(ui
[3], PWDAUTHSEP
) : NULL
;
3765 int flags
= ui
[3] ? atoi(s
? ++s
: ui
[3]) : 0;
3767 hostlist
[0].name
= ui
[2];
3769 hostlist
[0].next
= &hostlist
[1];
3770 hostlist
[1].name
= ui
[4];
3771 hostlist
[1].next
= NULL
;
3774 hostlist
[0].next
= NULL
;
3777 imap_set_passwd(l
, ui
[0], ui
[1], hostlist
, flags
& 0x01, 0, 0);
3782 if (tmptext
) fs_give((void **) &tmptext
);
3784 if (text
) fs_give((void **)&text
);
3789 #endif /* PASSFILE */
3795 write_passfile(pinerc
, l
)
3799 char *authend
, *authtype
;
3802 unsigned long bloblen
= 0, len
;
3803 int i
, totalparts
, k
, uid
;
3804 char target
[MAXPWDBUFFERSIZE
];
3805 char *blob
= NIL
, blob2
[50], *blobp
;
3806 char part
[MAILTMPLEN
];
3810 if(using_passfile
== 0)
3813 dprint((9, "write_passfile\n"));
3815 erase_windows_credentials(); /* erase all passwords from credentials */
3816 /* start writing them back to credentials manager */
3817 for(uid
= 1; l
; l
= l
->next
, uid
++){ /* enforce that uid >= 1 */
3818 /* determine how many parts to create first */
3819 len
= (l
->passwd
? strlen(l
->passwd
) : 0)
3820 + ((l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
) ? 1 : 0)
3821 + ((l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
) ? strlen(l
->hosts
->next
->name
) : 0) + 1;
3825 fs_resize((void **) &blob
, bloblen
);
3828 sprintf(blob
, "%s%s%s",
3829 l
->passwd
? l
->passwd
: "",
3830 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3832 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3833 ? l
->hosts
->next
->name
: "");
3834 i
= len
- 1; /* strlen(blob) */
3836 for (totalparts
= 1; i
> MAXPWDBUFFERSIZE
; totalparts
++, i
-= PWDBUFFERSIZE
);
3837 authtype
= l
->passwd
;
3838 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3840 if (authend
!= NULL
){
3842 sprintf(blob2
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3843 *authend
= PWDAUTHSEP
;
3846 sprintf(blob2
, "%d", l
->altflag
);
3848 for (k
= 1, i
= len
- 1, blobp
= blob
; k
<= totalparts
; k
++) {
3849 snprintf(target
, sizeof(target
), "%s.%d.%d-%d_%s\t%s\t%s",
3850 TNAME
, uid
, k
, totalparts
,
3851 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3852 l
->user
? l
->user
: "",
3854 ltarget
= utf8_to_lptstr((LPSTR
)target
);
3856 memset((void*)&cred
, 0, sizeof(cred
));
3858 cred
.Type
= CRED_TYPE_GENERIC
;
3859 cred
.TargetName
= ltarget
;
3860 if (i
> MAXPWDBUFFERSIZE
) {
3861 strncpy(part
, blobp
, PWDBUFFERSIZE
);
3862 part
[PWDBUFFERSIZE
] = '\0';
3863 blobp
+= PWDBUFFERSIZE
;
3867 strcpy(part
, blobp
);
3868 cred
.CredentialBlobSize
= strlen(part
) + 1;
3869 cred
.CredentialBlob
= (LPBYTE
)&part
;
3870 cred
.Persist
= CRED_PERSIST_ENTERPRISE
;
3871 g_CredWriteW(&cred
, 0);
3872 fs_give((void**)<arget
);
3876 if(blob
) fs_give((void **) &blob
);
3878 #endif /* WINCRED > 0 */
3882 char target
[10*MAILTMPLEN
];
3883 char blob
[10*MAILTMPLEN
];
3884 SecKeychainItemRef itemRef
= NULL
;
3886 if(using_passfile
== 0)
3889 dprint((9, "write_passfile\n"));
3891 for(; l
; l
= l
->next
){
3892 authtype
= l
->passwd
;
3893 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3894 if(authend
!= NULL
){
3896 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3897 *authend
= PWDAUTHSEP
;
3900 sprintf(blob
, "%d", l
->altflag
);
3902 snprintf(target
, sizeof(target
), "%s\t%s\t%s",
3903 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3904 l
->user
? l
->user
: "",
3907 snprintf(blob
, sizeof(blob
), "%s%s%s",
3908 l
->passwd
? l
->passwd
: "",
3909 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3911 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3912 ? l
->hosts
->next
->name
: "");
3914 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target
), target
, strlen(TNAME
), TNAME
, strlen(blob
), blob
));
3916 rc
= SecKeychainAddGenericPassword(NULL
,
3917 strlen(target
), target
,
3918 strlen(TNAME
), TNAME
,
3922 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3925 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc
));
3928 if(rc
== errSecDuplicateItem
){
3929 /* fix existing entry */
3930 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3932 if(!(rc
=SecKeychainFindGenericPassword(NULL
,
3933 strlen(target
), target
,
3934 strlen(TNAME
), TNAME
,
3936 &itemRef
)) && itemRef
){
3938 rc
= SecKeychainItemModifyAttributesAndData(itemRef
, NULL
, strlen(blob
), blob
);
3940 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc
));
3944 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc
));
3949 #else /* PASSFILE */
3950 char *tmp
= NULL
, passfile
[MAXPATH
+ 1], blob
[MAILTMPLEN
];
3952 size_t tmplen
= 0, newlen
;
3955 char *text
= NULL
, tmp2
[MAXPATH
+ 1];
3959 if(using_passfile
== 0)
3962 dprint((9, "write_passfile\n"));
3964 /* if there's no passfile to read, bag it!! */
3965 if(!passfile_name(pinerc
, passfile
, sizeof(passfile
)) || !(fp
= our_fopen(passfile
, "wb"))){
3971 strncpy(tmp2
, passfile
, sizeof(tmp2
));
3972 tmp2
[sizeof(tmp2
)-1] = '\0';
3975 for(n
= 0; l
; l
= l
->next
, n
++){
3976 authtype
= l
->passwd
;
3977 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3978 if(authend
!= NULL
){
3980 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3981 *authend
= PWDAUTHSEP
;
3984 sprintf(blob
, "%d", l
->altflag
);
3986 newlen
= strlen(l
->passwd
) + strlen(l
->user
) + strlen(l
->hosts
->name
)
3987 + strlen(blob
) + strlen((l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "")
3988 + strlen((l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
: "")
3991 if(tmplen
< newlen
){
3992 fs_resize((void **)&tmp
, newlen
);
3996 /*** do any necessary ENcryption here ***/
3997 sprintf(tmp
, "%s\t%s\t%s\t%s%s%s\n", l
->passwd
, l
->user
,
3998 l
->hosts
->name
, blob
,
3999 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "",
4000 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
4002 dprint((10, "write_passfile: %s", tmp
? tmp
: "?"));
4004 for(i
= 0; tmp
[i
]; i
++)
4005 tmp
[i
] = xlate_in(tmp
[i
]);
4008 fs_resize((void **)&text
, (len
+ strlen(tmp
) + 1)*sizeof(char));
4010 len
+= strlen(tmp
) + 1;
4011 strncat(text
, tmp
, strlen(tmp
));
4017 if(tmp
) fs_give((void **) &tmp
);
4021 i
= 0; /* to quell gcc */
4022 if(ps_global
->pwdcert
== NULL
){
4023 q_status_message(SM_ORDER
, 3, 3, "Attempting to encrypt password file");
4024 i
= setup_pwdcert(&ps_global
->pwdcert
);
4025 if((i
== 0 || i
== -5) && ps_global
->pwdcert
== NULL
)
4026 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
4028 if(ps_global
->pwdcert
== NULL
){ /* we tried but failed */
4030 q_status_message1(SM_ORDER
, 3, 3, "Error: no directory %s to save certificates", ps_global
->pwdcertdir
);
4032 q_status_message(SM_ORDER
, 3, 3, "Refusing to write non-encrypted password file");
4034 else if(encrypt_file((char *)tmp2
, text
, ps_global
->pwdcert
) == 0)
4035 q_status_message(SM_ORDER
, 3, 3, "Failed to encrypt password file");
4036 fs_give((void **)&text
); /* do not save this text */
4039 #endif /* PASSFILE */
4042 #endif /* LOCAL_PASSWD_CACHE */
4047 erase_windows_credentials(void)
4049 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
4054 if(init_wincred_funcs() != 1)
4058 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
4060 for(k
= 0; k
< count
; k
++)
4061 g_CredDeleteW(pcred
[k
]->TargetName
, CRED_TYPE_GENERIC
, 0);
4063 g_CredFree((PVOID
) pcred
);
4069 ask_erase_credentials(void)
4071 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y'){
4072 erase_windows_credentials();
4073 q_status_message(SM_ORDER
, 3, 3, "All preserved passwords have been erased");
4076 q_status_message(SM_ORDER
, 3, 3, "Previously preserved passwords will not be erased");
4079 #endif /* WINCRED */
4082 #ifdef LOCAL_PASSWD_CACHE
4084 get_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
)
4085 char *pinerc
, **passwd
, *user
;
4086 STRLIST_S
*hostlist
;
4089 return get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, NULL
);
4093 * get_passfile_passwd_auth - return the password contained in the special password
4094 * cache. The file is assumed to be in the same directory
4095 * as the pinerc with the name defined above.
4098 get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, authtype
)
4099 char *pinerc
, **passwd
, *user
;
4100 STRLIST_S
*hostlist
;
4104 dprint((10, "get_passfile_passwd_auth\n"));
4105 return((mm_login_list
|| read_passfile(pinerc
, &mm_login_list
))
4106 ? imap_get_passwd_auth(mm_login_list
, passwd
,
4107 user
, hostlist
, altflag
, authtype
)
4112 is_using_passfile(void)
4114 return(using_passfile
== 1);
4118 * Just trying to guess the username the user might want to use on this
4119 * host, the user will confirm.
4122 get_passfile_user(pinerc
, hostlist
)
4124 STRLIST_S
*hostlist
;
4126 return((mm_login_list
|| read_passfile(pinerc
, &mm_login_list
))
4127 ? imap_get_user(mm_login_list
, hostlist
)
4133 preserve_prompt(char *pinerc
)
4135 return preserve_prompt_auth(pinerc
, NULL
);
4139 preserve_prompt_auth(char *pinerc
, char *authtype
)
4141 ps_global
->preserve_password
= 0;
4144 #define PROMPT_PWD _("Preserve password for next login")
4145 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4147 * This prompt was going to be able to be turned on and off via a registry
4148 * setting controlled from the config menu. We decided to always use the
4149 * dialog for login, and there the prompt is unobtrusive enough to always
4150 * be in there. As a result, windows should never reach this, but now
4151 * OS X somewhat uses the behavior just described.
4153 if(mswin_store_pass_prompt()
4154 && (want_to(authtype
4155 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4157 'y', 'x', NO_HELP
, WT_NORM
)
4159 ps_global
->preserve_password
= 1;
4169 #define PROMPT_PWD _("Preserve password for next login")
4170 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4173 if((rc
= macos_store_pass_prompt()) != 0){
4175 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4176 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
4178 ps_global
->preserve_password
= 1;
4180 macos_set_store_pass_prompt(1);
4181 q_status_message(SM_ORDER
, 4, 4,
4182 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4187 macos_set_store_pass_prompt(0);
4188 q_status_message(SM_ORDER
, 4, 4,
4189 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4194 #else /* PASSFILE */
4195 #define PROMPT_PWD _("Preserve password on DISK for next login")
4196 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
4198 char tmp
[MAILTMPLEN
];
4201 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || our_stat(tmp
, &sbuf
) < 0)
4204 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
4206 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4207 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
4209 ps_global
->preserve_password
= 1;
4213 #endif /* PASSFILE */
4216 #endif /* LOCAL_PASSWD_CACHE */
4219 #ifdef APPLEKEYCHAIN
4223 * 1 if store pass prompt is set in the "registry" to on
4225 * -1 if not set to anything
4228 macos_store_pass_prompt(void)
4235 if(storepassprompt
== -1){
4236 if(!(rc
=SecKeychainFindGenericPassword(NULL
, 0, NULL
,
4237 strlen(TNAMEPROMPT
),
4239 (void **) &data
, NULL
))){
4240 val
= (len
== 1 && data
&& data
[0] == '1');
4244 if(storepassprompt
== -1 && !rc
){
4246 storepassprompt
= 1;
4248 storepassprompt
= 0;
4251 return(storepassprompt
);
4256 macos_set_store_pass_prompt(int val
)
4258 storepassprompt
= val
? 1 : 0;
4260 SecKeychainAddGenericPassword(NULL
, 0, NULL
, strlen(TNAMEPROMPT
),
4261 TNAMEPROMPT
, 1, val
? "1" : "0", NULL
);
4266 macos_erase_keychain(void)
4268 SecKeychainAttributeList attrList
;
4269 SecKeychainSearchRef searchRef
= NULL
;
4270 SecKeychainAttribute attrs1
[] = {
4271 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
4273 SecKeychainAttribute attrs2
[] = {
4274 { kSecAccountItemAttr
, strlen(TNAMEPROMPT
), TNAMEPROMPT
}
4277 dprint((9, "macos_erase_keychain\n"));
4280 * Seems like we ought to be able to combine attrs1 and attrs2
4281 * into a single array, but I couldn't get it to work.
4284 /* search for only our items in the keychain */
4286 attrList
.attr
= attrs1
;
4288 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4289 kSecGenericPasswordItemClass
,
4293 SecKeychainItemRef itemRef
= NULL
;
4296 * Go through each item we found and put it
4299 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4300 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4301 SecKeychainItemDelete(itemRef
);
4305 CFRelease(searchRef
);
4310 attrList
.attr
= attrs2
;
4312 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4313 kSecGenericPasswordItemClass
,
4317 SecKeychainItemRef itemRef
= NULL
;
4320 * Go through each item we found and put it
4323 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4324 SecKeychainItemDelete(itemRef
);
4328 CFRelease(searchRef
);
4333 #endif /* APPLEKEYCHAIN */
4335 #ifdef LOCAL_PASSWD_CACHE
4338 set_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
)
4339 char *pinerc
, *passwd
, *user
;
4340 STRLIST_S
*hostlist
;
4341 int altflag
, already_prompted
;
4343 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, NULL
);
4346 * set_passfile_passwd - set the password file entry associated with
4347 * cache. The file is assumed to be in the same directory
4348 * as the pinerc with the name defined above.
4349 * already_prompted: 0 not prompted
4350 * 1 prompted, answered yes
4351 * 2 prompted, answered no
4354 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, authtype
)
4355 char *pinerc
, *passwd
, *user
;
4356 STRLIST_S
*hostlist
;
4357 int altflag
, already_prompted
;
4360 dprint((10, "set_passfile_passwd_auth\n"));
4361 if(((already_prompted
== 0 && preserve_prompt_auth(pinerc
, authtype
))
4362 || already_prompted
== 1)
4363 && !ps_global
->nowrite_password_cache
4364 && (mm_login_list
|| read_passfile(pinerc
, &mm_login_list
))){
4365 imap_set_passwd_auth(&mm_login_list
, passwd
, user
, hostlist
, altflag
, 0, 0, authtype
);
4366 write_passfile(pinerc
, mm_login_list
);
4371 update_passfile_hostlist(pinerc
, user
, hostlist
, altflag
)
4374 STRLIST_S
*hostlist
;
4377 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, NULL
);
4381 * Passfile lines are
4383 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4385 * In pine4.40 and before there was no orig_hostname.
4386 * This routine attempts to repair that.
4389 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, authtype
)
4392 STRLIST_S
*hostlist
;
4398 #else /* !WINCRED */
4400 size_t len
= authtype
? strlen(authtype
) : 0;
4401 size_t offset
= authtype
? 1 : 0;
4403 for(l
= mm_login_list
; l
; l
= l
->next
)
4404 if(imap_same_host_auth(l
->hosts
, hostlist
, authtype
)
4406 && !strcmp(user
, l
->user
+ len
+ offset
)
4407 && l
->altflag
== altflag
){
4411 if(l
&& l
->hosts
&& hostlist
&& !l
->hosts
->next
&& hostlist
->next
4412 && hostlist
->next
->name
4413 && !ps_global
->nowrite_password_cache
){
4414 l
->hosts
->next
= new_strlist_auth(hostlist
->next
->name
, authtype
, PWDAUTHSEP
);
4415 write_passfile(pinerc
, mm_login_list
);
4417 #endif /* !WINCRED */
4420 #endif /* LOCAL_PASSWD_CACHE */
4425 * Load and init the WinCred structure.
4426 * This gives us a way to skip the WinCred code
4427 * if the dll doesn't exist.
4430 init_wincred_funcs(void)
4436 /* Assume the worst. */
4439 hmod
= LoadLibrary(TEXT("advapi32.dll"));
4442 FARPROC fpCredWriteW
;
4443 FARPROC fpCredEnumerateW
;
4444 FARPROC fpCredDeleteW
;
4447 fpCredWriteW
= GetProcAddress(hmod
, "CredWriteW");
4448 fpCredEnumerateW
= GetProcAddress(hmod
, "CredEnumerateW");
4449 fpCredDeleteW
= GetProcAddress(hmod
, "CredDeleteW");
4450 fpCredFree
= GetProcAddress(hmod
, "CredFree");
4452 if(fpCredWriteW
&& fpCredEnumerateW
&& fpCredDeleteW
&& fpCredFree
)
4454 g_CredWriteW
= (CREDWRITEW
*)fpCredWriteW
;
4455 g_CredEnumerateW
= (CREDENUMERATEW
*)fpCredEnumerateW
;
4456 g_CredDeleteW
= (CREDDELETEW
*)fpCredDeleteW
;
4457 g_CredFree
= (CREDFREE
*)fpCredFree
;
4463 mswin_set_erasecreds_callback(ask_erase_credentials
);
4466 return g_CredInited
;
4469 #endif /* WINCRED */