2 * ========================================================================
3 * Copyright 2013-2021 Eduardo Chappa
4 * Copyright 2006-2009 University of Washington
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 /*======================================================================
17 The call back routines for the c-client/imap
18 - handles error messages and other notification
19 - handles prelimirary notification of new mail and expunged mail
20 - prompting for imap server login and password
39 #include "xoauth2conf.h"
40 #include "confscroll.h"
42 #include "../pith/state.h"
43 #include "../pith/conf.h"
44 #include "../pith/msgno.h"
45 #include "../pith/filter.h"
46 #include "../pith/news.h"
47 #include "../pith/util.h"
48 #include "../pith/list.h"
49 #include "../pith/margin.h"
51 #include "../pith/smime.h"
56 #define TNAME "UWash_Alpine_"
57 #define TNAMESTAR "UWash_Alpine_*"
58 #define PWDBUFFERSIZE (250)
59 #define MAXPWDBUFFERSIZE (2*PWDBUFFERSIZE) /* This number must be less than 512 */
62 * WinCred Function prototypes
64 typedef BOOL (WINAPI CREDWRITEW
) ( __in PCREDENTIALW Credential
, __in DWORD Flags
);
65 typedef BOOL (WINAPI CREDENUMERATEW
) ( __in LPCWSTR Filter
, __reserved DWORD Flags
,
66 __out DWORD
*Count
, __deref_out_ecount(*Count
) PCREDENTIALW
**Credential
);
67 typedef BOOL (WINAPI CREDDELETEW
) ( __in LPCWSTR TargetName
, __in DWORD Type
,
68 __reserved DWORD Flags
);
69 typedef VOID (WINAPI CREDFREE
) ( __in PVOID Buffer
);
74 int g_CredInited
= 0; /* 1 for loaded successfully,
75 * -1 for not available.
76 * 0 for not initialized yet.
78 CREDWRITEW
*g_CredWriteW
;
79 CREDENUMERATEW
*g_CredEnumerateW
;
80 CREDDELETEW
*g_CredDeleteW
;
86 #include <Security/SecKeychain.h>
87 #include <Security/SecKeychainItem.h>
88 #include <Security/SecKeychainSearch.h>
89 #define TNAME "UWash_Alpine"
90 #define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
92 int macos_store_pass_prompt(void);
93 void macos_set_store_pass_prompt(int);
95 static int storepassprompt
= -1;
96 #endif /* APPLEKEYCHAIN */
100 * Internal prototypes
102 void mm_login_alt_cue(NETMBX
*);
103 long pine_tcptimeout_noscreen(long, long, char *);
104 int answer_cert_failure(int, MSGNO_S
*, SCROLL_S
*);
105 int oauth2_auth_answer(int, MSGNO_S
*, SCROLL_S
*);
106 OAUTH2_S
*oauth2_select_flow(char *);
107 int xoauth2_flow_tool(struct pine
*, int, CONF_S
**, unsigned int);
109 #ifdef LOCAL_PASSWD_CACHE
110 int read_passfile(char *, MMLOGIN_S
**);
111 void write_passfile(char *, MMLOGIN_S
*);
112 int preserve_prompt(char *);
113 int preserve_prompt_auth(char *, char *authtype
);
114 void update_passfile_hostlist(char *, char *, STRLIST_S
*, int);
115 void update_passfile_hostlist_auth(char *, char *, STRLIST_S
*, int, char *);
116 void free_passfile_cache_work(MMLOGIN_S
**);
118 static MMLOGIN_S
*passfile_cache
= NULL
;
119 static int using_passfile
= -1;
120 int save_password
= 1;
121 #endif /* LOCAL_PASSWD_CACHE */
125 char xlate_out(char);
126 int line_get(char *, size_t, char **);
127 #endif /* PASSFILE */
130 void ask_erase_credentials(void);
131 int init_wincred_funcs(void);
135 static char *details_cert
, *details_host
, *details_reason
;
137 extern XOAUTH2_INFO_S xoauth_default
[];
140 * This is the private information of the client, which is passed to
141 * c-client for processing. Every c-client application must have its
144 OAUTH2_S alpine_oauth2_list
[] = {
146 {"imap.gmail.com", "smtp.gmail.com", NULL
, NULL
},
147 {{"client_id", NULL
},
148 {"client_secret", NULL
},
149 {"tenant", NULL
}, /* not used */
150 {"code", NULL
}, /* access code from the authorization process */
151 {"refresh_token", NULL
},
152 {"scope", "https://mail.google.com/"},
153 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
154 {"grant_type", "authorization_code"},
155 {"grant_type", "refresh_token"},
156 {"response_type", "code"},
158 {"device_code", NULL
} /* not used */
160 {{"GET", (unsigned char *) "https://accounts.google.com/o/oauth2/auth", /* authorization address, get access code */
161 {OA2_Id
, OA2_Scope
, OA2_Redirect
, OA2_Response
, OA2_End
, OA2_End
, OA2_End
}},
162 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* Device Info information, not used */
163 {"POST", (unsigned char *) "https://accounts.google.com/o/oauth2/token", /* Address to get refresh token from access code */
164 {OA2_Id
, OA2_Secret
, OA2_Redirect
, OA2_GrantTypeforAccessToken
, OA2_Code
, OA2_End
, OA2_End
}},
165 {"POST", (unsigned char *) "https://accounts.google.com/o/oauth2/token", /* access token from refresh token */
166 {OA2_Id
, OA2_Secret
, OA2_RefreshToken
, OA2_GrantTypefromRefreshToken
, OA2_End
, OA2_End
, OA2_End
}}
168 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information */
169 NULL
, /* access token */
170 NULL
, /* special IMAP ID */
172 0, /* expiration time */
173 0, /* first time indicator */
174 1, /* client secret required */
175 0, /* Cancel refresh token */
176 GMAIL_FLAGS
/* default flags. For Gmail this should be set to OA2_AUTHORIZE */
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", (unsigned char *) "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", (unsigned char *) "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", (unsigned char *) "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 NULL
, /* special IMAP ID */
205 0, /* expiration time */
206 0, /* first time indicator */
207 0, /* client secret required */
208 0, /* Cancel refresh token */
209 OUTLOOK_FLAGS
/* default flags. For OUTLOOK this should be set to OA2_DEVICE */
212 {"outlook.office365.com", "smtp.office365.com", NULL
, NULL
},
213 {{"client_id", NULL
},
214 {"client_secret", NULL
}, /* not used, but needed */
215 {"tenant", NULL
}, /* used */
216 {"code", NULL
}, /* used during authorization */
217 {"refresh_token", NULL
},
218 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
219 {"redirect_uri", "http://localhost"},
220 {"grant_type", "authorization_code"},
221 {"grant_type", "refresh_token"},
222 {"response_type", "code"},
223 {"state", NULL
}, /* not used */
224 {"device_code", NULL
} /* not used */
226 {{"GET", (unsigned char *) "https://login.microsoftonline.com/\001/oauth2/v2.0/authorize", /* Get Access Code */
227 {OA2_Id
, OA2_Scope
, OA2_Redirect
, OA2_Response
, OA2_End
, OA2_End
, OA2_End
}},
228 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* device code, not used */
229 {"POST", (unsigned char *) "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
230 {OA2_Id
, OA2_Redirect
, OA2_Scope
, OA2_GrantTypeforAccessToken
, OA2_Secret
, OA2_Code
, OA2_End
}},
231 {"POST", (unsigned char *) "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
232 {OA2_Id
, OA2_RefreshToken
, OA2_Scope
, OA2_GrantTypefromRefreshToken
, OA2_Secret
, OA2_End
, OA2_End
}}
234 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information, not used */
235 NULL
, /* access token */
236 NULL
, /* special IMAP ID */
238 0, /* expiration time */
239 0, /* first time indicator */
240 1, /* client secret required */
241 0, /* Cancel refresh token */
242 OUTLOOK_FLAGS
/* default flags. For OUTLOOK this should be set to OA2_DEVICE */
245 {"imap.mail.yahoo.com", "smtp.mail.yahoo.com", NULL
, NULL
},
246 {{"client_id", NULL
},
247 {"client_secret", NULL
}, /* used */
248 {"tenant", NULL
}, /* not used */
249 {"code", NULL
}, /* used during authorization */
250 {"refresh_token", NULL
},
251 {"scope", NULL
}, /* not used! */
252 {"redirect_uri", "oob"}, /* https://localhost */
253 {"grant_type", "authorization_code"},
254 {"grant_type", "refresh_token"},
255 {"response_type", "code"},
256 {"state", NULL
}, /* used */
257 {"device_code", NULL
} /* not used */
259 {{"GET", (unsigned char *) "https://api.login.yahoo.com/oauth2/request_auth", /* Get Access Code */
260 {OA2_Id
, OA2_Redirect
, OA2_Response
, OA2_State
, OA2_End
, OA2_End
, OA2_End
}},
261 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* device code, not used */
262 {"POST", (unsigned char *) "https://api.login.yahoo.com/oauth2/get_token", /* Get first Refresh Token and Access token */
263 {OA2_Id
, OA2_Secret
, OA2_Redirect
, OA2_Code
, OA2_GrantTypeforAccessToken
, OA2_End
, OA2_End
}},
264 {"POST", (unsigned char *) "https://api.login.yahoo.com/oauth2/get_token", /* Get access token from refresh token */
265 {OA2_Id
, OA2_Secret
, OA2_Redirect
, OA2_RefreshToken
, OA2_GrantTypefromRefreshToken
, OA2_End
, OA2_End
}}
267 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information, not used */
268 NULL
, /* access token */
269 "ALPINE_V1", /* special IMAP ID */
271 0, /* expiration time */
272 0, /* first time indicator */
273 1, /* client secret required */
274 0, /* Cancel refresh token */
275 YAHOO_FLAGS
/* default flags. For YAHOO this should be set to OA2_AUTHORIZE */
278 {"imap.yandex.com", "smtp.yandex.com", NULL
, NULL
},
279 {{"client_id", NULL
},
280 {"client_secret", NULL
}, /* not used, but needed */
281 {"tenant", NULL
}, /* not used */
282 {"code", NULL
}, /* used during authorization */
283 {"refresh_token", NULL
},
284 {"scope", NULL
}, /* not needed, so not used */
285 {"redirect_uri", "https://oauth.yandex.ru/verification_code"},
286 {"grant_type", "authorization_code"},
287 {"grant_type", "refresh_token"},
288 {"response_type", "code"},
289 {"state", NULL
}, /* not used */
290 {"device_code", NULL
} /* not used */
292 {{"GET", (unsigned char *) "https://oauth.yandex.com/authorize", /* Get Access Code */
293 {OA2_Id
, OA2_Redirect
, OA2_Response
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}},
294 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* device code, not used */
295 {"POST", (unsigned char *) "https://oauth.yandex.com/token", /* Get first Refresh Token and Access token */
296 {OA2_Id
, OA2_Redirect
, OA2_GrantTypeforAccessToken
, OA2_Secret
, OA2_Code
, OA2_End
, OA2_End
}},
297 {"POST", (unsigned char *) "https://oauth.yandex.com/token", /* Get access token from refresh token */
298 {OA2_Id
, OA2_RefreshToken
, OA2_GrantTypefromRefreshToken
, OA2_Secret
, OA2_End
, OA2_End
, OA2_End
}}
300 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information, not used */
301 NULL
, /* access token */
302 NULL
, /* special IMAP ID */
304 0, /* expiration time */
305 0, /* first time indicator */
306 1, /* client secret required */
307 0, /* Cancel refresh token */
308 YANDEX_FLAGS
/* default flags. For YANDEX this should be set to OA2_AUTHORIZE */
310 {NULL
, /* Name, unsigned char * */
311 {NULL
, NULL
, NULL
, NULL
}, /* host and equivalents */
312 {{NULL
, NULL
}, /* client-id */
313 {NULL
, NULL
}, /* client-secret */
314 {NULL
, NULL
}, /* tenant - outlook */
315 {NULL
, NULL
}, /* access_code, for authorize method */
316 {NULL
, NULL
}, /* refresh token */
317 {NULL
, NULL
}, /* scope */
318 {NULL
, NULL
}, /* redirect */
319 {NULL
, NULL
}, /* grant type for access token */
320 {NULL
, NULL
}, /* grant type from refresh token */
321 {NULL
, NULL
}, /* response */
322 {NULL
, NULL
}, /* state */
323 {NULL
, NULL
} /* device code */
325 {{NULL
, NULL
, {0, 0, 0, 0, 0, 0, 0}}, /* method, server, parameters for authorize */
326 {NULL
, NULL
, {0, 0, 0, 0, 0, 0, 0}}, /* method, server, parameters for device login */
327 {NULL
, NULL
, {0, 0, 0, 0, 0, 0, 0}}, /* method, server, parameters for getting refresh token */
328 {NULL
, NULL
, {0, 0, 0, 0, 0, 0, 0}} /* method, server, parameters for refreshing access token */
330 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information */
331 NULL
, /* access token */
332 NULL
, /* special IMAP ID */
334 0, /* expiration time */
336 0, /* require secret */
337 0, /* cancel refresh token */
343 xoauth2_flow_tool(struct pine
*ps
, int cmd
, CONF_S
**cl
, unsigned int flags
)
349 *((*cl
)->d
.xf
.selected
) = (*cl
)->d
.xf
.pat
;
350 rv
= simple_exit_cmd(flags
);
353 rv
= simple_exit_cmd(flags
);
361 ps
->mangled_body
= 1;
367 oauth2_select_flow(char *host
)
369 OAUTH2_S
*oa2list
, *oa2
;
374 CONF_S
*ctmp
= NULL
, *first_line
= NULL
;
375 OAUTH2_S
*x_sel
= NULL
;
379 dprint((9, "xoauth2 select flow"));
380 ps_global
->next_screen
= SCREEN_FUN_NULL
;
382 memset(&screen
, 0, sizeof(screen
));
384 for(i
= 0; i
< sizeof(tmp
) && i
< ps_global
->ttyo
->screen_cols
; i
++)
389 ctmp
->flags
|= CF_NOSELECT
;
390 ctmp
->value
= cpystr(tmp
);
393 ctmp
->flags
|= CF_NOSELECT
;
394 ctmp
->value
= cpystr(_("Please select below the authorization flow you would like to follow:"));
397 ctmp
->flags
|= CF_NOSELECT
;
398 ctmp
->value
= cpystr(tmp
);
400 for(oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
401 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
402 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
]){
406 method
= oa2list
->server_mthd
[0].name
? "Authorize"
407 : (oa2list
->server_mthd
[1].name
? "Device" : "Unknown");
408 sprintf(tmp
, "%s (%s)", oa2list
->name
, method
);
409 ctmp
->value
= cpystr(tmp
);
410 ctmp
->d
.xf
.selected
= &x_sel
;
411 ctmp
->d
.xf
.pat
= oa2list
;
412 ctmp
->keymenu
= &xoauth2_id_select_km
;
413 ctmp
->help
= NO_HELP
;
414 ctmp
->help_title
= NULL
;
415 ctmp
->tool
= xoauth2_flow_tool
;
416 ctmp
->flags
= CF_STARTITEM
;
420 (void)conf_scroll_screen(ps_global
, &screen
, first_line
, _("SELECT AUTHORIZATION FLOW"),
421 _("xoauth2"), 0, NULL
);
430 for(oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++)
431 n
+= strlen((char *) oa2list
->name
) + 5; /* number, parenthesis, space */
432 n
+= 1024; /* large enough to display lines of 80 characters in UTF-8 */
433 s
= fs_get(n
*sizeof(char));
434 strcpy(s
, _("Please select below the authorization flow you would like to follow:"));
435 sprintf(s
+ strlen(s
), _("Please select the client-id to use from the following list.\n\n"));
436 for(j
= 1, oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
437 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
438 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
])
439 sprintf(s
+ strlen(s
), " %d) %.70s\n", j
++, oa2list
->name
);
441 display_init_err(s
, 0);
443 strncpy(prompt
, _("Enter your selection number: "), sizeof(prompt
));
444 prompt
[sizeof(prompt
)-1] = '\0';
446 rv
= optionally_enter(reply
, 0, 0, sizeof(reply
), prompt
, NULL
, NO_HELP
, 0);
448 rv
= (sel
>= 0 && sel
< i
) ? 0 : -1;
451 for(j
= 1, oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
452 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
453 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
]){
463 typedef struct auth_code_s
{
469 oauth2device_decode_reply(void *datap
, void *replyp
)
471 OAUTH2_DEVICEPROC_S
*av
= (OAUTH2_DEVICEPROC_S
*)datap
;
472 int reply
= *(int *) replyp
;
474 return reply
< 0 ? av
->code_failure
: (reply
== 0 ? av
->code_success
: av
->code_wait
);
478 oauth2_elapsed_done(void *aux_valuep
)
480 OAUTH2_S
*oauth2
= aux_valuep
? ((OAUTH2_DEVICEPROC_S
*) aux_valuep
)->xoauth2
: NULL
;
481 static time_t savedt
= 0, now
;
484 if(aux_valuep
== NULL
) savedt
= 0; /* reset last time we approved */
487 if(oauth2
->devicecode
.interval
+ now
>= savedt
)
496 oauth2_set_device_info(OAUTH2_S
*oa2
, char *method
)
498 char tmp
[MAILTMPLEN
];
499 char *name
= (char *) oa2
->name
;
501 OAUTH2_DEVICECODE_S
*deviceinfo
= &oa2
->devicecode
;
502 OAUTH2_DEVICEPROC_S aux_value
;
506 STORE_S
*in_store
, *out_store
;
508 HANDLE_S
*handles
= NULL
;
509 AUTH_CODE_S user_input
;
511 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
512 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
515 aux_value
.xoauth2
= oa2
;
516 aux_value
.code_success
= 'e';
517 aux_value
.code_failure
= 'e';
518 aux_value
.code_wait
= NO_OP_COMMAND
;
520 so_puts(in_store
, "<HTML><P>");
521 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name
);
522 so_puts(in_store
, tmp
);
523 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name
, method
),
524 so_puts(in_store
, tmp
);
526 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
528 _("</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."),
529 deviceinfo
->verification_uri
, deviceinfo
->verification_uri
, deviceinfo
->user_code
);
530 so_puts(in_store
, tmp
);
533 so_puts(in_store
, "</P><P>");
534 so_puts(in_store
, (char *) deviceinfo
->message
);
536 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
537 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), name
);
538 so_puts(in_store
, tmp
);
539 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
541 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 "));
542 so_puts(in_store
, _("to grant access to Alpine to your data. "));
543 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 "));
544 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 "));
545 so_puts(in_store
, _("alive at the end of this process, and your connection will proceed from there."));
546 so_puts(in_store
, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit"));
547 so_puts(in_store
, _("</P></HTML>"));
549 so_seek(in_store
, 0L, 0);
550 init_handles(&handles
);
552 gf_link_filter(gf_html2plain
,
553 gf_html2plain_opt(NULL
,
554 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
555 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
556 gf_set_so_readc(&gc
, in_store
);
557 gf_set_so_writec(&pc
, out_store
);
559 gf_clear_so_writec(out_store
);
560 gf_clear_so_readc(in_store
);
562 memset(&sargs
, 0, sizeof(SCROLL_S
));
563 sargs
.text
.handles
= handles
;
564 sargs
.text
.text
= so_text(out_store
);
565 sargs
.text
.src
= CharStar
;
566 sargs
.text
.desc
= _("help text");
567 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
568 sargs
.proc
.tool
= oauth2_auth_answer
;
569 sargs
.proc
.data
.p
= (void *)&user_input
;
570 sargs
.keys
.menu
= &oauth2_device_auth_keymenu
;
571 /* don't want to re-enter c-client */
572 sargs
.quell_newmail
= 1;
573 setbitmap(sargs
.keys
.bitmap
);
574 sargs
.help
.text
= h_oauth2_start
;
575 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
576 sargs
.aux_function
= oauth2deviceinfo_get_accesscode
;
577 sargs
.aux_value
= (void *) &aux_value
;
578 sargs
.aux_condition
= oauth2_elapsed_done
;
579 sargs
.decode_aux_rv_value
= oauth2device_decode_reply
;
580 sargs
.aux_rv_value
= (void *) &aux_rv_value
;
584 ps_global
->mangled_screen
= 1;
585 ps_global
->painted_body_on_startup
= 0;
586 ps_global
->painted_footer_on_startup
= 0;
587 } while (user_input
.answer
!= 'e');
591 free_handles(&handles
);
592 oauth2_elapsed_done(NULL
);
596 * If screen hasn't been initialized yet, use want_to.
600 tmp_20k_buf
[0] = '\0';
601 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
602 _("Authorizing Alpine Access to %s Email Services\n\n"), name
);
603 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
605 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
606 _("Alpine is attempting to log you into your %s account, using the %s method. "), name
, method
),
607 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
609 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
610 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
611 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
612 deviceinfo
->verification_uri
, deviceinfo
->user_code
);
613 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
616 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
617 "%s\n\n", deviceinfo
->message
);
618 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
621 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
622 _("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
);
623 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
625 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
626 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
627 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
629 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
630 "%s", _("to grant access to Alpine to your data.\n\n"));
631 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
633 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
634 "%s", _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
635 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
637 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
638 "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
639 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
641 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
642 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
643 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
645 display_init_err(tmp_20k_buf
, 0);
646 memset((void *)tmp
, 0, sizeof(tmp
));
647 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
648 tmp
[sizeof(tmp
)-1] = '\0';
650 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
654 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
655 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
656 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
658 aux_value
.xoauth2
= oa2
;
659 aux_value
.code_success
= 'y';
660 aux_value
.code_failure
= 'n';
661 aux_value
.code_wait
= 'w';
663 strncpy(tmp
, _("Continue waiting"), sizeof(tmp
));
664 tmp
[sizeof(tmp
)-1] = '\0';
666 if(oauth2_elapsed_done((void *) &aux_value
) == 0)
667 oauth2deviceinfo_get_accesscode((void *) &aux_value
, (void *) &rv
);
668 ch
= oauth2device_decode_reply((void *) &aux_value
, (void *) &rv
);
669 } while (ch
== 'w' || want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y');
670 oauth2_elapsed_done(NULL
);
676 oauth2_get_access_code(unsigned char *url
, char *method
, OAUTH2_S
*oauth2
, int *tryanother
)
678 char tmp
[MAILTMPLEN
];
683 STORE_S
*in_store
, *out_store
;
685 HANDLE_S
*handles
= NULL
;
686 AUTH_CODE_S user_input
;
688 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
689 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
692 so_puts(in_store
, "<HTML><BODY><P>");
693 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2
->name
);
694 so_puts(in_store
, tmp
);
695 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
696 so_puts(in_store
, tmp
);
698 if(strucmp((char *) oauth2
->name
, (char *) GMAIL_NAME
) == 0){
699 so_puts(in_store
, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
700 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."));
701 so_puts(in_store
, _("<UL> "));
702 so_puts(in_store
, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
703 so_puts(in_store
, _("and create a project. The name of the project is not important."));
704 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."));
705 so_puts(in_store
, _("<LI> Create OAUTH Credentials."));
706 so_puts(in_store
, _("</UL> "));
707 so_puts(in_store
, _("<P> As a result of this process, you will get a client-id and a client-secret."));
708 so_puts(in_store
, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
709 so_puts(in_store
, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
710 so_puts(in_store
, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
711 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."));
714 so_puts(in_store
, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
715 so_puts(in_store
,"</P><P>");
716 sprintf(tmp_20k_buf
, _("<A HREF=\"%s\">%s</A>"), url
, url
);
717 so_puts(in_store
, tmp_20k_buf
);
719 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
720 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2
->name
);
721 so_puts(in_store
, tmp
);
722 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
724 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. "));
725 so_puts(in_store
, _(" At the end of this process, you will be given an access code or redirected to a web page."));
726 so_puts(in_store
, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
727 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. "));
728 so_puts(in_store
, _(" Once you have completed this process, Alpine will proceed with authentication."));
729 so_puts(in_store
, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
730 so_puts(in_store
, _("</P></BODY></HTML>"));
732 so_seek(in_store
, 0L, 0);
733 init_handles(&handles
);
735 gf_link_filter(gf_html2plain
,
736 gf_html2plain_opt(NULL
,
737 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
738 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
739 gf_set_so_readc(&gc
, in_store
);
740 gf_set_so_writec(&pc
, out_store
);
742 gf_clear_so_writec(out_store
);
743 gf_clear_so_readc(in_store
);
745 memset(&sargs
, 0, sizeof(SCROLL_S
));
746 sargs
.text
.handles
= handles
;
747 sargs
.text
.text
= so_text(out_store
);
748 sargs
.text
.src
= CharStar
;
749 sargs
.text
.desc
= _("help text");
750 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
751 sargs
.proc
.tool
= oauth2_auth_answer
;
752 sargs
.proc
.data
.p
= (void *)&user_input
;
753 sargs
.keys
.menu
= &oauth2_auth_keymenu
;
754 /* don't want to re-enter c-client */
755 sargs
.quell_newmail
= 1;
756 setbitmap(sargs
.keys
.bitmap
);
757 sargs
.help
.text
= h_oauth2_start
;
758 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
762 ps_global
->mangled_screen
= 1;
763 ps_global
->painted_body_on_startup
= 0;
764 ps_global
->painted_footer_on_startup
= 0;
765 } while (user_input
.answer
!= 'e');
767 if(!struncmp(user_input
.code
, "http://", 7)
768 || !struncmp(user_input
.code
, "https://", 8)){
770 s
= strstr(user_input
.code
, "code=");
778 else code
= user_input
.code
? cpystr(user_input
.code
) : NULL
;
779 if(user_input
.code
) fs_give((void **) &user_input
.code
);
781 if(code
== NULL
) *tryanother
= 1;
785 free_handles(&handles
);
788 int flags
, rc
, q_line
;
789 /* TRANSLATORS: user needs to input an access code from the server */
790 char *accesscodelabel
= _("Copy and Paste Access Code");
791 char prompt
[MAILTMPLEN
], token
[MAILTMPLEN
];
793 * If screen hasn't been initialized yet, use want_to.
796 tmp_20k_buf
[0] = '\0';
797 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
798 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2
->name
);
799 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
801 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
802 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
803 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
805 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
806 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
807 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
809 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
811 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
813 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
814 _("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
);
815 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
817 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
818 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
819 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
821 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
822 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
823 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
825 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
826 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
827 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
829 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
830 "%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. "));
831 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
833 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
834 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
835 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
837 display_init_err(tmp_20k_buf
, 0);
838 memset((void *)tmp
, 0, sizeof(tmp
));
839 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
840 tmp
[sizeof(tmp
)-1] = '\0';
842 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
843 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
844 flags
= OE_APPEND_CURRENT
;
845 sprintf(prompt
, "%s: ", accesscodelabel
);
847 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
848 prompt
, NULL
, NO_HELP
, &flags
);
849 } while (rc
!= 0 && rc
!= 1);
850 if(!struncmp(token
, "http://", 7)
851 || !struncmp(token
, "https://", 8)){
853 s
= strstr(token
, "code=");
861 else code
= token
[0] ? cpystr(token
) : NULL
;
868 void mm_login_oauth2(NETMBX
*, char *, char *, OAUTH2_S
*, long int, char *, char *);
870 /* The purpose of this function is to report to c-client the values of the
871 * different tokens and codes so that c-client can try to log in the user
872 * to the server. This function DOES NOT attempt to get these values for
873 * the user. That is attempted in the c-client side (as best as it can be
874 * done given our circumstances: no http support, no javascript support,
875 * etc.). This is the best we can do:
877 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
878 * as best as we can. Unloaded means that there is no server known to
879 * connect, no access token, etc. Pretty much nothing is known about
880 * how to get access code, access token, etc. We ask the user to fill
881 * it up for us, if they can. If the user fills it up we save those
884 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
885 * save the information that c-client got for us.
887 * 3. When saving this information we use the password caching facilities,
888 * but we must do it in a different format so that old information and
889 * new information are not mixed. In order to accommodate this for new
890 * authentication methods, we save the information in the same fields,
891 * but this time we modify it slightly, so that old functions fail to
892 * understand the new information and so not modify it nor use it. The
893 * modification is simple: Every piece of information that was saved
894 * before is prepended XOAUTH2\001 to indicate the authentication
895 * method for which it works. The character \001 is a separator. New
896 * Alpine will know how to deal with this, but old versions, will not
897 * strip this prefix from the information and fail to get the
898 * information or modify it when needed. Only new versions of Alpine will
899 * know how to process this information.
900 * new_value = authenticator_method separator old_value
901 * authenticator_method = "XOAUTH2_S"
903 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
904 * authenticator is "XOAUTH2\001imap.gmail.com".
905 * In addition, the password field is not used to encode the password
906 * anymore, it is used to save login information needed in a format that
907 * the caller function chooses, but that must be preceded by the
908 * "authenticator_method separator" code as above.
911 mm_login_oauth2(NETMBX
*mb
, char *user
, char *method
,
912 OAUTH2_S
*login
, long int trial
,
913 char *usethisprompt
, char *altuserforcache
)
915 char *token
, tmp
[MAILTMPLEN
];
916 char prompt
[4*MAILTMPLEN
];
917 char *OldRefreshToken
, *OldAccessToken
;
918 char *NewRefreshToken
, *NewAccessToken
;
919 char *SaveRefreshToken
, *SaveAccessToken
;
920 /* TRANSLATORS: A label for the hostname that the user is logging in on */
921 char *hostlabel
= _("HOST");
922 /* TRANSLATORS: user is logging in as a particular user (a particular
923 login name), this is just labelling that user name. */
924 char *userlabel
= _("USER");
925 STRLIST_S hostlist
[2], hostlist2
[OAUTH2_TOT_EQUIV
+1];
926 int len
, q_line
, flags
, i
, j
;
929 int ChangeAccessToken
, ChangeRefreshToken
, ChangeExpirationTime
;
930 OAUTH2_S
*oa2list
, *oa2
;
933 unsigned long OldExpirationTime
, NewExpirationTime
, SaveExpirationTime
;
934 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
935 int preserve_password
= -1;
938 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
939 trial
, mb
->user
? mb
->user
: "(null)",
940 mb
->service
? mb
->service
: "(null)",
941 mb
->port
? " port=" : "",
942 mb
->port
? comatose(mb
->port
) : "",
943 altuserforcache
? " altuserforcache =" : "",
944 altuserforcache
? altuserforcache
: ""));
946 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
948 save_in_init
= ps_global
->in_init_seq
;
949 ps_global
->in_init_seq
= 0;
950 ps_global
->no_newmail_check_from_optionally_enter
= 1;
952 /* make sure errors are seen */
953 if(ps_global
->ttyo
&& !ps_global
->noshow_error
)
954 flush_status_messages(0);
956 token
= NULL
; /* start from scratch */
958 hostlist
[0].name
= mb
->host
;
959 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
960 hostlist
[0].next
= &hostlist
[1];
961 hostlist
[1].name
= mb
->orighost
;
962 hostlist
[1].next
= NULL
;
965 hostlist
[0].next
= NULL
;
967 if(hostlist
[0].name
){
968 dprint((9, "mm_login_oauth2: host=%s\n",
969 hostlist
[0].name
? hostlist
[0].name
: "?"));
970 if(hostlist
[0].next
&& hostlist
[1].name
){
971 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist
[1].name
));
975 if(trial
== 0L && !altuserforcache
){
976 if(*mb
->user
!= '\0')
977 strncpy(user
, mb
->user
, NETMAXUSER
);
979 flags
= OE_APPEND_CURRENT
;
980 sprintf(prompt
, "%s: %s - %s: ", hostlabel
, mb
->orighost
, userlabel
);
981 optionally_enter(user
, q_line
, 0, NETMAXUSER
, prompt
, NULL
, NO_HELP
, &flags
);
983 user
[NETMAXUSER
-1] = '\0';
987 * We check to see if the server we are going to log in to is already
988 * registered. This gives us a list of servers with the same
989 * credentials, so we use the same credentials for all of them.
992 for(registered
= 0, oa2list
= alpine_oauth2_list
;
993 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
995 for(i
= 0; i
< OAUTH2_TOT_EQUIV
996 && oa2list
->host
[i
] != NULL
997 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
998 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
1005 x
= oauth2_get_client_info(oa2list
->name
, user
);
1007 for(oa2list
= alpine_oauth2_list
;
1008 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
1010 for(i
= 0; i
< OAUTH2_TOT_EQUIV
1011 && oa2list
->host
[i
] != NULL
1012 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
1013 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
1014 char *flow
= oa2list
->server_mthd
[0].name
? "Authorize"
1015 : (oa2list
->server_mthd
[1].name
? "Device" : "Unknown");
1016 if(!strucmp(x
->flow
, flow
)) break; /* found it */
1020 /* else use the one we found earlier, the user has to configure this better */
1024 hostlist2
[i
= 0].name
= mb
->host
;
1025 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
))
1026 hostlist2
[++i
].name
= mb
->orighost
;
1028 for(j
= 0; j
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[j
] != NULL
; j
++){
1030 for(k
= 0; k
<= i
&& hostlist2
[k
].name
1031 && strcmp(hostlist2
[k
].name
, oa2list
->host
[j
]); k
++);
1033 hostlist2
[++i
].name
= oa2list
->host
[j
];
1035 hostlist2
[i
+1].name
= NULL
;
1036 hostlist2
[i
+1].next
= NULL
;
1037 for(j
= i
; j
>= 0; j
--)
1038 hostlist2
[j
].next
= &hostlist2
[j
+1];
1041 if(registered
){ /* redo the app_id, no questions asked */
1042 free_id(&ps_global
->id
);
1043 ps_global
->id
= set_alpine_id(oa2list
->app_id
? oa2list
->app_id
: PACKAGE_NAME
, PACKAGE_VERSION
);
1044 mail_parameters(NULL
, SET_IDPARAMS
, (void *) ps_global
->id
);
1048 * We check if we have a refresh token saved somewhere, if so
1049 * we use it to get a new access token, otherwise we need to
1050 * get an access code so we can get (and save) a refresh token
1051 * and use the access token.
1053 if(trial
== 0L && !altuserforcache
){
1054 /* Search for a refresh token that is already loaded ... */
1055 if(imap_get_passwd_auth(mm_login_list
, &token
, user
,
1056 registered
? hostlist2
: hostlist
,
1057 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
1058 dprint((9, "mm_login_oauth2: found a refresh token\n"));
1059 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1060 ps_global
->in_init_seq
= save_in_init
;
1062 #ifdef LOCAL_PASSWD_CACHE
1063 /* or see if we have saved one in the local password cache and load it */
1064 else if(get_passfile_passwd_auth(ps_global
->pinerc
, &token
,
1065 user
, registered
? hostlist2
: hostlist
,
1066 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
1067 imap_set_passwd_auth(&mm_login_list
, token
, user
,
1068 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
1069 update_passfile_hostlist_auth(ps_global
->pinerc
, user
, hostlist
,
1070 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
);
1071 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
1072 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1073 ps_global
->in_init_seq
= save_in_init
;
1075 if(token
&& *token
) preserve_password
= 1; /* resave it, no need to ask */
1076 #endif /* LOCAL_PASSWD_CACHE */
1078 user
[NETMAXUSER
-1] = '\0';
1080 /* The Old* variables is what c_client knows */
1081 OldRefreshToken
= login
->cancel_refresh_token
? NULL
: login
->param
[OA2_RefreshToken
].value
;
1082 OldAccessToken
= login
->access_token
;
1083 OldExpirationTime
= login
->expiration
;
1085 /* The New* variables is what Alpine knows */
1086 NewRefreshToken
= NewAccessToken
= NULL
;
1087 NewExpirationTime
= 0L;
1088 ChangeAccessToken
= ChangeRefreshToken
= ChangeExpirationTime
= 0;
1090 if(token
&& *token
&& !login
->cancel_refresh_token
){
1094 t
= strchr(s
, PWDAUTHSEP
);
1096 NewRefreshToken
= cpystr(s
);
1099 NewRefreshToken
= cpystr(s
);
1101 t
= strchr(s
, PWDAUTHSEP
);
1103 NewAccessToken
= cpystr(s
);
1106 NewAccessToken
= cpystr(s
);
1108 NewExpirationTime
= strtol(s
, &s
, 10);
1109 if(NewExpirationTime
<= 0 || NewExpirationTime
<= time(0))
1110 NewExpirationTime
= 0L;
1113 /* check we got good information, and send good information below */
1114 if(NewRefreshToken
&& !*NewRefreshToken
)
1115 fs_give((void **) &NewRefreshToken
);
1116 if(NewAccessToken
&& (NewExpirationTime
== 0L || !*NewAccessToken
))
1117 fs_give((void **) &NewAccessToken
);
1120 if(NewRefreshToken
== NULL
)
1121 login
->first_time
++;
1123 if(login
->first_time
){ /* count how many authorization methods we support */
1126 for(nmethods
= 0, oa2
= alpine_oauth2_list
; oa2
&& oa2
->name
; oa2
++){
1127 for(j
= 0; j
< OAUTH2_TOT_EQUIV
1129 && oa2
->host
[j
] != NULL
1130 && strucmp(oa2
->host
[j
], mb
->orighost
) != 0; j
++);
1131 if(oa2
&& oa2
->host
&& j
< OAUTH2_TOT_EQUIV
&& oa2
->host
[j
]
1132 && ((oa2
->server_mthd
[0].name
&& (oa2
->flags
& OA2_AUTHORIZE
))
1133 || (oa2
->server_mthd
[1].name
&& (oa2
->flags
& OA2_DEVICE
))))
1138 oa2list
= oauth2_select_flow(mb
->orighost
);
1140 if(!oa2list
) registered
= 0;
1143 /* Default to saving what we already had saved */
1145 SaveRefreshToken
= login
->cancel_refresh_token
? NULL
: NewRefreshToken
;
1146 SaveAccessToken
= NewAccessToken
;
1147 SaveExpirationTime
= NewExpirationTime
;
1149 /* Translation of the logic below:
1150 * if (c-client has a refresh token
1151 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1152 forget the Alpine refresh token;
1153 make the Alpine Refresh token = c-client refresh token.;
1154 signal that we changed the refresh token;
1155 In this situation we do not need to clear up the Alpine access token. This will
1156 expire, so we can use it until it expires. We can save the c-client refresh token
1157 together with the Alpine Access Token and the expiration date of the Alpine Access
1159 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1160 forget the Alpine refresh token;
1161 if Alpine has an access token, forget it;
1162 reset the expiration time
1163 signal that we changed the refresh token; (because the service expired it)
1167 if(OldRefreshToken
!= NULL
1168 && (NewRefreshToken
== NULL
|| strcmp(OldRefreshToken
, NewRefreshToken
))){
1169 if(NewRefreshToken
) fs_give((void **) &NewRefreshToken
);
1170 NewRefreshToken
= cpystr(OldRefreshToken
);
1171 ChangeRefreshToken
++;
1172 SaveRefreshToken
= OldRefreshToken
;
1173 SaveAccessToken
= NewAccessToken
;
1174 SaveExpirationTime
= NewExpirationTime
;
1175 } else if (OldRefreshToken
== NULL
&& NewRefreshToken
!= NULL
&& trial
> 0){
1176 fs_give((void **) &NewRefreshToken
);
1177 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1178 NewExpirationTime
= 0L;
1179 ChangeRefreshToken
++;
1180 SaveRefreshToken
= NULL
;
1181 SaveAccessToken
= NULL
;
1182 SaveExpirationTime
= 0L;
1185 if(OldAccessToken
!= NULL
1186 && (NewAccessToken
== NULL
|| strcmp(OldAccessToken
, NewAccessToken
))){
1187 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1188 NewAccessToken
= cpystr(OldAccessToken
);
1189 ChangeAccessToken
++;
1190 NewExpirationTime
= OldExpirationTime
;
1191 SaveRefreshToken
= NewRefreshToken
;
1192 SaveAccessToken
= NewAccessToken
;
1193 SaveExpirationTime
= NewExpirationTime
;
1197 login
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1198 login
->access_token
= SaveAccessToken
;
1199 login
->expiration
= SaveExpirationTime
;
1201 oa2list
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1202 oa2list
->access_token
= SaveAccessToken
;
1203 oa2list
->expiration
= SaveExpirationTime
;
1204 oa2list
->first_time
= login
->first_time
;
1205 oa2list
->cancel_refresh_token
= login
->cancel_refresh_token
;
1206 *login
= *oa2list
; /* load login pointer */
1209 if(!ChangeAccessToken
&& !ChangeRefreshToken
&& !login
->cancel_refresh_token
)
1212 /* get ready to save this information. The format will be
1213 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1214 * (spaces added for clarity, \001 is PWDAUTHSEP)
1216 if(token
) fs_give((void **) &token
);
1217 sprintf(tmp
, "%lu", SaveExpirationTime
);
1218 tmp
[sizeof(tmp
) - 1] = '\0';
1219 len
= strlen(SaveRefreshToken
? SaveRefreshToken
: "")
1220 + strlen(SaveAccessToken
? SaveAccessToken
: "")
1222 token
= fs_get(len
+ 1);
1223 sprintf(token
, "%s%c%s%c%lu",
1224 SaveRefreshToken
? SaveRefreshToken
: "", PWDAUTHSEP
,
1225 SaveAccessToken
? SaveAccessToken
: "", PWDAUTHSEP
,
1226 SaveExpirationTime
);
1228 /* remember the access information for next time */
1229 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
1230 imap_set_passwd_auth(&mm_login_list
, token
,
1231 altuserforcache
? altuserforcache
: user
, hostlist
,
1232 (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
1233 #ifdef LOCAL_PASSWD_CACHE
1234 /* if requested, remember it on disk for next session */
1235 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
1236 set_passfile_passwd_auth(ps_global
->pinerc
, token
,
1237 altuserforcache
? altuserforcache
: user
, hostlist
,
1238 (mb
->sslflag
||mb
->tlsflag
),
1239 (preserve_password
== -1 ? 0
1240 : (preserve_password
== 0 ? 2 :1)), OA2NAME
);
1241 #endif /* LOCAL_PASSWD_CACHE */
1243 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1247 set_alpine_id(char *pname
, char *pversion
)
1251 if(!pname
|| !pversion
) return NULL
;
1253 id
= fs_get(sizeof(IDLIST
));
1254 id
->name
= cpystr("name");
1255 id
->value
= cpystr(pname
);
1256 id
->next
= fs_get(sizeof(IDLIST
));
1257 id
->next
->name
= cpystr("version");
1258 id
->next
->value
= cpystr(pversion
);
1259 id
->next
->next
= NULL
;
1263 /*----------------------------------------------------------------------
1264 receive notification from IMAP
1266 Args: stream -- Mail stream message is relevant to
1267 string -- The message text
1268 errflg -- Set if it is a serious error
1270 Result: message displayed in status line
1272 The facility is for general notices, such as connection to server;
1273 server shutting down etc... It is used infrequently.
1274 ----------------------------------------------------------------------*/
1276 mm_notify(MAILSTREAM
*stream
, char *string
, long int errflg
)
1281 now
= time((time_t *)0);
1282 tm_now
= localtime(&now
);
1284 /* be sure to log the message... */
1286 if(ps_global
->debug_imap
|| ps_global
->debugmem
)
1287 dprint((errflg
== TCPDEBUG
|| errflg
== HTTPDEBUG
? 7 : 2,
1288 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1289 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1290 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1291 (!errflg
) ? "babble" :
1292 (errflg
== ERROR
) ? "error" :
1293 (errflg
== WARN
) ? "warning" :
1294 (errflg
== PARSE
) ? "parse" :
1295 (errflg
== TCPDEBUG
) ? "tcp" :
1296 (errflg
== HTTPDEBUG
) ? "http" :
1297 (errflg
== BYE
) ? "bye" : "unknown",
1298 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1299 string
? string
: "?"));
1302 snprintf(ps_global
->last_error
, sizeof(ps_global
->last_error
), "%s : %.*s",
1303 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1304 (int) MIN(MAX_SCREEN_COLS
, sizeof(ps_global
->last_error
)-70),
1306 ps_global
->last_error
[ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1307 : sizeof(ps_global
->last_error
)-1] = '\0';
1310 * Then either set special bits in the pine struct or
1311 * display the message if it's tagged as an "ALERT" or
1312 * its errflg > NIL (i.e., WARN, or ERROR)
1316 * We'd like to sp_mark_stream_dead() here but we can't do that because
1317 * that might call mail_close and we are already in a c-client callback.
1318 * So just set the dead bit and clean it up later.
1320 sp_set_dead_stream(stream
, 1);
1321 else if(!strncmp(string
, "[TRYCREATE]", 11))
1322 ps_global
->try_to_create
= 1;
1323 else if(!strncmp(string
, "[REFERRAL ", 10))
1324 ; /* handled in the imap_referral() callback */
1325 else if(!strncmp(string
, "[ALERT]", 7))
1326 q_status_message2(SM_MODAL
, 3, 3,
1327 _("Alert received while accessing \"%s\": %s"),
1328 (stream
&& stream
->mailbox
)
1329 ? stream
->mailbox
: "-no folder-",
1330 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf
+10000),
1331 SIZEOF_20KBUF
-10000, string
));
1332 else if(!strncmp(string
, "[UNSEEN ", 8)){
1336 for(p
= string
+ 8; isdigit(*p
); p
++)
1337 n
= (n
* 10) + (*p
- '0');
1339 sp_set_first_unseen(stream
, n
);
1341 else if(!strncmp(string
, "[READ-ONLY]", 11)
1342 && !(stream
&& stream
->mailbox
&& IS_NEWS(stream
)))
1343 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, "%s : %s",
1344 (stream
&& stream
->mailbox
)
1345 ? stream
->mailbox
: "-no folder-",
1347 else if((errflg
&& errflg
!= BYE
&& errflg
!= PARSE
)
1348 && !ps_global
->noshow_error
1350 && (ps_global
->noshow_warn
|| (stream
&& stream
->unhealthy
))))
1351 q_status_message(SM_ORDER
| ((errflg
== ERROR
) ? SM_DING
: 0),
1352 3, 6, ps_global
->last_error
);
1356 /*----------------------------------------------------------------------
1357 Queue imap log message for display in the message line
1359 Args: string -- The message
1360 errflg -- flag set to 1 if pertains to an error
1362 Result: Message queued for display
1364 The c-client/imap reports most of it's status and errors here
1367 mm_log(char *string
, long int errflg
)
1369 char message
[sizeof(ps_global
->c_client_error
)];
1371 int was_capitalized
;
1372 static char saw_kerberos_init_warning
;
1376 now
= time((time_t *)0);
1377 tm_now
= localtime(&now
);
1379 dprint((((errflg
== TCPDEBUG
) && ps_global
->debug_tcp
) ? 1 :
1380 (errflg
== TCPDEBUG
) ? 10 :
1381 ((errflg
== HTTPDEBUG
) && ps_global
->debug_http
) ? 1 :
1382 (errflg
== HTTPDEBUG
) ? 10 : 2,
1383 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1384 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1385 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1386 (!errflg
) ? "babble" :
1387 (errflg
== ERROR
) ? "error" :
1388 (errflg
== WARN
) ? "warning" :
1389 (errflg
== PARSE
) ? "parse" :
1390 (errflg
== TCPDEBUG
) ? "tcp" :
1391 (errflg
== HTTPDEBUG
) ? "http" :
1392 (errflg
== BYE
) ? "bye" : "unknown",
1393 string
? string
: "?"));
1395 if(errflg
== ERROR
&& !strncmp(string
, "[TRYCREATE]", 11)){
1396 ps_global
->try_to_create
= 1;
1399 else if(ps_global
->try_to_create
1400 || !strncmp(string
, "[CLOSED]", 8)
1401 || (sp_dead_stream(ps_global
->mail_stream
) && strstr(string
, "No-op")))
1403 * Don't display if creating new folder OR
1404 * warning about a dead stream ...
1408 strncpy(message
, string
, sizeof(message
));
1409 message
[sizeof(message
) - 1] = '\0';
1411 if(errflg
== WARN
&& srchstr(message
, "try running kinit") != NULL
){
1412 if(saw_kerberos_init_warning
)
1415 saw_kerberos_init_warning
= 1;
1418 /*---- replace all "mailbox" with "folder" ------*/
1419 occurence
= srchstr(message
, "mailbox");
1422 || isspace((unsigned char) *(occurence
+7))
1423 || *(occurence
+7) == ':'){
1424 was_capitalized
= isupper((unsigned char) *occurence
);
1425 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 7, (errflg
== PARSE
? "address" : "folder"));
1427 *occurence
= (errflg
== PARSE
? 'A' : 'F');
1432 occurence
= srchstr(occurence
, "mailbox");
1435 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1436 occurence
= srchstr(message
, "GSSAPI");
1439 || isspace((unsigned char) *(occurence
+6))
1440 || *(occurence
+6) == ':')
1441 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 6, "Kerberos");
1445 occurence
= srchstr(occurence
, "GSSAPI");
1449 ps_global
->mm_log_error
= 1;
1451 if(errflg
== PARSE
|| (errflg
== ERROR
&& ps_global
->noshow_error
))
1452 strncpy(ps_global
->c_client_error
, message
,
1453 sizeof(ps_global
->c_client_error
));
1455 if(ps_global
->noshow_error
1456 || (ps_global
->noshow_warn
&& errflg
== WARN
)
1457 || !(errflg
== ERROR
|| errflg
== WARN
))
1458 return; /* Only care about errors; don't print when asked not to */
1460 /*---- Display the message ------*/
1461 q_status_message((errflg
== ERROR
) ? (SM_ORDER
| SM_DING
) : SM_ORDER
,
1463 strncpy(ps_global
->last_error
, message
, sizeof(ps_global
->last_error
));
1464 ps_global
->last_error
[sizeof(ps_global
->last_error
) - 1] = '\0';
1468 mm_login_method_work(NETMBX
*mb
, char *user
, void *login
, long int trial
,
1469 char *method
, char *usethisprompt
, char *altuserforcache
)
1473 if(strucmp(method
, OA2NAME
) == 0 || strucmp(method
, BEARERNAME
) == 0)
1474 mm_login_oauth2(mb
, user
, method
, (OAUTH2_S
*) login
, trial
, usethisprompt
, altuserforcache
);
1478 mm_login_work(NETMBX
*mb
, char *user
, char **pwd
, long int trial
,
1479 char *usethisprompt
, char *altuserforcache
)
1481 char tmp
[MAILTMPLEN
];
1482 char prompt
[1000], *last
;
1483 char port
[20], non_def_port
[20], insecure
[20];
1484 char defuser
[NETMAXUSER
];
1485 char hostleadin
[80], hostname
[200], defubuf
[200];
1486 char logleadin
[80], pwleadin
[50];
1487 char hostlist0
[MAILTMPLEN
], hostlist1
[MAILTMPLEN
];
1488 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1489 that the password will be sent unencrypted over the network. This is
1490 just a warning message that gets added parenthetically when the user
1491 is asked for a password. */
1492 char *insec
= _(" (INSECURE)");
1493 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1494 after having already failed at least once. */
1495 char *retry
= _("Retrying - ");
1496 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1497 char *hostlabel
= _("HOST");
1498 /* TRANSLATORS: user is logging in as a particular user (a particular
1499 login name), this is just labelling that user name. */
1500 char *userlabel
= _("USER");
1501 STRLIST_S hostlist
[2];
1503 int len
, rc
, q_line
, flags
;
1504 int oespace
, avail
, need
, save_dont_use
;
1507 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1508 int preserve_password
= -1;
1511 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1512 trial
, mb
->user
? mb
->user
: "(null)",
1513 mb
->service
? mb
->service
: "(null)",
1514 mb
->port
? " port=" : "",
1515 mb
->port
? comatose(mb
->port
) : "",
1516 altuserforcache
? " altuserforcache =" : "",
1517 altuserforcache
? altuserforcache
: ""));
1518 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
1520 save_in_init
= ps_global
->in_init_seq
;
1521 ps_global
->in_init_seq
= 0;
1522 ps_global
->no_newmail_check_from_optionally_enter
= 1;
1524 /* make sure errors are seen */
1526 flush_status_messages(0);
1528 /* redo app id in case we are logging in to an IMAP server that supports the IMAP ID extension */
1529 free_id(&ps_global
->id
);
1530 ps_global
->id
= set_alpine_id(PACKAGE_NAME
, PACKAGE_VERSION
);
1531 mail_parameters(NULL
, SET_IDPARAMS
, (void *) ps_global
->id
);
1534 * Add port number to hostname if going through a tunnel or something
1536 non_def_port
[0] = '\0';
1537 if(mb
->port
&& mb
->service
&&
1538 (sv
= getservbyname(mb
->service
, "tcp")) &&
1539 (mb
->port
!= ntohs(sv
->s_port
))){
1540 snprintf(non_def_port
, sizeof(non_def_port
), ":%lu", mb
->port
);
1541 non_def_port
[sizeof(non_def_port
)-1] = '\0';
1542 dprint((9, "mm_login: using non-default port=%s\n", non_def_port
));
1546 * set up host list for sybil servers...
1549 strncpy(hostlist0
, mb
->host
, sizeof(hostlist0
)-1);
1550 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1551 strncat(hostlist0
, non_def_port
, sizeof(hostlist0
)-strlen(hostlist0
)-1);
1552 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1553 hostlist
[0].name
= hostlist0
;
1554 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1555 strncpy(hostlist1
, mb
->orighost
, sizeof(hostlist1
)-1);
1556 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1557 strncat(hostlist1
, non_def_port
, sizeof(hostlist1
)-strlen(hostlist1
)-1);
1558 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1559 hostlist
[0].next
= &hostlist
[1];
1560 hostlist
[1].name
= hostlist1
;
1561 hostlist
[1].next
= NULL
;
1564 hostlist
[0].next
= NULL
;
1567 hostlist
[0].name
= mb
->host
;
1568 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1569 hostlist
[0].next
= &hostlist
[1];
1570 hostlist
[1].name
= mb
->orighost
;
1571 hostlist
[1].next
= NULL
;
1574 hostlist
[0].next
= NULL
;
1577 if(hostlist
[0].name
){
1578 dprint((9, "mm_login: host=%s\n",
1579 hostlist
[0].name
? hostlist
[0].name
: "?"));
1580 if(hostlist
[0].next
&& hostlist
[1].name
){
1581 dprint((9, "mm_login: orighost=%s\n", hostlist
[1].name
));
1586 * Initialize user name with either
1587 * 1) /user= value in the stream being logged into,
1588 * or 2) the user name we're running under.
1590 * Note that VAR_USER_ID is not yet initialized if this login is
1591 * the one to access the remote config file. In that case, the user
1592 * can supply the username in the config file name with /user=.
1594 if(trial
== 0L && !altuserforcache
){
1595 strncpy(user
, (*mb
->user
) ? mb
->user
:
1596 ps_global
->VAR_USER_ID
? ps_global
->VAR_USER_ID
: "",
1598 user
[NETMAXUSER
-1] = '\0';
1600 /* try last working password associated with this host. */
1601 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1602 (mb
->sslflag
||mb
->tlsflag
))){
1603 dprint((9, "mm_login: found a password to try\n"));
1604 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1605 ps_global
->in_init_seq
= save_in_init
;
1609 #ifdef LOCAL_PASSWD_CACHE
1610 /* check to see if there's a password left over from last session */
1611 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1612 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1613 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1614 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1615 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1616 (mb
->sslflag
||mb
->tlsflag
));
1617 dprint((9, "mm_login: found a password in passfile to try\n"));
1618 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1619 ps_global
->in_init_seq
= save_in_init
;
1622 #endif /* LOCAL_PASSWD_CACHE */
1625 * If no explicit user name supplied and we've not logged in
1626 * with our local user name, see if we've visited this
1627 * host before as someone else.
1630 ((last
= imap_get_user(mm_login_list
, hostlist
))
1631 #ifdef LOCAL_PASSWD_CACHE
1633 (last
= get_passfile_user(ps_global
->pinerc
, hostlist
))
1634 #endif /* LOCAL_PASSWD_CACHE */
1636 strncpy(user
, last
, NETMAXUSER
);
1637 user
[NETMAXUSER
-1] = '\0';
1638 dprint((9, "mm_login: found user=%s\n",
1639 user
? user
: "?"));
1641 /* try last working password associated with this host/user. */
1642 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1643 (mb
->sslflag
||mb
->tlsflag
))){
1645 "mm_login: found a password for user=%s to try\n",
1646 user
? user
: "?"));
1647 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1648 ps_global
->in_init_seq
= save_in_init
;
1652 #ifdef LOCAL_PASSWD_CACHE
1653 /* check to see if there's a password left over from last session */
1654 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1655 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1656 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1657 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1658 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1659 (mb
->sslflag
||mb
->tlsflag
));
1661 "mm_login: found a password for user=%s in passfile to try\n",
1662 user
? user
: "?"));
1663 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1664 ps_global
->in_init_seq
= save_in_init
;
1667 #endif /* LOCAL_PASSWD_CACHE */
1670 #if !defined(DOS) && !defined(OS2)
1671 if(!*mb
->user
&& !*user
&&
1672 (last
= (ps_global
->ui
.login
&& ps_global
->ui
.login
[0])
1673 ? ps_global
->ui
.login
: NULL
)
1675 strncpy(user
, last
, NETMAXUSER
);
1676 user
[NETMAXUSER
-1] = '\0';
1677 dprint((9, "mm_login: found user=%s\n",
1678 user
? user
: "?"));
1680 /* try last working password associated with this host. */
1681 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1682 (mb
->sslflag
||mb
->tlsflag
))){
1683 dprint((9, "mm_login:ui: found a password to try\n"));
1684 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1685 ps_global
->in_init_seq
= save_in_init
;
1689 #ifdef LOCAL_PASSWD_CACHE
1690 /* check to see if there's a password left over from last session */
1691 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1692 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1693 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1694 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1695 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1696 (mb
->sslflag
||mb
->tlsflag
));
1697 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1698 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1699 ps_global
->in_init_seq
= save_in_init
;
1702 #endif /* LOCAL_PASSWD_CACHE */
1707 user
[NETMAXUSER
-1] = '\0';
1713 * Even if we have a user now, user gets a chance to change it.
1715 ps_global
->mangled_footer
= 1;
1716 if(!*mb
->user
&& !altuserforcache
){
1721 * Instead of offering user with a value that the user can edit,
1722 * we offer [user] as a default so that the user can type CR to
1723 * use it. Otherwise, the user has to type in whole name.
1725 strncpy(defuser
, user
, sizeof(defuser
)-1);
1726 defuser
[sizeof(defuser
)-1] = '\0';
1730 * Need space for "Retrying - "
1736 * about 15 chars for input
1739 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1740 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1741 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1743 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1744 hostname
[sizeof(hostname
)-1] = '\0';
1747 * Add port number to hostname if going through a tunnel or something
1750 strncpy(port
, non_def_port
, sizeof(port
));
1755 /* if not encrypted and SSL/TLS is supported */
1756 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1757 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1758 strncpy(insecure
, insec
, sizeof(insecure
));
1760 /* TRANSLATORS: user is being asked to type in their login name */
1761 snprintf(logleadin
, sizeof(logleadin
), " %s", _("ENTER LOGIN NAME"));
1763 snprintf(defubuf
, sizeof(defubuf
), "%s%s%s : ", (*defuser
) ? " [" : "",
1764 (*defuser
) ? defuser
: "",
1765 (*defuser
) ? "]" : "");
1766 defubuf
[sizeof(defubuf
)-1] = '\0';
1767 /* space reserved after prompt */
1768 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1770 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1771 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1772 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) + oespace
;
1774 /* If we're retrying cut the hostname back to the first word. */
1775 if(avail
< need
&& trial
> 0){
1778 len
= strlen(hostname
);
1779 if((p
= strchr(hostname
, '.')) != NULL
){
1781 need
-= (len
- strlen(hostname
));
1786 need
-= utf8_width(retry
);
1791 /* reduce length of logleadin */
1792 len
= utf8_width(logleadin
);
1793 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1794 longer version doesn't fit on screen */
1795 snprintf(logleadin
, sizeof(logleadin
), " %s", _("LOGIN"));
1796 need
-= (len
- utf8_width(logleadin
));
1799 /* get two spaces from hostleadin */
1800 len
= utf8_width(hostleadin
);
1801 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
1802 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
1803 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1804 need
-= (len
- utf8_width(hostleadin
));
1806 /* get rid of port */
1807 if(avail
< need
&& strlen(port
) > 0){
1808 need
-= strlen(port
);
1816 * Reduce space for hostname. Best we can do is 6 chars
1819 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
1820 len
= strlen(hostname
);
1821 strncpy(hostname
+reduce_to
-3, "...", 4);
1822 need
-= (len
- strlen(hostname
));
1824 if(avail
< need
&& strlen(insecure
) > 0){
1825 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
1827 insecure
[strlen(insecure
)-4] = ')';
1828 insecure
[strlen(insecure
)-3] = '\0';
1831 need
-= utf8_width(insecure
);
1837 if(strlen(defubuf
) > 3){
1838 len
= strlen(defubuf
);
1839 strncpy(defubuf
, " [..] :", 9);
1840 need
-= (len
- strlen(defubuf
));
1844 strncpy(defubuf
, ":", 2);
1847 * If it still doesn't fit, optionally_enter gets
1848 * to worry about it.
1856 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s",
1857 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
);
1858 prompt
[sizeof(prompt
)-1] = '\0';
1862 mm_login_alt_cue(mb
);
1864 flags
= OE_APPEND_CURRENT
;
1865 save_dont_use
= ps_global
->dont_use_init_cmds
;
1866 ps_global
->dont_use_init_cmds
= 1;
1868 if(!*user
&& *defuser
){
1869 strncpy(user
, defuser
, NETMAXUSER
);
1870 user
[NETMAXUSER
-1] = '\0';
1873 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, pwd
, NETMAXPASSWD
,
1874 #ifdef LOCAL_PASSWD_CACHE
1875 is_using_passfile() ? 1 :
1876 #endif /* LOCAL_PASSWD_CACHE */
1877 0, 0, &preserve_password
);
1878 ps_global
->dont_use_init_cmds
= save_dont_use
;
1879 if(rc
== 0 && *user
&& *pwd
)
1881 #else /* !_WINDOWS */
1882 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
1883 prompt
, NULL
, help
, &flags
);
1884 #endif /* !_WINDOWS */
1885 ps_global
->dont_use_init_cmds
= save_dont_use
;
1888 help
= help
== NO_HELP
? h_oe_login
: NO_HELP
;
1893 if(rc
== 0 && !*user
){
1894 strncpy(user
, defuser
, NETMAXUSER
);
1895 user
[NETMAXUSER
-1] = '\0';
1902 if(rc
== 1 || !user
[0]) {
1903 ps_global
->user_says_cancel
= (rc
== 1);
1908 strncpy(user
, mb
->user
, NETMAXUSER
);
1909 user
[NETMAXUSER
-1] = '\0';
1912 user
[NETMAXUSER
-1] = '\0';
1914 if(!(user
[0] || altuserforcache
)){
1915 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1916 ps_global
->in_init_seq
= save_in_init
;
1921 * Now that we have a user, we can check in the cache again to see
1922 * if there is a password there. Try last working password associated
1923 * with this host and user.
1925 if(trial
== 0L && !*mb
->user
&& !altuserforcache
){
1926 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1927 (mb
->sslflag
||mb
->tlsflag
))){
1928 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1929 ps_global
->in_init_seq
= save_in_init
;
1933 #ifdef LOCAL_PASSWD_CACHE
1934 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1935 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1936 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1937 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1938 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1939 ps_global
->in_init_seq
= save_in_init
;
1942 #endif /* LOCAL_PASSWD_CACHE */
1944 else if(trial
== 0 && altuserforcache
){
1945 if(imap_get_passwd(mm_login_list
, pwd
, altuserforcache
, hostlist
,
1946 (mb
->sslflag
||mb
->tlsflag
))){
1947 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1948 ps_global
->in_init_seq
= save_in_init
;
1952 #ifdef LOCAL_PASSWD_CACHE
1953 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1954 altuserforcache
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1955 imap_set_passwd(&mm_login_list
, *pwd
, altuserforcache
,
1956 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1957 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1958 ps_global
->in_init_seq
= save_in_init
;
1961 #endif /* LOCAL_PASSWD_CACHE */
1965 * Didn't find password in cache or this isn't the first try. Ask user.
1970 * Need space for "Retrying - "
1976 * " ENTER PASSWORD: "
1977 * about 15 chars for input
1980 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1981 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1983 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1984 hostname
[sizeof(hostname
)-1] = '\0';
1987 * Add port number to hostname if going through a tunnel or something
1990 strncpy(port
, non_def_port
, sizeof(port
));
1996 /* if not encrypted and SSL/TLS is supported */
1997 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1998 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1999 strncpy(insecure
, insec
, sizeof(insecure
));
2002 strncpy(logleadin
, usethisprompt
, sizeof(logleadin
));
2003 logleadin
[sizeof(logleadin
)-1] = '\0';
2008 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
2010 strncpy(defubuf
, user
, sizeof(defubuf
)-1);
2011 defubuf
[sizeof(defubuf
)-1] = '\0';
2014 /* TRANSLATORS: user is being asked to type in their password */
2015 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("ENTER PASSWORD"));
2017 /* space reserved after prompt */
2018 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
2020 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
2021 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
2022 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) +
2023 utf8_width(pwleadin
) + oespace
;
2025 if(avail
< need
&& trial
> 0){
2028 len
= strlen(hostname
);
2029 if((p
= strchr(hostname
, '.')) != NULL
){
2031 need
-= (len
- strlen(hostname
));
2036 need
-= utf8_width(retry
);
2042 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
2046 rplstr(pwleadin
, sizeof(pwleadin
), 1, "");
2050 /* get two spaces from hostleadin */
2051 len
= utf8_width(hostleadin
);
2052 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
2053 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
2054 hostleadin
[sizeof(hostleadin
)-1] = '\0';
2055 need
-= (len
- utf8_width(hostleadin
));
2057 /* get rid of port */
2058 if(avail
< need
&& strlen(port
) > 0){
2059 need
-= strlen(port
);
2064 len
= utf8_width(pwleadin
);
2065 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
2066 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("PASSWORD"));
2067 need
-= (len
- utf8_width(pwleadin
));
2075 * Reduce space for hostname. Best we can do is 6 chars
2078 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
2079 len
= strlen(hostname
);
2080 strncpy(hostname
+reduce_to
-3, "...", 4);
2081 need
-= (len
- strlen(hostname
));
2083 if(avail
< need
&& strlen(insecure
) > 0){
2084 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
2086 insecure
[strlen(insecure
)-4] = ')';
2087 insecure
[strlen(insecure
)-3] = '\0';
2090 need
-= utf8_width(insecure
);
2096 len
= utf8_width(logleadin
);
2097 strncpy(logleadin
, " ", sizeof(logleadin
));
2098 logleadin
[sizeof(logleadin
)-1] = '\0';
2099 need
-= (len
- utf8_width(logleadin
));
2102 reduce_to
= (need
- avail
< strlen(defubuf
) - 6) ? (strlen(defubuf
)-(need
-avail
)) : 0;
2104 strncpy(defubuf
+reduce_to
-3, "...", 4);
2113 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s%s",
2114 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
, pwleadin
);
2115 prompt
[sizeof(prompt
)-1] = '\0';
2120 mm_login_alt_cue(mb
);
2122 save_dont_use
= ps_global
->dont_use_init_cmds
;
2123 ps_global
->dont_use_init_cmds
= 1;
2124 flags
= F_ON(F_QUELL_ASTERISKS
, ps_global
) ? OE_PASSWD_NOAST
: OE_PASSWD
;
2125 flags
|= OE_KEEP_TRAILING_SPACE
;
2129 tmpp
= fs_get(NETMAXPASSWD
*sizeof(char));
2130 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, &tmpp
, NETMAXPASSWD
, 0, 1,
2131 &preserve_password
);
2132 strncpy(tmp
, tmpp
, sizeof(tmp
));
2133 tmp
[sizeof(tmp
)-1] = '\0';
2134 if(tmpp
) fs_give((void **)&tmpp
);
2136 #else /* !_WINDOWS */
2137 rc
= optionally_enter(tmp
, q_line
, 0, NETMAXPASSWD
,
2138 prompt
, NULL
, help
, &flags
);
2139 #endif /* !_WINDOWS */
2140 if(rc
!= 1) *pwd
= cpystr(tmp
);
2141 ps_global
->dont_use_init_cmds
= save_dont_use
;
2144 help
= help
== NO_HELP
? h_oe_passwd
: NO_HELP
;
2152 if(rc
== 1 || !tmp
[0]) {
2153 ps_global
->user_says_cancel
= (rc
== 1);
2155 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2156 ps_global
->in_init_seq
= save_in_init
;
2163 /* remember the password for next time */
2164 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
2165 imap_set_passwd(&mm_login_list
, *pwd
,
2166 altuserforcache
? altuserforcache
: user
, hostlist
,
2167 (mb
->sslflag
||mb
->tlsflag
), 0, 0);
2168 #ifdef LOCAL_PASSWD_CACHE
2169 /* if requested, remember it on disk for next session */
2170 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
2171 set_passfile_passwd(ps_global
->pinerc
, *pwd
,
2172 altuserforcache
? altuserforcache
: user
, hostlist
,
2173 (mb
->sslflag
||mb
->tlsflag
),
2174 (preserve_password
== -1 ? 0
2175 : (preserve_password
== 0 ? 2 :1)));
2176 #endif /* LOCAL_PASSWD_CACHE */
2178 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2183 mm_login_alt_cue(NETMBX
*mb
)
2185 if(ps_global
->ttyo
){
2188 lastc
= pico_set_colors(ps_global
->VAR_TITLE_FORE_COLOR
,
2189 ps_global
->VAR_TITLE_BACK_COLOR
,
2192 mark_titlebar_dirty();
2193 PutLine0(0, ps_global
->ttyo
->screen_cols
- 1,
2194 (mb
->sslflag
||mb
->tlsflag
) ? "+" : " ");
2197 (void)pico_set_colorp(lastc
, PSC_NONE
);
2198 free_color_pair(&lastc
);
2206 /*----------------------------------------------------------------------
2207 Receive notification of an error writing to disk
2209 Args: stream -- The stream the error occurred on
2210 errcode -- The system error code (errno)
2211 serious -- Flag indicating error is serious (mail may be lost)
2213 Result: If error is non serious, the stream is marked as having an error
2214 and deletes are disallowed until error clears
2215 If error is serious this goes modal, allowing the user to retry
2216 or get a shell escape to fix the condition. When the condition is
2217 serious it means that mail existing in the mailbox will be lost
2218 if Pine exits without writing, so we try to induce the user to
2219 fix the error, go get someone that can fix the error, or whatever
2220 and don't provide an easy way out.
2223 mm_diskerror (MAILSTREAM
*stream
, long int errcode
, long int serious
)
2227 static ESCKEY_S de_opts
[] = {
2228 {'r', 'r', "R", "Retry"},
2229 {'f', 'f', "F", "FileBrowser"},
2230 {'s', 's', "S", "ShellPrompt"},
2233 #define DE_COLS (ps_global->ttyo->screen_cols)
2234 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2236 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2238 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2239 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2241 "The reported error number is %s. The last reported mail error was:"
2242 static char *de_msg
[] = {
2243 "Please try to correct the error preventing Alpine from saving your",
2244 "mail folder. For example if the disk is out of space try removing",
2245 "unneeded files. You might also contact your system administrator.",
2247 "Both Alpine's File Browser and an option to enter the system's",
2248 "command prompt are offered to aid in fixing the problem. When",
2249 "you believe the problem is resolved, choose the \"Retry\" option.",
2250 "Be aware that messages may be lost or this folder left in an",
2251 "inaccessible condition if you exit or kill Alpine before the problem",
2254 static char *de_shell_msg
[] = {
2255 "\n\nPlease attempt to correct the error preventing saving of the",
2256 "mail folder. If you do not know how to correct the problem, contact",
2257 "your system administrator. To return to Alpine, type \"exit\".",
2261 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2262 DE_FOLDER(stream
), errcode
, serious
? "" : "not "));
2263 dprint((0, "***** message: \"%s\"\n\n",
2264 ps_global
->last_error
? ps_global
->last_error
: "?"));
2267 sp_set_io_error_on_stream(stream
, 1);
2272 /* replace pine's body display with screen full of explanatory text */
2274 PutLine1(2, MAX((DE_COLS
- sizeof(DE_STR1
)
2275 - strlen(DE_FOLDER(stream
)))/2, 0),
2276 DE_STR1
, DE_FOLDER(stream
));
2278 PutLine1(3, 4, DE_STR2
, long2string(errcode
));
2280 PutLine0(4, 0, " \"");
2281 removing_leading_white_space(ps_global
->last_error
);
2282 for(i
= 4, p
= ps_global
->last_error
; *p
&& i
< DE_LINE
; ){
2283 for(s
= NULL
, q
= p
; *q
&& q
- p
< DE_COLS
- 16; q
++)
2284 if(isspace((unsigned char)*q
))
2295 PutLine0(i
, 0, " ");
2296 while(*p
&& isspace((unsigned char)*p
))
2307 for(j
= ++i
; i
< DE_LINE
&& de_msg
[i
-j
]; i
++){
2309 PutLine0(i
, 0, " ");
2310 Write_to_screen(de_msg
[i
-j
]);
2316 switch(radio_buttons(DE_PMT
, -FOOTER_ROWS(ps_global
), de_opts
,
2317 'r', 0, NO_HELP
, RB_FLUSH_IN
| RB_NO_NEWMAIL
)){
2318 case 'r' : /* Retry! */
2319 ps_global
->mangled_screen
= 1;
2322 case 'f' : /* File Browser */
2324 char full_filename
[MAXPATH
+1], filename
[MAXPATH
+1];
2327 build_path(full_filename
, ps_global
->home_dir
, filename
,
2328 sizeof(full_filename
));
2329 file_lister("DISK ERROR", full_filename
, sizeof(full_filename
),
2330 filename
, sizeof(filename
), FALSE
, FB_SAVE
);
2337 end_keyboard(ps_global
? F_ON(F_USE_FK
,ps_global
) : 0);
2338 end_tty_driver(ps_global
);
2339 for(i
= 0; de_shell_msg
[i
]; i
++)
2340 puts(de_shell_msg
[i
]);
2343 * Don't use our piping mechanism to spawn a subshell here
2344 * since it will the server (thus reentering c-client).
2351 init_tty_driver(ps_global
);
2352 init_keyboard(F_ON(F_USE_FK
,ps_global
));
2356 if(ps_global
->redrawer
)
2357 (*ps_global
->redrawer
)();
2363 pine_tcptimeout_noscreen(long int elapsed
, long int sincelast
, char *host
)
2372 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2373 snprintf(pmt
, sizeof(pmt
),
2374 _("No reply in %s seconds from server %s. Break connection"),
2375 long2string(elapsed
), host
);
2376 pmt
[sizeof(pmt
)-1] = '\0';
2377 if(want_to(pmt
, 'n', 'n', NO_HELP
, WT_FLUSH_IN
) == 'y'){
2378 ps_global
->user_says_cancel
= 1;
2383 ps_global
->tcptimeout
= 0;
2389 * -------------------------------------------------------------
2390 * These are declared in pith/imap.h as mandatory to implement.
2391 * -------------------------------------------------------------
2396 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2399 pine_tcptimeout(long int elapsed
, long int sincelast
, char *host
)
2401 long rv
= 1L; /* keep trying by default */
2404 ps_global
->tcptimeout
= 1;
2406 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2407 long2string(elapsed
), host
));
2416 if(ps_global
->noshow_timeout
)
2419 if(ps_global
->can_interrupt
2420 && ps_global
->close_connection_timeout
> 0L
2421 && elapsed
>= (long)ps_global
->tcp_query_timeout
2422 && elapsed
>= (long)ps_global
->close_connection_timeout
){
2423 ps_global
->can_interrupt
= 0; /* do not return here */
2424 ps_global
->read_bail
= 0;
2425 ps_global
->user_says_cancel
= 1;
2429 if(!ps_global
->ttyo
)
2430 return(pine_tcptimeout_noscreen(elapsed
, sincelast
, host
));
2435 * Prompt after a minute (since by then things are probably really bad)
2436 * A prompt timeout means "keep trying"...
2438 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2441 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2442 if((clear_inverse
= !InverseState()) != 0)
2447 PutLine2(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
), 0,
2448 _("No reply in %s seconds from server %s. Break connection?"),
2449 long2string(elapsed
), host
);
2454 if(ps_global
->read_bail
|| ch
== 'y' || ch
== 'Y'){
2455 ps_global
->read_bail
= 0;
2456 ps_global
->user_says_cancel
= 1;
2463 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2466 if(rv
== 1L){ /* just warn 'em something's up */
2467 q_status_message2(SM_ORDER
, 0, 0,
2468 _("No reply in %s seconds from server %s. Still Waiting..."),
2469 long2string(elapsed
), host
);
2470 flush_status_messages(0); /* make sure it's seen */
2473 mark_status_dirty(); /* make sure it gets cleared */
2475 resume_busy_cue((rv
== 1) ? 3 : 0);
2476 ps_global
->tcptimeout
= 0;
2481 QUOTALIST
*pine_quotalist_copy (QUOTALIST
*pquota
)
2483 QUOTALIST
*cquota
= NULL
;
2486 cquota
= mail_newquotalist();
2487 if (pquota
->name
&& *pquota
->name
)
2488 cquota
->name
= cpystr(pquota
->name
);
2489 cquota
->usage
= pquota
->usage
;
2490 cquota
->limit
= pquota
->limit
;
2492 cquota
->next
= pine_quotalist_copy(pquota
->next
);
2498 /* c-client callback to handle quota */
2501 pine_parse_quota (MAILSTREAM
*stream
, unsigned char *msg
, QUOTALIST
*pquota
)
2503 ps_global
->quota
= pine_quotalist_copy (pquota
);
2507 * C-client callback to handle SSL/TLS certificate validation failures
2509 * Returning 0 means error becomes fatal
2510 * Non-zero means certificate problem is ignored and SSL session is
2513 * We remember the answer and won't re-ask for subsequent open attempts to
2514 * the same hostname.
2517 pine_sslcertquery(char *reason
, char *host
, char *cert
)
2520 char *unknown
= "<unknown>";
2523 int ok_novalidate
= 0, warned
= 0;
2525 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2526 host
? host
: "?", reason
? reason
: "?",
2527 cert
? cert
: "?"));
2529 hostlist
.name
= host
? host
: "";
2530 hostlist
.next
= NULL
;
2533 * See if we've been asked about this host before.
2535 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2536 /* we were asked before, did we say Yes? */
2542 "sslcertificatequery: approved automatically\n"));
2546 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2549 if(ps_global
->ttyo
){
2551 STORE_S
*in_store
, *out_store
;
2553 HANDLE_S
*handles
= NULL
;
2554 int the_answer
= 'n';
2556 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
2557 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2560 so_puts(in_store
, "<HTML><P>");
2561 so_puts(in_store
, _("There was a failure validating the SSL/TLS certificate for the server"));
2563 so_puts(in_store
, "<P><CENTER>");
2564 so_puts(in_store
, host
? host
: unknown
);
2565 so_puts(in_store
, "</CENTER>");
2567 so_puts(in_store
, "<P>");
2568 so_puts(in_store
, _("The reason for the failure was"));
2570 /* squirrel away details */
2572 fs_give((void **)&details_host
);
2574 fs_give((void **)&details_reason
);
2576 fs_give((void **)&details_cert
);
2578 details_host
= cpystr(host
? host
: unknown
);
2579 details_reason
= cpystr(reason
? reason
: unknown
);
2580 details_cert
= cpystr(cert
? cert
: unknown
);
2582 so_puts(in_store
, "<P><CENTER>");
2583 snprintf(tmp
, sizeof(tmp
), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2584 reason
? reason
: unknown
);
2585 tmp
[sizeof(tmp
)-1] = '\0';
2587 so_puts(in_store
, tmp
);
2588 so_puts(in_store
, "</CENTER>");
2590 so_puts(in_store
, "<P>");
2591 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."));
2593 so_puts(in_store
, "<P>");
2594 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"));
2596 so_puts(in_store
, "<P><CENTER>");
2597 so_puts(in_store
, "/novalidate-cert");
2598 so_puts(in_store
, "</CENTER>");
2600 so_puts(in_store
, "<P>");
2601 so_puts(in_store
, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2603 so_puts(in_store
, "<P><CENTER>");
2604 so_puts(in_store
, host
? host
: unknown
);
2605 so_puts(in_store
, "</CENTER>");
2607 so_puts(in_store
, "<P>");
2608 so_puts(in_store
, _("in your configuration, replace those characters with"));
2610 so_puts(in_store
, "<P><CENTER>");
2611 so_puts(in_store
, host
? host
: unknown
);
2612 so_puts(in_store
, "/novalidate-cert");
2613 so_puts(in_store
, "</CENTER>");
2615 so_puts(in_store
, "<P>");
2616 so_puts(in_store
, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2618 so_seek(in_store
, 0L, 0);
2619 init_handles(&handles
);
2621 gf_link_filter(gf_html2plain
,
2622 gf_html2plain_opt(NULL
,
2623 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
2624 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
2625 gf_set_so_readc(&gc
, in_store
);
2626 gf_set_so_writec(&pc
, out_store
);
2628 gf_clear_so_writec(out_store
);
2629 gf_clear_so_readc(in_store
);
2631 memset(&sargs
, 0, sizeof(SCROLL_S
));
2632 sargs
.text
.handles
= handles
;
2633 sargs
.text
.text
= so_text(out_store
);
2634 sargs
.text
.src
= CharStar
;
2635 sargs
.text
.desc
= _("help text");
2636 sargs
.bar
.title
= _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2637 sargs
.proc
.tool
= answer_cert_failure
;
2638 sargs
.proc
.data
.p
= (void *)&the_answer
;
2639 sargs
.keys
.menu
= &ans_certquery_keymenu
;
2640 /* don't want to re-enter c-client */
2641 sargs
.quell_newmail
= 1;
2642 setbitmap(sargs
.keys
.bitmap
);
2643 sargs
.help
.text
= h_tls_validation_failure
;
2644 sargs
.help
.title
= _("HELP FOR CERT VALIDATION FAILURE");
2648 if(the_answer
== 'y')
2650 else if(the_answer
== 'n')
2651 ps_global
->user_says_cancel
= 1;
2653 ps_global
->mangled_screen
= 1;
2654 ps_global
->painted_body_on_startup
= 0;
2655 ps_global
->painted_footer_on_startup
= 0;
2657 so_give(&out_store
);
2658 free_handles(&handles
);
2660 fs_give((void **)&details_host
);
2662 fs_give((void **)&details_reason
);
2664 fs_give((void **)&details_cert
);
2668 * If screen hasn't been initialized yet, use want_to.
2671 memset((void *)tmp
, 0, sizeof(tmp
));
2673 reason
? reason
: _("SSL/TLS certificate validation failure"),
2675 tmp
[sizeof(tmp
)-1] = '\0';
2676 strncat(tmp
, _(": Continue anyway "), sizeof(tmp
)-strlen(tmp
)-1);
2678 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y')
2683 q_status_message1(SM_ORDER
, 1, 3, _("Open of %s cancelled"),
2684 host
? host
: unknown
);
2686 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, rv
? 1 : 0, 0);
2688 dprint((5, "sslcertificatequery: %s\n",
2689 rv
? "approved" : "rejected"));
2696 pine_newsrcquery(MAILSTREAM
*stream
, char *mulname
, char *name
)
2698 char buf
[MAILTMPLEN
];
2700 if((can_access(mulname
, ACCESS_EXISTS
) == 0)
2701 || !(can_access(name
, ACCESS_EXISTS
) == 0))
2704 snprintf(buf
, sizeof(buf
),
2705 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2707 strlen(last_cmpnt(name
)) > 15 ? "..." : "");
2708 buf
[sizeof(buf
)-1] = '\0';
2709 if(want_to(buf
, 'n', 'n', NO_HELP
, WT_NORM
) == 'y')
2710 rename_file(name
, mulname
);
2716 url_local_certdetails(char *url
)
2718 if(!struncmp(url
, "x-alpine-cert:", 14)){
2723 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
2724 q_status_message(SM_ORDER
| SM_DING
, 7, 10,
2725 _("Error allocating space for details."));
2729 so_puts(store
, _("Host given by user:\n\n "));
2730 so_puts(store
, details_host
);
2731 so_puts(store
, _("\n\nReason for failure:\n\n "));
2732 so_puts(store
, details_reason
);
2733 so_puts(store
, _("\n\nCertificate being verified:\n\n"));
2734 folded
= fold(details_cert
, ps_global
->ttyo
->screen_cols
, ps_global
->ttyo
->screen_cols
, " ", " ", FLD_NONE
);
2735 so_puts(store
, folded
);
2736 fs_give((void **)&folded
);
2737 so_puts(store
, "\n");
2739 memset(&sargs
, 0, sizeof(SCROLL_S
));
2740 sargs
.text
.text
= so_text(store
);
2741 sargs
.text
.src
= CharStar
;
2742 sargs
.text
.desc
= _("Details");
2743 sargs
.bar
.title
= _("CERT VALIDATION DETAILS");
2744 sargs
.help
.text
= NO_HELP
;
2745 sargs
.help
.title
= NULL
;
2746 sargs
.quell_newmail
= 1;
2747 sargs
.help
.text
= h_tls_failure_details
;
2748 sargs
.help
.title
= _("HELP FOR CERT VALIDATION DETAILS");
2752 so_give(&store
); /* free resources associated with store */
2753 ps_global
->mangled_screen
= 1;
2762 * C-client callback to handle SSL/TLS certificate validation failures
2765 pine_sslfailure(char *host
, char *reason
, long unsigned int flags
)
2769 int the_answer
= 'n', indent
, len
, cols
;
2770 char buf
[500], buf2
[500];
2772 char *hst
= host
? host
: "<unknown>";
2773 char *rsn
= reason
? reason
: "<unknown>";
2774 char *notls
= "/notls";
2776 int ok_novalidate
= 0, warned
= 0;
2779 dprint((1, "sslfailure: host=%s reason=%s\n",
2783 if(flags
& NET_SILENT
)
2786 hostlist
.name
= host
? host
: "";
2787 hostlist
.next
= NULL
;
2790 * See if we've been told about this host before.
2792 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2793 /* we were told already */
2795 snprintf(buf
, sizeof(buf
), _("SSL/TLS failure for %s: %s"), hst
, rsn
);
2796 buf
[sizeof(buf
)-1] = '\0';
2802 cols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
2805 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2808 strncpy(buf
, _("There was an SSL/TLS failure for the server"), sizeof(buf
));
2809 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2810 so_puts(store
, folded
);
2811 fs_give((void **)&folded
);
2812 so_puts(store
, "\n");
2814 if((len
=strlen(hst
)) <= cols
){
2815 if((indent
=((cols
-len
)/2)) > 0)
2816 so_puts(store
, repeat_char(indent
, SPACE
));
2818 so_puts(store
, hst
);
2819 so_puts(store
, "\n");
2822 strncpy(buf
, hst
, sizeof(buf
));
2823 buf
[sizeof(buf
)-1] = '\0';
2824 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2825 so_puts(store
, folded
);
2826 fs_give((void **)&folded
);
2829 so_puts(store
, "\n");
2831 strncpy(buf
, _("The reason for the failure was"), sizeof(buf
));
2832 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2833 so_puts(store
, folded
);
2834 fs_give((void **)&folded
);
2835 so_puts(store
, "\n");
2837 if((len
=strlen(rsn
)) <= cols
){
2838 if((indent
=((cols
-len
)/2)) > 0)
2839 so_puts(store
, repeat_char(indent
, SPACE
));
2841 so_puts(store
, rsn
);
2842 so_puts(store
, "\n");
2845 strncpy(buf
, rsn
, sizeof(buf
));
2846 buf
[sizeof(buf
)-1] = '\0';
2847 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2848 so_puts(store
, folded
);
2849 fs_give((void **)&folded
);
2852 so_puts(store
, "\n");
2854 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
));
2855 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2856 so_puts(store
, folded
);
2857 fs_give((void **)&folded
);
2858 so_puts(store
, "\n");
2860 if((len
=strlen(notls
)) <= cols
){
2861 if((indent
=((cols
-len
)/2)) > 0)
2862 so_puts(store
, repeat_char(indent
, SPACE
));
2864 so_puts(store
, notls
);
2865 so_puts(store
, "\n");
2868 strncpy(buf
, notls
, sizeof(buf
));
2869 buf
[sizeof(buf
)-1] = '\0';
2870 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2871 so_puts(store
, folded
);
2872 fs_give((void **)&folded
);
2875 so_puts(store
, "\n");
2877 strncpy(buf
, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2879 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2880 so_puts(store
, folded
);
2881 fs_give((void **)&folded
);
2882 so_puts(store
, "\n");
2884 if((len
=strlen(hst
)) <= cols
){
2885 if((indent
=((cols
-len
)/2)) > 0)
2886 so_puts(store
, repeat_char(indent
, SPACE
));
2888 so_puts(store
, hst
);
2889 so_puts(store
, "\n");
2892 strncpy(buf
, hst
, sizeof(buf
));
2893 buf
[sizeof(buf
)-1] = '\0';
2894 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2895 so_puts(store
, folded
);
2896 fs_give((void **)&folded
);
2899 so_puts(store
, "\n");
2901 strncpy(buf
, _("in your configuration, replace those characters with"),
2903 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2904 so_puts(store
, folded
);
2905 fs_give((void **)&folded
);
2906 so_puts(store
, "\n");
2908 snprintf(buf2
, sizeof(buf2
), "%s%s", hst
, notls
);
2909 buf2
[sizeof(buf2
)-1] = '\0';
2910 if((len
=strlen(buf2
)) <= cols
){
2911 if((indent
=((cols
-len
)/2)) > 0)
2912 so_puts(store
, repeat_char(indent
, SPACE
));
2914 so_puts(store
, buf2
);
2915 so_puts(store
, "\n");
2918 strncpy(buf
, buf2
, sizeof(buf
));
2919 buf
[sizeof(buf
)-1] = '\0';
2920 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2921 so_puts(store
, folded
);
2922 fs_give((void **)&folded
);
2925 so_puts(store
, "\n");
2927 if(ps_global
->ttyo
){
2928 strncpy(buf
, _("Type RETURN to continue."), sizeof(buf
));
2929 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2930 so_puts(store
, folded
);
2931 fs_give((void **)&folded
);
2934 memset(&sargs
, 0, sizeof(SCROLL_S
));
2935 sargs
.text
.text
= so_text(store
);
2936 sargs
.text
.src
= CharStar
;
2937 sargs
.text
.desc
= _("help text");
2938 sargs
.bar
.title
= _("SSL/TLS FAILURE");
2939 sargs
.proc
.tool
= answer_cert_failure
;
2940 sargs
.proc
.data
.p
= (void *)&the_answer
;
2941 sargs
.keys
.menu
= &ans_certfail_keymenu
;
2942 setbitmap(sargs
.keys
.bitmap
);
2943 /* don't want to re-enter c-client */
2944 sargs
.quell_newmail
= 1;
2945 sargs
.help
.text
= h_tls_failure
;
2946 sargs
.help
.title
= _("HELP FOR TLS/SSL FAILURE");
2957 * The screen isn't initialized yet, which should mean that this
2958 * is the result of a -p argument. Display_args_err knows how to deal
2959 * with the uninitialized screen, so we mess with the data to get it
2960 * in shape for display_args_err. This is pretty hacky.
2963 so_seek(store
, 0L, 0); /* rewind */
2964 /* count the lines */
2965 while(so_readc(&c
, store
))
2969 qp
= q
= (char **)fs_get((cnt
+1) * sizeof(char *));
2970 memset(q
, 0, (cnt
+1) * sizeof(char *));
2972 so_seek(store
, 0L, 0); /* rewind */
2974 while(so_readc(&c
, store
)){
2977 *qp
++ = cpystr(buf
);
2984 display_args_err(NULL
, q
, 0);
2985 free_list_array(&q
);
2988 ps_global
->mangled_screen
= 1;
2989 ps_global
->painted_body_on_startup
= 0;
2990 ps_global
->painted_footer_on_startup
= 0;
2993 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, ok_novalidate
, 1);
2998 answer_cert_failure(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
3002 ps_global
->next_screen
= SCREEN_FUN_NULL
;
3006 *(int *)(sparms
->proc
.data
.p
) = 'y';
3010 *(int *)(sparms
->proc
.data
.p
) = 'n';
3014 alpine_panic("Unexpected command in answer_cert_failure");
3023 oauth2_auth_answer(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
3028 /* TRANSLATORS: user needs to input an access code from the server */
3029 char *accesscodelabel
= _("Copy and Paste Access Code");
3030 char token
[MAILTMPLEN
], prompt
[MAILTMPLEN
];
3032 ps_global
->next_screen
= SCREEN_FUN_NULL
;
3037 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
3038 flags
= OE_APPEND_CURRENT
;
3039 sprintf(prompt
, "%s: ", accesscodelabel
);
3041 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
3042 prompt
, NULL
, NO_HELP
, &flags
);
3043 } while (rc
!= 0 && rc
!= 1);
3044 user
.code
= rc
== 0 ? cpystr(token
) : NULL
;
3046 rv
= rc
== 1 ? 0 : 1;
3055 alpine_panic("Unexpected command in oauth2_auth_answer");
3058 *(AUTH_CODE_S
*) sparms
->proc
.data
.p
= user
;
3063 /*----------------------------------------------------------------------
3064 This can be used to prevent the flickering of the check_cue char
3065 caused by numerous (5000+) fetches by c-client. Right now, the only
3066 practical use found is newsgroup subsciption.
3068 check_cue_display will check if this global is set, and won't clear
3069 the check_cue_char if set.
3072 set_read_predicted(int i
)
3074 ps_global
->read_predicted
= i
==1;
3076 if(!i
&& F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3077 check_cue_display(" ");
3082 /*----------------------------------------------------------------------
3083 Exported method to retrieve logged in user name associated with stream
3085 Args: host -- host to find associated login name with.
3090 pine_block_notify(int reason
, void *data
)
3093 case BLOCK_SENSITIVE
: /* sensitive code, disallow alarms */
3096 case BLOCK_NONSENSITIVE
: /* non-sensitive code, allow alarms */
3099 case BLOCK_TCPWRITE
: /* blocked on TCP write */
3100 case BLOCK_FILELOCK
: /* blocked on file locking */
3102 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3103 check_cue_display(">");
3105 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3109 case BLOCK_DNSLOOKUP
: /* blocked on DNS lookup */
3110 case BLOCK_TCPOPEN
: /* blocked on TCP open */
3111 case BLOCK_TCPREAD
: /* blocked on TCP read */
3112 case BLOCK_TCPCLOSE
: /* blocked on TCP close */
3114 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3115 check_cue_display("<");
3117 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3122 case BLOCK_NONE
: /* not blocked */
3124 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3125 check_cue_display(" ");
3136 mm_expunged_current(long unsigned int rawno
)
3138 /* expunged something we're viewing? */
3139 if(!ps_global
->expunge_in_progress
3140 && (mn_is_cur(ps_global
->msgmap
, mn_raw2m(ps_global
->msgmap
, (long) rawno
))
3141 && (ps_global
->prev_screen
== mail_view_screen
3142 || ps_global
->prev_screen
== attachment_screen
))){
3143 ps_global
->next_screen
= mail_index_screen
;
3144 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3145 "Message you were viewing is gone!");
3153 * Specific functions to support caching username/passwd/host
3154 * triples on disk for use from one session to the next...
3157 #define FIRSTCH 0x20
3159 #define TABSZ (LASTCH - FIRSTCH + 1)
3161 static int xlate_key
;
3165 * xlate_in() - xlate_in the given character
3173 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3174 eti
+= (c
- FIRSTCH
);
3175 eti
-= (eti
>= 2*TABSZ
) ? 2*TABSZ
: (eti
>= TABSZ
) ? TABSZ
: 0;
3176 return((xlate_key
= eti
) + FIRSTCH
);
3184 * xlate_out() - xlate_out the given character
3192 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3193 xch
= c
- (dti
= xlate_key
);
3194 xch
+= (xch
< FIRSTCH
-TABSZ
) ? 2*TABSZ
: (xch
< FIRSTCH
) ? TABSZ
: 0;
3195 dti
= (xch
- FIRSTCH
) + dti
;
3196 dti
-= (dti
>= 2*TABSZ
) ? 2*TABSZ
: (dti
>= TABSZ
) ? TABSZ
: 0;
3203 #endif /* PASSFILE */
3206 #ifdef LOCAL_PASSWD_CACHE
3210 line_get(char *tmp
, size_t len
, char **textp
)
3216 || (s
= strchr(*textp
, '\n')) == NULL
3217 || (s
- *textp
) > len
- 1)
3224 snprintf(tmp
, len
, "%s\n", *textp
);
3231 typedef struct pwd_s
{
3241 #define SAME_VALUE(X, Y) ((((X) == NULL && (Y) == NULL) \
3242 || ((X) && (Y) && !strcmp((X), (Y)))) ? 1 : 0)
3246 * Passfile lines are
3248 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3250 * In pine4.40 and before there was no orig_hostname, and there still isn't
3251 * if it is the same as hostname.
3254 * Use Windows credentials. The TargetName of the credential is
3255 * UWash_Alpine_.partnumber-totalparts_<hostname:port>\tuser\taltflag
3256 * and the blob consists of
3257 * passwd\torighost (if different from host)
3259 * We don't use anything fancy we just copy out all the credentials which
3260 * begin with TNAME and put them into our cache, so we don't lookup based
3261 * on the TargetName or anything like that. That was so we could re-use
3262 * the existing code and so that orighost data could be easily used.
3265 read_passfile(pinerc
, l
)
3271 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3274 char *tmp
, *blob
, *target
= NULL
;
3278 unsigned long m
, n
, p
, loc
;
3280 if(using_passfile
== 0)
3281 return(using_passfile
);
3284 if (init_wincred_funcs() != 1) {
3286 return(using_passfile
);
3290 dprint((9, "read_passfile\n"));
3294 /* this code exists because the XOAUTH2 support makes us save
3295 * access tokens as if they were passwords. However, some servers
3296 * produce extremely long access-tokens that do not fit in the credentials
3297 * and therefore need to be split into several entries.
3299 * The plan is the following:
3300 * step 1: Read and save all the information in the credentials
3301 * step 2: flatten the information into one line
3302 * step 3: process that line.
3304 if (g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)) {
3305 pwd
= fs_get((count
+ 1)*sizeof(ALPINE_PWD_S
*));
3306 memset((void *)pwd
, 0, (count
+ 1)*sizeof(ALPINE_PWD_S
*));
3308 /* this is step 1 */
3309 for (k
= 0; k
< count
; k
++) { /* go through each credential */
3310 target
= lptstr_to_utf8(pcred
[k
]->TargetName
);
3311 tmp
= srchstr(target
, TNAME
);
3313 tmp
+= strlen(TNAME
);
3316 m
= strtoul(tmp
, &tmp
, 10);
3319 n
= strtol(tmp
, &tmp
, 10);
3320 if (*tmp
== '_') tmp
++;
3327 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3328 for (i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++) {
3329 for (ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3330 ; /* find end of data */
3333 tmp
[i
++] = '\0'; /* tie off data */
3336 /* improve this. We are trying to find where we saved
3337 * this data, and in general this is fast if there is
3338 * only a few data, which is not unreasonable, but probably
3339 * can be done better.
3341 for (loc
= 0; pwd
[loc
]
3342 && !(SAME_VALUE(ui
[0], pwd
[loc
]->host
)
3343 && SAME_VALUE(ui
[1], pwd
[loc
]->user
)
3344 && SAME_VALUE(ui
[2], pwd
[loc
]->sflags
)); loc
++);
3346 if (pwd
[loc
] == NULL
) {
3347 pwd
[loc
] = fs_get(sizeof(ALPINE_PWD_S
));
3348 memset((void *) pwd
[loc
], 0, sizeof(ALPINE_PWD_S
));
3349 pwd
[loc
]->blobarray
= fs_get((n
+ 1) * sizeof(char*));
3350 memset((void *) pwd
[loc
]->blobarray
, 0, (n
+ 1) * sizeof(char*));
3353 if (pwd
[loc
]->host
== NULL
)
3354 pwd
[loc
]->host
= ui
[0] ? cpystr(ui
[0]) : NULL
;
3355 if (pwd
[loc
]->user
== NULL
)
3356 pwd
[loc
]->user
= ui
[1] ? cpystr(ui
[1]) : NULL
;
3357 if (pwd
[loc
]->sflags
== NULL
)
3358 pwd
[loc
]->sflags
= ui
[2] ? cpystr(ui
[2]) : NULL
;
3359 blob
= (char *) pcred
[k
]->CredentialBlob
;
3360 pwd
[loc
]->blobarray
[m
- 1] = blob
? cpystr(blob
) : NULL
;
3362 if (target
) fs_give((void**)&target
);
3365 for (k
= 0; k
< count
; k
++) {
3367 for (i
= 0, j
= 0; pwd
[k
]->blobarray
[j
]; j
++)
3368 i
+= strlen(pwd
[k
]->blobarray
[j
]);
3369 pwd
[k
]->blob
= fs_get(i
+ 1);
3370 pwd
[k
]->blob
[0] = '\0';
3371 for (j
= 0; pwd
[k
]->blobarray
[j
]; j
++) {
3372 strcat(pwd
[k
]->blob
, pwd
[k
]->blobarray
[j
]);
3373 fs_give((void **) &pwd
[k
]->blobarray
[j
]);
3375 fs_give((void **) pwd
[k
]->blobarray
);
3377 else k
= count
; /* we are done with this step! */
3380 for (k
= 0; k
< count
; k
++) {
3381 if (pwd
[k
] && pwd
[k
]->blob
) {
3382 blob
= pwd
[k
]->blob
;
3383 for (i
= 0, j
= 3; blob
[i
] && j
< 5; j
++) {
3384 for (ui
[j
] = &blob
[i
]; blob
[i
] && blob
[i
] != '\t'; i
++)
3385 ; /* find end of data */
3388 blob
[i
++] = '\0'; /* tie off data */
3390 if (pwd
[k
]->passwd
== NULL
)
3391 pwd
[k
]->passwd
= ui
[3] ? cpystr(ui
[3]) : NULL
;
3392 if (pwd
[k
]->orighost
== NULL
)
3393 pwd
[k
]->orighost
= ui
[4] ? cpystr(ui
[4]) : NULL
;
3394 fs_give((void **) &pwd
[k
]->blob
);
3397 /* now process all lines, and free memory */
3398 for (k
= 0; k
< count
&& pwd
[k
] != NULL
; k
++){
3399 if (pwd
[k
]->passwd
&& pwd
[k
]->host
&& pwd
[k
]->user
) { /* valid field? */
3400 STRLIST_S hostlist
[2];
3403 tmp
= pwd
[k
]->sflags
? strchr(pwd
[k
]->sflags
, PWDAUTHSEP
) : NULL
;
3404 flags
= pwd
[k
]->sflags
? atoi(tmp
? ++tmp
: pwd
[k
]->sflags
) : 0;
3405 hostlist
[0].name
= pwd
[k
]->host
;
3406 if (pwd
[k
]->orighost
) {
3407 hostlist
[0].next
= &hostlist
[1];
3408 hostlist
[1].name
= pwd
[k
]->orighost
;
3409 hostlist
[1].next
= NULL
;
3412 hostlist
[0].next
= NULL
;
3414 imap_set_passwd(l
, pwd
[k
]->passwd
, pwd
[k
]->user
, hostlist
, flags
& 0x01, 0, 0);
3416 if (pwd
[k
]->passwd
) fs_give((void **) &pwd
[k
]->passwd
);
3417 if (pwd
[k
]->user
) fs_give((void **) &pwd
[k
]->user
);
3418 if (pwd
[k
]->host
) fs_give((void **) &pwd
[k
]->host
);
3419 if (pwd
[k
]->sflags
) fs_give((void **) &pwd
[k
]->sflags
);
3420 if (pwd
[k
]->orighost
) fs_give((void **) &pwd
[k
]->orighost
);
3421 fs_give((void **) &pwd
[k
]);
3423 g_CredFree((PVOID
)pcred
);
3425 fs_give((void **) pwd
);
3429 # else /* old windows */
3436 char target
[MAILTMPLEN
];
3437 char *tmp
, *host
, *user
, *sflags
, *passwd
, *orighost
;
3440 SecKeychainAttributeList attrList
;
3441 SecKeychainSearchRef searchRef
= NULL
;
3442 SecKeychainAttribute attrs
[] = {
3443 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
3446 if(using_passfile
== 0)
3447 return(using_passfile
);
3449 dprint((9, "read_passfile\n"));
3452 /* search for only our items in the keychain */
3454 attrList
.attr
= attrs
;
3457 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
3458 kSecGenericPasswordItemClass
,
3461 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3463 SecKeychainItemRef itemRef
= NULL
;
3464 SecKeychainAttributeInfo info
;
3465 SecKeychainAttributeList
*attrList
= NULL
;
3468 char *blobcopy
= NULL
; /* NULL terminated copy */
3470 UInt32 tags
[] = {kSecAccountItemAttr
,
3471 kSecServiceItemAttr
};
3472 UInt32 formats
[] = {0,0};
3474 dprint((10, "read_passfile: searchRef not NULL\n"));
3477 info
.format
= formats
;
3480 * Go through each item we found and put it
3483 while(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
)) && itemRef
){
3484 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3485 rc
= SecKeychainItemCopyAttributesAndData(itemRef
,
3490 if(rc
== 0 && attrList
){
3491 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList
->count
));
3493 blobcopy
= (char *) fs_get((blength
+ 1) * sizeof(char));
3494 strncpy(blobcopy
, (char *) blob
, blength
);
3495 blobcopy
[blength
] = '\0';
3498 * I'm not real clear on how this works. It seems to be
3499 * necessary to combine the attributes from two passes
3500 * (attrList->count == 2) in order to get the full set
3501 * of attributes we inserted into the keychain in the
3502 * first place. So, we reset host...orighost outside of
3503 * the following for loop, not inside.
3505 host
= user
= sflags
= passwd
= orighost
= NULL
;
3506 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3508 for(k
= 0; k
< attrList
->count
; k
++){
3510 if(attrList
->attr
[k
].length
){
3512 (char *) attrList
->attr
[k
].data
,
3513 MIN(attrList
->attr
[k
].length
,sizeof(target
)));
3514 target
[MIN(attrList
->attr
[k
].length
,sizeof(target
)-1)] = '\0';
3518 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
3519 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3520 ; /* find end of data */
3523 tmp
[i
++] = '\0'; /* tie off data */
3535 for(i
= 0, j
= 3; blobcopy
[i
] && j
< 5; j
++){
3536 for(ui
[j
] = &blobcopy
[i
]; blobcopy
[i
] && blobcopy
[i
] != '\t'; i
++)
3537 ; /* find end of data */
3540 blobcopy
[i
++] = '\0'; /* tie off data */
3549 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
:""));
3552 if(passwd
&& host
&& user
){ /* valid field? */
3553 STRLIST_S hostlist
[2];
3556 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
3557 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
3558 hostlist
[0].name
= host
;
3560 hostlist
[0].next
= &hostlist
[1];
3561 hostlist
[1].name
= orighost
;
3562 hostlist
[1].next
= NULL
;
3565 hostlist
[0].next
= NULL
;
3568 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
3572 fs_give((void **) & blobcopy
);
3574 SecKeychainItemFreeAttributesAndData(attrList
, (void *) blob
);
3578 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc
));
3585 CFRelease(searchRef
);
3589 dprint((10, "read_passfile: searchRef NULL\n"));
3594 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc
));
3597 return(using_passfile
);
3599 #else /* PASSFILE */
3601 char tmp
[MAILTMPLEN
], *ui
[5];
3602 int i
, j
, n
, rv
= 0;
3604 char *tmptext
= NULL
;
3607 char tmp2
[MAILTMPLEN
];
3608 char *text
= NULL
, *text2
= NULL
;
3613 if(using_passfile
== 0)
3614 return(using_passfile
);
3616 dprint((9, "read_passfile\n"));
3618 /* if there's no password to read, bag it!! */
3619 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb"))){
3621 return(using_passfile
);
3625 if(our_stat(tmp
, &sbuf
) == 0)
3627 fp
= our_fopen(tmp
, "rb"); /* reopen to read data */
3629 /* the next call initializes the key/certificate pair used to
3630 * encrypt and decrypt a password file. The details of how this is
3631 * done is in the file pith/smime.c. During this setup we might call
3632 * smime_init(), but no matter what happens we must call smime_deinit()
3633 * there. The reason why this is so is because we can not assume that
3634 * the .pinerc file has been read by this time, so this code might not
3635 * know about the ps_global->smime structure or any of its components,
3636 * and it shouldn't because it only needs ps_global->pwdcert, so
3637 * do not init smime here, because the .pinerc might not have been
3638 * read and we do not really know where the keys and certificates really
3640 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3641 * it is called for the first time and there are certificates at all,
3642 * or when it is called after the first time and the user refuses to
3643 * create a self-signed certificate. In this situation we will just
3644 * let the user live in an insecure world, but no more passwords will
3645 * be saved in the password file, and only those found there will be used.
3648 fgets(tmp2
, sizeof(tmp2
), fp
);
3650 if(strcmp(tmp2
, "-----BEGIN PKCS7-----\n")){
3651 /* there is an already existing password file, that is not encrypted
3652 * and there is no key to encrypt it yet, go again through setup_pwdcert
3653 * and encrypt it now.
3655 if(tmp2
[0]){ /* not empty, UNencrypted password file */
3656 if(ps_global
->pwdcert
== NULL
)
3657 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3658 if((rv
== 0 || rv
== -5) && ps_global
->pwdcert
== NULL
)
3659 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3660 if(ps_global
->pwdcert
== NULL
){
3661 q_status_message(SM_ORDER
, 3, 3,
3662 " Failed to create private key. Using UNencrypted Password file. ");
3667 q_status_message(SM_ORDER
, 3, 3,
3668 " Failed to unlock private key. Using UNencrypted Password file. ");
3669 save_password
= 0; /* do not save more passwords */
3672 if(ps_global
->pwdcert
!= NULL
3673 && encrypt_file((char *)tmp
, NULL
, (PERSONAL_CERT
*)ps_global
->pwdcert
))
3678 if(ps_global
->pwdcert
== NULL
)
3679 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3684 * if password file is encrypted we attempt to decrypt. We ask the
3685 * user for the password to unlock the password file. If the user
3686 * enters the password and it unlocks the file, use it and keep saving
3687 * passwords in it. If the user enters the wrong passwords and does
3688 * not unlock it, we will not see that here, but in decrypt_file, so
3689 * the only other possibility is that the user cancels. In that case
3690 * we will see i == -1. In that case, we will let the user attempt
3691 * manual login to the server they want to login, but passwords will
3692 * not be saved so that the password file will not be saved
3693 * unencrypted and rewritten again.
3696 text
= text2
= decrypt_file((char *)tmp
, &i
, (PERSONAL_CERT
*)ps_global
->pwdcert
);
3697 len
= text2
? strlen(text2
) : 0;
3699 case -2: using_passfile
= 0;
3702 case 1 : save_password
= 1;
3706 case -1: save_password
= 0;
3715 if(using_passfile
== 0){
3717 if(text
) fs_give((void **)&text
);
3719 return using_passfile
;
3723 tmptext
= fs_get(len
+ 1);
3725 for(n
= 0; encrypted
? line_get(tmptext
, len
+ 1, &text2
)
3726 : (fgets(tmptext
, len
+1, fp
) != NULL
); n
++){
3728 for(n
= 0; fgets(tmptext
, len
+1, fp
); n
++){
3730 /*** do any necessary DEcryption here ***/
3732 for(i
= 0; tmptext
[i
]; i
++)
3733 tmptext
[i
] = xlate_out(tmptext
[i
]);
3735 if(i
&& tmptext
[i
-1] == '\n')
3736 tmptext
[i
-1] = '\0'; /* blast '\n' */
3738 dprint((10, "read_passfile: %s\n", tmp
? tmp
: "?"));
3739 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3740 for(i
= 0, j
= 0; tmptext
[i
] && j
< 5; j
++){
3741 for(ui
[j
] = &tmptext
[i
]; tmptext
[i
] && tmptext
[i
] != '\t'; i
++)
3742 ; /* find end of data */
3745 tmptext
[i
++] = '\0'; /* tie off data */
3748 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3749 if(ui
[0] && ui
[1] && ui
[2]){ /* valid field? */
3750 STRLIST_S hostlist
[2];
3751 char *s
= ui
[3] ? strchr(ui
[3], PWDAUTHSEP
) : NULL
;
3752 int flags
= ui
[3] ? atoi(s
? ++s
: ui
[3]) : 0;
3754 hostlist
[0].name
= ui
[2];
3756 hostlist
[0].next
= &hostlist
[1];
3757 hostlist
[1].name
= ui
[4];
3758 hostlist
[1].next
= NULL
;
3761 hostlist
[0].next
= NULL
;
3764 imap_set_passwd(l
, ui
[0], ui
[1], hostlist
, flags
& 0x01, 0, 0);
3769 if (tmptext
) fs_give((void **) &tmptext
);
3771 if (text
) fs_give((void **)&text
);
3776 #endif /* PASSFILE */
3782 write_passfile(pinerc
, l
)
3786 char *authend
, *authtype
;
3790 char target
[10*MAILTMPLEN
];
3791 char blob
[10 * MAILTMPLEN
], blob2
[10*MAILTMPLEN
], *blobp
;
3792 char part
[MAILTMPLEN
];
3796 if(using_passfile
== 0)
3799 dprint((9, "write_passfile\n"));
3801 for(; l
; l
= l
->next
){
3802 /* determine how many parts to create first */
3803 snprintf(blob
, sizeof(blob
), "%s%s%s",
3804 l
->passwd
? l
->passwd
: "",
3805 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3807 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3808 ? l
->hosts
->next
->name
: "");
3811 for (j
= 1; i
> MAXPWDBUFFERSIZE
; j
++, i
-= PWDBUFFERSIZE
);
3812 authtype
= l
->passwd
;
3813 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3814 if (authend
!= NULL
){
3816 sprintf(blob2
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3817 *authend
= PWDAUTHSEP
;
3820 sprintf(blob2
, "%d", l
->altflag
);
3821 for (k
= 1, i
= strlen(blob
), blobp
= blob
; k
<= j
; k
++) {
3822 snprintf(target
, sizeof(target
), "%s.%d-%d_%s\t%s\t%s",
3824 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3825 l
->user
? l
->user
: "",
3827 ltarget
= utf8_to_lptstr((LPSTR
)target
);
3829 memset((void*)&cred
, 0, sizeof(cred
));
3831 cred
.Type
= CRED_TYPE_GENERIC
;
3832 cred
.TargetName
= ltarget
;
3833 if (i
> MAXPWDBUFFERSIZE
) {
3834 strncpy(part
, blobp
, PWDBUFFERSIZE
);
3835 part
[PWDBUFFERSIZE
] = '\0';
3836 blobp
+= PWDBUFFERSIZE
;
3840 strcpy(part
, blobp
);
3841 cred
.CredentialBlobSize
= strlen(part
) + 1;
3842 cred
.CredentialBlob
= (LPBYTE
)&part
;
3843 cred
.Persist
= CRED_PERSIST_ENTERPRISE
;
3844 g_CredWriteW(&cred
, 0);
3845 fs_give((void**)<arget
);
3849 #endif /* WINCRED > 0 */
3853 char target
[10*MAILTMPLEN
];
3854 char blob
[10*MAILTMPLEN
];
3855 SecKeychainItemRef itemRef
= NULL
;
3857 if(using_passfile
== 0)
3860 dprint((9, "write_passfile\n"));
3862 for(; l
; l
= l
->next
){
3863 authtype
= l
->passwd
;
3864 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3865 if(authend
!= NULL
){
3867 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3868 *authend
= PWDAUTHSEP
;
3871 sprintf(blob
, "%d", l
->altflag
);
3873 snprintf(target
, sizeof(target
), "%s\t%s\t%s",
3874 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3875 l
->user
? l
->user
: "",
3878 snprintf(blob
, sizeof(blob
), "%s%s%s",
3879 l
->passwd
? l
->passwd
: "",
3880 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3882 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3883 ? l
->hosts
->next
->name
: "");
3885 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target
), target
, strlen(TNAME
), TNAME
, strlen(blob
), blob
));
3887 rc
= SecKeychainAddGenericPassword(NULL
,
3888 strlen(target
), target
,
3889 strlen(TNAME
), TNAME
,
3893 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3896 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc
));
3899 if(rc
== errSecDuplicateItem
){
3900 /* fix existing entry */
3901 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3903 if(!(rc
=SecKeychainFindGenericPassword(NULL
,
3904 strlen(target
), target
,
3905 strlen(TNAME
), TNAME
,
3907 &itemRef
)) && itemRef
){
3909 rc
= SecKeychainItemModifyAttributesAndData(itemRef
, NULL
, strlen(blob
), blob
);
3911 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc
));
3915 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc
));
3920 #else /* PASSFILE */
3921 char tmp
[10*MAILTMPLEN
], blob
[10*MAILTMPLEN
];
3925 char *text
= NULL
, tmp2
[10*MAILTMPLEN
];
3929 if(using_passfile
== 0)
3932 dprint((9, "write_passfile\n"));
3934 /* if there's no passfile to read, bag it!! */
3935 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "wb"))){
3941 strncpy(tmp2
, tmp
, sizeof(tmp2
));
3942 tmp2
[sizeof(tmp2
)-1] = '\0';
3945 for(n
= 0; l
; l
= l
->next
, n
++){
3946 authtype
= l
->passwd
;
3947 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3948 if(authend
!= NULL
){
3950 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3951 *authend
= PWDAUTHSEP
;
3954 sprintf(blob
, "%d", l
->altflag
);
3956 /*** do any necessary ENcryption here ***/
3957 snprintf(tmp
, sizeof(tmp
), "%s\t%s\t%s\t%s%s%s\n", l
->passwd
, l
->user
,
3958 l
->hosts
->name
, blob
,
3959 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "",
3960 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
3962 dprint((10, "write_passfile: %s", tmp
? tmp
: "?"));
3964 for(i
= 0; tmp
[i
]; i
++)
3965 tmp
[i
] = xlate_in(tmp
[i
]);
3968 fs_resize((void **)&text
, (len
+ strlen(tmp
) + 1)*sizeof(char));
3970 len
+= strlen(tmp
) + 1;
3971 strncat(text
, tmp
, strlen(tmp
));
3980 if(ps_global
->pwdcert
== NULL
){
3981 q_status_message(SM_ORDER
, 3, 3, "Attempting to encrypt password file");
3982 i
= setup_pwdcert(&ps_global
->pwdcert
);
3983 if((i
== 0 || i
== -5) && ps_global
->pwdcert
== NULL
)
3984 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3986 if(ps_global
->pwdcert
== NULL
){ /* we tried but failed */
3988 q_status_message1(SM_ORDER
, 3, 3, "Error: no directory %s to save certificates", ps_global
->pwdcertdir
);
3990 q_status_message(SM_ORDER
, 3, 3, "Refusing to write non-encrypted password file");
3992 else if(encrypt_file((char *)tmp2
, text
, ps_global
->pwdcert
) == 0)
3993 q_status_message(SM_ORDER
, 3, 3, "Failed to encrypt password file");
3994 fs_give((void **)&text
); /* do not save this text */
3997 #endif /* PASSFILE */
4000 #endif /* LOCAL_PASSWD_CACHE */
4005 erase_windows_credentials(void)
4007 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
4012 if(init_wincred_funcs() != 1)
4016 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
4018 for(k
= 0; k
< count
; k
++)
4019 g_CredDeleteW(pcred
[k
]->TargetName
, CRED_TYPE_GENERIC
, 0);
4021 g_CredFree((PVOID
) pcred
);
4027 ask_erase_credentials(void)
4029 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y'){
4030 erase_windows_credentials();
4031 q_status_message(SM_ORDER
, 3, 3, "All preserved passwords have been erased");
4034 q_status_message(SM_ORDER
, 3, 3, "Previously preserved passwords will not be erased");
4037 #endif /* WINCRED */
4040 #ifdef LOCAL_PASSWD_CACHE
4042 get_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
)
4043 char *pinerc
, **passwd
, *user
;
4044 STRLIST_S
*hostlist
;
4047 return get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, NULL
);
4051 * get_passfile_passwd_auth - return the password contained in the special password
4052 * cache. The file is assumed to be in the same directory
4053 * as the pinerc with the name defined above.
4056 get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, authtype
)
4057 char *pinerc
, **passwd
, *user
;
4058 STRLIST_S
*hostlist
;
4062 dprint((10, "get_passfile_passwd_auth\n"));
4063 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
4064 ? imap_get_passwd_auth(passfile_cache
, passwd
,
4065 user
, hostlist
, altflag
, authtype
)
4070 free_passfile_cache_work(MMLOGIN_S
**pwdcache
)
4072 if(pwdcache
== NULL
|| *pwdcache
== NULL
)
4075 if((*pwdcache
)->user
) fs_give((void **)&(*pwdcache
)->user
);
4076 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
4077 if((*pwdcache
)->hosts
) free_strlist(&(*pwdcache
)->hosts
);
4078 free_passfile_cache_work(&(*pwdcache
)->next
);
4079 fs_give((void **)pwdcache
);
4084 free_passfile_cache(void)
4087 free_passfile_cache_work(&passfile_cache
);
4091 is_using_passfile(void)
4093 return(using_passfile
== 1);
4097 * Just trying to guess the username the user might want to use on this
4098 * host, the user will confirm.
4101 get_passfile_user(pinerc
, hostlist
)
4103 STRLIST_S
*hostlist
;
4105 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
4106 ? imap_get_user(passfile_cache
, hostlist
)
4112 preserve_prompt(char *pinerc
)
4114 return preserve_prompt_auth(pinerc
, NULL
);
4118 preserve_prompt_auth(char *pinerc
, char *authtype
)
4122 #define PROMPT_PWD _("Preserve password for next login")
4123 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4125 * This prompt was going to be able to be turned on and off via a registry
4126 * setting controlled from the config menu. We decided to always use the
4127 * dialog for login, and there the prompt is unobtrusive enough to always
4128 * be in there. As a result, windows should never reach this, but now
4129 * OS X somewhat uses the behavior just described.
4131 if(mswin_store_pass_prompt()
4132 && (want_to(authtype
4133 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4135 'y', 'x', NO_HELP
, WT_NORM
)
4145 #define PROMPT_PWD _("Preserve password for next login")
4146 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4149 if((rc
= macos_store_pass_prompt()) != 0){
4151 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4152 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
4155 macos_set_store_pass_prompt(1);
4156 q_status_message(SM_ORDER
, 4, 4,
4157 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4162 macos_set_store_pass_prompt(0);
4163 q_status_message(SM_ORDER
, 4, 4,
4164 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4169 #else /* PASSFILE */
4170 #define PROMPT_PWD _("Preserve password on DISK for next login")
4171 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
4173 char tmp
[MAILTMPLEN
];
4176 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || our_stat(tmp
, &sbuf
) < 0)
4179 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
4180 return(want_to(authtype
4181 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4182 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
4185 #endif /* PASSFILE */
4188 #endif /* LOCAL_PASSWD_CACHE */
4191 #ifdef APPLEKEYCHAIN
4195 * 1 if store pass prompt is set in the "registry" to on
4197 * -1 if not set to anything
4200 macos_store_pass_prompt(void)
4207 if(storepassprompt
== -1){
4208 if(!(rc
=SecKeychainFindGenericPassword(NULL
, 0, NULL
,
4209 strlen(TNAMEPROMPT
),
4211 (void **) &data
, NULL
))){
4212 val
= (len
== 1 && data
&& data
[0] == '1');
4216 if(storepassprompt
== -1 && !rc
){
4218 storepassprompt
= 1;
4220 storepassprompt
= 0;
4223 return(storepassprompt
);
4228 macos_set_store_pass_prompt(int val
)
4230 storepassprompt
= val
? 1 : 0;
4232 SecKeychainAddGenericPassword(NULL
, 0, NULL
, strlen(TNAMEPROMPT
),
4233 TNAMEPROMPT
, 1, val
? "1" : "0", NULL
);
4238 macos_erase_keychain(void)
4240 SecKeychainAttributeList attrList
;
4241 SecKeychainSearchRef searchRef
= NULL
;
4242 SecKeychainAttribute attrs1
[] = {
4243 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
4245 SecKeychainAttribute attrs2
[] = {
4246 { kSecAccountItemAttr
, strlen(TNAMEPROMPT
), TNAMEPROMPT
}
4249 dprint((9, "macos_erase_keychain\n"));
4252 * Seems like we ought to be able to combine attrs1 and attrs2
4253 * into a single array, but I couldn't get it to work.
4256 /* search for only our items in the keychain */
4258 attrList
.attr
= attrs1
;
4260 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4261 kSecGenericPasswordItemClass
,
4265 SecKeychainItemRef itemRef
= NULL
;
4268 * Go through each item we found and put it
4271 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4272 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4273 SecKeychainItemDelete(itemRef
);
4277 CFRelease(searchRef
);
4282 attrList
.attr
= attrs2
;
4284 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4285 kSecGenericPasswordItemClass
,
4289 SecKeychainItemRef itemRef
= NULL
;
4292 * Go through each item we found and put it
4295 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4296 SecKeychainItemDelete(itemRef
);
4300 CFRelease(searchRef
);
4305 #endif /* APPLEKEYCHAIN */
4307 #ifdef LOCAL_PASSWD_CACHE
4310 set_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
)
4311 char *pinerc
, *passwd
, *user
;
4312 STRLIST_S
*hostlist
;
4313 int altflag
, already_prompted
;
4315 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, NULL
);
4318 * set_passfile_passwd - set the password file entry associated with
4319 * cache. The file is assumed to be in the same directory
4320 * as the pinerc with the name defined above.
4321 * already_prompted: 0 not prompted
4322 * 1 prompted, answered yes
4323 * 2 prompted, answered no
4326 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, authtype
)
4327 char *pinerc
, *passwd
, *user
;
4328 STRLIST_S
*hostlist
;
4329 int altflag
, already_prompted
;
4332 dprint((10, "set_passfile_passwd_auth\n"));
4333 if(((already_prompted
== 0 && preserve_prompt_auth(pinerc
, authtype
))
4334 || already_prompted
== 1)
4335 && !ps_global
->nowrite_password_cache
4336 && (passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))){
4337 imap_set_passwd_auth(&passfile_cache
, passwd
, user
, hostlist
, altflag
, 0, 0, authtype
);
4338 write_passfile(pinerc
, passfile_cache
);
4343 update_passfile_hostlist(pinerc
, user
, hostlist
, altflag
)
4346 STRLIST_S
*hostlist
;
4349 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, NULL
);
4353 * Passfile lines are
4355 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4357 * In pine4.40 and before there was no orig_hostname.
4358 * This routine attempts to repair that.
4361 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, authtype
)
4364 STRLIST_S
*hostlist
;
4370 #else /* !WINCRED */
4372 size_t len
= authtype
? strlen(authtype
) : 0;
4373 size_t offset
= authtype
? 1 : 0;
4375 for(l
= passfile_cache
; l
; l
= l
->next
)
4376 if(imap_same_host_auth(l
->hosts
, hostlist
, authtype
)
4378 && !strcmp(user
, l
->user
+ len
+ offset
)
4379 && l
->altflag
== altflag
){
4383 if(l
&& l
->hosts
&& hostlist
&& !l
->hosts
->next
&& hostlist
->next
4384 && hostlist
->next
->name
4385 && !ps_global
->nowrite_password_cache
){
4386 l
->hosts
->next
= new_strlist_auth(hostlist
->next
->name
, authtype
, PWDAUTHSEP
);
4387 write_passfile(pinerc
, passfile_cache
);
4389 #endif /* !WINCRED */
4392 #endif /* LOCAL_PASSWD_CACHE */
4397 * Load and init the WinCred structure.
4398 * This gives us a way to skip the WinCred code
4399 * if the dll doesn't exist.
4402 init_wincred_funcs(void)
4408 /* Assume the worst. */
4411 hmod
= LoadLibrary(TEXT("advapi32.dll"));
4414 FARPROC fpCredWriteW
;
4415 FARPROC fpCredEnumerateW
;
4416 FARPROC fpCredDeleteW
;
4419 fpCredWriteW
= GetProcAddress(hmod
, "CredWriteW");
4420 fpCredEnumerateW
= GetProcAddress(hmod
, "CredEnumerateW");
4421 fpCredDeleteW
= GetProcAddress(hmod
, "CredDeleteW");
4422 fpCredFree
= GetProcAddress(hmod
, "CredFree");
4424 if(fpCredWriteW
&& fpCredEnumerateW
&& fpCredDeleteW
&& fpCredFree
)
4426 g_CredWriteW
= (CREDWRITEW
*)fpCredWriteW
;
4427 g_CredEnumerateW
= (CREDENUMERATEW
*)fpCredEnumerateW
;
4428 g_CredDeleteW
= (CREDDELETEW
*)fpCredDeleteW
;
4429 g_CredFree
= (CREDFREE
*)fpCredFree
;
4435 mswin_set_erasecreds_callback(ask_erase_credentials
);
4438 return g_CredInited
;
4441 #endif /* WINCRED */