#if !defined(lint) && !defined(DOS)
static char rcsid[] = "$Id: imap.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $";
#endif
/*
* ========================================================================
* Copyright 2013-2019 Eduardo Chappa
* Copyright 2006-2009 University of Washington
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* ========================================================================
*/
/*======================================================================
imap.c
The call back routines for the c-client/imap
- handles error messages and other notification
- handles prelimirary notification of new mail and expunged mail
- prompting for imap server login and password
====*/
#include "headers.h"
#include "alpine.h"
#include "imap.h"
#include "status.h"
#include "mailview.h"
#include "mailcmd.h"
#include "radio.h"
#include "keymenu.h"
#include "signal.h"
#include "mailpart.h"
#include "mailindx.h"
#include "arg.h"
#include "busy.h"
#include "titlebar.h"
#include "xoauth2.h"
#include "../pith/state.h"
#include "../pith/conf.h"
#include "../pith/msgno.h"
#include "../pith/filter.h"
#include "../pith/news.h"
#include "../pith/util.h"
#include "../pith/list.h"
#include "../pith/margin.h"
#ifdef SMIME
#include "../pith/smime.h"
#endif /* SMIME */
#if (WINCRED > 0)
#include ");
sprintf(tmp, _("
Alpine is attempting to log you into your %s account, using the XOAUTH2 method."), oauth2->name), so_puts(in_store, tmp); so_puts(in_store, _(" In order to do that, Alpine needs to open the following URL:")); so_puts(in_store,"
"); sprintf(tmp_20k_buf, _("%s."), url, url); so_puts(in_store, tmp_20k_buf); so_puts(in_store, _("
Alpine will try to use your URL Viewers setting to find a browser to open this URL.")); sprintf(tmp, _(" When you open this link, you will be sent to %s's servers to complete this process."), oauth2->name); so_puts(in_store, tmp); so_puts(in_store, _(" Alternatively, you can copy and paste the previous link and open it with the browser of your choice.")); so_puts(in_store, _("
After you open the previous link, you will be asked to authenticate and later to authorize access to Alpine. ")); so_puts(in_store, _(" At the end of this process, you will be given an access code or redirected to a web page.")); so_puts(in_store, _(" If you see a code, copy it and then press 'C', and enter the code at the prompt.")); 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. ")); so_puts(in_store, _(" Once you have completed this process, Alpine will proceed with authentication.")); so_puts(in_store, _(" If you do not wish to proceed, cancel by pressing 'E' to exit")); so_puts(in_store, _("
")); so_seek(in_store, 0L, 0); init_handles(&handles); gf_filter_init(); gf_link_filter(gf_html2plain, gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols, non_messageview_margin(), &handles, NULL, GFHP_LOCAL_HANDLES)); gf_set_so_readc(&gc, in_store); gf_set_so_writec(&pc, out_store); gf_pipe(gc, pc); gf_clear_so_writec(out_store); gf_clear_so_readc(in_store); memset(&sargs, 0, sizeof(SCROLL_S)); sargs.text.handles = handles; sargs.text.text = so_text(out_store); sargs.text.src = CharStar; sargs.text.desc = _("help text"); sargs.bar.title = _("SETTING UP XOAUTH2 AUTHORIZATION"); sargs.proc.tool = oauth2_auth_answer; sargs.proc.data.p = (void *)&user_input; sargs.keys.menu = &oauth2_auth_keymenu; /* don't want to re-enter c-client */ sargs.quell_newmail = 1; setbitmap(sargs.keys.bitmap); sargs.help.text = h_oauth2_start; sargs.help.title = _("HELP FOR SETTING UP XOAUTH2"); do { scrolltool(&sargs); ps_global->mangled_screen = 1; ps_global->painted_body_on_startup = 0; ps_global->painted_footer_on_startup = 0; } while (user_input.answer != 'e'); if(!struncmp(user_input.code, "https://", 8)){ char *s, *t; s = strstr(user_input.code, "code="); if(s != NULL){ t = strchr(s, '&'); if(t) *t = '\0'; code = cpystr(s+5); if(t) *t = '&'; } } else code = user_input.code ? cpystr(user_input.code) : NULL; if(user_input.code) fs_give((void **) &user_input.code); if(code == NULL) *tryanother = 1; so_give(&in_store); so_give(&out_store); free_handles(&handles); } else{ int flags, rc, q_line; /* TRANSLATORS: user needs to input an access code from the server */ char *accesscodelabel = _("Copy and Paste Access Code"); char prompt[MAILTMPLEN], token[MAILTMPLEN]; /* * If screen hasn't been initialized yet, use want_to. */ try_wantto: memset((void *)tmp, 0, sizeof(tmp)); strncpy(tmp, _("Alpine would like to get authorization to access your email: "), sizeof(tmp)); tmp[sizeof(tmp)-1] = '\0'; strncat(tmp, _(": Proceed "), sizeof(tmp)-strlen(tmp)-1); if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y'){ q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3); flags = OE_APPEND_CURRENT; sprintf(prompt, "%s: ", accesscodelabel); do { rc = optionally_enter(token, q_line, 0, MAILTMPLEN, prompt, NULL, NO_HELP, &flags); } while (rc != 0 && rc != 1); if(!struncmp(token, "https://", 8)){ char *s, *t; s = strstr(token, "code="); if(s != NULL){ t = strchr(s, '&'); if(t) *t = '\0'; code = cpystr(s+5); if(t) *t = '&'; } } else code = token[0] ? cpystr(token) : NULL; } } return code; } void mm_login_oauth2(NETMBX *, char *, OAUTH2_S *, long int, char *, char *); /* The purpose of this function is to report to c-client the values of the * different tokens and codes so that c-client can try to log in the user * to the server. This function DOES NOT attempt to get these values for * the user. That is attempted in the c-client side (as best as it can be * done given our circumstances: no http support, no javascript support, * etc.). This is the best we can do: * * 1. In a first call, get an unloaded OAUTH2_S login pointer and load it * as best as we can. Unloaded means that there is no server known to * connect, no access token, etc. Pretty much nothing is known about * how to get access code, access token, etc. We ask the user to fill * it up for us, if they can. If the user fills it up we save those * values. * * 2. In a subsequent call, if the OAUTH2_S login pointer is loaded, we * save the information that c-client got for us. * * 3. When saving this information we use the password caching facilities, * but we must do it in a different format so that old information and * new information are not mixed. In order to accomodate this for new * authentication methods, we save the information in the same fields, * but this time we modify it slightly, so that old functions fail to * understand the new information and so not modify it nor use it. The * modification is simple: Every piece of information that was saved * before is prepended XOAUTH2\001 to indicate the authentication * method for which it works. The character \001 is a separator. New * Alpine will know how to deal with this, but old versions, will not * strip this prefix from the information and fail to get the * information or modify it when needed. Only new versions of Alpine will * know how to process this information. * new_value = authenticator_method separator old_value * authenticator_method = "XOAUTH2_S" * separator = "U+1" * example: if old value is imap.gmail.com, the new value for the XOAUTH2 * authenticator is "XOAUTH2\001imap.gmail.com". * In addition, the password field is not used to encode the password * anymore, it is used to save login information needed in a format that * the caller function chooses, but that must be preceeded by the * "authenticator_method separator" code as above. */ void mm_login_oauth2(NETMBX *mb, char *user, OAUTH2_S *login, long int trial, char *usethisprompt, char *altuserforcache) { char *token, tmp[MAILTMPLEN]; char prompt[4*MAILTMPLEN], value[4*MAILTMPLEN], *last; char defuser[NETMAXUSER]; char hostleadin[80], hostname[200], defubuf[200]; char logleadin[80], pwleadin[50]; char *url_oauth2; char *tool = NULL; char *OldRefreshToken, *OldAccessToken; char *NewRefreshToken, *NewAccessToken; char *SaveRefreshToken, *SaveAccessToken; /* TRANSLATORS: A label for the hostname that the user is logging in on */ char *hostlabel = _("HOST"); /* TRANSLATORS: user is logging in as a particular user (a particular login name), this is just labelling that user name. */ char *userlabel = _("USER"); STRLIST_S hostlist[2], hostlist2[OAUTH2_TOT_EQUIV+1]; HelpType help ; int len, rc, q_line, flags, i, j; int oespace, avail, need, save_dont_use; int save_in_init; int registered; int ChangeAccessToken, ChangeRefreshToken, ChangeExpirationTime; OAUTH2_S *oa2list; unsigned long OldExpirationTime, NewExpirationTime, SaveExpirationTime; #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE) int preserve_password = -1; #endif dprint((9, "mm_login_oauth2 trial=%ld user=%s service=%s%s%s%s%s\n", trial, mb->user ? mb->user : "(null)", mb->service ? mb->service : "(null)", mb->port ? " port=" : "", mb->port ? comatose(mb->port) : "", altuserforcache ? " altuserforcache =" : "", altuserforcache ? altuserforcache : "")); q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3); save_in_init = ps_global->in_init_seq; ps_global->in_init_seq = 0; ps_global->no_newmail_check_from_optionally_enter = 1; /* make sure errors are seen */ if(ps_global->ttyo) flush_status_messages(0); token = NULL; /* start from scratch */ hostlist[0].name = mb->host; if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){ hostlist[0].next = &hostlist[1]; hostlist[1].name = mb->orighost; hostlist[1].next = NULL; } else hostlist[0].next = NULL; if(hostlist[0].name){ dprint((9, "mm_login_oauth2: host=%s\n", hostlist[0].name ? hostlist[0].name : "?")); if(hostlist[0].next && hostlist[1].name){ dprint((9, "mm_login_oauth2: orighost=%s\n", hostlist[1].name)); } } /* * We check to see if the server we are going to log in to is already * registered. This gives us a list of servers with the same * credentials, so we use the same credentials for all of them. */ for(registered = 0, oa2list = alpine_oauth2_list; oa2list && oa2list->host != NULL && oa2list->host[0] != NULL; oa2list++){ for(i = 0; i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL && strucmp(oa2list->host[i], mb->orighost) != 0; i++); if(i < OAUTH2_TOT_EQUIV && oa2list->host[i] != NULL){ registered++; break; } } if(registered){ hostlist2[i = 0].name = mb->host; if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)) hostlist2[++i].name = mb->orighost; for(j = 0; j < OAUTH2_TOT_EQUIV && oa2list->host[j] != NULL; j++){ int k; for(k = 0; k <= i && hostlist2[k].name && strcmp(hostlist2[k].name, oa2list->host[j]); k++); if(k == i + 1) hostlist2[++i].name = oa2list->host[j]; } hostlist2[i+1].name = NULL; hostlist2[i+1].next = NULL; for(j = i; j >= 0; j--) hostlist2[j].next = &hostlist2[j+1]; } /* * We check if we have a refresh token saved somewhere, if so * we use it to get a new access token, otherwise we need to * get an access code so we can get (and save) a refresh token * and use the access token. */ if(trial == 0L && !altuserforcache){ int code; if(*mb->user != '\0') strncpy(user, mb->user, NETMAXUSER); else{ flags = OE_APPEND_CURRENT; sprintf(prompt, "%s: %s - %s: ", hostlabel, mb->orighost, userlabel); rc = optionally_enter(user, q_line, 0, NETMAXUSER, prompt, NULL, NO_HELP, &flags); } user[NETMAXUSER-1] = '\0'; /* Search for a refresh token that is already loaded ... */ if(imap_get_passwd_auth(mm_login_list, &token, user, registered ? hostlist2 : hostlist, (mb->sslflag||mb->tlsflag), OA2NAME)){ dprint((9, "mm_login_oauth2: found a refresh token\n")); ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; } #ifdef LOCAL_PASSWD_CACHE /* or see if we have saved one in the local password cache and load it */ else if(get_passfile_passwd_auth(ps_global->pinerc, &token, user, registered ? hostlist2 : hostlist, (mb->sslflag||mb->tlsflag), OA2NAME)){ imap_set_passwd_auth(&mm_login_list, token, user, hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME); update_passfile_hostlist_auth(ps_global->pinerc, user, hostlist, (mb->sslflag||mb->tlsflag), OA2NAME); dprint((9, "mm_login_oauth2: found a refresh token in cache\n")); ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; } if(token && *token) preserve_password = 1; /* resave it, no need to ask */ #endif /* LOCAL_PASSWD_CACHE */ } user[NETMAXUSER-1] = '\0'; /* The Old* variables is what c_client knows */ OldRefreshToken = login->param[OA2_RefreshToken].value; OldAccessToken = login->access_token; OldExpirationTime = login->expiration; /* The New* variables is what Alpine knows */ NewRefreshToken = NewAccessToken = NULL; NewExpirationTime = 0L; ChangeAccessToken = ChangeRefreshToken = ChangeExpirationTime = 0; if(token && *token){ char *s, *t; s = token; t = strchr(s, PWDAUTHSEP); if(t == NULL) NewRefreshToken = cpystr(s); else { *t++ = '\0'; NewRefreshToken = cpystr(s); s = t; t = strchr(s, PWDAUTHSEP); if(t == NULL) NewAccessToken = cpystr(s); else { *t++ = '\0'; NewAccessToken = cpystr(s); s = t; NewExpirationTime = strtol(s, &s, 10); if(NewExpirationTime <= 0 || NewExpirationTime <= time(0)) NewExpirationTime = 0L; } } /* check we got good information, and send good information below */ if(NewRefreshToken && !*NewRefreshToken) fs_give((void **) &NewRefreshToken); if(NewAccessToken && (NewExpirationTime == 0L || !*NewAccessToken)) fs_give((void **) &NewAccessToken); } /* Default to saving what we already had saved */ SaveRefreshToken = NewRefreshToken; SaveAccessToken = NewAccessToken; SaveExpirationTime = NewExpirationTime; /* Translation of the logic below: * if (c-client has a refresh token and (alpine does not have a refresh token or (alpine and c-client have different refresh tokens)){ forget the Alpine refresh token; make the Alpine Refresh token = c-client refresh token.; signal that we changed the refresh token; In this situation we do not need to clear up the Alpine access token. This will expire, so we can use it until it expires. We can save the c-client refresh token together with the Alpine Access Token and the expiration date of the Alpine Access Token. } else if(c-client does not have a refresh token and Alpine has one and this is not the first attempt){ forget the Alpine refresh token; if Alpine has an access token, forget it; reset the expiration time signal that we changed the refresh token; (because the service expired it) } */ if(OldRefreshToken != NULL && (NewRefreshToken == NULL || strcmp(OldRefreshToken, NewRefreshToken))){ if(NewRefreshToken) fs_give((void **) &NewRefreshToken); NewRefreshToken = cpystr(OldRefreshToken); ChangeRefreshToken++; SaveRefreshToken = OldRefreshToken; SaveAccessToken = NewAccessToken; SaveExpirationTime = NewExpirationTime; } else if (OldRefreshToken == NULL && NewRefreshToken != NULL && trial > 0){ fs_give((void **) &NewRefreshToken); if(NewAccessToken) fs_give((void **) &NewAccessToken); NewExpirationTime = 0L; ChangeRefreshToken++; SaveRefreshToken = NULL; SaveAccessToken = NULL; SaveExpirationTime = 0L; } if(OldAccessToken != NULL && (NewAccessToken == NULL || strcmp(OldAccessToken, NewAccessToken))){ if(NewAccessToken) fs_give((void **) &NewAccessToken); NewAccessToken = cpystr(OldAccessToken); ChangeAccessToken++; NewExpirationTime = OldExpirationTime; SaveRefreshToken = NewRefreshToken; SaveAccessToken = NewAccessToken; SaveExpirationTime = NewExpirationTime; } if(!registered){ login->param[OA2_RefreshToken].value = SaveRefreshToken; login->access_token = SaveAccessToken; login->expiration = SaveExpirationTime; } else { oa2list->param[OA2_RefreshToken].value = SaveRefreshToken; oa2list->access_token = SaveAccessToken; oa2list->expiration = SaveExpirationTime; *login = *oa2list; /* load login pointer */ } if(!ChangeAccessToken && !ChangeRefreshToken) return; /* get ready to save this information. The format will be * RefreshToken \001 LastAccessToken \001 ExpirationTime * (spaces added for clarity, \001 is PWDAUTHSEP) */ if(token) fs_give((void **) &token); sprintf(tmp, "%lu", SaveExpirationTime); tmp[sizeof(tmp) - 1] = '\0'; len = strlen(SaveRefreshToken ? SaveRefreshToken : "") + strlen(SaveAccessToken ? SaveAccessToken : "") + strlen(tmp) + 2; token = fs_get(len + 1); sprintf(token, "%s%c%s%c%lu", SaveRefreshToken ? SaveRefreshToken : "", PWDAUTHSEP, SaveAccessToken ? SaveAccessToken : "", PWDAUTHSEP, SaveExpirationTime); /* remember the access information for next time */ if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global)) imap_set_passwd_auth(&mm_login_list, token, altuserforcache ? altuserforcache : user, hostlist, (mb->sslflag||mb->tlsflag), 0, 0, OA2NAME); #ifdef LOCAL_PASSWD_CACHE /* if requested, remember it on disk for next session */ if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global)) set_passfile_passwd_auth(ps_global->pinerc, token, altuserforcache ? altuserforcache : user, hostlist, (mb->sslflag||mb->tlsflag), (preserve_password == -1 ? 0 : (preserve_password == 0 ? 2 :1)), OA2NAME); #endif /* LOCAL_PASSWD_CACHE */ ps_global->no_newmail_check_from_optionally_enter = 0; } /*---------------------------------------------------------------------- recieve notification from IMAP Args: stream -- Mail stream message is relavant to string -- The message text errflg -- Set if it is a serious error Result: message displayed in status line The facility is for general notices, such as connection to server; server shutting down etc... It is used infrequently. ----------------------------------------------------------------------*/ void mm_notify(MAILSTREAM *stream, char *string, long int errflg) { time_t now; struct tm *tm_now; now = time((time_t *)0); tm_now = localtime(&now); /* be sure to log the message... */ #ifdef DEBUG if(ps_global->debug_imap || ps_global->debugmem) dprint((errflg == TCPDEBUG ? 7 : 2, "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_notify %s: %s: %s\n", tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec, tm_now->tm_mon+1, tm_now->tm_mday, (!errflg) ? "babble" : (errflg == ERROR) ? "error" : (errflg == WARN) ? "warning" : (errflg == PARSE) ? "parse" : (errflg == TCPDEBUG) ? "tcp" : (errflg == BYE) ? "bye" : "unknown", (stream && stream->mailbox) ? stream->mailbox : "-no folder-", string ? string : "?")); #endif snprintf(ps_global->last_error, sizeof(ps_global->last_error), "%s : %.*s", (stream && stream->mailbox) ? stream->mailbox : "-no folder-", (int) MIN(MAX_SCREEN_COLS, sizeof(ps_global->last_error)-70), string); ps_global->last_error[ps_global->ttyo ? ps_global->ttyo->screen_cols : sizeof(ps_global->last_error)-1] = '\0'; /* * Then either set special bits in the pine struct or * display the message if it's tagged as an "ALERT" or * its errflg > NIL (i.e., WARN, or ERROR) */ if(errflg == BYE) /* * We'd like to sp_mark_stream_dead() here but we can't do that because * that might call mail_close and we are already in a c-client callback. * So just set the dead bit and clean it up later. */ sp_set_dead_stream(stream, 1); else if(!strncmp(string, "[TRYCREATE]", 11)) ps_global->try_to_create = 1; else if(!strncmp(string, "[REFERRAL ", 10)) ; /* handled in the imap_referral() callback */ else if(!strncmp(string, "[ALERT]", 7)) q_status_message2(SM_MODAL, 3, 3, _("Alert received while accessing \"%s\": %s"), (stream && stream->mailbox) ? stream->mailbox : "-no folder-", rfc1522_decode_to_utf8((unsigned char *)(tmp_20k_buf+10000), SIZEOF_20KBUF-10000, string)); else if(!strncmp(string, "[UNSEEN ", 8)){ char *p; long n = 0; for(p = string + 8; isdigit(*p); p++) n = (n * 10) + (*p - '0'); sp_set_first_unseen(stream, n); } else if(!strncmp(string, "[READ-ONLY]", 11) && !(stream && stream->mailbox && IS_NEWS(stream))) q_status_message2(SM_ORDER | SM_DING, 3, 3, "%s : %s", (stream && stream->mailbox) ? stream->mailbox : "-no folder-", string + 11); else if((errflg && errflg != BYE && errflg != PARSE) && !ps_global->noshow_error && !(errflg == WARN && (ps_global->noshow_warn || (stream && stream->unhealthy)))) q_status_message(SM_ORDER | ((errflg == ERROR) ? SM_DING : 0), 3, 6, ps_global->last_error); } /*---------------------------------------------------------------------- Queue imap log message for display in the message line Args: string -- The message errflg -- flag set to 1 if pertains to an error Result: Message queued for display The c-client/imap reports most of it's status and errors here ---*/ void mm_log(char *string, long int errflg) { char message[sizeof(ps_global->c_client_error)]; char *occurence; int was_capitalized; static char saw_kerberos_init_warning; time_t now; struct tm *tm_now; now = time((time_t *)0); tm_now = localtime(&now); dprint((((errflg == TCPDEBUG) && ps_global->debug_tcp) ? 1 : (errflg == TCPDEBUG) ? 10 : 2, "IMAP %2.2d:%2.2d:%2.2d %d/%d mm_log %s: %s\n", tm_now->tm_hour, tm_now->tm_min, tm_now->tm_sec, tm_now->tm_mon+1, tm_now->tm_mday, (!errflg) ? "babble" : (errflg == ERROR) ? "error" : (errflg == WARN) ? "warning" : (errflg == PARSE) ? "parse" : (errflg == TCPDEBUG) ? "tcp" : (errflg == BYE) ? "bye" : "unknown", string ? string : "?")); if(errflg == ERROR && !strncmp(string, "[TRYCREATE]", 11)){ ps_global->try_to_create = 1; return; } else if(ps_global->try_to_create || !strncmp(string, "[CLOSED]", 8) || (sp_dead_stream(ps_global->mail_stream) && strstr(string, "No-op"))) /* * Don't display if creating new folder OR * warning about a dead stream ... */ return; strncpy(message, string, sizeof(message)); message[sizeof(message) - 1] = '\0'; if(errflg == WARN && srchstr(message, "try running kinit") != NULL){ if(saw_kerberos_init_warning) return; saw_kerberos_init_warning = 1; } /*---- replace all "mailbox" with "folder" ------*/ occurence = srchstr(message, "mailbox"); while(occurence) { if(!*(occurence+7) || isspace((unsigned char) *(occurence+7)) || *(occurence+7) == ':'){ was_capitalized = isupper((unsigned char) *occurence); rplstr(occurence, sizeof(message)-(occurence-message), 7, (errflg == PARSE ? "address" : "folder")); if(was_capitalized) *occurence = (errflg == PARSE ? 'A' : 'F'); } else occurence += 7; occurence = srchstr(occurence, "mailbox"); } /*---- replace all "GSSAPI" with "Kerberos" ------*/ occurence = srchstr(message, "GSSAPI"); while(occurence) { if(!*(occurence+6) || isspace((unsigned char) *(occurence+6)) || *(occurence+6) == ':') rplstr(occurence, sizeof(message)-(occurence-message), 6, "Kerberos"); else occurence += 6; occurence = srchstr(occurence, "GSSAPI"); } if(errflg == ERROR) ps_global->mm_log_error = 1; if(errflg == PARSE || (errflg == ERROR && ps_global->noshow_error)) strncpy(ps_global->c_client_error, message, sizeof(ps_global->c_client_error)); if(ps_global->noshow_error || (ps_global->noshow_warn && errflg == WARN) || !(errflg == ERROR || errflg == WARN)) return; /* Only care about errors; don't print when asked not to */ /*---- Display the message ------*/ q_status_message((errflg == ERROR) ? (SM_ORDER | SM_DING) : SM_ORDER, 3, 5, message); strncpy(ps_global->last_error, message, sizeof(ps_global->last_error)); ps_global->last_error[sizeof(ps_global->last_error) - 1] = '\0'; } void mm_login_method_work(NETMBX *mb, char *user, void *login, long int trial, char *method, char *usethisprompt, char *altuserforcache) { if(method == NULL) return; if(strucmp(method, OA2NAME) == 0) mm_login_oauth2(mb, user, (OAUTH2_S *) login, trial, usethisprompt, altuserforcache); } void mm_login_work(NETMBX *mb, char *user, char **pwd, long int trial, char *usethisprompt, char *altuserforcache) { char tmp[MAILTMPLEN]; char prompt[1000], *last; char port[20], non_def_port[20], insecure[20]; char defuser[NETMAXUSER]; char hostleadin[80], hostname[200], defubuf[200]; char logleadin[80], pwleadin[50]; char hostlist0[MAILTMPLEN], hostlist1[MAILTMPLEN]; /* TRANSLATORS: when logging in, this text is added to the prompt to show that the password will be sent unencrypted over the network. This is just a warning message that gets added parenthetically when the user is asked for a password. */ char *insec = _(" (INSECURE)"); /* TRANSLATORS: Retrying is shown when the user is being asked for a password after having already failed at least once. */ char *retry = _("Retrying - "); /* TRANSLATORS: A label for the hostname that the user is logging in on */ char *hostlabel = _("HOST"); /* TRANSLATORS: user is logging in as a particular user (a particular login name), this is just labelling that user name. */ char *userlabel = _("USER"); STRLIST_S hostlist[2]; HelpType help ; int len, rc, q_line, flags; int oespace, avail, need, save_dont_use; int save_in_init; struct servent *sv; #if defined(_WINDOWS) || defined(LOCAL_PASSWD_CACHE) int preserve_password = -1; #endif dprint((9, "mm_login_work trial=%ld user=%s service=%s%s%s%s%s\n", trial, mb->user ? mb->user : "(null)", mb->service ? mb->service : "(null)", mb->port ? " port=" : "", mb->port ? comatose(mb->port) : "", altuserforcache ? " altuserforcache =" : "", altuserforcache ? altuserforcache : "")); q_line = -(ps_global->ttyo ? ps_global->ttyo->footer_rows : 3); save_in_init = ps_global->in_init_seq; ps_global->in_init_seq = 0; ps_global->no_newmail_check_from_optionally_enter = 1; /* make sure errors are seen */ if(ps_global->ttyo) flush_status_messages(0); /* * Add port number to hostname if going through a tunnel or something */ non_def_port[0] = '\0'; if(mb->port && mb->service && (sv = getservbyname(mb->service, "tcp")) && (mb->port != ntohs(sv->s_port))){ snprintf(non_def_port, sizeof(non_def_port), ":%lu", mb->port); non_def_port[sizeof(non_def_port)-1] = '\0'; dprint((9, "mm_login: using non-default port=%s\n", non_def_port ? non_def_port : "?")); } /* * set up host list for sybil servers... */ if(*non_def_port){ strncpy(hostlist0, mb->host, sizeof(hostlist0)-1); hostlist0[sizeof(hostlist0)-1] = '\0'; strncat(hostlist0, non_def_port, sizeof(hostlist0)-strlen(hostlist0)-1); hostlist0[sizeof(hostlist0)-1] = '\0'; hostlist[0].name = hostlist0; if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){ strncpy(hostlist1, mb->orighost, sizeof(hostlist1)-1); hostlist1[sizeof(hostlist1)-1] = '\0'; strncat(hostlist1, non_def_port, sizeof(hostlist1)-strlen(hostlist1)-1); hostlist1[sizeof(hostlist1)-1] = '\0'; hostlist[0].next = &hostlist[1]; hostlist[1].name = hostlist1; hostlist[1].next = NULL; } else hostlist[0].next = NULL; } else{ hostlist[0].name = mb->host; if(mb->orighost && mb->orighost[0] && strucmp(mb->host, mb->orighost)){ hostlist[0].next = &hostlist[1]; hostlist[1].name = mb->orighost; hostlist[1].next = NULL; } else hostlist[0].next = NULL; } if(hostlist[0].name){ dprint((9, "mm_login: host=%s\n", hostlist[0].name ? hostlist[0].name : "?")); if(hostlist[0].next && hostlist[1].name){ dprint((9, "mm_login: orighost=%s\n", hostlist[1].name)); } } /* * Initialize user name with either * 1) /user= value in the stream being logged into, * or 2) the user name we're running under. * * Note that VAR_USER_ID is not yet initialized if this login is * the one to access the remote config file. In that case, the user * can supply the username in the config file name with /user=. */ if(trial == 0L && !altuserforcache){ strncpy(user, (*mb->user) ? mb->user : ps_global->VAR_USER_ID ? ps_global->VAR_USER_ID : "", NETMAXUSER); user[NETMAXUSER-1] = '\0'; /* try last working password associated with this host. */ if(imap_get_passwd(mm_login_list, pwd, user, hostlist, (mb->sslflag||mb->tlsflag))){ dprint((9, "mm_login: found a password to try\n")); ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } #ifdef LOCAL_PASSWD_CACHE /* check to see if there's a password left over from last session */ if(get_passfile_passwd(ps_global->pinerc, pwd, user, hostlist, (mb->sslflag||mb->tlsflag))){ imap_set_passwd(&mm_login_list, *pwd, user, hostlist, (mb->sslflag||mb->tlsflag), 0, 0); update_passfile_hostlist(ps_global->pinerc, user, hostlist, (mb->sslflag||mb->tlsflag)); dprint((9, "mm_login: found a password in passfile to try\n")); ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } #endif /* LOCAL_PASSWD_CACHE */ /* * If no explicit user name supplied and we've not logged in * with our local user name, see if we've visited this * host before as someone else. */ if(!*mb->user && ((last = imap_get_user(mm_login_list, hostlist)) #ifdef LOCAL_PASSWD_CACHE || (last = get_passfile_user(ps_global->pinerc, hostlist)) #endif /* LOCAL_PASSWD_CACHE */ )){ strncpy(user, last, NETMAXUSER); user[NETMAXUSER-1] = '\0'; dprint((9, "mm_login: found user=%s\n", user ? user : "?")); /* try last working password associated with this host/user. */ if(imap_get_passwd(mm_login_list, pwd, user, hostlist, (mb->sslflag||mb->tlsflag))){ dprint((9, "mm_login: found a password for user=%s to try\n", user ? user : "?")); ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } #ifdef LOCAL_PASSWD_CACHE /* check to see if there's a password left over from last session */ if(get_passfile_passwd(ps_global->pinerc, pwd, user, hostlist, (mb->sslflag||mb->tlsflag))){ imap_set_passwd(&mm_login_list, *pwd, user, hostlist, (mb->sslflag||mb->tlsflag), 0, 0); update_passfile_hostlist(ps_global->pinerc, user, hostlist, (mb->sslflag||mb->tlsflag)); dprint((9, "mm_login: found a password for user=%s in passfile to try\n", user ? user : "?")); ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } #endif /* LOCAL_PASSWD_CACHE */ } #if !defined(DOS) && !defined(OS2) if(!*mb->user && !*user && (last = (ps_global->ui.login && ps_global->ui.login[0]) ? ps_global->ui.login : NULL) ){ strncpy(user, last, NETMAXUSER); user[NETMAXUSER-1] = '\0'; dprint((9, "mm_login: found user=%s\n", user ? user : "?")); /* try last working password associated with this host. */ if(imap_get_passwd(mm_login_list, pwd, user, hostlist, (mb->sslflag||mb->tlsflag))){ dprint((9, "mm_login:ui: found a password to try\n")); ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } #ifdef LOCAL_PASSWD_CACHE /* check to see if there's a password left over from last session */ if(get_passfile_passwd(ps_global->pinerc, pwd, user, hostlist, (mb->sslflag||mb->tlsflag))){ imap_set_passwd(&mm_login_list, *pwd, user, hostlist, (mb->sslflag||mb->tlsflag), 0, 0); update_passfile_hostlist(ps_global->pinerc, user, hostlist, (mb->sslflag||mb->tlsflag)); dprint((9, "mm_login:ui: found a password in passfile to try\n")); ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } #endif /* LOCAL_PASSWD_CACHE */ } #endif } user[NETMAXUSER-1] = '\0'; if(trial == 0) retry = ""; /* * Even if we have a user now, user gets a chance to change it. */ ps_global->mangled_footer = 1; if(!*mb->user && !altuserforcache){ help = NO_HELP; /* * Instead of offering user with a value that the user can edit, * we offer [user] as a default so that the user can type CR to * use it. Otherwise, the user has to type in whole name. */ strncpy(defuser, user, sizeof(defuser)-1); defuser[sizeof(defuser)-1] = '\0'; user[0] = '\0'; /* * Need space for "Retrying - " * "+ HOST: " * hostname * " (INSECURE)" * ENTER LOGIN NAME * " [defuser] : " * about 15 chars for input */ snprintf(hostleadin, sizeof(hostleadin), "%s%s: ", (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel); hostleadin[sizeof(hostleadin)-1] = '\0'; strncpy(hostname, mb->host, sizeof(hostname)-1); hostname[sizeof(hostname)-1] = '\0'; /* * Add port number to hostname if going through a tunnel or something */ if(*non_def_port) strncpy(port, non_def_port, sizeof(port)); else port[0] = '\0'; insecure[0] = '\0'; /* if not encrypted and SSL/TLS is supported */ if(!(mb->sslflag||mb->tlsflag) && mail_parameters(NIL, GET_SSLDRIVER, NIL)) strncpy(insecure, insec, sizeof(insecure)); /* TRANSLATORS: user is being asked to type in their login name */ snprintf(logleadin, sizeof(logleadin), " %s", _("ENTER LOGIN NAME")); snprintf(defubuf, sizeof(defubuf), "%s%s%s : ", (*defuser) ? " [" : "", (*defuser) ? defuser : "", (*defuser) ? "]" : ""); defubuf[sizeof(defubuf)-1] = '\0'; /* space reserved after prompt */ oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6); avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80; need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) + utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + oespace; /* If we're retrying cut the hostname back to the first word. */ if(avail < need && trial > 0){ char *p; len = strlen(hostname); if((p = strchr(hostname, '.')) != NULL){ *p = '\0'; need -= (len - strlen(hostname)); } } if(avail < need){ need -= utf8_width(retry); retry = ""; if(avail < need){ /* reduce length of logleadin */ len = utf8_width(logleadin); /* TRANSLATORS: An abbreviated form of ENTER LOGIN NAME because longer version doesn't fit on screen */ snprintf(logleadin, sizeof(logleadin), " %s", _("LOGIN")); need -= (len - utf8_width(logleadin)); if(avail < need){ /* get two spaces from hostleadin */ len = utf8_width(hostleadin); snprintf(hostleadin, sizeof(hostleadin), "%s%s:", (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel); hostleadin[sizeof(hostleadin)-1] = '\0'; need -= (len - utf8_width(hostleadin)); /* get rid of port */ if(avail < need && strlen(port) > 0){ need -= strlen(port); port[0] = '\0'; } if(avail < need){ int reduce_to; /* * Reduce space for hostname. Best we can do is 6 chars * with hos... */ reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6; len = strlen(hostname); strncpy(hostname+reduce_to-3, "...", 4); need -= (len - strlen(hostname)); if(avail < need && strlen(insecure) > 0){ if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){ need -= 3; insecure[strlen(insecure)-4] = ')'; insecure[strlen(insecure)-3] = '\0'; } else{ need -= utf8_width(insecure); insecure[0] = '\0'; } } if(avail < need){ if(strlen(defubuf) > 3){ len = strlen(defubuf); strncpy(defubuf, " [..] :", 9); need -= (len - strlen(defubuf)); } if(avail < need) strncpy(defubuf, ":", 2); /* * If it still doesn't fit, optionally_enter gets * to worry about it. */ } } } } } snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s", retry, hostleadin, hostname, port, insecure, logleadin, defubuf); prompt[sizeof(prompt)-1] = '\0'; while(1) { if(ps_global->ttyo) mm_login_alt_cue(mb); flags = OE_APPEND_CURRENT; save_dont_use = ps_global->dont_use_init_cmds; ps_global->dont_use_init_cmds = 1; #ifdef _WINDOWS if(!*user && *defuser){ strncpy(user, defuser, NETMAXUSER); user[NETMAXUSER-1] = '\0'; } rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD, #ifdef LOCAL_PASSWD_CACHE is_using_passfile() ? 1 : #endif /* LOCAL_PASSWD_CACHE */ 0, 0, &preserve_password); ps_global->dont_use_init_cmds = save_dont_use; if(rc == 0 && *user && *pwd) goto nopwpmt; #else /* !_WINDOWS */ rc = optionally_enter(user, q_line, 0, NETMAXUSER, prompt, NULL, help, &flags); #endif /* !_WINDOWS */ ps_global->dont_use_init_cmds = save_dont_use; if(rc == 3) { help = help == NO_HELP ? h_oe_login : NO_HELP; continue; } /* default */ if(rc == 0 && !*user){ strncpy(user, defuser, NETMAXUSER); user[NETMAXUSER-1] = '\0'; } if(rc != 4) break; } if(rc == 1 || !user[0]) { ps_global->user_says_cancel = (rc == 1); user[0] = '\0'; } } else{ strncpy(user, mb->user, NETMAXUSER); user[NETMAXUSER-1] = '\0'; } user[NETMAXUSER-1] = '\0'; if(!(user[0] || altuserforcache)){ ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } /* * Now that we have a user, we can check in the cache again to see * if there is a password there. Try last working password associated * with this host and user. */ if(trial == 0L && !*mb->user && !altuserforcache){ if(imap_get_passwd(mm_login_list, pwd, user, hostlist, (mb->sslflag||mb->tlsflag))){ ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } #ifdef LOCAL_PASSWD_CACHE if(get_passfile_passwd(ps_global->pinerc, pwd, user, hostlist, (mb->sslflag||mb->tlsflag))){ imap_set_passwd(&mm_login_list, *pwd, user, hostlist, (mb->sslflag||mb->tlsflag), 0, 0); ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } #endif /* LOCAL_PASSWD_CACHE */ } else if(trial == 0 && altuserforcache){ if(imap_get_passwd(mm_login_list, pwd, altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){ ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } #ifdef LOCAL_PASSWD_CACHE if(get_passfile_passwd(ps_global->pinerc, pwd, altuserforcache, hostlist, (mb->sslflag||mb->tlsflag))){ imap_set_passwd(&mm_login_list, *pwd, altuserforcache, hostlist, (mb->sslflag||mb->tlsflag), 0, 0); ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } #endif /* LOCAL_PASSWD_CACHE */ } /* * Didn't find password in cache or this isn't the first try. Ask user. */ help = NO_HELP; /* * Need space for "Retrying - " * "+ HOST: " * hostname * " (INSECURE) " * " USER: " * user * " ENTER PASSWORD: " * about 15 chars for input */ snprintf(hostleadin, sizeof(hostleadin), "%s%s: ", (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+ " : "", hostlabel); strncpy(hostname, mb->host, sizeof(hostname)-1); hostname[sizeof(hostname)-1] = '\0'; /* * Add port number to hostname if going through a tunnel or something */ if(*non_def_port) strncpy(port, non_def_port, sizeof(port)); else port[0] = '\0'; insecure[0] = '\0'; /* if not encrypted and SSL/TLS is supported */ if(!(mb->sslflag||mb->tlsflag) && mail_parameters(NIL, GET_SSLDRIVER, NIL)) strncpy(insecure, insec, sizeof(insecure)); if(usethisprompt){ strncpy(logleadin, usethisprompt, sizeof(logleadin)); logleadin[sizeof(logleadin)-1] = '\0'; defubuf[0] = '\0'; user[0] = '\0'; } else{ snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel); strncpy(defubuf, user, sizeof(defubuf)-1); defubuf[sizeof(defubuf)-1] = '\0'; } /* TRANSLATORS: user is being asked to type in their password */ snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("ENTER PASSWORD")); /* space reserved after prompt */ oespace = MAX(MIN(15, (ps_global->ttyo ? ps_global->ttyo->screen_cols : 80)/5), 6); avail = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80; need = utf8_width(retry) + utf8_width(hostleadin) + strlen(hostname) + strlen(port) + utf8_width(insecure) + utf8_width(logleadin) + strlen(defubuf) + utf8_width(pwleadin) + oespace; if(avail < need && trial > 0){ char *p; len = strlen(hostname); if((p = strchr(hostname, '.')) != NULL){ *p = '\0'; need -= (len - strlen(hostname)); } } if(avail < need){ need -= utf8_width(retry); retry = ""; if(avail < need){ if(!usethisprompt){ snprintf(logleadin, sizeof(logleadin), " %s: ", userlabel); need--; } rplstr(pwleadin, sizeof(pwleadin), 1, ""); need--; if(avail < need){ /* get two spaces from hostleadin */ len = utf8_width(hostleadin); snprintf(hostleadin, sizeof(hostleadin), "%s%s:", (!ps_global->ttyo && (mb->sslflag||mb->tlsflag)) ? "+" : "", hostlabel); hostleadin[sizeof(hostleadin)-1] = '\0'; need -= (len - utf8_width(hostleadin)); /* get rid of port */ if(avail < need && strlen(port) > 0){ need -= strlen(port); port[0] = '\0'; } if(avail < need){ len = utf8_width(pwleadin); /* TRANSLATORS: An abbreviated form of ENTER PASSWORD */ snprintf(pwleadin, sizeof(pwleadin), " %s: ", _("PASSWORD")); need -= (len - utf8_width(pwleadin)); } } if(avail < need){ int reduce_to; /* * Reduce space for hostname. Best we can do is 6 chars * with hos... */ reduce_to = (need - avail < strlen(hostname) - 6) ? (strlen(hostname)-(need-avail)) : 6; len = strlen(hostname); strncpy(hostname+reduce_to-3, "...", 4); need -= (len - strlen(hostname)); if(avail < need && strlen(insecure) > 0){ if(need - avail <= 3 && !strcmp(insecure," (INSECURE)")){ need -= 3; insecure[strlen(insecure)-4] = ')'; insecure[strlen(insecure)-3] = '\0'; } else{ need -= utf8_width(insecure); insecure[0] = '\0'; } } if(avail < need){ len = utf8_width(logleadin); strncpy(logleadin, " ", sizeof(logleadin)); logleadin[sizeof(logleadin)-1] = '\0'; need -= (len - utf8_width(logleadin)); if(avail < need){ reduce_to = (need - avail < strlen(defubuf) - 6) ? (strlen(defubuf)-(need-avail)) : 0; if(reduce_to) strncpy(defubuf+reduce_to-3, "...", 4); else defubuf[0] = '\0'; } } } } } snprintf(prompt, sizeof(prompt), "%s%s%s%s%s%s%s%s", retry, hostleadin, hostname, port, insecure, logleadin, defubuf, pwleadin); prompt[sizeof(prompt)-1] = '\0'; tmp[0] = '\0'; while(1) { if(ps_global->ttyo) mm_login_alt_cue(mb); save_dont_use = ps_global->dont_use_init_cmds; ps_global->dont_use_init_cmds = 1; flags = F_ON(F_QUELL_ASTERISKS, ps_global) ? OE_PASSWD_NOAST : OE_PASSWD; flags |= OE_KEEP_TRAILING_SPACE; #ifdef _WINDOWS rc = os_login_dialog(mb, user, NETMAXUSER, tmp, NETMAXPASSWD, 0, 1, &preserve_password); #else /* !_WINDOWS */ rc = optionally_enter(tmp, q_line, 0, NETMAXPASSWD, prompt, NULL, help, &flags); #endif /* !_WINDOWS */ if(rc != 1) *pwd = cpystr(tmp); ps_global->dont_use_init_cmds = save_dont_use; if(rc == 3) { help = help == NO_HELP ? h_oe_passwd : NO_HELP; } else if(rc == 4){ } else break; } if(rc == 1 || !tmp[0]) { ps_global->user_says_cancel = (rc == 1); user[0] = '\0'; ps_global->no_newmail_check_from_optionally_enter = 0; ps_global->in_init_seq = save_in_init; return; } #ifdef _WINDOWS nopwpmt: #endif /* remember the password for next time */ if(F_OFF(F_DISABLE_PASSWORD_CACHING,ps_global)) imap_set_passwd(&mm_login_list, *pwd, altuserforcache ? altuserforcache : user, hostlist, (mb->sslflag||mb->tlsflag), 0, 0); #ifdef LOCAL_PASSWD_CACHE /* if requested, remember it on disk for next session */ if(save_password && F_OFF(F_DISABLE_PASSWORD_FILE_SAVING,ps_global)) set_passfile_passwd(ps_global->pinerc, *pwd, altuserforcache ? altuserforcache : user, hostlist, (mb->sslflag||mb->tlsflag), (preserve_password == -1 ? 0 : (preserve_password == 0 ? 2 :1))); #endif /* LOCAL_PASSWD_CACHE */ ps_global->no_newmail_check_from_optionally_enter = 0; } void mm_login_alt_cue(NETMBX *mb) { if(ps_global->ttyo){ COLOR_PAIR *lastc; lastc = pico_set_colors(ps_global->VAR_TITLE_FORE_COLOR, ps_global->VAR_TITLE_BACK_COLOR, PSC_REV | PSC_RET); mark_titlebar_dirty(); PutLine0(0, ps_global->ttyo->screen_cols - 1, (mb->sslflag||mb->tlsflag) ? "+" : " "); if(lastc){ (void)pico_set_colorp(lastc, PSC_NONE); free_color_pair(&lastc); } fflush(stdout); } } /*---------------------------------------------------------------------- Receive notification of an error writing to disk Args: stream -- The stream the error occured on errcode -- The system error code (errno) serious -- Flag indicating error is serious (mail may be lost) Result: If error is non serious, the stream is marked as having an error and deletes are disallowed until error clears If error is serious this goes modal, allowing the user to retry or get a shell escape to fix the condition. When the condition is serious it means that mail existing in the mailbox will be lost if Pine exits without writing, so we try to induce the user to fix the error, go get someone that can fix the error, or whatever and don't provide an easy way out. ----*/ long mm_diskerror (MAILSTREAM *stream, long int errcode, long int serious) { int i, j; char *p, *q, *s; static ESCKEY_S de_opts[] = { {'r', 'r', "R", "Retry"}, {'f', 'f', "F", "FileBrowser"}, {'s', 's', "S", "ShellPrompt"}, {-1, 0, NULL, NULL} }; #define DE_COLS (ps_global->ttyo->screen_cols) #define DE_LINE (ps_global->ttyo->screen_rows - 3) #define DE_FOLDER(X) (((X) && (X)->mailbox) ? (X)->mailbox : ""); so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server")); so_puts(in_store, "
"); so_puts(in_store, _("The reason for the failure was")); /* squirrel away details */ if(details_host) fs_give((void **)&details_host); if(details_reason) fs_give((void **)&details_reason); if(details_cert) fs_give((void **)&details_cert); details_host = cpystr(host ? host : unknown); details_reason = cpystr(reason ? reason : unknown); details_cert = cpystr(cert ? cert : unknown); so_puts(in_store, "
"); 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.")); so_puts(in_store, "
"); 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")); so_puts(in_store, "
"); so_puts(in_store, _("to the name of the folder you attempted to access. In other words, wherever you see the characters")); so_puts(in_store, "
"); so_puts(in_store, _("in your configuration, replace those characters with")); so_puts(in_store, "
");
so_puts(in_store, _("Answer \"Yes\" to ignore the warning and continue, \"No\" to cancel the open of this folder."));
so_seek(in_store, 0L, 0);
init_handles(&handles);
gf_filter_init();
gf_link_filter(gf_html2plain,
gf_html2plain_opt(NULL,
ps_global->ttyo->screen_cols, non_messageview_margin(),
&handles, NULL, GFHP_LOCAL_HANDLES));
gf_set_so_readc(&gc, in_store);
gf_set_so_writec(&pc, out_store);
gf_pipe(gc, pc);
gf_clear_so_writec(out_store);
gf_clear_so_readc(in_store);
memset(&sargs, 0, sizeof(SCROLL_S));
sargs.text.handles = handles;
sargs.text.text = so_text(out_store);
sargs.text.src = CharStar;
sargs.text.desc = _("help text");
sargs.bar.title = _("SSL/TLS CERTIFICATE VALIDATION FAILURE");
sargs.proc.tool = answer_cert_failure;
sargs.proc.data.p = (void *)&the_answer;
sargs.keys.menu = &ans_certquery_keymenu;
/* don't want to re-enter c-client */
sargs.quell_newmail = 1;
setbitmap(sargs.keys.bitmap);
sargs.help.text = h_tls_validation_failure;
sargs.help.title = _("HELP FOR CERT VALIDATION FAILURE");
scrolltool(&sargs);
if(the_answer == 'y')
rv++;
ps_global->mangled_screen = 1;
ps_global->painted_body_on_startup = 0;
ps_global->painted_footer_on_startup = 0;
so_give(&in_store);
so_give(&out_store);
free_handles(&handles);
if(details_host)
fs_give((void **)&details_host);
if(details_reason)
fs_give((void **)&details_reason);
if(details_cert)
fs_give((void **)&details_cert);
}
else{
/*
* If screen hasn't been initialized yet, use want_to.
*/
try_wantto:
memset((void *)tmp, 0, sizeof(tmp));
strncpy(tmp,
reason ? reason : _("SSL/TLS certificate validation failure"),
sizeof(tmp));
tmp[sizeof(tmp)-1] = '\0';
strncat(tmp, _(": Continue anyway "), sizeof(tmp)-strlen(tmp)-1);
if(want_to(tmp, 'n', 'x', NO_HELP, WT_NORM) == 'y')
rv++;
}
if(rv == 0)
q_status_message1(SM_ORDER, 1, 3, _("Open of %s cancelled"),
host ? host : unknown);
imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, rv ? 1 : 0, 0);
dprint((5, "sslcertificatequery: %s\n",
rv ? "approved" : "rejected"));
return(rv);
}
char *
pine_newsrcquery(MAILSTREAM *stream, char *mulname, char *name)
{
char buf[MAILTMPLEN];
if((can_access(mulname, ACCESS_EXISTS) == 0)
|| !(can_access(name, ACCESS_EXISTS) == 0))
return(mulname);
snprintf(buf, sizeof(buf),
_("Rename newsrc \"%s%s\" for use as new host-specific newsrc"),
last_cmpnt(name),
strlen(last_cmpnt(name)) > 15 ? "..." : "");
buf[sizeof(buf)-1] = '\0';
if(want_to(buf, 'n', 'n', NO_HELP, WT_NORM) == 'y')
rename_file(name, mulname);
return(mulname);
}
int
url_local_certdetails(char *url)
{
if(!struncmp(url, "x-alpine-cert:", 14)){
STORE_S *store;
SCROLL_S sargs;
char *folded;
if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))){
q_status_message(SM_ORDER | SM_DING, 7, 10,
_("Error allocating space for details."));
return(0);
}
so_puts(store, _("Host given by user:\n\n "));
so_puts(store, details_host);
so_puts(store, _("\n\nReason for failure:\n\n "));
so_puts(store, details_reason);
so_puts(store, _("\n\nCertificate being verified:\n\n"));
folded = fold(details_cert, ps_global->ttyo->screen_cols, ps_global->ttyo->screen_cols, " ", " ", FLD_NONE);
so_puts(store, folded);
fs_give((void **)&folded);
so_puts(store, "\n");
memset(&sargs, 0, sizeof(SCROLL_S));
sargs.text.text = so_text(store);
sargs.text.src = CharStar;
sargs.text.desc = _("Details");
sargs.bar.title = _("CERT VALIDATION DETAILS");
sargs.help.text = NO_HELP;
sargs.help.title = NULL;
sargs.quell_newmail = 1;
sargs.help.text = h_tls_failure_details;
sargs.help.title = _("HELP FOR CERT VALIDATION DETAILS");
scrolltool(&sargs);
so_give(&store); /* free resources associated with store */
ps_global->mangled_screen = 1;
return(1);
}
return(0);
}
/*
* C-client callback to handle SSL/TLS certificate validation failures
*/
void
pine_sslfailure(char *host, char *reason, long unsigned int flags)
{
SCROLL_S sargs;
STORE_S *store;
int the_answer = 'n', indent, len, cols;
char buf[500], buf2[500];
char *folded;
char *hst = host ? host : "