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 NULL
, /* special IMAP ID */
175 0, /* expiration time */
176 0, /* first time indicator */
177 1, /* client secret required */
178 0 /* Cancel refresh token */
181 {"outlook.office365.com", "smtp.office365.com", NULL
, NULL
},
182 {{"client_id", NULL
},
183 {"client_secret", NULL
}, /* not used, but needed */
184 {"tenant", NULL
}, /* used */
185 {"code", NULL
}, /* not used, not needed */
186 {"refresh_token", NULL
},
187 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
188 {"grant_type", "urn:ietf:params:oauth:grant-type:device_code"},
189 {"scope", NULL
}, /* not used */
190 {"grant_type", "refresh_token"},
191 {"response_type", "code"}, /* not used */
192 {"state", NULL
}, /* not used */
193 {"device_code", NULL
} /* only used for frst time set up */
195 {{NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* Get Access Code, Not used */
196 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/devicecode", /* first time use and get device code information */
197 {OA2_Id
, OA2_Scope
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}},
198 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
199 {OA2_Id
, OA2_Redirect
, OA2_DeviceCode
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}},
200 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
201 {OA2_Id
, OA2_RefreshToken
, OA2_Scope
, OA2_GrantTypefromRefreshToken
, OA2_End
, OA2_End
, OA2_End
}}
203 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information */
204 NULL
, /* access token */
205 NULL
, /* special IMAP ID */
207 0, /* expiration time */
208 0, /* first time indicator */
209 0, /* client secret required */
210 0 /* Cancel refresh token */
213 {"outlook.office365.com", "smtp.office365.com", NULL
, NULL
},
214 {{"client_id", NULL
},
215 {"client_secret", NULL
}, /* not used, but needed */
216 {"tenant", NULL
}, /* used */
217 {"code", NULL
}, /* used during authorization */
218 {"refresh_token", NULL
},
219 {"scope", "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"},
220 {"redirect_uri", "http://localhost"},
221 {"grant_type", "authorization_code"},
222 {"grant_type", "refresh_token"},
223 {"response_type", "code"},
224 {"state", NULL
}, /* not used */
225 {"device_code", NULL
} /* not used */
227 {{"GET", "https://login.microsoftonline.com/\001/oauth2/v2.0/authorize", /* Get Access Code */
228 {OA2_Id
, OA2_Scope
, OA2_Redirect
, OA2_Response
, OA2_End
, OA2_End
, OA2_End
}},
229 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* device code, not used */
230 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get first Refresh Token and Access token */
231 {OA2_Id
, OA2_Redirect
, OA2_Scope
, OA2_GrantTypeforAccessToken
, OA2_Secret
, OA2_Code
, OA2_End
}},
232 {"POST", "https://login.microsoftonline.com/\001/oauth2/v2.0/token", /* Get access token from refresh token */
233 {OA2_Id
, OA2_RefreshToken
, OA2_Scope
, OA2_GrantTypefromRefreshToken
, OA2_Secret
, OA2_End
, OA2_End
}}
235 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information, not used */
236 NULL
, /* access token */
237 NULL
, /* special IMAP ID */
239 0, /* expiration time */
240 0, /* first time indicator */
241 1, /* client secret required */
242 0 /* Cancel refresh token */
245 {"imap.mail.yahoo.com", "smtp.mail.yahoo.com", NULL
, NULL
},
246 {{"client_id", NULL
},
247 {"client_secret", NULL
}, /* used */
248 {"tenant", NULL
}, /* not used */
249 {"code", NULL
}, /* used during authorization */
250 {"refresh_token", NULL
},
251 {"scope", NULL
}, /* not used! */
252 {"redirect_uri", "oob"}, /* https://localhost */
253 {"grant_type", "authorization_code"},
254 {"grant_type", "refresh_token"},
255 {"response_type", "code"},
256 {"state", NULL
}, /* used */
257 {"device_code", NULL
} /* not used */
259 {{"GET", "https://api.login.yahoo.com/oauth2/request_auth", /* Get Access Code */
260 {OA2_Id
, OA2_Redirect
, OA2_Response
, OA2_State
, OA2_End
, OA2_End
, OA2_End
}},
261 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* device code, not used */
262 {"POST", "https://api.login.yahoo.com/oauth2/get_token", /* Get first Refresh Token and Access token */
263 {OA2_Id
, OA2_Secret
, OA2_Redirect
, OA2_Code
, OA2_GrantTypeforAccessToken
, OA2_End
, OA2_End
}},
264 {"POST", "https://api.login.yahoo.com/oauth2/get_token", /* Get access token from refresh token */
265 {OA2_Id
, OA2_Secret
, OA2_Redirect
, OA2_RefreshToken
, OA2_GrantTypefromRefreshToken
, OA2_End
, OA2_End
}}
267 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information, not used */
268 NULL
, /* access token */
269 "ALPINE_V1", /* special IMAP ID */
271 0, /* expiration time */
272 0, /* first time indicator */
273 1, /* client secret required */
274 0 /* Cancel refresh token */
277 {"imap.yandex.com", "smtp.yandex.com", NULL
, NULL
},
278 {{"client_id", NULL
},
279 {"client_secret", NULL
}, /* not used, but needed */
280 {"tenant", NULL
}, /* not used */
281 {"code", NULL
}, /* used during authorization */
282 {"refresh_token", NULL
},
283 {"scope", NULL
}, /* not needed, so not used */
284 {"redirect_uri", "https://oauth.yandex.ru/verification_code"},
285 {"grant_type", "authorization_code"},
286 {"grant_type", "refresh_token"},
287 {"response_type", "code"},
288 {"state", NULL
}, /* not used */
289 {"device_code", NULL
} /* not used */
291 {{"GET", "https://oauth.yandex.com/authorize", /* Get Access Code */
292 {OA2_Id
, OA2_Redirect
, OA2_Response
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}},
293 {NULL
, NULL
, {OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
, OA2_End
}}, /* device code, not used */
294 {"POST", "https://oauth.yandex.com/token", /* Get first Refresh Token and Access token */
295 {OA2_Id
, OA2_Redirect
, OA2_GrantTypeforAccessToken
, OA2_Secret
, OA2_Code
, OA2_End
, OA2_End
}},
296 {"POST", "https://oauth.yandex.com/token", /* Get access token from refresh token */
297 {OA2_Id
, OA2_RefreshToken
, OA2_GrantTypefromRefreshToken
, OA2_Secret
, OA2_End
, OA2_End
, OA2_End
}}
299 {NULL
, NULL
, NULL
, 0, 0, NULL
}, /* device_code information, not used */
300 NULL
, /* access token */
301 NULL
, /* special IMAP ID */
303 0, /* expiration time */
304 0, /* first time indicator */
305 1, /* client secret required */
306 0 /* Cancel refresh token */
308 { NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, NULL
, 0, 0, 0, 0, 0},
312 xoauth2_flow_tool(struct pine
*ps
, int cmd
, CONF_S
**cl
, unsigned int flags
)
318 *((*cl
)->d
.xf
.selected
) = (*cl
)->d
.xf
.pat
;
319 rv
= simple_exit_cmd(flags
);
322 rv
= simple_exit_cmd(flags
);
330 ps
->mangled_body
= 1;
336 oauth2_select_flow(char *host
)
338 OAUTH2_S
*oa2list
, *oa2
;
343 CONF_S
*ctmp
= NULL
, *first_line
= NULL
;
344 OAUTH2_S
*x_sel
= NULL
;
348 dprint((9, "xoauth2 select flow"));
349 ps_global
->next_screen
= SCREEN_FUN_NULL
;
351 memset(&screen
, 0, sizeof(screen
));
353 for(i
= 0; i
< sizeof(tmp
) && i
< ps_global
->ttyo
->screen_cols
; i
++)
358 ctmp
->flags
|= CF_NOSELECT
;
359 ctmp
->value
= cpystr(tmp
);
362 ctmp
->flags
|= CF_NOSELECT
;
363 ctmp
->value
= cpystr(_("Please select below the authorization flow you would like to follow:"));
366 ctmp
->flags
|= CF_NOSELECT
;
367 ctmp
->value
= cpystr(tmp
);
369 for(oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
370 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
371 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
]){
375 method
= oa2list
->server_mthd
[0].name
? "Authorize"
376 : (oa2list
->server_mthd
[1].name
? "Device" : "Unknown");
377 sprintf(tmp
, "%s (%s)", oa2list
->name
, method
);
378 ctmp
->value
= cpystr(tmp
);
379 ctmp
->d
.xf
.selected
= &x_sel
;
380 ctmp
->d
.xf
.pat
= oa2list
;
381 ctmp
->keymenu
= &xoauth2_id_select_km
;
382 ctmp
->help
= NO_HELP
;
383 ctmp
->help_title
= NULL
;
384 ctmp
->tool
= xoauth2_flow_tool
;
385 ctmp
->flags
= CF_STARTITEM
;
389 (void)conf_scroll_screen(ps_global
, &screen
, first_line
, _("SELECT AUTHORIZATION FLOW"),
390 _("xoauth2"), 0, NULL
);
399 for(oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++)
400 n
+= strlen(oa2list
->name
); + 5; /* number, parenthesis, space */
401 n
+= 1024; /* large enough to display to lines of 80 characters in UTF-8 */
402 s
= fs_get(n
*sizeof(char));
403 strcpy(s
, _("Please select below the authorization flow you would like to follow:"));
404 sprintf(s
+ strlen(s
), _("Please select the client-id to use from the following list.\n\n"));
405 for(j
= 1, oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
406 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
407 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
])
408 sprintf(s
+ strlen(s
), " %d) %.70s\n", j
++, oa2list
->name
);
410 display_init_err(s
, 0);
412 strncpy(prompt
, _("Enter your selection number: "), sizeof(prompt
));
413 prompt
[sizeof(prompt
)-1] = '\0';
415 rv
= optionally_enter(reply
, 0, 0, sizeof(reply
), prompt
, NULL
, NO_HELP
, 0);
417 rv
= (sel
>= 0 && sel
< i
) ? 0 : -1;
420 for(j
= 1, oa2list
= alpine_oauth2_list
; oa2list
&& oa2list
->name
;oa2list
++){
421 for(i
= 0; oa2list
&& oa2list
->host
&& oa2list
->host
[i
] && strucmp(oa2list
->host
[i
], host
); i
++);
422 if(oa2list
&& oa2list
->host
&& i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
]){
432 typedef struct auth_code_s
{
438 oauth2device_decode_reply(void *datap
, void *replyp
)
440 OAUTH2_DEVICEPROC_S
*av
= (OAUTH2_DEVICEPROC_S
*)datap
;
441 int reply
= *(int *) replyp
;
443 return reply
< 0 ? av
->code_failure
: (reply
== 0 ? av
->code_success
: av
->code_wait
);
447 oauth2_elapsed_done(void *aux_valuep
)
449 OAUTH2_S
*oauth2
= aux_valuep
? ((OAUTH2_DEVICEPROC_S
*) aux_valuep
)->xoauth2
: NULL
;
450 static time_t savedt
= 0, now
;
453 if(aux_valuep
== NULL
) savedt
= 0; /* reset last time we approved */
456 if(oauth2
->devicecode
.interval
+ now
>= savedt
)
465 oauth2_set_device_info(OAUTH2_S
*oa2
, char *method
)
467 char tmp
[MAILTMPLEN
];
469 char *name
= oa2
->name
;
471 OAUTH2_DEVICECODE_S
*deviceinfo
= &oa2
->devicecode
;
472 OAUTH2_DEVICEPROC_S aux_value
;
476 STORE_S
*in_store
, *out_store
;
478 HANDLE_S
*handles
= NULL
;
479 AUTH_CODE_S user_input
;
481 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
482 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
485 aux_value
.xoauth2
= oa2
;
486 aux_value
.code_success
= 'e';
487 aux_value
.code_failure
= 'e';
488 aux_value
.code_wait
= NO_OP_COMMAND
;
490 so_puts(in_store
, "<HTML><P>");
491 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), name
);
492 so_puts(in_store
, tmp
);
493 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), name
, method
),
494 so_puts(in_store
, tmp
);
496 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
498 _("</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."),
499 deviceinfo
->verification_uri
, deviceinfo
->verification_uri
, deviceinfo
->user_code
);
500 so_puts(in_store
, tmp
);
503 so_puts(in_store
, "</P><P>");
504 so_puts(in_store
, deviceinfo
->message
);
506 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
507 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), name
);
508 so_puts(in_store
, tmp
);
509 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
511 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 "));
512 so_puts(in_store
, _("to grant access to Alpine to your data. "));
513 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 "));
514 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 "));
515 so_puts(in_store
, _("alive at the end of this process, and your connection will proceed from there."));
516 so_puts(in_store
, _(" </P><P>If you do not wish to proceed, cancel at any time by pressing 'E' to exit"));
517 so_puts(in_store
, _("</P></HTML>"));
519 so_seek(in_store
, 0L, 0);
520 init_handles(&handles
);
522 gf_link_filter(gf_html2plain
,
523 gf_html2plain_opt(NULL
,
524 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
525 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
526 gf_set_so_readc(&gc
, in_store
);
527 gf_set_so_writec(&pc
, out_store
);
529 gf_clear_so_writec(out_store
);
530 gf_clear_so_readc(in_store
);
532 memset(&sargs
, 0, sizeof(SCROLL_S
));
533 sargs
.text
.handles
= handles
;
534 sargs
.text
.text
= so_text(out_store
);
535 sargs
.text
.src
= CharStar
;
536 sargs
.text
.desc
= _("help text");
537 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
538 sargs
.proc
.tool
= oauth2_auth_answer
;
539 sargs
.proc
.data
.p
= (void *)&user_input
;
540 sargs
.keys
.menu
= &oauth2_device_auth_keymenu
;
541 /* don't want to re-enter c-client */
542 sargs
.quell_newmail
= 1;
543 setbitmap(sargs
.keys
.bitmap
);
544 sargs
.help
.text
= h_oauth2_start
;
545 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
546 sargs
.aux_function
= oauth2deviceinfo_get_accesscode
;
547 sargs
.aux_value
= (void *) &aux_value
;
548 sargs
.aux_condition
= oauth2_elapsed_done
;
549 sargs
.decode_aux_rv_value
= oauth2device_decode_reply
;
550 sargs
.aux_rv_value
= (void *) &aux_rv_value
;
554 ps_global
->mangled_screen
= 1;
555 ps_global
->painted_body_on_startup
= 0;
556 ps_global
->painted_footer_on_startup
= 0;
557 } while (user_input
.answer
!= 'e');
561 free_handles(&handles
);
562 oauth2_elapsed_done(NULL
);
565 int flags
, rc
, q_line
;
566 /* TRANSLATORS: user needs to input an access code from the server */
567 char prompt
[MAILTMPLEN
], token
[MAILTMPLEN
];
569 * If screen hasn't been initialized yet, use want_to.
573 tmp_20k_buf
[0] = '\0';
574 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
575 _("Authorizing Alpine Access to %s Email Services\n\n"), name
);
576 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
578 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
579 _("Alpine is attempting to log you into your %s account, using the %s method. "), name
, method
),
580 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
582 if(deviceinfo
->verification_uri
&& deviceinfo
->user_code
){
583 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
584 _("To sign in, user a web browser to open the page %s and enter the code \"%s\" without the quotes.\n\n"),
585 deviceinfo
->verification_uri
, deviceinfo
->user_code
);
586 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
589 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
590 "%s\n\n", deviceinfo
->message
);
591 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
594 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
595 _("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
);
596 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
598 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
599 "%s", _("After you open the previous link, please enter the code above, and then you will be asked to authenticate and later "));
600 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
602 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
603 "%s", _("to grant access to Alpine to your data.\n\n"));
604 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
606 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
607 "%s", _(" Once you have authorized Alpine, you will be asked if you want to preserve the Refresh Token and Access Code. "));
608 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
610 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
611 "%s", _("If you do not wish to repeat this process again, answer \'Y\'es. If you did this process quickly, your connection "));
612 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
614 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
615 "%s", _("to the server will still be alive at the end of this process, and your connection will proceed from there.\n\n"));
616 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
618 display_init_err(tmp_20k_buf
, 0);
619 memset((void *)tmp
, 0, sizeof(tmp
));
620 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
621 tmp
[sizeof(tmp
)-1] = '\0';
623 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
626 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
627 flags
= OE_APPEND_CURRENT
;
629 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
630 "%s", _("After you are done going through the process described above, press \'y\' to continue, or \'n\' to cancel\n"));
631 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
633 aux_value
.xoauth2
= oa2
;
634 aux_value
.code_success
= 'y';
635 aux_value
.code_failure
= 'n';
636 aux_value
.code_wait
= 'w';
638 strncpy(tmp
, _("Continue waiting"), sizeof(tmp
));
639 tmp
[sizeof(tmp
)-1] = '\0';
641 if(oauth2_elapsed_done((void *) &aux_value
) == 0)
642 oauth2deviceinfo_get_accesscode((void *) &aux_value
, (void *) &rv
);
643 ch
= oauth2device_decode_reply((void *) &aux_value
, (void *) &rv
);
644 } while (ch
== 'w' || want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y');
645 oauth2_elapsed_done(NULL
);
651 oauth2_get_access_code(unsigned char *url
, char *method
, OAUTH2_S
*oauth2
, int *tryanother
)
653 char tmp
[MAILTMPLEN
];
658 STORE_S
*in_store
, *out_store
;
660 HANDLE_S
*handles
= NULL
;
661 AUTH_CODE_S user_input
;
663 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
664 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
667 so_puts(in_store
, "<HTML><BODY><P>");
668 sprintf(tmp
, _("<CENTER>Authorizing Alpine Access to %s Email Services</CENTER>"), oauth2
->name
);
669 so_puts(in_store
, tmp
);
670 sprintf(tmp
, _("<P>Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
671 so_puts(in_store
, tmp
);
673 if(strucmp(oauth2
->name
, GMAIL_NAME
) == 0){
674 so_puts(in_store
, _(" If this is your first time setting up this type of authentication, please follow the steps below. "));
675 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."));
676 so_puts(in_store
, _("<UL> "));
677 so_puts(in_store
, _("<LI>First, login to <A HREF=\"https://console.developers.google.com\">https://console.developers.google.com</A> "));
678 so_puts(in_store
, _("and create a project. The name of the project is not important."));
679 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."));
680 so_puts(in_store
, _("<LI> Create OAUTH Credentials."));
681 so_puts(in_store
, _("</UL> "));
682 so_puts(in_store
, _("<P> As a result of this process, you will get a client-id and a client-secret."));
683 so_puts(in_store
, _(" Exit this screen, and from Alpine's Main Screen press S U to save these values permanently."));
684 so_puts(in_store
, _(" Then retry login into Gmail's server, skip these steps, and continue with the steps below."));
685 so_puts(in_store
, _("</P><P> Cancelling this process will lead to an error in authentication that can be ignored."));
686 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."));
689 so_puts(in_store
, _("</P><P>In order to authorize Alpine to access your email, Alpine needs to open the following URL:"));
690 so_puts(in_store
,"</P><P>");
691 sprintf(tmp_20k_buf
, _("<A HREF=\"%s\">%s</A>"), url
, url
);
692 so_puts(in_store
, tmp_20k_buf
);
694 so_puts(in_store
, _("</P><P> Alpine will try to use your URL Viewers setting to find a browser to open this URL."));
695 sprintf(tmp
, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2
->name
);
696 so_puts(in_store
, tmp
);
697 so_puts(in_store
, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice."));
699 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. "));
700 so_puts(in_store
, _(" At the end of this process, you will be given an access code or redirected to a web page."));
701 so_puts(in_store
, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
702 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. "));
703 so_puts(in_store
, _(" Once you have completed this process, Alpine will proceed with authentication."));
704 so_puts(in_store
, _(" If you do not wish to proceed, cancel by pressing 'E' to exit"));
705 so_puts(in_store
, _("</P></BODY></HTML>"));
707 so_seek(in_store
, 0L, 0);
708 init_handles(&handles
);
710 gf_link_filter(gf_html2plain
,
711 gf_html2plain_opt(NULL
,
712 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
713 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
714 gf_set_so_readc(&gc
, in_store
);
715 gf_set_so_writec(&pc
, out_store
);
717 gf_clear_so_writec(out_store
);
718 gf_clear_so_readc(in_store
);
720 memset(&sargs
, 0, sizeof(SCROLL_S
));
721 sargs
.text
.handles
= handles
;
722 sargs
.text
.text
= so_text(out_store
);
723 sargs
.text
.src
= CharStar
;
724 sargs
.text
.desc
= _("help text");
725 sargs
.bar
.title
= _("SETTING UP XOAUTH2 AUTHORIZATION");
726 sargs
.proc
.tool
= oauth2_auth_answer
;
727 sargs
.proc
.data
.p
= (void *)&user_input
;
728 sargs
.keys
.menu
= &oauth2_auth_keymenu
;
729 /* don't want to re-enter c-client */
730 sargs
.quell_newmail
= 1;
731 setbitmap(sargs
.keys
.bitmap
);
732 sargs
.help
.text
= h_oauth2_start
;
733 sargs
.help
.title
= _("HELP FOR SETTING UP XOAUTH2");
737 ps_global
->mangled_screen
= 1;
738 ps_global
->painted_body_on_startup
= 0;
739 ps_global
->painted_footer_on_startup
= 0;
740 } while (user_input
.answer
!= 'e');
742 if(!struncmp(user_input
.code
, "http://", 7)
743 || !struncmp(user_input
.code
, "https://", 8)){
745 s
= strstr(user_input
.code
, "code=");
753 else code
= user_input
.code
? cpystr(user_input
.code
) : NULL
;
754 if(user_input
.code
) fs_give((void **) &user_input
.code
);
756 if(code
== NULL
) *tryanother
= 1;
760 free_handles(&handles
);
763 int flags
, rc
, q_line
;
764 /* TRANSLATORS: user needs to input an access code from the server */
765 char *accesscodelabel
= _("Copy and Paste Access Code");
766 char prompt
[MAILTMPLEN
], token
[MAILTMPLEN
];
768 * If screen hasn't been initialized yet, use want_to.
771 tmp_20k_buf
[0] = '\0';
772 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
773 _("Auhtorizing Alpine Access to %s Email Services\n\n"), oauth2
->name
);
774 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
776 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
777 _("Alpine is attempting to log you into your %s account, using the %s method."), oauth2
->name
, method
),
778 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
780 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
781 "%s\n\n",_(" In order to do that, Alpine needs to open the following URL:"));
782 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
784 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
786 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
788 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
789 _("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
);
790 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
792 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
793 "%s", _("After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine."));
794 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
796 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
797 "%s", _(" At the end of this process, you will be given an access code or redirected to a web page.\n"));
798 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
800 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
801 "%s", _(" If you see a code, copy it and then press 'C', and enter the code at the prompt."));
802 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
804 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
805 "%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. "));
806 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
808 snprintf(tmp_20k_buf
+strlen(tmp_20k_buf
), SIZEOF_20KBUF
-strlen(tmp_20k_buf
),
809 "%s", _(" Once you have completed this process, Alpine will proceed with authentication.\n"));
810 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
812 display_init_err(tmp_20k_buf
, 0);
813 memset((void *)tmp
, 0, sizeof(tmp
));
814 strncpy(tmp
, _("Alpine would like to get authorization to access your email. Proceed "), sizeof(tmp
));
815 tmp
[sizeof(tmp
)-1] = '\0';
817 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y'){
818 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
819 flags
= OE_APPEND_CURRENT
;
820 sprintf(prompt
, "%s: ", accesscodelabel
);
822 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
823 prompt
, NULL
, NO_HELP
, &flags
);
824 } while (rc
!= 0 && rc
!= 1);
825 if(!struncmp(token
, "http://", 7)
826 || !struncmp(token
, "https://", 8)){
828 s
= strstr(token
, "code=");
836 else code
= token
[0] ? cpystr(token
) : NULL
;
843 void mm_login_oauth2(NETMBX
*, char *, char *, OAUTH2_S
*, long int, char *, char *);
845 /* The purpose of this function is to report to c-client the values of the
846 * different tokens and codes so that c-client can try to log in the user
847 * to the server. This function DOES NOT attempt to get these values for
848 * the user. That is attempted in the c-client side (as best as it can be
849 * done given our circumstances: no http support, no javascript support,
850 * etc.). This is the best we can do:
852 * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it
853 * as best as we can. Unloaded means that there is no server known to
854 * connect, no access token, etc. Pretty much nothing is known about
855 * how to get access code, access token, etc. We ask the user to fill
856 * it up for us, if they can. If the user fills it up we save those
859 * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we
860 * save the information that c-client got for us.
862 * 3. When saving this information we use the password caching facilities,
863 * but we must do it in a different format so that old information and
864 * new information are not mixed. In order to accommodate this for new
865 * authentication methods, we save the information in the same fields,
866 * but this time we modify it slightly, so that old functions fail to
867 * understand the new information and so not modify it nor use it. The
868 * modification is simple: Every piece of information that was saved
869 * before is prepended XOAUTH2\001 to indicate the authentication
870 * method for which it works. The character \001 is a separator. New
871 * Alpine will know how to deal with this, but old versions, will not
872 * strip this prefix from the information and fail to get the
873 * information or modify it when needed. Only new versions of Alpine will
874 * know how to process this information.
875 * new_value = authenticator_method separator old_value
876 * authenticator_method = "XOAUTH2_S"
878 * example: if old value is imap.gmail.com, the new value for the XOAUTH2
879 * authenticator is "XOAUTH2\001imap.gmail.com".
880 * In addition, the password field is not used to encode the password
881 * anymore, it is used to save login information needed in a format that
882 * the caller function chooses, but that must be preceded by the
883 * "authenticator_method separator" code as above.
886 mm_login_oauth2(NETMBX
*mb
, char *user
, char *method
,
887 OAUTH2_S
*login
, long int trial
,
888 char *usethisprompt
, char *altuserforcache
)
890 char *token
, tmp
[MAILTMPLEN
];
891 char prompt
[4*MAILTMPLEN
], value
[4*MAILTMPLEN
], *last
;
892 char defuser
[NETMAXUSER
];
893 char hostleadin
[80], hostname
[200], defubuf
[200];
894 char logleadin
[80], pwleadin
[50];
897 char *OldRefreshToken
, *OldAccessToken
;
898 char *NewRefreshToken
, *NewAccessToken
;
899 char *SaveRefreshToken
, *SaveAccessToken
;
900 /* TRANSLATORS: A label for the hostname that the user is logging in on */
901 char *hostlabel
= _("HOST");
902 /* TRANSLATORS: user is logging in as a particular user (a particular
903 login name), this is just labelling that user name. */
904 char *userlabel
= _("USER");
905 STRLIST_S hostlist
[2], hostlist2
[OAUTH2_TOT_EQUIV
+1];
907 int len
, rc
, q_line
, flags
, i
, j
;
908 int oespace
, avail
, need
, save_dont_use
;
911 int ChangeAccessToken
, ChangeRefreshToken
, ChangeExpirationTime
;
912 OAUTH2_S
*oa2list
, *oa2
;
915 unsigned long OldExpirationTime
, NewExpirationTime
, SaveExpirationTime
;
916 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
917 int preserve_password
= -1;
920 dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n",
921 trial
, mb
->user
? mb
->user
: "(null)",
922 mb
->service
? mb
->service
: "(null)",
923 mb
->port
? " port=" : "",
924 mb
->port
? comatose(mb
->port
) : "",
925 altuserforcache
? " altuserforcache =" : "",
926 altuserforcache
? altuserforcache
: ""));
928 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
930 save_in_init
= ps_global
->in_init_seq
;
931 ps_global
->in_init_seq
= 0;
932 ps_global
->no_newmail_check_from_optionally_enter
= 1;
934 /* make sure errors are seen */
935 if(ps_global
->ttyo
&& !ps_global
->noshow_error
)
936 flush_status_messages(0);
938 token
= NULL
; /* start from scratch */
940 hostlist
[0].name
= mb
->host
;
941 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
942 hostlist
[0].next
= &hostlist
[1];
943 hostlist
[1].name
= mb
->orighost
;
944 hostlist
[1].next
= NULL
;
947 hostlist
[0].next
= NULL
;
949 if(hostlist
[0].name
){
950 dprint((9, "mm_login_oauth2: host=%s\n",
951 hostlist
[0].name
? hostlist
[0].name
: "?"));
952 if(hostlist
[0].next
&& hostlist
[1].name
){
953 dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist
[1].name
));
957 if(trial
== 0L && !altuserforcache
){
958 if(*mb
->user
!= '\0')
959 strncpy(user
, mb
->user
, NETMAXUSER
);
961 flags
= OE_APPEND_CURRENT
;
962 sprintf(prompt
, "%s: %s - %s: ", hostlabel
, mb
->orighost
, userlabel
);
963 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
964 prompt
, NULL
, NO_HELP
, &flags
);
966 user
[NETMAXUSER
-1] = '\0';
970 * We check to see if the server we are going to log in to is already
971 * registered. This gives us a list of servers with the same
972 * credentials, so we use the same credentials for all of them.
975 for(registered
= 0, oa2list
= alpine_oauth2_list
;
976 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
978 for(i
= 0; i
< OAUTH2_TOT_EQUIV
979 && oa2list
->host
[i
] != NULL
980 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
981 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
988 x
= oauth2_get_client_info(oa2list
->name
, user
);
990 for(oa2list
= alpine_oauth2_list
;
991 oa2list
&& oa2list
->host
!= NULL
&& oa2list
->host
[0] != NULL
;
993 for(i
= 0; i
< OAUTH2_TOT_EQUIV
994 && oa2list
->host
[i
] != NULL
995 && strucmp(oa2list
->host
[i
], mb
->orighost
) != 0; i
++);
996 if(i
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[i
] != NULL
){
997 char *flow
= oa2list
->server_mthd
[0].name
? "Authorize"
998 : (oa2list
->server_mthd
[1].name
? "Device" : "Unknown");
999 if(!strucmp(x
->flow
, flow
)) break; /* found it */
1003 /* else use the one we found earlier, the user has to configure this better */
1007 hostlist2
[i
= 0].name
= mb
->host
;
1008 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
))
1009 hostlist2
[++i
].name
= mb
->orighost
;
1011 for(j
= 0; j
< OAUTH2_TOT_EQUIV
&& oa2list
->host
[j
] != NULL
; j
++){
1013 for(k
= 0; k
<= i
&& hostlist2
[k
].name
1014 && strcmp(hostlist2
[k
].name
, oa2list
->host
[j
]); k
++);
1016 hostlist2
[++i
].name
= oa2list
->host
[j
];
1018 hostlist2
[i
+1].name
= NULL
;
1019 hostlist2
[i
+1].next
= NULL
;
1020 for(j
= i
; j
>= 0; j
--)
1021 hostlist2
[j
].next
= &hostlist2
[j
+1];
1024 if(registered
){ /* redo the app_id, no questions asked */
1025 free_id(&ps_global
->id
);
1026 ps_global
->id
= set_alpine_id(oa2list
->app_id
? oa2list
->app_id
: PACKAGE_NAME
, PACKAGE_VERSION
);
1027 mail_parameters(NULL
, SET_IDPARAMS
, (void *) ps_global
->id
);
1031 * We check if we have a refresh token saved somewhere, if so
1032 * we use it to get a new access token, otherwise we need to
1033 * get an access code so we can get (and save) a refresh token
1034 * and use the access token.
1036 if(trial
== 0L && !altuserforcache
){
1037 /* Search for a refresh token that is already loaded ... */
1038 if(imap_get_passwd_auth(mm_login_list
, &token
, user
,
1039 registered
? hostlist2
: hostlist
,
1040 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
1041 dprint((9, "mm_login_oauth2: found a refresh token\n"));
1042 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1043 ps_global
->in_init_seq
= save_in_init
;
1045 #ifdef LOCAL_PASSWD_CACHE
1046 /* or see if we have saved one in the local password cache and load it */
1047 else if(get_passfile_passwd_auth(ps_global
->pinerc
, &token
,
1048 user
, registered
? hostlist2
: hostlist
,
1049 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
)){
1050 imap_set_passwd_auth(&mm_login_list
, token
, user
,
1051 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
1052 update_passfile_hostlist_auth(ps_global
->pinerc
, user
, hostlist
,
1053 (mb
->sslflag
||mb
->tlsflag
), OA2NAME
);
1054 dprint((9, "mm_login_oauth2: found a refresh token in cache\n"));
1055 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1056 ps_global
->in_init_seq
= save_in_init
;
1058 if(token
&& *token
) preserve_password
= 1; /* resave it, no need to ask */
1059 #endif /* LOCAL_PASSWD_CACHE */
1061 user
[NETMAXUSER
-1] = '\0';
1063 /* The Old* variables is what c_client knows */
1064 OldRefreshToken
= login
->cancel_refresh_token
? NULL
: login
->param
[OA2_RefreshToken
].value
;
1065 OldAccessToken
= login
->access_token
;
1066 OldExpirationTime
= login
->expiration
;
1068 /* The New* variables is what Alpine knows */
1069 NewRefreshToken
= NewAccessToken
= NULL
;
1070 NewExpirationTime
= 0L;
1071 ChangeAccessToken
= ChangeRefreshToken
= ChangeExpirationTime
= 0;
1073 if(token
&& *token
&& !login
->cancel_refresh_token
){
1077 t
= strchr(s
, PWDAUTHSEP
);
1079 NewRefreshToken
= cpystr(s
);
1082 NewRefreshToken
= cpystr(s
);
1084 t
= strchr(s
, PWDAUTHSEP
);
1086 NewAccessToken
= cpystr(s
);
1089 NewAccessToken
= cpystr(s
);
1091 NewExpirationTime
= strtol(s
, &s
, 10);
1092 if(NewExpirationTime
<= 0 || NewExpirationTime
<= time(0))
1093 NewExpirationTime
= 0L;
1096 /* check we got good information, and send good information below */
1097 if(NewRefreshToken
&& !*NewRefreshToken
)
1098 fs_give((void **) &NewRefreshToken
);
1099 if(NewAccessToken
&& (NewExpirationTime
== 0L || !*NewAccessToken
))
1100 fs_give((void **) &NewAccessToken
);
1103 if(NewRefreshToken
== NULL
)
1104 login
->first_time
++;
1106 if(login
->first_time
){ /* count how many authorization methods we support */
1109 for(nmethods
= 0, oa2
= alpine_oauth2_list
; oa2
&& oa2
->name
; oa2
++){
1110 for(j
= 0; j
< OAUTH2_TOT_EQUIV
1112 && oa2
->host
[j
] != NULL
1113 && strucmp(oa2
->host
[j
], mb
->orighost
) != 0; j
++);
1114 if(oa2
&& oa2
->host
&& j
< OAUTH2_TOT_EQUIV
&& oa2
->host
[j
])
1119 oa2list
= oauth2_select_flow(mb
->orighost
);
1121 if(!oa2list
) registered
= 0;
1124 /* Default to saving what we already had saved */
1126 SaveRefreshToken
= login
->cancel_refresh_token
? NULL
: NewRefreshToken
;
1127 SaveAccessToken
= NewAccessToken
;
1128 SaveExpirationTime
= NewExpirationTime
;
1130 /* Translation of the logic below:
1131 * if (c-client has a refresh token
1132 and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){
1133 forget the Alpine refresh token;
1134 make the Alpine Refresh token = c-client refresh token.;
1135 signal that we changed the refresh token;
1136 In this situation we do not need to clear up the Alpine access token. This will
1137 expire, so we can use it until it expires. We can save the c-client refresh token
1138 together with the Alpine Access Token and the expiration date of the Alpine Access
1140 } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){
1141 forget the Alpine refresh token;
1142 if Alpine has an access token, forget it;
1143 reset the expiration time
1144 signal that we changed the refresh token; (because the service expired it)
1148 if(OldRefreshToken
!= NULL
1149 && (NewRefreshToken
== NULL
|| strcmp(OldRefreshToken
, NewRefreshToken
))){
1150 if(NewRefreshToken
) fs_give((void **) &NewRefreshToken
);
1151 NewRefreshToken
= cpystr(OldRefreshToken
);
1152 ChangeRefreshToken
++;
1153 SaveRefreshToken
= OldRefreshToken
;
1154 SaveAccessToken
= NewAccessToken
;
1155 SaveExpirationTime
= NewExpirationTime
;
1156 } else if (OldRefreshToken
== NULL
&& NewRefreshToken
!= NULL
&& trial
> 0){
1157 fs_give((void **) &NewRefreshToken
);
1158 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1159 NewExpirationTime
= 0L;
1160 ChangeRefreshToken
++;
1161 SaveRefreshToken
= NULL
;
1162 SaveAccessToken
= NULL
;
1163 SaveExpirationTime
= 0L;
1166 if(OldAccessToken
!= NULL
1167 && (NewAccessToken
== NULL
|| strcmp(OldAccessToken
, NewAccessToken
))){
1168 if(NewAccessToken
) fs_give((void **) &NewAccessToken
);
1169 NewAccessToken
= cpystr(OldAccessToken
);
1170 ChangeAccessToken
++;
1171 NewExpirationTime
= OldExpirationTime
;
1172 SaveRefreshToken
= NewRefreshToken
;
1173 SaveAccessToken
= NewAccessToken
;
1174 SaveExpirationTime
= NewExpirationTime
;
1178 login
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1179 login
->access_token
= SaveAccessToken
;
1180 login
->expiration
= SaveExpirationTime
;
1182 oa2list
->param
[OA2_RefreshToken
].value
= SaveRefreshToken
;
1183 oa2list
->access_token
= SaveAccessToken
;
1184 oa2list
->expiration
= SaveExpirationTime
;
1185 oa2list
->first_time
= login
->first_time
;
1186 oa2list
->cancel_refresh_token
= login
->cancel_refresh_token
;
1187 *login
= *oa2list
; /* load login pointer */
1190 if(!ChangeAccessToken
&& !ChangeRefreshToken
&& !login
->cancel_refresh_token
)
1193 /* get ready to save this information. The format will be
1194 * RefreshToken \001 LastAccessToken \001 ExpirationTime
1195 * (spaces added for clarity, \001 is PWDAUTHSEP)
1197 if(token
) fs_give((void **) &token
);
1198 sprintf(tmp
, "%lu", SaveExpirationTime
);
1199 tmp
[sizeof(tmp
) - 1] = '\0';
1200 len
= strlen(SaveRefreshToken
? SaveRefreshToken
: "")
1201 + strlen(SaveAccessToken
? SaveAccessToken
: "")
1203 token
= fs_get(len
+ 1);
1204 sprintf(token
, "%s%c%s%c%lu",
1205 SaveRefreshToken
? SaveRefreshToken
: "", PWDAUTHSEP
,
1206 SaveAccessToken
? SaveAccessToken
: "", PWDAUTHSEP
,
1207 SaveExpirationTime
);
1209 /* remember the access information for next time */
1210 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
1211 imap_set_passwd_auth(&mm_login_list
, token
,
1212 altuserforcache
? altuserforcache
: user
, hostlist
,
1213 (mb
->sslflag
||mb
->tlsflag
), 0, 0, OA2NAME
);
1214 #ifdef LOCAL_PASSWD_CACHE
1215 /* if requested, remember it on disk for next session */
1216 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
1217 set_passfile_passwd_auth(ps_global
->pinerc
, token
,
1218 altuserforcache
? altuserforcache
: user
, hostlist
,
1219 (mb
->sslflag
||mb
->tlsflag
),
1220 (preserve_password
== -1 ? 0
1221 : (preserve_password
== 0 ? 2 :1)), OA2NAME
);
1222 #endif /* LOCAL_PASSWD_CACHE */
1224 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1228 set_alpine_id(unsigned char *pname
, unsigned char *pversion
)
1232 if(!pname
|| !pversion
) return NULL
;
1234 id
= fs_get(sizeof(IDLIST
));
1235 id
->name
= cpystr("name");
1236 id
->value
= cpystr(pname
);
1237 id
->next
= fs_get(sizeof(IDLIST
));
1238 id
->next
->name
= cpystr("version");
1239 id
->next
->value
= cpystr(pversion
);
1240 id
->next
->next
= NULL
;
1244 /*----------------------------------------------------------------------
1245 receive notification from IMAP
1247 Args: stream -- Mail stream message is relevant to
1248 string -- The message text
1249 errflg -- Set if it is a serious error
1251 Result: message displayed in status line
1253 The facility is for general notices, such as connection to server;
1254 server shutting down etc... It is used infrequently.
1255 ----------------------------------------------------------------------*/
1257 mm_notify(MAILSTREAM
*stream
, char *string
, long int errflg
)
1262 now
= time((time_t *)0);
1263 tm_now
= localtime(&now
);
1265 /* be sure to log the message... */
1267 if(ps_global
->debug_imap
|| ps_global
->debugmem
)
1268 dprint((errflg
== TCPDEBUG
|| errflg
== HTTPDEBUG
? 7 : 2,
1269 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n",
1270 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1271 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1272 (!errflg
) ? "babble" :
1273 (errflg
== ERROR
) ? "error" :
1274 (errflg
== WARN
) ? "warning" :
1275 (errflg
== PARSE
) ? "parse" :
1276 (errflg
== TCPDEBUG
) ? "tcp" :
1277 (errflg
== HTTPDEBUG
) ? "http" :
1278 (errflg
== BYE
) ? "bye" : "unknown",
1279 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1280 string
? string
: "?"));
1283 snprintf(ps_global
->last_error
, sizeof(ps_global
->last_error
), "%s : %.*s",
1284 (stream
&& stream
->mailbox
) ? stream
->mailbox
: "-no folder-",
1285 (int) MIN(MAX_SCREEN_COLS
, sizeof(ps_global
->last_error
)-70),
1287 ps_global
->last_error
[ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
1288 : sizeof(ps_global
->last_error
)-1] = '\0';
1291 * Then either set special bits in the pine struct or
1292 * display the message if it's tagged as an "ALERT" or
1293 * its errflg > NIL (i.e., WARN, or ERROR)
1297 * We'd like to sp_mark_stream_dead() here but we can't do that because
1298 * that might call mail_close and we are already in a c-client callback.
1299 * So just set the dead bit and clean it up later.
1301 sp_set_dead_stream(stream
, 1);
1302 else if(!strncmp(string
, "[TRYCREATE]", 11))
1303 ps_global
->try_to_create
= 1;
1304 else if(!strncmp(string
, "[REFERRAL ", 10))
1305 ; /* handled in the imap_referral() callback */
1306 else if(!strncmp(string
, "[ALERT]", 7))
1307 q_status_message2(SM_MODAL
, 3, 3,
1308 _("Alert received while accessing \"%s\": %s"),
1309 (stream
&& stream
->mailbox
)
1310 ? stream
->mailbox
: "-no folder-",
1311 rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf
+10000),
1312 SIZEOF_20KBUF
-10000, string
));
1313 else if(!strncmp(string
, "[UNSEEN ", 8)){
1317 for(p
= string
+ 8; isdigit(*p
); p
++)
1318 n
= (n
* 10) + (*p
- '0');
1320 sp_set_first_unseen(stream
, n
);
1322 else if(!strncmp(string
, "[READ-ONLY]", 11)
1323 && !(stream
&& stream
->mailbox
&& IS_NEWS(stream
)))
1324 q_status_message2(SM_ORDER
| SM_DING
, 3, 3, "%s : %s",
1325 (stream
&& stream
->mailbox
)
1326 ? stream
->mailbox
: "-no folder-",
1328 else if((errflg
&& errflg
!= BYE
&& errflg
!= PARSE
)
1329 && !ps_global
->noshow_error
1331 && (ps_global
->noshow_warn
|| (stream
&& stream
->unhealthy
))))
1332 q_status_message(SM_ORDER
| ((errflg
== ERROR
) ? SM_DING
: 0),
1333 3, 6, ps_global
->last_error
);
1337 /*----------------------------------------------------------------------
1338 Queue imap log message for display in the message line
1340 Args: string -- The message
1341 errflg -- flag set to 1 if pertains to an error
1343 Result: Message queued for display
1345 The c-client/imap reports most of it's status and errors here
1348 mm_log(char *string
, long int errflg
)
1350 char message
[sizeof(ps_global
->c_client_error
)];
1352 int was_capitalized
;
1353 static char saw_kerberos_init_warning
;
1357 now
= time((time_t *)0);
1358 tm_now
= localtime(&now
);
1360 dprint((((errflg
== TCPDEBUG
) && ps_global
->debug_tcp
) ? 1 :
1361 (errflg
== TCPDEBUG
) ? 10 :
1362 ((errflg
== HTTPDEBUG
) && ps_global
->debug_http
) ? 1 :
1363 (errflg
== HTTPDEBUG
) ? 10 : 2,
1364 "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n",
1365 tm_now
->tm_hour
, tm_now
->tm_min
, tm_now
->tm_sec
,
1366 tm_now
->tm_mon
+1, tm_now
->tm_mday
,
1367 (!errflg
) ? "babble" :
1368 (errflg
== ERROR
) ? "error" :
1369 (errflg
== WARN
) ? "warning" :
1370 (errflg
== PARSE
) ? "parse" :
1371 (errflg
== TCPDEBUG
) ? "tcp" :
1372 (errflg
== HTTPDEBUG
) ? "http" :
1373 (errflg
== BYE
) ? "bye" : "unknown",
1374 string
? string
: "?"));
1376 if(errflg
== ERROR
&& !strncmp(string
, "[TRYCREATE]", 11)){
1377 ps_global
->try_to_create
= 1;
1380 else if(ps_global
->try_to_create
1381 || !strncmp(string
, "[CLOSED]", 8)
1382 || (sp_dead_stream(ps_global
->mail_stream
) && strstr(string
, "No-op")))
1384 * Don't display if creating new folder OR
1385 * warning about a dead stream ...
1389 strncpy(message
, string
, sizeof(message
));
1390 message
[sizeof(message
) - 1] = '\0';
1392 if(errflg
== WARN
&& srchstr(message
, "try running kinit") != NULL
){
1393 if(saw_kerberos_init_warning
)
1396 saw_kerberos_init_warning
= 1;
1399 /*---- replace all "mailbox" with "folder" ------*/
1400 occurence
= srchstr(message
, "mailbox");
1403 || isspace((unsigned char) *(occurence
+7))
1404 || *(occurence
+7) == ':'){
1405 was_capitalized
= isupper((unsigned char) *occurence
);
1406 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 7, (errflg
== PARSE
? "address" : "folder"));
1408 *occurence
= (errflg
== PARSE
? 'A' : 'F');
1413 occurence
= srchstr(occurence
, "mailbox");
1416 /*---- replace all "GSSAPI" with "Kerberos" ------*/
1417 occurence
= srchstr(message
, "GSSAPI");
1420 || isspace((unsigned char) *(occurence
+6))
1421 || *(occurence
+6) == ':')
1422 rplstr(occurence
, sizeof(message
)-(occurence
-message
), 6, "Kerberos");
1426 occurence
= srchstr(occurence
, "GSSAPI");
1430 ps_global
->mm_log_error
= 1;
1432 if(errflg
== PARSE
|| (errflg
== ERROR
&& ps_global
->noshow_error
))
1433 strncpy(ps_global
->c_client_error
, message
,
1434 sizeof(ps_global
->c_client_error
));
1436 if(ps_global
->noshow_error
1437 || (ps_global
->noshow_warn
&& errflg
== WARN
)
1438 || !(errflg
== ERROR
|| errflg
== WARN
))
1439 return; /* Only care about errors; don't print when asked not to */
1441 /*---- Display the message ------*/
1442 q_status_message((errflg
== ERROR
) ? (SM_ORDER
| SM_DING
) : SM_ORDER
,
1444 strncpy(ps_global
->last_error
, message
, sizeof(ps_global
->last_error
));
1445 ps_global
->last_error
[sizeof(ps_global
->last_error
) - 1] = '\0';
1449 mm_login_method_work(NETMBX
*mb
, char *user
, void *login
, long int trial
,
1450 char *method
, char *usethisprompt
, char *altuserforcache
)
1454 if(strucmp(method
, OA2NAME
) == 0 || strucmp(method
, BEARERNAME
) == 0)
1455 mm_login_oauth2(mb
, user
, method
, (OAUTH2_S
*) login
, trial
, usethisprompt
, altuserforcache
);
1459 mm_login_work(NETMBX
*mb
, char *user
, char **pwd
, long int trial
,
1460 char *usethisprompt
, char *altuserforcache
)
1462 char tmp
[MAILTMPLEN
];
1463 char prompt
[1000], *last
;
1464 char port
[20], non_def_port
[20], insecure
[20];
1465 char defuser
[NETMAXUSER
];
1466 char hostleadin
[80], hostname
[200], defubuf
[200];
1467 char logleadin
[80], pwleadin
[50];
1468 char hostlist0
[MAILTMPLEN
], hostlist1
[MAILTMPLEN
];
1469 /* TRANSLATORS: when logging in, this text is added to the prompt to show
1470 that the password will be sent unencrypted over the network. This is
1471 just a warning message that gets added parenthetically when the user
1472 is asked for a password. */
1473 char *insec
= _(" (INSECURE)");
1474 /* TRANSLATORS: Retrying is shown when the user is being asked for a password
1475 after having already failed at least once. */
1476 char *retry
= _("Retrying - ");
1477 /* TRANSLATORS: A label for the hostname that the user is logging in on */
1478 char *hostlabel
= _("HOST");
1479 /* TRANSLATORS: user is logging in as a particular user (a particular
1480 login name), this is just labelling that user name. */
1481 char *userlabel
= _("USER");
1482 STRLIST_S hostlist
[2];
1484 int len
, rc
, q_line
, flags
;
1485 int oespace
, avail
, need
, save_dont_use
;
1488 #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE)
1489 int preserve_password
= -1;
1492 dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n",
1493 trial
, mb
->user
? mb
->user
: "(null)",
1494 mb
->service
? mb
->service
: "(null)",
1495 mb
->port
? " port=" : "",
1496 mb
->port
? comatose(mb
->port
) : "",
1497 altuserforcache
? " altuserforcache =" : "",
1498 altuserforcache
? altuserforcache
: ""));
1499 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
1501 save_in_init
= ps_global
->in_init_seq
;
1502 ps_global
->in_init_seq
= 0;
1503 ps_global
->no_newmail_check_from_optionally_enter
= 1;
1505 /* make sure errors are seen */
1507 flush_status_messages(0);
1509 /* redo app id in case we are loging in to an IMAP server that supports the IMAP ID extension */
1510 free_id(&ps_global
->id
);
1511 ps_global
->id
= set_alpine_id(PACKAGE_NAME
, PACKAGE_VERSION
);
1512 mail_parameters(NULL
, SET_IDPARAMS
, (void *) ps_global
->id
);
1515 * Add port number to hostname if going through a tunnel or something
1517 non_def_port
[0] = '\0';
1518 if(mb
->port
&& mb
->service
&&
1519 (sv
= getservbyname(mb
->service
, "tcp")) &&
1520 (mb
->port
!= ntohs(sv
->s_port
))){
1521 snprintf(non_def_port
, sizeof(non_def_port
), ":%lu", mb
->port
);
1522 non_def_port
[sizeof(non_def_port
)-1] = '\0';
1523 dprint((9, "mm_login: using non-default port=%s\n",
1524 non_def_port
? non_def_port
: "?"));
1528 * set up host list for sybil servers...
1531 strncpy(hostlist0
, mb
->host
, sizeof(hostlist0
)-1);
1532 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1533 strncat(hostlist0
, non_def_port
, sizeof(hostlist0
)-strlen(hostlist0
)-1);
1534 hostlist0
[sizeof(hostlist0
)-1] = '\0';
1535 hostlist
[0].name
= hostlist0
;
1536 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1537 strncpy(hostlist1
, mb
->orighost
, sizeof(hostlist1
)-1);
1538 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1539 strncat(hostlist1
, non_def_port
, sizeof(hostlist1
)-strlen(hostlist1
)-1);
1540 hostlist1
[sizeof(hostlist1
)-1] = '\0';
1541 hostlist
[0].next
= &hostlist
[1];
1542 hostlist
[1].name
= hostlist1
;
1543 hostlist
[1].next
= NULL
;
1546 hostlist
[0].next
= NULL
;
1549 hostlist
[0].name
= mb
->host
;
1550 if(mb
->orighost
&& mb
->orighost
[0] && strucmp(mb
->host
, mb
->orighost
)){
1551 hostlist
[0].next
= &hostlist
[1];
1552 hostlist
[1].name
= mb
->orighost
;
1553 hostlist
[1].next
= NULL
;
1556 hostlist
[0].next
= NULL
;
1559 if(hostlist
[0].name
){
1560 dprint((9, "mm_login: host=%s\n",
1561 hostlist
[0].name
? hostlist
[0].name
: "?"));
1562 if(hostlist
[0].next
&& hostlist
[1].name
){
1563 dprint((9, "mm_login: orighost=%s\n", hostlist
[1].name
));
1568 * Initialize user name with either
1569 * 1) /user= value in the stream being logged into,
1570 * or 2) the user name we're running under.
1572 * Note that VAR_USER_ID is not yet initialized if this login is
1573 * the one to access the remote config file. In that case, the user
1574 * can supply the username in the config file name with /user=.
1576 if(trial
== 0L && !altuserforcache
){
1577 strncpy(user
, (*mb
->user
) ? mb
->user
:
1578 ps_global
->VAR_USER_ID
? ps_global
->VAR_USER_ID
: "",
1580 user
[NETMAXUSER
-1] = '\0';
1582 /* try last working password associated with this host. */
1583 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1584 (mb
->sslflag
||mb
->tlsflag
))){
1585 dprint((9, "mm_login: found a password to try\n"));
1586 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1587 ps_global
->in_init_seq
= save_in_init
;
1591 #ifdef LOCAL_PASSWD_CACHE
1592 /* check to see if there's a password left over from last session */
1593 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1594 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1595 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1596 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1597 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1598 (mb
->sslflag
||mb
->tlsflag
));
1599 dprint((9, "mm_login: found a password in passfile to try\n"));
1600 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1601 ps_global
->in_init_seq
= save_in_init
;
1604 #endif /* LOCAL_PASSWD_CACHE */
1607 * If no explicit user name supplied and we've not logged in
1608 * with our local user name, see if we've visited this
1609 * host before as someone else.
1612 ((last
= imap_get_user(mm_login_list
, hostlist
))
1613 #ifdef LOCAL_PASSWD_CACHE
1615 (last
= get_passfile_user(ps_global
->pinerc
, hostlist
))
1616 #endif /* LOCAL_PASSWD_CACHE */
1618 strncpy(user
, last
, NETMAXUSER
);
1619 user
[NETMAXUSER
-1] = '\0';
1620 dprint((9, "mm_login: found user=%s\n",
1621 user
? user
: "?"));
1623 /* try last working password associated with this host/user. */
1624 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1625 (mb
->sslflag
||mb
->tlsflag
))){
1627 "mm_login: found a password for user=%s to try\n",
1628 user
? user
: "?"));
1629 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1630 ps_global
->in_init_seq
= save_in_init
;
1634 #ifdef LOCAL_PASSWD_CACHE
1635 /* check to see if there's a password left over from last session */
1636 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1637 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1638 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1639 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1640 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1641 (mb
->sslflag
||mb
->tlsflag
));
1643 "mm_login: found a password for user=%s in passfile to try\n",
1644 user
? user
: "?"));
1645 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1646 ps_global
->in_init_seq
= save_in_init
;
1649 #endif /* LOCAL_PASSWD_CACHE */
1652 #if !defined(DOS) && !defined(OS2)
1653 if(!*mb
->user
&& !*user
&&
1654 (last
= (ps_global
->ui
.login
&& ps_global
->ui
.login
[0])
1655 ? ps_global
->ui
.login
: NULL
)
1657 strncpy(user
, last
, NETMAXUSER
);
1658 user
[NETMAXUSER
-1] = '\0';
1659 dprint((9, "mm_login: found user=%s\n",
1660 user
? user
: "?"));
1662 /* try last working password associated with this host. */
1663 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1664 (mb
->sslflag
||mb
->tlsflag
))){
1665 dprint((9, "mm_login:ui: found a password to try\n"));
1666 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1667 ps_global
->in_init_seq
= save_in_init
;
1671 #ifdef LOCAL_PASSWD_CACHE
1672 /* check to see if there's a password left over from last session */
1673 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1674 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1675 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1676 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1677 update_passfile_hostlist(ps_global
->pinerc
, user
, hostlist
,
1678 (mb
->sslflag
||mb
->tlsflag
));
1679 dprint((9, "mm_login:ui: found a password in passfile to try\n"));
1680 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1681 ps_global
->in_init_seq
= save_in_init
;
1684 #endif /* LOCAL_PASSWD_CACHE */
1689 user
[NETMAXUSER
-1] = '\0';
1695 * Even if we have a user now, user gets a chance to change it.
1697 ps_global
->mangled_footer
= 1;
1698 if(!*mb
->user
&& !altuserforcache
){
1703 * Instead of offering user with a value that the user can edit,
1704 * we offer [user] as a default so that the user can type CR to
1705 * use it. Otherwise, the user has to type in whole name.
1707 strncpy(defuser
, user
, sizeof(defuser
)-1);
1708 defuser
[sizeof(defuser
)-1] = '\0';
1712 * Need space for "Retrying - "
1718 * about 15 chars for input
1721 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1722 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1723 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1725 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1726 hostname
[sizeof(hostname
)-1] = '\0';
1729 * Add port number to hostname if going through a tunnel or something
1732 strncpy(port
, non_def_port
, sizeof(port
));
1737 /* if not encrypted and SSL/TLS is supported */
1738 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1739 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1740 strncpy(insecure
, insec
, sizeof(insecure
));
1742 /* TRANSLATORS: user is being asked to type in their login name */
1743 snprintf(logleadin
, sizeof(logleadin
), " %s", _("ENTER LOGIN NAME"));
1745 snprintf(defubuf
, sizeof(defubuf
), "%s%s%s : ", (*defuser
) ? " [" : "",
1746 (*defuser
) ? defuser
: "",
1747 (*defuser
) ? "]" : "");
1748 defubuf
[sizeof(defubuf
)-1] = '\0';
1749 /* space reserved after prompt */
1750 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
1752 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
1753 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
1754 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) + oespace
;
1756 /* If we're retrying cut the hostname back to the first word. */
1757 if(avail
< need
&& trial
> 0){
1760 len
= strlen(hostname
);
1761 if((p
= strchr(hostname
, '.')) != NULL
){
1763 need
-= (len
- strlen(hostname
));
1768 need
-= utf8_width(retry
);
1773 /* reduce length of logleadin */
1774 len
= utf8_width(logleadin
);
1775 /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because
1776 longer version doesn't fit on screen */
1777 snprintf(logleadin
, sizeof(logleadin
), " %s", _("LOGIN"));
1778 need
-= (len
- utf8_width(logleadin
));
1781 /* get two spaces from hostleadin */
1782 len
= utf8_width(hostleadin
);
1783 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
1784 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
1785 hostleadin
[sizeof(hostleadin
)-1] = '\0';
1786 need
-= (len
- utf8_width(hostleadin
));
1788 /* get rid of port */
1789 if(avail
< need
&& strlen(port
) > 0){
1790 need
-= strlen(port
);
1798 * Reduce space for hostname. Best we can do is 6 chars
1801 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
1802 len
= strlen(hostname
);
1803 strncpy(hostname
+reduce_to
-3, "...", 4);
1804 need
-= (len
- strlen(hostname
));
1806 if(avail
< need
&& strlen(insecure
) > 0){
1807 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
1809 insecure
[strlen(insecure
)-4] = ')';
1810 insecure
[strlen(insecure
)-3] = '\0';
1813 need
-= utf8_width(insecure
);
1819 if(strlen(defubuf
) > 3){
1820 len
= strlen(defubuf
);
1821 strncpy(defubuf
, " [..] :", 9);
1822 need
-= (len
- strlen(defubuf
));
1826 strncpy(defubuf
, ":", 2);
1829 * If it still doesn't fit, optionally_enter gets
1830 * to worry about it.
1838 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s",
1839 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
);
1840 prompt
[sizeof(prompt
)-1] = '\0';
1844 mm_login_alt_cue(mb
);
1846 flags
= OE_APPEND_CURRENT
;
1847 save_dont_use
= ps_global
->dont_use_init_cmds
;
1848 ps_global
->dont_use_init_cmds
= 1;
1850 if(!*user
&& *defuser
){
1851 strncpy(user
, defuser
, NETMAXUSER
);
1852 user
[NETMAXUSER
-1] = '\0';
1855 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, pwd
, NETMAXPASSWD
,
1856 #ifdef LOCAL_PASSWD_CACHE
1857 is_using_passfile() ? 1 :
1858 #endif /* LOCAL_PASSWD_CACHE */
1859 0, 0, &preserve_password
);
1860 ps_global
->dont_use_init_cmds
= save_dont_use
;
1861 if(rc
== 0 && *user
&& *pwd
)
1863 #else /* !_WINDOWS */
1864 rc
= optionally_enter(user
, q_line
, 0, NETMAXUSER
,
1865 prompt
, NULL
, help
, &flags
);
1866 #endif /* !_WINDOWS */
1867 ps_global
->dont_use_init_cmds
= save_dont_use
;
1870 help
= help
== NO_HELP
? h_oe_login
: NO_HELP
;
1875 if(rc
== 0 && !*user
){
1876 strncpy(user
, defuser
, NETMAXUSER
);
1877 user
[NETMAXUSER
-1] = '\0';
1884 if(rc
== 1 || !user
[0]) {
1885 ps_global
->user_says_cancel
= (rc
== 1);
1890 strncpy(user
, mb
->user
, NETMAXUSER
);
1891 user
[NETMAXUSER
-1] = '\0';
1894 user
[NETMAXUSER
-1] = '\0';
1896 if(!(user
[0] || altuserforcache
)){
1897 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1898 ps_global
->in_init_seq
= save_in_init
;
1903 * Now that we have a user, we can check in the cache again to see
1904 * if there is a password there. Try last working password associated
1905 * with this host and user.
1907 if(trial
== 0L && !*mb
->user
&& !altuserforcache
){
1908 if(imap_get_passwd(mm_login_list
, pwd
, user
, hostlist
,
1909 (mb
->sslflag
||mb
->tlsflag
))){
1910 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1911 ps_global
->in_init_seq
= save_in_init
;
1915 #ifdef LOCAL_PASSWD_CACHE
1916 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1917 user
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1918 imap_set_passwd(&mm_login_list
, *pwd
, user
,
1919 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1920 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1921 ps_global
->in_init_seq
= save_in_init
;
1924 #endif /* LOCAL_PASSWD_CACHE */
1926 else if(trial
== 0 && altuserforcache
){
1927 if(imap_get_passwd(mm_login_list
, pwd
, altuserforcache
, hostlist
,
1928 (mb
->sslflag
||mb
->tlsflag
))){
1929 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1930 ps_global
->in_init_seq
= save_in_init
;
1934 #ifdef LOCAL_PASSWD_CACHE
1935 if(get_passfile_passwd(ps_global
->pinerc
, pwd
,
1936 altuserforcache
, hostlist
, (mb
->sslflag
||mb
->tlsflag
))){
1937 imap_set_passwd(&mm_login_list
, *pwd
, altuserforcache
,
1938 hostlist
, (mb
->sslflag
||mb
->tlsflag
), 0, 0);
1939 ps_global
->no_newmail_check_from_optionally_enter
= 0;
1940 ps_global
->in_init_seq
= save_in_init
;
1943 #endif /* LOCAL_PASSWD_CACHE */
1947 * Didn't find password in cache or this isn't the first try. Ask user.
1952 * Need space for "Retrying - "
1958 * " ENTER PASSWORD: "
1959 * about 15 chars for input
1962 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s: ",
1963 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+ " : "", hostlabel
);
1965 strncpy(hostname
, mb
->host
, sizeof(hostname
)-1);
1966 hostname
[sizeof(hostname
)-1] = '\0';
1969 * Add port number to hostname if going through a tunnel or something
1972 strncpy(port
, non_def_port
, sizeof(port
));
1978 /* if not encrypted and SSL/TLS is supported */
1979 if(!(mb
->sslflag
||mb
->tlsflag
) &&
1980 mail_parameters(NIL
, GET_SSLDRIVER
, NIL
))
1981 strncpy(insecure
, insec
, sizeof(insecure
));
1984 strncpy(logleadin
, usethisprompt
, sizeof(logleadin
));
1985 logleadin
[sizeof(logleadin
)-1] = '\0';
1990 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
1992 strncpy(defubuf
, user
, sizeof(defubuf
)-1);
1993 defubuf
[sizeof(defubuf
)-1] = '\0';
1996 /* TRANSLATORS: user is being asked to type in their password */
1997 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("ENTER PASSWORD"));
1999 /* space reserved after prompt */
2000 oespace
= MAX(MIN(15, (ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80)/5), 6);
2002 avail
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
2003 need
= utf8_width(retry
) + utf8_width(hostleadin
) + strlen(hostname
) + strlen(port
) +
2004 utf8_width(insecure
) + utf8_width(logleadin
) + strlen(defubuf
) +
2005 utf8_width(pwleadin
) + oespace
;
2007 if(avail
< need
&& trial
> 0){
2010 len
= strlen(hostname
);
2011 if((p
= strchr(hostname
, '.')) != NULL
){
2013 need
-= (len
- strlen(hostname
));
2018 need
-= utf8_width(retry
);
2024 snprintf(logleadin
, sizeof(logleadin
), " %s: ", userlabel
);
2028 rplstr(pwleadin
, sizeof(pwleadin
), 1, "");
2032 /* get two spaces from hostleadin */
2033 len
= utf8_width(hostleadin
);
2034 snprintf(hostleadin
, sizeof(hostleadin
), "%s%s:",
2035 (!ps_global
->ttyo
&& (mb
->sslflag
||mb
->tlsflag
)) ? "+" : "", hostlabel
);
2036 hostleadin
[sizeof(hostleadin
)-1] = '\0';
2037 need
-= (len
- utf8_width(hostleadin
));
2039 /* get rid of port */
2040 if(avail
< need
&& strlen(port
) > 0){
2041 need
-= strlen(port
);
2046 len
= utf8_width(pwleadin
);
2047 /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */
2048 snprintf(pwleadin
, sizeof(pwleadin
), " %s: ", _("PASSWORD"));
2049 need
-= (len
- utf8_width(pwleadin
));
2057 * Reduce space for hostname. Best we can do is 6 chars
2060 reduce_to
= (need
- avail
< strlen(hostname
) - 6) ? (strlen(hostname
)-(need
-avail
)) : 6;
2061 len
= strlen(hostname
);
2062 strncpy(hostname
+reduce_to
-3, "...", 4);
2063 need
-= (len
- strlen(hostname
));
2065 if(avail
< need
&& strlen(insecure
) > 0){
2066 if(need
- avail
<= 3 && !strcmp(insecure
," (INSECURE)")){
2068 insecure
[strlen(insecure
)-4] = ')';
2069 insecure
[strlen(insecure
)-3] = '\0';
2072 need
-= utf8_width(insecure
);
2078 len
= utf8_width(logleadin
);
2079 strncpy(logleadin
, " ", sizeof(logleadin
));
2080 logleadin
[sizeof(logleadin
)-1] = '\0';
2081 need
-= (len
- utf8_width(logleadin
));
2084 reduce_to
= (need
- avail
< strlen(defubuf
) - 6) ? (strlen(defubuf
)-(need
-avail
)) : 0;
2086 strncpy(defubuf
+reduce_to
-3, "...", 4);
2095 snprintf(prompt
, sizeof(prompt
), "%s%s%s%s%s%s%s%s",
2096 retry
, hostleadin
, hostname
, port
, insecure
, logleadin
, defubuf
, pwleadin
);
2097 prompt
[sizeof(prompt
)-1] = '\0';
2102 mm_login_alt_cue(mb
);
2104 save_dont_use
= ps_global
->dont_use_init_cmds
;
2105 ps_global
->dont_use_init_cmds
= 1;
2106 flags
= F_ON(F_QUELL_ASTERISKS
, ps_global
) ? OE_PASSWD_NOAST
: OE_PASSWD
;
2107 flags
|= OE_KEEP_TRAILING_SPACE
;
2111 tmpp
= fs_get(NETMAXPASSWD
*sizeof(char));
2112 rc
= os_login_dialog(mb
, user
, NETMAXUSER
, &tmpp
, NETMAXPASSWD
, 0, 1,
2113 &preserve_password
);
2114 strncpy(tmp
, tmpp
, sizeof(tmp
));
2115 tmp
[sizeof(tmp
)-1] = '\0';
2116 if(tmpp
) fs_give((void **)&tmpp
);
2118 #else /* !_WINDOWS */
2119 rc
= optionally_enter(tmp
, q_line
, 0, NETMAXPASSWD
,
2120 prompt
, NULL
, help
, &flags
);
2121 #endif /* !_WINDOWS */
2122 if(rc
!= 1) *pwd
= cpystr(tmp
);
2123 ps_global
->dont_use_init_cmds
= save_dont_use
;
2126 help
= help
== NO_HELP
? h_oe_passwd
: NO_HELP
;
2134 if(rc
== 1 || !tmp
[0]) {
2135 ps_global
->user_says_cancel
= (rc
== 1);
2137 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2138 ps_global
->in_init_seq
= save_in_init
;
2145 /* remember the password for next time */
2146 if(F_OFF(F_DISABLE_PASSWORD_CACHING
,ps_global
))
2147 imap_set_passwd(&mm_login_list
, *pwd
,
2148 altuserforcache
? altuserforcache
: user
, hostlist
,
2149 (mb
->sslflag
||mb
->tlsflag
), 0, 0);
2150 #ifdef LOCAL_PASSWD_CACHE
2151 /* if requested, remember it on disk for next session */
2152 if(save_password
&& F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
2153 set_passfile_passwd(ps_global
->pinerc
, *pwd
,
2154 altuserforcache
? altuserforcache
: user
, hostlist
,
2155 (mb
->sslflag
||mb
->tlsflag
),
2156 (preserve_password
== -1 ? 0
2157 : (preserve_password
== 0 ? 2 :1)));
2158 #endif /* LOCAL_PASSWD_CACHE */
2160 ps_global
->no_newmail_check_from_optionally_enter
= 0;
2165 mm_login_alt_cue(NETMBX
*mb
)
2167 if(ps_global
->ttyo
){
2170 lastc
= pico_set_colors(ps_global
->VAR_TITLE_FORE_COLOR
,
2171 ps_global
->VAR_TITLE_BACK_COLOR
,
2174 mark_titlebar_dirty();
2175 PutLine0(0, ps_global
->ttyo
->screen_cols
- 1,
2176 (mb
->sslflag
||mb
->tlsflag
) ? "+" : " ");
2179 (void)pico_set_colorp(lastc
, PSC_NONE
);
2180 free_color_pair(&lastc
);
2188 /*----------------------------------------------------------------------
2189 Receive notification of an error writing to disk
2191 Args: stream -- The stream the error occurred on
2192 errcode -- The system error code (errno)
2193 serious -- Flag indicating error is serious (mail may be lost)
2195 Result: If error is non serious, the stream is marked as having an error
2196 and deletes are disallowed until error clears
2197 If error is serious this goes modal, allowing the user to retry
2198 or get a shell escape to fix the condition. When the condition is
2199 serious it means that mail existing in the mailbox will be lost
2200 if Pine exits without writing, so we try to induce the user to
2201 fix the error, go get someone that can fix the error, or whatever
2202 and don't provide an easy way out.
2205 mm_diskerror (MAILSTREAM
*stream
, long int errcode
, long int serious
)
2209 static ESCKEY_S de_opts
[] = {
2210 {'r', 'r', "R", "Retry"},
2211 {'f', 'f', "F", "FileBrowser"},
2212 {'s', 's', "S", "ShellPrompt"},
2215 #define DE_COLS (ps_global->ttyo->screen_cols)
2216 #define DE_LINE (ps_global->ttyo->screen_rows - 3)
2218 #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : "<no folder>")
2220 "Disk error! Choose Retry, or the File browser or Shell to clean up: "
2221 #define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\""
2223 "The reported error number is %s. The last reported mail error was:"
2224 static char *de_msg
[] = {
2225 "Please try to correct the error preventing Alpine from saving your",
2226 "mail folder. For example if the disk is out of space try removing",
2227 "unneeded files. You might also contact your system administrator.",
2229 "Both Alpine's File Browser and an option to enter the system's",
2230 "command prompt are offered to aid in fixing the problem. When",
2231 "you believe the problem is resolved, choose the \"Retry\" option.",
2232 "Be aware that messages may be lost or this folder left in an",
2233 "inaccessible condition if you exit or kill Alpine before the problem",
2236 static char *de_shell_msg
[] = {
2237 "\n\nPlease attempt to correct the error preventing saving of the",
2238 "mail folder. If you do not know how to correct the problem, contact",
2239 "your system administrator. To return to Alpine, type \"exit\".",
2243 "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n",
2244 DE_FOLDER(stream
), errcode
, serious
? "" : "not "));
2245 dprint((0, "***** message: \"%s\"\n\n",
2246 ps_global
->last_error
? ps_global
->last_error
: "?"));
2249 sp_set_io_error_on_stream(stream
, 1);
2254 /* replace pine's body display with screen full of explanatory text */
2256 PutLine1(2, MAX((DE_COLS
- sizeof(DE_STR1
)
2257 - strlen(DE_FOLDER(stream
)))/2, 0),
2258 DE_STR1
, DE_FOLDER(stream
));
2260 PutLine1(3, 4, DE_STR2
, long2string(errcode
));
2262 PutLine0(4, 0, " \"");
2263 removing_leading_white_space(ps_global
->last_error
);
2264 for(i
= 4, p
= ps_global
->last_error
; *p
&& i
< DE_LINE
; ){
2265 for(s
= NULL
, q
= p
; *q
&& q
- p
< DE_COLS
- 16; q
++)
2266 if(isspace((unsigned char)*q
))
2277 PutLine0(i
, 0, " ");
2278 while(*p
&& isspace((unsigned char)*p
))
2289 for(j
= ++i
; i
< DE_LINE
&& de_msg
[i
-j
]; i
++){
2291 PutLine0(i
, 0, " ");
2292 Write_to_screen(de_msg
[i
-j
]);
2298 switch(radio_buttons(DE_PMT
, -FOOTER_ROWS(ps_global
), de_opts
,
2299 'r', 0, NO_HELP
, RB_FLUSH_IN
| RB_NO_NEWMAIL
)){
2300 case 'r' : /* Retry! */
2301 ps_global
->mangled_screen
= 1;
2304 case 'f' : /* File Browser */
2306 char full_filename
[MAXPATH
+1], filename
[MAXPATH
+1];
2309 build_path(full_filename
, ps_global
->home_dir
, filename
,
2310 sizeof(full_filename
));
2311 file_lister("DISK ERROR", full_filename
, sizeof(full_filename
),
2312 filename
, sizeof(filename
), FALSE
, FB_SAVE
);
2319 end_keyboard(ps_global
? F_ON(F_USE_FK
,ps_global
) : 0);
2320 end_tty_driver(ps_global
);
2321 for(i
= 0; de_shell_msg
[i
]; i
++)
2322 puts(de_shell_msg
[i
]);
2325 * Don't use our piping mechanism to spawn a subshell here
2326 * since it will the server (thus reentering c-client).
2333 init_tty_driver(ps_global
);
2334 init_keyboard(F_ON(F_USE_FK
,ps_global
));
2338 if(ps_global
->redrawer
)
2339 (*ps_global
->redrawer
)();
2345 pine_tcptimeout_noscreen(long int elapsed
, long int sincelast
, char *host
)
2354 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2355 snprintf(pmt
, sizeof(pmt
),
2356 _("No reply in %s seconds from server %s. Break connection"),
2357 long2string(elapsed
), host
);
2358 pmt
[sizeof(pmt
)-1] = '\0';
2359 if(want_to(pmt
, 'n', 'n', NO_HELP
, WT_FLUSH_IN
) == 'y'){
2360 ps_global
->user_says_cancel
= 1;
2365 ps_global
->tcptimeout
= 0;
2371 * -------------------------------------------------------------
2372 * These are declared in pith/imap.h as mandatory to implement.
2373 * -------------------------------------------------------------
2378 * pine_tcptimeout - C-client callback to handle tcp-related timeouts.
2381 pine_tcptimeout(long int elapsed
, long int sincelast
, char *host
)
2383 long rv
= 1L; /* keep trying by default */
2386 ps_global
->tcptimeout
= 1;
2388 dprint((1, "tcptimeout: waited %s seconds, server: %s\n",
2389 long2string(elapsed
), host
));
2398 if(ps_global
->noshow_timeout
)
2401 if(ps_global
->can_interrupt
2402 && ps_global
->close_connection_timeout
> 0L
2403 && elapsed
>= (long)ps_global
->tcp_query_timeout
2404 && elapsed
>= (long)ps_global
->close_connection_timeout
){
2405 ps_global
->can_interrupt
= 0; /* do not return here */
2406 ps_global
->read_bail
= 0;
2407 ps_global
->user_says_cancel
= 1;
2411 if(!ps_global
->ttyo
)
2412 return(pine_tcptimeout_noscreen(elapsed
, sincelast
, host
));
2417 * Prompt after a minute (since by then things are probably really bad)
2418 * A prompt timeout means "keep trying"...
2420 if(elapsed
>= (long)ps_global
->tcp_query_timeout
){
2423 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2424 if((clear_inverse
= !InverseState()) != 0)
2429 PutLine2(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
), 0,
2430 _("No reply in %s seconds from server %s. Break connection?"),
2431 long2string(elapsed
), host
);
2436 if(ps_global
->read_bail
|| ch
== 'y' || ch
== 'Y'){
2437 ps_global
->read_bail
= 0;
2438 ps_global
->user_says_cancel
= 1;
2445 ClearLine(ps_global
->ttyo
->screen_rows
- FOOTER_ROWS(ps_global
));
2448 if(rv
== 1L){ /* just warn 'em something's up */
2449 q_status_message2(SM_ORDER
, 0, 0,
2450 _("No reply in %s seconds from server %s. Still Waiting..."),
2451 long2string(elapsed
), host
);
2452 flush_status_messages(0); /* make sure it's seen */
2455 mark_status_dirty(); /* make sure it gets cleared */
2457 resume_busy_cue((rv
== 1) ? 3 : 0);
2458 ps_global
->tcptimeout
= 0;
2463 QUOTALIST
*pine_quotalist_copy (QUOTALIST
*pquota
)
2465 QUOTALIST
*cquota
= NULL
;
2468 cquota
= mail_newquotalist();
2469 if (pquota
->name
&& *pquota
->name
)
2470 cquota
->name
= cpystr(pquota
->name
);
2471 cquota
->usage
= pquota
->usage
;
2472 cquota
->limit
= pquota
->limit
;
2474 cquota
->next
= pine_quotalist_copy(pquota
->next
);
2480 /* c-client callback to handle quota */
2483 pine_parse_quota (MAILSTREAM
*stream
, unsigned char *msg
, QUOTALIST
*pquota
)
2485 ps_global
->quota
= pine_quotalist_copy (pquota
);
2489 * C-client callback to handle SSL/TLS certificate validation failures
2491 * Returning 0 means error becomes fatal
2492 * Non-zero means certificate problem is ignored and SSL session is
2495 * We remember the answer and won't re-ask for subsequent open attempts to
2496 * the same hostname.
2499 pine_sslcertquery(char *reason
, char *host
, char *cert
)
2502 char *unknown
= "<unknown>";
2505 int ok_novalidate
= 0, warned
= 0;
2507 dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n",
2508 host
? host
: "?", reason
? reason
: "?",
2509 cert
? cert
: "?"));
2511 hostlist
.name
= host
? host
: "";
2512 hostlist
.next
= NULL
;
2515 * See if we've been asked about this host before.
2517 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2518 /* we were asked before, did we say Yes? */
2524 "sslcertificatequery: approved automatically\n"));
2528 dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n"));
2531 if(ps_global
->ttyo
){
2533 STORE_S
*in_store
, *out_store
;
2535 HANDLE_S
*handles
= NULL
;
2536 int the_answer
= 'n';
2538 if(!(in_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)) ||
2539 !(out_store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2542 so_puts(in_store
, "<HTML><P>");
2543 so_puts(in_store
, _("There was a failure validating the SSL/TLS certificate for the server"));
2545 so_puts(in_store
, "<P><CENTER>");
2546 so_puts(in_store
, host
? host
: unknown
);
2547 so_puts(in_store
, "</CENTER>");
2549 so_puts(in_store
, "<P>");
2550 so_puts(in_store
, _("The reason for the failure was"));
2552 /* squirrel away details */
2554 fs_give((void **)&details_host
);
2556 fs_give((void **)&details_reason
);
2558 fs_give((void **)&details_cert
);
2560 details_host
= cpystr(host
? host
: unknown
);
2561 details_reason
= cpystr(reason
? reason
: unknown
);
2562 details_cert
= cpystr(cert
? cert
: unknown
);
2564 so_puts(in_store
, "<P><CENTER>");
2565 snprintf(tmp
, sizeof(tmp
), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)",
2566 reason
? reason
: unknown
);
2567 tmp
[sizeof(tmp
)-1] = '\0';
2569 so_puts(in_store
, tmp
);
2570 so_puts(in_store
, "</CENTER>");
2572 so_puts(in_store
, "<P>");
2573 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."));
2575 so_puts(in_store
, "<P>");
2576 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"));
2578 so_puts(in_store
, "<P><CENTER>");
2579 so_puts(in_store
, "/novalidate-cert");
2580 so_puts(in_store
, "</CENTER>");
2582 so_puts(in_store
, "<P>");
2583 so_puts(in_store
, _("to the name of the folder you attempted to access. In other words, wherever you see the characters"));
2585 so_puts(in_store
, "<P><CENTER>");
2586 so_puts(in_store
, host
? host
: unknown
);
2587 so_puts(in_store
, "</CENTER>");
2589 so_puts(in_store
, "<P>");
2590 so_puts(in_store
, _("in your configuration, replace those characters with"));
2592 so_puts(in_store
, "<P><CENTER>");
2593 so_puts(in_store
, host
? host
: unknown
);
2594 so_puts(in_store
, "/novalidate-cert");
2595 so_puts(in_store
, "</CENTER>");
2597 so_puts(in_store
, "<P>");
2598 so_puts(in_store
, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
2600 so_seek(in_store
, 0L, 0);
2601 init_handles(&handles
);
2603 gf_link_filter(gf_html2plain
,
2604 gf_html2plain_opt(NULL
,
2605 ps_global
->ttyo
->screen_cols
, non_messageview_margin(),
2606 &handles
, NULL
, GFHP_LOCAL_HANDLES
));
2607 gf_set_so_readc(&gc
, in_store
);
2608 gf_set_so_writec(&pc
, out_store
);
2610 gf_clear_so_writec(out_store
);
2611 gf_clear_so_readc(in_store
);
2613 memset(&sargs
, 0, sizeof(SCROLL_S
));
2614 sargs
.text
.handles
= handles
;
2615 sargs
.text
.text
= so_text(out_store
);
2616 sargs
.text
.src
= CharStar
;
2617 sargs
.text
.desc
= _("help text");
2618 sargs
.bar
.title
= _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
2619 sargs
.proc
.tool
= answer_cert_failure
;
2620 sargs
.proc
.data
.p
= (void *)&the_answer
;
2621 sargs
.keys
.menu
= &ans_certquery_keymenu
;
2622 /* don't want to re-enter c-client */
2623 sargs
.quell_newmail
= 1;
2624 setbitmap(sargs
.keys
.bitmap
);
2625 sargs
.help
.text
= h_tls_validation_failure
;
2626 sargs
.help
.title
= _("HELP FOR CERT VALIDATION FAILURE");
2630 if(the_answer
== 'y')
2632 else if(the_answer
== 'n')
2633 ps_global
->user_says_cancel
= 1;
2635 ps_global
->mangled_screen
= 1;
2636 ps_global
->painted_body_on_startup
= 0;
2637 ps_global
->painted_footer_on_startup
= 0;
2639 so_give(&out_store
);
2640 free_handles(&handles
);
2642 fs_give((void **)&details_host
);
2644 fs_give((void **)&details_reason
);
2646 fs_give((void **)&details_cert
);
2650 * If screen hasn't been initialized yet, use want_to.
2653 memset((void *)tmp
, 0, sizeof(tmp
));
2655 reason
? reason
: _("SSL/TLS certificate validation failure"),
2657 tmp
[sizeof(tmp
)-1] = '\0';
2658 strncat(tmp
, _(": Continue anyway "), sizeof(tmp
)-strlen(tmp
)-1);
2660 if(want_to(tmp
, 'n', 'x', NO_HELP
, WT_NORM
) == 'y')
2665 q_status_message1(SM_ORDER
, 1, 3, _("Open of %s cancelled"),
2666 host
? host
: unknown
);
2668 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, rv
? 1 : 0, 0);
2670 dprint((5, "sslcertificatequery: %s\n",
2671 rv
? "approved" : "rejected"));
2678 pine_newsrcquery(MAILSTREAM
*stream
, char *mulname
, char *name
)
2680 char buf
[MAILTMPLEN
];
2682 if((can_access(mulname
, ACCESS_EXISTS
) == 0)
2683 || !(can_access(name
, ACCESS_EXISTS
) == 0))
2686 snprintf(buf
, sizeof(buf
),
2687 _("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
2689 strlen(last_cmpnt(name
)) > 15 ? "..." : "");
2690 buf
[sizeof(buf
)-1] = '\0';
2691 if(want_to(buf
, 'n', 'n', NO_HELP
, WT_NORM
) == 'y')
2692 rename_file(name
, mulname
);
2698 url_local_certdetails(char *url
)
2700 if(!struncmp(url
, "x-alpine-cert:", 14)){
2705 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
))){
2706 q_status_message(SM_ORDER
| SM_DING
, 7, 10,
2707 _("Error allocating space for details."));
2711 so_puts(store
, _("Host given by user:\n\n "));
2712 so_puts(store
, details_host
);
2713 so_puts(store
, _("\n\nReason for failure:\n\n "));
2714 so_puts(store
, details_reason
);
2715 so_puts(store
, _("\n\nCertificate being verified:\n\n"));
2716 folded
= fold(details_cert
, ps_global
->ttyo
->screen_cols
, ps_global
->ttyo
->screen_cols
, " ", " ", FLD_NONE
);
2717 so_puts(store
, folded
);
2718 fs_give((void **)&folded
);
2719 so_puts(store
, "\n");
2721 memset(&sargs
, 0, sizeof(SCROLL_S
));
2722 sargs
.text
.text
= so_text(store
);
2723 sargs
.text
.src
= CharStar
;
2724 sargs
.text
.desc
= _("Details");
2725 sargs
.bar
.title
= _("CERT VALIDATION DETAILS");
2726 sargs
.help
.text
= NO_HELP
;
2727 sargs
.help
.title
= NULL
;
2728 sargs
.quell_newmail
= 1;
2729 sargs
.help
.text
= h_tls_failure_details
;
2730 sargs
.help
.title
= _("HELP FOR CERT VALIDATION DETAILS");
2734 so_give(&store
); /* free resources associated with store */
2735 ps_global
->mangled_screen
= 1;
2744 * C-client callback to handle SSL/TLS certificate validation failures
2747 pine_sslfailure(char *host
, char *reason
, long unsigned int flags
)
2751 int the_answer
= 'n', indent
, len
, cols
;
2752 char buf
[500], buf2
[500];
2754 char *hst
= host
? host
: "<unknown>";
2755 char *rsn
= reason
? reason
: "<unknown>";
2756 char *notls
= "/notls";
2758 int ok_novalidate
= 0, warned
= 0;
2761 dprint((1, "sslfailure: host=%s reason=%s\n",
2765 if(flags
& NET_SILENT
)
2768 hostlist
.name
= host
? host
: "";
2769 hostlist
.next
= NULL
;
2772 * See if we've been told about this host before.
2774 if(imap_get_ssl(cert_failure_list
, &hostlist
, &ok_novalidate
, &warned
)){
2775 /* we were told already */
2777 snprintf(buf
, sizeof(buf
), _("SSL/TLS failure for %s: %s"), hst
, rsn
);
2778 buf
[sizeof(buf
)-1] = '\0';
2784 cols
= ps_global
->ttyo
? ps_global
->ttyo
->screen_cols
: 80;
2787 if(!(store
= so_get(CharStar
, NULL
, EDIT_ACCESS
)))
2790 strncpy(buf
, _("There was an SSL/TLS failure for the server"), sizeof(buf
));
2791 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2792 so_puts(store
, folded
);
2793 fs_give((void **)&folded
);
2794 so_puts(store
, "\n");
2796 if((len
=strlen(hst
)) <= cols
){
2797 if((indent
=((cols
-len
)/2)) > 0)
2798 so_puts(store
, repeat_char(indent
, SPACE
));
2800 so_puts(store
, hst
);
2801 so_puts(store
, "\n");
2804 strncpy(buf
, hst
, sizeof(buf
));
2805 buf
[sizeof(buf
)-1] = '\0';
2806 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2807 so_puts(store
, folded
);
2808 fs_give((void **)&folded
);
2811 so_puts(store
, "\n");
2813 strncpy(buf
, _("The reason for the failure was"), sizeof(buf
));
2814 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2815 so_puts(store
, folded
);
2816 fs_give((void **)&folded
);
2817 so_puts(store
, "\n");
2819 if((len
=strlen(rsn
)) <= cols
){
2820 if((indent
=((cols
-len
)/2)) > 0)
2821 so_puts(store
, repeat_char(indent
, SPACE
));
2823 so_puts(store
, rsn
);
2824 so_puts(store
, "\n");
2827 strncpy(buf
, rsn
, sizeof(buf
));
2828 buf
[sizeof(buf
)-1] = '\0';
2829 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2830 so_puts(store
, folded
);
2831 fs_give((void **)&folded
);
2834 so_puts(store
, "\n");
2836 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
));
2837 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2838 so_puts(store
, folded
);
2839 fs_give((void **)&folded
);
2840 so_puts(store
, "\n");
2842 if((len
=strlen(notls
)) <= cols
){
2843 if((indent
=((cols
-len
)/2)) > 0)
2844 so_puts(store
, repeat_char(indent
, SPACE
));
2846 so_puts(store
, notls
);
2847 so_puts(store
, "\n");
2850 strncpy(buf
, notls
, sizeof(buf
));
2851 buf
[sizeof(buf
)-1] = '\0';
2852 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2853 so_puts(store
, folded
);
2854 fs_give((void **)&folded
);
2857 so_puts(store
, "\n");
2859 strncpy(buf
, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"),
2861 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2862 so_puts(store
, folded
);
2863 fs_give((void **)&folded
);
2864 so_puts(store
, "\n");
2866 if((len
=strlen(hst
)) <= cols
){
2867 if((indent
=((cols
-len
)/2)) > 0)
2868 so_puts(store
, repeat_char(indent
, SPACE
));
2870 so_puts(store
, hst
);
2871 so_puts(store
, "\n");
2874 strncpy(buf
, hst
, sizeof(buf
));
2875 buf
[sizeof(buf
)-1] = '\0';
2876 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2877 so_puts(store
, folded
);
2878 fs_give((void **)&folded
);
2881 so_puts(store
, "\n");
2883 strncpy(buf
, _("in your configuration, replace those characters with"),
2885 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2886 so_puts(store
, folded
);
2887 fs_give((void **)&folded
);
2888 so_puts(store
, "\n");
2890 snprintf(buf2
, sizeof(buf2
), "%s%s", hst
, notls
);
2891 buf2
[sizeof(buf2
)-1] = '\0';
2892 if((len
=strlen(buf2
)) <= cols
){
2893 if((indent
=((cols
-len
)/2)) > 0)
2894 so_puts(store
, repeat_char(indent
, SPACE
));
2896 so_puts(store
, buf2
);
2897 so_puts(store
, "\n");
2900 strncpy(buf
, buf2
, sizeof(buf
));
2901 buf
[sizeof(buf
)-1] = '\0';
2902 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2903 so_puts(store
, folded
);
2904 fs_give((void **)&folded
);
2907 so_puts(store
, "\n");
2909 if(ps_global
->ttyo
){
2910 strncpy(buf
, _("Type RETURN to continue."), sizeof(buf
));
2911 folded
= fold(buf
, cols
, cols
, "", "", FLD_NONE
);
2912 so_puts(store
, folded
);
2913 fs_give((void **)&folded
);
2916 memset(&sargs
, 0, sizeof(SCROLL_S
));
2917 sargs
.text
.text
= so_text(store
);
2918 sargs
.text
.src
= CharStar
;
2919 sargs
.text
.desc
= _("help text");
2920 sargs
.bar
.title
= _("SSL/TLS FAILURE");
2921 sargs
.proc
.tool
= answer_cert_failure
;
2922 sargs
.proc
.data
.p
= (void *)&the_answer
;
2923 sargs
.keys
.menu
= &ans_certfail_keymenu
;
2924 setbitmap(sargs
.keys
.bitmap
);
2925 /* don't want to re-enter c-client */
2926 sargs
.quell_newmail
= 1;
2927 sargs
.help
.text
= h_tls_failure
;
2928 sargs
.help
.title
= _("HELP FOR TLS/SSL FAILURE");
2939 * The screen isn't initialized yet, which should mean that this
2940 * is the result of a -p argument. Display_args_err knows how to deal
2941 * with the uninitialized screen, so we mess with the data to get it
2942 * in shape for display_args_err. This is pretty hacky.
2945 so_seek(store
, 0L, 0); /* rewind */
2946 /* count the lines */
2947 while(so_readc(&c
, store
))
2951 qp
= q
= (char **)fs_get((cnt
+1) * sizeof(char *));
2952 memset(q
, 0, (cnt
+1) * sizeof(char *));
2954 so_seek(store
, 0L, 0); /* rewind */
2956 while(so_readc(&c
, store
)){
2959 *qp
++ = cpystr(buf
);
2966 display_args_err(NULL
, q
, 0);
2967 free_list_array(&q
);
2970 ps_global
->mangled_screen
= 1;
2971 ps_global
->painted_body_on_startup
= 0;
2972 ps_global
->painted_footer_on_startup
= 0;
2975 imap_set_passwd(&cert_failure_list
, "", "", &hostlist
, 0, ok_novalidate
, 1);
2980 answer_cert_failure(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
2984 ps_global
->next_screen
= SCREEN_FUN_NULL
;
2988 *(int *)(sparms
->proc
.data
.p
) = 'y';
2992 *(int *)(sparms
->proc
.data
.p
) = 'n';
2996 alpine_panic("Unexpected command in answer_cert_failure");
3005 oauth2_auth_answer(int cmd
, MSGNO_S
*msgmap
, SCROLL_S
*sparms
)
3010 /* TRANSLATORS: user needs to input an access code from the server */
3011 char *accesscodelabel
= _("Copy and Paste Access Code");
3012 char token
[MAILTMPLEN
], prompt
[MAILTMPLEN
];
3014 ps_global
->next_screen
= SCREEN_FUN_NULL
;
3019 q_line
= -(ps_global
->ttyo
? ps_global
->ttyo
->footer_rows
: 3);
3020 flags
= OE_APPEND_CURRENT
;
3021 sprintf(prompt
, "%s: ", accesscodelabel
);
3023 rc
= optionally_enter(token
, q_line
, 0, MAILTMPLEN
,
3024 prompt
, NULL
, NO_HELP
, &flags
);
3025 } while (rc
!= 0 && rc
!= 1);
3026 user
.code
= rc
== 0 ? cpystr(token
) : NULL
;
3028 rv
= rc
== 1 ? 0 : 1;
3037 alpine_panic("Unexpected command in oauth2_auth_answer");
3040 *(AUTH_CODE_S
*) sparms
->proc
.data
.p
= user
;
3045 /*----------------------------------------------------------------------
3046 This can be used to prevent the flickering of the check_cue char
3047 caused by numerous (5000+) fetches by c-client. Right now, the only
3048 practical use found is newsgroup subsciption.
3050 check_cue_display will check if this global is set, and won't clear
3051 the check_cue_char if set.
3054 set_read_predicted(int i
)
3056 ps_global
->read_predicted
= i
==1;
3058 if(!i
&& F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3059 check_cue_display(" ");
3064 /*----------------------------------------------------------------------
3065 Exported method to retrieve logged in user name associated with stream
3067 Args: host -- host to find associated login name with.
3072 pine_block_notify(int reason
, void *data
)
3075 case BLOCK_SENSITIVE
: /* sensitive code, disallow alarms */
3078 case BLOCK_NONSENSITIVE
: /* non-sensitive code, allow alarms */
3081 case BLOCK_TCPWRITE
: /* blocked on TCP write */
3082 case BLOCK_FILELOCK
: /* blocked on file locking */
3084 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3085 check_cue_display(">");
3087 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3091 case BLOCK_DNSLOOKUP
: /* blocked on DNS lookup */
3092 case BLOCK_TCPOPEN
: /* blocked on TCP open */
3093 case BLOCK_TCPREAD
: /* blocked on TCP read */
3094 case BLOCK_TCPCLOSE
: /* blocked on TCP close */
3096 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3097 check_cue_display("<");
3099 mswin_setcursor(MSWIN_CURSOR_BUSY
);
3104 case BLOCK_NONE
: /* not blocked */
3106 if(F_ON(F_SHOW_DELAY_CUE
, ps_global
))
3107 check_cue_display(" ");
3118 mm_expunged_current(long unsigned int rawno
)
3120 /* expunged something we're viewing? */
3121 if(!ps_global
->expunge_in_progress
3122 && (mn_is_cur(ps_global
->msgmap
, mn_raw2m(ps_global
->msgmap
, (long) rawno
))
3123 && (ps_global
->prev_screen
== mail_view_screen
3124 || ps_global
->prev_screen
== attachment_screen
))){
3125 ps_global
->next_screen
= mail_index_screen
;
3126 q_status_message(SM_ORDER
| SM_DING
, 3, 3,
3127 "Message you were viewing is gone!");
3135 * Specific functions to support caching username/passwd/host
3136 * triples on disk for use from one session to the next...
3139 #define FIRSTCH 0x20
3141 #define TABSZ (LASTCH - FIRSTCH + 1)
3143 static int xlate_key
;
3147 * xlate_in() - xlate_in the given character
3155 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3156 eti
+= (c
- FIRSTCH
);
3157 eti
-= (eti
>= 2*TABSZ
) ? 2*TABSZ
: (eti
>= TABSZ
) ? TABSZ
: 0;
3158 return((xlate_key
= eti
) + FIRSTCH
);
3166 * xlate_out() - xlate_out the given character
3174 if((c
>= FIRSTCH
) && (c
<= LASTCH
)){
3175 xch
= c
- (dti
= xlate_key
);
3176 xch
+= (xch
< FIRSTCH
-TABSZ
) ? 2*TABSZ
: (xch
< FIRSTCH
) ? TABSZ
: 0;
3177 dti
= (xch
- FIRSTCH
) + dti
;
3178 dti
-= (dti
>= 2*TABSZ
) ? 2*TABSZ
: (dti
>= TABSZ
) ? TABSZ
: 0;
3185 #endif /* PASSFILE */
3188 #ifdef LOCAL_PASSWD_CACHE
3192 line_get(char *tmp
, size_t len
, char **textp
)
3198 || (s
= strchr(*textp
, '\n')) == NULL
3199 || (s
- *textp
) > len
- 1)
3206 snprintf(tmp
, len
, "%s\n", *textp
);
3214 * Passfile lines are
3216 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
3218 * In pine4.40 and before there was no orig_hostname, and there still isn't
3219 * if it is the same as hostname.
3222 * Use Windows credentials. The TargetName of the credential is
3223 * UWash_Alpine_<hostname:port>\tuser\taltflag
3224 * and the blob consists of
3225 * passwd\torighost (if different from host)
3227 * We don't use anything fancy we just copy out all the credentials which
3228 * begin with TNAME and put them into our cache, so we don't lookup based
3229 * on the TargetName or anything like that. That was so we could re-use
3230 * the existing code and so that orighost data could be easily used.
3233 read_passfile(pinerc
, l
)
3239 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3242 char *tmp
, *blob
, *target
= NULL
;
3243 char *host
, *user
, *sflags
, *passwd
, *orighost
;
3247 if(using_passfile
== 0)
3248 return(using_passfile
);
3251 if(init_wincred_funcs() != 1){
3253 return(using_passfile
);
3257 dprint((9, "read_passfile\n"));
3261 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
3263 for(k
= 0; k
< count
; k
++){
3265 host
= user
= sflags
= passwd
= orighost
= NULL
;
3266 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3268 target
= lptstr_to_utf8(pcred
[k
]->TargetName
);
3269 tmp
= srchstr(target
, TNAME
);
3272 tmp
+= strlen(TNAME
);
3273 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
3274 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3275 ; /* find end of data */
3278 tmp
[i
++] = '\0'; /* tie off data */
3286 blob
= (char *) pcred
[k
]->CredentialBlob
;
3288 for(i
= 0, j
= 3; blob
[i
] && j
< 5; j
++){
3289 for(ui
[j
] = &blob
[i
]; blob
[i
] && blob
[i
] != '\t'; i
++)
3290 ; /* find end of data */
3293 blob
[i
++] = '\0'; /* tie off data */
3300 if(passwd
&& host
&& user
){ /* valid field? */
3301 STRLIST_S hostlist
[2];
3304 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
3305 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
3306 hostlist
[0].name
= host
;
3308 hostlist
[0].next
= &hostlist
[1];
3309 hostlist
[1].name
= orighost
;
3310 hostlist
[1].next
= NULL
;
3313 hostlist
[0].next
= NULL
;
3316 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
3320 fs_give((void **) &target
);
3323 g_CredFree((PVOID
) pcred
);
3329 # else /* old windows */
3336 char target
[MAILTMPLEN
];
3337 char *tmp
, *host
, *user
, *sflags
, *passwd
, *orighost
;
3340 SecKeychainAttributeList attrList
;
3341 SecKeychainSearchRef searchRef
= NULL
;
3342 SecKeychainAttribute attrs
[] = {
3343 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
3346 if(using_passfile
== 0)
3347 return(using_passfile
);
3349 dprint((9, "read_passfile\n"));
3352 /* search for only our items in the keychain */
3354 attrList
.attr
= attrs
;
3357 if(!(rc
=SecKeychainSearchCreateFromAttributes(NULL
,
3358 kSecGenericPasswordItemClass
,
3361 dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n"));
3363 SecKeychainItemRef itemRef
= NULL
;
3364 SecKeychainAttributeInfo info
;
3365 SecKeychainAttributeList
*attrList
= NULL
;
3368 char *blobcopy
= NULL
; /* NULL terminated copy */
3370 UInt32 tags
[] = {kSecAccountItemAttr
,
3371 kSecServiceItemAttr
};
3372 UInt32 formats
[] = {0,0};
3374 dprint((10, "read_passfile: searchRef not NULL\n"));
3377 info
.format
= formats
;
3380 * Go through each item we found and put it
3383 while(!(rc
=SecKeychainSearchCopyNext(searchRef
, &itemRef
)) && itemRef
){
3384 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
3385 rc
= SecKeychainItemCopyAttributesAndData(itemRef
,
3390 if(rc
== 0 && attrList
){
3391 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList
->count
));
3393 blobcopy
= (char *) fs_get((blength
+ 1) * sizeof(char));
3394 strncpy(blobcopy
, (char *) blob
, blength
);
3395 blobcopy
[blength
] = '\0';
3398 * I'm not real clear on how this works. It seems to be
3399 * necessary to combine the attributes from two passes
3400 * (attrList->count == 2) in order to get the full set
3401 * of attributes we inserted into the keychain in the
3402 * first place. So, we reset host...orighost outside of
3403 * the following for loop, not inside.
3405 host
= user
= sflags
= passwd
= orighost
= NULL
;
3406 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3408 for(k
= 0; k
< attrList
->count
; k
++){
3410 if(attrList
->attr
[k
].length
){
3412 (char *) attrList
->attr
[k
].data
,
3413 MIN(attrList
->attr
[k
].length
,sizeof(target
)));
3414 target
[MIN(attrList
->attr
[k
].length
,sizeof(target
)-1)] = '\0';
3418 for(i
= 0, j
= 0; tmp
[i
] && j
< 3; j
++){
3419 for(ui
[j
] = &tmp
[i
]; tmp
[i
] && tmp
[i
] != '\t'; i
++)
3420 ; /* find end of data */
3423 tmp
[i
++] = '\0'; /* tie off data */
3435 for(i
= 0, j
= 3; blobcopy
[i
] && j
< 5; j
++){
3436 for(ui
[j
] = &blobcopy
[i
]; blobcopy
[i
] && blobcopy
[i
] != '\t'; i
++)
3437 ; /* find end of data */
3440 blobcopy
[i
++] = '\0'; /* tie off data */
3449 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
:""));
3452 if(passwd
&& host
&& user
){ /* valid field? */
3453 STRLIST_S hostlist
[2];
3456 tmp
= sflags
? strchr(sflags
, PWDAUTHSEP
) : NULL
;
3457 flags
= sflags
? atoi(tmp
? ++tmp
: sflags
) : 0;
3458 hostlist
[0].name
= host
;
3460 hostlist
[0].next
= &hostlist
[1];
3461 hostlist
[1].name
= orighost
;
3462 hostlist
[1].next
= NULL
;
3465 hostlist
[0].next
= NULL
;
3468 imap_set_passwd(l
, passwd
, user
, hostlist
, flags
& 0x01, 0, 0);
3472 fs_give((void **) & blobcopy
);
3474 SecKeychainItemFreeAttributesAndData(attrList
, (void *) blob
);
3478 dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc
));
3485 CFRelease(searchRef
);
3489 dprint((10, "read_passfile: searchRef NULL\n"));
3494 dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc
));
3497 return(using_passfile
);
3499 #else /* PASSFILE */
3501 char tmp
[MAILTMPLEN
], *ui
[5];
3502 int i
, j
, n
, rv
= 0;
3504 char *tmptext
= NULL
;
3507 char tmp2
[MAILTMPLEN
];
3508 char *text
= NULL
, *text2
= NULL
;
3513 if(using_passfile
== 0)
3514 return(using_passfile
);
3516 dprint((9, "read_passfile\n"));
3518 /* if there's no password to read, bag it!! */
3519 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "rb"))){
3521 return(using_passfile
);
3525 if(our_stat(tmp
, &sbuf
) == 0)
3527 fp
= our_fopen(tmp
, "rb"); /* reopen to read data */
3529 /* the next call initializes the key/certificate pair used to
3530 * encrypt and decrypt a password file. The details of how this is
3531 * done is in the file pith/smime.c. During this setup we might call
3532 * smime_init(), but no matter what happens we must call smime_deinit()
3533 * there. The reason why this is so is because we can not assume that
3534 * the .pinerc file has been read by this time, so this code might not
3535 * know about the ps_global->smime structure or any of its components,
3536 * and it shouldn't because it only needs ps_global->pwdcert, so
3537 * do not init smime here, because the .pinerc might not have been
3538 * read and we do not really know where the keys and certificates really
3540 * Remark: setupwdcert will produce a null ps_global->pwdcert only when
3541 * it is called for the first time and there are certificates at all,
3542 * or when it is called after the first time and the user refuses to
3543 * create a self-signed certificate. In this situation we will just
3544 * let the user live in an insecure world, but no more passwords will
3545 * be saved in the password file, and only those found there will be used.
3548 fgets(tmp2
, sizeof(tmp2
), fp
);
3550 if(strcmp(tmp2
, "-----BEGIN PKCS7-----\n")){
3551 /* there is an already existing password file, that is not encrypted
3552 * and there is no key to encrypt it yet, go again through setup_pwdcert
3553 * and encrypt it now.
3555 if(tmp2
[0]){ /* not empty, UNencrypted password file */
3556 if(ps_global
->pwdcert
== NULL
)
3557 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3558 if((rv
== 0 || rv
== -5) && ps_global
->pwdcert
== NULL
)
3559 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3560 if(ps_global
->pwdcert
== NULL
){
3561 q_status_message(SM_ORDER
, 3, 3,
3562 " Failed to create private key. Using UNencrypted Password file. ");
3567 q_status_message(SM_ORDER
, 3, 3,
3568 " Failed to unlock private key. Using UNencrypted Password file. ");
3569 save_password
= 0; /* do not save more passwords */
3572 if(ps_global
->pwdcert
!= NULL
3573 && encrypt_file((char *)tmp
, NULL
, (PERSONAL_CERT
*)ps_global
->pwdcert
))
3578 if(ps_global
->pwdcert
== NULL
)
3579 rv
= setup_pwdcert(&ps_global
->pwdcert
);
3584 * if password file is encrypted we attempt to decrypt. We ask the
3585 * user for the password to unlock the password file. If the user
3586 * enters the password and it unlocks the file, use it and keep saving
3587 * passwords in it. If the user enters the wrong passwords and does
3588 * not unlock it, we will not see that here, but in decrypt_file, so
3589 * the only other possibility is that the user cancels. In that case
3590 * we will see i == -1. In that case, we will let the user attempt
3591 * manual login to the server they want to login, but passwords will
3592 * not be saved so that the password file will not be saved
3593 * unencrypted and rewritten again.
3596 text
= text2
= decrypt_file((char *)tmp
, &i
, (PERSONAL_CERT
*)ps_global
->pwdcert
);
3597 len
= text2
? strlen(text2
) : 0;
3599 case -2: using_passfile
= 0;
3602 case 1 : save_password
= 1;
3606 case -1: save_password
= 0;
3615 if(using_passfile
== 0){
3617 if(text
) fs_give((void **)&text
);
3619 return using_passfile
;
3623 tmptext
= fs_get(len
+ 1);
3625 for(n
= 0; encrypted
? line_get(tmptext
, len
+ 1, &text2
)
3626 : (fgets(tmptext
, len
+1, fp
) != NULL
); n
++){
3628 for(n
= 0; fgets(tmptext
, len
+1, fp
); n
++){
3630 /*** do any necessary DEcryption here ***/
3632 for(i
= 0; tmptext
[i
]; i
++)
3633 tmptext
[i
] = xlate_out(tmptext
[i
]);
3635 if(i
&& tmptext
[i
-1] == '\n')
3636 tmptext
[i
-1] = '\0'; /* blast '\n' */
3638 dprint((10, "read_passfile: %s\n", tmp
? tmp
: "?"));
3639 ui
[0] = ui
[1] = ui
[2] = ui
[3] = ui
[4] = NULL
;
3640 for(i
= 0, j
= 0; tmptext
[i
] && j
< 5; j
++){
3641 for(ui
[j
] = &tmptext
[i
]; tmptext
[i
] && tmptext
[i
] != '\t'; i
++)
3642 ; /* find end of data */
3645 tmptext
[i
++] = '\0'; /* tie off data */
3648 dprint((10, "read_passfile: calling imap_set_passwd\n"));
3649 if(ui
[0] && ui
[1] && ui
[2]){ /* valid field? */
3650 STRLIST_S hostlist
[2];
3651 char *s
= ui
[3] ? strchr(ui
[3], PWDAUTHSEP
) : NULL
;
3652 int flags
= ui
[3] ? atoi(s
? ++s
: ui
[3]) : 0;
3654 hostlist
[0].name
= ui
[2];
3656 hostlist
[0].next
= &hostlist
[1];
3657 hostlist
[1].name
= ui
[4];
3658 hostlist
[1].next
= NULL
;
3661 hostlist
[0].next
= NULL
;
3664 imap_set_passwd(l
, ui
[0], ui
[1], hostlist
, flags
& 0x01, 0, 0);
3669 if (tmptext
) fs_give((void **) &tmptext
);
3671 if (text
) fs_give((void **)&text
);
3676 #endif /* PASSFILE */
3682 write_passfile(pinerc
, l
)
3686 char *authend
, *authtype
;
3689 char target
[10*MAILTMPLEN
];
3690 char blob
[10*MAILTMPLEN
];
3694 if(using_passfile
== 0)
3697 dprint((9, "write_passfile\n"));
3699 for(; l
; l
= l
->next
){
3700 authtype
= l
->passwd
;
3701 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3702 if(authend
!= NULL
){
3704 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3705 *authend
= PWDAUTHSEP
;
3708 sprintf(blob
, "%d", l
->altflag
);
3710 snprintf(target
, sizeof(target
), "%s%s\t%s\t%s",
3712 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3713 l
->user
? l
->user
: "",
3715 ltarget
= utf8_to_lptstr((LPSTR
) target
);
3718 snprintf(blob
, sizeof(blob
), "%s%s%s",
3719 l
->passwd
? l
->passwd
: "",
3720 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3722 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3723 ? l
->hosts
->next
->name
: "");
3724 memset((void *) &cred
, 0, sizeof(cred
));
3726 cred
.Type
= CRED_TYPE_GENERIC
;
3727 cred
.TargetName
= ltarget
;
3728 cred
.CredentialBlobSize
= strlen(blob
)+1;
3729 cred
.CredentialBlob
= (LPBYTE
) &blob
;
3730 cred
.Persist
= CRED_PERSIST_ENTERPRISE
;
3731 g_CredWriteW(&cred
, 0);
3733 fs_give((void **) <arget
);
3736 #endif /* WINCRED > 0 */
3740 char target
[10*MAILTMPLEN
];
3741 char blob
[10*MAILTMPLEN
];
3742 SecKeychainItemRef itemRef
= NULL
;
3744 if(using_passfile
== 0)
3747 dprint((9, "write_passfile\n"));
3749 for(; l
; l
= l
->next
){
3750 authtype
= l
->passwd
;
3751 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3752 if(authend
!= NULL
){
3754 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3755 *authend
= PWDAUTHSEP
;
3758 sprintf(blob
, "%d", l
->altflag
);
3760 snprintf(target
, sizeof(target
), "%s\t%s\t%s",
3761 (l
->hosts
&& l
->hosts
->name
) ? l
->hosts
->name
: "",
3762 l
->user
? l
->user
: "",
3765 snprintf(blob
, sizeof(blob
), "%s%s%s",
3766 l
->passwd
? l
->passwd
: "",
3767 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3769 (l
->hosts
&& l
->hosts
->next
&& l
->hosts
->next
->name
)
3770 ? l
->hosts
->next
->name
: "");
3772 dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target
), target
, strlen(TNAME
), TNAME
, strlen(blob
), blob
));
3774 rc
= SecKeychainAddGenericPassword(NULL
,
3775 strlen(target
), target
,
3776 strlen(TNAME
), TNAME
,
3780 dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n"));
3783 dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc
));
3786 if(rc
== errSecDuplicateItem
){
3787 /* fix existing entry */
3788 dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n"));
3790 if(!(rc
=SecKeychainFindGenericPassword(NULL
,
3791 strlen(target
), target
,
3792 strlen(TNAME
), TNAME
,
3794 &itemRef
)) && itemRef
){
3796 rc
= SecKeychainItemModifyAttributesAndData(itemRef
, NULL
, strlen(blob
), blob
);
3798 dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc
));
3802 dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc
));
3807 #else /* PASSFILE */
3808 char tmp
[10*MAILTMPLEN
], blob
[10*MAILTMPLEN
];
3812 char *text
= NULL
, tmp2
[10*MAILTMPLEN
];
3816 if(using_passfile
== 0)
3819 dprint((9, "write_passfile\n"));
3821 /* if there's no passfile to read, bag it!! */
3822 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || !(fp
= our_fopen(tmp
, "wb"))){
3828 strncpy(tmp2
, tmp
, sizeof(tmp2
));
3829 tmp2
[sizeof(tmp2
)-1] = '\0';
3832 for(n
= 0; l
; l
= l
->next
, n
++){
3833 authtype
= l
->passwd
;
3834 authend
= strchr(l
->passwd
, PWDAUTHSEP
);
3835 if(authend
!= NULL
){
3837 sprintf(blob
, "%s%c%d", authtype
, PWDAUTHSEP
, l
->altflag
);
3838 *authend
= PWDAUTHSEP
;
3841 sprintf(blob
, "%d", l
->altflag
);
3843 /*** do any necessary ENcryption here ***/
3844 snprintf(tmp
, sizeof(tmp
), "%s\t%s\t%s\t%s%s%s\n", l
->passwd
, l
->user
,
3845 l
->hosts
->name
, blob
,
3846 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? "\t" : "",
3847 (l
->hosts
->next
&& l
->hosts
->next
->name
) ? l
->hosts
->next
->name
3849 dprint((10, "write_passfile: %s", tmp
? tmp
: "?"));
3851 for(i
= 0; tmp
[i
]; i
++)
3852 tmp
[i
] = xlate_in(tmp
[i
]);
3855 fs_resize((void **)&text
, (len
+ strlen(tmp
) + 1)*sizeof(char));
3857 len
+= strlen(tmp
) + 1;
3858 strncat(text
, tmp
, strlen(tmp
));
3867 if(ps_global
->pwdcert
== NULL
){
3868 q_status_message(SM_ORDER
, 3, 3, "Attempting to encrypt password file");
3869 i
= setup_pwdcert(&ps_global
->pwdcert
);
3870 if((i
== 0 || i
== -5) && ps_global
->pwdcert
== NULL
)
3871 ps_global
->pwdcert
= (void *) ALPINE_self_signed_certificate(NULL
, 0, ps_global
->pwdcertdir
, MASTERNAME
);
3873 if(ps_global
->pwdcert
== NULL
){ /* we tried but failed */
3875 q_status_message1(SM_ORDER
, 3, 3, "Error: no directory %s to save certificates", ps_global
->pwdcertdir
);
3877 q_status_message(SM_ORDER
, 3, 3, "Refusing to write non-encrypted password file");
3879 else if(encrypt_file((char *)tmp2
, text
, ps_global
->pwdcert
) == 0)
3880 q_status_message(SM_ORDER
, 3, 3, "Failed to encrypt password file");
3881 fs_give((void **)&text
); /* do not save this text */
3884 #endif /* PASSFILE */
3887 #endif /* LOCAL_PASSWD_CACHE */
3892 erase_windows_credentials(void)
3894 LPCTSTR lfilter
= TEXT(TNAMESTAR
);
3899 if(init_wincred_funcs() != 1)
3903 if(g_CredEnumerateW(lfilter
, 0, &count
, &pcred
)){
3905 for(k
= 0; k
< count
; k
++)
3906 g_CredDeleteW(pcred
[k
]->TargetName
, CRED_TYPE_GENERIC
, 0);
3908 g_CredFree((PVOID
) pcred
);
3914 ask_erase_credentials(void)
3916 if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP
, WT_NORM
) == 'y'){
3917 erase_windows_credentials();
3918 q_status_message(SM_ORDER
, 3, 3, "All preserved passwords have been erased");
3921 q_status_message(SM_ORDER
, 3, 3, "Previously preserved passwords will not be erased");
3924 #endif /* WINCRED */
3927 #ifdef LOCAL_PASSWD_CACHE
3929 get_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
)
3930 char *pinerc
, **passwd
, *user
;
3931 STRLIST_S
*hostlist
;
3934 return get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, NULL
);
3938 * get_passfile_passwd_auth - return the password contained in the special passord
3939 * cache. The file is assumed to be in the same directory
3940 * as the pinerc with the name defined above.
3943 get_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, authtype
)
3944 char *pinerc
, **passwd
, *user
;
3945 STRLIST_S
*hostlist
;
3949 dprint((10, "get_passfile_passwd_auth\n"));
3950 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
3951 ? imap_get_passwd_auth(passfile_cache
, passwd
,
3952 user
, hostlist
, altflag
, authtype
)
3957 free_passfile_cache_work(MMLOGIN_S
**pwdcache
)
3959 if(pwdcache
== NULL
|| *pwdcache
== NULL
)
3962 if((*pwdcache
)->user
) fs_give((void **)&(*pwdcache
)->user
);
3963 // if((*pwdcache)->passwd) fs_give((void **)&(*pwdcache)->passwd);
3964 if((*pwdcache
)->hosts
) free_strlist(&(*pwdcache
)->hosts
);
3965 free_passfile_cache_work(&(*pwdcache
)->next
);
3966 fs_give((void **)pwdcache
);
3971 free_passfile_cache(void)
3974 free_passfile_cache_work(&passfile_cache
);
3978 is_using_passfile(void)
3980 return(using_passfile
== 1);
3984 * Just trying to guess the username the user might want to use on this
3985 * host, the user will confirm.
3988 get_passfile_user(pinerc
, hostlist
)
3990 STRLIST_S
*hostlist
;
3992 return((passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))
3993 ? imap_get_user(passfile_cache
, hostlist
)
3999 preserve_prompt(char *pinerc
)
4001 return preserve_prompt_auth(pinerc
, NULL
);
4005 preserve_prompt_auth(char *pinerc
, char *authtype
)
4009 #define PROMPT_PWD _("Preserve password for next login")
4010 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4012 * This prompt was going to be able to be turned on and off via a registry
4013 * setting controlled from the config menu. We decided to always use the
4014 * dialog for login, and there the prompt is unobtrusive enough to always
4015 * be in there. As a result, windows should never reach this, but now
4016 * OS X somewhat uses the behavior just described.
4018 if(mswin_store_pass_prompt()
4019 && (want_to(authtype
4020 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4022 'y', 'x', NO_HELP
, WT_NORM
)
4032 #define PROMPT_PWD _("Preserve password for next login")
4033 #define PROMPT_OA2 _("Preserve Refresh and Access tokens for next login")
4036 if((rc
= macos_store_pass_prompt()) != 0){
4038 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4039 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
4042 macos_set_store_pass_prompt(1);
4043 q_status_message(SM_ORDER
, 4, 4,
4044 _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4049 macos_set_store_pass_prompt(0);
4050 q_status_message(SM_ORDER
, 4, 4,
4051 _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry"));
4056 #else /* PASSFILE */
4057 #define PROMPT_PWD _("Preserve password on DISK for next login")
4058 #define PROMPT_OA2 _("Preserve Refresh and Access tokens on DISK for next login")
4060 char tmp
[MAILTMPLEN
];
4063 if(!passfile_name(pinerc
, tmp
, sizeof(tmp
)) || our_stat(tmp
, &sbuf
) < 0)
4066 if(F_OFF(F_DISABLE_PASSWORD_FILE_SAVING
,ps_global
))
4067 return(want_to(authtype
4068 ? (strcmp(authtype
, OA2NAME
) ? PROMPT_PWD
: PROMPT_OA2
)
4069 : PROMPT_PWD
, 'y', 'x', NO_HELP
, WT_NORM
)
4072 #endif /* PASSFILE */
4075 #endif /* LOCAL_PASSWD_CACHE */
4078 #ifdef APPLEKEYCHAIN
4082 * 1 if store pass prompt is set in the "registry" to on
4084 * -1 if not set to anything
4087 macos_store_pass_prompt(void)
4094 if(storepassprompt
== -1){
4095 if(!(rc
=SecKeychainFindGenericPassword(NULL
, 0, NULL
,
4096 strlen(TNAMEPROMPT
),
4098 (void **) &data
, NULL
))){
4099 val
= (len
== 1 && data
&& data
[0] == '1');
4103 if(storepassprompt
== -1 && !rc
){
4105 storepassprompt
= 1;
4107 storepassprompt
= 0;
4110 return(storepassprompt
);
4115 macos_set_store_pass_prompt(int val
)
4117 storepassprompt
= val
? 1 : 0;
4119 SecKeychainAddGenericPassword(NULL
, 0, NULL
, strlen(TNAMEPROMPT
),
4120 TNAMEPROMPT
, 1, val
? "1" : "0", NULL
);
4125 macos_erase_keychain(void)
4127 SecKeychainAttributeList attrList
;
4128 SecKeychainSearchRef searchRef
= NULL
;
4129 SecKeychainAttribute attrs1
[] = {
4130 { kSecAccountItemAttr
, strlen(TNAME
), TNAME
}
4132 SecKeychainAttribute attrs2
[] = {
4133 { kSecAccountItemAttr
, strlen(TNAMEPROMPT
), TNAMEPROMPT
}
4136 dprint((9, "macos_erase_keychain\n"));
4139 * Seems like we ought to be able to combine attrs1 and attrs2
4140 * into a single array, but I couldn't get it to work.
4143 /* search for only our items in the keychain */
4145 attrList
.attr
= attrs1
;
4147 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4148 kSecGenericPasswordItemClass
,
4152 SecKeychainItemRef itemRef
= NULL
;
4155 * Go through each item we found and put it
4158 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4159 dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n"));
4160 SecKeychainItemDelete(itemRef
);
4164 CFRelease(searchRef
);
4169 attrList
.attr
= attrs2
;
4171 if(!SecKeychainSearchCreateFromAttributes(NULL
,
4172 kSecGenericPasswordItemClass
,
4176 SecKeychainItemRef itemRef
= NULL
;
4179 * Go through each item we found and put it
4182 while(!SecKeychainSearchCopyNext(searchRef
, &itemRef
) && itemRef
){
4183 SecKeychainItemDelete(itemRef
);
4187 CFRelease(searchRef
);
4192 #endif /* APPLEKEYCHAIN */
4194 #ifdef LOCAL_PASSWD_CACHE
4197 set_passfile_passwd(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
)
4198 char *pinerc
, *passwd
, *user
;
4199 STRLIST_S
*hostlist
;
4200 int altflag
, already_prompted
;
4202 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, NULL
);
4205 * set_passfile_passwd - set the password file entry associated with
4206 * cache. The file is assumed to be in the same directory
4207 * as the pinerc with the name defined above.
4208 * already_prompted: 0 not prompted
4209 * 1 prompted, answered yes
4210 * 2 prompted, answered no
4213 set_passfile_passwd_auth(pinerc
, passwd
, user
, hostlist
, altflag
, already_prompted
, authtype
)
4214 char *pinerc
, *passwd
, *user
;
4215 STRLIST_S
*hostlist
;
4216 int altflag
, already_prompted
;
4219 dprint((10, "set_passfile_passwd_auth\n"));
4220 if(((already_prompted
== 0 && preserve_prompt_auth(pinerc
, authtype
))
4221 || already_prompted
== 1)
4222 && !ps_global
->nowrite_password_cache
4223 && (passfile_cache
|| read_passfile(pinerc
, &passfile_cache
))){
4224 imap_set_passwd_auth(&passfile_cache
, passwd
, user
, hostlist
, altflag
, 0, 0, authtype
);
4225 write_passfile(pinerc
, passfile_cache
);
4230 update_passfile_hostlist(pinerc
, user
, hostlist
, altflag
)
4233 STRLIST_S
*hostlist
;
4236 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, NULL
);
4240 * Passfile lines are
4242 * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n
4244 * In pine4.40 and before there was no orig_hostname.
4245 * This routine attempts to repair that.
4248 update_passfile_hostlist_auth(pinerc
, user
, hostlist
, altflag
, authtype
)
4251 STRLIST_S
*hostlist
;
4257 #else /* !WINCRED */
4259 size_t len
= authtype
? strlen(authtype
) : 0;
4260 size_t offset
= authtype
? 1 : 0;
4262 for(l
= passfile_cache
; l
; l
= l
->next
)
4263 if(imap_same_host_auth(l
->hosts
, hostlist
, authtype
)
4265 && !strcmp(user
, l
->user
+ len
+ offset
)
4266 && l
->altflag
== altflag
){
4270 if(l
&& l
->hosts
&& hostlist
&& !l
->hosts
->next
&& hostlist
->next
4271 && hostlist
->next
->name
4272 && !ps_global
->nowrite_password_cache
){
4273 l
->hosts
->next
= new_strlist_auth(hostlist
->next
->name
, authtype
, PWDAUTHSEP
);
4274 write_passfile(pinerc
, passfile_cache
);
4276 #endif /* !WINCRED */
4279 #endif /* LOCAL_PASSWD_CACHE */
4284 * Load and init the WinCred structure.
4285 * This gives us a way to skip the WinCred code
4286 * if the dll doesn't exist.
4289 init_wincred_funcs(void)
4295 /* Assume the worst. */
4298 hmod
= LoadLibrary(TEXT("advapi32.dll"));
4301 FARPROC fpCredWriteW
;
4302 FARPROC fpCredEnumerateW
;
4303 FARPROC fpCredDeleteW
;
4306 fpCredWriteW
= GetProcAddress(hmod
, "CredWriteW");
4307 fpCredEnumerateW
= GetProcAddress(hmod
, "CredEnumerateW");
4308 fpCredDeleteW
= GetProcAddress(hmod
, "CredDeleteW");
4309 fpCredFree
= GetProcAddress(hmod
, "CredFree");
4311 if(fpCredWriteW
&& fpCredEnumerateW
&& fpCredDeleteW
&& fpCredFree
)
4313 g_CredWriteW
= (CREDWRITEW
*)fpCredWriteW
;
4314 g_CredEnumerateW
= (CREDENUMERATEW
*)fpCredEnumerateW
;
4315 g_CredDeleteW
= (CREDDELETEW
*)fpCredDeleteW
;
4316 g_CredFree
= (CREDFREE
*)fpCredFree
;
4322 mswin_set_erasecreds_callback(ask_erase_credentials
);
4325 return g_CredInited
;
4328 #endif /* WINCRED */