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
, NETMBX
*mb
)
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
;
375 HANDLE_S
*handles
= NULL
;
376 AUTH_CODE_S user_input
;
378 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
379 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
382 aux_value
.xoauth2
= oa2
;
383 aux_value
.code_success
= 'e';
384 aux_value
.code_failure
= 'e';
385 aux_value
.code_wait
= NO_OP_COMMAND
;
387 so_puts(in_store
, "<HTML><P>");
388 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name
);
389 so_puts(in_store
, tmp
);
390 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account with username <B>%s</B> using the %s method."), name
, mb
->user
, method
),
391 so_puts(in_store
, tmp
);
393 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
395 _("</P><P>To sign in, use a web browser to open the page <A HREF=\"%s\">%s</A> and enter the code %s."),
396 deviceinfo
->verification_uri
, deviceinfo
->verification_uri
, deviceinfo
->user_code
);
397 so_puts(in_store
, tmp
);
400 so_puts(in_store
, "</P><P>");
401 so_puts(in_store
, (char *) deviceinfo
->message
);
403 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
404 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), name
);
405 so_puts(in_store
, tmp
);
406 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
408 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 "));
409 so_puts(in_store
, _("to grant access to Alpine to your data. "));
411 cache_method_message(in_store
);
413 so_puts(in_store
, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit."));
414 so_puts(in_store
, _("</P></HTML>"));
416 so_seek(in_store
, 0L, 0);
417 init_handles(&handles
);
419 gf_link_filter(gf_html2plain
,
420 gf_html2plain_opt(NULL
,
421 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
422 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
423 gf_set_so_readc(&gc
, in_store
);
424 gf_set_so_writec(&pc
, out_store
);
426 gf_clear_so_writec(out_store
);
427 gf_clear_so_readc(in_store
);
429 memset(&sargs
, 0, sizeof(SCROLL_S
));
430 sargs
.text
.handles
= handles
;
431 sargs
.text
.text
= so_text(out_store
);
432 sargs
.text
.src
= CharStar
;
433 sargs
.text
.desc
= _("help text");
434 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
435 sargs
.proc
.tool
= oauth2_auth_answer
;
436 sargs
.proc
.data
.p
= (void *)&user_input
;
437 sargs
.keys
.menu
= &oauth2_device_auth_keymenu
;
438 /* don't want to re-enter c-client */
439 sargs
.quell_newmail
= 1;
440 setbitmap(sargs
.keys
.bitmap
);
441 sargs
.help
.text
= h_oauth2_start_device
;
442 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
443 sargs
.aux_function
= oauth2deviceinfo_get_accesscode
;
444 sargs
.aux_value
= (void *) &aux_value
;
445 sargs
.aux_condition
= oauth2_elapsed_done
;
446 sargs
.decode_aux_rv_value
= oauth2device_decode_reply
;
447 sargs
.aux_rv_value
= (void *) &aux_rv_value
;
450 if(scrolltool(&sargs
) == MC_NO
)
451 ps_global
->user_says_cancel
= 1;
452 ps_global
->mangled_screen
= 1;
453 ps_global
->painted_body_on_startup
= 0;
454 ps_global
->painted_footer_on_startup
= 0;
455 } while (user_input
.answer
!= 'e');
459 free_handles(&handles
);
460 oauth2_elapsed_done(NULL
);
464 * If screen hasn't been initialized yet, use want_to.
468 tmp_20k_buf
[0] = '\0';
469 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
470 _("Authorizing Alpine Access to %s Email Services\n\n"), name
);
471 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
473 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
474 _("Alpine is attempting to log you into your %s account with username %s using the %s method."), name
, mb
->user
, method
);
475 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
477 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
478 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
479 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
480 deviceinfo
->verification_uri
, deviceinfo
->user_code
);
481 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
484 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
485 "%s\n\n", deviceinfo
->message
);
486 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
489 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
490 _("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
);
491 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
493 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
494 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
495 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
497 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
498 "%s", _("to grant access to Alpine to your data.\n\n"));
499 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
501 cache_method_message_no_screen();
503 display_init_err(tmp_20k_buf
, 0);
504 memset((void *)tmp
, 0, sizeof(tmp
));
505 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
506 tmp
[sizeof(tmp
)-1] = '\0';
508 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
512 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
513 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
514 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
516 aux_value
.xoauth2
= oa2
;
517 aux_value
.code_success
= 'y';
518 aux_value
.code_failure
= 'n';
519 aux_value
.code_wait
= 'w';
521 strncpy(tmp
, _("Continue waiting"), sizeof(tmp
));
522 tmp
[sizeof(tmp
)-1] = '\0';
524 if(oauth2_elapsed_done((void *) &aux_value
) == 0)
525 oauth2deviceinfo_get_accesscode((void *) &aux_value
, (void *) &rv
);
526 ch
= oauth2device_decode_reply((void *) &aux_value
, (void *) &rv
);
527 } while (ch
== 'w' || want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y');
528 oauth2_elapsed_done(NULL
);
534 oauth2_get_access_code(unsigned char *url
, char *method
, OAUTH2_S
*oauth2
, NETMBX
*mb
, int *tryanother
)
536 char tmp
[MAILTMPLEN
];
539 dprint((2, "-- oauth2_get_access_code\n"));
540 ps_global
->in_xoauth2_auth
= 1;
543 STORE_S
*in_store
, *out_store
;
546 HANDLE_S
*handles
= NULL
;
547 AUTH_CODE_S user_input
;
549 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
550 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
553 so_puts(in_store
, "<HTML><BODY><P>");
554 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2
->name
);
555 so_puts(in_store
, tmp
);
556 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account with username <B>%s</B> using the %s method."), oauth2
->name
, mb
->user
, method
),
557 so_puts(in_store
, tmp
);
559 if(strucmp((char *) oauth2
->name
, (char *) GMAIL_NAME
) == 0 && strstr(url
, (char *) GMAIL_ID
) != NULL
){
560 so_puts(in_store
, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
561 so_puts(in_store
, _("</P><P> First you must register Alpine with Google and create a client-id and client-secret.</P>"));
562 so_puts(in_store
, _("<UL> "));
563 so_puts(in_store
, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
564 so_puts(in_store
, _("and create a project. The name of the project is not important."));
565 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."));
566 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."));
567 so_puts(in_store
, _("<LI> Create OAUTH Credentials."));
568 so_puts(in_store
, _("</UL> "));
569 so_puts(in_store
, _("<P> As a result of this process, you will get a client-id and a client-secret."));
570 so_puts(in_store
, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently,"));
571 so_puts(in_store
, _(" then retry login into Gmail's server."));
572 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://alpineapp.email/alpine/alpine-info/misc/RegisteringAlpineinGmail.html\">link</A>."));
575 so_puts(in_store
, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
576 so_puts(in_store
,"</P><P>");
577 sprintf(tmp_20k_buf
, _("<A HREF=\"%s\">%s</A>"), url
, url
);
578 so_puts(in_store
, tmp_20k_buf
);
580 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
581 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2
->name
);
582 so_puts(in_store
, tmp
);
583 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
585 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. "));
586 so_puts(in_store
, _(" At the end of this process, you will be given an access code or redirected to a web page."));
587 so_puts(in_store
, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
588 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. "));
590 cache_method_message(in_store
);
593 so_puts(in_store
, _("</P><P> If you do not wish to proceed, cancel by pressing 'E' to exit."));
594 so_puts(in_store
, _("</P></BODY></HTML>"));
596 so_seek(in_store
, 0L, 0);
597 init_handles(&handles
);
599 gf_link_filter(gf_html2plain
,
600 gf_html2plain_opt(NULL
,
601 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
602 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
603 gf_set_so_readc(&gc
, in_store
);
604 gf_set_so_writec(&pc
, out_store
);
606 gf_clear_so_writec(out_store
);
607 gf_clear_so_readc(in_store
);
609 memset(&sargs
, 0, sizeof(SCROLL_S
));
610 sargs
.text
.handles
= handles
;
611 sargs
.text
.text
= so_text(out_store
);
612 sargs
.text
.src
= CharStar
;
613 sargs
.text
.desc
= _("help text");
614 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
615 sargs
.proc
.tool
= oauth2_auth_answer
;
616 sargs
.proc
.data
.p
= (void *)&user_input
;
617 sargs
.keys
.menu
= &oauth2_auth_keymenu
;
618 /* don't want to re-enter c-client */
619 sargs
.quell_newmail
= 1;
620 setbitmap(sargs
.keys
.bitmap
);
621 sargs
.help
.text
= h_oauth2_start
;
622 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
625 if(scrolltool(&sargs
) == MC_NO
)
626 ps_global
->user_says_cancel
= 1;
627 ps_global
->mangled_screen
= 1;
628 ps_global
->painted_body_on_startup
= 0;
629 ps_global
->painted_footer_on_startup
= 0;
630 } while (user_input
.answer
!= 'e');
632 if(!struncmp(user_input
.code
, "http://", 7)
633 || !struncmp(user_input
.code
, "https://", 8)){
635 s
= strstr(user_input
.code
, "code=");
643 else code
= user_input
.code
? cpystr(user_input
.code
) : NULL
;
644 if(user_input
.code
) fs_give((void **) &user_input
.code
);
646 if(code
== NULL
) *tryanother
= 1;
650 free_handles(&handles
);
653 int flags
, rc
, q_line
;
654 /* TRANSLATORS: user needs to input an access code from the server */
655 char *accesscodelabel
= _("Copy and Paste Access Code");
656 char prompt
[MAILTMPLEN
], token
[MAILTMPLEN
];
658 * If screen hasn't been initialized yet, use want_to.
661 tmp_20k_buf
[0] = '\0';
662 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
663 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2
->name
);
664 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
666 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
667 _("Alpine is attempting to log you into your %s account with username %s using the %s method."), oauth2
->name
, mb
->user
, method
);
668 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
670 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
671 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
672 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
674 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
676 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
678 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
679 _("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
);
680 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
682 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
683 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
684 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
686 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
687 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
688 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
690 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
691 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
692 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
694 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
695 "%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. "));
696 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
698 cache_method_message_no_screen();
700 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
701 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
702 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
704 display_init_err(tmp_20k_buf
, 0);
705 memset((void *)tmp
, 0, sizeof(tmp
));
706 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
707 tmp
[sizeof(tmp
)-1] = '\0';
709 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
710 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
711 flags
= OE_APPEND_CURRENT
;
712 sprintf(prompt
, "%s: ", accesscodelabel
);
714 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
715 prompt
, NULL
, NO_HELP
, &flags
);
716 } while (rc
!= 0 && rc
!= 1);
717 if(!struncmp(token
, "http://", 7)
718 || !struncmp(token
, "https://", 8)){
720 s
= strstr(token
, "code=");
728 else code
= token
[0] ? cpystr(token
) : NULL
;
735 void mm_login_oauth2(NETMBX
*, char *, char *, OAUTH2_S
*, long int, char *, char *);
737 /* The purpose of this function is to report to c-client the values of the
738 * different tokens and codes so that c-client can try to log in the user
739 * to the server. This function DOES NOT attempt to get these values for
740 * the user. That is attempted in the c-client side (as best as it can be
741 * done given our circumstances: no http support, no javascript support,
742 * etc.). This is the best we can do:
744 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
745 * as best as we can. Unloaded means that there is no server known to
746 * connect, no access token, etc. Pretty much nothing is known about
747 * how to get access code, access token, etc. We ask the user to fill
748 * it up for us, if they can. If the user fills it up we save those
751 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
752 * save the information that c-client got for us.
754 * 3. When saving this information we use the password caching facilities,
755 * but we must do it in a different format so that old information and
756 * new information are not mixed. In order to accommodate this for new
757 * authentication methods, we save the information in the same fields,
758 * but this time we modify it slightly, so that old functions fail to
759 * understand the new information and so not modify it nor use it. The
760 * modification is simple: Every piece of information that was saved
761 * before is prepended XOAUTH2\001 to indicate the authentication
762 * method for which it works. The character \001 is a separator. New
763 * Alpine will know how to deal with this, but old versions, will not
764 * strip this prefix from the information and fail to get the
765 * information or modify it when needed. Only new versions of Alpine will
766 * know how to process this information.
767 * new_value = authenticator_method separator old_value
768 * authenticator_method = "XOAUTH2_S"
770 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
771 * authenticator is "XOAUTH2\001imap.gmail.com".
772 * In addition, the password field is not used to encode the password
773 * anymore, it is used to save login information needed in a format that
774 * the caller function chooses, but that must be preceded by the
775 * "authenticator_method separator" code as above.
778 mm_login_oauth2(NETMBX
*mb
, char *user
, char *method
,
779 OAUTH2_S
*login
, long int trial
,
780 char *usethisprompt
, char *altuserforcache
)
782 char *token
, tmp
[MAILTMPLEN
];
783 char prompt
[4*MAILTMPLEN
];
784 char *OldRefreshToken
, *OldAccessToken
;
785 char *NewRefreshToken
, *NewAccessToken
;
786 char *SaveRefreshToken
, *SaveAccessToken
;
787 /* TRANSLATORS: A label for the hostname that the user is logging in on */
788 char *hostlabel
= _("HOST");
789 /* TRANSLATORS: user is logging in as a particular user (a particular
790 login name), this is just labelling that user name. */
791 char *userlabel
= _("USER");
792 STRLIST_S hostlist
[2], hostlist2
[OAUTH2_TOT_EQUIV
+1];
793 int len
, q_line
, flags
, i
, j
;
796 int ChangeAccessToken
, ChangeRefreshToken
, ChangeExpirationTime
;
797 OAUTH2_S
*oa2list
, *oa2
;
799 unsigned long OldExpirationTime
, NewExpirationTime
, SaveExpirationTime
;
800 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
801 int preserve_password
= -1;
804 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
805 trial
, mb
->user
? mb
->user
: "(null)",
806 mb
->service
? mb
->service
: "(null)",
807 mb
->port
? " port=" : "",
808 mb
->port
? comatose(mb
->port
) : "",
809 altuserforcache
? " altuserforcache =" : "",
810 altuserforcache
? altuserforcache
: ""));
812 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
814 save_in_init
= ps_global
->in_init_seq
;
815 ps_global
->in_init_seq
= 0;
816 ps_global
->no_newmail_check_from_optionally_enter
= 1;
818 /* make sure errors are seen */
819 if(ps_global
->ttyo
&& !ps_global
->noshow_error
820 && login
&& (login
->flags
& OA2_OPENSTREAM
))
821 flush_status_messages(0);
823 if(login
&& (login
->flags
& OA2_OPENSTREAM
))
824 login
->flags
|= ~OA2_OPENSTREAM
;
826 token
= NULL
; /* start from scratch */
828 hostlist
[0].name
= mb
->host
;
829 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
830 hostlist
[0].next
= &hostlist
[1];
831 hostlist
[1].name
= mb
->orighost
;
832 hostlist
[1].next
= NULL
;
835 hostlist
[0].next
= NULL
;
837 if(hostlist
[0].name
){
838 dprint((9, "mm_login_oauth2: host=%s\n",
839 hostlist
[0].name
? hostlist
[0].name
: "?"));
840 if(hostlist
[0].next
&& hostlist
[1].name
){
841 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist
[1].name
));
845 if(trial
== 0L && !altuserforcache
){
846 if(*mb
->user
!= '\0')
847 strncpy(user
, mb
->user
, NETMAXUSER
);
849 flags
= OE_APPEND_CURRENT
;
850 sprintf(prompt
, "%s: %s - %s: ", hostlabel
, mb
->orighost
, userlabel
);
851 optionally_enter(user
, q_line
, 0, NETMAXUSER
, prompt
, NULL
, NO_HELP
, &flags
);
853 user
[NETMAXUSER
-1] = '\0';
857 * We check to see if the server we are going to log in to is already
858 * registered. This gives us a list of servers with the same
859 * credentials, so we use the same credentials for all of them.
862 for(registered
= 0, oa2list
= alpine_oauth2_list
;
863 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
865 for(i
= 0; i
< OAUTH2_TOT_EQUIV
866 && oa2list
->host
[i
] != NULL
867 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
868 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
875 x
= oauth2_get_client_info(oa2list
->name
, user
);
876 if(!x
) return; /* user cancelled, let's get out of here */
878 int authorize
= 0, device
= 0;
879 for(oa2list
= alpine_oauth2_list
;
880 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
882 for(i
= 0; i
< OAUTH2_TOT_EQUIV
883 && oa2list
->host
[i
] != NULL
884 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
885 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
886 char *flow
= oa2list
->server_mthd
[0].name
? "Authorize"
887 : (oa2list
->server_mthd
[1].name
? "Device" : NULL
);
888 authorize
+= oa2list
->server_mthd
[0].name
? 1 : 0;
889 device
+= oa2list
->server_mthd
[1].name
? 1 : 0;
890 if(flow
&& !strucmp(x
->flow
, flow
)) break; /* found it */
893 if(!oa2list
|| !oa2list
->host
|| !oa2list
->host
[0]){
894 snprintf(tmp_20k_buf
, SIZEOF_20KBUF
,
895 _("%s does not support or recognize flow type \"%s\". Use %s%s%s"),
898 authorize
? "\"Authorize\"" : "",
899 authorize
? (device
? " or " : "") : "",
900 device
? "\"Device\"" : "");
901 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
902 q_status_message(SM_ORDER
| SM_DING
, 3, 5, tmp_20k_buf
);
903 free_xoauth2_info(&x
);
907 free_xoauth2_info(&x
);
911 hostlist2
[i
= 0].name
= mb
->host
;
912 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
))
913 hostlist2
[++i
].name
= mb
->orighost
;
915 for(j
= 0; j
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[j
] != NULL
; j
++){
917 for(k
= 0; k
<= i
&& hostlist2
[k
].name
918 && strcmp(hostlist2
[k
].name
, oa2list
->host
[j
]); k
++);
920 hostlist2
[++i
].name
= oa2list
->host
[j
];
922 hostlist2
[i
+1].name
= NULL
;
923 hostlist2
[i
+1].next
= NULL
;
924 for(j
= i
; j
>= 0; j
--)
925 hostlist2
[j
].next
= &hostlist2
[j
+1];
928 if(registered
){ /* redo the app_id, no questions asked */
929 free_id(&ps_global
->id
);
930 ps_global
->id
= set_alpine_id(oa2list
->app_id
? oa2list
->app_id
: PACKAGE_NAME
, PACKAGE_VERSION
);
931 mail_parameters(NULL
, SET_IDPARAMS
, (void *) ps_global
->id
);
935 oa2list
->param
[OA2_State
].value
= login
->param
[OA2_State
].value
;
937 if(login
->cancel_refresh_token
){
938 imap_delete_passwd_auth(&mm_login_list
, user
,
939 registered
? hostlist2
: hostlist
,
940 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
);
941 #ifdef LOCAL_PASSWD_CACHE
942 write_passfile(ps_global
->pinerc
, mm_login_list
);
943 #endif /* LOCAL_PASSWD_CACHE */
947 * We check if we have a refresh token saved somewhere, if so
948 * we use it to get a new access token, otherwise we need to
949 * get an access code so we can get (and save) a refresh token
950 * and use the access token.
952 if(trial
== 0L && !altuserforcache
){
953 /* Search for a refresh token that is already loaded ... */
954 if(imap_get_passwd_auth(mm_login_list
, &token
, user
,
955 registered
? hostlist2
: hostlist
,
956 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
957 dprint((9, "mm_login_oauth2: found a refresh token\n"));
958 ps_global
->no_newmail_check_from_optionally_enter
= 0;
959 ps_global
->in_init_seq
= save_in_init
;
961 #ifdef LOCAL_PASSWD_CACHE
962 /* or see if we have saved one in the local password cache and load it */
963 else if(get_passfile_passwd_auth(ps_global
->pinerc
, &token
,
964 user
, registered
? hostlist2
: hostlist
,
965 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
966 imap_set_passwd_auth(&mm_login_list
, token
, user
,
967 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
968 update_passfile_hostlist_auth(ps_global
->pinerc
, user
, hostlist
,
969 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
);
970 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
971 ps_global
->no_newmail_check_from_optionally_enter
= 0;
972 ps_global
->in_init_seq
= save_in_init
;
974 if(token
&& *token
) preserve_password
= 1; /* resave it, no need to ask */
975 #endif /* LOCAL_PASSWD_CACHE */
977 user
[NETMAXUSER
-1] = '\0';
979 /* The Old* variables is what c_client knows */
980 OldRefreshToken
= login
->cancel_refresh_token
? NULL
: login
->param
[OA2_RefreshToken
].value
;
981 OldAccessToken
= login
->access_token
;
982 OldExpirationTime
= login
->expiration
;
984 /* The New* variables is what Alpine knows */
985 NewRefreshToken
= NewAccessToken
= NULL
;
986 NewExpirationTime
= 0L;
987 ChangeAccessToken
= ChangeRefreshToken
= ChangeExpirationTime
= 0;
989 /* We have done all steps that cancellation requires us by this time */
990 if(login
->cancel_refresh_token
)
991 login
->cancel_refresh_token
= 0;
997 t
= strchr(s
, PWDAUTHSEP
);
999 NewRefreshToken
= cpystr(s
);
1002 NewRefreshToken
= cpystr(s
);
1004 t
= strchr(s
, PWDAUTHSEP
);
1006 NewAccessToken
= cpystr(s
);
1009 NewAccessToken
= cpystr(s
);
1011 NewExpirationTime
= strtol(s
, &s
, 10);
1012 if(NewExpirationTime
<= 0 || NewExpirationTime
<= time(0))
1013 NewExpirationTime
= 0L;
1016 /* check we got good information, and send good information below */
1017 if(NewRefreshToken
&& !*NewRefreshToken
)
1018 fs_give((void **) &NewRefreshToken
);
1019 if(NewAccessToken
&& (NewExpirationTime
== 0L || !*NewAccessToken
))
1020 fs_give((void **) &NewAccessToken
);
1023 if(NewRefreshToken
== NULL
)
1024 login
->first_time
++;
1026 if(login
->first_time
){ /* count how many authorization methods we support */
1029 for(nmethods
= 0, oa2
= alpine_oauth2_list
; oa2
&& oa2
->name
; oa2
++){
1030 for(j
= 0; j
< OAUTH2_TOT_EQUIV
1032 && oa2
->host
[j
] != NULL
1033 && strucmp(oa2
->host
[j
], mb
->orighost
) != 0; j
++);
1034 if(oa2
&& oa2
->host
&& j
< OAUTH2_TOT_EQUIV
&& oa2
->host
[j
]
1035 && ((oa2
->server_mthd
[0].name
&& (oa2
->flags
& OA2_AUTHORIZE
))
1036 || (oa2
->server_mthd
[1].name
&& (oa2
->flags
& OA2_DEVICE
))))
1041 oa2list
= oauth2_select_flow(mb
->orighost
);
1043 if(!oa2list
) registered
= 0;
1046 /* Default to saving what we already had saved */
1048 SaveRefreshToken
= NewRefreshToken
;
1049 SaveAccessToken
= NewAccessToken
;
1050 SaveExpirationTime
= NewExpirationTime
;
1052 /* Translation of the logic below:
1053 * if (c-client has a refresh token
1054 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1055 forget the Alpine refresh token;
1056 make the Alpine Refresh token = c-client refresh token.;
1057 signal that we changed the refresh token;
1058 In this situation we do not need to clear up the Alpine access token. This will
1059 expire, so we can use it until it expires. We can save the c-client refresh token
1060 together with the Alpine Access Token and the expiration date of the Alpine Access
1062 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1063 forget the Alpine refresh token;
1064 if Alpine has an access token, forget it;
1065 reset the expiration time
1066 signal that we changed the refresh token; (because the service expired it)
1070 if(OldRefreshToken
!= NULL
1071 && (NewRefreshToken
== NULL
|| strcmp(OldRefreshToken
, NewRefreshToken
))){
1072 if(NewRefreshToken
) fs_give((void **) &NewRefreshToken
);
1073 NewRefreshToken
= cpystr(OldRefreshToken
);
1074 ChangeRefreshToken
++;
1075 SaveRefreshToken
= OldRefreshToken
;
1076 SaveAccessToken
= NewAccessToken
;
1077 SaveExpirationTime
= NewExpirationTime
;
1078 } else if (OldRefreshToken
== NULL
&& NewRefreshToken
!= NULL
&& trial
> 0){
1079 fs_give((void **) &NewRefreshToken
);
1080 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1081 NewExpirationTime
= 0L;
1082 ChangeRefreshToken
++;
1083 SaveRefreshToken
= NULL
;
1084 SaveAccessToken
= NULL
;
1085 SaveExpirationTime
= 0L;
1088 if(OldAccessToken
!= NULL
1089 && (NewAccessToken
== NULL
|| strcmp(OldAccessToken
, NewAccessToken
))){
1090 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1091 NewAccessToken
= cpystr(OldAccessToken
);
1092 NewAccessToken
= OldAccessToken
;
1093 ChangeAccessToken
++;
1094 NewExpirationTime
= OldExpirationTime
;
1095 SaveRefreshToken
= NewRefreshToken
;
1096 SaveAccessToken
= NewAccessToken
;
1097 SaveExpirationTime
= NewExpirationTime
;
1101 login
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1102 login
->access_token
= SaveAccessToken
;
1103 login
->expiration
= SaveExpirationTime
;
1105 oa2list
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1106 oa2list
->access_token
= SaveAccessToken
;
1107 oa2list
->expiration
= SaveExpirationTime
;
1108 oa2list
->first_time
= login
->first_time
;
1109 oa2list
->cancel_refresh_token
= login
->cancel_refresh_token
;
1110 *login
= *oa2list
; /* load login pointer */
1112 if(token
) fs_give((void **) &token
);
1114 if(!ChangeAccessToken
&& !ChangeRefreshToken
)
1117 /* get ready to save this information. The format will be
1118 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1119 * (spaces added for clarity, \001 is PWDAUTHSEP)
1121 sprintf(tmp
, "%lu", SaveExpirationTime
);
1122 tmp
[sizeof(tmp
) - 1] = '\0';
1123 len
= strlen(SaveRefreshToken
? SaveRefreshToken
: "")
1124 + strlen(SaveAccessToken
? SaveAccessToken
: "")
1126 token
= fs_get(len
+ 1);
1127 sprintf(token
, "%s%c%s%c%lu",
1128 SaveRefreshToken
? SaveRefreshToken
: "", PWDAUTHSEP
,
1129 SaveAccessToken
? SaveAccessToken
: "", PWDAUTHSEP
,
1130 SaveExpirationTime
);
1132 /* remember the access information for next time */
1133 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
1134 imap_set_passwd_auth(&mm_login_list
, token
,
1135 altuserforcache
? altuserforcache
: user
, hostlist
,
1136 (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
1137 #ifdef LOCAL_PASSWD_CACHE
1138 /* if requested, remember it on disk for next session */
1139 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
1140 set_passfile_passwd_auth(ps_global
->pinerc
, &token
,
1141 altuserforcache
? altuserforcache
: user
, hostlist
,
1142 (mb
->sslflag
||mb
->tlsflag
),
1143 (preserve_password
== -1 ? 0
1144 : (preserve_password
== 0 ? 2 :1)), OA2NAME
);
1145 #endif /* LOCAL_PASSWD_CACHE */
1146 if (token
) fs_give((void **) &token
);
1147 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1151 set_alpine_id(char *pname
, char *pversion
)
1155 if(!pname
|| !pversion
) return NULL
;
1157 id
= fs_get(sizeof(IDLIST
));
1158 id
->name
= cpystr("name");
1159 id
->value
= cpystr(pname
);
1160 id
->next
= fs_get(sizeof(IDLIST
));
1161 id
->next
->name
= cpystr("version");
1162 id
->next
->value
= cpystr(pversion
);
1163 id
->next
->next
= NULL
;
1168 pine_delete_pwd(NETMBX
*mb
, char *user
)
1170 char hostlist0
[MAILTMPLEN
], hostlist1
[MAILTMPLEN
];
1171 char port
[20], non_def_port
[20];
1172 STRLIST_S hostlist
[2];
1175 /* do not invalidate password on cancel */
1176 if(ps_global
->user_says_cancel
!= 0)
1179 dprint((9, "pine_delete_pwd\n"));
1181 /* setup hostlist */
1182 non_def_port
[0] = '\0';
1183 if(mb
->port
&& mb
->service
&&
1184 (sv
= getservbyname(mb
->service
, "tcp")) &&
1185 (mb
->port
!= ntohs(sv
->s_port
))){
1186 snprintf(non_def_port
, sizeof(non_def_port
), ":%lu", mb
->port
);
1187 non_def_port
[sizeof(non_def_port
)-1] = '\0';
1188 dprint((9, "mm_login: using non-default port=%s\n",
1189 non_def_port
? non_def_port
: "?"));
1193 strncpy(hostlist0
, mb
->host
, sizeof(hostlist0
)-1);
1194 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1195 strncat(hostlist0
, non_def_port
, sizeof(hostlist0
)-strlen(hostlist0
)-1);
1196 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1197 hostlist
[0].name
= hostlist0
;
1198 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1199 strncpy(hostlist1
, mb
->orighost
, sizeof(hostlist1
)-1);
1200 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1201 strncat(hostlist1
, non_def_port
, sizeof(hostlist1
)-strlen(hostlist1
)-1);
1202 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1203 hostlist
[0].next
= &hostlist
[1];
1204 hostlist
[1].name
= hostlist1
;
1205 hostlist
[1].next
= NULL
;
1208 hostlist
[0].next
= NULL
;
1211 hostlist
[0].name
= mb
->host
;
1212 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1213 hostlist
[0].next
= &hostlist
[1];
1214 hostlist
[1].name
= mb
->orighost
;
1215 hostlist
[1].next
= NULL
;
1218 hostlist
[0].next
= NULL
;
1220 imap_delete_passwd(&mm_login_list
, user
, hostlist
, mb
->sslflag
||mb
->tlsflag
);
1221 #ifdef LOCAL_PASSWD_CACHE
1222 write_passfile(ps_global
->pinerc
, mm_login_list
);
1223 #endif /* LOCAL_PASSWD_CACHE */
1226 /*----------------------------------------------------------------------
1227 receive notification from IMAP
1229 Args: stream -- Mail stream message is relevant to
1230 string -- The message text
1231 errflg -- Set if it is a serious error
1233 Result: message displayed in status line
1235 The facility is for general notices, such as connection to server;
1236 server shutting down etc... It is used infrequently.
1237 ----------------------------------------------------------------------*/
1239 mm_notify(MAILSTREAM
*stream
, char *string
, long int errflg
)
1244 now
= time((time_t *)0);
1245 tm_now
= localtime(&now
);
1247 /* be sure to log the message... */
1249 if(ps_global
->debug_imap
|| ps_global
->debugmem
)
1250 dprint((errflg
== TCPDEBUG
|| errflg
== HTTPDEBUG
? 7 : 2,
1251 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1252 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1253 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1254 (!errflg
) ? "babble" :
1255 (errflg
== ERROR
) ? "error" :
1256 (errflg
== WARN
) ? "warning" :
1257 (errflg
== PARSE
) ? "parse" :
1258 (errflg
== TCPDEBUG
) ? "tcp" :
1259 (errflg
== HTTPDEBUG
) ? "http" :
1260 (errflg
== BYE
) ? "bye" : "unknown",
1261 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1262 string
? string
: "?"));
1265 snprintf(ps_global
->last_error
, sizeof(ps_global
->last_error
), "%s : %.*s",
1266 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1267 (int) MIN(MAX_SCREEN_COLS
, sizeof(ps_global
->last_error
)-70),
1269 ps_global
->last_error
[ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1270 : sizeof(ps_global
->last_error
)-1] = '\0';
1273 * Then either set special bits in the pine struct or
1274 * display the message if it's tagged as an "ALERT" or
1275 * its errflg > NIL (i.e., WARN, or ERROR)
1279 * We'd like to sp_mark_stream_dead() here but we can't do that because
1280 * that might call mail_close and we are already in a c-client callback.
1281 * So just set the dead bit and clean it up later.
1283 sp_set_dead_stream(stream
, 1);
1284 else if(!strncmp(string
, "[TRYCREATE]", 11))
1285 ps_global
->try_to_create
= 1;
1286 else if(!strncmp(string
, "[REFERRAL ", 10))
1287 ; /* handled in the imap_referral() callback */
1288 else if(!strncmp(string
, "[ALERT]", 7))
1289 q_status_message2(SM_MODAL
, 3, 3,
1290 _("Alert received while accessing \"%s\": %s"),
1291 (stream
&& stream
->mailbox
)
1292 ? stream
->mailbox
: "-no folder-",
1293 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf
+10000),
1294 SIZEOF_20KBUF
-10000, string
));
1295 else if(!strncmp(string
, "[UNSEEN ", 8)){
1299 for(p
= string
+ 8; isdigit(*p
); p
++)
1300 n
= (n
* 10) + (*p
- '0');
1302 sp_set_first_unseen(stream
, n
);
1304 else if(!strncmp(string
, "[READ-ONLY]", 11)
1305 && !(stream
&& stream
->mailbox
&& IS_NEWS(stream
)))
1306 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, "%s : %s",
1307 (stream
&& stream
->mailbox
)
1308 ? stream
->mailbox
: "-no folder-",
1310 else if((errflg
&& errflg
!= BYE
&& errflg
!= PARSE
)
1311 && !ps_global
->noshow_error
1313 && (ps_global
->noshow_warn
|| (stream
&& stream
->unhealthy
))))
1314 q_status_message(SM_ORDER
| ((errflg
== ERROR
) ? SM_DING
: 0),
1315 3, 6, ps_global
->last_error
);
1319 /*----------------------------------------------------------------------
1320 Queue imap log message for display in the message line
1322 Args: string -- The message
1323 errflg -- flag set to 1 if pertains to an error
1325 Result: Message queued for display
1327 The c-client/imap reports most of it's status and errors here
1330 mm_log(char *string
, long int errflg
)
1332 char message
[sizeof(ps_global
->c_client_error
)];
1334 int was_capitalized
;
1335 static char saw_kerberos_init_warning
;
1339 now
= time((time_t *)0);
1340 tm_now
= localtime(&now
);
1342 dprint((((errflg
== TCPDEBUG
) && ps_global
->debug_tcp
) ? debug
:
1343 (errflg
== TCPDEBUG
) ? 10 :
1344 ((errflg
== HTTPDEBUG
) && ps_global
->debug_http
) ? debug
:
1345 (errflg
== HTTPDEBUG
) ? 10 : 2,
1346 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1347 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1348 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1349 (!errflg
) ? "babble" :
1350 (errflg
== ERROR
) ? "error" :
1351 (errflg
== WARN
) ? "warning" :
1352 (errflg
== PARSE
) ? "parse" :
1353 (errflg
== TCPDEBUG
) ? "tcp" :
1354 (errflg
== HTTPDEBUG
) ? "http" :
1355 (errflg
== BYE
) ? "bye" : "unknown",
1356 string
? string
: "?"));
1358 if(errflg
== ERROR
&& !strncmp(string
, "[TRYCREATE]", 11)){
1359 ps_global
->try_to_create
= 1;
1362 else if(ps_global
->try_to_create
1363 || !strncmp(string
, "[CLOSED]", 8)
1364 || (sp_dead_stream(ps_global
->mail_stream
) && strstr(string
, "No-op")))
1366 * Don't display if creating new folder OR
1367 * warning about a dead stream ...
1371 /* if we took too long to authenticate, ignore this error */
1372 if(ps_global
->in_xoauth2_auth
&& strstr(string
, "[CLOSED]"))
1375 strncpy(message
, string
, sizeof(message
));
1376 message
[sizeof(message
) - 1] = '\0';
1378 if(errflg
== WARN
&& srchstr(message
, "try running kinit") != NULL
){
1379 if(saw_kerberos_init_warning
)
1382 saw_kerberos_init_warning
= 1;
1385 /*---- replace all "mailbox" with "folder" ------*/
1386 occurence
= srchstr(message
, "mailbox");
1389 || isspace((unsigned char) *(occurence
+7))
1390 || *(occurence
+7) == ':'){
1391 was_capitalized
= isupper((unsigned char) *occurence
);
1392 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 7, (errflg
== PARSE
? "address" : "folder"));
1394 *occurence
= (errflg
== PARSE
? 'A' : 'F');
1399 occurence
= srchstr(occurence
, "mailbox");
1402 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1403 occurence
= srchstr(message
, "GSSAPI");
1406 || isspace((unsigned char) *(occurence
+6))
1407 || *(occurence
+6) == ':')
1408 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 6, "Kerberos");
1412 occurence
= srchstr(occurence
, "GSSAPI");
1416 ps_global
->mm_log_error
= 1;
1418 if(errflg
== PARSE
|| (errflg
== ERROR
&& ps_global
->noshow_error
))
1419 strncpy(ps_global
->c_client_error
, message
,
1420 sizeof(ps_global
->c_client_error
));
1422 if(ps_global
->noshow_error
1423 || (ps_global
->noshow_warn
&& errflg
== WARN
)
1424 || !(errflg
== ERROR
|| errflg
== WARN
))
1425 return; /* Only care about errors; don't print when asked not to */
1427 /*---- Display the message ------*/
1428 q_status_message((errflg
== ERROR
) ? (SM_ORDER
| SM_DING
) : SM_ORDER
,
1430 strncpy(ps_global
->last_error
, message
, sizeof(ps_global
->last_error
));
1431 ps_global
->last_error
[sizeof(ps_global
->last_error
) - 1] = '\0';
1435 mm_login_method_work(NETMBX
*mb
, char *user
, void *login
, long int trial
,
1436 char *method
, char *usethisprompt
, char *altuserforcache
)
1440 if(strucmp(method
, OA2NAME
) == 0 || strucmp(method
, BEARERNAME
) == 0)
1441 mm_login_oauth2(mb
, user
, method
, (OAUTH2_S
*) login
, trial
, usethisprompt
, altuserforcache
);
1445 mm_login_work(NETMBX
*mb
, char *user
, char **pwd
, long int trial
,
1446 char *usethisprompt
, char *altuserforcache
)
1448 char tmp
[MAILTMPLEN
];
1449 char prompt
[1000], *last
;
1450 char port
[20], non_def_port
[20], insecure
[20];
1451 char defuser
[NETMAXUSER
];
1452 char hostleadin
[80], hostname
[200], defubuf
[200];
1453 char logleadin
[80], pwleadin
[50];
1454 char hostlist0
[MAILTMPLEN
], hostlist1
[MAILTMPLEN
];
1455 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1456 that the password will be sent unencrypted over the network. This is
1457 just a warning message that gets added parenthetically when the user
1458 is asked for a password. */
1459 char *insec
= _(" (INSECURE)");
1460 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1461 after having already failed at least once. */
1462 char *retry
= _("Retrying - ");
1463 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1464 char *hostlabel
= _("HOST");
1465 /* TRANSLATORS: user is logging in as a particular user (a particular
1466 login name), this is just labelling that user name. */
1467 char *userlabel
= _("USER");
1468 STRLIST_S hostlist
[2];
1470 int len
, rc
, q_line
, flags
;
1471 int oespace
, avail
, need
, save_dont_use
;
1474 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1475 int preserve_password
= -1;
1478 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1479 trial
, mb
->user
? mb
->user
: "(null)",
1480 mb
->service
? mb
->service
: "(null)",
1481 mb
->port
? " port=" : "",
1482 mb
->port
? comatose(mb
->port
) : "",
1483 altuserforcache
? " altuserforcache =" : "",
1484 altuserforcache
? altuserforcache
: ""));
1485 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
1487 save_in_init
= ps_global
->in_init_seq
;
1488 ps_global
->in_init_seq
= 0;
1489 ps_global
->no_newmail_check_from_optionally_enter
= 1;
1491 /* make sure errors are seen */
1493 flush_status_messages(0);
1495 /* redo app id in case we are logging in to an IMAP server that supports the IMAP ID extension */
1496 free_id(&ps_global
->id
);
1497 ps_global
->id
= set_alpine_id(PACKAGE_NAME
, PACKAGE_VERSION
);
1498 mail_parameters(NULL
, SET_IDPARAMS
, (void *) ps_global
->id
);
1501 * Add port number to hostname if going through a tunnel or something
1503 non_def_port
[0] = '\0';
1504 if(mb
->port
&& mb
->service
&&
1505 (sv
= getservbyname(mb
->service
, "tcp")) &&
1506 (mb
->port
!= ntohs(sv
->s_port
))){
1507 snprintf(non_def_port
, sizeof(non_def_port
), ":%lu", mb
->port
);
1508 non_def_port
[sizeof(non_def_port
)-1] = '\0';
1509 dprint((9, "mm_login: using non-default port=%s\n", non_def_port
));
1513 * set up host list for sybil servers...
1516 strncpy(hostlist0
, mb
->host
, sizeof(hostlist0
)-1);
1517 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1518 strncat(hostlist0
, non_def_port
, sizeof(hostlist0
)-strlen(hostlist0
)-1);
1519 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1520 hostlist
[0].name
= hostlist0
;
1521 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1522 strncpy(hostlist1
, mb
->orighost
, sizeof(hostlist1
)-1);
1523 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1524 strncat(hostlist1
, non_def_port
, sizeof(hostlist1
)-strlen(hostlist1
)-1);
1525 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1526 hostlist
[0].next
= &hostlist
[1];
1527 hostlist
[1].name
= hostlist1
;
1528 hostlist
[1].next
= NULL
;
1531 hostlist
[0].next
= NULL
;
1534 hostlist
[0].name
= mb
->host
;
1535 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1536 hostlist
[0].next
= &hostlist
[1];
1537 hostlist
[1].name
= mb
->orighost
;
1538 hostlist
[1].next
= NULL
;
1541 hostlist
[0].next
= NULL
;
1544 if(hostlist
[0].name
){
1545 dprint((9, "mm_login: host=%s\n",
1546 hostlist
[0].name
? hostlist
[0].name
: "?"));
1547 if(hostlist
[0].next
&& hostlist
[1].name
){
1548 dprint((9, "mm_login: orighost=%s\n", hostlist
[1].name
));
1553 * Initialize user name with either
1554 * 1) /user= value in the stream being logged into,
1555 * or 2) the user name we're running under.
1557 * Note that VAR_USER_ID is not yet initialized if this login is
1558 * the one to access the remote config file. In that case, the user
1559 * can supply the username in the config file name with /user=.
1561 if(trial
== 0L && !altuserforcache
){
1562 strncpy(user
, (*mb
->user
) ? mb
->user
:
1563 ps_global
->VAR_USER_ID
? ps_global
->VAR_USER_ID
: "",
1565 user
[NETMAXUSER
-1] = '\0';
1567 /* try last working password associated with this host. */
1568 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1569 (mb
->sslflag
||mb
->tlsflag
))){
1570 dprint((9, "mm_login: found a password to try\n"));
1571 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1572 ps_global
->in_init_seq
= save_in_init
;
1576 #ifdef LOCAL_PASSWD_CACHE
1577 /* check to see if there's a password left over from last session */
1578 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1579 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1580 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1581 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1582 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1583 (mb
->sslflag
||mb
->tlsflag
));
1584 dprint((9, "mm_login: found a password in passfile to try\n"));
1585 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1586 ps_global
->in_init_seq
= save_in_init
;
1589 #endif /* LOCAL_PASSWD_CACHE */
1592 * If no explicit user name supplied and we've not logged in
1593 * with our local user name, see if we've visited this
1594 * host before as someone else.
1597 ((last
= imap_get_user(mm_login_list
, hostlist
))
1598 #ifdef LOCAL_PASSWD_CACHE
1600 (last
= get_passfile_user(ps_global
->pinerc
, hostlist
))
1601 #endif /* LOCAL_PASSWD_CACHE */
1603 strncpy(user
, last
, NETMAXUSER
);
1604 user
[NETMAXUSER
-1] = '\0';
1605 dprint((9, "mm_login: found user=%s\n",
1606 user
? user
: "?"));
1608 /* try last working password associated with this host/user. */
1609 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1610 (mb
->sslflag
||mb
->tlsflag
))){
1612 "mm_login: found a password for user=%s to try\n",
1613 user
? user
: "?"));
1614 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1615 ps_global
->in_init_seq
= save_in_init
;
1619 #ifdef LOCAL_PASSWD_CACHE
1620 /* check to see if there's a password left over from last session */
1621 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1622 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1623 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1624 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1625 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1626 (mb
->sslflag
||mb
->tlsflag
));
1628 "mm_login: found a password for user=%s in passfile to try\n",
1629 user
? user
: "?"));
1630 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1631 ps_global
->in_init_seq
= save_in_init
;
1634 #endif /* LOCAL_PASSWD_CACHE */
1637 #if !defined(DOS) && !defined(OS2)
1638 if(!*mb
->user
&& !*user
&&
1639 (last
= (ps_global
->ui
.login
&& ps_global
->ui
.login
[0])
1640 ? ps_global
->ui
.login
: NULL
)
1642 strncpy(user
, last
, NETMAXUSER
);
1643 user
[NETMAXUSER
-1] = '\0';
1644 dprint((9, "mm_login: found user=%s\n",
1645 user
? user
: "?"));
1647 /* try last working password associated with this host. */
1648 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1649 (mb
->sslflag
||mb
->tlsflag
))){
1650 dprint((9, "mm_login:ui: found a password to try\n"));
1651 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1652 ps_global
->in_init_seq
= save_in_init
;
1656 #ifdef LOCAL_PASSWD_CACHE
1657 /* check to see if there's a password left over from last session */
1658 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1659 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1660 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1661 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1662 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1663 (mb
->sslflag
||mb
->tlsflag
));
1664 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1665 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1666 ps_global
->in_init_seq
= save_in_init
;
1669 #endif /* LOCAL_PASSWD_CACHE */
1674 user
[NETMAXUSER
-1] = '\0';
1680 * Even if we have a user now, user gets a chance to change it.
1682 ps_global
->mangled_footer
= 1;
1683 if(!*mb
->user
&& !altuserforcache
){
1688 * Instead of offering user with a value that the user can edit,
1689 * we offer [user] as a default so that the user can type CR to
1690 * use it. Otherwise, the user has to type in whole name.
1692 strncpy(defuser
, user
, sizeof(defuser
)-1);
1693 defuser
[sizeof(defuser
)-1] = '\0';
1697 * Need space for "Retrying - "
1703 * about 15 chars for input
1706 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1707 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1708 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1710 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1711 hostname
[sizeof(hostname
)-1] = '\0';
1714 * Add port number to hostname if going through a tunnel or something
1717 strncpy(port
, non_def_port
, sizeof(port
));
1722 /* if not encrypted and SSL/TLS is supported */
1723 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1724 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1725 strncpy(insecure
, insec
, sizeof(insecure
));
1727 /* TRANSLATORS: user is being asked to type in their login name */
1728 snprintf(logleadin
, sizeof(logleadin
), " %s", _("ENTER LOGIN NAME"));
1730 snprintf(defubuf
, sizeof(defubuf
), "%s%s%s : ", (*defuser
) ? " [" : "",
1731 (*defuser
) ? defuser
: "",
1732 (*defuser
) ? "]" : "");
1733 defubuf
[sizeof(defubuf
)-1] = '\0';
1734 /* space reserved after prompt */
1735 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1737 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1738 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1739 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) + oespace
;
1741 /* If we're retrying cut the hostname back to the first word. */
1742 if(avail
< need
&& trial
> 0){
1745 len
= strlen(hostname
);
1746 if((p
= strchr(hostname
, '.')) != NULL
){
1748 need
-= (len
- strlen(hostname
));
1753 need
-= utf8_width(retry
);
1758 /* reduce length of logleadin */
1759 len
= utf8_width(logleadin
);
1760 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1761 longer version doesn't fit on screen */
1762 snprintf(logleadin
, sizeof(logleadin
), " %s", _("LOGIN"));
1763 need
-= (len
- utf8_width(logleadin
));
1766 /* get two spaces from hostleadin */
1767 len
= utf8_width(hostleadin
);
1768 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
1769 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
1770 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1771 need
-= (len
- utf8_width(hostleadin
));
1773 /* get rid of port */
1774 if(avail
< need
&& strlen(port
) > 0){
1775 need
-= strlen(port
);
1783 * Reduce space for hostname. Best we can do is 6 chars
1786 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
1787 len
= strlen(hostname
);
1788 strncpy(hostname
+reduce_to
-3, "...", 4);
1789 need
-= (len
- strlen(hostname
));
1791 if(avail
< need
&& strlen(insecure
) > 0){
1792 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
1794 insecure
[strlen(insecure
)-4] = ')';
1795 insecure
[strlen(insecure
)-3] = '\0';
1798 need
-= utf8_width(insecure
);
1804 if(strlen(defubuf
) > 3){
1805 len
= strlen(defubuf
);
1806 strncpy(defubuf
, " [..] :", 9);
1807 need
-= (len
- strlen(defubuf
));
1811 strncpy(defubuf
, ":", 2);
1814 * If it still doesn't fit, optionally_enter gets
1815 * to worry about it.
1823 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s",
1824 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
);
1825 prompt
[sizeof(prompt
)-1] = '\0';
1829 mm_login_alt_cue(mb
);
1831 flags
= OE_APPEND_CURRENT
;
1832 save_dont_use
= ps_global
->dont_use_init_cmds
;
1833 ps_global
->dont_use_init_cmds
= 1;
1835 if(!*user
&& *defuser
){
1836 strncpy(user
, defuser
, NETMAXUSER
);
1837 user
[NETMAXUSER
-1] = '\0';
1840 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, pwd
, NETMAXPASSWD
,
1841 #ifdef LOCAL_PASSWD_CACHE
1842 is_using_passfile() ? 1 :
1843 #endif /* LOCAL_PASSWD_CACHE */
1844 0, 0, &preserve_password
);
1845 ps_global
->dont_use_init_cmds
= save_dont_use
;
1846 if(rc
== 0 && *user
&& *pwd
&& **pwd
)
1848 #else /* !_WINDOWS */
1849 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
1850 prompt
, NULL
, help
, &flags
);
1851 #endif /* !_WINDOWS */
1852 ps_global
->dont_use_init_cmds
= save_dont_use
;
1855 help
= help
== NO_HELP
? h_oe_login
: NO_HELP
;
1860 if(rc
== 0 && !*user
){
1861 strncpy(user
, defuser
, NETMAXUSER
);
1862 user
[NETMAXUSER
-1] = '\0';
1869 if(rc
== 1 || !user
[0]) {
1870 ps_global
->user_says_cancel
= (rc
== 1);
1875 strncpy(user
, mb
->user
, NETMAXUSER
);
1876 user
[NETMAXUSER
-1] = '\0';
1879 user
[NETMAXUSER
-1] = '\0';
1881 if(!(user
[0] || altuserforcache
)){
1882 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1883 ps_global
->in_init_seq
= save_in_init
;
1888 * Now that we have a user, we can check in the cache again to see
1889 * if there is a password there. Try last working password associated
1890 * with this host and user.
1892 if(trial
== 0L && !*mb
->user
&& !altuserforcache
){
1893 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1894 (mb
->sslflag
||mb
->tlsflag
))){
1895 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1896 ps_global
->in_init_seq
= save_in_init
;
1900 #ifdef LOCAL_PASSWD_CACHE
1901 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1902 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1903 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1904 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1905 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1906 ps_global
->in_init_seq
= save_in_init
;
1909 #endif /* LOCAL_PASSWD_CACHE */
1911 else if(trial
== 0 && altuserforcache
){
1912 if(imap_get_passwd(mm_login_list
, pwd
, altuserforcache
, hostlist
,
1913 (mb
->sslflag
||mb
->tlsflag
))){
1914 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1915 ps_global
->in_init_seq
= save_in_init
;
1919 #ifdef LOCAL_PASSWD_CACHE
1920 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1921 altuserforcache
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1922 imap_set_passwd(&mm_login_list
, *pwd
, altuserforcache
,
1923 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1924 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1925 ps_global
->in_init_seq
= save_in_init
;
1928 #endif /* LOCAL_PASSWD_CACHE */
1932 * Didn't find password in cache or this isn't the first try. Ask user.
1937 * Need space for "Retrying - "
1943 * " ENTER PASSWORD: "
1944 * about 15 chars for input
1947 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1948 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1950 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1951 hostname
[sizeof(hostname
)-1] = '\0';
1954 * Add port number to hostname if going through a tunnel or something
1957 strncpy(port
, non_def_port
, sizeof(port
));
1963 /* if not encrypted and SSL/TLS is supported */
1964 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1965 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1966 strncpy(insecure
, insec
, sizeof(insecure
));
1969 strncpy(logleadin
, usethisprompt
, sizeof(logleadin
));
1970 logleadin
[sizeof(logleadin
)-1] = '\0';
1975 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
1977 strncpy(defubuf
, user
, sizeof(defubuf
)-1);
1978 defubuf
[sizeof(defubuf
)-1] = '\0';
1981 /* TRANSLATORS: user is being asked to type in their password */
1982 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("ENTER PASSWORD"));
1984 /* space reserved after prompt */
1985 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1987 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1988 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1989 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) +
1990 utf8_width(pwleadin
) + oespace
;
1992 if(avail
< need
&& trial
> 0){
1995 len
= strlen(hostname
);
1996 if((p
= strchr(hostname
, '.')) != NULL
){
1998 need
-= (len
- strlen(hostname
));
2003 need
-= utf8_width(retry
);
2009 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
2013 rplstr(pwleadin
, sizeof(pwleadin
), 1, "");
2017 /* get two spaces from hostleadin */
2018 len
= utf8_width(hostleadin
);
2019 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
2020 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
2021 hostleadin
[sizeof(hostleadin
)-1] = '\0';
2022 need
-= (len
- utf8_width(hostleadin
));
2024 /* get rid of port */
2025 if(avail
< need
&& strlen(port
) > 0){
2026 need
-= strlen(port
);
2031 len
= utf8_width(pwleadin
);
2032 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
2033 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("PASSWORD"));
2034 need
-= (len
- utf8_width(pwleadin
));
2042 * Reduce space for hostname. Best we can do is 6 chars
2045 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
2046 len
= strlen(hostname
);
2047 strncpy(hostname
+reduce_to
-3, "...", 4);
2048 need
-= (len
- strlen(hostname
));
2050 if(avail
< need
&& strlen(insecure
) > 0){
2051 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
2053 insecure
[strlen(insecure
)-4] = ')';
2054 insecure
[strlen(insecure
)-3] = '\0';
2057 need
-= utf8_width(insecure
);
2063 len
= utf8_width(logleadin
);
2064 strncpy(logleadin
, " ", sizeof(logleadin
));
2065 logleadin
[sizeof(logleadin
)-1] = '\0';
2066 need
-= (len
- utf8_width(logleadin
));
2069 reduce_to
= (need
- avail
< strlen(defubuf
) - 6) ? (strlen(defubuf
)-(need
-avail
)) : 0;
2071 strncpy(defubuf
+reduce_to
-3, "...", 4);
2080 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s%s",
2081 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
, pwleadin
);
2082 prompt
[sizeof(prompt
)-1] = '\0';
2087 mm_login_alt_cue(mb
);
2089 save_dont_use
= ps_global
->dont_use_init_cmds
;
2090 ps_global
->dont_use_init_cmds
= 1;
2091 flags
= F_ON(F_QUELL_ASTERISKS
, ps_global
) ? OE_PASSWD_NOAST
: OE_PASSWD
;
2092 flags
|= OE_KEEP_TRAILING_SPACE
;
2096 tmpp
= fs_get(NETMAXPASSWD
*sizeof(char));
2097 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, &tmpp
, NETMAXPASSWD
, 0, 1,
2098 &preserve_password
);
2099 strncpy(tmp
, tmpp
, sizeof(tmp
));
2100 tmp
[sizeof(tmp
)-1] = '\0';
2101 if(tmpp
) fs_give((void **)&tmpp
);
2103 #else /* !_WINDOWS */
2104 rc
= optionally_enter(tmp
, q_line
, 0, NETMAXPASSWD
,
2105 prompt
, NULL
, help
, &flags
);
2106 #endif /* !_WINDOWS */
2107 if(rc
!= 1) *pwd
= cpystr(tmp
);
2108 ps_global
->dont_use_init_cmds
= save_dont_use
;
2111 help
= help
== NO_HELP
? h_oe_passwd
: NO_HELP
;
2119 if(rc
== 1 || !tmp
[0]) {
2120 ps_global
->user_says_cancel
= (rc
== 1);
2122 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2123 ps_global
->in_init_seq
= save_in_init
;
2130 /* remember the password for next time */
2131 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
2132 imap_set_passwd(&mm_login_list
, *pwd
,
2133 altuserforcache
? altuserforcache
: user
, hostlist
,
2134 (mb
->sslflag
||mb
->tlsflag
), 0, 0);
2135 #ifdef LOCAL_PASSWD_CACHE
2136 /* if requested, remember it on disk for next session */
2137 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
2138 set_passfile_passwd(ps_global
->pinerc
, pwd
,
2139 altuserforcache
? altuserforcache
: user
, hostlist
,
2140 (mb
->sslflag
||mb
->tlsflag
),
2141 (preserve_password
== -1 ? 0
2142 : (preserve_password
== 0 ? 2 :1)));
2143 #endif /* LOCAL_PASSWD_CACHE */
2145 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2150 mm_login_alt_cue(NETMBX
*mb
)
2152 if(ps_global
->ttyo
){
2155 lastc
= pico_set_colors(ps_global
->VAR_TITLE_FORE_COLOR
,
2156 ps_global
->VAR_TITLE_BACK_COLOR
,
2159 mark_titlebar_dirty();
2160 PutLine0(0, ps_global
->ttyo
->screen_cols
- 1,
2161 (mb
->sslflag
||mb
->tlsflag
) ? "+" : " ");
2164 (void)pico_set_colorp(lastc
, PSC_NONE
);
2165 free_color_pair(&lastc
);
2173 /*----------------------------------------------------------------------
2174 Receive notification of an error writing to disk
2176 Args: stream -- The stream the error occurred on
2177 errcode -- The system error code (errno)
2178 serious -- Flag indicating error is serious (mail may be lost)
2180 Result: If error is non serious, the stream is marked as having an error
2181 and deletes are disallowed until error clears
2182 If error is serious this goes modal, allowing the user to retry
2183 or get a shell escape to fix the condition. When the condition is
2184 serious it means that mail existing in the mailbox will be lost
2185 if Pine exits without writing, so we try to induce the user to
2186 fix the error, go get someone that can fix the error, or whatever
2187 and don't provide an easy way out.
2190 mm_diskerror (MAILSTREAM
*stream
, long int errcode
, long int serious
)
2194 static ESCKEY_S de_opts
[] = {
2195 {'r', 'r', "R", "Retry"},
2196 {'f', 'f', "F", "FileBrowser"},
2197 {'s', 's', "S", "ShellPrompt"},
2200 #define DE_COLS (ps_global->ttyo->screen_cols)
2201 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2203 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2205 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2206 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2208 "The reported error number is %s. The last reported mail error was:"
2209 static char *de_msg
[] = {
2210 "Please try to correct the error preventing Alpine from saving your",
2211 "mail folder. For example if the disk is out of space try removing",
2212 "unneeded files. You might also contact your system administrator.",
2214 "Both Alpine's File Browser and an option to enter the system's",
2215 "command prompt are offered to aid in fixing the problem. When",
2216 "you believe the problem is resolved, choose the \"Retry\" option.",
2217 "Be aware that messages may be lost or this folder left in an",
2218 "inaccessible condition if you exit or kill Alpine before the problem",
2221 static char *de_shell_msg
[] = {
2222 "\n\nPlease attempt to correct the error preventing saving of the",
2223 "mail folder. If you do not know how to correct the problem, contact",
2224 "your system administrator. To return to Alpine, type \"exit\".",
2228 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2229 DE_FOLDER(stream
), errcode
, serious
? "" : "not "));
2230 dprint((0, "***** message: \"%s\"\n\n",
2231 ps_global
->last_error
? ps_global
->last_error
: "?"));
2234 sp_set_io_error_on_stream(stream
, 1);
2239 /* replace pine's body display with screen full of explanatory text */
2241 PutLine1(2, MAX((DE_COLS
- sizeof(DE_STR1
)
2242 - strlen(DE_FOLDER(stream
)))/2, 0),
2243 DE_STR1
, DE_FOLDER(stream
));
2245 PutLine1(3, 4, DE_STR2
, long2string(errcode
));
2247 PutLine0(4, 0, " \"");
2248 removing_leading_white_space(ps_global
->last_error
);
2249 for(i
= 4, p
= ps_global
->last_error
; *p
&& i
< DE_LINE
; ){
2250 for(s
= NULL
, q
= p
; *q
&& q
- p
< DE_COLS
- 16; q
++)
2251 if(isspace((unsigned char)*q
))
2262 PutLine0(i
, 0, " ");
2263 while(*p
&& isspace((unsigned char)*p
))
2274 for(j
= ++i
; i
< DE_LINE
&& de_msg
[i
-j
]; i
++){
2276 PutLine0(i
, 0, " ");
2277 Write_to_screen(de_msg
[i
-j
]);
2283 switch(radio_buttons(DE_PMT
, -FOOTER_ROWS(ps_global
), de_opts
,
2284 'r', 0, NO_HELP
, RB_FLUSH_IN
| RB_NO_NEWMAIL
)){
2285 case 'r' : /* Retry! */
2286 ps_global
->mangled_screen
= 1;
2289 case 'f' : /* File Browser */
2291 char full_filename
[MAXPATH
+1], filename
[MAXPATH
+1];
2294 build_path(full_filename
, ps_global
->home_dir
, filename
,
2295 sizeof(full_filename
));
2296 file_lister("DISK ERROR", full_filename
, sizeof(full_filename
),
2297 filename
, sizeof(filename
), FALSE
, FB_SAVE
);
2304 end_keyboard(ps_global
? F_ON(F_USE_FK
,ps_global
) : 0);
2305 end_tty_driver(ps_global
);
2306 for(i
= 0; de_shell_msg
[i
]; i
++)
2307 puts(de_shell_msg
[i
]);
2310 * Don't use our piping mechanism to spawn a subshell here
2311 * since it will the server (thus reentering c-client).
2318 init_tty_driver(ps_global
);
2319 init_keyboard(F_ON(F_USE_FK
,ps_global
));
2323 if(ps_global
->redrawer
)
2324 (*ps_global
->redrawer
)();
2330 pine_tcptimeout_noscreen(long int elapsed
, long int sincelast
, char *host
)
2339 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2340 snprintf(pmt
, sizeof(pmt
),
2341 _("No reply in %s seconds from server %s. Break connection"),
2342 long2string(elapsed
), host
);
2343 pmt
[sizeof(pmt
)-1] = '\0';
2344 if(want_to(pmt
, 'n', 'n', NO_HELP
, WT_FLUSH_IN
) == 'y'){
2345 ps_global
->user_says_cancel
= 1;
2350 ps_global
->tcptimeout
= 0;
2356 * -------------------------------------------------------------
2357 * These are declared in pith/imap.h as mandatory to implement.
2358 * -------------------------------------------------------------
2363 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2366 pine_tcptimeout(long int elapsed
, long int sincelast
, char *host
)
2368 long rv
= 1L; /* keep trying by default */
2371 ps_global
->tcptimeout
= 1;
2373 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2374 long2string(elapsed
), host
));
2383 if(ps_global
->noshow_timeout
)
2386 if(ps_global
->can_interrupt
2387 && ps_global
->close_connection_timeout
> 0L
2388 && elapsed
>= (long)ps_global
->tcp_query_timeout
2389 && elapsed
>= (long)ps_global
->close_connection_timeout
){
2390 ps_global
->can_interrupt
= 0; /* do not return here */
2391 ps_global
->read_bail
= 0;
2392 ps_global
->user_says_cancel
= 1;
2396 if(!ps_global
->ttyo
)
2397 return(pine_tcptimeout_noscreen(elapsed
, sincelast
, host
));
2402 * Prompt after a minute (since by then things are probably really bad)
2403 * A prompt timeout means "keep trying"...
2405 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2408 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2409 if((clear_inverse
= !InverseState()) != 0)
2414 PutLine2(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
), 0,
2415 _("No reply in %s seconds from server %s. Break connection?"),
2416 long2string(elapsed
), host
);
2421 if(ps_global
->read_bail
|| ch
== 'y' || ch
== 'Y'){
2422 ps_global
->read_bail
= 0;
2423 ps_global
->user_says_cancel
= 1;
2430 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2433 if(rv
== 1L){ /* just warn 'em something's up */
2434 q_status_message2(SM_ORDER
, 0, 0,
2435 _("No reply in %s seconds from server %s. Still Waiting..."),
2436 long2string(elapsed
), host
);
2437 flush_status_messages(0); /* make sure it's seen */
2440 mark_status_dirty(); /* make sure it gets cleared */
2442 resume_busy_cue((rv
== 1) ? 3 : 0);
2443 ps_global
->tcptimeout
= 0;
2448 QUOTALIST
*pine_quotalist_copy (QUOTALIST
*pquota
)
2450 QUOTALIST
*cquota
= NULL
;
2453 cquota
= mail_newquotalist();
2454 if (pquota
->name
&& *pquota
->name
)
2455 cquota
->name
= cpystr(pquota
->name
);
2456 cquota
->usage
= pquota
->usage
;
2457 cquota
->limit
= pquota
->limit
;
2459 cquota
->next
= pine_quotalist_copy(pquota
->next
);
2465 /* c-client callback to handle quota */
2468 pine_parse_quota (MAILSTREAM
*stream
, unsigned char *msg
, QUOTALIST
*pquota
)
2470 ps_global
->quota
= pine_quotalist_copy (pquota
);
2474 * C-client callback to handle SSL/TLS certificate validation failures
2476 * Returning 0 means error becomes fatal
2477 * Non-zero means certificate problem is ignored and SSL session is
2480 * We remember the answer and won't re-ask for subsequent open attempts to
2481 * the same hostname.
2484 pine_sslcertquery(char *reason
, char *host
, char *cert
)
2487 char *unknown
= "<unknown>";
2490 int ok_novalidate
= 0, warned
= 0;
2492 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2493 host
? host
: "?", reason
? reason
: "?",
2494 cert
? cert
: "?"));
2496 hostlist
.name
= host
? host
: "";
2497 hostlist
.next
= NULL
;
2500 * See if we've been asked about this host before.
2502 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2503 /* we were asked before, did we say Yes? */
2509 "sslcertificatequery: approved automatically\n"));
2513 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2516 if(ps_global
->ttyo
){
2518 STORE_S
*in_store
, *out_store
;
2521 HANDLE_S
*handles
= NULL
;
2522 int the_answer
= 'n';
2524 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
2525 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2528 so_puts(in_store
, "<HTML><P>");
2529 so_puts(in_store
, _("There was a failure validating the SSL/TLS certificate for the server"));
2531 so_puts(in_store
, "<P><CENTER>");
2532 so_puts(in_store
, host
? host
: unknown
);
2533 so_puts(in_store
, "</CENTER>");
2535 so_puts(in_store
, "<P>");
2536 so_puts(in_store
, _("The reason for the failure was"));
2538 /* squirrel away details */
2540 fs_give((void **)&details_host
);
2542 fs_give((void **)&details_reason
);
2544 fs_give((void **)&details_cert
);
2546 details_host
= cpystr(host
? host
: unknown
);
2547 details_reason
= cpystr(reason
? reason
: unknown
);
2548 details_cert
= cpystr(cert
? cert
: unknown
);
2550 so_puts(in_store
, "<P><CENTER>");
2551 snprintf(tmp
, sizeof(tmp
), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2552 reason
? reason
: unknown
);
2553 tmp
[sizeof(tmp
)-1] = '\0';
2555 so_puts(in_store
, tmp
);
2556 so_puts(in_store
, "</CENTER>");
2558 so_puts(in_store
, "<P>");
2559 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."));
2561 so_puts(in_store
, "<P>");
2562 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"));
2564 so_puts(in_store
, "<P><CENTER>");
2565 so_puts(in_store
, "/novalidate-cert");
2566 so_puts(in_store
, "</CENTER>");
2568 so_puts(in_store
, "<P>");
2569 so_puts(in_store
, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2571 so_puts(in_store
, "<P><CENTER>");
2572 so_puts(in_store
, host
? host
: unknown
);
2573 so_puts(in_store
, "</CENTER>");
2575 so_puts(in_store
, "<P>");
2576 so_puts(in_store
, _("in your configuration, replace those characters with"));
2578 so_puts(in_store
, "<P><CENTER>");
2579 so_puts(in_store
, host
? host
: unknown
);
2580 so_puts(in_store
, "/novalidate-cert");
2581 so_puts(in_store
, "</CENTER>");
2583 so_puts(in_store
, "<P>");
2584 so_puts(in_store
, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2586 so_seek(in_store
, 0L, 0);
2587 init_handles(&handles
);
2589 gf_link_filter(gf_html2plain
,
2590 gf_html2plain_opt(NULL
,
2591 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
2592 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
2593 gf_set_so_readc(&gc
, in_store
);
2594 gf_set_so_writec(&pc
, out_store
);
2596 gf_clear_so_writec(out_store
);
2597 gf_clear_so_readc(in_store
);
2599 memset(&sargs
, 0, sizeof(SCROLL_S
));
2600 sargs
.text
.handles
= handles
;
2601 sargs
.text
.text
= so_text(out_store
);
2602 sargs
.text
.src
= CharStar
;
2603 sargs
.text
.desc
= _("help text");
2604 sargs
.bar
.title
= _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2605 sargs
.proc
.tool
= answer_cert_failure
;
2606 sargs
.proc
.data
.p
= (void *)&the_answer
;
2607 sargs
.keys
.menu
= &ans_certquery_keymenu
;
2608 /* don't want to re-enter c-client */
2609 sargs
.quell_newmail
= 1;
2610 setbitmap(sargs
.keys
.bitmap
);
2611 sargs
.help
.text
= h_tls_validation_failure
;
2612 sargs
.help
.title
= _("HELP FOR CERT VALIDATION FAILURE");
2616 if(the_answer
== 'y')
2618 else if(the_answer
== 'n')
2619 ps_global
->user_says_cancel
= 1;
2621 ps_global
->mangled_screen
= 1;
2622 ps_global
->painted_body_on_startup
= 0;
2623 ps_global
->painted_footer_on_startup
= 0;
2625 so_give(&out_store
);
2626 free_handles(&handles
);
2628 fs_give((void **)&details_host
);
2630 fs_give((void **)&details_reason
);
2632 fs_give((void **)&details_cert
);
2636 * If screen hasn't been initialized yet, use want_to.
2639 memset((void *)tmp
, 0, sizeof(tmp
));
2641 reason
? reason
: _("SSL/TLS certificate validation failure"),
2643 tmp
[sizeof(tmp
)-1] = '\0';
2644 strncat(tmp
, _(": Continue anyway "), sizeof(tmp
)-strlen(tmp
)-1);
2646 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y')
2651 q_status_message1(SM_ORDER
, 1, 3, _("Open of %s cancelled"),
2652 host
? host
: unknown
);
2654 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, rv
? 1 : 0, 0);
2656 dprint((5, "sslcertificatequery: %s\n",
2657 rv
? "approved" : "rejected"));
2664 pine_newsrcquery(MAILSTREAM
*stream
, char *mulname
, char *name
)
2666 char buf
[MAILTMPLEN
];
2668 if((can_access(mulname
, ACCESS_EXISTS
) == 0)
2669 || !(can_access(name
, ACCESS_EXISTS
) == 0))
2672 snprintf(buf
, sizeof(buf
),
2673 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2675 strlen(last_cmpnt(name
)) > 15 ? "..." : "");
2676 buf
[sizeof(buf
)-1] = '\0';
2677 if(want_to(buf
, 'n', 'n', NO_HELP
, WT_NORM
) == 'y')
2678 rename_file(name
, mulname
);
2684 url_local_certdetails(char *url
)
2686 if(!struncmp(url
, "x-alpine-cert:", 14)){
2691 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
2692 q_status_message(SM_ORDER
| SM_DING
, 7, 10,
2693 _("Error allocating space for details."));
2697 so_puts(store
, _("Host given by user:\n\n "));
2698 so_puts(store
, details_host
);
2699 so_puts(store
, _("\n\nReason for failure:\n\n "));
2700 so_puts(store
, details_reason
);
2701 so_puts(store
, _("\n\nCertificate being verified:\n\n"));
2702 folded
= fold(details_cert
, ps_global
->ttyo
->screen_cols
, ps_global
->ttyo
->screen_cols
, " ", " ", FLD_NONE
);
2703 so_puts(store
, folded
);
2704 fs_give((void **)&folded
);
2705 so_puts(store
, "\n");
2707 memset(&sargs
, 0, sizeof(SCROLL_S
));
2708 sargs
.text
.text
= so_text(store
);
2709 sargs
.text
.src
= CharStar
;
2710 sargs
.text
.desc
= _("Details");
2711 sargs
.bar
.title
= _("CERT VALIDATION DETAILS");
2712 sargs
.help
.text
= NO_HELP
;
2713 sargs
.help
.title
= NULL
;
2714 sargs
.quell_newmail
= 1;
2715 sargs
.help
.text
= h_tls_failure_details
;
2716 sargs
.help
.title
= _("HELP FOR CERT VALIDATION DETAILS");
2720 so_give(&store
); /* free resources associated with store */
2721 ps_global
->mangled_screen
= 1;
2730 * C-client callback to handle SSL/TLS certificate validation failures
2733 pine_sslfailure(char *host
, char *reason
, long unsigned int flags
)
2737 int the_answer
= 'n', indent
, len
, cols
;
2738 char buf
[500], buf2
[500];
2740 char *hst
= host
? host
: "<unknown>";
2741 char *rsn
= reason
? reason
: "<unknown>";
2742 char *notls
= "/notls";
2744 int ok_novalidate
= 0, warned
= 0;
2747 dprint((1, "sslfailure: host=%s reason=%s\n",
2751 if(flags
& NET_SILENT
)
2754 hostlist
.name
= host
? host
: "";
2755 hostlist
.next
= NULL
;
2758 * See if we've been told about this host before.
2760 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2761 /* we were told already */
2763 snprintf(buf
, sizeof(buf
), _("SSL/TLS failure for %s: %s"), hst
, rsn
);
2764 buf
[sizeof(buf
)-1] = '\0';
2770 cols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
2773 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2776 strncpy(buf
, _("There was an SSL/TLS failure for the server"), sizeof(buf
));
2777 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2778 so_puts(store
, folded
);
2779 fs_give((void **)&folded
);
2780 so_puts(store
, "\n");
2782 if((len
=strlen(hst
)) <= cols
){
2783 if((indent
=((cols
-len
)/2)) > 0)
2784 so_puts(store
, repeat_char(indent
, SPACE
));
2786 so_puts(store
, hst
);
2787 so_puts(store
, "\n");
2790 strncpy(buf
, hst
, sizeof(buf
));
2791 buf
[sizeof(buf
)-1] = '\0';
2792 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2793 so_puts(store
, folded
);
2794 fs_give((void **)&folded
);
2797 so_puts(store
, "\n");
2799 strncpy(buf
, _("The reason for the failure was"), sizeof(buf
));
2800 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2801 so_puts(store
, folded
);
2802 fs_give((void **)&folded
);
2803 so_puts(store
, "\n");
2805 if((len
=strlen(rsn
)) <= cols
){
2806 if((indent
=((cols
-len
)/2)) > 0)
2807 so_puts(store
, repeat_char(indent
, SPACE
));
2809 so_puts(store
, rsn
);
2810 so_puts(store
, "\n");
2813 strncpy(buf
, rsn
, sizeof(buf
));
2814 buf
[sizeof(buf
)-1] = '\0';
2815 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2816 so_puts(store
, folded
);
2817 fs_give((void **)&folded
);
2820 so_puts(store
, "\n");
2822 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
));
2823 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2824 so_puts(store
, folded
);
2825 fs_give((void **)&folded
);
2826 so_puts(store
, "\n");
2828 if((len
=strlen(notls
)) <= cols
){
2829 if((indent
=((cols
-len
)/2)) > 0)
2830 so_puts(store
, repeat_char(indent
, SPACE
));
2832 so_puts(store
, notls
);
2833 so_puts(store
, "\n");
2836 strncpy(buf
, notls
, sizeof(buf
));
2837 buf
[sizeof(buf
)-1] = '\0';
2838 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2839 so_puts(store
, folded
);
2840 fs_give((void **)&folded
);
2843 so_puts(store
, "\n");
2845 strncpy(buf
, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2847 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2848 so_puts(store
, folded
);
2849 fs_give((void **)&folded
);
2850 so_puts(store
, "\n");
2852 if((len
=strlen(hst
)) <= cols
){
2853 if((indent
=((cols
-len
)/2)) > 0)
2854 so_puts(store
, repeat_char(indent
, SPACE
));
2856 so_puts(store
, hst
);
2857 so_puts(store
, "\n");
2860 strncpy(buf
, hst
, sizeof(buf
));
2861 buf
[sizeof(buf
)-1] = '\0';
2862 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2863 so_puts(store
, folded
);
2864 fs_give((void **)&folded
);
2867 so_puts(store
, "\n");
2869 strncpy(buf
, _("in your configuration, replace those characters with"),
2871 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2872 so_puts(store
, folded
);
2873 fs_give((void **)&folded
);
2874 so_puts(store
, "\n");
2876 snprintf(buf2
, sizeof(buf2
), "%s%s", hst
, notls
);
2877 buf2
[sizeof(buf2
)-1] = '\0';
2878 if((len
=strlen(buf2
)) <= cols
){
2879 if((indent
=((cols
-len
)/2)) > 0)
2880 so_puts(store
, repeat_char(indent
, SPACE
));
2882 so_puts(store
, buf2
);
2883 so_puts(store
, "\n");
2886 strncpy(buf
, buf2
, sizeof(buf
));
2887 buf
[sizeof(buf
)-1] = '\0';
2888 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2889 so_puts(store
, folded
);
2890 fs_give((void **)&folded
);
2893 so_puts(store
, "\n");
2895 if(ps_global
->ttyo
){
2896 strncpy(buf
, _("Type RETURN to continue."), sizeof(buf
));
2897 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2898 so_puts(store
, folded
);
2899 fs_give((void **)&folded
);
2902 memset(&sargs
, 0, sizeof(SCROLL_S
));
2903 sargs
.text
.text
= so_text(store
);
2904 sargs
.text
.src
= CharStar
;
2905 sargs
.text
.desc
= _("help text");
2906 sargs
.bar
.title
= _("SSL/TLS FAILURE");
2907 sargs
.proc
.tool
= answer_cert_failure
;
2908 sargs
.proc
.data
.p
= (void *)&the_answer
;
2909 sargs
.keys
.menu
= &ans_certfail_keymenu
;
2910 setbitmap(sargs
.keys
.bitmap
);
2911 /* don't want to re-enter c-client */
2912 sargs
.quell_newmail
= 1;
2913 sargs
.help
.text
= h_tls_failure
;
2914 sargs
.help
.title
= _("HELP FOR TLS/SSL FAILURE");
2925 * The screen isn't initialized yet, which should mean that this
2926 * is the result of a -p argument. Display_args_err knows how to deal
2927 * with the uninitialized screen, so we mess with the data to get it
2928 * in shape for display_args_err. This is pretty hacky.
2931 so_seek(store
, 0L, 0); /* rewind */
2932 /* count the lines */
2933 while(so_readc(&c
, store
))
2937 qp
= q
= (char **)fs_get((cnt
+1) * sizeof(char *));
2938 memset(q
, 0, (cnt
+1) * sizeof(char *));
2940 so_seek(store
, 0L, 0); /* rewind */
2942 while(so_readc(&c
, store
)){
2945 *qp
++ = cpystr(buf
);
2952 display_args_err(NULL
, q
, 0);
2953 free_list_array(&q
);
2956 ps_global
->mangled_screen
= 1;
2957 ps_global
->painted_body_on_startup
= 0;
2958 ps_global
->painted_footer_on_startup
= 0;
2961 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, ok_novalidate
, 1);
2966 answer_cert_failure(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2970 ps_global
->next_screen
= SCREEN_FUN_NULL
;
2974 *(int *)(sparms
->proc
.data
.p
) = 'y';
2978 *(int *)(sparms
->proc
.data
.p
) = 'n';
2982 alpine_panic("Unexpected command in answer_cert_failure");
2991 oauth2_auth_answer(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2996 /* TRANSLATORS: user needs to input an access code from the server */
2997 char *accesscodelabel
= _("Copy and Paste Access Code");
2998 char token
[4*MAILTMPLEN
], prompt
[4*MAILTMPLEN
];
3000 ps_global
->next_screen
= SCREEN_FUN_NULL
;
3005 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
3006 flags
= OE_APPEND_CURRENT
;
3007 sprintf(prompt
, "%s: ", accesscodelabel
);
3009 rc
= optionally_enter(token
, q_line
, 0, 4*MAILTMPLEN
,
3010 prompt
, NULL
, NO_HELP
, &flags
);
3011 } while (rc
!= 0 && rc
!= 1);
3012 user
.code
= rc
== 0 ? cpystr(token
) : NULL
;
3014 rv
= rc
== 1 ? 0 : 1;
3023 alpine_panic("Unexpected command in oauth2_auth_answer");
3026 *(AUTH_CODE_S
*) sparms
->proc
.data
.p
= user
;
3031 /*----------------------------------------------------------------------
3032 This can be used to prevent the flickering of the check_cue char
3033 caused by numerous (5000+) fetches by c-client. Right now, the only
3034 practical use found is newsgroup subsciption.
3036 check_cue_display will check if this global is set, and won't clear
3037 the check_cue_char if set.
3040 set_read_predicted(int i
)
3042 ps_global
->read_predicted
= i
==1;
3044 if(!i
&& F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3045 check_cue_display(" ");
3050 /*----------------------------------------------------------------------
3051 Exported method to retrieve logged in user name associated with stream
3053 Args: host -- host to find associated login name with.
3058 pine_block_notify(int reason
, void *data
)
3061 case BLOCK_SENSITIVE
: /* sensitive code, disallow alarms */
3064 case BLOCK_NONSENSITIVE
: /* non-sensitive code, allow alarms */
3067 case BLOCK_TCPWRITE
: /* blocked on TCP write */
3068 case BLOCK_FILELOCK
: /* blocked on file locking */
3070 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3071 check_cue_display(">");
3073 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3077 case BLOCK_DNSLOOKUP
: /* blocked on DNS lookup */
3078 case BLOCK_TCPOPEN
: /* blocked on TCP open */
3079 case BLOCK_TCPREAD
: /* blocked on TCP read */
3080 case BLOCK_TCPCLOSE
: /* blocked on TCP close */
3082 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3083 check_cue_display("<");
3085 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3090 case BLOCK_NONE
: /* not blocked */
3092 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3093 check_cue_display(" ");
3104 mm_expunged_current(long unsigned int rawno
)
3106 /* expunged something we're viewing? */
3107 if(!ps_global
->expunge_in_progress
3108 && (mn_is_cur(ps_global
->msgmap
, mn_raw2m(ps_global
->msgmap
, (long) rawno
))
3109 && (ps_global
->prev_screen
== mail_view_screen
3110 || ps_global
->prev_screen
== attachment_screen
))){
3111 ps_global
->next_screen
= mail_index_screen
;
3112 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3113 "Message you were viewing is gone!");
3121 * Specific functions to support caching username/passwd/host
3122 * triples on disk for use from one session to the next...
3125 #define FIRSTCH 0x20
3127 #define TABSZ (LASTCH - FIRSTCH + 1)
3129 static int xlate_key
;
3133 * xlate_in() - xlate_in the given character
3141 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3142 eti
+= (c
- FIRSTCH
);
3143 eti
-= (eti
>= 2*TABSZ
) ? 2*TABSZ
: (eti
>= TABSZ
) ? TABSZ
: 0;
3144 return((xlate_key
= eti
) + FIRSTCH
);
3152 * xlate_out() - xlate_out the given character
3160 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3161 xch
= c
- (dti
= xlate_key
);
3162 xch
+= (xch
< FIRSTCH
-TABSZ
) ? 2*TABSZ
: (xch
< FIRSTCH
) ? TABSZ
: 0;
3163 dti
= (xch
- FIRSTCH
) + dti
;
3164 dti
-= (dti
>= 2*TABSZ
) ? 2*TABSZ
: (dti
>= TABSZ
) ? TABSZ
: 0;
3171 #endif /* PASSFILE */
3174 #ifdef LOCAL_PASSWD_CACHE
3176 int cache_method_was_setup (char *pinerc
)
3180 char tmp
[MAILTMPLEN
];
3182 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb")))
3184 if(fp
!= NULL
) fclose(fp
);
3185 #endif /* PASSFILE */
3190 line_get(char *tmp
, size_t len
, char **textp
)
3196 || (s
= strchr(*textp
, '\n')) == NULL
3197 || (s
- *textp
) > len
- 1)
3204 snprintf(tmp
, len
, "%s\n", *textp
);
3211 typedef struct pwd_s
{
3222 #define SAME_VALUE(X, Y) ((((X) == NULL && (Y) == NULL) \
3223 || ((X) && (Y) && !strcmp((X), (Y)))) ? 1 : 0)
3227 * Passfile lines are
3229 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3231 * In pine4.40 and before there was no orig_hostname, and there still isn't
3232 * if it is the same as hostname.
3235 * Use Windows credentials. The TargetName of the credential is
3236 * UWash_Alpine_.partnumber-totalparts_<hostname:port>\tuser\taltflag
3237 * and the blob consists of
3238 * passwd\torighost (if different from host)
3240 * We don't use anything fancy we just copy out all the credentials which
3241 * begin with TNAME and put them into our cache, so we don't lookup based
3242 * on the TargetName or anything like that. That was so we could re-use
3243 * the existing code and so that orighost data could be easily used.
3246 read_passfile(char *pinerc
, MMLOGIN_S
**l
)
3250 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3253 char *tmp
, *blob
, *target
= NULL
;
3254 ALPINE_PWD_S
**pwd
= NULL
;
3255 int uid
, rewrite_passfile
= 0;
3258 unsigned long m
, n
, p
, loc
;
3260 if(using_passfile
== 0)
3261 return(using_passfile
);
3264 if (init_wincred_funcs() != 1) {
3266 return(using_passfile
);
3270 dprint((9, "read_passfile\n"));
3274 /* this code exists because the XOAUTH2 support makes us save
3275 * access tokens as if they were passwords. However, some servers
3276 * produce extremely long access-tokens that do not fit in the credentials
3277 * and therefore need to be split into several entries.
3279 * The plan is the following:
3280 * step 1: Read and save all the information in the credentials
3281 * step 2: flatten the information into one line
3282 * step 3: process that line.
3284 if (g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)) {
3285 pwd
= fs_get((count
+ 1)*sizeof(ALPINE_PWD_S
*));
3286 memset((void *)pwd
, 0, (count
+ 1)*sizeof(ALPINE_PWD_S
*));
3288 /* this is step 1 */
3289 for (k
= 0; k
< count
; k
++) { /* go through each credential */
3290 target
= lptstr_to_utf8(pcred
[k
]->TargetName
);
3291 tmp
= srchstr(target
, TNAME
);
3293 tmp
+= strlen(TNAME
);
3296 m
= strtoul(tmp
, &tmp
, 10);
3300 m
= strtoul(tmp
, &tmp
, 10);
3303 uid
= 0; /* impossible value, uid >= 1, old format! */
3308 n
= strtol(tmp
, &tmp
, 10);
3309 if (*tmp
== '_') tmp
++;
3313 for(s
= tmp
; *s
&& *s
!= '_'; s
++);
3314 if(s
&& *s
) tmp
= s
;
3321 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3322 for (i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++) {
3323 for (ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3324 ; /* find end of data */
3327 tmp
[i
++] = '\0'; /* tie off data */
3330 /* improve this. We are trying to find where we saved
3331 * this data, and in general this is fast if there is
3332 * only a few data, which is not unreasonable, but probably
3333 * can be done better.
3335 for (loc
= 0; pwd
[loc
]
3336 && !(uid
== pwd
[loc
]->uid
3337 && SAME_VALUE(ui
[0], pwd
[loc
]->host
)
3338 && SAME_VALUE(ui
[1], pwd
[loc
]->user
)
3339 && SAME_VALUE(ui
[2], pwd
[loc
]->sflags
)); loc
++);
3341 if (pwd
[loc
] == NULL
) {
3342 pwd
[loc
] = fs_get(sizeof(ALPINE_PWD_S
));
3343 memset((void *) pwd
[loc
], 0, sizeof(ALPINE_PWD_S
));
3344 pwd
[loc
]->blobarray
= fs_get((n
+ 1) * sizeof(char*));
3345 memset((void *) pwd
[loc
]->blobarray
, 0, (n
+ 1) * sizeof(char*));
3348 pwd
[loc
]->uid
= uid
;
3349 if (pwd
[loc
]->host
== NULL
)
3350 pwd
[loc
]->host
= ui
[0] ? cpystr(ui
[0]) : NULL
;
3351 if (pwd
[loc
]->user
== NULL
)
3352 pwd
[loc
]->user
= ui
[1] ? cpystr(ui
[1]) : NULL
;
3353 if (pwd
[loc
]->sflags
== NULL
)
3354 pwd
[loc
]->sflags
= ui
[2] ? cpystr(ui
[2]) : NULL
;
3355 blob
= (char *) pcred
[k
]->CredentialBlob
;
3356 pwd
[loc
]->blobarray
[m
- 1] = blob
? cpystr(blob
) : NULL
;
3358 if (target
) fs_give((void**)&target
);
3361 for (k
= 0; k
< count
; k
++) {
3363 for (i
= 0, j
= 0; pwd
[k
]->blobarray
[j
]; j
++)
3364 i
+= strlen(pwd
[k
]->blobarray
[j
]);
3365 pwd
[k
]->blob
= fs_get(i
+ 1);
3366 pwd
[k
]->blob
[0] = '\0';
3367 for (j
= 0; pwd
[k
]->blobarray
[j
]; j
++) {
3368 strcat(pwd
[k
]->blob
, pwd
[k
]->blobarray
[j
]);
3369 fs_give((void **) &pwd
[k
]->blobarray
[j
]);
3371 fs_give((void **) &pwd
[k
]->blobarray
);
3373 else k
= count
; /* we are done with this step! */
3376 for (k
= 0; k
< count
; k
++) {
3377 if (pwd
[k
] && pwd
[k
]->blob
) {
3378 blob
= pwd
[k
]->blob
;
3379 for (i
= 0, j
= 3; blob
[i
] && j
< 5; j
++) {
3380 for (ui
[j
] = &blob
[i
]; blob
[i
] && blob
[i
] != '\t'; i
++)
3381 ; /* find end of data */
3384 blob
[i
++] = '\0'; /* tie off data */
3386 if (pwd
[k
]->passwd
== NULL
)
3387 pwd
[k
]->passwd
= ui
[3] ? cpystr(ui
[3]) : NULL
;
3388 if (pwd
[k
]->orighost
== NULL
)
3389 pwd
[k
]->orighost
= ui
[4] ? cpystr(ui
[4]) : NULL
;
3390 fs_give((void **) &pwd
[k
]->blob
);
3393 /* now process all lines, and free memory */
3394 for (k
= 0; k
< count
&& pwd
[k
] != NULL
; k
++){
3395 if (pwd
[k
]->passwd
&& pwd
[k
]->host
&& pwd
[k
]->user
) { /* valid field? */
3396 STRLIST_S hostlist
[2];
3399 tmp
= pwd
[k
]->sflags
? strchr(pwd
[k
]->sflags
, PWDAUTHSEP
) : NULL
;
3400 flags
= pwd
[k
]->sflags
? atoi(tmp
? ++tmp
: pwd
[k
]->sflags
) : 0;
3401 hostlist
[0].name
= pwd
[k
]->host
;
3402 if (pwd
[k
]->orighost
) {
3403 hostlist
[0].next
= &hostlist
[1];
3404 hostlist
[1].name
= pwd
[k
]->orighost
;
3405 hostlist
[1].next
= NULL
;
3408 hostlist
[0].next
= NULL
;
3410 imap_set_passwd(l
, pwd
[k
]->passwd
, pwd
[k
]->user
, hostlist
, flags
& 0x01, 0, 0);
3412 if (pwd
[k
]->passwd
) fs_give((void **) &pwd
[k
]->passwd
);
3413 if (pwd
[k
]->user
) fs_give((void **) &pwd
[k
]->user
);
3414 if (pwd
[k
]->host
) fs_give((void **) &pwd
[k
]->host
);
3415 if (pwd
[k
]->sflags
) fs_give((void **) &pwd
[k
]->sflags
);
3416 if (pwd
[k
]->orighost
) fs_give((void **) &pwd
[k
]->orighost
);
3417 fs_give((void **) &pwd
[k
]);
3419 g_CredFree((PVOID
)pcred
);
3421 fs_give((void **) &pwd
);
3422 if(rewrite_passfile
) write_passfile(pinerc
, *l
);
3426 # else /* old windows */
3433 char target
[MAILTMPLEN
];
3434 char *tmp
, *host
, *user
, *sflags
, *passwd
, *orighost
;
3437 SecKeychainAttributeList attrList
;
3438 SecKeychainSearchRef searchRef
= NULL
;
3439 SecKeychainAttribute attrs
[] = {
3440 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
3443 if(using_passfile
== 0)
3444 return(using_passfile
);
3446 dprint((9, "read_passfile\n"));
3449 /* search for only our items in the keychain */
3451 attrList
.attr
= attrs
;
3454 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
3455 kSecGenericPasswordItemClass
,
3458 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3460 SecKeychainItemRef itemRef
= NULL
;
3461 SecKeychainAttributeInfo info
;
3462 SecKeychainAttributeList
*attrList
= NULL
;
3465 char *blobcopy
= NULL
; /* NULL terminated copy */
3467 UInt32 tags
[] = {kSecAccountItemAttr
,
3468 kSecServiceItemAttr
};
3469 UInt32 formats
[] = {0,0};
3471 dprint((10, "read_passfile: searchRef not NULL\n"));
3474 info
.format
= formats
;
3477 * Go through each item we found and put it
3480 while(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
)) && itemRef
){
3481 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3482 rc
= SecKeychainItemCopyAttributesAndData(itemRef
,
3487 if(rc
== 0 && attrList
){
3488 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList
->count
));
3490 blobcopy
= (char *) fs_get((blength
+ 1) * sizeof(char));
3491 strncpy(blobcopy
, (char *) blob
, blength
);
3492 blobcopy
[blength
] = '\0';
3495 * I'm not real clear on how this works. It seems to be
3496 * necessary to combine the attributes from two passes
3497 * (attrList->count == 2) in order to get the full set
3498 * of attributes we inserted into the keychain in the
3499 * first place. So, we reset host...orighost outside of
3500 * the following for loop, not inside.
3502 host
= user
= sflags
= passwd
= orighost
= NULL
;
3503 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3505 for(k
= 0; k
< attrList
->count
; k
++){
3507 if(attrList
->attr
[k
].length
){
3509 (char *) attrList
->attr
[k
].data
,
3510 MIN(attrList
->attr
[k
].length
,sizeof(target
)));
3511 target
[MIN(attrList
->attr
[k
].length
,sizeof(target
)-1)] = '\0';
3515 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
3516 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3517 ; /* find end of data */
3520 tmp
[i
++] = '\0'; /* tie off data */
3532 for(i
= 0, j
= 3; blobcopy
[i
] && j
< 5; j
++){
3533 for(ui
[j
] = &blobcopy
[i
]; blobcopy
[i
] && blobcopy
[i
] != '\t'; i
++)
3534 ; /* find end of data */
3537 blobcopy
[i
++] = '\0'; /* tie off data */
3546 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
:""));
3549 if(passwd
&& host
&& user
){ /* valid field? */
3550 STRLIST_S hostlist
[2];
3553 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
3554 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
3555 hostlist
[0].name
= host
;
3557 hostlist
[0].next
= &hostlist
[1];
3558 hostlist
[1].name
= orighost
;
3559 hostlist
[1].next
= NULL
;
3562 hostlist
[0].next
= NULL
;
3565 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
3569 fs_give((void **) & blobcopy
);
3571 SecKeychainItemFreeAttributesAndData(attrList
, (void *) blob
);
3575 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc
));
3582 CFRelease(searchRef
);
3586 dprint((10, "read_passfile: searchRef NULL\n"));
3591 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc
));
3594 return(using_passfile
);
3596 #else /* PASSFILE */
3598 char tmp
[MAILTMPLEN
], *ui
[5];
3599 int i
, j
, n
, rv
= 0, error
= 0;
3601 char *tmptext
= NULL
;
3604 char tmp2
[MAILTMPLEN
];
3605 char *text
= NULL
, *text2
= NULL
;
3610 if(using_passfile
== 0)
3611 return(using_passfile
);
3613 dprint((9, "read_passfile\n"));
3615 /* if there's no password to read, create it if we can encrypt it,
3616 * or else let the user create it and bail out of here.
3619 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb"))){
3621 i
= our_creat(tmp
, 0600);
3624 if(!(fp
= our_fopen(tmp
, "rb")))
3635 return(using_passfile
);
3639 if(our_stat(tmp
, &sbuf
) == 0)
3641 fp
= our_fopen(tmp
, "rb"); /* reopen to read data */
3643 /* the next call initializes the key/certificate pair used to
3644 * encrypt and decrypt a password file. The details of how this is
3645 * done is in the file pith/smime.c. During this setup we might call
3646 * smime_init(), but no matter what happens we must call smime_deinit()
3647 * there. The reason why this is so is because we can not assume that
3648 * the .pinerc file has been read by this time, so this code might not
3649 * know about the ps_global->smime structure or any of its components,
3650 * and it shouldn't because it only needs ps_global->pwdcert, so
3651 * do not init smime here, because the .pinerc might not have been
3652 * read and we do not really know where the keys and certificates really
3654 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3655 * it is called for the first time and there are certificates at all,
3656 * or when it is called after the first time and the user refuses to
3657 * create a self-signed certificate. In this situation we will just
3658 * let the user live in an insecure world, but no more passwords will
3659 * be saved in the password file, and only those found there will be used.
3662 fgets(tmp2
, sizeof(tmp2
), fp
);
3664 if(strcmp(tmp2
, "-----BEGIN PKCS7-----\n")){
3665 /* there is an already existing password file, that is not encrypted
3666 * and there is no key to encrypt it yet, go again through setup_pwdcert
3667 * and encrypt it now.
3669 if(tmp2
[0]){ /* not empty, UNencrypted password file */
3670 if(ps_global
->pwdcert
== NULL
)
3671 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3672 if((rv
== 0 || rv
== -5) && ps_global
->pwdcert
== NULL
)
3673 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3674 if(ps_global
->pwdcert
== NULL
){
3675 q_status_message(SM_ORDER
, 3, 3,
3676 " Failed to create private key. Using UNencrypted Password file. ");
3681 q_status_message(SM_ORDER
, 3, 3,
3682 " Failed to unlock private key. Using UNencrypted Password file. ");
3683 save_password
= 0; /* do not save more passwords */
3686 if(ps_global
->pwdcert
!= NULL
3687 && encrypt_file((char *)tmp
, NULL
, (PERSONAL_CERT
*)ps_global
->pwdcert
))
3692 if(ps_global
->pwdcert
== NULL
)
3693 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3698 * if password file is encrypted we attempt to decrypt. We ask the
3699 * user for the password to unlock the password file. If the user
3700 * enters the password and it unlocks the file, use it and keep saving
3701 * passwords in it. If the user enters the wrong passwords and does
3702 * not unlock it, we will not see that here, but in decrypt_file, so
3703 * the only other possibility is that the user cancels. In that case
3704 * we will see i == -1. In that case, we will let the user attempt
3705 * manual login to the server they want to login, but passwords will
3706 * not be saved so that the password file will not be saved
3707 * unencrypted and rewritten again.
3710 text
= text2
= decrypt_file((char *)tmp
, &i
, (PERSONAL_CERT
*)ps_global
->pwdcert
);
3711 len
= text2
? strlen(text2
) : 0;
3713 case -2: using_passfile
= 0;
3716 case 1 : save_password
= 1;
3720 case -1: save_password
= 0;
3729 if(using_passfile
== 0){
3731 if(text
) fs_give((void **)&text
);
3733 return using_passfile
;
3737 tmptext
= fs_get(len
+ 1);
3739 for(n
= 0; encrypted
? line_get(tmptext
, len
+ 1, &text2
)
3740 : (fgets(tmptext
, len
+1, fp
) != NULL
); n
++){
3742 for(n
= 0; fgets(tmptext
, len
+1, fp
); n
++){
3744 /*** do any necessary DEcryption here ***/
3746 for(i
= 0; tmptext
[i
]; i
++)
3747 tmptext
[i
] = xlate_out(tmptext
[i
]);
3749 if(i
&& tmptext
[i
-1] == '\n')
3750 tmptext
[i
-1] = '\0'; /* blast '\n' */
3752 dprint((10, "read_passfile: %s\n", tmp
? tmp
: "?"));
3753 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3754 for(i
= 0, j
= 0; tmptext
[i
] && j
< 5; j
++){
3755 for(ui
[j
] = &tmptext
[i
]; tmptext
[i
] && tmptext
[i
] != '\t'; i
++)
3756 ; /* find end of data */
3759 tmptext
[i
++] = '\0'; /* tie off data */
3762 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3763 if(ui
[0] && ui
[1] && ui
[2]){ /* valid field? */
3764 STRLIST_S hostlist
[2];
3765 char *s
= ui
[3] ? strchr(ui
[3], PWDAUTHSEP
) : NULL
;
3766 int flags
= ui
[3] ? atoi(s
? ++s
: ui
[3]) : 0;
3768 hostlist
[0].name
= ui
[2];
3770 hostlist
[0].next
= &hostlist
[1];
3771 hostlist
[1].name
= ui
[4];
3772 hostlist
[1].next
= NULL
;
3775 hostlist
[0].next
= NULL
;
3778 imap_set_passwd(l
, ui
[0], ui
[1], hostlist
, flags
& 0x01, 0, 0);
3783 if (tmptext
) fs_give((void **) &tmptext
);
3785 if (text
) fs_give((void **)&text
);
3790 #endif /* PASSFILE */
3796 write_passfile(char *pinerc
, MMLOGIN_S
*l
)
3798 char *authend
, *authtype
;
3801 unsigned long bloblen
= 0, len
;
3802 int i
, totalparts
, k
, uid
;
3803 char target
[MAXPWDBUFFERSIZE
];
3804 char *blob
= NIL
, blob2
[50], *blobp
;
3805 char part
[MAILTMPLEN
];
3809 if(using_passfile
== 0)
3812 dprint((9, "write_passfile\n"));
3814 erase_windows_credentials(); /* erase all passwords from credentials */
3815 /* start writing them back to credentials manager */
3816 for(uid
= 1; l
; l
= l
->next
, uid
++){ /* enforce that uid >= 1 */
3817 /* determine how many parts to create first */
3818 len
= (l
->passwd
? strlen(l
->passwd
) : 0)
3819 + ((l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
) ? 1 : 0)
3820 + ((l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
) ? strlen(l
->hosts
->next
->name
) : 0) + 1;
3824 fs_resize((void **) &blob
, bloblen
);
3827 sprintf(blob
, "%s%s%s",
3828 l
->passwd
? l
->passwd
: "",
3829 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3831 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3832 ? l
->hosts
->next
->name
: "");
3833 i
= len
- 1; /* strlen(blob) */
3835 for (totalparts
= 1; i
> MAXPWDBUFFERSIZE
; totalparts
++, i
-= PWDBUFFERSIZE
);
3836 authtype
= l
->passwd
;
3837 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3839 if (authend
!= NULL
){
3841 sprintf(blob2
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3842 *authend
= PWDAUTHSEP
;
3845 sprintf(blob2
, "%d", l
->altflag
);
3847 for (k
= 1, i
= len
- 1, blobp
= blob
; k
<= totalparts
; k
++) {
3848 snprintf(target
, sizeof(target
), "%s.%d.%d-%d_%s\t%s\t%s",
3849 TNAME
, uid
, k
, totalparts
,
3850 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3851 l
->user
? l
->user
: "",
3853 ltarget
= utf8_to_lptstr((LPSTR
)target
);
3855 memset((void*)&cred
, 0, sizeof(cred
));
3857 cred
.Type
= CRED_TYPE_GENERIC
;
3858 cred
.TargetName
= ltarget
;
3859 if (i
> MAXPWDBUFFERSIZE
) {
3860 strncpy(part
, blobp
, PWDBUFFERSIZE
);
3861 part
[PWDBUFFERSIZE
] = '\0';
3862 blobp
+= PWDBUFFERSIZE
;
3866 strcpy(part
, blobp
);
3867 cred
.CredentialBlobSize
= strlen(part
) + 1;
3868 cred
.CredentialBlob
= (LPBYTE
)&part
;
3869 cred
.Persist
= CRED_PERSIST_ENTERPRISE
;
3870 g_CredWriteW(&cred
, 0);
3871 fs_give((void**)<arget
);
3875 if(blob
) fs_give((void **) &blob
);
3877 #endif /* WINCRED > 0 */
3881 char target
[10*MAILTMPLEN
];
3882 char blob
[10*MAILTMPLEN
];
3883 SecKeychainItemRef itemRef
= NULL
;
3885 if(using_passfile
== 0)
3888 dprint((9, "write_passfile\n"));
3890 for(; l
; l
= l
->next
){
3891 authtype
= l
->passwd
;
3892 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3893 if(authend
!= NULL
){
3895 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3896 *authend
= PWDAUTHSEP
;
3899 sprintf(blob
, "%d", l
->altflag
);
3901 snprintf(target
, sizeof(target
), "%s\t%s\t%s",
3902 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3903 l
->user
? l
->user
: "",
3906 snprintf(blob
, sizeof(blob
), "%s%s%s",
3907 l
->passwd
? l
->passwd
: "",
3908 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3910 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3911 ? l
->hosts
->next
->name
: "");
3913 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target
), target
, strlen(TNAME
), TNAME
, strlen(blob
), blob
));
3915 rc
= SecKeychainAddGenericPassword(NULL
,
3916 strlen(target
), target
,
3917 strlen(TNAME
), TNAME
,
3921 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3924 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc
));
3927 if(rc
== errSecDuplicateItem
){
3928 /* fix existing entry */
3929 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3931 if(!(rc
=SecKeychainFindGenericPassword(NULL
,
3932 strlen(target
), target
,
3933 strlen(TNAME
), TNAME
,
3935 &itemRef
)) && itemRef
){
3937 rc
= SecKeychainItemModifyAttributesAndData(itemRef
, NULL
, strlen(blob
), blob
);
3939 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc
));
3943 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc
));
3948 #else /* PASSFILE */
3949 char *tmp
= NULL
, passfile
[MAXPATH
+ 1], blob
[MAILTMPLEN
];
3951 size_t tmplen
= 0, newlen
;
3954 char *text
= NULL
, tmp2
[MAXPATH
+ 1];
3958 if(using_passfile
== 0)
3961 dprint((9, "write_passfile\n"));
3963 /* if there's no passfile to read, bag it!! */
3964 if(!passfile_name(pinerc
, passfile
, sizeof(passfile
)) || !(fp
= our_fopen(passfile
, "wb"))){
3970 strncpy(tmp2
, passfile
, sizeof(tmp2
));
3971 tmp2
[sizeof(tmp2
)-1] = '\0';
3974 for(n
= 0; l
; l
= l
->next
, n
++){
3975 authtype
= l
->passwd
;
3976 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3977 if(authend
!= NULL
){
3979 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3980 *authend
= PWDAUTHSEP
;
3983 sprintf(blob
, "%d", l
->altflag
);
3985 newlen
= strlen(l
->passwd
) + strlen(l
->user
) + strlen(l
->hosts
->name
)
3986 + strlen(blob
) + strlen((l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "")
3987 + strlen((l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
: "")
3990 if(tmplen
< newlen
){
3991 fs_resize((void **)&tmp
, newlen
);
3995 /*** do any necessary ENcryption here ***/
3996 sprintf(tmp
, "%s\t%s\t%s\t%s%s%s\n", l
->passwd
, l
->user
,
3997 l
->hosts
->name
, blob
,
3998 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "",
3999 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
4001 dprint((10, "write_passfile: %s", tmp
? tmp
: "?"));
4003 for(i
= 0; tmp
[i
]; i
++)
4004 tmp
[i
] = xlate_in(tmp
[i
]);
4007 fs_resize((void **)&text
, (len
+ strlen(tmp
) + 1)*sizeof(char));
4009 len
+= strlen(tmp
) + 1;
4010 strncat(text
, tmp
, strlen(tmp
));
4016 if(tmp
) fs_give((void **) &tmp
);
4020 i
= 0; /* to quell gcc */
4021 if(ps_global
->pwdcert
== NULL
){
4022 q_status_message(SM_ORDER
, 3, 3, "Attempting to encrypt password file");
4023 i
= setup_pwdcert(&ps_global
->pwdcert
);
4024 if((i
== 0 || i
== -5) && ps_global
->pwdcert
== NULL
)
4025 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
4027 if(ps_global
->pwdcert
== NULL
){ /* we tried but failed */
4029 q_status_message1(SM_ORDER
, 3, 3, "Error: no directory %s to save certificates", ps_global
->pwdcertdir
);
4031 q_status_message(SM_ORDER
, 3, 3, "Refusing to write non-encrypted password file");
4033 else if(encrypt_file((char *)tmp2
, text
, ps_global
->pwdcert
) == 0)
4034 q_status_message(SM_ORDER
, 3, 3, "Failed to encrypt password file");
4035 fs_give((void **)&text
); /* do not save this text */
4038 #endif /* PASSFILE */
4041 #endif /* LOCAL_PASSWD_CACHE */
4046 erase_windows_credentials(void)
4048 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
4053 if(init_wincred_funcs() != 1)
4057 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
4059 for(k
= 0; k
< count
; k
++)
4060 g_CredDeleteW(pcred
[k
]->TargetName
, CRED_TYPE_GENERIC
, 0);
4062 g_CredFree((PVOID
) pcred
);
4068 ask_erase_credentials(void)
4070 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y'){
4071 erase_windows_credentials();
4072 q_status_message(SM_ORDER
, 3, 3, "All preserved passwords have been erased");
4075 q_status_message(SM_ORDER
, 3, 3, "Previously preserved passwords will not be erased");
4078 #endif /* WINCRED */
4081 #ifdef LOCAL_PASSWD_CACHE
4083 get_passfile_passwd(char *pinerc
, char **passwd
, char *user
, STRLIST_S
*hostlist
, int altflag
)
4085 return get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, NULL
);
4089 * get_passfile_passwd_auth - return the password contained in the special password
4090 * cache. The file is assumed to be in the same directory
4091 * as the pinerc with the name defined above.
4094 get_passfile_passwd_auth(char *pinerc
, char **passwd
, char *user
, STRLIST_S
*hostlist
, int altflag
, char *authtype
)
4096 dprint((10, "get_passfile_passwd_auth\n"));
4097 return((mm_login_list
|| read_passfile(pinerc
, &mm_login_list
))
4098 ? imap_get_passwd_auth(mm_login_list
, passwd
,
4099 user
, hostlist
, altflag
, authtype
)
4104 is_using_passfile(void)
4106 return(using_passfile
== 1);
4110 * Just trying to guess the username the user might want to use on this
4111 * host, the user will confirm.
4114 get_passfile_user(char *pinerc
, STRLIST_S
*hostlist
)
4116 return((mm_login_list
|| read_passfile(pinerc
, &mm_login_list
))
4117 ? imap_get_user(mm_login_list
, hostlist
)
4123 preserve_prompt(char *pinerc
)
4125 return preserve_prompt_auth(pinerc
, NULL
);
4129 preserve_prompt_auth(char *pinerc
, char *authtype
)
4131 ps_global
->preserve_password
= 0;
4134 #define PROMPT_PWD _("Preserve password for next login")
4135 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4137 * This prompt was going to be able to be turned on and off via a registry
4138 * setting controlled from the config menu. We decided to always use the
4139 * dialog for login, and there the prompt is unobtrusive enough to always
4140 * be in there. As a result, windows should never reach this, but now
4141 * OS X somewhat uses the behavior just described.
4143 if(mswin_store_pass_prompt()
4144 && (want_to(authtype
4145 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4147 'y', 'x', NO_HELP
, WT_NORM
)
4149 ps_global
->preserve_password
= 1;
4159 #define PROMPT_PWD _("Preserve password for next login")
4160 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4163 if((rc
= macos_store_pass_prompt()) != 0){
4165 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4166 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
4168 ps_global
->preserve_password
= 1;
4170 macos_set_store_pass_prompt(1);
4171 q_status_message(SM_ORDER
, 4, 4,
4172 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4177 macos_set_store_pass_prompt(0);
4178 q_status_message(SM_ORDER
, 4, 4,
4179 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4184 #else /* PASSFILE */
4185 #define PROMPT_PWD _("Preserve password on DISK for next login")
4186 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
4188 char tmp
[MAILTMPLEN
];
4191 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || our_stat(tmp
, &sbuf
) < 0)
4194 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
4196 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4197 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
4199 ps_global
->preserve_password
= 1;
4203 #endif /* PASSFILE */
4206 #endif /* LOCAL_PASSWD_CACHE */
4209 #ifdef APPLEKEYCHAIN
4213 * 1 if store pass prompt is set in the "registry" to on
4215 * -1 if not set to anything
4218 macos_store_pass_prompt(void)
4225 if(storepassprompt
== -1){
4226 if(!(rc
=SecKeychainFindGenericPassword(NULL
, 0, NULL
,
4227 strlen(TNAMEPROMPT
),
4229 (void **) &data
, NULL
))){
4230 val
= (len
== 1 && data
&& data
[0] == '1');
4234 if(storepassprompt
== -1 && !rc
){
4236 storepassprompt
= 1;
4238 storepassprompt
= 0;
4241 return(storepassprompt
);
4246 macos_set_store_pass_prompt(int val
)
4248 storepassprompt
= val
? 1 : 0;
4250 SecKeychainAddGenericPassword(NULL
, 0, NULL
, strlen(TNAMEPROMPT
),
4251 TNAMEPROMPT
, 1, val
? "1" : "0", NULL
);
4256 macos_erase_keychain(void)
4258 SecKeychainAttributeList attrList
;
4259 SecKeychainSearchRef searchRef
= NULL
;
4260 SecKeychainAttribute attrs1
[] = {
4261 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
4263 SecKeychainAttribute attrs2
[] = {
4264 { kSecAccountItemAttr
, strlen(TNAMEPROMPT
), TNAMEPROMPT
}
4267 dprint((9, "macos_erase_keychain\n"));
4270 * Seems like we ought to be able to combine attrs1 and attrs2
4271 * into a single array, but I couldn't get it to work.
4274 /* search for only our items in the keychain */
4276 attrList
.attr
= attrs1
;
4278 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4279 kSecGenericPasswordItemClass
,
4283 SecKeychainItemRef itemRef
= NULL
;
4286 * Go through each item we found and put it
4289 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4290 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4291 SecKeychainItemDelete(itemRef
);
4295 CFRelease(searchRef
);
4300 attrList
.attr
= attrs2
;
4302 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4303 kSecGenericPasswordItemClass
,
4307 SecKeychainItemRef itemRef
= NULL
;
4310 * Go through each item we found and put it
4313 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4314 SecKeychainItemDelete(itemRef
);
4318 CFRelease(searchRef
);
4323 #endif /* APPLEKEYCHAIN */
4325 #ifdef LOCAL_PASSWD_CACHE
4328 set_passfile_passwd(char *pinerc
, char **passwd
, char *user
, STRLIST_S
*hostlist
, int altflag
, int already_prompted
)
4330 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, NULL
);
4333 * set_passfile_passwd - set the password file entry associated with
4334 * cache. The file is assumed to be in the same directory
4335 * as the pinerc with the name defined above.
4336 * already_prompted: 0 not prompted
4337 * 1 prompted, answered yes
4338 * 2 prompted, answered no
4341 set_passfile_passwd_auth(char *pinerc
, char **passwd
, char *user
, STRLIST_S
*hostlist
, int altflag
, int already_prompted
, char *authtype
)
4343 dprint((10, "set_passfile_passwd_auth\n"));
4344 if(((already_prompted
== 0 && preserve_prompt_auth(pinerc
, authtype
))
4345 || already_prompted
== 1)
4346 && !ps_global
->nowrite_password_cache
4347 && (mm_login_list
|| read_passfile(pinerc
, &mm_login_list
))){
4348 imap_set_passwd_auth(&mm_login_list
, *passwd
, user
, hostlist
, altflag
, 0, 0, authtype
);
4349 write_passfile(pinerc
, mm_login_list
);
4354 update_passfile_hostlist(char *pinerc
, char *user
, STRLIST_S
*hostlist
, int altflag
)
4356 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, NULL
);
4360 * Passfile lines are
4362 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4364 * In pine4.40 and before there was no orig_hostname.
4365 * This routine attempts to repair that.
4368 update_passfile_hostlist_auth(char *pinerc
, char *user
, STRLIST_S
*hostlist
, int altflag
, char *authtype
)
4372 #else /* !WINCRED */
4374 size_t len
= authtype
? strlen(authtype
) : 0;
4375 size_t offset
= authtype
? 1 : 0;
4377 for(l
= mm_login_list
; l
; l
= l
->next
)
4378 if(imap_same_host_auth(l
->hosts
, hostlist
, authtype
)
4380 && !strcmp(user
, l
->user
+ len
+ offset
)
4381 && l
->altflag
== altflag
){
4385 if(l
&& l
->hosts
&& hostlist
&& !l
->hosts
->next
&& hostlist
->next
4386 && hostlist
->next
->name
4387 && !ps_global
->nowrite_password_cache
){
4388 l
->hosts
->next
= new_strlist_auth(hostlist
->next
->name
, authtype
, PWDAUTHSEP
);
4389 write_passfile(pinerc
, mm_login_list
);
4391 #endif /* !WINCRED */
4394 #endif /* LOCAL_PASSWD_CACHE */
4399 * Load and init the WinCred structure.
4400 * This gives us a way to skip the WinCred code
4401 * if the dll doesn't exist.
4404 init_wincred_funcs(void)
4410 /* Assume the worst. */
4413 hmod
= LoadLibrary(TEXT("advapi32.dll"));
4416 FARPROC fpCredWriteW
;
4417 FARPROC fpCredEnumerateW
;
4418 FARPROC fpCredDeleteW
;
4421 fpCredWriteW
= GetProcAddress(hmod
, "CredWriteW");
4422 fpCredEnumerateW
= GetProcAddress(hmod
, "CredEnumerateW");
4423 fpCredDeleteW
= GetProcAddress(hmod
, "CredDeleteW");
4424 fpCredFree
= GetProcAddress(hmod
, "CredFree");
4426 if(fpCredWriteW
&& fpCredEnumerateW
&& fpCredDeleteW
&& fpCredFree
)
4428 g_CredWriteW
= (CREDWRITEW
*)fpCredWriteW
;
4429 g_CredEnumerateW
= (CREDENUMERATEW
*)fpCredEnumerateW
;
4430 g_CredDeleteW
= (CREDDELETEW
*)fpCredDeleteW
;
4431 g_CredFree
= (CREDFREE
*)fpCredFree
;
4437 mswin_set_erasecreds_callback(ask_erase_credentials
);
4440 return g_CredInited
;
4443 #endif /* WINCRED */