1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: imap.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2013-2020 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
21 The call back routines for the c-client/imap
22 - handles error messages and other notification
23 - handles prelimirary notification of new mail and expunged mail
24 - prompting for imap server login and password
43 #include "xoauth2conf.h"
44 #include "confscroll.h"
46 #include "../pith/state.h"
47 #include "../pith/conf.h"
48 #include "../pith/msgno.h"
49 #include "../pith/filter.h"
50 #include "../pith/news.h"
51 #include "../pith/util.h"
52 #include "../pith/list.h"
53 #include "../pith/margin.h"
55 #include "../pith/smime.h"
60 #define TNAME "UWash_Alpine_"
61 #define TNAMESTAR "UWash_Alpine_*"
64 * WinCred Function prototypes
66 typedef BOOL (WINAPI CREDWRITEW
) ( __in PCREDENTIALW Credential
, __in DWORD Flags
);
67 typedef BOOL (WINAPI CREDENUMERATEW
) ( __in LPCWSTR Filter
, __reserved DWORD Flags
,
68 __out DWORD
*Count
, __deref_out_ecount(*Count
) PCREDENTIALW
**Credential
);
69 typedef BOOL (WINAPI CREDDELETEW
) ( __in LPCWSTR TargetName
, __in DWORD Type
,
70 __reserved DWORD Flags
);
71 typedef VOID (WINAPI CREDFREE
) ( __in PVOID Buffer
);
76 int g_CredInited
= 0; /* 1 for loaded successfully,
77 * -1 for not available.
78 * 0 for not initialized yet.
80 CREDWRITEW
*g_CredWriteW
;
81 CREDENUMERATEW
*g_CredEnumerateW
;
82 CREDDELETEW
*g_CredDeleteW
;
88 #include <Security/SecKeychain.h>
89 #include <Security/SecKeychainItem.h>
90 #include <Security/SecKeychainSearch.h>
91 #define TNAME "UWash_Alpine"
92 #define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
94 int macos_store_pass_prompt(void);
95 void macos_set_store_pass_prompt(int);
97 static int storepassprompt
= -1;
98 #endif /* APPLEKEYCHAIN */
102 * Internal prototypes
104 void mm_login_alt_cue(NETMBX
*);
105 long pine_tcptimeout_noscreen(long, long, char *);
106 int answer_cert_failure(int, MSGNO_S
*, SCROLL_S
*);
107 int oauth2_auth_answer(int, MSGNO_S
*, SCROLL_S
*);
108 OAUTH2_S
*oauth2_select_flow(char *);
109 int xoauth2_flow_tool(struct pine
*, int, CONF_S
**, unsigned int);
111 #ifdef LOCAL_PASSWD_CACHE
112 int read_passfile(char *, MMLOGIN_S
**);
113 void write_passfile(char *, MMLOGIN_S
*);
114 int preserve_prompt(char *);
115 int preserve_prompt_auth(char *, char *authtype
);
116 void update_passfile_hostlist(char *, char *, STRLIST_S
*, int);
117 void update_passfile_hostlist_auth(char *, char *, STRLIST_S
*, int, char *);
118 void free_passfile_cache_work(MMLOGIN_S
**);
120 static MMLOGIN_S
*passfile_cache
= NULL
;
121 static int using_passfile
= -1;
122 int save_password
= 1;
123 #endif /* LOCAL_PASSWD_CACHE */
127 char xlate_out(char);
128 int line_get(char *, size_t, char **);
129 #endif /* PASSFILE */
132 void ask_erase_credentials(void);
133 int init_wincred_funcs(void);
137 static char *details_cert
, *details_host
, *details_reason
;
139 extern XOAUTH2_INFO_S xoauth_default
[];
142 * This is the private information of the client, which is passed to
143 * c-client for processing. Every c-client application must have its
146 OAUTH2_S alpine_oauth2_list
[] =
149 {"imap.gmail.com", "smtp.gmail.com", NULL
, NULL
},
150 {{"client_id", NULL
},
151 {"client_secret", NULL
},
152 {"tenant", NULL
}, /* not used */
153 {"code", NULL
}, /* access code from the authorization process */
154 {"refresh_token", NULL
},
155 {"scope", "https://mail.google.com/"},
156 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
157 {"grant_type", "authorization_code"},
158 {"grant_type", "refresh_token"},
159 {"response_type", "code"},
161 {"device_code", NULL
} /* not used */
163 {{"GET", "https://accounts.google.com/o/oauth2/auth", /* authorization address, get access code */
164 {OA2_Id
, OA2_Scope
, OA2_Redirect
, OA2_Response
, OA2_End
, OA2_End
, OA2_End
}},
165 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* Device Info information, not used */
166 {"POST", "https://accounts.google.com/o/oauth2/token", /* Address to get refresh token from access code */
167 {OA2_Id
, OA2_Secret
, OA2_Redirect
, OA2_GrantTypeforAccessToken
, OA2_Code
, OA2_End
, OA2_End
}},
168 {"POST", "https://accounts.google.com/o/oauth2/token", /* access token from refresh token */
169 {OA2_Id
, OA2_Secret
, OA2_RefreshToken
, OA2_GrantTypefromRefreshToken
, OA2_End
, OA2_End
, OA2_End
}}
171 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information */
172 NULL
, /* access token */
173 0, /* expiration time */
174 0, /* first time indicator */
175 1, /* client secret required */
176 0 /* Cancel refresh token */
179 {"outlook.office365.com", "smtp.office365.com", NULL
, NULL
},
180 {{"client_id", NULL
},
181 {"client_secret", NULL
}, /* not used, but needed */
182 {"tenant", NULL
}, /* used */
183 {"code", NULL
}, /* not used, not needed */
184 {"refresh_token", NULL
},
185 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
186 {"grant_type", "urn:ietf:params:oauth:grant-type:device_code"},
187 {"scope", NULL
}, /* not used */
188 {"grant_type", "refresh_token"},
189 {"response_type", "code"}, /* not used */
190 {"state", NULL
}, /* not used */
191 {"device_code", NULL
} /* only used for frst time set up */
193 {{NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* Get Access Code, Not used */
194 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/devicecode", /* first time use and get device code information */
195 {OA2_Id
, OA2_Scope
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}},
196 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
197 {OA2_Id
, OA2_Redirect
, OA2_DeviceCode
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}},
198 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
199 {OA2_Id
, OA2_RefreshToken
, OA2_Scope
, OA2_GrantTypefromRefreshToken
, OA2_End
, OA2_End
, OA2_End
}}
201 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information */
202 NULL
, /* access token */
203 0, /* expiration time */
204 0, /* first time indicator */
205 0, /* client secret required */
206 0 /* Cancel refresh token */
209 {"outlook.office365.com", "smtp.office365.com", NULL
, NULL
},
210 {{"client_id", NULL
},
211 {"client_secret", NULL
}, /* not used, but needed */
212 {"tenant", NULL
}, /* used */
213 {"code", NULL
}, /* used during authorization */
214 {"refresh_token", NULL
},
215 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
216 {"redirect_uri", "http://localhost"},
217 {"grant_type", "authorization_code"},
218 {"grant_type", "refresh_token"},
219 {"response_type", "code"},
220 {"state", NULL
}, /* not used */
221 {"device_code", NULL
} /* not used */
223 {{"GET", "https://login.microsoftonline.com/\001/oauth2/v2.0/authorize", /* Get Access Code */
224 {OA2_Id
, OA2_Scope
, OA2_Redirect
, OA2_Response
, OA2_End
, OA2_End
, OA2_End
}},
225 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* device code, not used */
226 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
227 {OA2_Id
, OA2_Redirect
, OA2_Scope
, OA2_GrantTypeforAccessToken
, OA2_Secret
, OA2_Code
, OA2_End
}},
228 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
229 {OA2_Id
, OA2_RefreshToken
, OA2_Scope
, OA2_GrantTypefromRefreshToken
, OA2_Secret
, OA2_End
, OA2_End
}}
231 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information, not used */
232 NULL
, /* access token */
233 0, /* expiration time */
234 0, /* first time indicator */
235 1, /* client secret required */
236 0 /* Cancel refresh token */
239 {"imap.yandex.com", "smtp.yandex.com", NULL
, NULL
},
240 {{"client_id", NULL
},
241 {"client_secret", NULL
}, /* not used, but needed */
242 {"tenant", NULL
}, /* not used */
243 {"code", NULL
}, /* used during authorization */
244 {"refresh_token", NULL
},
245 {"scope", NULL
}, /* not needed, so not used */
246 {"redirect_uri", "https://oauth.yandex.ru/verification_code"},
247 {"grant_type", "authorization_code"},
248 {"grant_type", "refresh_token"},
249 {"response_type", "code"},
250 {"state", NULL
}, /* not used */
251 {"device_code", NULL
} /* not used */
253 {{"GET", "https://oauth.yandex.com/authorize", /* Get Access Code */
254 {OA2_Id
, OA2_Redirect
, OA2_Response
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}},
255 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* device code, not used */
256 {"POST", "https://oauth.yandex.com/token", /* Get first Refresh Token and Access token */
257 {OA2_Id
, OA2_Redirect
, OA2_GrantTypeforAccessToken
, OA2_Secret
, OA2_Code
, OA2_End
, OA2_End
}},
258 {"POST", "https://oauth.yandex.com/token", /* Get access token from refresh token */
259 {OA2_Id
, OA2_RefreshToken
, OA2_GrantTypefromRefreshToken
, OA2_Secret
, OA2_End
, OA2_End
, OA2_End
}}
261 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information, not used */
262 NULL
, /* access token */
263 0, /* expiration time */
264 0, /* first time indicator */
265 1, /* client secret required */
266 0 /* Cancel refresh token */
268 { NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, 0, 0, 0, 0},
272 xoauth2_flow_tool(struct pine
*ps
, int cmd
, CONF_S
**cl
, unsigned int flags
)
278 *((*cl
)->d
.xf
.selected
) = (*cl
)->d
.xf
.pat
;
279 rv
= simple_exit_cmd(flags
);
282 rv
= simple_exit_cmd(flags
);
290 ps
->mangled_body
= 1;
296 oauth2_select_flow(char *host
)
298 OAUTH2_S
*oa2list
, *oa2
;
303 CONF_S
*ctmp
= NULL
, *first_line
= NULL
;
304 OAUTH2_S
*x_sel
= NULL
;
308 dprint((9, "xoauth2 select flow"));
309 ps_global
->next_screen
= SCREEN_FUN_NULL
;
311 memset(&screen
, 0, sizeof(screen
));
313 for(i
= 0; i
< sizeof(tmp
) && i
< ps_global
->ttyo
->screen_cols
; i
++)
318 ctmp
->flags
|= CF_NOSELECT
;
319 ctmp
->value
= cpystr(tmp
);
322 ctmp
->flags
|= CF_NOSELECT
;
323 ctmp
->value
= cpystr(_("Please select below the authorization flow you would like to follow:"));
326 ctmp
->flags
|= CF_NOSELECT
;
327 ctmp
->value
= cpystr(tmp
);
329 for(oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
330 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
331 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
]){
335 method
= oa2list
->server_mthd
[0].name
? "Authorize"
336 : (oa2list
->server_mthd
[1].name
? "Device" : "Unknown");
337 sprintf(tmp
, "%s (%s)", oa2list
->name
, method
);
338 ctmp
->value
= cpystr(tmp
);
339 ctmp
->d
.xf
.selected
= &x_sel
;
340 ctmp
->d
.xf
.pat
= oa2list
;
341 ctmp
->keymenu
= &xoauth2_id_select_km
;
342 ctmp
->help
= NO_HELP
;
343 ctmp
->help_title
= NULL
;
344 ctmp
->tool
= xoauth2_flow_tool
;
345 ctmp
->flags
= CF_STARTITEM
;
349 (void)conf_scroll_screen(ps_global
, &screen
, first_line
, _("SELECT AUTHORIZATION FLOW"),
350 _("xoauth2"), 0, NULL
);
359 for(oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++)
360 n
+= strlen(oa2list
->name
); + 5; /* number, parenthesis, space */
361 n
+= 1024; /* large enough to display to lines of 80 characters in UTF-8 */
362 s
= fs_get(n
*sizeof(char));
363 strcpy(s
, _("Please select below the authorization flow you would like to follow:"));
364 sprintf(s
+ strlen(s
), _("Please select the client-id to use from the following list.\n\n"));
365 for(j
= 1, oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
366 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
367 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
])
368 sprintf(s
+ strlen(s
), " %d) %.70s\n", j
++, oa2list
->name
);
370 display_init_err(s
, 0);
372 strncpy(prompt
, _("Enter your selection number: "), sizeof(prompt
));
373 prompt
[sizeof(prompt
)-1] = '\0';
375 rv
= optionally_enter(reply
, 0, 0, sizeof(reply
), prompt
, NULL
, NO_HELP
, 0);
377 rv
= (sel
>= 0 && sel
< i
) ? 0 : -1;
380 for(j
= 1, oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
381 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
382 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
]){
392 typedef struct auth_code_s
{
398 oauth2device_decode_reply(void *datap
, void *replyp
)
400 OAUTH2_DEVICEPROC_S
*av
= (OAUTH2_DEVICEPROC_S
*)datap
;
401 int reply
= *(int *) replyp
;
403 return reply
< 0 ? av
->code_failure
: (reply
== 0 ? av
->code_success
: av
->code_wait
);
407 oauth2_elapsed_done(void *aux_valuep
)
409 OAUTH2_S
*oauth2
= aux_valuep
? ((OAUTH2_DEVICEPROC_S
*) aux_valuep
)->xoauth2
: NULL
;
410 static time_t savedt
= 0, now
;
413 if(aux_valuep
== NULL
) savedt
= 0; /* reset last time we approved */
416 if(oauth2
->devicecode
.interval
+ now
>= savedt
)
425 oauth2_set_device_info(OAUTH2_S
*oa2
, char *method
)
427 char tmp
[MAILTMPLEN
];
429 char *name
= oa2
->name
;
431 OAUTH2_DEVICECODE_S
*deviceinfo
= &oa2
->devicecode
;
432 OAUTH2_DEVICEPROC_S aux_value
;
436 STORE_S
*in_store
, *out_store
;
438 HANDLE_S
*handles
= NULL
;
439 AUTH_CODE_S user_input
;
441 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
442 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
445 aux_value
.xoauth2
= oa2
;
446 aux_value
.code_success
= 'e';
447 aux_value
.code_failure
= 'e';
448 aux_value
.code_wait
= NO_OP_COMMAND
;
450 so_puts(in_store
, "<HTML><P>");
451 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name
);
452 so_puts(in_store
, tmp
);
453 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name
, method
),
454 so_puts(in_store
, tmp
);
456 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
458 _("</P><P>To sign in, use a web browser to open the page <A HREF=\"%s\">%s</A> and enter the code \"%s\" without the quotes."),
459 deviceinfo
->verification_uri
, deviceinfo
->verification_uri
, deviceinfo
->user_code
);
460 so_puts(in_store
, tmp
);
463 so_puts(in_store
, "</P><P>");
464 so_puts(in_store
, deviceinfo
->message
);
466 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
467 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), name
);
468 so_puts(in_store
, tmp
);
469 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
471 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 "));
472 so_puts(in_store
, _("to grant access to Alpine to your data. "));
473 so_puts(in_store
, _("</P><P> Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. If you do "));
474 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 "));
475 so_puts(in_store
, _("alive at the end of this process, and your connection will proceed from there."));
476 so_puts(in_store
, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit"));
477 so_puts(in_store
, _("</P></HTML>"));
479 so_seek(in_store
, 0L, 0);
480 init_handles(&handles
);
482 gf_link_filter(gf_html2plain
,
483 gf_html2plain_opt(NULL
,
484 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
485 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
486 gf_set_so_readc(&gc
, in_store
);
487 gf_set_so_writec(&pc
, out_store
);
489 gf_clear_so_writec(out_store
);
490 gf_clear_so_readc(in_store
);
492 memset(&sargs
, 0, sizeof(SCROLL_S
));
493 sargs
.text
.handles
= handles
;
494 sargs
.text
.text
= so_text(out_store
);
495 sargs
.text
.src
= CharStar
;
496 sargs
.text
.desc
= _("help text");
497 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
498 sargs
.proc
.tool
= oauth2_auth_answer
;
499 sargs
.proc
.data
.p
= (void *)&user_input
;
500 sargs
.keys
.menu
= &oauth2_device_auth_keymenu
;
501 /* don't want to re-enter c-client */
502 sargs
.quell_newmail
= 1;
503 setbitmap(sargs
.keys
.bitmap
);
504 sargs
.help
.text
= h_oauth2_start
;
505 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
506 sargs
.aux_function
= oauth2deviceinfo_get_accesscode
;
507 sargs
.aux_value
= (void *) &aux_value
;
508 sargs
.aux_condition
= oauth2_elapsed_done
;
509 sargs
.decode_aux_rv_value
= oauth2device_decode_reply
;
510 sargs
.aux_rv_value
= (void *) &aux_rv_value
;
514 ps_global
->mangled_screen
= 1;
515 ps_global
->painted_body_on_startup
= 0;
516 ps_global
->painted_footer_on_startup
= 0;
517 } while (user_input
.answer
!= 'e');
521 free_handles(&handles
);
522 oauth2_elapsed_done(NULL
);
525 int flags
, rc
, q_line
;
526 /* TRANSLATORS: user needs to input an access code from the server */
527 char prompt
[MAILTMPLEN
], token
[MAILTMPLEN
];
529 * If screen hasn't been initialized yet, use want_to.
533 tmp_20k_buf
[0] = '\0';
534 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
535 _("Authorizing Alpine Access to %s Email Services\n\n"), name
);
536 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
538 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
539 _("Alpine is attempting to log you into your %s account, using the %s method. "), name
, method
),
540 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
542 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
543 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
544 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
545 deviceinfo
->verification_uri
, deviceinfo
->user_code
);
546 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
549 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
550 "%s\n\n", deviceinfo
->message
);
551 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
554 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
555 _("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
);
556 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
558 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
559 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
560 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
562 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
563 "%s", _("to grant access to Alpine to your data.\n\n"));
564 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
566 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
567 "%s", _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
568 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
570 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
571 "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
572 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
574 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
575 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
576 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
578 display_init_err(tmp_20k_buf
, 0);
579 memset((void *)tmp
, 0, sizeof(tmp
));
580 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
581 tmp
[sizeof(tmp
)-1] = '\0';
583 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
586 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
587 flags
= OE_APPEND_CURRENT
;
589 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
590 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
591 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
593 aux_value
.xoauth2
= oa2
;
594 aux_value
.code_success
= 'y';
595 aux_value
.code_failure
= 'n';
596 aux_value
.code_wait
= 'w';
598 strncpy(tmp
, _("Continue waiting"), sizeof(tmp
));
599 tmp
[sizeof(tmp
)-1] = '\0';
601 if(oauth2_elapsed_done((void *) &aux_value
) == 0)
602 oauth2deviceinfo_get_accesscode((void *) &aux_value
, (void *) &rv
);
603 ch
= oauth2device_decode_reply((void *) &aux_value
, (void *) &rv
);
604 } while (ch
== 'w' || want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y');
605 oauth2_elapsed_done(NULL
);
611 oauth2_get_access_code(unsigned char *url
, char *method
, OAUTH2_S
*oauth2
, int *tryanother
)
613 char tmp
[MAILTMPLEN
];
618 STORE_S
*in_store
, *out_store
;
620 HANDLE_S
*handles
= NULL
;
621 AUTH_CODE_S user_input
;
623 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
624 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
627 so_puts(in_store
, "<HTML><BODY><P>");
628 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2
->name
);
629 so_puts(in_store
, tmp
);
630 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
631 so_puts(in_store
, tmp
);
633 if(strucmp(oauth2
->name
, GMAIL_NAME
) == 0){
634 so_puts(in_store
, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
635 so_puts(in_store
, _("</P><P> First you must register Alpine with Google and create a client-id and client-secret. If you already did that, then you can skip to the authorization step, and continue with the process outlined below."));
636 so_puts(in_store
, _("<UL> "));
637 so_puts(in_store
, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
638 so_puts(in_store
, _("and create a project. The name of the project is not important."));
639 so_puts(in_store
, _("<LI> Go to the Consent Screen and make your app INTERNAL, if your account is a G-Suite account, or EXTERNAL if it is a personal gmail.com account."));
640 so_puts(in_store
, _("<LI> Create OAUTH Credentials."));
641 so_puts(in_store
, _("</UL> "));
642 so_puts(in_store
, _("<P> As a result of this process, you will get a client-id and a client-secret."));
643 so_puts(in_store
, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
644 so_puts(in_store
, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
645 so_puts(in_store
, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
646 so_puts(in_store
, _("</P><P> If you completed these steps successfully, you are ready to move to the second part, where you will authorize Gmail to give access to Alpine to access your email."));
649 so_puts(in_store
, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
650 so_puts(in_store
,"</P><P>");
651 sprintf(tmp_20k_buf
, _("<A HREF=\"%s\">%s</A>"), url
, url
);
652 so_puts(in_store
, tmp_20k_buf
);
654 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
655 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2
->name
);
656 so_puts(in_store
, tmp
);
657 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
659 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. "));
660 so_puts(in_store
, _(" At the end of this process, you will be given an access code or redirected to a web page."));
661 so_puts(in_store
, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
662 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. "));
663 so_puts(in_store
, _(" Once you have completed this process, Alpine will proceed with authentication."));
664 so_puts(in_store
, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
665 so_puts(in_store
, _("</P></BODY></HTML>"));
667 so_seek(in_store
, 0L, 0);
668 init_handles(&handles
);
670 gf_link_filter(gf_html2plain
,
671 gf_html2plain_opt(NULL
,
672 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
673 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
674 gf_set_so_readc(&gc
, in_store
);
675 gf_set_so_writec(&pc
, out_store
);
677 gf_clear_so_writec(out_store
);
678 gf_clear_so_readc(in_store
);
680 memset(&sargs
, 0, sizeof(SCROLL_S
));
681 sargs
.text
.handles
= handles
;
682 sargs
.text
.text
= so_text(out_store
);
683 sargs
.text
.src
= CharStar
;
684 sargs
.text
.desc
= _("help text");
685 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
686 sargs
.proc
.tool
= oauth2_auth_answer
;
687 sargs
.proc
.data
.p
= (void *)&user_input
;
688 sargs
.keys
.menu
= &oauth2_auth_keymenu
;
689 /* don't want to re-enter c-client */
690 sargs
.quell_newmail
= 1;
691 setbitmap(sargs
.keys
.bitmap
);
692 sargs
.help
.text
= h_oauth2_start
;
693 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
697 ps_global
->mangled_screen
= 1;
698 ps_global
->painted_body_on_startup
= 0;
699 ps_global
->painted_footer_on_startup
= 0;
700 } while (user_input
.answer
!= 'e');
702 if(!struncmp(user_input
.code
, "http://", 7)
703 || !struncmp(user_input
.code
, "https://", 8)){
705 s
= strstr(user_input
.code
, "code=");
713 else code
= user_input
.code
? cpystr(user_input
.code
) : NULL
;
714 if(user_input
.code
) fs_give((void **) &user_input
.code
);
716 if(code
== NULL
) *tryanother
= 1;
720 free_handles(&handles
);
723 int flags
, rc
, q_line
;
724 /* TRANSLATORS: user needs to input an access code from the server */
725 char *accesscodelabel
= _("Copy and Paste Access Code");
726 char prompt
[MAILTMPLEN
], token
[MAILTMPLEN
];
728 * If screen hasn't been initialized yet, use want_to.
731 tmp_20k_buf
[0] = '\0';
732 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
733 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2
->name
);
734 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
736 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
737 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
738 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
740 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
741 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
742 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
744 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
746 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
748 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
749 _("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
);
750 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
752 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
753 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
754 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
756 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
757 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
758 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
760 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
761 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
762 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
764 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
765 "%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. "));
766 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
768 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
769 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
770 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
772 display_init_err(tmp_20k_buf
, 0);
773 memset((void *)tmp
, 0, sizeof(tmp
));
774 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
775 tmp
[sizeof(tmp
)-1] = '\0';
777 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
778 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
779 flags
= OE_APPEND_CURRENT
;
780 sprintf(prompt
, "%s: ", accesscodelabel
);
782 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
783 prompt
, NULL
, NO_HELP
, &flags
);
784 } while (rc
!= 0 && rc
!= 1);
785 if(!struncmp(token
, "http://", 7)
786 || !struncmp(token
, "https://", 8)){
788 s
= strstr(token
, "code=");
796 else code
= token
[0] ? cpystr(token
) : NULL
;
803 void mm_login_oauth2(NETMBX
*, char *, char *, OAUTH2_S
*, long int, char *, char *);
805 /* The purpose of this function is to report to c-client the values of the
806 * different tokens and codes so that c-client can try to log in the user
807 * to the server. This function DOES NOT attempt to get these values for
808 * the user. That is attempted in the c-client side (as best as it can be
809 * done given our circumstances: no http support, no javascript support,
810 * etc.). This is the best we can do:
812 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
813 * as best as we can. Unloaded means that there is no server known to
814 * connect, no access token, etc. Pretty much nothing is known about
815 * how to get access code, access token, etc. We ask the user to fill
816 * it up for us, if they can. If the user fills it up we save those
819 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
820 * save the information that c-client got for us.
822 * 3. When saving this information we use the password caching facilities,
823 * but we must do it in a different format so that old information and
824 * new information are not mixed. In order to accommodate this for new
825 * authentication methods, we save the information in the same fields,
826 * but this time we modify it slightly, so that old functions fail to
827 * understand the new information and so not modify it nor use it. The
828 * modification is simple: Every piece of information that was saved
829 * before is prepended XOAUTH2\001 to indicate the authentication
830 * method for which it works. The character \001 is a separator. New
831 * Alpine will know how to deal with this, but old versions, will not
832 * strip this prefix from the information and fail to get the
833 * information or modify it when needed. Only new versions of Alpine will
834 * know how to process this information.
835 * new_value = authenticator_method separator old_value
836 * authenticator_method = "XOAUTH2_S"
838 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
839 * authenticator is "XOAUTH2\001imap.gmail.com".
840 * In addition, the password field is not used to encode the password
841 * anymore, it is used to save login information needed in a format that
842 * the caller function chooses, but that must be preceded by the
843 * "authenticator_method separator" code as above.
846 mm_login_oauth2(NETMBX
*mb
, char *user
, char *method
,
847 OAUTH2_S
*login
, long int trial
,
848 char *usethisprompt
, char *altuserforcache
)
850 char *token
, tmp
[MAILTMPLEN
];
851 char prompt
[4*MAILTMPLEN
], value
[4*MAILTMPLEN
], *last
;
852 char defuser
[NETMAXUSER
];
853 char hostleadin
[80], hostname
[200], defubuf
[200];
854 char logleadin
[80], pwleadin
[50];
857 char *OldRefreshToken
, *OldAccessToken
;
858 char *NewRefreshToken
, *NewAccessToken
;
859 char *SaveRefreshToken
, *SaveAccessToken
;
860 /* TRANSLATORS: A label for the hostname that the user is logging in on */
861 char *hostlabel
= _("HOST");
862 /* TRANSLATORS: user is logging in as a particular user (a particular
863 login name), this is just labelling that user name. */
864 char *userlabel
= _("USER");
865 STRLIST_S hostlist
[2], hostlist2
[OAUTH2_TOT_EQUIV
+1];
867 int len
, rc
, q_line
, flags
, i
, j
;
868 int oespace
, avail
, need
, save_dont_use
;
871 int ChangeAccessToken
, ChangeRefreshToken
, ChangeExpirationTime
;
872 OAUTH2_S
*oa2list
, *oa2
;
875 unsigned long OldExpirationTime
, NewExpirationTime
, SaveExpirationTime
;
876 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
877 int preserve_password
= -1;
880 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
881 trial
, mb
->user
? mb
->user
: "(null)",
882 mb
->service
? mb
->service
: "(null)",
883 mb
->port
? " port=" : "",
884 mb
->port
? comatose(mb
->port
) : "",
885 altuserforcache
? " altuserforcache =" : "",
886 altuserforcache
? altuserforcache
: ""));
888 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
890 save_in_init
= ps_global
->in_init_seq
;
891 ps_global
->in_init_seq
= 0;
892 ps_global
->no_newmail_check_from_optionally_enter
= 1;
894 /* make sure errors are seen */
895 if(ps_global
->ttyo
&& !ps_global
->noshow_error
)
896 flush_status_messages(0);
898 token
= NULL
; /* start from scratch */
900 hostlist
[0].name
= mb
->host
;
901 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
902 hostlist
[0].next
= &hostlist
[1];
903 hostlist
[1].name
= mb
->orighost
;
904 hostlist
[1].next
= NULL
;
907 hostlist
[0].next
= NULL
;
909 if(hostlist
[0].name
){
910 dprint((9, "mm_login_oauth2: host=%s\n",
911 hostlist
[0].name
? hostlist
[0].name
: "?"));
912 if(hostlist
[0].next
&& hostlist
[1].name
){
913 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist
[1].name
));
917 if(trial
== 0L && !altuserforcache
){
918 if(*mb
->user
!= '\0')
919 strncpy(user
, mb
->user
, NETMAXUSER
);
921 flags
= OE_APPEND_CURRENT
;
922 sprintf(prompt
, "%s: %s - %s: ", hostlabel
, mb
->orighost
, userlabel
);
923 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
924 prompt
, NULL
, NO_HELP
, &flags
);
926 user
[NETMAXUSER
-1] = '\0';
930 * We check to see if the server we are going to log in to is already
931 * registered. This gives us a list of servers with the same
932 * credentials, so we use the same credentials for all of them.
935 for(registered
= 0, oa2list
= alpine_oauth2_list
;
936 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
938 for(i
= 0; i
< OAUTH2_TOT_EQUIV
939 && oa2list
->host
[i
] != NULL
940 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
941 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
948 x
= oauth2_get_client_info(oa2list
->name
, user
);
950 for(oa2list
= alpine_oauth2_list
;
951 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
953 for(i
= 0; i
< OAUTH2_TOT_EQUIV
954 && oa2list
->host
[i
] != NULL
955 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
956 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
957 char *flow
= oa2list
->server_mthd
[0].name
? "Authorize"
958 : (oa2list
->server_mthd
[1].name
? "Device" : "Unknown");
959 if(!strucmp(x
->flow
, flow
)) break; /* found it */
963 /* else use the one we found earlier, the user has to configure this better */
967 hostlist2
[i
= 0].name
= mb
->host
;
968 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
))
969 hostlist2
[++i
].name
= mb
->orighost
;
971 for(j
= 0; j
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[j
] != NULL
; j
++){
973 for(k
= 0; k
<= i
&& hostlist2
[k
].name
974 && strcmp(hostlist2
[k
].name
, oa2list
->host
[j
]); k
++);
976 hostlist2
[++i
].name
= oa2list
->host
[j
];
978 hostlist2
[i
+1].name
= NULL
;
979 hostlist2
[i
+1].next
= NULL
;
980 for(j
= i
; j
>= 0; j
--)
981 hostlist2
[j
].next
= &hostlist2
[j
+1];
985 * We check if we have a refresh token saved somewhere, if so
986 * we use it to get a new access token, otherwise we need to
987 * get an access code so we can get (and save) a refresh token
988 * and use the access token.
990 if(trial
== 0L && !altuserforcache
){
991 /* Search for a refresh token that is already loaded ... */
992 if(imap_get_passwd_auth(mm_login_list
, &token
, user
,
993 registered
? hostlist2
: hostlist
,
994 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
995 dprint((9, "mm_login_oauth2: found a refresh token\n"));
996 ps_global
->no_newmail_check_from_optionally_enter
= 0;
997 ps_global
->in_init_seq
= save_in_init
;
999 #ifdef LOCAL_PASSWD_CACHE
1000 /* or see if we have saved one in the local password cache and load it */
1001 else if(get_passfile_passwd_auth(ps_global
->pinerc
, &token
,
1002 user
, registered
? hostlist2
: hostlist
,
1003 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
1004 imap_set_passwd_auth(&mm_login_list
, token
, user
,
1005 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
1006 update_passfile_hostlist_auth(ps_global
->pinerc
, user
, hostlist
,
1007 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
);
1008 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
1009 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1010 ps_global
->in_init_seq
= save_in_init
;
1012 if(token
&& *token
) preserve_password
= 1; /* resave it, no need to ask */
1013 #endif /* LOCAL_PASSWD_CACHE */
1015 user
[NETMAXUSER
-1] = '\0';
1017 /* The Old* variables is what c_client knows */
1018 OldRefreshToken
= login
->cancel_refresh_token
? NULL
: login
->param
[OA2_RefreshToken
].value
;
1019 OldAccessToken
= login
->access_token
;
1020 OldExpirationTime
= login
->expiration
;
1022 /* The New* variables is what Alpine knows */
1023 NewRefreshToken
= NewAccessToken
= NULL
;
1024 NewExpirationTime
= 0L;
1025 ChangeAccessToken
= ChangeRefreshToken
= ChangeExpirationTime
= 0;
1027 if(token
&& *token
&& !login
->cancel_refresh_token
){
1031 t
= strchr(s
, PWDAUTHSEP
);
1033 NewRefreshToken
= cpystr(s
);
1036 NewRefreshToken
= cpystr(s
);
1038 t
= strchr(s
, PWDAUTHSEP
);
1040 NewAccessToken
= cpystr(s
);
1043 NewAccessToken
= cpystr(s
);
1045 NewExpirationTime
= strtol(s
, &s
, 10);
1046 if(NewExpirationTime
<= 0 || NewExpirationTime
<= time(0))
1047 NewExpirationTime
= 0L;
1050 /* check we got good information, and send good information below */
1051 if(NewRefreshToken
&& !*NewRefreshToken
)
1052 fs_give((void **) &NewRefreshToken
);
1053 if(NewAccessToken
&& (NewExpirationTime
== 0L || !*NewAccessToken
))
1054 fs_give((void **) &NewAccessToken
);
1057 if(NewRefreshToken
== NULL
)
1058 login
->first_time
++;
1060 if(login
->first_time
){ /* count how many authorization methods we support */
1063 for(nmethods
= 0, oa2
= alpine_oauth2_list
; oa2
&& oa2
->name
; oa2
++){
1064 for(j
= 0; j
< OAUTH2_TOT_EQUIV
1066 && oa2
->host
[j
] != NULL
1067 && strucmp(oa2
->host
[j
], mb
->orighost
) != 0; j
++);
1068 if(oa2
&& oa2
->host
&& j
< OAUTH2_TOT_EQUIV
&& oa2
->host
[j
])
1073 oa2list
= oauth2_select_flow(mb
->orighost
);
1075 if(!oa2list
) registered
= 0;
1078 /* Default to saving what we already had saved */
1080 SaveRefreshToken
= login
->cancel_refresh_token
? NULL
: NewRefreshToken
;
1081 SaveAccessToken
= NewAccessToken
;
1082 SaveExpirationTime
= NewExpirationTime
;
1084 /* Translation of the logic below:
1085 * if (c-client has a refresh token
1086 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1087 forget the Alpine refresh token;
1088 make the Alpine Refresh token = c-client refresh token.;
1089 signal that we changed the refresh token;
1090 In this situation we do not need to clear up the Alpine access token. This will
1091 expire, so we can use it until it expires. We can save the c-client refresh token
1092 together with the Alpine Access Token and the expiration date of the Alpine Access
1094 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1095 forget the Alpine refresh token;
1096 if Alpine has an access token, forget it;
1097 reset the expiration time
1098 signal that we changed the refresh token; (because the service expired it)
1102 if(OldRefreshToken
!= NULL
1103 && (NewRefreshToken
== NULL
|| strcmp(OldRefreshToken
, NewRefreshToken
))){
1104 if(NewRefreshToken
) fs_give((void **) &NewRefreshToken
);
1105 NewRefreshToken
= cpystr(OldRefreshToken
);
1106 ChangeRefreshToken
++;
1107 SaveRefreshToken
= OldRefreshToken
;
1108 SaveAccessToken
= NewAccessToken
;
1109 SaveExpirationTime
= NewExpirationTime
;
1110 } else if (OldRefreshToken
== NULL
&& NewRefreshToken
!= NULL
&& trial
> 0){
1111 fs_give((void **) &NewRefreshToken
);
1112 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1113 NewExpirationTime
= 0L;
1114 ChangeRefreshToken
++;
1115 SaveRefreshToken
= NULL
;
1116 SaveAccessToken
= NULL
;
1117 SaveExpirationTime
= 0L;
1120 if(OldAccessToken
!= NULL
1121 && (NewAccessToken
== NULL
|| strcmp(OldAccessToken
, NewAccessToken
))){
1122 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1123 NewAccessToken
= cpystr(OldAccessToken
);
1124 ChangeAccessToken
++;
1125 NewExpirationTime
= OldExpirationTime
;
1126 SaveRefreshToken
= NewRefreshToken
;
1127 SaveAccessToken
= NewAccessToken
;
1128 SaveExpirationTime
= NewExpirationTime
;
1132 login
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1133 login
->access_token
= SaveAccessToken
;
1134 login
->expiration
= SaveExpirationTime
;
1136 oa2list
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1137 oa2list
->access_token
= SaveAccessToken
;
1138 oa2list
->expiration
= SaveExpirationTime
;
1139 oa2list
->first_time
= login
->first_time
;
1140 oa2list
->cancel_refresh_token
= login
->cancel_refresh_token
;
1141 *login
= *oa2list
; /* load login pointer */
1144 if(!ChangeAccessToken
&& !ChangeRefreshToken
&& !login
->cancel_refresh_token
)
1147 /* get ready to save this information. The format will be
1148 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1149 * (spaces added for clarity, \001 is PWDAUTHSEP)
1151 if(token
) fs_give((void **) &token
);
1152 sprintf(tmp
, "%lu", SaveExpirationTime
);
1153 tmp
[sizeof(tmp
) - 1] = '\0';
1154 len
= strlen(SaveRefreshToken
? SaveRefreshToken
: "")
1155 + strlen(SaveAccessToken
? SaveAccessToken
: "")
1157 token
= fs_get(len
+ 1);
1158 sprintf(token
, "%s%c%s%c%lu",
1159 SaveRefreshToken
? SaveRefreshToken
: "", PWDAUTHSEP
,
1160 SaveAccessToken
? SaveAccessToken
: "", PWDAUTHSEP
,
1161 SaveExpirationTime
);
1163 /* remember the access information for next time */
1164 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
1165 imap_set_passwd_auth(&mm_login_list
, token
,
1166 altuserforcache
? altuserforcache
: user
, hostlist
,
1167 (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
1168 #ifdef LOCAL_PASSWD_CACHE
1169 /* if requested, remember it on disk for next session */
1170 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
1171 set_passfile_passwd_auth(ps_global
->pinerc
, token
,
1172 altuserforcache
? altuserforcache
: user
, hostlist
,
1173 (mb
->sslflag
||mb
->tlsflag
),
1174 (preserve_password
== -1 ? 0
1175 : (preserve_password
== 0 ? 2 :1)), OA2NAME
);
1176 #endif /* LOCAL_PASSWD_CACHE */
1178 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1181 /*----------------------------------------------------------------------
1182 receive notification from IMAP
1184 Args: stream -- Mail stream message is relevant to
1185 string -- The message text
1186 errflg -- Set if it is a serious error
1188 Result: message displayed in status line
1190 The facility is for general notices, such as connection to server;
1191 server shutting down etc... It is used infrequently.
1192 ----------------------------------------------------------------------*/
1194 mm_notify(MAILSTREAM
*stream
, char *string
, long int errflg
)
1199 now
= time((time_t *)0);
1200 tm_now
= localtime(&now
);
1202 /* be sure to log the message... */
1204 if(ps_global
->debug_imap
|| ps_global
->debugmem
)
1205 dprint((errflg
== TCPDEBUG
? 7 : 2,
1206 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1207 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1208 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1209 (!errflg
) ? "babble" :
1210 (errflg
== ERROR
) ? "error" :
1211 (errflg
== WARN
) ? "warning" :
1212 (errflg
== PARSE
) ? "parse" :
1213 (errflg
== TCPDEBUG
) ? "tcp" :
1214 (errflg
== BYE
) ? "bye" : "unknown",
1215 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1216 string
? string
: "?"));
1219 snprintf(ps_global
->last_error
, sizeof(ps_global
->last_error
), "%s : %.*s",
1220 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1221 (int) MIN(MAX_SCREEN_COLS
, sizeof(ps_global
->last_error
)-70),
1223 ps_global
->last_error
[ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1224 : sizeof(ps_global
->last_error
)-1] = '\0';
1227 * Then either set special bits in the pine struct or
1228 * display the message if it's tagged as an "ALERT" or
1229 * its errflg > NIL (i.e., WARN, or ERROR)
1233 * We'd like to sp_mark_stream_dead() here but we can't do that because
1234 * that might call mail_close and we are already in a c-client callback.
1235 * So just set the dead bit and clean it up later.
1237 sp_set_dead_stream(stream
, 1);
1238 else if(!strncmp(string
, "[TRYCREATE]", 11))
1239 ps_global
->try_to_create
= 1;
1240 else if(!strncmp(string
, "[REFERRAL ", 10))
1241 ; /* handled in the imap_referral() callback */
1242 else if(!strncmp(string
, "[ALERT]", 7))
1243 q_status_message2(SM_MODAL
, 3, 3,
1244 _("Alert received while accessing \"%s\": %s"),
1245 (stream
&& stream
->mailbox
)
1246 ? stream
->mailbox
: "-no folder-",
1247 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf
+10000),
1248 SIZEOF_20KBUF
-10000, string
));
1249 else if(!strncmp(string
, "[UNSEEN ", 8)){
1253 for(p
= string
+ 8; isdigit(*p
); p
++)
1254 n
= (n
* 10) + (*p
- '0');
1256 sp_set_first_unseen(stream
, n
);
1258 else if(!strncmp(string
, "[READ-ONLY]", 11)
1259 && !(stream
&& stream
->mailbox
&& IS_NEWS(stream
)))
1260 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, "%s : %s",
1261 (stream
&& stream
->mailbox
)
1262 ? stream
->mailbox
: "-no folder-",
1264 else if((errflg
&& errflg
!= BYE
&& errflg
!= PARSE
)
1265 && !ps_global
->noshow_error
1267 && (ps_global
->noshow_warn
|| (stream
&& stream
->unhealthy
))))
1268 q_status_message(SM_ORDER
| ((errflg
== ERROR
) ? SM_DING
: 0),
1269 3, 6, ps_global
->last_error
);
1273 /*----------------------------------------------------------------------
1274 Queue imap log message for display in the message line
1276 Args: string -- The message
1277 errflg -- flag set to 1 if pertains to an error
1279 Result: Message queued for display
1281 The c-client/imap reports most of it's status and errors here
1284 mm_log(char *string
, long int errflg
)
1286 char message
[sizeof(ps_global
->c_client_error
)];
1288 int was_capitalized
;
1289 static char saw_kerberos_init_warning
;
1293 now
= time((time_t *)0);
1294 tm_now
= localtime(&now
);
1296 dprint((((errflg
== TCPDEBUG
) && ps_global
->debug_tcp
) ? 1 :
1297 (errflg
== TCPDEBUG
) ? 10 : 2,
1298 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1299 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1300 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1301 (!errflg
) ? "babble" :
1302 (errflg
== ERROR
) ? "error" :
1303 (errflg
== WARN
) ? "warning" :
1304 (errflg
== PARSE
) ? "parse" :
1305 (errflg
== TCPDEBUG
) ? "tcp" :
1306 (errflg
== BYE
) ? "bye" : "unknown",
1307 string
? string
: "?"));
1309 if(errflg
== ERROR
&& !strncmp(string
, "[TRYCREATE]", 11)){
1310 ps_global
->try_to_create
= 1;
1313 else if(ps_global
->try_to_create
1314 || !strncmp(string
, "[CLOSED]", 8)
1315 || (sp_dead_stream(ps_global
->mail_stream
) && strstr(string
, "No-op")))
1317 * Don't display if creating new folder OR
1318 * warning about a dead stream ...
1322 strncpy(message
, string
, sizeof(message
));
1323 message
[sizeof(message
) - 1] = '\0';
1325 if(errflg
== WARN
&& srchstr(message
, "try running kinit") != NULL
){
1326 if(saw_kerberos_init_warning
)
1329 saw_kerberos_init_warning
= 1;
1332 /*---- replace all "mailbox" with "folder" ------*/
1333 occurence
= srchstr(message
, "mailbox");
1336 || isspace((unsigned char) *(occurence
+7))
1337 || *(occurence
+7) == ':'){
1338 was_capitalized
= isupper((unsigned char) *occurence
);
1339 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 7, (errflg
== PARSE
? "address" : "folder"));
1341 *occurence
= (errflg
== PARSE
? 'A' : 'F');
1346 occurence
= srchstr(occurence
, "mailbox");
1349 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1350 occurence
= srchstr(message
, "GSSAPI");
1353 || isspace((unsigned char) *(occurence
+6))
1354 || *(occurence
+6) == ':')
1355 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 6, "Kerberos");
1359 occurence
= srchstr(occurence
, "GSSAPI");
1363 ps_global
->mm_log_error
= 1;
1365 if(errflg
== PARSE
|| (errflg
== ERROR
&& ps_global
->noshow_error
))
1366 strncpy(ps_global
->c_client_error
, message
,
1367 sizeof(ps_global
->c_client_error
));
1369 if(ps_global
->noshow_error
1370 || (ps_global
->noshow_warn
&& errflg
== WARN
)
1371 || !(errflg
== ERROR
|| errflg
== WARN
))
1372 return; /* Only care about errors; don't print when asked not to */
1374 /*---- Display the message ------*/
1375 q_status_message((errflg
== ERROR
) ? (SM_ORDER
| SM_DING
) : SM_ORDER
,
1377 strncpy(ps_global
->last_error
, message
, sizeof(ps_global
->last_error
));
1378 ps_global
->last_error
[sizeof(ps_global
->last_error
) - 1] = '\0';
1382 mm_login_method_work(NETMBX
*mb
, char *user
, void *login
, long int trial
,
1383 char *method
, char *usethisprompt
, char *altuserforcache
)
1387 if(strucmp(method
, OA2NAME
) == 0 || strucmp(method
, BEARERNAME
) == 0)
1388 mm_login_oauth2(mb
, user
, method
, (OAUTH2_S
*) login
, trial
, usethisprompt
, altuserforcache
);
1392 mm_login_work(NETMBX
*mb
, char *user
, char **pwd
, long int trial
,
1393 char *usethisprompt
, char *altuserforcache
)
1395 char tmp
[MAILTMPLEN
];
1396 char prompt
[1000], *last
;
1397 char port
[20], non_def_port
[20], insecure
[20];
1398 char defuser
[NETMAXUSER
];
1399 char hostleadin
[80], hostname
[200], defubuf
[200];
1400 char logleadin
[80], pwleadin
[50];
1401 char hostlist0
[MAILTMPLEN
], hostlist1
[MAILTMPLEN
];
1402 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1403 that the password will be sent unencrypted over the network. This is
1404 just a warning message that gets added parenthetically when the user
1405 is asked for a password. */
1406 char *insec
= _(" (INSECURE)");
1407 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1408 after having already failed at least once. */
1409 char *retry
= _("Retrying - ");
1410 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1411 char *hostlabel
= _("HOST");
1412 /* TRANSLATORS: user is logging in as a particular user (a particular
1413 login name), this is just labelling that user name. */
1414 char *userlabel
= _("USER");
1415 STRLIST_S hostlist
[2];
1417 int len
, rc
, q_line
, flags
;
1418 int oespace
, avail
, need
, save_dont_use
;
1421 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1422 int preserve_password
= -1;
1425 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1426 trial
, mb
->user
? mb
->user
: "(null)",
1427 mb
->service
? mb
->service
: "(null)",
1428 mb
->port
? " port=" : "",
1429 mb
->port
? comatose(mb
->port
) : "",
1430 altuserforcache
? " altuserforcache =" : "",
1431 altuserforcache
? altuserforcache
: ""));
1432 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
1434 save_in_init
= ps_global
->in_init_seq
;
1435 ps_global
->in_init_seq
= 0;
1436 ps_global
->no_newmail_check_from_optionally_enter
= 1;
1438 /* make sure errors are seen */
1440 flush_status_messages(0);
1443 * Add port number to hostname if going through a tunnel or something
1445 non_def_port
[0] = '\0';
1446 if(mb
->port
&& mb
->service
&&
1447 (sv
= getservbyname(mb
->service
, "tcp")) &&
1448 (mb
->port
!= ntohs(sv
->s_port
))){
1449 snprintf(non_def_port
, sizeof(non_def_port
), ":%lu", mb
->port
);
1450 non_def_port
[sizeof(non_def_port
)-1] = '\0';
1451 dprint((9, "mm_login: using non-default port=%s\n",
1452 non_def_port
? non_def_port
: "?"));
1456 * set up host list for sybil servers...
1459 strncpy(hostlist0
, mb
->host
, sizeof(hostlist0
)-1);
1460 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1461 strncat(hostlist0
, non_def_port
, sizeof(hostlist0
)-strlen(hostlist0
)-1);
1462 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1463 hostlist
[0].name
= hostlist0
;
1464 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1465 strncpy(hostlist1
, mb
->orighost
, sizeof(hostlist1
)-1);
1466 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1467 strncat(hostlist1
, non_def_port
, sizeof(hostlist1
)-strlen(hostlist1
)-1);
1468 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1469 hostlist
[0].next
= &hostlist
[1];
1470 hostlist
[1].name
= hostlist1
;
1471 hostlist
[1].next
= NULL
;
1474 hostlist
[0].next
= NULL
;
1477 hostlist
[0].name
= mb
->host
;
1478 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1479 hostlist
[0].next
= &hostlist
[1];
1480 hostlist
[1].name
= mb
->orighost
;
1481 hostlist
[1].next
= NULL
;
1484 hostlist
[0].next
= NULL
;
1487 if(hostlist
[0].name
){
1488 dprint((9, "mm_login: host=%s\n",
1489 hostlist
[0].name
? hostlist
[0].name
: "?"));
1490 if(hostlist
[0].next
&& hostlist
[1].name
){
1491 dprint((9, "mm_login: orighost=%s\n", hostlist
[1].name
));
1496 * Initialize user name with either
1497 * 1) /user= value in the stream being logged into,
1498 * or 2) the user name we're running under.
1500 * Note that VAR_USER_ID is not yet initialized if this login is
1501 * the one to access the remote config file. In that case, the user
1502 * can supply the username in the config file name with /user=.
1504 if(trial
== 0L && !altuserforcache
){
1505 strncpy(user
, (*mb
->user
) ? mb
->user
:
1506 ps_global
->VAR_USER_ID
? ps_global
->VAR_USER_ID
: "",
1508 user
[NETMAXUSER
-1] = '\0';
1510 /* try last working password associated with this host. */
1511 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1512 (mb
->sslflag
||mb
->tlsflag
))){
1513 dprint((9, "mm_login: found a password to try\n"));
1514 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1515 ps_global
->in_init_seq
= save_in_init
;
1519 #ifdef LOCAL_PASSWD_CACHE
1520 /* check to see if there's a password left over from last session */
1521 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1522 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1523 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1524 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1525 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1526 (mb
->sslflag
||mb
->tlsflag
));
1527 dprint((9, "mm_login: found a password in passfile to try\n"));
1528 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1529 ps_global
->in_init_seq
= save_in_init
;
1532 #endif /* LOCAL_PASSWD_CACHE */
1535 * If no explicit user name supplied and we've not logged in
1536 * with our local user name, see if we've visited this
1537 * host before as someone else.
1540 ((last
= imap_get_user(mm_login_list
, hostlist
))
1541 #ifdef LOCAL_PASSWD_CACHE
1543 (last
= get_passfile_user(ps_global
->pinerc
, hostlist
))
1544 #endif /* LOCAL_PASSWD_CACHE */
1546 strncpy(user
, last
, NETMAXUSER
);
1547 user
[NETMAXUSER
-1] = '\0';
1548 dprint((9, "mm_login: found user=%s\n",
1549 user
? user
: "?"));
1551 /* try last working password associated with this host/user. */
1552 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1553 (mb
->sslflag
||mb
->tlsflag
))){
1555 "mm_login: found a password for user=%s to try\n",
1556 user
? user
: "?"));
1557 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1558 ps_global
->in_init_seq
= save_in_init
;
1562 #ifdef LOCAL_PASSWD_CACHE
1563 /* check to see if there's a password left over from last session */
1564 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1565 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1566 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1567 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1568 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1569 (mb
->sslflag
||mb
->tlsflag
));
1571 "mm_login: found a password for user=%s in passfile to try\n",
1572 user
? user
: "?"));
1573 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1574 ps_global
->in_init_seq
= save_in_init
;
1577 #endif /* LOCAL_PASSWD_CACHE */
1580 #if !defined(DOS) && !defined(OS2)
1581 if(!*mb
->user
&& !*user
&&
1582 (last
= (ps_global
->ui
.login
&& ps_global
->ui
.login
[0])
1583 ? ps_global
->ui
.login
: NULL
)
1585 strncpy(user
, last
, NETMAXUSER
);
1586 user
[NETMAXUSER
-1] = '\0';
1587 dprint((9, "mm_login: found user=%s\n",
1588 user
? user
: "?"));
1590 /* try last working password associated with this host. */
1591 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1592 (mb
->sslflag
||mb
->tlsflag
))){
1593 dprint((9, "mm_login:ui: found a password to try\n"));
1594 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1595 ps_global
->in_init_seq
= save_in_init
;
1599 #ifdef LOCAL_PASSWD_CACHE
1600 /* check to see if there's a password left over from last session */
1601 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1602 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1603 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1604 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1605 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1606 (mb
->sslflag
||mb
->tlsflag
));
1607 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1608 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1609 ps_global
->in_init_seq
= save_in_init
;
1612 #endif /* LOCAL_PASSWD_CACHE */
1617 user
[NETMAXUSER
-1] = '\0';
1623 * Even if we have a user now, user gets a chance to change it.
1625 ps_global
->mangled_footer
= 1;
1626 if(!*mb
->user
&& !altuserforcache
){
1631 * Instead of offering user with a value that the user can edit,
1632 * we offer [user] as a default so that the user can type CR to
1633 * use it. Otherwise, the user has to type in whole name.
1635 strncpy(defuser
, user
, sizeof(defuser
)-1);
1636 defuser
[sizeof(defuser
)-1] = '\0';
1640 * Need space for "Retrying - "
1646 * about 15 chars for input
1649 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1650 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1651 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1653 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1654 hostname
[sizeof(hostname
)-1] = '\0';
1657 * Add port number to hostname if going through a tunnel or something
1660 strncpy(port
, non_def_port
, sizeof(port
));
1665 /* if not encrypted and SSL/TLS is supported */
1666 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1667 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1668 strncpy(insecure
, insec
, sizeof(insecure
));
1670 /* TRANSLATORS: user is being asked to type in their login name */
1671 snprintf(logleadin
, sizeof(logleadin
), " %s", _("ENTER LOGIN NAME"));
1673 snprintf(defubuf
, sizeof(defubuf
), "%s%s%s : ", (*defuser
) ? " [" : "",
1674 (*defuser
) ? defuser
: "",
1675 (*defuser
) ? "]" : "");
1676 defubuf
[sizeof(defubuf
)-1] = '\0';
1677 /* space reserved after prompt */
1678 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1680 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1681 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1682 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) + oespace
;
1684 /* If we're retrying cut the hostname back to the first word. */
1685 if(avail
< need
&& trial
> 0){
1688 len
= strlen(hostname
);
1689 if((p
= strchr(hostname
, '.')) != NULL
){
1691 need
-= (len
- strlen(hostname
));
1696 need
-= utf8_width(retry
);
1701 /* reduce length of logleadin */
1702 len
= utf8_width(logleadin
);
1703 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1704 longer version doesn't fit on screen */
1705 snprintf(logleadin
, sizeof(logleadin
), " %s", _("LOGIN"));
1706 need
-= (len
- utf8_width(logleadin
));
1709 /* get two spaces from hostleadin */
1710 len
= utf8_width(hostleadin
);
1711 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
1712 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
1713 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1714 need
-= (len
- utf8_width(hostleadin
));
1716 /* get rid of port */
1717 if(avail
< need
&& strlen(port
) > 0){
1718 need
-= strlen(port
);
1726 * Reduce space for hostname. Best we can do is 6 chars
1729 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
1730 len
= strlen(hostname
);
1731 strncpy(hostname
+reduce_to
-3, "...", 4);
1732 need
-= (len
- strlen(hostname
));
1734 if(avail
< need
&& strlen(insecure
) > 0){
1735 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
1737 insecure
[strlen(insecure
)-4] = ')';
1738 insecure
[strlen(insecure
)-3] = '\0';
1741 need
-= utf8_width(insecure
);
1747 if(strlen(defubuf
) > 3){
1748 len
= strlen(defubuf
);
1749 strncpy(defubuf
, " [..] :", 9);
1750 need
-= (len
- strlen(defubuf
));
1754 strncpy(defubuf
, ":", 2);
1757 * If it still doesn't fit, optionally_enter gets
1758 * to worry about it.
1766 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s",
1767 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
);
1768 prompt
[sizeof(prompt
)-1] = '\0';
1772 mm_login_alt_cue(mb
);
1774 flags
= OE_APPEND_CURRENT
;
1775 save_dont_use
= ps_global
->dont_use_init_cmds
;
1776 ps_global
->dont_use_init_cmds
= 1;
1778 if(!*user
&& *defuser
){
1779 strncpy(user
, defuser
, NETMAXUSER
);
1780 user
[NETMAXUSER
-1] = '\0';
1783 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, pwd
, NETMAXPASSWD
,
1784 #ifdef LOCAL_PASSWD_CACHE
1785 is_using_passfile() ? 1 :
1786 #endif /* LOCAL_PASSWD_CACHE */
1787 0, 0, &preserve_password
);
1788 ps_global
->dont_use_init_cmds
= save_dont_use
;
1789 if(rc
== 0 && *user
&& *pwd
)
1791 #else /* !_WINDOWS */
1792 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
1793 prompt
, NULL
, help
, &flags
);
1794 #endif /* !_WINDOWS */
1795 ps_global
->dont_use_init_cmds
= save_dont_use
;
1798 help
= help
== NO_HELP
? h_oe_login
: NO_HELP
;
1803 if(rc
== 0 && !*user
){
1804 strncpy(user
, defuser
, NETMAXUSER
);
1805 user
[NETMAXUSER
-1] = '\0';
1812 if(rc
== 1 || !user
[0]) {
1813 ps_global
->user_says_cancel
= (rc
== 1);
1818 strncpy(user
, mb
->user
, NETMAXUSER
);
1819 user
[NETMAXUSER
-1] = '\0';
1822 user
[NETMAXUSER
-1] = '\0';
1824 if(!(user
[0] || altuserforcache
)){
1825 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1826 ps_global
->in_init_seq
= save_in_init
;
1831 * Now that we have a user, we can check in the cache again to see
1832 * if there is a password there. Try last working password associated
1833 * with this host and user.
1835 if(trial
== 0L && !*mb
->user
&& !altuserforcache
){
1836 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1837 (mb
->sslflag
||mb
->tlsflag
))){
1838 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1839 ps_global
->in_init_seq
= save_in_init
;
1843 #ifdef LOCAL_PASSWD_CACHE
1844 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1845 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1846 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1847 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1848 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1849 ps_global
->in_init_seq
= save_in_init
;
1852 #endif /* LOCAL_PASSWD_CACHE */
1854 else if(trial
== 0 && altuserforcache
){
1855 if(imap_get_passwd(mm_login_list
, pwd
, altuserforcache
, hostlist
,
1856 (mb
->sslflag
||mb
->tlsflag
))){
1857 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1858 ps_global
->in_init_seq
= save_in_init
;
1862 #ifdef LOCAL_PASSWD_CACHE
1863 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1864 altuserforcache
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1865 imap_set_passwd(&mm_login_list
, *pwd
, altuserforcache
,
1866 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1867 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1868 ps_global
->in_init_seq
= save_in_init
;
1871 #endif /* LOCAL_PASSWD_CACHE */
1875 * Didn't find password in cache or this isn't the first try. Ask user.
1880 * Need space for "Retrying - "
1886 * " ENTER PASSWORD: "
1887 * about 15 chars for input
1890 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1891 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1893 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1894 hostname
[sizeof(hostname
)-1] = '\0';
1897 * Add port number to hostname if going through a tunnel or something
1900 strncpy(port
, non_def_port
, sizeof(port
));
1906 /* if not encrypted and SSL/TLS is supported */
1907 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1908 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1909 strncpy(insecure
, insec
, sizeof(insecure
));
1912 strncpy(logleadin
, usethisprompt
, sizeof(logleadin
));
1913 logleadin
[sizeof(logleadin
)-1] = '\0';
1918 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
1920 strncpy(defubuf
, user
, sizeof(defubuf
)-1);
1921 defubuf
[sizeof(defubuf
)-1] = '\0';
1924 /* TRANSLATORS: user is being asked to type in their password */
1925 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("ENTER PASSWORD"));
1927 /* space reserved after prompt */
1928 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1930 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1931 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1932 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) +
1933 utf8_width(pwleadin
) + oespace
;
1935 if(avail
< need
&& trial
> 0){
1938 len
= strlen(hostname
);
1939 if((p
= strchr(hostname
, '.')) != NULL
){
1941 need
-= (len
- strlen(hostname
));
1946 need
-= utf8_width(retry
);
1952 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
1956 rplstr(pwleadin
, sizeof(pwleadin
), 1, "");
1960 /* get two spaces from hostleadin */
1961 len
= utf8_width(hostleadin
);
1962 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
1963 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
1964 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1965 need
-= (len
- utf8_width(hostleadin
));
1967 /* get rid of port */
1968 if(avail
< need
&& strlen(port
) > 0){
1969 need
-= strlen(port
);
1974 len
= utf8_width(pwleadin
);
1975 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
1976 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("PASSWORD"));
1977 need
-= (len
- utf8_width(pwleadin
));
1985 * Reduce space for hostname. Best we can do is 6 chars
1988 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
1989 len
= strlen(hostname
);
1990 strncpy(hostname
+reduce_to
-3, "...", 4);
1991 need
-= (len
- strlen(hostname
));
1993 if(avail
< need
&& strlen(insecure
) > 0){
1994 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
1996 insecure
[strlen(insecure
)-4] = ')';
1997 insecure
[strlen(insecure
)-3] = '\0';
2000 need
-= utf8_width(insecure
);
2006 len
= utf8_width(logleadin
);
2007 strncpy(logleadin
, " ", sizeof(logleadin
));
2008 logleadin
[sizeof(logleadin
)-1] = '\0';
2009 need
-= (len
- utf8_width(logleadin
));
2012 reduce_to
= (need
- avail
< strlen(defubuf
) - 6) ? (strlen(defubuf
)-(need
-avail
)) : 0;
2014 strncpy(defubuf
+reduce_to
-3, "...", 4);
2023 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s%s",
2024 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
, pwleadin
);
2025 prompt
[sizeof(prompt
)-1] = '\0';
2030 mm_login_alt_cue(mb
);
2032 save_dont_use
= ps_global
->dont_use_init_cmds
;
2033 ps_global
->dont_use_init_cmds
= 1;
2034 flags
= F_ON(F_QUELL_ASTERISKS
, ps_global
) ? OE_PASSWD_NOAST
: OE_PASSWD
;
2035 flags
|= OE_KEEP_TRAILING_SPACE
;
2039 tmpp
= fs_get(NETMAXPASSWD
*sizeof(char));
2040 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, &tmpp
, NETMAXPASSWD
, 0, 1,
2041 &preserve_password
);
2042 strncpy(tmp
, tmpp
, sizeof(tmp
));
2043 tmp
[sizeof(tmp
)-1] = '\0';
2044 if(tmpp
) fs_give((void **)&tmpp
);
2046 #else /* !_WINDOWS */
2047 rc
= optionally_enter(tmp
, q_line
, 0, NETMAXPASSWD
,
2048 prompt
, NULL
, help
, &flags
);
2049 #endif /* !_WINDOWS */
2050 if(rc
!= 1) *pwd
= cpystr(tmp
);
2051 ps_global
->dont_use_init_cmds
= save_dont_use
;
2054 help
= help
== NO_HELP
? h_oe_passwd
: NO_HELP
;
2062 if(rc
== 1 || !tmp
[0]) {
2063 ps_global
->user_says_cancel
= (rc
== 1);
2065 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2066 ps_global
->in_init_seq
= save_in_init
;
2073 /* remember the password for next time */
2074 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
2075 imap_set_passwd(&mm_login_list
, *pwd
,
2076 altuserforcache
? altuserforcache
: user
, hostlist
,
2077 (mb
->sslflag
||mb
->tlsflag
), 0, 0);
2078 #ifdef LOCAL_PASSWD_CACHE
2079 /* if requested, remember it on disk for next session */
2080 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
2081 set_passfile_passwd(ps_global
->pinerc
, *pwd
,
2082 altuserforcache
? altuserforcache
: user
, hostlist
,
2083 (mb
->sslflag
||mb
->tlsflag
),
2084 (preserve_password
== -1 ? 0
2085 : (preserve_password
== 0 ? 2 :1)));
2086 #endif /* LOCAL_PASSWD_CACHE */
2088 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2093 mm_login_alt_cue(NETMBX
*mb
)
2095 if(ps_global
->ttyo
){
2098 lastc
= pico_set_colors(ps_global
->VAR_TITLE_FORE_COLOR
,
2099 ps_global
->VAR_TITLE_BACK_COLOR
,
2102 mark_titlebar_dirty();
2103 PutLine0(0, ps_global
->ttyo
->screen_cols
- 1,
2104 (mb
->sslflag
||mb
->tlsflag
) ? "+" : " ");
2107 (void)pico_set_colorp(lastc
, PSC_NONE
);
2108 free_color_pair(&lastc
);
2116 /*----------------------------------------------------------------------
2117 Receive notification of an error writing to disk
2119 Args: stream -- The stream the error occurred on
2120 errcode -- The system error code (errno)
2121 serious -- Flag indicating error is serious (mail may be lost)
2123 Result: If error is non serious, the stream is marked as having an error
2124 and deletes are disallowed until error clears
2125 If error is serious this goes modal, allowing the user to retry
2126 or get a shell escape to fix the condition. When the condition is
2127 serious it means that mail existing in the mailbox will be lost
2128 if Pine exits without writing, so we try to induce the user to
2129 fix the error, go get someone that can fix the error, or whatever
2130 and don't provide an easy way out.
2133 mm_diskerror (MAILSTREAM
*stream
, long int errcode
, long int serious
)
2137 static ESCKEY_S de_opts
[] = {
2138 {'r', 'r', "R", "Retry"},
2139 {'f', 'f', "F", "FileBrowser"},
2140 {'s', 's', "S", "ShellPrompt"},
2143 #define DE_COLS (ps_global->ttyo->screen_cols)
2144 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2146 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2148 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2149 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2151 "The reported error number is %s. The last reported mail error was:"
2152 static char *de_msg
[] = {
2153 "Please try to correct the error preventing Alpine from saving your",
2154 "mail folder. For example if the disk is out of space try removing",
2155 "unneeded files. You might also contact your system administrator.",
2157 "Both Alpine's File Browser and an option to enter the system's",
2158 "command prompt are offered to aid in fixing the problem. When",
2159 "you believe the problem is resolved, choose the \"Retry\" option.",
2160 "Be aware that messages may be lost or this folder left in an",
2161 "inaccessible condition if you exit or kill Alpine before the problem",
2164 static char *de_shell_msg
[] = {
2165 "\n\nPlease attempt to correct the error preventing saving of the",
2166 "mail folder. If you do not know how to correct the problem, contact",
2167 "your system administrator. To return to Alpine, type \"exit\".",
2171 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2172 DE_FOLDER(stream
), errcode
, serious
? "" : "not "));
2173 dprint((0, "***** message: \"%s\"\n\n",
2174 ps_global
->last_error
? ps_global
->last_error
: "?"));
2177 sp_set_io_error_on_stream(stream
, 1);
2182 /* replace pine's body display with screen full of explanatory text */
2184 PutLine1(2, MAX((DE_COLS
- sizeof(DE_STR1
)
2185 - strlen(DE_FOLDER(stream
)))/2, 0),
2186 DE_STR1
, DE_FOLDER(stream
));
2188 PutLine1(3, 4, DE_STR2
, long2string(errcode
));
2190 PutLine0(4, 0, " \"");
2191 removing_leading_white_space(ps_global
->last_error
);
2192 for(i
= 4, p
= ps_global
->last_error
; *p
&& i
< DE_LINE
; ){
2193 for(s
= NULL
, q
= p
; *q
&& q
- p
< DE_COLS
- 16; q
++)
2194 if(isspace((unsigned char)*q
))
2205 PutLine0(i
, 0, " ");
2206 while(*p
&& isspace((unsigned char)*p
))
2217 for(j
= ++i
; i
< DE_LINE
&& de_msg
[i
-j
]; i
++){
2219 PutLine0(i
, 0, " ");
2220 Write_to_screen(de_msg
[i
-j
]);
2226 switch(radio_buttons(DE_PMT
, -FOOTER_ROWS(ps_global
), de_opts
,
2227 'r', 0, NO_HELP
, RB_FLUSH_IN
| RB_NO_NEWMAIL
)){
2228 case 'r' : /* Retry! */
2229 ps_global
->mangled_screen
= 1;
2232 case 'f' : /* File Browser */
2234 char full_filename
[MAXPATH
+1], filename
[MAXPATH
+1];
2237 build_path(full_filename
, ps_global
->home_dir
, filename
,
2238 sizeof(full_filename
));
2239 file_lister("DISK ERROR", full_filename
, sizeof(full_filename
),
2240 filename
, sizeof(filename
), FALSE
, FB_SAVE
);
2247 end_keyboard(ps_global
? F_ON(F_USE_FK
,ps_global
) : 0);
2248 end_tty_driver(ps_global
);
2249 for(i
= 0; de_shell_msg
[i
]; i
++)
2250 puts(de_shell_msg
[i
]);
2253 * Don't use our piping mechanism to spawn a subshell here
2254 * since it will the server (thus reentering c-client).
2261 init_tty_driver(ps_global
);
2262 init_keyboard(F_ON(F_USE_FK
,ps_global
));
2266 if(ps_global
->redrawer
)
2267 (*ps_global
->redrawer
)();
2273 pine_tcptimeout_noscreen(long int elapsed
, long int sincelast
, char *host
)
2282 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2283 snprintf(pmt
, sizeof(pmt
),
2284 _("No reply in %s seconds from server %s. Break connection"),
2285 long2string(elapsed
), host
);
2286 pmt
[sizeof(pmt
)-1] = '\0';
2287 if(want_to(pmt
, 'n', 'n', NO_HELP
, WT_FLUSH_IN
) == 'y'){
2288 ps_global
->user_says_cancel
= 1;
2293 ps_global
->tcptimeout
= 0;
2299 * -------------------------------------------------------------
2300 * These are declared in pith/imap.h as mandatory to implement.
2301 * -------------------------------------------------------------
2306 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2309 pine_tcptimeout(long int elapsed
, long int sincelast
, char *host
)
2311 long rv
= 1L; /* keep trying by default */
2314 ps_global
->tcptimeout
= 1;
2316 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2317 long2string(elapsed
), host
));
2326 if(ps_global
->noshow_timeout
)
2329 if(ps_global
->can_interrupt
2330 && ps_global
->close_connection_timeout
> 0L
2331 && elapsed
>= (long)ps_global
->tcp_query_timeout
2332 && elapsed
>= (long)ps_global
->close_connection_timeout
){
2333 ps_global
->can_interrupt
= 0; /* do not return here */
2334 ps_global
->read_bail
= 0;
2335 ps_global
->user_says_cancel
= 1;
2339 if(!ps_global
->ttyo
)
2340 return(pine_tcptimeout_noscreen(elapsed
, sincelast
, host
));
2345 * Prompt after a minute (since by then things are probably really bad)
2346 * A prompt timeout means "keep trying"...
2348 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2351 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2352 if((clear_inverse
= !InverseState()) != 0)
2357 PutLine2(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
), 0,
2358 _("No reply in %s seconds from server %s. Break connection?"),
2359 long2string(elapsed
), host
);
2364 if(ps_global
->read_bail
|| ch
== 'y' || ch
== 'Y'){
2365 ps_global
->read_bail
= 0;
2366 ps_global
->user_says_cancel
= 1;
2373 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2376 if(rv
== 1L){ /* just warn 'em something's up */
2377 q_status_message2(SM_ORDER
, 0, 0,
2378 _("No reply in %s seconds from server %s. Still Waiting..."),
2379 long2string(elapsed
), host
);
2380 flush_status_messages(0); /* make sure it's seen */
2383 mark_status_dirty(); /* make sure it gets cleared */
2385 resume_busy_cue((rv
== 1) ? 3 : 0);
2386 ps_global
->tcptimeout
= 0;
2391 QUOTALIST
*pine_quotalist_copy (QUOTALIST
*pquota
)
2393 QUOTALIST
*cquota
= NULL
;
2396 cquota
= mail_newquotalist();
2397 if (pquota
->name
&& *pquota
->name
)
2398 cquota
->name
= cpystr(pquota
->name
);
2399 cquota
->usage
= pquota
->usage
;
2400 cquota
->limit
= pquota
->limit
;
2402 cquota
->next
= pine_quotalist_copy(pquota
->next
);
2408 /* c-client callback to handle quota */
2411 pine_parse_quota (MAILSTREAM
*stream
, unsigned char *msg
, QUOTALIST
*pquota
)
2413 ps_global
->quota
= pine_quotalist_copy (pquota
);
2417 * C-client callback to handle SSL/TLS certificate validation failures
2419 * Returning 0 means error becomes fatal
2420 * Non-zero means certificate problem is ignored and SSL session is
2423 * We remember the answer and won't re-ask for subsequent open attempts to
2424 * the same hostname.
2427 pine_sslcertquery(char *reason
, char *host
, char *cert
)
2430 char *unknown
= "<unknown>";
2433 int ok_novalidate
= 0, warned
= 0;
2435 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2436 host
? host
: "?", reason
? reason
: "?",
2437 cert
? cert
: "?"));
2439 hostlist
.name
= host
? host
: "";
2440 hostlist
.next
= NULL
;
2443 * See if we've been asked about this host before.
2445 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2446 /* we were asked before, did we say Yes? */
2452 "sslcertificatequery: approved automatically\n"));
2456 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2459 if(ps_global
->ttyo
){
2461 STORE_S
*in_store
, *out_store
;
2463 HANDLE_S
*handles
= NULL
;
2464 int the_answer
= 'n';
2466 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
2467 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2470 so_puts(in_store
, "<HTML><P>");
2471 so_puts(in_store
, _("There was a failure validating the SSL/TLS certificate for the server"));
2473 so_puts(in_store
, "<P><CENTER>");
2474 so_puts(in_store
, host
? host
: unknown
);
2475 so_puts(in_store
, "</CENTER>");
2477 so_puts(in_store
, "<P>");
2478 so_puts(in_store
, _("The reason for the failure was"));
2480 /* squirrel away details */
2482 fs_give((void **)&details_host
);
2484 fs_give((void **)&details_reason
);
2486 fs_give((void **)&details_cert
);
2488 details_host
= cpystr(host
? host
: unknown
);
2489 details_reason
= cpystr(reason
? reason
: unknown
);
2490 details_cert
= cpystr(cert
? cert
: unknown
);
2492 so_puts(in_store
, "<P><CENTER>");
2493 snprintf(tmp
, sizeof(tmp
), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2494 reason
? reason
: unknown
);
2495 tmp
[sizeof(tmp
)-1] = '\0';
2497 so_puts(in_store
, tmp
);
2498 so_puts(in_store
, "</CENTER>");
2500 so_puts(in_store
, "<P>");
2501 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."));
2503 so_puts(in_store
, "<P>");
2504 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"));
2506 so_puts(in_store
, "<P><CENTER>");
2507 so_puts(in_store
, "/novalidate-cert");
2508 so_puts(in_store
, "</CENTER>");
2510 so_puts(in_store
, "<P>");
2511 so_puts(in_store
, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2513 so_puts(in_store
, "<P><CENTER>");
2514 so_puts(in_store
, host
? host
: unknown
);
2515 so_puts(in_store
, "</CENTER>");
2517 so_puts(in_store
, "<P>");
2518 so_puts(in_store
, _("in your configuration, replace those characters with"));
2520 so_puts(in_store
, "<P><CENTER>");
2521 so_puts(in_store
, host
? host
: unknown
);
2522 so_puts(in_store
, "/novalidate-cert");
2523 so_puts(in_store
, "</CENTER>");
2525 so_puts(in_store
, "<P>");
2526 so_puts(in_store
, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2528 so_seek(in_store
, 0L, 0);
2529 init_handles(&handles
);
2531 gf_link_filter(gf_html2plain
,
2532 gf_html2plain_opt(NULL
,
2533 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
2534 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
2535 gf_set_so_readc(&gc
, in_store
);
2536 gf_set_so_writec(&pc
, out_store
);
2538 gf_clear_so_writec(out_store
);
2539 gf_clear_so_readc(in_store
);
2541 memset(&sargs
, 0, sizeof(SCROLL_S
));
2542 sargs
.text
.handles
= handles
;
2543 sargs
.text
.text
= so_text(out_store
);
2544 sargs
.text
.src
= CharStar
;
2545 sargs
.text
.desc
= _("help text");
2546 sargs
.bar
.title
= _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2547 sargs
.proc
.tool
= answer_cert_failure
;
2548 sargs
.proc
.data
.p
= (void *)&the_answer
;
2549 sargs
.keys
.menu
= &ans_certquery_keymenu
;
2550 /* don't want to re-enter c-client */
2551 sargs
.quell_newmail
= 1;
2552 setbitmap(sargs
.keys
.bitmap
);
2553 sargs
.help
.text
= h_tls_validation_failure
;
2554 sargs
.help
.title
= _("HELP FOR CERT VALIDATION FAILURE");
2558 if(the_answer
== 'y')
2560 else if(the_answer
== 'n')
2561 ps_global
->user_says_cancel
= 1;
2563 ps_global
->mangled_screen
= 1;
2564 ps_global
->painted_body_on_startup
= 0;
2565 ps_global
->painted_footer_on_startup
= 0;
2567 so_give(&out_store
);
2568 free_handles(&handles
);
2570 fs_give((void **)&details_host
);
2572 fs_give((void **)&details_reason
);
2574 fs_give((void **)&details_cert
);
2578 * If screen hasn't been initialized yet, use want_to.
2581 memset((void *)tmp
, 0, sizeof(tmp
));
2583 reason
? reason
: _("SSL/TLS certificate validation failure"),
2585 tmp
[sizeof(tmp
)-1] = '\0';
2586 strncat(tmp
, _(": Continue anyway "), sizeof(tmp
)-strlen(tmp
)-1);
2588 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y')
2593 q_status_message1(SM_ORDER
, 1, 3, _("Open of %s cancelled"),
2594 host
? host
: unknown
);
2596 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, rv
? 1 : 0, 0);
2598 dprint((5, "sslcertificatequery: %s\n",
2599 rv
? "approved" : "rejected"));
2606 pine_newsrcquery(MAILSTREAM
*stream
, char *mulname
, char *name
)
2608 char buf
[MAILTMPLEN
];
2610 if((can_access(mulname
, ACCESS_EXISTS
) == 0)
2611 || !(can_access(name
, ACCESS_EXISTS
) == 0))
2614 snprintf(buf
, sizeof(buf
),
2615 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2617 strlen(last_cmpnt(name
)) > 15 ? "..." : "");
2618 buf
[sizeof(buf
)-1] = '\0';
2619 if(want_to(buf
, 'n', 'n', NO_HELP
, WT_NORM
) == 'y')
2620 rename_file(name
, mulname
);
2626 url_local_certdetails(char *url
)
2628 if(!struncmp(url
, "x-alpine-cert:", 14)){
2633 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
2634 q_status_message(SM_ORDER
| SM_DING
, 7, 10,
2635 _("Error allocating space for details."));
2639 so_puts(store
, _("Host given by user:\n\n "));
2640 so_puts(store
, details_host
);
2641 so_puts(store
, _("\n\nReason for failure:\n\n "));
2642 so_puts(store
, details_reason
);
2643 so_puts(store
, _("\n\nCertificate being verified:\n\n"));
2644 folded
= fold(details_cert
, ps_global
->ttyo
->screen_cols
, ps_global
->ttyo
->screen_cols
, " ", " ", FLD_NONE
);
2645 so_puts(store
, folded
);
2646 fs_give((void **)&folded
);
2647 so_puts(store
, "\n");
2649 memset(&sargs
, 0, sizeof(SCROLL_S
));
2650 sargs
.text
.text
= so_text(store
);
2651 sargs
.text
.src
= CharStar
;
2652 sargs
.text
.desc
= _("Details");
2653 sargs
.bar
.title
= _("CERT VALIDATION DETAILS");
2654 sargs
.help
.text
= NO_HELP
;
2655 sargs
.help
.title
= NULL
;
2656 sargs
.quell_newmail
= 1;
2657 sargs
.help
.text
= h_tls_failure_details
;
2658 sargs
.help
.title
= _("HELP FOR CERT VALIDATION DETAILS");
2662 so_give(&store
); /* free resources associated with store */
2663 ps_global
->mangled_screen
= 1;
2672 * C-client callback to handle SSL/TLS certificate validation failures
2675 pine_sslfailure(char *host
, char *reason
, long unsigned int flags
)
2679 int the_answer
= 'n', indent
, len
, cols
;
2680 char buf
[500], buf2
[500];
2682 char *hst
= host
? host
: "<unknown>";
2683 char *rsn
= reason
? reason
: "<unknown>";
2684 char *notls
= "/notls";
2686 int ok_novalidate
= 0, warned
= 0;
2689 dprint((1, "sslfailure: host=%s reason=%s\n",
2693 if(flags
& NET_SILENT
)
2696 hostlist
.name
= host
? host
: "";
2697 hostlist
.next
= NULL
;
2700 * See if we've been told about this host before.
2702 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2703 /* we were told already */
2705 snprintf(buf
, sizeof(buf
), _("SSL/TLS failure for %s: %s"), hst
, rsn
);
2706 buf
[sizeof(buf
)-1] = '\0';
2712 cols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
2715 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2718 strncpy(buf
, _("There was an SSL/TLS failure for the server"), sizeof(buf
));
2719 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2720 so_puts(store
, folded
);
2721 fs_give((void **)&folded
);
2722 so_puts(store
, "\n");
2724 if((len
=strlen(hst
)) <= cols
){
2725 if((indent
=((cols
-len
)/2)) > 0)
2726 so_puts(store
, repeat_char(indent
, SPACE
));
2728 so_puts(store
, hst
);
2729 so_puts(store
, "\n");
2732 strncpy(buf
, hst
, sizeof(buf
));
2733 buf
[sizeof(buf
)-1] = '\0';
2734 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2735 so_puts(store
, folded
);
2736 fs_give((void **)&folded
);
2739 so_puts(store
, "\n");
2741 strncpy(buf
, _("The reason for the failure was"), sizeof(buf
));
2742 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2743 so_puts(store
, folded
);
2744 fs_give((void **)&folded
);
2745 so_puts(store
, "\n");
2747 if((len
=strlen(rsn
)) <= cols
){
2748 if((indent
=((cols
-len
)/2)) > 0)
2749 so_puts(store
, repeat_char(indent
, SPACE
));
2751 so_puts(store
, rsn
);
2752 so_puts(store
, "\n");
2755 strncpy(buf
, rsn
, sizeof(buf
));
2756 buf
[sizeof(buf
)-1] = '\0';
2757 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2758 so_puts(store
, folded
);
2759 fs_give((void **)&folded
);
2762 so_puts(store
, "\n");
2764 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
));
2765 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2766 so_puts(store
, folded
);
2767 fs_give((void **)&folded
);
2768 so_puts(store
, "\n");
2770 if((len
=strlen(notls
)) <= cols
){
2771 if((indent
=((cols
-len
)/2)) > 0)
2772 so_puts(store
, repeat_char(indent
, SPACE
));
2774 so_puts(store
, notls
);
2775 so_puts(store
, "\n");
2778 strncpy(buf
, notls
, sizeof(buf
));
2779 buf
[sizeof(buf
)-1] = '\0';
2780 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2781 so_puts(store
, folded
);
2782 fs_give((void **)&folded
);
2785 so_puts(store
, "\n");
2787 strncpy(buf
, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2789 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2790 so_puts(store
, folded
);
2791 fs_give((void **)&folded
);
2792 so_puts(store
, "\n");
2794 if((len
=strlen(hst
)) <= cols
){
2795 if((indent
=((cols
-len
)/2)) > 0)
2796 so_puts(store
, repeat_char(indent
, SPACE
));
2798 so_puts(store
, hst
);
2799 so_puts(store
, "\n");
2802 strncpy(buf
, hst
, sizeof(buf
));
2803 buf
[sizeof(buf
)-1] = '\0';
2804 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2805 so_puts(store
, folded
);
2806 fs_give((void **)&folded
);
2809 so_puts(store
, "\n");
2811 strncpy(buf
, _("in your configuration, replace those characters with"),
2813 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2814 so_puts(store
, folded
);
2815 fs_give((void **)&folded
);
2816 so_puts(store
, "\n");
2818 snprintf(buf2
, sizeof(buf2
), "%s%s", hst
, notls
);
2819 buf2
[sizeof(buf2
)-1] = '\0';
2820 if((len
=strlen(buf2
)) <= cols
){
2821 if((indent
=((cols
-len
)/2)) > 0)
2822 so_puts(store
, repeat_char(indent
, SPACE
));
2824 so_puts(store
, buf2
);
2825 so_puts(store
, "\n");
2828 strncpy(buf
, buf2
, sizeof(buf
));
2829 buf
[sizeof(buf
)-1] = '\0';
2830 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2831 so_puts(store
, folded
);
2832 fs_give((void **)&folded
);
2835 so_puts(store
, "\n");
2837 if(ps_global
->ttyo
){
2838 strncpy(buf
, _("Type RETURN to continue."), sizeof(buf
));
2839 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2840 so_puts(store
, folded
);
2841 fs_give((void **)&folded
);
2844 memset(&sargs
, 0, sizeof(SCROLL_S
));
2845 sargs
.text
.text
= so_text(store
);
2846 sargs
.text
.src
= CharStar
;
2847 sargs
.text
.desc
= _("help text");
2848 sargs
.bar
.title
= _("SSL/TLS FAILURE");
2849 sargs
.proc
.tool
= answer_cert_failure
;
2850 sargs
.proc
.data
.p
= (void *)&the_answer
;
2851 sargs
.keys
.menu
= &ans_certfail_keymenu
;
2852 setbitmap(sargs
.keys
.bitmap
);
2853 /* don't want to re-enter c-client */
2854 sargs
.quell_newmail
= 1;
2855 sargs
.help
.text
= h_tls_failure
;
2856 sargs
.help
.title
= _("HELP FOR TLS/SSL FAILURE");
2867 * The screen isn't initialized yet, which should mean that this
2868 * is the result of a -p argument. Display_args_err knows how to deal
2869 * with the uninitialized screen, so we mess with the data to get it
2870 * in shape for display_args_err. This is pretty hacky.
2873 so_seek(store
, 0L, 0); /* rewind */
2874 /* count the lines */
2875 while(so_readc(&c
, store
))
2879 qp
= q
= (char **)fs_get((cnt
+1) * sizeof(char *));
2880 memset(q
, 0, (cnt
+1) * sizeof(char *));
2882 so_seek(store
, 0L, 0); /* rewind */
2884 while(so_readc(&c
, store
)){
2887 *qp
++ = cpystr(buf
);
2894 display_args_err(NULL
, q
, 0);
2895 free_list_array(&q
);
2898 ps_global
->mangled_screen
= 1;
2899 ps_global
->painted_body_on_startup
= 0;
2900 ps_global
->painted_footer_on_startup
= 0;
2903 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, ok_novalidate
, 1);
2908 answer_cert_failure(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2912 ps_global
->next_screen
= SCREEN_FUN_NULL
;
2916 *(int *)(sparms
->proc
.data
.p
) = 'y';
2920 *(int *)(sparms
->proc
.data
.p
) = 'n';
2924 alpine_panic("Unexpected command in answer_cert_failure");
2933 oauth2_auth_answer(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2938 /* TRANSLATORS: user needs to input an access code from the server */
2939 char *accesscodelabel
= _("Copy and Paste Access Code");
2940 char token
[MAILTMPLEN
], prompt
[MAILTMPLEN
];
2942 ps_global
->next_screen
= SCREEN_FUN_NULL
;
2947 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
2948 flags
= OE_APPEND_CURRENT
;
2949 sprintf(prompt
, "%s: ", accesscodelabel
);
2951 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
2952 prompt
, NULL
, NO_HELP
, &flags
);
2953 } while (rc
!= 0 && rc
!= 1);
2954 user
.code
= rc
== 0 ? cpystr(token
) : NULL
;
2956 rv
= rc
== 1 ? 0 : 1;
2965 alpine_panic("Unexpected command in oauth2_auth_answer");
2968 *(AUTH_CODE_S
*) sparms
->proc
.data
.p
= user
;
2973 /*----------------------------------------------------------------------
2974 This can be used to prevent the flickering of the check_cue char
2975 caused by numerous (5000+) fetches by c-client. Right now, the only
2976 practical use found is newsgroup subsciption.
2978 check_cue_display will check if this global is set, and won't clear
2979 the check_cue_char if set.
2982 set_read_predicted(int i
)
2984 ps_global
->read_predicted
= i
==1;
2986 if(!i
&& F_ON(F_SHOW_DELAY_CUE
, ps_global
))
2987 check_cue_display(" ");
2992 /*----------------------------------------------------------------------
2993 Exported method to retrieve logged in user name associated with stream
2995 Args: host -- host to find associated login name with.
3000 pine_block_notify(int reason
, void *data
)
3003 case BLOCK_SENSITIVE
: /* sensitive code, disallow alarms */
3006 case BLOCK_NONSENSITIVE
: /* non-sensitive code, allow alarms */
3009 case BLOCK_TCPWRITE
: /* blocked on TCP write */
3010 case BLOCK_FILELOCK
: /* blocked on file locking */
3012 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3013 check_cue_display(">");
3015 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3019 case BLOCK_DNSLOOKUP
: /* blocked on DNS lookup */
3020 case BLOCK_TCPOPEN
: /* blocked on TCP open */
3021 case BLOCK_TCPREAD
: /* blocked on TCP read */
3022 case BLOCK_TCPCLOSE
: /* blocked on TCP close */
3024 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3025 check_cue_display("<");
3027 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3032 case BLOCK_NONE
: /* not blocked */
3034 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3035 check_cue_display(" ");
3046 mm_expunged_current(long unsigned int rawno
)
3048 /* expunged something we're viewing? */
3049 if(!ps_global
->expunge_in_progress
3050 && (mn_is_cur(ps_global
->msgmap
, mn_raw2m(ps_global
->msgmap
, (long) rawno
))
3051 && (ps_global
->prev_screen
== mail_view_screen
3052 || ps_global
->prev_screen
== attachment_screen
))){
3053 ps_global
->next_screen
= mail_index_screen
;
3054 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3055 "Message you were viewing is gone!");
3063 * Specific functions to support caching username/passwd/host
3064 * triples on disk for use from one session to the next...
3067 #define FIRSTCH 0x20
3069 #define TABSZ (LASTCH - FIRSTCH + 1)
3071 static int xlate_key
;
3075 * xlate_in() - xlate_in the given character
3083 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3084 eti
+= (c
- FIRSTCH
);
3085 eti
-= (eti
>= 2*TABSZ
) ? 2*TABSZ
: (eti
>= TABSZ
) ? TABSZ
: 0;
3086 return((xlate_key
= eti
) + FIRSTCH
);
3094 * xlate_out() - xlate_out the given character
3102 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3103 xch
= c
- (dti
= xlate_key
);
3104 xch
+= (xch
< FIRSTCH
-TABSZ
) ? 2*TABSZ
: (xch
< FIRSTCH
) ? TABSZ
: 0;
3105 dti
= (xch
- FIRSTCH
) + dti
;
3106 dti
-= (dti
>= 2*TABSZ
) ? 2*TABSZ
: (dti
>= TABSZ
) ? TABSZ
: 0;
3113 #endif /* PASSFILE */
3116 #ifdef LOCAL_PASSWD_CACHE
3120 line_get(char *tmp
, size_t len
, char **textp
)
3126 || (s
= strchr(*textp
, '\n')) == NULL
3127 || (s
- *textp
) > len
- 1)
3134 snprintf(tmp
, len
, "%s\n", *textp
);
3142 * Passfile lines are
3144 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3146 * In pine4.40 and before there was no orig_hostname, and there still isn't
3147 * if it is the same as hostname.
3150 * Use Windows credentials. The TargetName of the credential is
3151 * UWash_Alpine_<hostname:port>\tuser\taltflag
3152 * and the blob consists of
3153 * passwd\torighost (if different from host)
3155 * We don't use anything fancy we just copy out all the credentials which
3156 * begin with TNAME and put them into our cache, so we don't lookup based
3157 * on the TargetName or anything like that. That was so we could re-use
3158 * the existing code and so that orighost data could be easily used.
3161 read_passfile(pinerc
, l
)
3167 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3170 char *tmp
, *blob
, *target
= NULL
;
3171 char *host
, *user
, *sflags
, *passwd
, *orighost
;
3175 if(using_passfile
== 0)
3176 return(using_passfile
);
3179 if(init_wincred_funcs() != 1){
3181 return(using_passfile
);
3185 dprint((9, "read_passfile\n"));
3189 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
3191 for(k
= 0; k
< count
; k
++){
3193 host
= user
= sflags
= passwd
= orighost
= NULL
;
3194 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3196 target
= lptstr_to_utf8(pcred
[k
]->TargetName
);
3197 tmp
= srchstr(target
, TNAME
);
3200 tmp
+= strlen(TNAME
);
3201 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
3202 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3203 ; /* find end of data */
3206 tmp
[i
++] = '\0'; /* tie off data */
3214 blob
= (char *) pcred
[k
]->CredentialBlob
;
3216 for(i
= 0, j
= 3; blob
[i
] && j
< 5; j
++){
3217 for(ui
[j
] = &blob
[i
]; blob
[i
] && blob
[i
] != '\t'; i
++)
3218 ; /* find end of data */
3221 blob
[i
++] = '\0'; /* tie off data */
3228 if(passwd
&& host
&& user
){ /* valid field? */
3229 STRLIST_S hostlist
[2];
3232 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
3233 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
3234 hostlist
[0].name
= host
;
3236 hostlist
[0].next
= &hostlist
[1];
3237 hostlist
[1].name
= orighost
;
3238 hostlist
[1].next
= NULL
;
3241 hostlist
[0].next
= NULL
;
3244 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
3248 fs_give((void **) &target
);
3251 g_CredFree((PVOID
) pcred
);
3257 # else /* old windows */
3264 char target
[MAILTMPLEN
];
3265 char *tmp
, *host
, *user
, *sflags
, *passwd
, *orighost
;
3268 SecKeychainAttributeList attrList
;
3269 SecKeychainSearchRef searchRef
= NULL
;
3270 SecKeychainAttribute attrs
[] = {
3271 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
3274 if(using_passfile
== 0)
3275 return(using_passfile
);
3277 dprint((9, "read_passfile\n"));
3280 /* search for only our items in the keychain */
3282 attrList
.attr
= attrs
;
3285 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
3286 kSecGenericPasswordItemClass
,
3289 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3291 SecKeychainItemRef itemRef
= NULL
;
3292 SecKeychainAttributeInfo info
;
3293 SecKeychainAttributeList
*attrList
= NULL
;
3296 char *blobcopy
= NULL
; /* NULL terminated copy */
3298 UInt32 tags
[] = {kSecAccountItemAttr
,
3299 kSecServiceItemAttr
};
3300 UInt32 formats
[] = {0,0};
3302 dprint((10, "read_passfile: searchRef not NULL\n"));
3305 info
.format
= formats
;
3308 * Go through each item we found and put it
3311 while(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
)) && itemRef
){
3312 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3313 rc
= SecKeychainItemCopyAttributesAndData(itemRef
,
3318 if(rc
== 0 && attrList
){
3319 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList
->count
));
3321 blobcopy
= (char *) fs_get((blength
+ 1) * sizeof(char));
3322 strncpy(blobcopy
, (char *) blob
, blength
);
3323 blobcopy
[blength
] = '\0';
3326 * I'm not real clear on how this works. It seems to be
3327 * necessary to combine the attributes from two passes
3328 * (attrList->count == 2) in order to get the full set
3329 * of attributes we inserted into the keychain in the
3330 * first place. So, we reset host...orighost outside of
3331 * the following for loop, not inside.
3333 host
= user
= sflags
= passwd
= orighost
= NULL
;
3334 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3336 for(k
= 0; k
< attrList
->count
; k
++){
3338 if(attrList
->attr
[k
].length
){
3340 (char *) attrList
->attr
[k
].data
,
3341 MIN(attrList
->attr
[k
].length
,sizeof(target
)));
3342 target
[MIN(attrList
->attr
[k
].length
,sizeof(target
)-1)] = '\0';
3346 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
3347 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3348 ; /* find end of data */
3351 tmp
[i
++] = '\0'; /* tie off data */
3363 for(i
= 0, j
= 3; blobcopy
[i
] && j
< 5; j
++){
3364 for(ui
[j
] = &blobcopy
[i
]; blobcopy
[i
] && blobcopy
[i
] != '\t'; i
++)
3365 ; /* find end of data */
3368 blobcopy
[i
++] = '\0'; /* tie off data */
3377 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
:""));
3380 if(passwd
&& host
&& user
){ /* valid field? */
3381 STRLIST_S hostlist
[2];
3384 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
3385 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
3386 hostlist
[0].name
= host
;
3388 hostlist
[0].next
= &hostlist
[1];
3389 hostlist
[1].name
= orighost
;
3390 hostlist
[1].next
= NULL
;
3393 hostlist
[0].next
= NULL
;
3396 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
3400 fs_give((void **) & blobcopy
);
3402 SecKeychainItemFreeAttributesAndData(attrList
, (void *) blob
);
3406 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc
));
3413 CFRelease(searchRef
);
3417 dprint((10, "read_passfile: searchRef NULL\n"));
3422 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc
));
3425 return(using_passfile
);
3427 #else /* PASSFILE */
3429 char tmp
[MAILTMPLEN
], *ui
[5];
3430 int i
, j
, n
, rv
= 0;
3432 char *tmptext
= NULL
;
3435 char tmp2
[MAILTMPLEN
];
3436 char *text
= NULL
, *text2
= NULL
;
3441 if(using_passfile
== 0)
3442 return(using_passfile
);
3444 dprint((9, "read_passfile\n"));
3446 /* if there's no password to read, bag it!! */
3447 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb"))){
3449 return(using_passfile
);
3453 if(our_stat(tmp
, &sbuf
) == 0)
3455 fp
= our_fopen(tmp
, "rb"); /* reopen to read data */
3457 /* the next call initializes the key/certificate pair used to
3458 * encrypt and decrypt a password file. The details of how this is
3459 * done is in the file pith/smime.c. During this setup we might call
3460 * smime_init(), but no matter what happens we must call smime_deinit()
3461 * there. The reason why this is so is because we can not assume that
3462 * the .pinerc file has been read by this time, so this code might not
3463 * know about the ps_global->smime structure or any of its components,
3464 * and it shouldn't because it only needs ps_global->pwdcert, so
3465 * do not init smime here, because the .pinerc might not have been
3466 * read and we do not really know where the keys and certificates really
3468 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3469 * it is called for the first time and there are certificates at all,
3470 * or when it is called after the first time and the user refuses to
3471 * create a self-signed certificate. In this situation we will just
3472 * let the user live in an insecure world, but no more passwords will
3473 * be saved in the password file, and only those found there will be used.
3476 fgets(tmp2
, sizeof(tmp2
), fp
);
3478 if(strcmp(tmp2
, "-----BEGIN PKCS7-----\n")){
3479 /* there is an already existing password file, that is not encrypted
3480 * and there is no key to encrypt it yet, go again through setup_pwdcert
3481 * and encrypt it now.
3483 if(tmp2
[0]){ /* not empty, UNencrypted password file */
3484 if(ps_global
->pwdcert
== NULL
)
3485 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3486 if((rv
== 0 || rv
== -5) && ps_global
->pwdcert
== NULL
)
3487 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3488 if(ps_global
->pwdcert
== NULL
){
3489 q_status_message(SM_ORDER
, 3, 3,
3490 " Failed to create private key. Using UNencrypted Password file. ");
3495 q_status_message(SM_ORDER
, 3, 3,
3496 " Failed to unlock private key. Using UNencrypted Password file. ");
3497 save_password
= 0; /* do not save more passwords */
3500 if(ps_global
->pwdcert
!= NULL
3501 && encrypt_file((char *)tmp
, NULL
, (PERSONAL_CERT
*)ps_global
->pwdcert
))
3506 if(ps_global
->pwdcert
== NULL
)
3507 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3512 * if password file is encrypted we attempt to decrypt. We ask the
3513 * user for the password to unlock the password file. If the user
3514 * enters the password and it unlocks the file, use it and keep saving
3515 * passwords in it. If the user enters the wrong passwords and does
3516 * not unlock it, we will not see that here, but in decrypt_file, so
3517 * the only other possibility is that the user cancels. In that case
3518 * we will see i == -1. In that case, we will let the user attempt
3519 * manual login to the server they want to login, but passwords will
3520 * not be saved so that the password file will not be saved
3521 * unencrypted and rewritten again.
3524 text
= text2
= decrypt_file((char *)tmp
, &i
, (PERSONAL_CERT
*)ps_global
->pwdcert
);
3525 len
= text2
? strlen(text2
) : 0;
3527 case -2: using_passfile
= 0;
3530 case 1 : save_password
= 1;
3534 case -1: save_password
= 0;
3543 if(using_passfile
== 0){
3545 if(text
) fs_give((void **)&text
);
3547 return using_passfile
;
3551 tmptext
= fs_get(len
+ 1);
3553 for(n
= 0; encrypted
? line_get(tmptext
, len
+ 1, &text2
)
3554 : (fgets(tmptext
, len
+1, fp
) != NULL
); n
++){
3556 for(n
= 0; fgets(tmptext
, len
+1, fp
); n
++){
3558 /*** do any necessary DEcryption here ***/
3560 for(i
= 0; tmptext
[i
]; i
++)
3561 tmptext
[i
] = xlate_out(tmptext
[i
]);
3563 if(i
&& tmptext
[i
-1] == '\n')
3564 tmptext
[i
-1] = '\0'; /* blast '\n' */
3566 dprint((10, "read_passfile: %s\n", tmp
? tmp
: "?"));
3567 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3568 for(i
= 0, j
= 0; tmptext
[i
] && j
< 5; j
++){
3569 for(ui
[j
] = &tmptext
[i
]; tmptext
[i
] && tmptext
[i
] != '\t'; i
++)
3570 ; /* find end of data */
3573 tmptext
[i
++] = '\0'; /* tie off data */
3576 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3577 if(ui
[0] && ui
[1] && ui
[2]){ /* valid field? */
3578 STRLIST_S hostlist
[2];
3579 char *s
= ui
[3] ? strchr(ui
[3], PWDAUTHSEP
) : NULL
;
3580 int flags
= ui
[3] ? atoi(s
? ++s
: ui
[3]) : 0;
3582 hostlist
[0].name
= ui
[2];
3584 hostlist
[0].next
= &hostlist
[1];
3585 hostlist
[1].name
= ui
[4];
3586 hostlist
[1].next
= NULL
;
3589 hostlist
[0].next
= NULL
;
3592 imap_set_passwd(l
, ui
[0], ui
[1], hostlist
, flags
& 0x01, 0, 0);
3597 if (tmptext
) fs_give((void **) &tmptext
);
3599 if (text
) fs_give((void **)&text
);
3604 #endif /* PASSFILE */
3610 write_passfile(pinerc
, l
)
3614 char *authend
, *authtype
;
3617 char target
[4*MAILTMPLEN
];
3618 char blob
[4*MAILTMPLEN
];
3622 if(using_passfile
== 0)
3625 dprint((9, "write_passfile\n"));
3627 for(; l
; l
= l
->next
){
3628 authtype
= l
->passwd
;
3629 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3630 if(authend
!= NULL
){
3632 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3633 *authend
= PWDAUTHSEP
;
3636 sprintf(blob
, "%d", l
->altflag
);
3638 snprintf(target
, sizeof(target
), "%s%s\t%s\t%s",
3640 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3641 l
->user
? l
->user
: "",
3643 ltarget
= utf8_to_lptstr((LPSTR
) target
);
3646 snprintf(blob
, sizeof(blob
), "%s%s%s",
3647 l
->passwd
? l
->passwd
: "",
3648 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3650 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3651 ? l
->hosts
->next
->name
: "");
3652 memset((void *) &cred
, 0, sizeof(cred
));
3654 cred
.Type
= CRED_TYPE_GENERIC
;
3655 cred
.TargetName
= ltarget
;
3656 cred
.CredentialBlobSize
= strlen(blob
)+1;
3657 cred
.CredentialBlob
= (LPBYTE
) &blob
;
3658 cred
.Persist
= CRED_PERSIST_ENTERPRISE
;
3659 g_CredWriteW(&cred
, 0);
3661 fs_give((void **) <arget
);
3664 #endif /* WINCRED > 0 */
3668 char target
[4*MAILTMPLEN
];
3669 char blob
[4*MAILTMPLEN
];
3670 SecKeychainItemRef itemRef
= NULL
;
3672 if(using_passfile
== 0)
3675 dprint((9, "write_passfile\n"));
3677 for(; l
; l
= l
->next
){
3678 authtype
= l
->passwd
;
3679 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3680 if(authend
!= NULL
){
3682 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3683 *authend
= PWDAUTHSEP
;
3686 sprintf(blob
, "%d", l
->altflag
);
3688 snprintf(target
, sizeof(target
), "%s\t%s\t%s",
3689 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3690 l
->user
? l
->user
: "",
3693 snprintf(blob
, sizeof(blob
), "%s%s%s",
3694 l
->passwd
? l
->passwd
: "",
3695 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3697 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3698 ? l
->hosts
->next
->name
: "");
3700 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target
), target
, strlen(TNAME
), TNAME
, strlen(blob
), blob
));
3702 rc
= SecKeychainAddGenericPassword(NULL
,
3703 strlen(target
), target
,
3704 strlen(TNAME
), TNAME
,
3708 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3711 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc
));
3714 if(rc
== errSecDuplicateItem
){
3715 /* fix existing entry */
3716 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3718 if(!(rc
=SecKeychainFindGenericPassword(NULL
,
3719 strlen(target
), target
,
3720 strlen(TNAME
), TNAME
,
3722 &itemRef
)) && itemRef
){
3724 rc
= SecKeychainItemModifyAttributesAndData(itemRef
, NULL
, strlen(blob
), blob
);
3726 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc
));
3730 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc
));
3735 #else /* PASSFILE */
3736 char tmp
[4*MAILTMPLEN
], blob
[4*MAILTMPLEN
];
3740 char *text
= NULL
, tmp2
[4*MAILTMPLEN
];
3744 if(using_passfile
== 0)
3747 dprint((9, "write_passfile\n"));
3749 /* if there's no passfile to read, bag it!! */
3750 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "wb"))){
3756 strncpy(tmp2
, tmp
, sizeof(tmp2
));
3757 tmp2
[sizeof(tmp2
)-1] = '\0';
3760 for(n
= 0; l
; l
= l
->next
, n
++){
3761 authtype
= l
->passwd
;
3762 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3763 if(authend
!= NULL
){
3765 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3766 *authend
= PWDAUTHSEP
;
3769 sprintf(blob
, "%d", l
->altflag
);
3771 /*** do any necessary ENcryption here ***/
3772 snprintf(tmp
, sizeof(tmp
), "%s\t%s\t%s\t%s%s%s\n", l
->passwd
, l
->user
,
3773 l
->hosts
->name
, blob
,
3774 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "",
3775 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
3777 dprint((10, "write_passfile: %s", tmp
? tmp
: "?"));
3779 for(i
= 0; tmp
[i
]; i
++)
3780 tmp
[i
] = xlate_in(tmp
[i
]);
3783 fs_resize((void **)&text
, (len
+ strlen(tmp
) + 1)*sizeof(char));
3785 len
+= strlen(tmp
) + 1;
3786 strncat(text
, tmp
, strlen(tmp
));
3795 if(ps_global
->pwdcert
== NULL
){
3796 q_status_message(SM_ORDER
, 3, 3, "Attempting to encrypt password file");
3797 i
= setup_pwdcert(&ps_global
->pwdcert
);
3798 if((i
== 0 || i
== -5) && ps_global
->pwdcert
== NULL
)
3799 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3801 if(ps_global
->pwdcert
== NULL
){ /* we tried but failed */
3803 q_status_message1(SM_ORDER
, 3, 3, "Error: no directory %s to save certificates", ps_global
->pwdcertdir
);
3805 q_status_message(SM_ORDER
, 3, 3, "Refusing to write non-encrypted password file");
3807 else if(encrypt_file((char *)tmp2
, text
, ps_global
->pwdcert
) == 0)
3808 q_status_message(SM_ORDER
, 3, 3, "Failed to encrypt password file");
3809 fs_give((void **)&text
); /* do not save this text */
3812 #endif /* PASSFILE */
3815 #endif /* LOCAL_PASSWD_CACHE */
3820 erase_windows_credentials(void)
3822 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3827 if(init_wincred_funcs() != 1)
3831 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
3833 for(k
= 0; k
< count
; k
++)
3834 g_CredDeleteW(pcred
[k
]->TargetName
, CRED_TYPE_GENERIC
, 0);
3836 g_CredFree((PVOID
) pcred
);
3842 ask_erase_credentials(void)
3844 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y'){
3845 erase_windows_credentials();
3846 q_status_message(SM_ORDER
, 3, 3, "All preserved passwords have been erased");
3849 q_status_message(SM_ORDER
, 3, 3, "Previously preserved passwords will not be erased");
3852 #endif /* WINCRED */
3855 #ifdef LOCAL_PASSWD_CACHE
3857 get_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
)
3858 char *pinerc
, **passwd
, *user
;
3859 STRLIST_S
*hostlist
;
3862 return get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, NULL
);
3866 * get_passfile_passwd_auth - return the password contained in the special passord
3867 * cache. The file is assumed to be in the same directory
3868 * as the pinerc with the name defined above.
3871 get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, authtype
)
3872 char *pinerc
, **passwd
, *user
;
3873 STRLIST_S
*hostlist
;
3877 dprint((10, "get_passfile_passwd_auth\n"));
3878 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
3879 ? imap_get_passwd_auth(passfile_cache
, passwd
,
3880 user
, hostlist
, altflag
, authtype
)
3885 free_passfile_cache_work(MMLOGIN_S
**pwdcache
)
3887 if(pwdcache
== NULL
|| *pwdcache
== NULL
)
3890 if((*pwdcache
)->user
) fs_give((void **)&(*pwdcache
)->user
);
3891 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
3892 if((*pwdcache
)->hosts
) free_strlist(&(*pwdcache
)->hosts
);
3893 free_passfile_cache_work(&(*pwdcache
)->next
);
3894 fs_give((void **)pwdcache
);
3899 free_passfile_cache(void)
3902 free_passfile_cache_work(&passfile_cache
);
3906 is_using_passfile(void)
3908 return(using_passfile
== 1);
3912 * Just trying to guess the username the user might want to use on this
3913 * host, the user will confirm.
3916 get_passfile_user(pinerc
, hostlist
)
3918 STRLIST_S
*hostlist
;
3920 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
3921 ? imap_get_user(passfile_cache
, hostlist
)
3927 preserve_prompt(char *pinerc
)
3929 return preserve_prompt_auth(pinerc
, NULL
);
3933 preserve_prompt_auth(char *pinerc
, char *authtype
)
3937 #define PROMPT_PWD _("Preserve password for next login")
3938 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3940 * This prompt was going to be able to be turned on and off via a registry
3941 * setting controlled from the config menu. We decided to always use the
3942 * dialog for login, and there the prompt is unobtrusive enough to always
3943 * be in there. As a result, windows should never reach this, but now
3944 * OS X somewhat uses the behavior just described.
3946 if(mswin_store_pass_prompt()
3947 && (want_to(authtype
3948 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
3950 'y', 'x', NO_HELP
, WT_NORM
)
3960 #define PROMPT_PWD _("Preserve password for next login")
3961 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3964 if((rc
= macos_store_pass_prompt()) != 0){
3966 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
3967 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
3970 macos_set_store_pass_prompt(1);
3971 q_status_message(SM_ORDER
, 4, 4,
3972 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3977 macos_set_store_pass_prompt(0);
3978 q_status_message(SM_ORDER
, 4, 4,
3979 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3984 #else /* PASSFILE */
3985 #define PROMPT_PWD _("Preserve password on DISK for next login")
3986 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
3988 char tmp
[MAILTMPLEN
];
3991 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || our_stat(tmp
, &sbuf
) < 0)
3994 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
3995 return(want_to(authtype
3996 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
3997 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
4000 #endif /* PASSFILE */
4003 #endif /* LOCAL_PASSWD_CACHE */
4006 #ifdef APPLEKEYCHAIN
4010 * 1 if store pass prompt is set in the "registry" to on
4012 * -1 if not set to anything
4015 macos_store_pass_prompt(void)
4022 if(storepassprompt
== -1){
4023 if(!(rc
=SecKeychainFindGenericPassword(NULL
, 0, NULL
,
4024 strlen(TNAMEPROMPT
),
4026 (void **) &data
, NULL
))){
4027 val
= (len
== 1 && data
&& data
[0] == '1');
4031 if(storepassprompt
== -1 && !rc
){
4033 storepassprompt
= 1;
4035 storepassprompt
= 0;
4038 return(storepassprompt
);
4043 macos_set_store_pass_prompt(int val
)
4045 storepassprompt
= val
? 1 : 0;
4047 SecKeychainAddGenericPassword(NULL
, 0, NULL
, strlen(TNAMEPROMPT
),
4048 TNAMEPROMPT
, 1, val
? "1" : "0", NULL
);
4053 macos_erase_keychain(void)
4055 SecKeychainAttributeList attrList
;
4056 SecKeychainSearchRef searchRef
= NULL
;
4057 SecKeychainAttribute attrs1
[] = {
4058 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
4060 SecKeychainAttribute attrs2
[] = {
4061 { kSecAccountItemAttr
, strlen(TNAMEPROMPT
), TNAMEPROMPT
}
4064 dprint((9, "macos_erase_keychain\n"));
4067 * Seems like we ought to be able to combine attrs1 and attrs2
4068 * into a single array, but I couldn't get it to work.
4071 /* search for only our items in the keychain */
4073 attrList
.attr
= attrs1
;
4075 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4076 kSecGenericPasswordItemClass
,
4080 SecKeychainItemRef itemRef
= NULL
;
4083 * Go through each item we found and put it
4086 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4087 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4088 SecKeychainItemDelete(itemRef
);
4092 CFRelease(searchRef
);
4097 attrList
.attr
= attrs2
;
4099 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4100 kSecGenericPasswordItemClass
,
4104 SecKeychainItemRef itemRef
= NULL
;
4107 * Go through each item we found and put it
4110 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4111 SecKeychainItemDelete(itemRef
);
4115 CFRelease(searchRef
);
4120 #endif /* APPLEKEYCHAIN */
4122 #ifdef LOCAL_PASSWD_CACHE
4125 set_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
)
4126 char *pinerc
, *passwd
, *user
;
4127 STRLIST_S
*hostlist
;
4128 int altflag
, already_prompted
;
4130 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, NULL
);
4133 * set_passfile_passwd - set the password file entry associated with
4134 * cache. The file is assumed to be in the same directory
4135 * as the pinerc with the name defined above.
4136 * already_prompted: 0 not prompted
4137 * 1 prompted, answered yes
4138 * 2 prompted, answered no
4141 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, authtype
)
4142 char *pinerc
, *passwd
, *user
;
4143 STRLIST_S
*hostlist
;
4144 int altflag
, already_prompted
;
4147 dprint((10, "set_passfile_passwd_auth\n"));
4148 if(((already_prompted
== 0 && preserve_prompt_auth(pinerc
, authtype
))
4149 || already_prompted
== 1)
4150 && !ps_global
->nowrite_password_cache
4151 && (passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))){
4152 imap_set_passwd_auth(&passfile_cache
, passwd
, user
, hostlist
, altflag
, 0, 0, authtype
);
4153 write_passfile(pinerc
, passfile_cache
);
4158 update_passfile_hostlist(pinerc
, user
, hostlist
, altflag
)
4161 STRLIST_S
*hostlist
;
4164 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, NULL
);
4168 * Passfile lines are
4170 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4172 * In pine4.40 and before there was no orig_hostname.
4173 * This routine attempts to repair that.
4176 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, authtype
)
4179 STRLIST_S
*hostlist
;
4185 #else /* !WINCRED */
4187 size_t len
= authtype
? strlen(authtype
) : 0;
4188 size_t offset
= authtype
? 1 : 0;
4190 for(l
= passfile_cache
; l
; l
= l
->next
)
4191 if(imap_same_host_auth(l
->hosts
, hostlist
, authtype
)
4193 && !strcmp(user
, l
->user
+ len
+ offset
)
4194 && l
->altflag
== altflag
){
4198 if(l
&& l
->hosts
&& hostlist
&& !l
->hosts
->next
&& hostlist
->next
4199 && hostlist
->next
->name
4200 && !ps_global
->nowrite_password_cache
){
4201 l
->hosts
->next
= new_strlist_auth(hostlist
->next
->name
, authtype
, PWDAUTHSEP
);
4202 write_passfile(pinerc
, passfile_cache
);
4204 #endif /* !WINCRED */
4207 #endif /* LOCAL_PASSWD_CACHE */
4212 * Load and init the WinCred structure.
4213 * This gives us a way to skip the WinCred code
4214 * if the dll doesn't exist.
4217 init_wincred_funcs(void)
4223 /* Assume the worst. */
4226 hmod
= LoadLibrary(TEXT("advapi32.dll"));
4229 FARPROC fpCredWriteW
;
4230 FARPROC fpCredEnumerateW
;
4231 FARPROC fpCredDeleteW
;
4234 fpCredWriteW
= GetProcAddress(hmod
, "CredWriteW");
4235 fpCredEnumerateW
= GetProcAddress(hmod
, "CredEnumerateW");
4236 fpCredDeleteW
= GetProcAddress(hmod
, "CredDeleteW");
4237 fpCredFree
= GetProcAddress(hmod
, "CredFree");
4239 if(fpCredWriteW
&& fpCredEnumerateW
&& fpCredDeleteW
&& fpCredFree
)
4241 g_CredWriteW
= (CREDWRITEW
*)fpCredWriteW
;
4242 g_CredEnumerateW
= (CREDENUMERATEW
*)fpCredEnumerateW
;
4243 g_CredDeleteW
= (CREDDELETEW
*)fpCredDeleteW
;
4244 g_CredFree
= (CREDFREE
*)fpCredFree
;
4250 mswin_set_erasecreds_callback(ask_erase_credentials
);
4253 return g_CredInited
;
4256 #endif /* WINCRED */