1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: imap.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2013-2020 Eduardo Chappa
8 * Copyright 2006-2009 University of Washington
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
21 The call back routines for the c-client/imap
22 - handles error messages and other notification
23 - handles prelimirary notification of new mail and expunged mail
24 - prompting for imap server login and password
43 #include "xoauth2conf.h"
44 #include "confscroll.h"
46 #include "../pith/state.h"
47 #include "../pith/conf.h"
48 #include "../pith/msgno.h"
49 #include "../pith/filter.h"
50 #include "../pith/news.h"
51 #include "../pith/util.h"
52 #include "../pith/list.h"
53 #include "../pith/margin.h"
55 #include "../pith/smime.h"
60 #define TNAME "UWash_Alpine_"
61 #define TNAMESTAR "UWash_Alpine_*"
64 * WinCred Function prototypes
66 typedef BOOL (WINAPI CREDWRITEW
) ( __in PCREDENTIALW Credential
, __in DWORD Flags
);
67 typedef BOOL (WINAPI CREDENUMERATEW
) ( __in LPCWSTR Filter
, __reserved DWORD Flags
,
68 __out DWORD
*Count
, __deref_out_ecount(*Count
) PCREDENTIALW
**Credential
);
69 typedef BOOL (WINAPI CREDDELETEW
) ( __in LPCWSTR TargetName
, __in DWORD Type
,
70 __reserved DWORD Flags
);
71 typedef VOID (WINAPI CREDFREE
) ( __in PVOID Buffer
);
76 int g_CredInited
= 0; /* 1 for loaded successfully,
77 * -1 for not available.
78 * 0 for not initialized yet.
80 CREDWRITEW
*g_CredWriteW
;
81 CREDENUMERATEW
*g_CredEnumerateW
;
82 CREDDELETEW
*g_CredDeleteW
;
88 #include <Security/SecKeychain.h>
89 #include <Security/SecKeychainItem.h>
90 #include <Security/SecKeychainSearch.h>
91 #define TNAME "UWash_Alpine"
92 #define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password"
94 int macos_store_pass_prompt(void);
95 void macos_set_store_pass_prompt(int);
97 static int storepassprompt
= -1;
98 #endif /* APPLEKEYCHAIN */
102 * Internal prototypes
104 void mm_login_alt_cue(NETMBX
*);
105 long pine_tcptimeout_noscreen(long, long, char *);
106 int answer_cert_failure(int, MSGNO_S
*, SCROLL_S
*);
107 int oauth2_auth_answer(int, MSGNO_S
*, SCROLL_S
*);
108 OAUTH2_S
*oauth2_select_flow(char *);
109 int xoauth2_flow_tool(struct pine
*, int, CONF_S
**, unsigned int);
111 #ifdef LOCAL_PASSWD_CACHE
112 int read_passfile(char *, MMLOGIN_S
**);
113 void write_passfile(char *, MMLOGIN_S
*);
114 int preserve_prompt(char *);
115 int preserve_prompt_auth(char *, char *authtype
);
116 void update_passfile_hostlist(char *, char *, STRLIST_S
*, int);
117 void update_passfile_hostlist_auth(char *, char *, STRLIST_S
*, int, char *);
118 void free_passfile_cache_work(MMLOGIN_S
**);
120 static MMLOGIN_S
*passfile_cache
= NULL
;
121 static int using_passfile
= -1;
122 int save_password
= 1;
123 #endif /* LOCAL_PASSWD_CACHE */
127 char xlate_out(char);
128 int line_get(char *, size_t, char **);
129 #endif /* PASSFILE */
132 void ask_erase_credentials(void);
133 int init_wincred_funcs(void);
137 static char *details_cert
, *details_host
, *details_reason
;
139 extern XOAUTH2_INFO_S xoauth_default
[];
142 * This is the private information of the client, which is passed to
143 * c-client for processing. Every c-client application must have its
146 OAUTH2_S alpine_oauth2_list
[] =
149 {"imap.gmail.com", "smtp.gmail.com", NULL
, NULL
},
150 {{"client_id", NULL
},
151 {"client_secret", NULL
},
152 {"tenant", NULL
}, /* not used */
153 {"code", NULL
}, /* access code from the authorization process */
154 {"refresh_token", NULL
},
155 {"scope", "https://mail.google.com/"},
156 {"redirect_uri", "urn:ietf:wg:oauth:2.0:oob"},
157 {"grant_type", "authorization_code"},
158 {"grant_type", "refresh_token"},
159 {"response_type", "code"},
161 {"device_code", NULL
} /* not used */
163 {{"GET", "https://accounts.google.com/o/oauth2/auth", /* authorization address, get access code */
164 {OA2_Id
, OA2_Scope
, OA2_Redirect
, OA2_Response
, OA2_End
, OA2_End
, OA2_End
}},
165 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* Device Info information, not used */
166 {"POST", "https://accounts.google.com/o/oauth2/token", /* Address to get refresh token from access code */
167 {OA2_Id
, OA2_Secret
, OA2_Redirect
, OA2_GrantTypeforAccessToken
, OA2_Code
, OA2_End
, OA2_End
}},
168 {"POST", "https://accounts.google.com/o/oauth2/token", /* access token from refresh token */
169 {OA2_Id
, OA2_Secret
, OA2_RefreshToken
, OA2_GrantTypefromRefreshToken
, OA2_End
, OA2_End
, OA2_End
}}
171 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information */
172 NULL
, /* access token */
173 0, /* expiration time */
174 0, /* first time indicator */
175 1 /* client secret required */
178 {"outlook.office365.com", "smtp.office365.com", NULL
, NULL
},
179 {{"client_id", NULL
},
180 {"client_secret", NULL
}, /* not used, but needed */
181 {"tenant", NULL
}, /* used */
182 {"code", NULL
}, /* not used, not needed */
183 {"refresh_token", NULL
},
184 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
185 {"grant_type", "urn:ietf:params:oauth:grant-type:device_code"},
186 {"scope", NULL
}, /* not used */
187 {"grant_type", "refresh_token"},
188 {"response_type", "code"}, /* not used */
189 {"state", NULL
}, /* not used */
190 {"device_code", NULL
} /* only used for frst time set up */
192 {{NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* Get Access Code, Not used */
193 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/devicecode", /* first time use and get device code information */
194 {OA2_Id
, OA2_Scope
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}},
195 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
196 {OA2_Id
, OA2_Redirect
, OA2_DeviceCode
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}},
197 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
198 {OA2_Id
, OA2_RefreshToken
, OA2_Scope
, OA2_GrantTypefromRefreshToken
, OA2_End
, OA2_End
, OA2_End
}}
200 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information */
201 NULL
, /* access token */
202 0, /* expiration time */
203 0, /* first time indicator */
204 0 /* client secret required */
207 {"outlook.office365.com", "smtp.office365.com", NULL
, NULL
},
208 {{"client_id", NULL
},
209 {"client_secret", NULL
}, /* not used, but needed */
210 {"tenant", NULL
}, /* used */
211 {"code", NULL
}, /* used during authorization */
212 {"refresh_token", NULL
},
213 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
214 {"redirect_uri", "http://localhost"},
215 {"grant_type", "authorization_code"},
216 {"grant_type", "refresh_token"},
217 {"response_type", "code"},
218 {"state", NULL
}, /* not used */
219 {"device_code", NULL
} /* not used */
221 {{"GET", "https://login.microsoftonline.com/\001/oauth2/v2.0/authorize", /* Get Access Code */
222 {OA2_Id
, OA2_Scope
, OA2_Redirect
, OA2_Response
, OA2_End
, OA2_End
, OA2_End
}},
223 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* device code, not used */
224 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
225 {OA2_Id
, OA2_Redirect
, OA2_Scope
, OA2_GrantTypeforAccessToken
, OA2_Secret
, OA2_Code
, OA2_End
}},
226 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
227 {OA2_Id
, OA2_RefreshToken
, OA2_Scope
, OA2_GrantTypefromRefreshToken
, OA2_Secret
, OA2_End
, OA2_End
}}
229 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information, not used */
230 NULL
, /* access token */
231 0, /* expiration time */
232 0, /* first time indicator */
233 1 /* client secret required */
236 {"imap.yandex.com", "smtp.yandex.com", NULL
, NULL
},
237 {{"client_id", NULL
},
238 {"client_secret", NULL
}, /* not used, but needed */
239 {"tenant", NULL
}, /* not used */
240 {"code", NULL
}, /* used during authorization */
241 {"refresh_token", NULL
},
242 {"scope", NULL
}, /* not needed, so not used */
243 {"redirect_uri", "https://oauth.yandex.ru/verification_code"},
244 {"grant_type", "authorization_code"},
245 {"grant_type", "refresh_token"},
246 {"response_type", "code"},
247 {"state", NULL
}, /* not used */
248 {"device_code", NULL
} /* not used */
250 {{"GET", "https://oauth.yandex.com/authorize", /* Get Access Code */
251 {OA2_Id
, OA2_Redirect
, OA2_Response
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}},
252 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* device code, not used */
253 {"POST", "https://oauth.yandex.com/token", /* Get first Refresh Token and Access token */
254 {OA2_Id
, OA2_Redirect
, OA2_GrantTypeforAccessToken
, OA2_Secret
, OA2_Code
, OA2_End
, OA2_End
}},
255 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
256 {OA2_Id
, OA2_RefreshToken
, OA2_GrantTypefromRefreshToken
, OA2_Secret
, OA2_End
, OA2_End
, OA2_End
}}
258 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information, not used */
259 NULL
, /* access token */
260 0, /* expiration time */
261 0, /* first time indicator */
262 1 /* client secret required */
264 { NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, 0, 0, 0},
268 xoauth2_flow_tool(struct pine
*ps
, int cmd
, CONF_S
**cl
, unsigned int flags
)
274 *((*cl
)->d
.xf
.selected
) = (*cl
)->d
.xf
.pat
;
275 rv
= simple_exit_cmd(flags
);
278 rv
= simple_exit_cmd(flags
);
286 ps
->mangled_body
= 1;
292 oauth2_select_flow(char *host
)
294 OAUTH2_S
*oa2list
, *oa2
;
299 CONF_S
*ctmp
= NULL
, *first_line
= NULL
;
300 OAUTH2_S
*x_sel
= NULL
;
304 dprint((9, "xoauth2 select flow"));
305 ps_global
->next_screen
= SCREEN_FUN_NULL
;
307 memset(&screen
, 0, sizeof(screen
));
309 for(i
= 0; i
< sizeof(tmp
) && i
< ps_global
->ttyo
->screen_cols
; i
++)
314 ctmp
->flags
|= CF_NOSELECT
;
315 ctmp
->value
= cpystr(tmp
);
318 ctmp
->flags
|= CF_NOSELECT
;
319 ctmp
->value
= cpystr(_("Please select below the authorization flow you would like to follow:"));
322 ctmp
->flags
|= CF_NOSELECT
;
323 ctmp
->value
= cpystr(tmp
);
325 for(oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
326 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
327 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
]){
331 method
= oa2list
->server_mthd
[0].name
? "Authorize"
332 : (oa2list
->server_mthd
[1].name
? "Device" : "Unknown");
333 sprintf(tmp
, "%s (%s)", oa2list
->name
, method
);
334 ctmp
->value
= cpystr(tmp
);
335 ctmp
->d
.xf
.selected
= &x_sel
;
336 ctmp
->d
.xf
.pat
= oa2list
;
337 ctmp
->keymenu
= &xoauth2_id_select_km
;
338 ctmp
->help
= NO_HELP
;
339 ctmp
->help_title
= NULL
;
340 ctmp
->tool
= xoauth2_flow_tool
;
341 ctmp
->flags
= CF_STARTITEM
;
345 (void)conf_scroll_screen(ps_global
, &screen
, first_line
, _("SELECT AUTHORIZATION FLOW"),
346 _("xoauth2"), 0, NULL
);
355 for(oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++)
356 n
+= strlen(oa2list
->name
); + 5; /* number, parenthesis, space */
357 n
+= 1024; /* large enough to display to lines of 80 characters in UTF-8 */
358 s
= fs_get(n
*sizeof(char));
359 strcpy(s
, _("Please select below the authorization flow you would like to follow:"));
360 sprintf(s
+ strlen(s
), _("Please select the client-id to use from the following list.\n\n"));
361 for(j
= 1, oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
362 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
363 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
])
364 sprintf(s
+ strlen(s
), " %d) %.70s\n", j
++, oa2list
->name
);
366 display_init_err(s
, 0);
368 strncpy(prompt
, _("Enter your selection number: "), sizeof(prompt
));
369 prompt
[sizeof(prompt
)-1] = '\0';
371 rv
= optionally_enter(reply
, 0, 0, sizeof(reply
), prompt
, NULL
, NO_HELP
, 0);
373 rv
= (sel
>= 0 && sel
< i
) ? 0 : -1;
376 for(j
= 1, oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
377 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
378 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
]){
388 typedef struct auth_code_s
{
394 oauth2device_decode_reply(void *datap
, void *replyp
)
396 OAUTH2_DEVICEPROC_S
*av
= (OAUTH2_DEVICEPROC_S
*)datap
;
397 int reply
= *(int *) replyp
;
399 return reply
< 0 ? av
->code_failure
: (reply
== 0 ? av
->code_success
: av
->code_wait
);
403 oauth2_elapsed_done(void *aux_valuep
)
405 OAUTH2_S
*oauth2
= aux_valuep
? ((OAUTH2_DEVICEPROC_S
*) aux_valuep
)->xoauth2
: NULL
;
406 static time_t savedt
= 0, now
;
409 if(aux_valuep
== NULL
) savedt
= 0; /* reset last time we approved */
412 if(oauth2
->devicecode
.interval
+ now
>= savedt
)
421 oauth2_set_device_info(OAUTH2_S
*oa2
, char *method
)
423 char tmp
[MAILTMPLEN
];
425 char *name
= oa2
->name
;
427 OAUTH2_DEVICECODE_S
*deviceinfo
= &oa2
->devicecode
;
428 OAUTH2_DEVICEPROC_S aux_value
;
432 STORE_S
*in_store
, *out_store
;
434 HANDLE_S
*handles
= NULL
;
435 AUTH_CODE_S user_input
;
437 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
438 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
441 aux_value
.xoauth2
= oa2
;
442 aux_value
.code_success
= 'e';
443 aux_value
.code_failure
= 'e';
444 aux_value
.code_wait
= NO_OP_COMMAND
;
446 so_puts(in_store
, "<HTML><P>");
447 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name
);
448 so_puts(in_store
, tmp
);
449 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name
, method
),
450 so_puts(in_store
, tmp
);
452 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
454 _("</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."),
455 deviceinfo
->verification_uri
, deviceinfo
->verification_uri
, deviceinfo
->user_code
);
456 so_puts(in_store
, tmp
);
459 so_puts(in_store
, "</P><P>");
460 so_puts(in_store
, deviceinfo
->message
);
462 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
463 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), name
);
464 so_puts(in_store
, tmp
);
465 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
467 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 "));
468 so_puts(in_store
, _("to grant access to Alpine to your data. "));
469 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 "));
470 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 "));
471 so_puts(in_store
, _("alive at the end of this process, and your connection will proceed from there."));
472 so_puts(in_store
, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit"));
473 so_puts(in_store
, _("</P></HTML>"));
475 so_seek(in_store
, 0L, 0);
476 init_handles(&handles
);
478 gf_link_filter(gf_html2plain
,
479 gf_html2plain_opt(NULL
,
480 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
481 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
482 gf_set_so_readc(&gc
, in_store
);
483 gf_set_so_writec(&pc
, out_store
);
485 gf_clear_so_writec(out_store
);
486 gf_clear_so_readc(in_store
);
488 memset(&sargs
, 0, sizeof(SCROLL_S
));
489 sargs
.text
.handles
= handles
;
490 sargs
.text
.text
= so_text(out_store
);
491 sargs
.text
.src
= CharStar
;
492 sargs
.text
.desc
= _("help text");
493 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
494 sargs
.proc
.tool
= oauth2_auth_answer
;
495 sargs
.proc
.data
.p
= (void *)&user_input
;
496 sargs
.keys
.menu
= &oauth2_device_auth_keymenu
;
497 /* don't want to re-enter c-client */
498 sargs
.quell_newmail
= 1;
499 setbitmap(sargs
.keys
.bitmap
);
500 sargs
.help
.text
= h_oauth2_start
;
501 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
502 sargs
.aux_function
= oauth2deviceinfo_get_accesscode
;
503 sargs
.aux_value
= (void *) &aux_value
;
504 sargs
.aux_condition
= oauth2_elapsed_done
;
505 sargs
.decode_aux_rv_value
= oauth2device_decode_reply
;
506 sargs
.aux_rv_value
= (void *) &aux_rv_value
;
510 ps_global
->mangled_screen
= 1;
511 ps_global
->painted_body_on_startup
= 0;
512 ps_global
->painted_footer_on_startup
= 0;
513 } while (user_input
.answer
!= 'e');
517 free_handles(&handles
);
518 oauth2_elapsed_done(NULL
);
521 int flags
, rc
, q_line
;
522 /* TRANSLATORS: user needs to input an access code from the server */
523 char prompt
[MAILTMPLEN
], token
[MAILTMPLEN
];
525 * If screen hasn't been initialized yet, use want_to.
529 tmp_20k_buf
[0] = '\0';
530 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
531 _("Authorizing Alpine Access to %s Email Services\n\n"), name
);
532 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
534 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
535 _("Alpine is attempting to log you into your %s account, using the %s method. "), name
, method
),
536 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
538 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
539 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
540 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
541 deviceinfo
->verification_uri
, deviceinfo
->user_code
);
542 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
545 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
546 "%s\n\n", deviceinfo
->message
);
547 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
550 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
551 _("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
);
552 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
554 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
555 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
556 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
558 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
559 "%s", _("to grant access to Alpine to your data.\n\n"));
560 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
562 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
563 "%s", _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
564 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
566 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
567 "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
568 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
570 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
571 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
572 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
574 display_init_err(tmp_20k_buf
, 0);
575 memset((void *)tmp
, 0, sizeof(tmp
));
576 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
577 tmp
[sizeof(tmp
)-1] = '\0';
579 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
582 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
583 flags
= OE_APPEND_CURRENT
;
585 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
586 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
587 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
589 aux_value
.xoauth2
= oa2
;
590 aux_value
.code_success
= 'y';
591 aux_value
.code_failure
= 'n';
592 aux_value
.code_wait
= 'w';
594 strncpy(tmp
, _("Continue waiting"), sizeof(tmp
));
595 tmp
[sizeof(tmp
)-1] = '\0';
597 if(oauth2_elapsed_done((void *) &aux_value
) == 0)
598 oauth2deviceinfo_get_accesscode((void *) &aux_value
, (void *) &rv
);
599 ch
= oauth2device_decode_reply((void *) &aux_value
, (void *) &rv
);
600 } while (ch
== 'w' || want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y');
601 oauth2_elapsed_done(NULL
);
607 oauth2_get_access_code(unsigned char *url
, char *method
, OAUTH2_S
*oauth2
, int *tryanother
)
609 char tmp
[MAILTMPLEN
];
614 STORE_S
*in_store
, *out_store
;
616 HANDLE_S
*handles
= NULL
;
617 AUTH_CODE_S user_input
;
619 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
620 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
623 so_puts(in_store
, "<HTML><BODY><P>");
624 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2
->name
);
625 so_puts(in_store
, tmp
);
626 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
627 so_puts(in_store
, tmp
);
629 if(strucmp(oauth2
->name
, GMAIL_NAME
) == 0){
630 so_puts(in_store
, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
631 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."));
632 so_puts(in_store
, _("<UL> "));
633 so_puts(in_store
, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
634 so_puts(in_store
, _("and create a project. The name of the project is not important."));
635 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."));
636 so_puts(in_store
, _("<LI> Create OAUTH Credentials."));
637 so_puts(in_store
, _("</UL> "));
638 so_puts(in_store
, _("<P> As a result of this process, you will get a client-id and a client-secret."));
639 so_puts(in_store
, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
640 so_puts(in_store
, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
641 so_puts(in_store
, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
642 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."));
645 so_puts(in_store
, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
646 so_puts(in_store
,"</P><P>");
647 sprintf(tmp_20k_buf
, _("<A HREF=\"%s\">%s</A>"), url
, url
);
648 so_puts(in_store
, tmp_20k_buf
);
650 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
651 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2
->name
);
652 so_puts(in_store
, tmp
);
653 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
655 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. "));
656 so_puts(in_store
, _(" At the end of this process, you will be given an access code or redirected to a web page."));
657 so_puts(in_store
, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
658 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. "));
659 so_puts(in_store
, _(" Once you have completed this process, Alpine will proceed with authentication."));
660 so_puts(in_store
, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
661 so_puts(in_store
, _("</P></BODY></HTML>"));
663 so_seek(in_store
, 0L, 0);
664 init_handles(&handles
);
666 gf_link_filter(gf_html2plain
,
667 gf_html2plain_opt(NULL
,
668 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
669 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
670 gf_set_so_readc(&gc
, in_store
);
671 gf_set_so_writec(&pc
, out_store
);
673 gf_clear_so_writec(out_store
);
674 gf_clear_so_readc(in_store
);
676 memset(&sargs
, 0, sizeof(SCROLL_S
));
677 sargs
.text
.handles
= handles
;
678 sargs
.text
.text
= so_text(out_store
);
679 sargs
.text
.src
= CharStar
;
680 sargs
.text
.desc
= _("help text");
681 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
682 sargs
.proc
.tool
= oauth2_auth_answer
;
683 sargs
.proc
.data
.p
= (void *)&user_input
;
684 sargs
.keys
.menu
= &oauth2_auth_keymenu
;
685 /* don't want to re-enter c-client */
686 sargs
.quell_newmail
= 1;
687 setbitmap(sargs
.keys
.bitmap
);
688 sargs
.help
.text
= h_oauth2_start
;
689 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
693 ps_global
->mangled_screen
= 1;
694 ps_global
->painted_body_on_startup
= 0;
695 ps_global
->painted_footer_on_startup
= 0;
696 } while (user_input
.answer
!= 'e');
698 if(!struncmp(user_input
.code
, "http://", 7)
699 || !struncmp(user_input
.code
, "https://", 8)){
701 s
= strstr(user_input
.code
, "code=");
709 else code
= user_input
.code
? cpystr(user_input
.code
) : NULL
;
710 if(user_input
.code
) fs_give((void **) &user_input
.code
);
712 if(code
== NULL
) *tryanother
= 1;
716 free_handles(&handles
);
719 int flags
, rc
, q_line
;
720 /* TRANSLATORS: user needs to input an access code from the server */
721 char *accesscodelabel
= _("Copy and Paste Access Code");
722 char prompt
[MAILTMPLEN
], token
[MAILTMPLEN
];
724 * If screen hasn't been initialized yet, use want_to.
727 tmp_20k_buf
[0] = '\0';
728 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
729 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2
->name
);
730 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
732 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
733 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
734 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
736 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
737 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
738 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
740 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
742 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
744 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
745 _("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
);
746 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
748 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
749 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
750 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
752 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
753 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
754 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
756 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
757 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
758 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
760 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
761 "%s", _(" If you 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. "));
762 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
764 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
765 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
766 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
768 display_init_err(tmp_20k_buf
, 0);
769 memset((void *)tmp
, 0, sizeof(tmp
));
770 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
771 tmp
[sizeof(tmp
)-1] = '\0';
773 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
774 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
775 flags
= OE_APPEND_CURRENT
;
776 sprintf(prompt
, "%s: ", accesscodelabel
);
778 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
779 prompt
, NULL
, NO_HELP
, &flags
);
780 } while (rc
!= 0 && rc
!= 1);
781 if(!struncmp(token
, "http://", 7)
782 || !struncmp(token
, "https://", 8)){
784 s
= strstr(token
, "code=");
792 else code
= token
[0] ? cpystr(token
) : NULL
;
799 void mm_login_oauth2(NETMBX
*, char *, char *, OAUTH2_S
*, long int, char *, char *);
801 /* The purpose of this function is to report to c-client the values of the
802 * different tokens and codes so that c-client can try to log in the user
803 * to the server. This function DOES NOT attempt to get these values for
804 * the user. That is attempted in the c-client side (as best as it can be
805 * done given our circumstances: no http support, no javascript support,
806 * etc.). This is the best we can do:
808 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
809 * as best as we can. Unloaded means that there is no server known to
810 * connect, no access token, etc. Pretty much nothing is known about
811 * how to get access code, access token, etc. We ask the user to fill
812 * it up for us, if they can. If the user fills it up we save those
815 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
816 * save the information that c-client got for us.
818 * 3. When saving this information we use the password caching facilities,
819 * but we must do it in a different format so that old information and
820 * new information are not mixed. In order to accommodate this for new
821 * authentication methods, we save the information in the same fields,
822 * but this time we modify it slightly, so that old functions fail to
823 * understand the new information and so not modify it nor use it. The
824 * modification is simple: Every piece of information that was saved
825 * before is prepended XOAUTH2\001 to indicate the authentication
826 * method for which it works. The character \001 is a separator. New
827 * Alpine will know how to deal with this, but old versions, will not
828 * strip this prefix from the information and fail to get the
829 * information or modify it when needed. Only new versions of Alpine will
830 * know how to process this information.
831 * new_value = authenticator_method separator old_value
832 * authenticator_method = "XOAUTH2_S"
834 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
835 * authenticator is "XOAUTH2\001imap.gmail.com".
836 * In addition, the password field is not used to encode the password
837 * anymore, it is used to save login information needed in a format that
838 * the caller function chooses, but that must be preceded by the
839 * "authenticator_method separator" code as above.
842 mm_login_oauth2(NETMBX
*mb
, char *user
, char *method
,
843 OAUTH2_S
*login
, long int trial
,
844 char *usethisprompt
, char *altuserforcache
)
846 char *token
, tmp
[MAILTMPLEN
];
847 char prompt
[4*MAILTMPLEN
], value
[4*MAILTMPLEN
], *last
;
848 char defuser
[NETMAXUSER
];
849 char hostleadin
[80], hostname
[200], defubuf
[200];
850 char logleadin
[80], pwleadin
[50];
853 char *OldRefreshToken
, *OldAccessToken
;
854 char *NewRefreshToken
, *NewAccessToken
;
855 char *SaveRefreshToken
, *SaveAccessToken
;
856 /* TRANSLATORS: A label for the hostname that the user is logging in on */
857 char *hostlabel
= _("HOST");
858 /* TRANSLATORS: user is logging in as a particular user (a particular
859 login name), this is just labelling that user name. */
860 char *userlabel
= _("USER");
861 STRLIST_S hostlist
[2], hostlist2
[OAUTH2_TOT_EQUIV
+1];
863 int len
, rc
, q_line
, flags
, i
, j
;
864 int oespace
, avail
, need
, save_dont_use
;
867 int ChangeAccessToken
, ChangeRefreshToken
, ChangeExpirationTime
;
868 OAUTH2_S
*oa2list
, *oa2
;
871 unsigned long OldExpirationTime
, NewExpirationTime
, SaveExpirationTime
;
872 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
873 int preserve_password
= -1;
876 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
877 trial
, mb
->user
? mb
->user
: "(null)",
878 mb
->service
? mb
->service
: "(null)",
879 mb
->port
? " port=" : "",
880 mb
->port
? comatose(mb
->port
) : "",
881 altuserforcache
? " altuserforcache =" : "",
882 altuserforcache
? altuserforcache
: ""));
884 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
886 save_in_init
= ps_global
->in_init_seq
;
887 ps_global
->in_init_seq
= 0;
888 ps_global
->no_newmail_check_from_optionally_enter
= 1;
890 /* make sure errors are seen */
892 flush_status_messages(0);
894 token
= NULL
; /* start from scratch */
896 hostlist
[0].name
= mb
->host
;
897 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
898 hostlist
[0].next
= &hostlist
[1];
899 hostlist
[1].name
= mb
->orighost
;
900 hostlist
[1].next
= NULL
;
903 hostlist
[0].next
= NULL
;
905 if(hostlist
[0].name
){
906 dprint((9, "mm_login_oauth2: host=%s\n",
907 hostlist
[0].name
? hostlist
[0].name
: "?"));
908 if(hostlist
[0].next
&& hostlist
[1].name
){
909 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist
[1].name
));
913 if(trial
== 0L && !altuserforcache
){
914 if(*mb
->user
!= '\0')
915 strncpy(user
, mb
->user
, NETMAXUSER
);
917 flags
= OE_APPEND_CURRENT
;
918 sprintf(prompt
, "%s: %s - %s: ", hostlabel
, mb
->orighost
, userlabel
);
919 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
920 prompt
, NULL
, NO_HELP
, &flags
);
922 user
[NETMAXUSER
-1] = '\0';
926 * We check to see if the server we are going to log in to is already
927 * registered. This gives us a list of servers with the same
928 * credentials, so we use the same credentials for all of them.
931 for(registered
= 0, oa2list
= alpine_oauth2_list
;
932 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
934 for(i
= 0; i
< OAUTH2_TOT_EQUIV
935 && oa2list
->host
[i
] != NULL
936 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
937 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
944 x
= oauth2_get_client_info(oa2list
->name
, user
);
946 for(oa2list
= alpine_oauth2_list
;
947 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
949 for(i
= 0; i
< OAUTH2_TOT_EQUIV
950 && oa2list
->host
[i
] != NULL
951 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
952 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
953 char *flow
= oa2list
->server_mthd
[0].name
? "Authorize"
954 : (oa2list
->server_mthd
[1].name
? "Device" : "Unknown");
955 if(!strucmp(x
->flow
, flow
)) break; /* found it */
959 /* else use the one we found earlier, the user has to configure this better */
963 hostlist2
[i
= 0].name
= mb
->host
;
964 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
))
965 hostlist2
[++i
].name
= mb
->orighost
;
967 for(j
= 0; j
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[j
] != NULL
; j
++){
969 for(k
= 0; k
<= i
&& hostlist2
[k
].name
970 && strcmp(hostlist2
[k
].name
, oa2list
->host
[j
]); k
++);
972 hostlist2
[++i
].name
= oa2list
->host
[j
];
974 hostlist2
[i
+1].name
= NULL
;
975 hostlist2
[i
+1].next
= NULL
;
976 for(j
= i
; j
>= 0; j
--)
977 hostlist2
[j
].next
= &hostlist2
[j
+1];
981 * We check if we have a refresh token saved somewhere, if so
982 * we use it to get a new access token, otherwise we need to
983 * get an access code so we can get (and save) a refresh token
984 * and use the access token.
986 if(trial
== 0L && !altuserforcache
){
987 /* Search for a refresh token that is already loaded ... */
988 if(imap_get_passwd_auth(mm_login_list
, &token
, user
,
989 registered
? hostlist2
: hostlist
,
990 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
991 dprint((9, "mm_login_oauth2: found a refresh token\n"));
992 ps_global
->no_newmail_check_from_optionally_enter
= 0;
993 ps_global
->in_init_seq
= save_in_init
;
995 #ifdef LOCAL_PASSWD_CACHE
996 /* or see if we have saved one in the local password cache and load it */
997 else if(get_passfile_passwd_auth(ps_global
->pinerc
, &token
,
998 user
, registered
? hostlist2
: hostlist
,
999 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
1000 imap_set_passwd_auth(&mm_login_list
, token
, user
,
1001 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
1002 update_passfile_hostlist_auth(ps_global
->pinerc
, user
, hostlist
,
1003 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
);
1004 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
1005 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1006 ps_global
->in_init_seq
= save_in_init
;
1008 if(token
&& *token
) preserve_password
= 1; /* resave it, no need to ask */
1009 #endif /* LOCAL_PASSWD_CACHE */
1011 user
[NETMAXUSER
-1] = '\0';
1013 /* The Old* variables is what c_client knows */
1014 OldRefreshToken
= login
->param
[OA2_RefreshToken
].value
;
1015 OldAccessToken
= login
->access_token
;
1016 OldExpirationTime
= login
->expiration
;
1018 /* The New* variables is what Alpine knows */
1019 NewRefreshToken
= NewAccessToken
= NULL
;
1020 NewExpirationTime
= 0L;
1021 ChangeAccessToken
= ChangeRefreshToken
= ChangeExpirationTime
= 0;
1023 if(token
&& *token
){
1027 t
= strchr(s
, PWDAUTHSEP
);
1029 NewRefreshToken
= cpystr(s
);
1032 NewRefreshToken
= cpystr(s
);
1034 t
= strchr(s
, PWDAUTHSEP
);
1036 NewAccessToken
= cpystr(s
);
1039 NewAccessToken
= cpystr(s
);
1041 NewExpirationTime
= strtol(s
, &s
, 10);
1042 if(NewExpirationTime
<= 0 || NewExpirationTime
<= time(0))
1043 NewExpirationTime
= 0L;
1046 /* check we got good information, and send good information below */
1047 if(NewRefreshToken
&& !*NewRefreshToken
)
1048 fs_give((void **) &NewRefreshToken
);
1049 if(NewAccessToken
&& (NewExpirationTime
== 0L || !*NewAccessToken
))
1050 fs_give((void **) &NewAccessToken
);
1052 else login
->first_time
++;
1054 if(login
->first_time
){ /* count how many authorization methods we support */
1057 for(nmethods
= 0, oa2
= alpine_oauth2_list
; oa2
&& oa2
->name
; oa2
++){
1058 for(j
= 0; j
< OAUTH2_TOT_EQUIV
1060 && oa2
->host
[j
] != NULL
1061 && strucmp(oa2
->host
[j
], mb
->orighost
) != 0; j
++);
1062 if(oa2
&& oa2
->host
&& j
< OAUTH2_TOT_EQUIV
&& oa2
->host
[j
])
1067 oa2list
= oauth2_select_flow(mb
->orighost
);
1069 if(!oa2list
) registered
= 0;
1072 /* Default to saving what we already had saved */
1074 SaveRefreshToken
= NewRefreshToken
;
1075 SaveAccessToken
= NewAccessToken
;
1076 SaveExpirationTime
= NewExpirationTime
;
1078 /* Translation of the logic below:
1079 * if (c-client has a refresh token
1080 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1081 forget the Alpine refresh token;
1082 make the Alpine Refresh token = c-client refresh token.;
1083 signal that we changed the refresh token;
1084 In this situation we do not need to clear up the Alpine access token. This will
1085 expire, so we can use it until it expires. We can save the c-client refresh token
1086 together with the Alpine Access Token and the expiration date of the Alpine Access
1088 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1089 forget the Alpine refresh token;
1090 if Alpine has an access token, forget it;
1091 reset the expiration time
1092 signal that we changed the refresh token; (because the service expired it)
1096 if(OldRefreshToken
!= NULL
1097 && (NewRefreshToken
== NULL
|| strcmp(OldRefreshToken
, NewRefreshToken
))){
1098 if(NewRefreshToken
) fs_give((void **) &NewRefreshToken
);
1099 NewRefreshToken
= cpystr(OldRefreshToken
);
1100 ChangeRefreshToken
++;
1101 SaveRefreshToken
= OldRefreshToken
;
1102 SaveAccessToken
= NewAccessToken
;
1103 SaveExpirationTime
= NewExpirationTime
;
1104 } else if (OldRefreshToken
== NULL
&& NewRefreshToken
!= NULL
&& trial
> 0){
1105 fs_give((void **) &NewRefreshToken
);
1106 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1107 NewExpirationTime
= 0L;
1108 ChangeRefreshToken
++;
1109 SaveRefreshToken
= NULL
;
1110 SaveAccessToken
= NULL
;
1111 SaveExpirationTime
= 0L;
1114 if(OldAccessToken
!= NULL
1115 && (NewAccessToken
== NULL
|| strcmp(OldAccessToken
, NewAccessToken
))){
1116 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1117 NewAccessToken
= cpystr(OldAccessToken
);
1118 ChangeAccessToken
++;
1119 NewExpirationTime
= OldExpirationTime
;
1120 SaveRefreshToken
= NewRefreshToken
;
1121 SaveAccessToken
= NewAccessToken
;
1122 SaveExpirationTime
= NewExpirationTime
;
1126 login
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1127 login
->access_token
= SaveAccessToken
;
1128 login
->expiration
= SaveExpirationTime
;
1130 oa2list
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1131 oa2list
->access_token
= SaveAccessToken
;
1132 oa2list
->expiration
= SaveExpirationTime
;
1133 oa2list
->first_time
= login
->first_time
;
1134 *login
= *oa2list
; /* load login pointer */
1137 if(!ChangeAccessToken
&& !ChangeRefreshToken
)
1140 /* get ready to save this information. The format will be
1141 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1142 * (spaces added for clarity, \001 is PWDAUTHSEP)
1144 if(token
) fs_give((void **) &token
);
1145 sprintf(tmp
, "%lu", SaveExpirationTime
);
1146 tmp
[sizeof(tmp
) - 1] = '\0';
1147 len
= strlen(SaveRefreshToken
? SaveRefreshToken
: "")
1148 + strlen(SaveAccessToken
? SaveAccessToken
: "")
1150 token
= fs_get(len
+ 1);
1151 sprintf(token
, "%s%c%s%c%lu",
1152 SaveRefreshToken
? SaveRefreshToken
: "", PWDAUTHSEP
,
1153 SaveAccessToken
? SaveAccessToken
: "", PWDAUTHSEP
,
1154 SaveExpirationTime
);
1156 /* remember the access information for next time */
1157 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
1158 imap_set_passwd_auth(&mm_login_list
, token
,
1159 altuserforcache
? altuserforcache
: user
, hostlist
,
1160 (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
1161 #ifdef LOCAL_PASSWD_CACHE
1162 /* if requested, remember it on disk for next session */
1163 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
1164 set_passfile_passwd_auth(ps_global
->pinerc
, token
,
1165 altuserforcache
? altuserforcache
: user
, hostlist
,
1166 (mb
->sslflag
||mb
->tlsflag
),
1167 (preserve_password
== -1 ? 0
1168 : (preserve_password
== 0 ? 2 :1)), OA2NAME
);
1169 #endif /* LOCAL_PASSWD_CACHE */
1171 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1174 /*----------------------------------------------------------------------
1175 receive notification from IMAP
1177 Args: stream -- Mail stream message is relevant to
1178 string -- The message text
1179 errflg -- Set if it is a serious error
1181 Result: message displayed in status line
1183 The facility is for general notices, such as connection to server;
1184 server shutting down etc... It is used infrequently.
1185 ----------------------------------------------------------------------*/
1187 mm_notify(MAILSTREAM
*stream
, char *string
, long int errflg
)
1192 now
= time((time_t *)0);
1193 tm_now
= localtime(&now
);
1195 /* be sure to log the message... */
1197 if(ps_global
->debug_imap
|| ps_global
->debugmem
)
1198 dprint((errflg
== TCPDEBUG
? 7 : 2,
1199 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1200 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1201 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1202 (!errflg
) ? "babble" :
1203 (errflg
== ERROR
) ? "error" :
1204 (errflg
== WARN
) ? "warning" :
1205 (errflg
== PARSE
) ? "parse" :
1206 (errflg
== TCPDEBUG
) ? "tcp" :
1207 (errflg
== BYE
) ? "bye" : "unknown",
1208 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1209 string
? string
: "?"));
1212 snprintf(ps_global
->last_error
, sizeof(ps_global
->last_error
), "%s : %.*s",
1213 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1214 (int) MIN(MAX_SCREEN_COLS
, sizeof(ps_global
->last_error
)-70),
1216 ps_global
->last_error
[ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1217 : sizeof(ps_global
->last_error
)-1] = '\0';
1220 * Then either set special bits in the pine struct or
1221 * display the message if it's tagged as an "ALERT" or
1222 * its errflg > NIL (i.e., WARN, or ERROR)
1226 * We'd like to sp_mark_stream_dead() here but we can't do that because
1227 * that might call mail_close and we are already in a c-client callback.
1228 * So just set the dead bit and clean it up later.
1230 sp_set_dead_stream(stream
, 1);
1231 else if(!strncmp(string
, "[TRYCREATE]", 11))
1232 ps_global
->try_to_create
= 1;
1233 else if(!strncmp(string
, "[REFERRAL ", 10))
1234 ; /* handled in the imap_referral() callback */
1235 else if(!strncmp(string
, "[ALERT]", 7))
1236 q_status_message2(SM_MODAL
, 3, 3,
1237 _("Alert received while accessing \"%s\": %s"),
1238 (stream
&& stream
->mailbox
)
1239 ? stream
->mailbox
: "-no folder-",
1240 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf
+10000),
1241 SIZEOF_20KBUF
-10000, string
));
1242 else if(!strncmp(string
, "[UNSEEN ", 8)){
1246 for(p
= string
+ 8; isdigit(*p
); p
++)
1247 n
= (n
* 10) + (*p
- '0');
1249 sp_set_first_unseen(stream
, n
);
1251 else if(!strncmp(string
, "[READ-ONLY]", 11)
1252 && !(stream
&& stream
->mailbox
&& IS_NEWS(stream
)))
1253 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, "%s : %s",
1254 (stream
&& stream
->mailbox
)
1255 ? stream
->mailbox
: "-no folder-",
1257 else if((errflg
&& errflg
!= BYE
&& errflg
!= PARSE
)
1258 && !ps_global
->noshow_error
1260 && (ps_global
->noshow_warn
|| (stream
&& stream
->unhealthy
))))
1261 q_status_message(SM_ORDER
| ((errflg
== ERROR
) ? SM_DING
: 0),
1262 3, 6, ps_global
->last_error
);
1266 /*----------------------------------------------------------------------
1267 Queue imap log message for display in the message line
1269 Args: string -- The message
1270 errflg -- flag set to 1 if pertains to an error
1272 Result: Message queued for display
1274 The c-client/imap reports most of it's status and errors here
1277 mm_log(char *string
, long int errflg
)
1279 char message
[sizeof(ps_global
->c_client_error
)];
1281 int was_capitalized
;
1282 static char saw_kerberos_init_warning
;
1286 now
= time((time_t *)0);
1287 tm_now
= localtime(&now
);
1289 dprint((((errflg
== TCPDEBUG
) && ps_global
->debug_tcp
) ? 1 :
1290 (errflg
== TCPDEBUG
) ? 10 : 2,
1291 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1292 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1293 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1294 (!errflg
) ? "babble" :
1295 (errflg
== ERROR
) ? "error" :
1296 (errflg
== WARN
) ? "warning" :
1297 (errflg
== PARSE
) ? "parse" :
1298 (errflg
== TCPDEBUG
) ? "tcp" :
1299 (errflg
== BYE
) ? "bye" : "unknown",
1300 string
? string
: "?"));
1302 if(errflg
== ERROR
&& !strncmp(string
, "[TRYCREATE]", 11)){
1303 ps_global
->try_to_create
= 1;
1306 else if(ps_global
->try_to_create
1307 || !strncmp(string
, "[CLOSED]", 8)
1308 || (sp_dead_stream(ps_global
->mail_stream
) && strstr(string
, "No-op")))
1310 * Don't display if creating new folder OR
1311 * warning about a dead stream ...
1315 strncpy(message
, string
, sizeof(message
));
1316 message
[sizeof(message
) - 1] = '\0';
1318 if(errflg
== WARN
&& srchstr(message
, "try running kinit") != NULL
){
1319 if(saw_kerberos_init_warning
)
1322 saw_kerberos_init_warning
= 1;
1325 /*---- replace all "mailbox" with "folder" ------*/
1326 occurence
= srchstr(message
, "mailbox");
1329 || isspace((unsigned char) *(occurence
+7))
1330 || *(occurence
+7) == ':'){
1331 was_capitalized
= isupper((unsigned char) *occurence
);
1332 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 7, (errflg
== PARSE
? "address" : "folder"));
1334 *occurence
= (errflg
== PARSE
? 'A' : 'F');
1339 occurence
= srchstr(occurence
, "mailbox");
1342 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1343 occurence
= srchstr(message
, "GSSAPI");
1346 || isspace((unsigned char) *(occurence
+6))
1347 || *(occurence
+6) == ':')
1348 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 6, "Kerberos");
1352 occurence
= srchstr(occurence
, "GSSAPI");
1356 ps_global
->mm_log_error
= 1;
1358 if(errflg
== PARSE
|| (errflg
== ERROR
&& ps_global
->noshow_error
))
1359 strncpy(ps_global
->c_client_error
, message
,
1360 sizeof(ps_global
->c_client_error
));
1362 if(ps_global
->noshow_error
1363 || (ps_global
->noshow_warn
&& errflg
== WARN
)
1364 || !(errflg
== ERROR
|| errflg
== WARN
))
1365 return; /* Only care about errors; don't print when asked not to */
1367 /*---- Display the message ------*/
1368 q_status_message((errflg
== ERROR
) ? (SM_ORDER
| SM_DING
) : SM_ORDER
,
1370 strncpy(ps_global
->last_error
, message
, sizeof(ps_global
->last_error
));
1371 ps_global
->last_error
[sizeof(ps_global
->last_error
) - 1] = '\0';
1375 mm_login_method_work(NETMBX
*mb
, char *user
, void *login
, long int trial
,
1376 char *method
, char *usethisprompt
, char *altuserforcache
)
1380 if(strucmp(method
, OA2NAME
) == 0 || strucmp(method
, BEARERNAME
) == 0)
1381 mm_login_oauth2(mb
, user
, method
, (OAUTH2_S
*) login
, trial
, usethisprompt
, altuserforcache
);
1385 mm_login_work(NETMBX
*mb
, char *user
, char **pwd
, long int trial
,
1386 char *usethisprompt
, char *altuserforcache
)
1388 char tmp
[MAILTMPLEN
];
1389 char prompt
[1000], *last
;
1390 char port
[20], non_def_port
[20], insecure
[20];
1391 char defuser
[NETMAXUSER
];
1392 char hostleadin
[80], hostname
[200], defubuf
[200];
1393 char logleadin
[80], pwleadin
[50];
1394 char hostlist0
[MAILTMPLEN
], hostlist1
[MAILTMPLEN
];
1395 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1396 that the password will be sent unencrypted over the network. This is
1397 just a warning message that gets added parenthetically when the user
1398 is asked for a password. */
1399 char *insec
= _(" (INSECURE)");
1400 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1401 after having already failed at least once. */
1402 char *retry
= _("Retrying - ");
1403 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1404 char *hostlabel
= _("HOST");
1405 /* TRANSLATORS: user is logging in as a particular user (a particular
1406 login name), this is just labelling that user name. */
1407 char *userlabel
= _("USER");
1408 STRLIST_S hostlist
[2];
1410 int len
, rc
, q_line
, flags
;
1411 int oespace
, avail
, need
, save_dont_use
;
1414 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1415 int preserve_password
= -1;
1418 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1419 trial
, mb
->user
? mb
->user
: "(null)",
1420 mb
->service
? mb
->service
: "(null)",
1421 mb
->port
? " port=" : "",
1422 mb
->port
? comatose(mb
->port
) : "",
1423 altuserforcache
? " altuserforcache =" : "",
1424 altuserforcache
? altuserforcache
: ""));
1425 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
1427 save_in_init
= ps_global
->in_init_seq
;
1428 ps_global
->in_init_seq
= 0;
1429 ps_global
->no_newmail_check_from_optionally_enter
= 1;
1431 /* make sure errors are seen */
1433 flush_status_messages(0);
1436 * Add port number to hostname if going through a tunnel or something
1438 non_def_port
[0] = '\0';
1439 if(mb
->port
&& mb
->service
&&
1440 (sv
= getservbyname(mb
->service
, "tcp")) &&
1441 (mb
->port
!= ntohs(sv
->s_port
))){
1442 snprintf(non_def_port
, sizeof(non_def_port
), ":%lu", mb
->port
);
1443 non_def_port
[sizeof(non_def_port
)-1] = '\0';
1444 dprint((9, "mm_login: using non-default port=%s\n",
1445 non_def_port
? non_def_port
: "?"));
1449 * set up host list for sybil servers...
1452 strncpy(hostlist0
, mb
->host
, sizeof(hostlist0
)-1);
1453 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1454 strncat(hostlist0
, non_def_port
, sizeof(hostlist0
)-strlen(hostlist0
)-1);
1455 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1456 hostlist
[0].name
= hostlist0
;
1457 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1458 strncpy(hostlist1
, mb
->orighost
, sizeof(hostlist1
)-1);
1459 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1460 strncat(hostlist1
, non_def_port
, sizeof(hostlist1
)-strlen(hostlist1
)-1);
1461 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1462 hostlist
[0].next
= &hostlist
[1];
1463 hostlist
[1].name
= hostlist1
;
1464 hostlist
[1].next
= NULL
;
1467 hostlist
[0].next
= NULL
;
1470 hostlist
[0].name
= mb
->host
;
1471 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1472 hostlist
[0].next
= &hostlist
[1];
1473 hostlist
[1].name
= mb
->orighost
;
1474 hostlist
[1].next
= NULL
;
1477 hostlist
[0].next
= NULL
;
1480 if(hostlist
[0].name
){
1481 dprint((9, "mm_login: host=%s\n",
1482 hostlist
[0].name
? hostlist
[0].name
: "?"));
1483 if(hostlist
[0].next
&& hostlist
[1].name
){
1484 dprint((9, "mm_login: orighost=%s\n", hostlist
[1].name
));
1489 * Initialize user name with either
1490 * 1) /user= value in the stream being logged into,
1491 * or 2) the user name we're running under.
1493 * Note that VAR_USER_ID is not yet initialized if this login is
1494 * the one to access the remote config file. In that case, the user
1495 * can supply the username in the config file name with /user=.
1497 if(trial
== 0L && !altuserforcache
){
1498 strncpy(user
, (*mb
->user
) ? mb
->user
:
1499 ps_global
->VAR_USER_ID
? ps_global
->VAR_USER_ID
: "",
1501 user
[NETMAXUSER
-1] = '\0';
1503 /* try last working password associated with this host. */
1504 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1505 (mb
->sslflag
||mb
->tlsflag
))){
1506 dprint((9, "mm_login: found a password to try\n"));
1507 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1508 ps_global
->in_init_seq
= save_in_init
;
1512 #ifdef LOCAL_PASSWD_CACHE
1513 /* check to see if there's a password left over from last session */
1514 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1515 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1516 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1517 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1518 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1519 (mb
->sslflag
||mb
->tlsflag
));
1520 dprint((9, "mm_login: found a password in passfile to try\n"));
1521 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1522 ps_global
->in_init_seq
= save_in_init
;
1525 #endif /* LOCAL_PASSWD_CACHE */
1528 * If no explicit user name supplied and we've not logged in
1529 * with our local user name, see if we've visited this
1530 * host before as someone else.
1533 ((last
= imap_get_user(mm_login_list
, hostlist
))
1534 #ifdef LOCAL_PASSWD_CACHE
1536 (last
= get_passfile_user(ps_global
->pinerc
, hostlist
))
1537 #endif /* LOCAL_PASSWD_CACHE */
1539 strncpy(user
, last
, NETMAXUSER
);
1540 user
[NETMAXUSER
-1] = '\0';
1541 dprint((9, "mm_login: found user=%s\n",
1542 user
? user
: "?"));
1544 /* try last working password associated with this host/user. */
1545 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1546 (mb
->sslflag
||mb
->tlsflag
))){
1548 "mm_login: found a password for user=%s to try\n",
1549 user
? user
: "?"));
1550 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1551 ps_global
->in_init_seq
= save_in_init
;
1555 #ifdef LOCAL_PASSWD_CACHE
1556 /* check to see if there's a password left over from last session */
1557 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1558 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1559 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1560 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1561 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1562 (mb
->sslflag
||mb
->tlsflag
));
1564 "mm_login: found a password for user=%s in passfile to try\n",
1565 user
? user
: "?"));
1566 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1567 ps_global
->in_init_seq
= save_in_init
;
1570 #endif /* LOCAL_PASSWD_CACHE */
1573 #if !defined(DOS) && !defined(OS2)
1574 if(!*mb
->user
&& !*user
&&
1575 (last
= (ps_global
->ui
.login
&& ps_global
->ui
.login
[0])
1576 ? ps_global
->ui
.login
: NULL
)
1578 strncpy(user
, last
, NETMAXUSER
);
1579 user
[NETMAXUSER
-1] = '\0';
1580 dprint((9, "mm_login: found user=%s\n",
1581 user
? user
: "?"));
1583 /* try last working password associated with this host. */
1584 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1585 (mb
->sslflag
||mb
->tlsflag
))){
1586 dprint((9, "mm_login:ui: found a password to try\n"));
1587 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1588 ps_global
->in_init_seq
= save_in_init
;
1592 #ifdef LOCAL_PASSWD_CACHE
1593 /* check to see if there's a password left over from last session */
1594 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1595 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1596 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1597 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1598 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1599 (mb
->sslflag
||mb
->tlsflag
));
1600 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1601 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1602 ps_global
->in_init_seq
= save_in_init
;
1605 #endif /* LOCAL_PASSWD_CACHE */
1610 user
[NETMAXUSER
-1] = '\0';
1616 * Even if we have a user now, user gets a chance to change it.
1618 ps_global
->mangled_footer
= 1;
1619 if(!*mb
->user
&& !altuserforcache
){
1624 * Instead of offering user with a value that the user can edit,
1625 * we offer [user] as a default so that the user can type CR to
1626 * use it. Otherwise, the user has to type in whole name.
1628 strncpy(defuser
, user
, sizeof(defuser
)-1);
1629 defuser
[sizeof(defuser
)-1] = '\0';
1633 * Need space for "Retrying - "
1639 * about 15 chars for input
1642 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1643 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1644 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1646 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1647 hostname
[sizeof(hostname
)-1] = '\0';
1650 * Add port number to hostname if going through a tunnel or something
1653 strncpy(port
, non_def_port
, sizeof(port
));
1658 /* if not encrypted and SSL/TLS is supported */
1659 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1660 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1661 strncpy(insecure
, insec
, sizeof(insecure
));
1663 /* TRANSLATORS: user is being asked to type in their login name */
1664 snprintf(logleadin
, sizeof(logleadin
), " %s", _("ENTER LOGIN NAME"));
1666 snprintf(defubuf
, sizeof(defubuf
), "%s%s%s : ", (*defuser
) ? " [" : "",
1667 (*defuser
) ? defuser
: "",
1668 (*defuser
) ? "]" : "");
1669 defubuf
[sizeof(defubuf
)-1] = '\0';
1670 /* space reserved after prompt */
1671 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1673 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1674 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1675 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) + oespace
;
1677 /* If we're retrying cut the hostname back to the first word. */
1678 if(avail
< need
&& trial
> 0){
1681 len
= strlen(hostname
);
1682 if((p
= strchr(hostname
, '.')) != NULL
){
1684 need
-= (len
- strlen(hostname
));
1689 need
-= utf8_width(retry
);
1694 /* reduce length of logleadin */
1695 len
= utf8_width(logleadin
);
1696 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1697 longer version doesn't fit on screen */
1698 snprintf(logleadin
, sizeof(logleadin
), " %s", _("LOGIN"));
1699 need
-= (len
- utf8_width(logleadin
));
1702 /* get two spaces from hostleadin */
1703 len
= utf8_width(hostleadin
);
1704 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
1705 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
1706 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1707 need
-= (len
- utf8_width(hostleadin
));
1709 /* get rid of port */
1710 if(avail
< need
&& strlen(port
) > 0){
1711 need
-= strlen(port
);
1719 * Reduce space for hostname. Best we can do is 6 chars
1722 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
1723 len
= strlen(hostname
);
1724 strncpy(hostname
+reduce_to
-3, "...", 4);
1725 need
-= (len
- strlen(hostname
));
1727 if(avail
< need
&& strlen(insecure
) > 0){
1728 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
1730 insecure
[strlen(insecure
)-4] = ')';
1731 insecure
[strlen(insecure
)-3] = '\0';
1734 need
-= utf8_width(insecure
);
1740 if(strlen(defubuf
) > 3){
1741 len
= strlen(defubuf
);
1742 strncpy(defubuf
, " [..] :", 9);
1743 need
-= (len
- strlen(defubuf
));
1747 strncpy(defubuf
, ":", 2);
1750 * If it still doesn't fit, optionally_enter gets
1751 * to worry about it.
1759 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s",
1760 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
);
1761 prompt
[sizeof(prompt
)-1] = '\0';
1765 mm_login_alt_cue(mb
);
1767 flags
= OE_APPEND_CURRENT
;
1768 save_dont_use
= ps_global
->dont_use_init_cmds
;
1769 ps_global
->dont_use_init_cmds
= 1;
1771 if(!*user
&& *defuser
){
1772 strncpy(user
, defuser
, NETMAXUSER
);
1773 user
[NETMAXUSER
-1] = '\0';
1776 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, pwd
, NETMAXPASSWD
,
1777 #ifdef LOCAL_PASSWD_CACHE
1778 is_using_passfile() ? 1 :
1779 #endif /* LOCAL_PASSWD_CACHE */
1780 0, 0, &preserve_password
);
1781 ps_global
->dont_use_init_cmds
= save_dont_use
;
1782 if(rc
== 0 && *user
&& *pwd
)
1784 #else /* !_WINDOWS */
1785 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
1786 prompt
, NULL
, help
, &flags
);
1787 #endif /* !_WINDOWS */
1788 ps_global
->dont_use_init_cmds
= save_dont_use
;
1791 help
= help
== NO_HELP
? h_oe_login
: NO_HELP
;
1796 if(rc
== 0 && !*user
){
1797 strncpy(user
, defuser
, NETMAXUSER
);
1798 user
[NETMAXUSER
-1] = '\0';
1805 if(rc
== 1 || !user
[0]) {
1806 ps_global
->user_says_cancel
= (rc
== 1);
1811 strncpy(user
, mb
->user
, NETMAXUSER
);
1812 user
[NETMAXUSER
-1] = '\0';
1815 user
[NETMAXUSER
-1] = '\0';
1817 if(!(user
[0] || altuserforcache
)){
1818 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1819 ps_global
->in_init_seq
= save_in_init
;
1824 * Now that we have a user, we can check in the cache again to see
1825 * if there is a password there. Try last working password associated
1826 * with this host and user.
1828 if(trial
== 0L && !*mb
->user
&& !altuserforcache
){
1829 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1830 (mb
->sslflag
||mb
->tlsflag
))){
1831 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1832 ps_global
->in_init_seq
= save_in_init
;
1836 #ifdef LOCAL_PASSWD_CACHE
1837 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1838 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1839 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1840 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1841 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1842 ps_global
->in_init_seq
= save_in_init
;
1845 #endif /* LOCAL_PASSWD_CACHE */
1847 else if(trial
== 0 && altuserforcache
){
1848 if(imap_get_passwd(mm_login_list
, pwd
, altuserforcache
, hostlist
,
1849 (mb
->sslflag
||mb
->tlsflag
))){
1850 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1851 ps_global
->in_init_seq
= save_in_init
;
1855 #ifdef LOCAL_PASSWD_CACHE
1856 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1857 altuserforcache
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1858 imap_set_passwd(&mm_login_list
, *pwd
, altuserforcache
,
1859 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1860 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1861 ps_global
->in_init_seq
= save_in_init
;
1864 #endif /* LOCAL_PASSWD_CACHE */
1868 * Didn't find password in cache or this isn't the first try. Ask user.
1873 * Need space for "Retrying - "
1879 * " ENTER PASSWORD: "
1880 * about 15 chars for input
1883 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1884 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1886 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1887 hostname
[sizeof(hostname
)-1] = '\0';
1890 * Add port number to hostname if going through a tunnel or something
1893 strncpy(port
, non_def_port
, sizeof(port
));
1899 /* if not encrypted and SSL/TLS is supported */
1900 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1901 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1902 strncpy(insecure
, insec
, sizeof(insecure
));
1905 strncpy(logleadin
, usethisprompt
, sizeof(logleadin
));
1906 logleadin
[sizeof(logleadin
)-1] = '\0';
1911 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
1913 strncpy(defubuf
, user
, sizeof(defubuf
)-1);
1914 defubuf
[sizeof(defubuf
)-1] = '\0';
1917 /* TRANSLATORS: user is being asked to type in their password */
1918 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("ENTER PASSWORD"));
1920 /* space reserved after prompt */
1921 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1923 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1924 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1925 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) +
1926 utf8_width(pwleadin
) + oespace
;
1928 if(avail
< need
&& trial
> 0){
1931 len
= strlen(hostname
);
1932 if((p
= strchr(hostname
, '.')) != NULL
){
1934 need
-= (len
- strlen(hostname
));
1939 need
-= utf8_width(retry
);
1945 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
1949 rplstr(pwleadin
, sizeof(pwleadin
), 1, "");
1953 /* get two spaces from hostleadin */
1954 len
= utf8_width(hostleadin
);
1955 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
1956 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
1957 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1958 need
-= (len
- utf8_width(hostleadin
));
1960 /* get rid of port */
1961 if(avail
< need
&& strlen(port
) > 0){
1962 need
-= strlen(port
);
1967 len
= utf8_width(pwleadin
);
1968 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
1969 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("PASSWORD"));
1970 need
-= (len
- utf8_width(pwleadin
));
1978 * Reduce space for hostname. Best we can do is 6 chars
1981 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
1982 len
= strlen(hostname
);
1983 strncpy(hostname
+reduce_to
-3, "...", 4);
1984 need
-= (len
- strlen(hostname
));
1986 if(avail
< need
&& strlen(insecure
) > 0){
1987 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
1989 insecure
[strlen(insecure
)-4] = ')';
1990 insecure
[strlen(insecure
)-3] = '\0';
1993 need
-= utf8_width(insecure
);
1999 len
= utf8_width(logleadin
);
2000 strncpy(logleadin
, " ", sizeof(logleadin
));
2001 logleadin
[sizeof(logleadin
)-1] = '\0';
2002 need
-= (len
- utf8_width(logleadin
));
2005 reduce_to
= (need
- avail
< strlen(defubuf
) - 6) ? (strlen(defubuf
)-(need
-avail
)) : 0;
2007 strncpy(defubuf
+reduce_to
-3, "...", 4);
2016 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s%s",
2017 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
, pwleadin
);
2018 prompt
[sizeof(prompt
)-1] = '\0';
2023 mm_login_alt_cue(mb
);
2025 save_dont_use
= ps_global
->dont_use_init_cmds
;
2026 ps_global
->dont_use_init_cmds
= 1;
2027 flags
= F_ON(F_QUELL_ASTERISKS
, ps_global
) ? OE_PASSWD_NOAST
: OE_PASSWD
;
2028 flags
|= OE_KEEP_TRAILING_SPACE
;
2030 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, tmp
, NETMAXPASSWD
, 0, 1,
2031 &preserve_password
);
2032 #else /* !_WINDOWS */
2033 rc
= optionally_enter(tmp
, q_line
, 0, NETMAXPASSWD
,
2034 prompt
, NULL
, help
, &flags
);
2035 #endif /* !_WINDOWS */
2036 if(rc
!= 1) *pwd
= cpystr(tmp
);
2037 ps_global
->dont_use_init_cmds
= save_dont_use
;
2040 help
= help
== NO_HELP
? h_oe_passwd
: NO_HELP
;
2048 if(rc
== 1 || !tmp
[0]) {
2049 ps_global
->user_says_cancel
= (rc
== 1);
2051 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2052 ps_global
->in_init_seq
= save_in_init
;
2059 /* remember the password for next time */
2060 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
2061 imap_set_passwd(&mm_login_list
, *pwd
,
2062 altuserforcache
? altuserforcache
: user
, hostlist
,
2063 (mb
->sslflag
||mb
->tlsflag
), 0, 0);
2064 #ifdef LOCAL_PASSWD_CACHE
2065 /* if requested, remember it on disk for next session */
2066 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
2067 set_passfile_passwd(ps_global
->pinerc
, *pwd
,
2068 altuserforcache
? altuserforcache
: user
, hostlist
,
2069 (mb
->sslflag
||mb
->tlsflag
),
2070 (preserve_password
== -1 ? 0
2071 : (preserve_password
== 0 ? 2 :1)));
2072 #endif /* LOCAL_PASSWD_CACHE */
2074 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2079 mm_login_alt_cue(NETMBX
*mb
)
2081 if(ps_global
->ttyo
){
2084 lastc
= pico_set_colors(ps_global
->VAR_TITLE_FORE_COLOR
,
2085 ps_global
->VAR_TITLE_BACK_COLOR
,
2088 mark_titlebar_dirty();
2089 PutLine0(0, ps_global
->ttyo
->screen_cols
- 1,
2090 (mb
->sslflag
||mb
->tlsflag
) ? "+" : " ");
2093 (void)pico_set_colorp(lastc
, PSC_NONE
);
2094 free_color_pair(&lastc
);
2102 /*----------------------------------------------------------------------
2103 Receive notification of an error writing to disk
2105 Args: stream -- The stream the error occurred on
2106 errcode -- The system error code (errno)
2107 serious -- Flag indicating error is serious (mail may be lost)
2109 Result: If error is non serious, the stream is marked as having an error
2110 and deletes are disallowed until error clears
2111 If error is serious this goes modal, allowing the user to retry
2112 or get a shell escape to fix the condition. When the condition is
2113 serious it means that mail existing in the mailbox will be lost
2114 if Pine exits without writing, so we try to induce the user to
2115 fix the error, go get someone that can fix the error, or whatever
2116 and don't provide an easy way out.
2119 mm_diskerror (MAILSTREAM
*stream
, long int errcode
, long int serious
)
2123 static ESCKEY_S de_opts
[] = {
2124 {'r', 'r', "R", "Retry"},
2125 {'f', 'f', "F", "FileBrowser"},
2126 {'s', 's', "S", "ShellPrompt"},
2129 #define DE_COLS (ps_global->ttyo->screen_cols)
2130 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2132 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2134 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2135 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2137 "The reported error number is %s. The last reported mail error was:"
2138 static char *de_msg
[] = {
2139 "Please try to correct the error preventing Alpine from saving your",
2140 "mail folder. For example if the disk is out of space try removing",
2141 "unneeded files. You might also contact your system administrator.",
2143 "Both Alpine's File Browser and an option to enter the system's",
2144 "command prompt are offered to aid in fixing the problem. When",
2145 "you believe the problem is resolved, choose the \"Retry\" option.",
2146 "Be aware that messages may be lost or this folder left in an",
2147 "inaccessible condition if you exit or kill Alpine before the problem",
2150 static char *de_shell_msg
[] = {
2151 "\n\nPlease attempt to correct the error preventing saving of the",
2152 "mail folder. If you do not know how to correct the problem, contact",
2153 "your system administrator. To return to Alpine, type \"exit\".",
2157 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2158 DE_FOLDER(stream
), errcode
, serious
? "" : "not "));
2159 dprint((0, "***** message: \"%s\"\n\n",
2160 ps_global
->last_error
? ps_global
->last_error
: "?"));
2163 sp_set_io_error_on_stream(stream
, 1);
2168 /* replace pine's body display with screen full of explanatory text */
2170 PutLine1(2, MAX((DE_COLS
- sizeof(DE_STR1
)
2171 - strlen(DE_FOLDER(stream
)))/2, 0),
2172 DE_STR1
, DE_FOLDER(stream
));
2174 PutLine1(3, 4, DE_STR2
, long2string(errcode
));
2176 PutLine0(4, 0, " \"");
2177 removing_leading_white_space(ps_global
->last_error
);
2178 for(i
= 4, p
= ps_global
->last_error
; *p
&& i
< DE_LINE
; ){
2179 for(s
= NULL
, q
= p
; *q
&& q
- p
< DE_COLS
- 16; q
++)
2180 if(isspace((unsigned char)*q
))
2191 PutLine0(i
, 0, " ");
2192 while(*p
&& isspace((unsigned char)*p
))
2203 for(j
= ++i
; i
< DE_LINE
&& de_msg
[i
-j
]; i
++){
2205 PutLine0(i
, 0, " ");
2206 Write_to_screen(de_msg
[i
-j
]);
2212 switch(radio_buttons(DE_PMT
, -FOOTER_ROWS(ps_global
), de_opts
,
2213 'r', 0, NO_HELP
, RB_FLUSH_IN
| RB_NO_NEWMAIL
)){
2214 case 'r' : /* Retry! */
2215 ps_global
->mangled_screen
= 1;
2218 case 'f' : /* File Browser */
2220 char full_filename
[MAXPATH
+1], filename
[MAXPATH
+1];
2223 build_path(full_filename
, ps_global
->home_dir
, filename
,
2224 sizeof(full_filename
));
2225 file_lister("DISK ERROR", full_filename
, sizeof(full_filename
),
2226 filename
, sizeof(filename
), FALSE
, FB_SAVE
);
2233 end_keyboard(ps_global
? F_ON(F_USE_FK
,ps_global
) : 0);
2234 end_tty_driver(ps_global
);
2235 for(i
= 0; de_shell_msg
[i
]; i
++)
2236 puts(de_shell_msg
[i
]);
2239 * Don't use our piping mechanism to spawn a subshell here
2240 * since it will the server (thus reentering c-client).
2247 init_tty_driver(ps_global
);
2248 init_keyboard(F_ON(F_USE_FK
,ps_global
));
2252 if(ps_global
->redrawer
)
2253 (*ps_global
->redrawer
)();
2259 pine_tcptimeout_noscreen(long int elapsed
, long int sincelast
, char *host
)
2268 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2269 snprintf(pmt
, sizeof(pmt
),
2270 _("No reply in %s seconds from server %s. Break connection"),
2271 long2string(elapsed
), host
);
2272 pmt
[sizeof(pmt
)-1] = '\0';
2273 if(want_to(pmt
, 'n', 'n', NO_HELP
, WT_FLUSH_IN
) == 'y'){
2274 ps_global
->user_says_cancel
= 1;
2279 ps_global
->tcptimeout
= 0;
2285 * -------------------------------------------------------------
2286 * These are declared in pith/imap.h as mandatory to implement.
2287 * -------------------------------------------------------------
2292 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2295 pine_tcptimeout(long int elapsed
, long int sincelast
, char *host
)
2297 long rv
= 1L; /* keep trying by default */
2300 ps_global
->tcptimeout
= 1;
2302 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2303 long2string(elapsed
), host
));
2312 if(ps_global
->noshow_timeout
)
2315 if(ps_global
->can_interrupt
2316 && ps_global
->close_connection_timeout
> 0L
2317 && elapsed
>= (long)ps_global
->tcp_query_timeout
2318 && elapsed
>= (long)ps_global
->close_connection_timeout
){
2319 ps_global
->can_interrupt
= 0; /* do not return here */
2320 ps_global
->read_bail
= 0;
2321 ps_global
->user_says_cancel
= 1;
2325 if(!ps_global
->ttyo
)
2326 return(pine_tcptimeout_noscreen(elapsed
, sincelast
, host
));
2331 * Prompt after a minute (since by then things are probably really bad)
2332 * A prompt timeout means "keep trying"...
2334 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2337 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2338 if((clear_inverse
= !InverseState()) != 0)
2343 PutLine2(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
), 0,
2344 _("No reply in %s seconds from server %s. Break connection?"),
2345 long2string(elapsed
), host
);
2350 if(ps_global
->read_bail
|| ch
== 'y' || ch
== 'Y'){
2351 ps_global
->read_bail
= 0;
2352 ps_global
->user_says_cancel
= 1;
2359 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2362 if(rv
== 1L){ /* just warn 'em something's up */
2363 q_status_message2(SM_ORDER
, 0, 0,
2364 _("No reply in %s seconds from server %s. Still Waiting..."),
2365 long2string(elapsed
), host
);
2366 flush_status_messages(0); /* make sure it's seen */
2369 mark_status_dirty(); /* make sure it gets cleared */
2371 resume_busy_cue((rv
== 1) ? 3 : 0);
2372 ps_global
->tcptimeout
= 0;
2377 QUOTALIST
*pine_quotalist_copy (QUOTALIST
*pquota
)
2379 QUOTALIST
*cquota
= NULL
;
2382 cquota
= mail_newquotalist();
2383 if (pquota
->name
&& *pquota
->name
)
2384 cquota
->name
= cpystr(pquota
->name
);
2385 cquota
->usage
= pquota
->usage
;
2386 cquota
->limit
= pquota
->limit
;
2388 cquota
->next
= pine_quotalist_copy(pquota
->next
);
2394 /* c-client callback to handle quota */
2397 pine_parse_quota (MAILSTREAM
*stream
, unsigned char *msg
, QUOTALIST
*pquota
)
2399 ps_global
->quota
= pine_quotalist_copy (pquota
);
2403 * C-client callback to handle SSL/TLS certificate validation failures
2405 * Returning 0 means error becomes fatal
2406 * Non-zero means certificate problem is ignored and SSL session is
2409 * We remember the answer and won't re-ask for subsequent open attempts to
2410 * the same hostname.
2413 pine_sslcertquery(char *reason
, char *host
, char *cert
)
2416 char *unknown
= "<unknown>";
2419 int ok_novalidate
= 0, warned
= 0;
2421 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2422 host
? host
: "?", reason
? reason
: "?",
2423 cert
? cert
: "?"));
2425 hostlist
.name
= host
? host
: "";
2426 hostlist
.next
= NULL
;
2429 * See if we've been asked about this host before.
2431 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2432 /* we were asked before, did we say Yes? */
2438 "sslcertificatequery: approved automatically\n"));
2442 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2445 if(ps_global
->ttyo
){
2447 STORE_S
*in_store
, *out_store
;
2449 HANDLE_S
*handles
= NULL
;
2450 int the_answer
= 'n';
2452 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
2453 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2456 so_puts(in_store
, "<HTML><P>");
2457 so_puts(in_store
, _("There was a failure validating the SSL/TLS certificate for the server"));
2459 so_puts(in_store
, "<P><CENTER>");
2460 so_puts(in_store
, host
? host
: unknown
);
2461 so_puts(in_store
, "</CENTER>");
2463 so_puts(in_store
, "<P>");
2464 so_puts(in_store
, _("The reason for the failure was"));
2466 /* squirrel away details */
2468 fs_give((void **)&details_host
);
2470 fs_give((void **)&details_reason
);
2472 fs_give((void **)&details_cert
);
2474 details_host
= cpystr(host
? host
: unknown
);
2475 details_reason
= cpystr(reason
? reason
: unknown
);
2476 details_cert
= cpystr(cert
? cert
: unknown
);
2478 so_puts(in_store
, "<P><CENTER>");
2479 snprintf(tmp
, sizeof(tmp
), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2480 reason
? reason
: unknown
);
2481 tmp
[sizeof(tmp
)-1] = '\0';
2483 so_puts(in_store
, tmp
);
2484 so_puts(in_store
, "</CENTER>");
2486 so_puts(in_store
, "<P>");
2487 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."));
2489 so_puts(in_store
, "<P>");
2490 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"));
2492 so_puts(in_store
, "<P><CENTER>");
2493 so_puts(in_store
, "/novalidate-cert");
2494 so_puts(in_store
, "</CENTER>");
2496 so_puts(in_store
, "<P>");
2497 so_puts(in_store
, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2499 so_puts(in_store
, "<P><CENTER>");
2500 so_puts(in_store
, host
? host
: unknown
);
2501 so_puts(in_store
, "</CENTER>");
2503 so_puts(in_store
, "<P>");
2504 so_puts(in_store
, _("in your configuration, replace those characters with"));
2506 so_puts(in_store
, "<P><CENTER>");
2507 so_puts(in_store
, host
? host
: unknown
);
2508 so_puts(in_store
, "/novalidate-cert");
2509 so_puts(in_store
, "</CENTER>");
2511 so_puts(in_store
, "<P>");
2512 so_puts(in_store
, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2514 so_seek(in_store
, 0L, 0);
2515 init_handles(&handles
);
2517 gf_link_filter(gf_html2plain
,
2518 gf_html2plain_opt(NULL
,
2519 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
2520 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
2521 gf_set_so_readc(&gc
, in_store
);
2522 gf_set_so_writec(&pc
, out_store
);
2524 gf_clear_so_writec(out_store
);
2525 gf_clear_so_readc(in_store
);
2527 memset(&sargs
, 0, sizeof(SCROLL_S
));
2528 sargs
.text
.handles
= handles
;
2529 sargs
.text
.text
= so_text(out_store
);
2530 sargs
.text
.src
= CharStar
;
2531 sargs
.text
.desc
= _("help text");
2532 sargs
.bar
.title
= _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2533 sargs
.proc
.tool
= answer_cert_failure
;
2534 sargs
.proc
.data
.p
= (void *)&the_answer
;
2535 sargs
.keys
.menu
= &ans_certquery_keymenu
;
2536 /* don't want to re-enter c-client */
2537 sargs
.quell_newmail
= 1;
2538 setbitmap(sargs
.keys
.bitmap
);
2539 sargs
.help
.text
= h_tls_validation_failure
;
2540 sargs
.help
.title
= _("HELP FOR CERT VALIDATION FAILURE");
2544 if(the_answer
== 'y')
2546 else if(the_answer
== 'n')
2547 ps_global
->user_says_cancel
= 1;
2549 ps_global
->mangled_screen
= 1;
2550 ps_global
->painted_body_on_startup
= 0;
2551 ps_global
->painted_footer_on_startup
= 0;
2553 so_give(&out_store
);
2554 free_handles(&handles
);
2556 fs_give((void **)&details_host
);
2558 fs_give((void **)&details_reason
);
2560 fs_give((void **)&details_cert
);
2564 * If screen hasn't been initialized yet, use want_to.
2567 memset((void *)tmp
, 0, sizeof(tmp
));
2569 reason
? reason
: _("SSL/TLS certificate validation failure"),
2571 tmp
[sizeof(tmp
)-1] = '\0';
2572 strncat(tmp
, _(": Continue anyway "), sizeof(tmp
)-strlen(tmp
)-1);
2574 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y')
2579 q_status_message1(SM_ORDER
, 1, 3, _("Open of %s cancelled"),
2580 host
? host
: unknown
);
2582 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, rv
? 1 : 0, 0);
2584 dprint((5, "sslcertificatequery: %s\n",
2585 rv
? "approved" : "rejected"));
2592 pine_newsrcquery(MAILSTREAM
*stream
, char *mulname
, char *name
)
2594 char buf
[MAILTMPLEN
];
2596 if((can_access(mulname
, ACCESS_EXISTS
) == 0)
2597 || !(can_access(name
, ACCESS_EXISTS
) == 0))
2600 snprintf(buf
, sizeof(buf
),
2601 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2603 strlen(last_cmpnt(name
)) > 15 ? "..." : "");
2604 buf
[sizeof(buf
)-1] = '\0';
2605 if(want_to(buf
, 'n', 'n', NO_HELP
, WT_NORM
) == 'y')
2606 rename_file(name
, mulname
);
2612 url_local_certdetails(char *url
)
2614 if(!struncmp(url
, "x-alpine-cert:", 14)){
2619 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
2620 q_status_message(SM_ORDER
| SM_DING
, 7, 10,
2621 _("Error allocating space for details."));
2625 so_puts(store
, _("Host given by user:\n\n "));
2626 so_puts(store
, details_host
);
2627 so_puts(store
, _("\n\nReason for failure:\n\n "));
2628 so_puts(store
, details_reason
);
2629 so_puts(store
, _("\n\nCertificate being verified:\n\n"));
2630 folded
= fold(details_cert
, ps_global
->ttyo
->screen_cols
, ps_global
->ttyo
->screen_cols
, " ", " ", FLD_NONE
);
2631 so_puts(store
, folded
);
2632 fs_give((void **)&folded
);
2633 so_puts(store
, "\n");
2635 memset(&sargs
, 0, sizeof(SCROLL_S
));
2636 sargs
.text
.text
= so_text(store
);
2637 sargs
.text
.src
= CharStar
;
2638 sargs
.text
.desc
= _("Details");
2639 sargs
.bar
.title
= _("CERT VALIDATION DETAILS");
2640 sargs
.help
.text
= NO_HELP
;
2641 sargs
.help
.title
= NULL
;
2642 sargs
.quell_newmail
= 1;
2643 sargs
.help
.text
= h_tls_failure_details
;
2644 sargs
.help
.title
= _("HELP FOR CERT VALIDATION DETAILS");
2648 so_give(&store
); /* free resources associated with store */
2649 ps_global
->mangled_screen
= 1;
2658 * C-client callback to handle SSL/TLS certificate validation failures
2661 pine_sslfailure(char *host
, char *reason
, long unsigned int flags
)
2665 int the_answer
= 'n', indent
, len
, cols
;
2666 char buf
[500], buf2
[500];
2668 char *hst
= host
? host
: "<unknown>";
2669 char *rsn
= reason
? reason
: "<unknown>";
2670 char *notls
= "/notls";
2672 int ok_novalidate
= 0, warned
= 0;
2675 dprint((1, "sslfailure: host=%s reason=%s\n",
2679 if(flags
& NET_SILENT
)
2682 hostlist
.name
= host
? host
: "";
2683 hostlist
.next
= NULL
;
2686 * See if we've been told about this host before.
2688 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2689 /* we were told already */
2691 snprintf(buf
, sizeof(buf
), _("SSL/TLS failure for %s: %s"), hst
, rsn
);
2692 buf
[sizeof(buf
)-1] = '\0';
2698 cols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
2701 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2704 strncpy(buf
, _("There was an SSL/TLS failure for the server"), sizeof(buf
));
2705 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2706 so_puts(store
, folded
);
2707 fs_give((void **)&folded
);
2708 so_puts(store
, "\n");
2710 if((len
=strlen(hst
)) <= cols
){
2711 if((indent
=((cols
-len
)/2)) > 0)
2712 so_puts(store
, repeat_char(indent
, SPACE
));
2714 so_puts(store
, hst
);
2715 so_puts(store
, "\n");
2718 strncpy(buf
, hst
, sizeof(buf
));
2719 buf
[sizeof(buf
)-1] = '\0';
2720 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2721 so_puts(store
, folded
);
2722 fs_give((void **)&folded
);
2725 so_puts(store
, "\n");
2727 strncpy(buf
, _("The reason for the failure was"), sizeof(buf
));
2728 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2729 so_puts(store
, folded
);
2730 fs_give((void **)&folded
);
2731 so_puts(store
, "\n");
2733 if((len
=strlen(rsn
)) <= cols
){
2734 if((indent
=((cols
-len
)/2)) > 0)
2735 so_puts(store
, repeat_char(indent
, SPACE
));
2737 so_puts(store
, rsn
);
2738 so_puts(store
, "\n");
2741 strncpy(buf
, rsn
, sizeof(buf
));
2742 buf
[sizeof(buf
)-1] = '\0';
2743 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2744 so_puts(store
, folded
);
2745 fs_give((void **)&folded
);
2748 so_puts(store
, "\n");
2750 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
));
2751 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2752 so_puts(store
, folded
);
2753 fs_give((void **)&folded
);
2754 so_puts(store
, "\n");
2756 if((len
=strlen(notls
)) <= cols
){
2757 if((indent
=((cols
-len
)/2)) > 0)
2758 so_puts(store
, repeat_char(indent
, SPACE
));
2760 so_puts(store
, notls
);
2761 so_puts(store
, "\n");
2764 strncpy(buf
, notls
, sizeof(buf
));
2765 buf
[sizeof(buf
)-1] = '\0';
2766 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2767 so_puts(store
, folded
);
2768 fs_give((void **)&folded
);
2771 so_puts(store
, "\n");
2773 strncpy(buf
, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2775 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2776 so_puts(store
, folded
);
2777 fs_give((void **)&folded
);
2778 so_puts(store
, "\n");
2780 if((len
=strlen(hst
)) <= cols
){
2781 if((indent
=((cols
-len
)/2)) > 0)
2782 so_puts(store
, repeat_char(indent
, SPACE
));
2784 so_puts(store
, hst
);
2785 so_puts(store
, "\n");
2788 strncpy(buf
, hst
, sizeof(buf
));
2789 buf
[sizeof(buf
)-1] = '\0';
2790 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2791 so_puts(store
, folded
);
2792 fs_give((void **)&folded
);
2795 so_puts(store
, "\n");
2797 strncpy(buf
, _("in your configuration, replace those characters with"),
2799 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2800 so_puts(store
, folded
);
2801 fs_give((void **)&folded
);
2802 so_puts(store
, "\n");
2804 snprintf(buf2
, sizeof(buf2
), "%s%s", hst
, notls
);
2805 buf2
[sizeof(buf2
)-1] = '\0';
2806 if((len
=strlen(buf2
)) <= cols
){
2807 if((indent
=((cols
-len
)/2)) > 0)
2808 so_puts(store
, repeat_char(indent
, SPACE
));
2810 so_puts(store
, buf2
);
2811 so_puts(store
, "\n");
2814 strncpy(buf
, buf2
, sizeof(buf
));
2815 buf
[sizeof(buf
)-1] = '\0';
2816 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2817 so_puts(store
, folded
);
2818 fs_give((void **)&folded
);
2821 so_puts(store
, "\n");
2823 if(ps_global
->ttyo
){
2824 strncpy(buf
, _("Type RETURN to continue."), sizeof(buf
));
2825 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2826 so_puts(store
, folded
);
2827 fs_give((void **)&folded
);
2830 memset(&sargs
, 0, sizeof(SCROLL_S
));
2831 sargs
.text
.text
= so_text(store
);
2832 sargs
.text
.src
= CharStar
;
2833 sargs
.text
.desc
= _("help text");
2834 sargs
.bar
.title
= _("SSL/TLS FAILURE");
2835 sargs
.proc
.tool
= answer_cert_failure
;
2836 sargs
.proc
.data
.p
= (void *)&the_answer
;
2837 sargs
.keys
.menu
= &ans_certfail_keymenu
;
2838 setbitmap(sargs
.keys
.bitmap
);
2839 /* don't want to re-enter c-client */
2840 sargs
.quell_newmail
= 1;
2841 sargs
.help
.text
= h_tls_failure
;
2842 sargs
.help
.title
= _("HELP FOR TLS/SSL FAILURE");
2853 * The screen isn't initialized yet, which should mean that this
2854 * is the result of a -p argument. Display_args_err knows how to deal
2855 * with the uninitialized screen, so we mess with the data to get it
2856 * in shape for display_args_err. This is pretty hacky.
2859 so_seek(store
, 0L, 0); /* rewind */
2860 /* count the lines */
2861 while(so_readc(&c
, store
))
2865 qp
= q
= (char **)fs_get((cnt
+1) * sizeof(char *));
2866 memset(q
, 0, (cnt
+1) * sizeof(char *));
2868 so_seek(store
, 0L, 0); /* rewind */
2870 while(so_readc(&c
, store
)){
2873 *qp
++ = cpystr(buf
);
2880 display_args_err(NULL
, q
, 0);
2881 free_list_array(&q
);
2884 ps_global
->mangled_screen
= 1;
2885 ps_global
->painted_body_on_startup
= 0;
2886 ps_global
->painted_footer_on_startup
= 0;
2889 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, ok_novalidate
, 1);
2894 answer_cert_failure(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2898 ps_global
->next_screen
= SCREEN_FUN_NULL
;
2902 *(int *)(sparms
->proc
.data
.p
) = 'y';
2906 *(int *)(sparms
->proc
.data
.p
) = 'n';
2910 alpine_panic("Unexpected command in answer_cert_failure");
2919 oauth2_auth_answer(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2924 /* TRANSLATORS: user needs to input an access code from the server */
2925 char *accesscodelabel
= _("Copy and Paste Access Code");
2926 char token
[MAILTMPLEN
], prompt
[MAILTMPLEN
];
2928 ps_global
->next_screen
= SCREEN_FUN_NULL
;
2933 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
2934 flags
= OE_APPEND_CURRENT
;
2935 sprintf(prompt
, "%s: ", accesscodelabel
);
2937 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
2938 prompt
, NULL
, NO_HELP
, &flags
);
2939 } while (rc
!= 0 && rc
!= 1);
2940 user
.code
= rc
== 0 ? cpystr(token
) : NULL
;
2942 rv
= rc
== 1 ? 0 : 1;
2951 alpine_panic("Unexpected command in oauth2_auth_answer");
2954 *(AUTH_CODE_S
*) sparms
->proc
.data
.p
= user
;
2959 /*----------------------------------------------------------------------
2960 This can be used to prevent the flickering of the check_cue char
2961 caused by numerous (5000+) fetches by c-client. Right now, the only
2962 practical use found is newsgroup subsciption.
2964 check_cue_display will check if this global is set, and won't clear
2965 the check_cue_char if set.
2968 set_read_predicted(int i
)
2970 ps_global
->read_predicted
= i
==1;
2972 if(!i
&& F_ON(F_SHOW_DELAY_CUE
, ps_global
))
2973 check_cue_display(" ");
2978 /*----------------------------------------------------------------------
2979 Exported method to retrieve logged in user name associated with stream
2981 Args: host -- host to find associated login name with.
2986 pine_block_notify(int reason
, void *data
)
2989 case BLOCK_SENSITIVE
: /* sensitive code, disallow alarms */
2992 case BLOCK_NONSENSITIVE
: /* non-sensitive code, allow alarms */
2995 case BLOCK_TCPWRITE
: /* blocked on TCP write */
2996 case BLOCK_FILELOCK
: /* blocked on file locking */
2998 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
2999 check_cue_display(">");
3001 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3005 case BLOCK_DNSLOOKUP
: /* blocked on DNS lookup */
3006 case BLOCK_TCPOPEN
: /* blocked on TCP open */
3007 case BLOCK_TCPREAD
: /* blocked on TCP read */
3008 case BLOCK_TCPCLOSE
: /* blocked on TCP close */
3010 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3011 check_cue_display("<");
3013 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3018 case BLOCK_NONE
: /* not blocked */
3020 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3021 check_cue_display(" ");
3032 mm_expunged_current(long unsigned int rawno
)
3034 /* expunged something we're viewing? */
3035 if(!ps_global
->expunge_in_progress
3036 && (mn_is_cur(ps_global
->msgmap
, mn_raw2m(ps_global
->msgmap
, (long) rawno
))
3037 && (ps_global
->prev_screen
== mail_view_screen
3038 || ps_global
->prev_screen
== attachment_screen
))){
3039 ps_global
->next_screen
= mail_index_screen
;
3040 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3041 "Message you were viewing is gone!");
3049 * Specific functions to support caching username/passwd/host
3050 * triples on disk for use from one session to the next...
3053 #define FIRSTCH 0x20
3055 #define TABSZ (LASTCH - FIRSTCH + 1)
3057 static int xlate_key
;
3061 * xlate_in() - xlate_in the given character
3069 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3070 eti
+= (c
- FIRSTCH
);
3071 eti
-= (eti
>= 2*TABSZ
) ? 2*TABSZ
: (eti
>= TABSZ
) ? TABSZ
: 0;
3072 return((xlate_key
= eti
) + FIRSTCH
);
3080 * xlate_out() - xlate_out the given character
3088 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3089 xch
= c
- (dti
= xlate_key
);
3090 xch
+= (xch
< FIRSTCH
-TABSZ
) ? 2*TABSZ
: (xch
< FIRSTCH
) ? TABSZ
: 0;
3091 dti
= (xch
- FIRSTCH
) + dti
;
3092 dti
-= (dti
>= 2*TABSZ
) ? 2*TABSZ
: (dti
>= TABSZ
) ? TABSZ
: 0;
3099 #endif /* PASSFILE */
3102 #ifdef LOCAL_PASSWD_CACHE
3106 line_get(char *tmp
, size_t len
, char **textp
)
3112 || (s
= strchr(*textp
, '\n')) == NULL
3113 || (s
- *textp
) > len
- 1)
3120 snprintf(tmp
, len
, "%s\n", *textp
);
3128 * Passfile lines are
3130 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3132 * In pine4.40 and before there was no orig_hostname, and there still isn't
3133 * if it is the same as hostname.
3136 * Use Windows credentials. The TargetName of the credential is
3137 * UWash_Alpine_<hostname:port>\tuser\taltflag
3138 * and the blob consists of
3139 * passwd\torighost (if different from host)
3141 * We don't use anything fancy we just copy out all the credentials which
3142 * begin with TNAME and put them into our cache, so we don't lookup based
3143 * on the TargetName or anything like that. That was so we could re-use
3144 * the existing code and so that orighost data could be easily used.
3147 read_passfile(pinerc
, l
)
3153 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3156 char *tmp
, *blob
, *target
= NULL
;
3157 char *host
, *user
, *sflags
, *passwd
, *orighost
;
3161 if(using_passfile
== 0)
3162 return(using_passfile
);
3165 if(init_wincred_funcs() != 1){
3167 return(using_passfile
);
3171 dprint((9, "read_passfile\n"));
3175 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
3177 for(k
= 0; k
< count
; k
++){
3179 host
= user
= sflags
= passwd
= orighost
= NULL
;
3180 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3182 target
= lptstr_to_utf8(pcred
[k
]->TargetName
);
3183 tmp
= srchstr(target
, TNAME
);
3186 tmp
+= strlen(TNAME
);
3187 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
3188 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3189 ; /* find end of data */
3192 tmp
[i
++] = '\0'; /* tie off data */
3200 blob
= (char *) pcred
[k
]->CredentialBlob
;
3202 for(i
= 0, j
= 3; blob
[i
] && j
< 5; j
++){
3203 for(ui
[j
] = &blob
[i
]; blob
[i
] && blob
[i
] != '\t'; i
++)
3204 ; /* find end of data */
3207 blob
[i
++] = '\0'; /* tie off data */
3214 if(passwd
&& host
&& user
){ /* valid field? */
3215 STRLIST_S hostlist
[2];
3218 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
3219 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
3220 hostlist
[0].name
= host
;
3222 hostlist
[0].next
= &hostlist
[1];
3223 hostlist
[1].name
= orighost
;
3224 hostlist
[1].next
= NULL
;
3227 hostlist
[0].next
= NULL
;
3230 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
3234 fs_give((void **) &target
);
3237 g_CredFree((PVOID
) pcred
);
3243 # else /* old windows */
3250 char target
[MAILTMPLEN
];
3251 char *tmp
, *host
, *user
, *sflags
, *passwd
, *orighost
;
3254 SecKeychainAttributeList attrList
;
3255 SecKeychainSearchRef searchRef
= NULL
;
3256 SecKeychainAttribute attrs
[] = {
3257 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
3260 if(using_passfile
== 0)
3261 return(using_passfile
);
3263 dprint((9, "read_passfile\n"));
3266 /* search for only our items in the keychain */
3268 attrList
.attr
= attrs
;
3271 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
3272 kSecGenericPasswordItemClass
,
3275 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3277 SecKeychainItemRef itemRef
= NULL
;
3278 SecKeychainAttributeInfo info
;
3279 SecKeychainAttributeList
*attrList
= NULL
;
3282 char *blobcopy
= NULL
; /* NULL terminated copy */
3284 UInt32 tags
[] = {kSecAccountItemAttr
,
3285 kSecServiceItemAttr
};
3286 UInt32 formats
[] = {0,0};
3288 dprint((10, "read_passfile: searchRef not NULL\n"));
3291 info
.format
= formats
;
3294 * Go through each item we found and put it
3297 while(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
)) && itemRef
){
3298 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3299 rc
= SecKeychainItemCopyAttributesAndData(itemRef
,
3304 if(rc
== 0 && attrList
){
3305 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList
->count
));
3307 blobcopy
= (char *) fs_get((blength
+ 1) * sizeof(char));
3308 strncpy(blobcopy
, (char *) blob
, blength
);
3309 blobcopy
[blength
] = '\0';
3312 * I'm not real clear on how this works. It seems to be
3313 * necessary to combine the attributes from two passes
3314 * (attrList->count == 2) in order to get the full set
3315 * of attributes we inserted into the keychain in the
3316 * first place. So, we reset host...orighost outside of
3317 * the following for loop, not inside.
3319 host
= user
= sflags
= passwd
= orighost
= NULL
;
3320 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3322 for(k
= 0; k
< attrList
->count
; k
++){
3324 if(attrList
->attr
[k
].length
){
3326 (char *) attrList
->attr
[k
].data
,
3327 MIN(attrList
->attr
[k
].length
,sizeof(target
)));
3328 target
[MIN(attrList
->attr
[k
].length
,sizeof(target
)-1)] = '\0';
3332 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
3333 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3334 ; /* find end of data */
3337 tmp
[i
++] = '\0'; /* tie off data */
3349 for(i
= 0, j
= 3; blobcopy
[i
] && j
< 5; j
++){
3350 for(ui
[j
] = &blobcopy
[i
]; blobcopy
[i
] && blobcopy
[i
] != '\t'; i
++)
3351 ; /* find end of data */
3354 blobcopy
[i
++] = '\0'; /* tie off data */
3363 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
:""));
3366 if(passwd
&& host
&& user
){ /* valid field? */
3367 STRLIST_S hostlist
[2];
3370 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
3371 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
3372 hostlist
[0].name
= host
;
3374 hostlist
[0].next
= &hostlist
[1];
3375 hostlist
[1].name
= orighost
;
3376 hostlist
[1].next
= NULL
;
3379 hostlist
[0].next
= NULL
;
3382 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
3386 fs_give((void **) & blobcopy
);
3388 SecKeychainItemFreeAttributesAndData(attrList
, (void *) blob
);
3392 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc
));
3399 CFRelease(searchRef
);
3403 dprint((10, "read_passfile: searchRef NULL\n"));
3408 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc
));
3411 return(using_passfile
);
3413 #else /* PASSFILE */
3415 char tmp
[MAILTMPLEN
], *ui
[5];
3416 int i
, j
, n
, rv
= 0;
3418 char *tmptext
= NULL
;
3421 char tmp2
[MAILTMPLEN
];
3422 char *text
= NULL
, *text2
= NULL
;
3427 if(using_passfile
== 0)
3428 return(using_passfile
);
3430 dprint((9, "read_passfile\n"));
3432 /* if there's no password to read, bag it!! */
3433 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb"))){
3435 return(using_passfile
);
3439 if(our_stat(tmp
, &sbuf
) == 0)
3441 fp
= our_fopen(tmp
, "rb"); /* reopen to read data */
3443 /* the next call initializes the key/certificate pair used to
3444 * encrypt and decrypt a password file. The details of how this is
3445 * done is in the file pith/smime.c. During this setup we might call
3446 * smime_init(), but no matter what happens we must call smime_deinit()
3447 * there. The reason why this is so is because we can not assume that
3448 * the .pinerc file has been read by this time, so this code might not
3449 * know about the ps_global->smime structure or any of its components,
3450 * and it shouldn't because it only needs ps_global->pwdcert, so
3451 * do not init smime here, because the .pinerc might not have been
3452 * read and we do not really know where the keys and certificates really
3454 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3455 * it is called for the first time and there are certificates at all,
3456 * or when it is called after the first time and the user refuses to
3457 * create a self-signed certificate. In this situation we will just
3458 * let the user live in an insecure world, but no more passwords will
3459 * be saved in the password file, and only those found there will be used.
3462 fgets(tmp2
, sizeof(tmp2
), fp
);
3464 if(strcmp(tmp2
, "-----BEGIN PKCS7-----\n")){
3465 /* there is an already existing password file, that is not encrypted
3466 * and there is no key to encrypt it yet, go again through setup_pwdcert
3467 * and encrypt it now.
3469 if(tmp2
[0]){ /* not empty, UNencrypted password file */
3470 if(ps_global
->pwdcert
== NULL
)
3471 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3472 if((rv
== 0 || rv
== -5) && ps_global
->pwdcert
== NULL
)
3473 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3474 if(ps_global
->pwdcert
== NULL
){
3475 q_status_message(SM_ORDER
, 3, 3,
3476 " Failed to create private key. Using UNencrypted Password file. ");
3481 q_status_message(SM_ORDER
, 3, 3,
3482 " Failed to unlock private key. Using UNencrypted Password file. ");
3483 save_password
= 0; /* do not save more passwords */
3486 if(ps_global
->pwdcert
!= NULL
3487 && encrypt_file((char *)tmp
, NULL
, (PERSONAL_CERT
*)ps_global
->pwdcert
))
3492 if(ps_global
->pwdcert
== NULL
)
3493 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3498 * if password file is encrypted we attempt to decrypt. We ask the
3499 * user for the password to unlock the password file. If the user
3500 * enters the password and it unlocks the file, use it and keep saving
3501 * passwords in it. If the user enters the wrong passwords and does
3502 * not unlock it, we will not see that here, but in decrypt_file, so
3503 * the only other possibility is that the user cancels. In that case
3504 * we will see i == -1. In that case, we will let the user attempt
3505 * manual login to the server they want to login, but passwords will
3506 * not be saved so that the password file will not be saved
3507 * unencrypted and rewritten again.
3510 text
= text2
= decrypt_file((char *)tmp
, &i
, (PERSONAL_CERT
*)ps_global
->pwdcert
);
3511 len
= text2
? strlen(text2
) : 0;
3513 case -2: using_passfile
= 0;
3516 case 1 : save_password
= 1;
3520 case -1: save_password
= 0;
3529 if(using_passfile
== 0){
3531 if(text
) fs_give((void **)&text
);
3533 return using_passfile
;
3537 tmptext
= fs_get(len
+ 1);
3539 for(n
= 0; encrypted
? line_get(tmptext
, len
+ 1, &text2
)
3540 : (fgets(tmptext
, len
+1, fp
) != NULL
); n
++){
3542 for(n
= 0; fgets(tmptext
, len
+1, fp
); n
++){
3544 /*** do any necessary DEcryption here ***/
3546 for(i
= 0; tmptext
[i
]; i
++)
3547 tmptext
[i
] = xlate_out(tmptext
[i
]);
3549 if(i
&& tmptext
[i
-1] == '\n')
3550 tmptext
[i
-1] = '\0'; /* blast '\n' */
3552 dprint((10, "read_passfile: %s\n", tmp
? tmp
: "?"));
3553 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3554 for(i
= 0, j
= 0; tmptext
[i
] && j
< 5; j
++){
3555 for(ui
[j
] = &tmptext
[i
]; tmptext
[i
] && tmptext
[i
] != '\t'; i
++)
3556 ; /* find end of data */
3559 tmptext
[i
++] = '\0'; /* tie off data */
3562 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3563 if(ui
[0] && ui
[1] && ui
[2]){ /* valid field? */
3564 STRLIST_S hostlist
[2];
3565 char *s
= ui
[3] ? strchr(ui
[3], PWDAUTHSEP
) : NULL
;
3566 int flags
= ui
[3] ? atoi(s
? ++s
: ui
[3]) : 0;
3568 hostlist
[0].name
= ui
[2];
3570 hostlist
[0].next
= &hostlist
[1];
3571 hostlist
[1].name
= ui
[4];
3572 hostlist
[1].next
= NULL
;
3575 hostlist
[0].next
= NULL
;
3578 imap_set_passwd(l
, ui
[0], ui
[1], hostlist
, flags
& 0x01, 0, 0);
3583 if (tmptext
) fs_give((void **) &tmptext
);
3585 if (text
) fs_give((void **)&text
);
3590 #endif /* PASSFILE */
3596 write_passfile(pinerc
, l
)
3600 char *authend
, *authtype
;
3603 char target
[4*MAILTMPLEN
];
3604 char blob
[4*MAILTMPLEN
];
3608 if(using_passfile
== 0)
3611 dprint((9, "write_passfile\n"));
3613 for(; l
; l
= l
->next
){
3614 authtype
= l
->passwd
;
3615 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3616 if(authend
!= NULL
){
3618 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3619 *authend
= PWDAUTHSEP
;
3622 sprintf(blob
, "%d", l
->altflag
);
3624 snprintf(target
, sizeof(target
), "%s%s\t%s\t%s",
3626 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3627 l
->user
? l
->user
: "",
3629 ltarget
= utf8_to_lptstr((LPSTR
) target
);
3632 snprintf(blob
, sizeof(blob
), "%s%s%s",
3633 l
->passwd
? l
->passwd
: "",
3634 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3636 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3637 ? l
->hosts
->next
->name
: "");
3638 memset((void *) &cred
, 0, sizeof(cred
));
3640 cred
.Type
= CRED_TYPE_GENERIC
;
3641 cred
.TargetName
= ltarget
;
3642 cred
.CredentialBlobSize
= strlen(blob
)+1;
3643 cred
.CredentialBlob
= (LPBYTE
) &blob
;
3644 cred
.Persist
= CRED_PERSIST_ENTERPRISE
;
3645 g_CredWriteW(&cred
, 0);
3647 fs_give((void **) <arget
);
3650 #endif /* WINCRED > 0 */
3654 char target
[4*MAILTMPLEN
];
3655 char blob
[4*MAILTMPLEN
];
3656 SecKeychainItemRef itemRef
= NULL
;
3658 if(using_passfile
== 0)
3661 dprint((9, "write_passfile\n"));
3663 for(; l
; l
= l
->next
){
3664 authtype
= l
->passwd
;
3665 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3666 if(authend
!= NULL
){
3668 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3669 *authend
= PWDAUTHSEP
;
3672 sprintf(blob
, "%d", l
->altflag
);
3674 snprintf(target
, sizeof(target
), "%s\t%s\t%s",
3675 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3676 l
->user
? l
->user
: "",
3679 snprintf(blob
, sizeof(blob
), "%s%s%s",
3680 l
->passwd
? l
->passwd
: "",
3681 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3683 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3684 ? l
->hosts
->next
->name
: "");
3686 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target
), target
, strlen(TNAME
), TNAME
, strlen(blob
), blob
));
3688 rc
= SecKeychainAddGenericPassword(NULL
,
3689 strlen(target
), target
,
3690 strlen(TNAME
), TNAME
,
3694 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3697 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc
));
3700 if(rc
== errSecDuplicateItem
){
3701 /* fix existing entry */
3702 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3704 if(!(rc
=SecKeychainFindGenericPassword(NULL
,
3705 strlen(target
), target
,
3706 strlen(TNAME
), TNAME
,
3708 &itemRef
)) && itemRef
){
3710 rc
= SecKeychainItemModifyAttributesAndData(itemRef
, NULL
, strlen(blob
), blob
);
3712 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc
));
3716 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc
));
3721 #else /* PASSFILE */
3722 char tmp
[4*MAILTMPLEN
], blob
[4*MAILTMPLEN
];
3726 char *text
= NULL
, tmp2
[4*MAILTMPLEN
];
3730 if(using_passfile
== 0)
3733 dprint((9, "write_passfile\n"));
3735 /* if there's no passfile to read, bag it!! */
3736 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "wb"))){
3742 strncpy(tmp2
, tmp
, sizeof(tmp2
));
3743 tmp2
[sizeof(tmp2
)-1] = '\0';
3746 for(n
= 0; l
; l
= l
->next
, n
++){
3747 authtype
= l
->passwd
;
3748 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3749 if(authend
!= NULL
){
3751 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3752 *authend
= PWDAUTHSEP
;
3755 sprintf(blob
, "%d", l
->altflag
);
3757 /*** do any necessary ENcryption here ***/
3758 snprintf(tmp
, sizeof(tmp
), "%s\t%s\t%s\t%s%s%s\n", l
->passwd
, l
->user
,
3759 l
->hosts
->name
, blob
,
3760 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "",
3761 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
3763 dprint((10, "write_passfile: %s", tmp
? tmp
: "?"));
3765 for(i
= 0; tmp
[i
]; i
++)
3766 tmp
[i
] = xlate_in(tmp
[i
]);
3769 fs_resize((void **)&text
, (len
+ strlen(tmp
) + 1)*sizeof(char));
3771 len
+= strlen(tmp
) + 1;
3772 strncat(text
, tmp
, strlen(tmp
));
3781 if(ps_global
->pwdcert
== NULL
){
3782 q_status_message(SM_ORDER
, 3, 3, "Attempting to encrypt password file");
3783 i
= setup_pwdcert(&ps_global
->pwdcert
);
3784 if((i
== 0 || i
== -5) && ps_global
->pwdcert
== NULL
)
3785 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3787 if(ps_global
->pwdcert
== NULL
){ /* we tried but failed */
3789 q_status_message1(SM_ORDER
, 3, 3, "Error: no directory %s to save certificates", ps_global
->pwdcertdir
);
3791 q_status_message(SM_ORDER
, 3, 3, "Refusing to write non-encrypted password file");
3793 else if(encrypt_file((char *)tmp2
, text
, ps_global
->pwdcert
) == 0)
3794 q_status_message(SM_ORDER
, 3, 3, "Failed to encrypt password file");
3795 fs_give((void **)&text
); /* do not save this text */
3798 #endif /* PASSFILE */
3801 #endif /* LOCAL_PASSWD_CACHE */
3806 erase_windows_credentials(void)
3808 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3813 if(init_wincred_funcs() != 1)
3817 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
3819 for(k
= 0; k
< count
; k
++)
3820 g_CredDeleteW(pcred
[k
]->TargetName
, CRED_TYPE_GENERIC
, 0);
3822 g_CredFree((PVOID
) pcred
);
3828 ask_erase_credentials(void)
3830 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y'){
3831 erase_windows_credentials();
3832 q_status_message(SM_ORDER
, 3, 3, "All preserved passwords have been erased");
3835 q_status_message(SM_ORDER
, 3, 3, "Previously preserved passwords will not be erased");
3838 #endif /* WINCRED */
3841 #ifdef LOCAL_PASSWD_CACHE
3843 get_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
)
3844 char *pinerc
, **passwd
, *user
;
3845 STRLIST_S
*hostlist
;
3848 return get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, NULL
);
3852 * get_passfile_passwd_auth - return the password contained in the special passord
3853 * cache. The file is assumed to be in the same directory
3854 * as the pinerc with the name defined above.
3857 get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, authtype
)
3858 char *pinerc
, **passwd
, *user
;
3859 STRLIST_S
*hostlist
;
3863 dprint((10, "get_passfile_passwd_auth\n"));
3864 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
3865 ? imap_get_passwd_auth(passfile_cache
, passwd
,
3866 user
, hostlist
, altflag
, authtype
)
3871 free_passfile_cache_work(MMLOGIN_S
**pwdcache
)
3873 if(pwdcache
== NULL
|| *pwdcache
== NULL
)
3876 if((*pwdcache
)->user
) fs_give((void **)&(*pwdcache
)->user
);
3877 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
3878 if((*pwdcache
)->hosts
) free_strlist(&(*pwdcache
)->hosts
);
3879 free_passfile_cache_work(&(*pwdcache
)->next
);
3880 fs_give((void **)pwdcache
);
3885 free_passfile_cache(void)
3888 free_passfile_cache_work(&passfile_cache
);
3892 is_using_passfile(void)
3894 return(using_passfile
== 1);
3898 * Just trying to guess the username the user might want to use on this
3899 * host, the user will confirm.
3902 get_passfile_user(pinerc
, hostlist
)
3904 STRLIST_S
*hostlist
;
3906 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
3907 ? imap_get_user(passfile_cache
, hostlist
)
3913 preserve_prompt(char *pinerc
)
3915 return preserve_prompt_auth(pinerc
, NULL
);
3919 preserve_prompt_auth(char *pinerc
, char *authtype
)
3923 #define PROMPT_PWD _("Preserve password for next login")
3924 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3926 * This prompt was going to be able to be turned on and off via a registry
3927 * setting controlled from the config menu. We decided to always use the
3928 * dialog for login, and there the prompt is unobtrusive enough to always
3929 * be in there. As a result, windows should never reach this, but now
3930 * OS X somewhat uses the behavior just described.
3932 if(mswin_store_pass_prompt()
3933 && (want_to(authtype
3934 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
3936 'y', 'x', NO_HELP
, WT_NORM
)
3946 #define PROMPT_PWD _("Preserve password for next login")
3947 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
3950 if((rc
= macos_store_pass_prompt()) != 0){
3952 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
3953 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
3956 macos_set_store_pass_prompt(1);
3957 q_status_message(SM_ORDER
, 4, 4,
3958 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3963 macos_set_store_pass_prompt(0);
3964 q_status_message(SM_ORDER
, 4, 4,
3965 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
3970 #else /* PASSFILE */
3971 #define PROMPT_PWD _("Preserve password on DISK for next login")
3972 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
3974 char tmp
[MAILTMPLEN
];
3977 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || our_stat(tmp
, &sbuf
) < 0)
3980 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
3981 return(want_to(authtype
3982 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
3983 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
3986 #endif /* PASSFILE */
3989 #endif /* LOCAL_PASSWD_CACHE */
3992 #ifdef APPLEKEYCHAIN
3996 * 1 if store pass prompt is set in the "registry" to on
3998 * -1 if not set to anything
4001 macos_store_pass_prompt(void)
4008 if(storepassprompt
== -1){
4009 if(!(rc
=SecKeychainFindGenericPassword(NULL
, 0, NULL
,
4010 strlen(TNAMEPROMPT
),
4012 (void **) &data
, NULL
))){
4013 val
= (len
== 1 && data
&& data
[0] == '1');
4017 if(storepassprompt
== -1 && !rc
){
4019 storepassprompt
= 1;
4021 storepassprompt
= 0;
4024 return(storepassprompt
);
4029 macos_set_store_pass_prompt(int val
)
4031 storepassprompt
= val
? 1 : 0;
4033 SecKeychainAddGenericPassword(NULL
, 0, NULL
, strlen(TNAMEPROMPT
),
4034 TNAMEPROMPT
, 1, val
? "1" : "0", NULL
);
4039 macos_erase_keychain(void)
4041 SecKeychainAttributeList attrList
;
4042 SecKeychainSearchRef searchRef
= NULL
;
4043 SecKeychainAttribute attrs1
[] = {
4044 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
4046 SecKeychainAttribute attrs2
[] = {
4047 { kSecAccountItemAttr
, strlen(TNAMEPROMPT
), TNAMEPROMPT
}
4050 dprint((9, "macos_erase_keychain\n"));
4053 * Seems like we ought to be able to combine attrs1 and attrs2
4054 * into a single array, but I couldn't get it to work.
4057 /* search for only our items in the keychain */
4059 attrList
.attr
= attrs1
;
4061 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4062 kSecGenericPasswordItemClass
,
4066 SecKeychainItemRef itemRef
= NULL
;
4069 * Go through each item we found and put it
4072 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4073 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4074 SecKeychainItemDelete(itemRef
);
4078 CFRelease(searchRef
);
4083 attrList
.attr
= attrs2
;
4085 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4086 kSecGenericPasswordItemClass
,
4090 SecKeychainItemRef itemRef
= NULL
;
4093 * Go through each item we found and put it
4096 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4097 SecKeychainItemDelete(itemRef
);
4101 CFRelease(searchRef
);
4106 #endif /* APPLEKEYCHAIN */
4108 #ifdef LOCAL_PASSWD_CACHE
4111 set_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
)
4112 char *pinerc
, *passwd
, *user
;
4113 STRLIST_S
*hostlist
;
4114 int altflag
, already_prompted
;
4116 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, NULL
);
4119 * set_passfile_passwd - set the password file entry associated with
4120 * cache. The file is assumed to be in the same directory
4121 * as the pinerc with the name defined above.
4122 * already_prompted: 0 not prompted
4123 * 1 prompted, answered yes
4124 * 2 prompted, answered no
4127 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, authtype
)
4128 char *pinerc
, *passwd
, *user
;
4129 STRLIST_S
*hostlist
;
4130 int altflag
, already_prompted
;
4133 dprint((10, "set_passfile_passwd_auth\n"));
4134 if(((already_prompted
== 0 && preserve_prompt_auth(pinerc
, authtype
))
4135 || already_prompted
== 1)
4136 && !ps_global
->nowrite_password_cache
4137 && (passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))){
4138 imap_set_passwd_auth(&passfile_cache
, passwd
, user
, hostlist
, altflag
, 0, 0, authtype
);
4139 write_passfile(pinerc
, passfile_cache
);
4144 update_passfile_hostlist(pinerc
, user
, hostlist
, altflag
)
4147 STRLIST_S
*hostlist
;
4150 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, NULL
);
4154 * Passfile lines are
4156 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4158 * In pine4.40 and before there was no orig_hostname.
4159 * This routine attempts to repair that.
4162 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, authtype
)
4165 STRLIST_S
*hostlist
;
4171 #else /* !WINCRED */
4173 size_t len
= authtype
? strlen(authtype
) : 0;
4174 size_t offset
= authtype
? 1 : 0;
4176 for(l
= passfile_cache
; l
; l
= l
->next
)
4177 if(imap_same_host_auth(l
->hosts
, hostlist
, authtype
)
4179 && !strcmp(user
, l
->user
+ len
+ offset
)
4180 && l
->altflag
== altflag
){
4184 if(l
&& l
->hosts
&& hostlist
&& !l
->hosts
->next
&& hostlist
->next
4185 && hostlist
->next
->name
4186 && !ps_global
->nowrite_password_cache
){
4187 l
->hosts
->next
= new_strlist_auth(hostlist
->next
->name
, authtype
, PWDAUTHSEP
);
4188 write_passfile(pinerc
, passfile_cache
);
4190 #endif /* !WINCRED */
4193 #endif /* LOCAL_PASSWD_CACHE */
4198 * Load and init the WinCred structure.
4199 * This gives us a way to skip the WinCred code
4200 * if the dll doesn't exist.
4203 init_wincred_funcs(void)
4209 /* Assume the worst. */
4212 hmod
= LoadLibrary(TEXT("advapi32.dll"));
4215 FARPROC fpCredWriteW
;
4216 FARPROC fpCredEnumerateW
;
4217 FARPROC fpCredDeleteW
;
4220 fpCredWriteW
= GetProcAddress(hmod
, "CredWriteW");
4221 fpCredEnumerateW
= GetProcAddress(hmod
, "CredEnumerateW");
4222 fpCredDeleteW
= GetProcAddress(hmod
, "CredDeleteW");
4223 fpCredFree
= GetProcAddress(hmod
, "CredFree");
4225 if(fpCredWriteW
&& fpCredEnumerateW
&& fpCredDeleteW
&& fpCredFree
)
4227 g_CredWriteW
= (CREDWRITEW
*)fpCredWriteW
;
4228 g_CredEnumerateW
= (CREDENUMERATEW
*)fpCredEnumerateW
;
4229 g_CredDeleteW
= (CREDDELETEW
*)fpCredDeleteW
;
4230 g_CredFree
= (CREDFREE
*)fpCredFree
;
4236 mswin_set_erasecreds_callback(ask_erase_credentials
);
4239 return g_CredInited
;
4242 #endif /* WINCRED */