diff options
author | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
---|---|---|
committer | Eduardo Chappa <echappa@gmx.com> | 2013-02-03 00:59:38 -0700 |
commit | 094ca96844842928810f14844413109fc6cdd890 (patch) | |
tree | e60efbb980f38ba9308ccb4fb2b77b87bbc115f3 /alpine/imap.c | |
download | alpine-094ca96844842928810f14844413109fc6cdd890.tar.xz |
Initial Alpine Version
Diffstat (limited to 'alpine/imap.c')
-rw-r--r-- | alpine/imap.c | 3011 |
1 files changed, 3011 insertions, 0 deletions
diff --git a/alpine/imap.c b/alpine/imap.c new file mode 100644 index 00000000..074b9f6d --- /dev/null +++ b/alpine/imap.c @@ -0,0 +1,3011 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: imap.c 1266 2009-07-14 18:39:12Z hubert@u.washington.edu $"; +#endif + +/* + * ======================================================================== + * Copyright 2006-2009 University of Washington + * Copyright 2013 Eduardo Chappa + * + * 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 "../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 <wincred.h> +#define TNAME "UWash_Alpine_" +#define TNAMESTAR "UWash_Alpine_*" + +/* + * WinCred Function prototypes + */ +typedef BOOL (WINAPI CREDWRITEW) ( __in PCREDENTIALW Credential, __in DWORD Flags ); +typedef BOOL (WINAPI CREDENUMERATEW) ( __in LPCWSTR Filter, __reserved DWORD Flags, + __out DWORD *Count, __deref_out_ecount(*Count) PCREDENTIALW **Credential ); +typedef BOOL (WINAPI CREDDELETEW) ( __in LPCWSTR TargetName, __in DWORD Type, + __reserved DWORD Flags ); +typedef VOID (WINAPI CREDFREE) ( __in PVOID Buffer ); + +/* + * WinCred functions + */ +int g_CredInited = 0; /* 1 for loaded successfully, + * -1 for not available. + * 0 for not initialized yet. + */ +CREDWRITEW *g_CredWriteW; +CREDENUMERATEW *g_CredEnumerateW; +CREDDELETEW *g_CredDeleteW; +CREDFREE *g_CredFree; + +#endif /* WINCRED */ + +#ifdef APPLEKEYCHAIN +#include <Security/SecKeychain.h> +#include <Security/SecKeychainItem.h> +#include <Security/SecKeychainSearch.h> +#define TNAME "UWash_Alpine" +#define TNAMEPROMPT "UWash_Alpine_Prompt_For_Password" + +int macos_store_pass_prompt(void); +void macos_set_store_pass_prompt(int); + +static int storepassprompt = -1; +#endif /* APPLEKEYCHAIN */ + + +/* + * Internal prototypes + */ +void mm_login_alt_cue(NETMBX *); +long pine_tcptimeout_noscreen(long, long, char *); +int answer_cert_failure(int, MSGNO_S *, SCROLL_S *); + +#ifdef LOCAL_PASSWD_CACHE +int read_passfile(char *, MMLOGIN_S **); +void write_passfile(char *, MMLOGIN_S *); +int preserve_prompt(void); +void update_passfile_hostlist(char *, char *, STRLIST_S *, int); + +static MMLOGIN_S *passfile_cache = NULL; +static int using_passfile = -1; +int save_password = 1; +#endif /* LOCAL_PASSWD_CACHE */ + +#ifdef PASSFILE +char xlate_in(int); +char xlate_out(char); +char *passfile_name(char *, char *, size_t); +int line_get(char *, size_t, char **); +#endif /* PASSFILE */ + +#if (WINCRED > 0) +void ask_erase_credentials(void); +int init_wincred_funcs(void); +#endif /* WINCRED */ + + +static char *details_cert, *details_host, *details_reason; + + +/*---------------------------------------------------------------------- + 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-", + 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_work(NETMBX *mb, char *user, char *pwd, long int trial, + char *usethisprompt, char *altuserforcache) +{ + 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'; + pwd[0] = '\0'; + } + } + else{ + strncpy(user, mb->user, NETMAXUSER); + user[NETMAXUSER-1] = '\0'; + } + + user[NETMAXUSER-1] = '\0'; + pwd[NETMAXPASSWD-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'; + + *pwd = '\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; +#ifdef _WINDOWS + rc = os_login_dialog(mb, user, NETMAXUSER, pwd, NETMAXPASSWD, 0, 1, + &preserve_password); +#else /* !_WINDOWS */ + rc = optionally_enter(pwd, q_line, 0, NETMAXPASSWD, + prompt, NULL, help, &flags); +#endif /* !_WINDOWS */ + 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 || !pwd[0]) { + ps_global->user_says_cancel = (rc == 1); + user[0] = pwd[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) + 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 : "<no folder>") +#define DE_PMT \ + "Disk error! Choose Retry, or the File browser or Shell to clean up: " +#define DE_STR1 "SERIOUS DISK ERROR WRITING: \"%s\"" +#define DE_STR2 \ + "The reported error number is %s. The last reported mail error was:" + static char *de_msg[] = { + "Please try to correct the error preventing Alpine from saving your", + "mail folder. For example if the disk is out of space try removing", + "unneeded files. You might also contact your system administrator.", + "", + "Both Alpine's File Browser and an option to enter the system's", + "command prompt are offered to aid in fixing the problem. When", + "you believe the problem is resolved, choose the \"Retry\" option.", + "Be aware that messages may be lost or this folder left in an", + "inaccessible condition if you exit or kill Alpine before the problem", + "is resolved.", + NULL}; + static char *de_shell_msg[] = { + "\n\nPlease attempt to correct the error preventing saving of the", + "mail folder. If you do not know how to correct the problem, contact", + "your system administrator. To return to Alpine, type \"exit\".", + NULL}; + + dprint((0, + "\n***** DISK ERROR on stream %s. Error code %ld. Error is %sserious\n", + DE_FOLDER(stream), errcode, serious ? "" : "not ")); + dprint((0, "***** message: \"%s\"\n\n", + ps_global->last_error ? ps_global->last_error : "?")); + + if(!serious) { + sp_set_io_error_on_stream(stream, 1); + return (1) ; + } + + while(1){ + /* replace pine's body display with screen full of explanatory text */ + ClearLine(2); + PutLine1(2, MAX((DE_COLS - sizeof(DE_STR1) + - strlen(DE_FOLDER(stream)))/2, 0), + DE_STR1, DE_FOLDER(stream)); + ClearLine(3); + PutLine1(3, 4, DE_STR2, long2string(errcode)); + + PutLine0(4, 0, " \""); + removing_leading_white_space(ps_global->last_error); + for(i = 4, p = ps_global->last_error; *p && i < DE_LINE; ){ + for(s = NULL, q = p; *q && q - p < DE_COLS - 16; q++) + if(isspace((unsigned char)*q)) + s = q; + + if(*q && s) + q = s; + + while(p < q) + Writechar(*p++, 0); + + if(*(p = q)){ + ClearLine(++i); + PutLine0(i, 0, " "); + while(*p && isspace((unsigned char)*p)) + p++; + } + else{ + Writechar('\"', 0); + CleartoEOLN(); + break; + } + } + + ClearLine(++i); + for(j = ++i; i < DE_LINE && de_msg[i-j]; i++){ + ClearLine(i); + PutLine0(i, 0, " "); + Write_to_screen(de_msg[i-j]); + } + + while(i < DE_LINE) + ClearLine(i++); + + switch(radio_buttons(DE_PMT, -FOOTER_ROWS(ps_global), de_opts, + 'r', 0, NO_HELP, RB_FLUSH_IN | RB_NO_NEWMAIL)){ + case 'r' : /* Retry! */ + ps_global->mangled_screen = 1; + return(0L); + + case 'f' : /* File Browser */ + { + char full_filename[MAXPATH+1], filename[MAXPATH+1]; + + filename[0] = '\0'; + build_path(full_filename, ps_global->home_dir, filename, + sizeof(full_filename)); + file_lister("DISK ERROR", full_filename, sizeof(full_filename), + filename, sizeof(filename), FALSE, FB_SAVE); + } + + break; + + case 's' : + EndInverse(); + end_keyboard(ps_global ? F_ON(F_USE_FK,ps_global) : 0); + end_tty_driver(ps_global); + for(i = 0; de_shell_msg[i]; i++) + puts(de_shell_msg[i]); + + /* + * Don't use our piping mechanism to spawn a subshell here + * since it will the server (thus reentering c-client). + * Bad thing to do. + */ +#ifdef _WINDOWS +#else + system("csh"); +#endif + init_tty_driver(ps_global); + init_keyboard(F_ON(F_USE_FK,ps_global)); + break; + } + + if(ps_global->redrawer) + (*ps_global->redrawer)(); + } +} + + +long +pine_tcptimeout_noscreen(long int elapsed, long int sincelast, char *host) +{ + long rv = 1L; + char pmt[128]; + +#ifdef _WINDOWS + mswin_killsplash(); +#endif + + if(elapsed >= (long)ps_global->tcp_query_timeout){ + snprintf(pmt, sizeof(pmt), + _("No reply in %s seconds from server %s. Break connection"), + long2string(elapsed), host); + pmt[sizeof(pmt)-1] = '\0'; + if(want_to(pmt, 'n', 'n', NO_HELP, WT_FLUSH_IN) == 'y'){ + ps_global->user_says_cancel = 1; + return(0L); + } + } + + return(rv); +} + + +/* + * ------------------------------------------------------------- + * These are declared in pith/imap.h as mandatory to implement. + * ------------------------------------------------------------- + */ + + +/* + * pine_tcptimeout - C-client callback to handle tcp-related timeouts. + */ +long +pine_tcptimeout(long int elapsed, long int sincelast, char *host) +{ + long rv = 1L; /* keep trying by default */ + unsigned long ch; + +#ifdef DEBUG + dprint((1, "tcptimeout: waited %s seconds\n", + long2string(elapsed))); + if(debugfile) + fflush(debugfile); +#endif + +#ifdef _WINDOWS + mswin_killsplash(); +#endif + + if(ps_global->noshow_timeout) + return(rv); + + if(!ps_global->ttyo) + return(pine_tcptimeout_noscreen(elapsed, sincelast, host)); + + suspend_busy_cue(); + + /* + * Prompt after a minute (since by then things are probably really bad) + * A prompt timeout means "keep trying"... + */ + if(elapsed >= (long)ps_global->tcp_query_timeout){ + int clear_inverse; + + ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)); + if((clear_inverse = !InverseState()) != 0) + StartInverse(); + + Writechar(BELL, 0); + + PutLine2(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global), 0, + _("No reply in %s seconds from server %s. Break connection?"), + long2string(elapsed), host); + CleartoEOLN(); + fflush(stdout); + flush_input(); + ch = read_char(7); + if(ch == 'y' || ch == 'Y'){ + ps_global->user_says_cancel = 1; + rv = 0L; + } + + if(clear_inverse) + EndInverse(); + + ClearLine(ps_global->ttyo->screen_rows - FOOTER_ROWS(ps_global)); + } + + if(rv == 1L){ /* just warn 'em something's up */ + q_status_message2(SM_ORDER, 0, 0, + _("No reply in %s seconds from server %s. Still Waiting..."), + long2string(elapsed), host); + flush_status_messages(0); /* make sure it's seen */ + } + + mark_status_dirty(); /* make sure it get's cleared */ + + resume_busy_cue((rv == 1) ? 3 : 0); + + return(rv); +} + +QUOTALIST *pine_quotalist_copy (QUOTALIST *pquota) +{ + QUOTALIST *cquota = NULL; + + if(pquota){ + cquota = mail_newquotalist(); + if (pquota->name && *pquota->name) + cquota->name = cpystr(pquota->name); + cquota->usage = pquota->usage; + cquota->limit = pquota->limit; + if (pquota->next) + cquota->next = pine_quotalist_copy(pquota->next); + } + return cquota; +} + + +/* c-client callback to handle quota */ + +void +pine_parse_quota (MAILSTREAM *stream, unsigned char *msg, QUOTALIST *pquota) +{ + ps_global->quota = pine_quotalist_copy (pquota); +} + +/* + * C-client callback to handle SSL/TLS certificate validation failures + * + * Returning 0 means error becomes fatal + * Non-zero means certificate problem is ignored and SSL session is + * established + * + * We remember the answer and won't re-ask for subsequent open attempts to + * the same hostname. + */ +long +pine_sslcertquery(char *reason, char *host, char *cert) +{ + char tmp[500]; + char *unknown = "<unknown>"; + long rv = 0L; + STRLIST_S hostlist; + int ok_novalidate = 0, warned = 0; + + dprint((1, "sslcertificatequery: host=%s reason=%s cert=%s\n", + host ? host : "?", reason ? reason : "?", + cert ? cert : "?")); + + hostlist.name = host ? host : ""; + hostlist.next = NULL; + + /* + * See if we've been asked about this host before. + */ + if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){ + /* we were asked before, did we say Yes? */ + if(ok_novalidate) + rv++; + + if(rv){ + dprint((5, + "sslcertificatequery: approved automatically\n")); + return(rv); + } + + dprint((1, "sslcertificatequery: we were asked before and said No, so ask again\n")); + } + + if(ps_global->ttyo){ + SCROLL_S sargs; + STORE_S *in_store, *out_store; + gf_io_t pc, gc; + HANDLE_S *handles = NULL; + int the_answer = 'n'; + + if(!(in_store = so_get(CharStar, NULL, EDIT_ACCESS)) || + !(out_store = so_get(CharStar, NULL, EDIT_ACCESS))) + goto try_wantto; + + so_puts(in_store, "<HTML><P>"); + so_puts(in_store, _("There was a failure validating the SSL/TLS certificate for the server")); + + so_puts(in_store, "<P><CENTER>"); + so_puts(in_store, host ? host : unknown); + so_puts(in_store, "</CENTER>"); + + so_puts(in_store, "<P>"); + 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, "<P><CENTER>"); + snprintf(tmp, sizeof(tmp), "%s (<A HREF=\"X-Alpine-Cert:\">details</A>)", + reason ? reason : unknown); + tmp[sizeof(tmp)-1] = '\0'; + + so_puts(in_store, tmp); + so_puts(in_store, "</CENTER>"); + + so_puts(in_store, "<P>"); + 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, "<P>"); + 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, "<P><CENTER>"); + so_puts(in_store, "/novalidate-cert"); + so_puts(in_store, "</CENTER>"); + + so_puts(in_store, "<P>"); + 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, "<P><CENTER>"); + so_puts(in_store, host ? host : unknown); + so_puts(in_store, "</CENTER>"); + + so_puts(in_store, "<P>"); + so_puts(in_store, _("in your configuration, replace those characters with")); + + so_puts(in_store, "<P><CENTER>"); + so_puts(in_store, host ? host : unknown); + so_puts(in_store, "/novalidate-cert"); + so_puts(in_store, "</CENTER>"); + + so_puts(in_store, "<P>"); + 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 : "<unknown>"; + char *rsn = reason ? reason : "<unknown>"; + char *notls = "/notls"; + STRLIST_S hostlist; + int ok_novalidate = 0, warned = 0; + + + dprint((1, "sslfailure: host=%s reason=%s\n", + hst ? hst : "?", + rsn ? rsn : "?")); + + if(flags & NET_SILENT) + return; + + hostlist.name = host ? host : ""; + hostlist.next = NULL; + + /* + * See if we've been told about this host before. + */ + if(imap_get_ssl(cert_failure_list, &hostlist, &ok_novalidate, &warned)){ + /* we were told already */ + if(warned){ + snprintf(buf, sizeof(buf), _("SSL/TLS failure for %s: %s"), hst, rsn); + buf[sizeof(buf)-1] = '\0'; + mm_log(buf, ERROR); + return; + } + } + + cols = ps_global->ttyo ? ps_global->ttyo->screen_cols : 80; + cols--; + + if(!(store = so_get(CharStar, NULL, EDIT_ACCESS))) + return; + + strncpy(buf, _("There was an SSL/TLS failure for the server"), sizeof(buf)); + folded = fold(buf, cols, cols, "", "", FLD_NONE); + so_puts(store, folded); + fs_give((void **)&folded); + so_puts(store, "\n"); + + if((len=strlen(hst)) <= cols){ + if((indent=((cols-len)/2)) > 0) + so_puts(store, repeat_char(indent, SPACE)); + + so_puts(store, hst); + so_puts(store, "\n"); + } + else{ + strncpy(buf, hst, sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + folded = fold(buf, cols, cols, "", "", FLD_NONE); + so_puts(store, folded); + fs_give((void **)&folded); + } + + so_puts(store, "\n"); + + strncpy(buf, _("The reason for the failure was"), sizeof(buf)); + folded = fold(buf, cols, cols, "", "", FLD_NONE); + so_puts(store, folded); + fs_give((void **)&folded); + so_puts(store, "\n"); + + if((len=strlen(rsn)) <= cols){ + if((indent=((cols-len)/2)) > 0) + so_puts(store, repeat_char(indent, SPACE)); + + so_puts(store, rsn); + so_puts(store, "\n"); + } + else{ + strncpy(buf, rsn, sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + folded = fold(buf, cols, cols, "", "", FLD_NONE); + so_puts(store, folded); + fs_give((void **)&folded); + } + + so_puts(store, "\n"); + + 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)); + folded = fold(buf, cols, cols, "", "", FLD_NONE); + so_puts(store, folded); + fs_give((void **)&folded); + so_puts(store, "\n"); + + if((len=strlen(notls)) <= cols){ + if((indent=((cols-len)/2)) > 0) + so_puts(store, repeat_char(indent, SPACE)); + + so_puts(store, notls); + so_puts(store, "\n"); + } + else{ + strncpy(buf, notls, sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + folded = fold(buf, cols, cols, "", "", FLD_NONE); + so_puts(store, folded); + fs_give((void **)&folded); + } + + so_puts(store, "\n"); + + strncpy(buf, _("to the name of the mail server you are attempting to access. In other words, wherever you see the characters"), + sizeof(buf)); + folded = fold(buf, cols, cols, "", "", FLD_NONE); + so_puts(store, folded); + fs_give((void **)&folded); + so_puts(store, "\n"); + + if((len=strlen(hst)) <= cols){ + if((indent=((cols-len)/2)) > 0) + so_puts(store, repeat_char(indent, SPACE)); + + so_puts(store, hst); + so_puts(store, "\n"); + } + else{ + strncpy(buf, hst, sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + folded = fold(buf, cols, cols, "", "", FLD_NONE); + so_puts(store, folded); + fs_give((void **)&folded); + } + + so_puts(store, "\n"); + + strncpy(buf, _("in your configuration, replace those characters with"), + sizeof(buf)); + folded = fold(buf, cols, cols, "", "", FLD_NONE); + so_puts(store, folded); + fs_give((void **)&folded); + so_puts(store, "\n"); + + snprintf(buf2, sizeof(buf2), "%s%s", hst, notls); + buf2[sizeof(buf2)-1] = '\0'; + if((len=strlen(buf2)) <= cols){ + if((indent=((cols-len)/2)) > 0) + so_puts(store, repeat_char(indent, SPACE)); + + so_puts(store, buf2); + so_puts(store, "\n"); + } + else{ + strncpy(buf, buf2, sizeof(buf)); + buf[sizeof(buf)-1] = '\0'; + folded = fold(buf, cols, cols, "", "", FLD_NONE); + so_puts(store, folded); + fs_give((void **)&folded); + } + + so_puts(store, "\n"); + + if(ps_global->ttyo){ + strncpy(buf, _("Type RETURN to continue."), sizeof(buf)); + folded = fold(buf, cols, cols, "", "", FLD_NONE); + so_puts(store, folded); + fs_give((void **)&folded); + } + + memset(&sargs, 0, sizeof(SCROLL_S)); + sargs.text.text = so_text(store); + sargs.text.src = CharStar; + sargs.text.desc = _("help text"); + sargs.bar.title = _("SSL/TLS FAILURE"); + sargs.proc.tool = answer_cert_failure; + sargs.proc.data.p = (void *)&the_answer; + sargs.keys.menu = &ans_certfail_keymenu; + setbitmap(sargs.keys.bitmap); + /* don't want to re-enter c-client */ + sargs.quell_newmail = 1; + sargs.help.text = h_tls_failure; + sargs.help.title = _("HELP FOR TLS/SSL FAILURE"); + + if(ps_global->ttyo) + scrolltool(&sargs); + else{ + char **q, **qp; + char *p; + unsigned char c; + int cnt = 0; + + /* + * The screen isn't initialized yet, which should mean that this + * is the result of a -p argument. Display_args_err knows how to deal + * with the uninitialized screen, so we mess with the data to get it + * in shape for display_args_err. This is pretty hacky. + */ + + so_seek(store, 0L, 0); /* rewind */ + /* count the lines */ + while(so_readc(&c, store)) + if(c == '\n') + cnt++; + + qp = q = (char **)fs_get((cnt+1) * sizeof(char *)); + memset(q, 0, (cnt+1) * sizeof(char *)); + + so_seek(store, 0L, 0); /* rewind */ + p = buf; + while(so_readc(&c, store)){ + if(c == '\n'){ + *p = '\0'; + *qp++ = cpystr(buf); + p = buf; + } + else + *p++ = c; + } + + display_args_err(NULL, q, 0); + free_list_array(&q); + } + + ps_global->mangled_screen = 1; + ps_global->painted_body_on_startup = 0; + ps_global->painted_footer_on_startup = 0; + so_give(&store); + + imap_set_passwd(&cert_failure_list, "", "", &hostlist, 0, ok_novalidate, 1); +} + + +int +answer_cert_failure(int cmd, MSGNO_S *msgmap, SCROLL_S *sparms) +{ + int rv = 1; + + ps_global->next_screen = SCREEN_FUN_NULL; + + switch(cmd){ + case MC_YES : + *(int *)(sparms->proc.data.p) = 'y'; + break; + + case MC_NO : + *(int *)(sparms->proc.data.p) = 'n'; + break; + + default: + panic("Unexpected command in answer_cert_failure"); + break; + } + + return(rv); +} + + +/*---------------------------------------------------------------------- + This can be used to prevent the flickering of the check_cue char + caused by numerous (5000+) fetches by c-client. Right now, the only + practical use found is newsgroup subsciption. + + check_cue_display will check if this global is set, and won't clear + the check_cue_char if set. + ----*/ +void +set_read_predicted(int i) +{ + ps_global->read_predicted = i==1; +#ifdef _WINDOWS + if(!i && F_ON(F_SHOW_DELAY_CUE, ps_global)) + check_cue_display(" "); +#endif +} + + +/*---------------------------------------------------------------------- + Exported method to retrieve logged in user name associated with stream + + Args: host -- host to find associated login name with. + + Result: + ----*/ +void * +pine_block_notify(int reason, void *data) +{ + switch(reason){ + case BLOCK_SENSITIVE: /* sensitive code, disallow alarms */ + break; + + case BLOCK_NONSENSITIVE: /* non-sensitive code, allow alarms */ + break; + + case BLOCK_TCPWRITE: /* blocked on TCP write */ + case BLOCK_FILELOCK: /* blocked on file locking */ +#ifdef _WINDOWS + if(F_ON(F_SHOW_DELAY_CUE, ps_global)) + check_cue_display(">"); + + mswin_setcursor(MSWIN_CURSOR_BUSY); +#endif + break; + + case BLOCK_DNSLOOKUP: /* blocked on DNS lookup */ + case BLOCK_TCPOPEN: /* blocked on TCP open */ + case BLOCK_TCPREAD: /* blocked on TCP read */ + case BLOCK_TCPCLOSE: /* blocked on TCP close */ +#ifdef _WINDOWS + if(F_ON(F_SHOW_DELAY_CUE, ps_global)) + check_cue_display("<"); + + mswin_setcursor(MSWIN_CURSOR_BUSY); +#endif + break; + + default : + case BLOCK_NONE: /* not blocked */ +#ifdef _WINDOWS + if(F_ON(F_SHOW_DELAY_CUE, ps_global)) + check_cue_display(" "); +#endif + break; + + } + + return(NULL); +} + + +void +mm_expunged_current(long unsigned int rawno) +{ + /* expunged something we're viewing? */ + if(!ps_global->expunge_in_progress + && (mn_is_cur(ps_global->msgmap, mn_raw2m(ps_global->msgmap, (long) rawno)) + && (ps_global->prev_screen == mail_view_screen + || ps_global->prev_screen == attachment_screen))){ + ps_global->next_screen = mail_index_screen; + q_status_message(SM_ORDER | SM_DING , 3, 3, + "Message you were viewing is gone!"); + } +} + + +#ifdef PASSFILE + +/* + * Specific functions to support caching username/passwd/host + * triples on disk for use from one session to the next... + */ + +#define FIRSTCH 0x20 +#define LASTCH 0x7e +#define TABSZ (LASTCH - FIRSTCH + 1) + +static int xlate_key; + + +/* + * xlate_in() - xlate_in the given character + */ +char +xlate_in(int c) +{ + register int eti; + + eti = xlate_key; + if((c >= FIRSTCH) && (c <= LASTCH)){ + eti += (c - FIRSTCH); + eti -= (eti >= 2*TABSZ) ? 2*TABSZ : (eti >= TABSZ) ? TABSZ : 0; + return((xlate_key = eti) + FIRSTCH); + } + else + return(c); +} + + +/* + * xlate_out() - xlate_out the given character + */ +char +xlate_out(char c) +{ + register int dti; + register int xch; + + if((c >= FIRSTCH) && (c <= LASTCH)){ + xch = c - (dti = xlate_key); + xch += (xch < FIRSTCH-TABSZ) ? 2*TABSZ : (xch < FIRSTCH) ? TABSZ : 0; + dti = (xch - FIRSTCH) + dti; + dti -= (dti >= 2*TABSZ) ? 2*TABSZ : (dti >= TABSZ) ? TABSZ : 0; + xlate_key = dti; + return(xch); + } + else + return(c); +} + + +char * +passfile_name(char *pinerc, char *path, size_t len) +{ + struct stat sbuf; + char *p = NULL; + int i, j; + + if(!path || !((pinerc && pinerc[0]) || ps_global->passfile)) + return(NULL); + + if(ps_global->passfile) + strncpy(path, ps_global->passfile, len-1); + else{ + if((p = last_cmpnt(pinerc)) && *(p-1) && *(p-1) != PASSFILE[0]) + for(i = 0; pinerc < p && i < len; pinerc++, i++) + path[i] = *pinerc; + else + i = 0; + + for(j = 0; (i < len) && (path[i] = PASSFILE[j]); i++, j++) + ; + + } + + path[len-1] = '\0'; + + dprint((9, "Looking for passfile \"%s\"\n", + path ? path : "?")); + +#if defined(DOS) || defined(OS2) + return((our_stat(path, &sbuf) == 0 + && ((sbuf.st_mode & S_IFMT) == S_IFREG)) + ? path : NULL); +#else + /* First, make sure it's ours and not sym-linked */ + if(our_lstat(path, &sbuf) == 0 + && ((sbuf.st_mode & S_IFMT) == S_IFREG) + && sbuf.st_uid == getuid()){ + /* if too liberal permissions, fix them */ + if((sbuf.st_mode & 077) != 0) + if(our_chmod(path, sbuf.st_mode & ~077) != 0) + return(NULL); + + return(path); + } + else + return(NULL); +#endif +} + +#endif /* PASSFILE */ + + +#ifdef LOCAL_PASSWD_CACHE + + +int +line_get(char *tmp, size_t len, char **textp) +{ + char *s, c; + + tmp[0] = '\0'; + if (*textp == NULL) + return 0; + s = strchr(*textp, '\n'); + if(s != NULL){ + *s = '\0'; + if(*(s-1) == '\r') + *(s-1) = '\0'; + if(strlen(*textp) < len - 1) + strcpy(tmp, *textp); + else + return 0; + strcat(tmp, "\n"); + *textp = s+1; + } + else + return 0; + + return 1; +} +/* + * For UNIX: + * Passfile lines are + * + * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n + * + * In pine4.40 and before there was no orig_hostname, and there still isn't + * if it is the same as hostname. + * + * else for WINDOWS: + * Use Windows credentials. The TargetName of the credential is + * UWash_Alpine_<hostname:port>\tuser\taltflag + * and the blob consists of + * passwd\torighost (if different from host) + * + * We don't use anything fancy we just copy out all the credentials which + * begin with TNAME and put them into our cache, so we don't lookup based + * on the TargetName or anything like that. That was so we could re-use + * the existing code and so that orighost data could be easily used. + */ +int +read_passfile(pinerc, l) + char *pinerc; + MMLOGIN_S **l; +{ +#ifdef WINCRED +# if (WINCRED > 0) + LPCTSTR lfilter = TEXT(TNAMESTAR); + DWORD count, k; + PCREDENTIAL *pcred; + char *tmp, *blob, *target = NULL; + char *host, *user, *sflags, *passwd, *orighost; + char *ui[5]; + int i, j; + + if(using_passfile == 0) + return(using_passfile); + + if(!g_CredInited){ + if(init_wincred_funcs() != 1){ + using_passfile = 0; + return(using_passfile); + } + } + + dprint((9, "read_passfile\n")); + + using_passfile = 1; + + if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){ + if(pcred){ + for(k = 0; k < count; k++){ + + host = user = sflags = passwd = orighost = NULL; + ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL; + + target = lptstr_to_utf8(pcred[k]->TargetName); + tmp = srchstr(target, TNAME); + + if(tmp){ + tmp += strlen(TNAME); + for(i = 0, j = 0; tmp[i] && j < 3; j++){ + for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++) + ; /* find end of data */ + + if(tmp[i]) + tmp[i++] = '\0'; /* tie off data */ + } + + host = ui[0]; + user = ui[1]; + sflags = ui[2]; + } + + blob = (char *) pcred[k]->CredentialBlob; + if(blob){ + for(i = 0, j = 3; blob[i] && j < 5; j++){ + for(ui[j] = &blob[i]; blob[i] && blob[i] != '\t'; i++) + ; /* find end of data */ + + if(blob[i]) + blob[i++] = '\0'; /* tie off data */ + } + + passwd = ui[3]; + orighost = ui[4]; + } + + if(passwd && host && user){ /* valid field? */ + STRLIST_S hostlist[2]; + int flags = sflags ? atoi(sflags) : 0; + + hostlist[0].name = host; + if(orighost){ + hostlist[0].next = &hostlist[1]; + hostlist[1].name = orighost; + hostlist[1].next = NULL; + } + else{ + hostlist[0].next = NULL; + } + + imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0); + } + + if(target) + fs_give((void **) &target); + } + + g_CredFree((PVOID) pcred); + } + } + + return(1); + +# else /* old windows */ + using_passfile = 0; + return(0); +# endif + +#elif APPLEKEYCHAIN + + char target[MAILTMPLEN]; + char *tmp, *host, *user, *sflags, *passwd, *orighost; + char *ui[5]; + int i, j, k, rc; + SecKeychainAttributeList attrList; + SecKeychainSearchRef searchRef = NULL; + SecKeychainAttribute attrs[] = { + { kSecAccountItemAttr, strlen(TNAME), TNAME } + }; + + if(using_passfile == 0) + return(using_passfile); + + dprint((9, "read_passfile\n")); + + + /* search for only our items in the keychain */ + attrList.count = 1; + attrList.attr = attrs; + + using_passfile = 1; + if(!(rc=SecKeychainSearchCreateFromAttributes(NULL, + kSecGenericPasswordItemClass, + &attrList, + &searchRef))){ + dprint((10, "read_passfile: SecKeychainSearchCreate succeeded\n")); + if(searchRef){ + SecKeychainItemRef itemRef = NULL; + SecKeychainAttributeInfo info; + SecKeychainAttributeList *attrList = NULL; + UInt32 blength = 0; + char *blob = NULL; + char *blobcopy = NULL; /* NULL terminated copy */ + + UInt32 tags[] = {kSecAccountItemAttr, + kSecServiceItemAttr}; + UInt32 formats[] = {0,0}; + + dprint((10, "read_passfile: searchRef not NULL\n")); + info.count = 2; + info.tag = tags; + info.format = formats; + + /* + * Go through each item we found and put it + * into our list. + */ + while(!(rc=SecKeychainSearchCopyNext(searchRef, &itemRef)) && itemRef){ + dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n")); + rc = SecKeychainItemCopyAttributesAndData(itemRef, + &info, NULL, + &attrList, + &blength, + (void **) &blob); + if(rc == 0 && attrList){ + dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData succeeded, count=%d\n", attrList->count)); + + blobcopy = (char *) fs_get((blength + 1) * sizeof(char)); + strncpy(blobcopy, (char *) blob, blength); + blobcopy[blength] = '\0'; + + /* + * I'm not real clear on how this works. It seems to be + * necessary to combine the attributes from two passes + * (attrList->count == 2) in order to get the full set + * of attributes we inserted into the keychain in the + * first place. So, we reset host...orighost outside of + * the following for loop, not inside. + */ + host = user = sflags = passwd = orighost = NULL; + ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL; + + for(k = 0; k < attrList->count; k++){ + + if(attrList->attr[k].length){ + strncpy(target, + (char *) attrList->attr[k].data, + MIN(attrList->attr[k].length,sizeof(target))); + target[MIN(attrList->attr[k].length,sizeof(target)-1)] = '\0'; + } + + tmp = target; + for(i = 0, j = 0; tmp[i] && j < 3; j++){ + for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++) + ; /* find end of data */ + + if(tmp[i]) + tmp[i++] = '\0'; /* tie off data */ + } + + if(ui[0]) + host = ui[0]; + + if(ui[1]) + user = ui[1]; + + if(ui[2]) + sflags = ui[2]; + + for(i = 0, j = 3; blobcopy[i] && j < 5; j++){ + for(ui[j] = &blobcopy[i]; blobcopy[i] && blobcopy[i] != '\t'; i++) + ; /* find end of data */ + + if(blobcopy[i]) + blobcopy[i++] = '\0'; /* tie off data */ + } + + if(ui[3]) + passwd = ui[3]; + + if(ui[4]) + orighost = ui[4]; + + 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:"")); + } + + if(passwd && host && user){ /* valid field? */ + STRLIST_S hostlist[2]; + int flags = sflags ? atoi(sflags) : 0; + + hostlist[0].name = host; + if(orighost){ + hostlist[0].next = &hostlist[1]; + hostlist[1].name = orighost; + hostlist[1].next = NULL; + } + else{ + hostlist[0].next = NULL; + } + + imap_set_passwd(l, passwd, user, hostlist, flags & 0x01, 0, 0); + } + + if(blobcopy) + fs_give((void **) & blobcopy); + + SecKeychainItemFreeAttributesAndData(attrList, (void *) blob); + } + else{ + using_passfile = 0; + dprint((10, "read_passfile: SecKeychainItemCopyAttributesAndData failed, rc=%d\n", rc)); + } + + CFRelease(itemRef); + itemRef = NULL; + } + + CFRelease(searchRef); + } + else{ + using_passfile = 0; + dprint((10, "read_passfile: searchRef NULL\n")); + } + } + else{ + using_passfile = 0; + dprint((10, "read_passfile: SecKeychainSearchCreateFromAttributes failed, rc=%d\n", rc)); + } + + return(using_passfile); + +#else /* PASSFILE */ + + char tmp[MAILTMPLEN], *ui[5]; + int i, j, n; +#ifdef SMIME + char tmp2[MAILTMPLEN]; + char *text = NULL, *text2 = NULL; + int encrypted = 0; +#endif /* SMIME */ + FILE *fp; + + if(using_passfile == 0) + return(using_passfile); + + dprint((9, "read_passfile\n")); + + /* if there's no password to read, bag it!! */ + if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "rb"))){ + using_passfile = 0; + return(using_passfile); + }; + +#ifdef SMIME + tmp2[0] = '\0'; + fgets(tmp2, sizeof(tmp2), fp); + if(strcmp(tmp2, "-----BEGIN PKCS7-----\n")){ + fclose(fp); + if(encrypt_file(tmp, NULL)) + encrypted++; + } + else{ + fclose(fp); + encrypted++; + } + + /* + * if password file is encrypted we attemtp to decrypt. We ask the + * user for the password to unlock the password file. If the user + * enters the password and it unlocks the file, use it and keep saving + * passwords in it. If the user enters the wrong passwords and does + * not unlock it, we will not see that here, but in decrypt_file, so + * the only other possibility is that the user cancels. In that case + * we will see i == -1. In that case, we will let the user attempt + * manual login to the server they want to login, but passwords will + * not be saved so that the password file will not be saved + * unencrypted and rewritten again. + */ + if(encrypted){ + text = text2 = decrypt_file(tmp, &i); + switch(i){ + case 1 : save_password = 1; + break; + + case -1: save_password = 0; + break; + + default: break; + } + } + else + fp = our_fopen(tmp, "rb"); /* reopen to read data */ +#endif /* SMIME */ + + using_passfile = 1; +#ifdef SMIME + for(n = 0; encrypted ? line_get(tmp, sizeof(tmp), &text2) + : (fgets(tmp, sizeof(tmp), fp) != NULL); n++){ +#else /* SMIME */ + for(n = 0; fgets(tmp, sizeof(tmp), fp); n++){ +#endif /* SMIME */ + /*** do any necessary DEcryption here ***/ + xlate_key = n; + for(i = 0; tmp[i]; i++) + tmp[i] = xlate_out(tmp[i]); + + if(i && tmp[i-1] == '\n') + tmp[i-1] = '\0'; /* blast '\n' */ + + dprint((10, "read_passfile: %s\n", tmp ? tmp : "?")); + ui[0] = ui[1] = ui[2] = ui[3] = ui[4] = NULL; + for(i = 0, j = 0; tmp[i] && j < 5; j++){ + for(ui[j] = &tmp[i]; tmp[i] && tmp[i] != '\t'; i++) + ; /* find end of data */ + + if(tmp[i]) + tmp[i++] = '\0'; /* tie off data */ + } + + dprint((10, "read_passfile: calling imap_set_passwd\n")); + if(ui[0] && ui[1] && ui[2]){ /* valid field? */ + STRLIST_S hostlist[2]; + int flags = ui[3] ? atoi(ui[3]) : 0; + + hostlist[0].name = ui[2]; + if(ui[4]){ + hostlist[0].next = &hostlist[1]; + hostlist[1].name = ui[4]; + hostlist[1].next = NULL; + } + else{ + hostlist[0].next = NULL; + } + + imap_set_passwd(l, ui[0], ui[1], hostlist, flags & 0x01, 0, 0); + } + } + +#ifdef SMIME + if (text) fs_give((void **)&text); +#else /* SMIME */ + fclose(fp); +#endif /* SMIME */ + return(1); +#endif /* PASSFILE */ +} + + + +void +write_passfile(pinerc, l) + char *pinerc; + MMLOGIN_S *l; +{ +#ifdef WINCRED +# if (WINCRED > 0) + char target[MAILTMPLEN]; + char blob[MAILTMPLEN]; + CREDENTIAL cred; + LPTSTR ltarget = 0; + + if(using_passfile == 0) + return; + + dprint((9, "write_passfile\n")); + + for(; l; l = l->next){ + snprintf(target, sizeof(target), "%s%s\t%s\t%d", + TNAME, + (l->hosts && l->hosts->name) ? l->hosts->name : "", + l->user ? l->user : "", + l->altflag); + ltarget = utf8_to_lptstr((LPSTR) target); + + if(ltarget){ + snprintf(blob, sizeof(blob), "%s%s%s", + l->passwd ? l->passwd : "", + (l->hosts && l->hosts->next && l->hosts->next->name) + ? "\t" : "", + (l->hosts && l->hosts->next && l->hosts->next->name) + ? l->hosts->next->name : ""); + memset((void *) &cred, 0, sizeof(cred)); + cred.Flags = 0; + cred.Type = CRED_TYPE_GENERIC; + cred.TargetName = ltarget; + cred.CredentialBlobSize = strlen(blob)+1; + cred.CredentialBlob = (LPBYTE) &blob; + cred.Persist = CRED_PERSIST_ENTERPRISE; + g_CredWriteW(&cred, 0); + + fs_give((void **) <arget); + } + } + #endif /* WINCRED > 0 */ + +#elif APPLEKEYCHAIN + + int rc; + char target[MAILTMPLEN]; + char blob[MAILTMPLEN]; + SecKeychainItemRef itemRef = NULL; + + if(using_passfile == 0) + return; + + dprint((9, "write_passfile\n")); + + for(; l; l = l->next){ + snprintf(target, sizeof(target), "%s\t%s\t%d", + (l->hosts && l->hosts->name) ? l->hosts->name : "", + l->user ? l->user : "", + l->altflag); + + snprintf(blob, sizeof(blob), "%s%s%s", + l->passwd ? l->passwd : "", + (l->hosts && l->hosts->next && l->hosts->next->name) + ? "\t" : "", + (l->hosts && l->hosts->next && l->hosts->next->name) + ? l->hosts->next->name : ""); + + dprint((10, "write_passfile: SecKeychainAddGenericPassword(NULL, %d, %s, %d, %s, %d, %s, NULL)\n", strlen(target), target, strlen(TNAME), TNAME, strlen(blob), blob)); + + rc = SecKeychainAddGenericPassword(NULL, + strlen(target), target, + strlen(TNAME), TNAME, + strlen(blob), blob, + NULL); + if(rc==0){ + dprint((10, "write_passfile: SecKeychainAddGenericPassword succeeded\n")); + } + else{ + dprint((10, "write_passfile: SecKeychainAddGenericPassword returned rc=%d\n", rc)); + } + + if(rc == errSecDuplicateItem){ + /* fix existing entry */ + dprint((10, "write_passfile: SecKeychainAddGenericPassword found existing entry\n")); + itemRef = NULL; + if(!(rc=SecKeychainFindGenericPassword(NULL, + strlen(target), target, + strlen(TNAME), TNAME, + NULL, NULL, + &itemRef)) && itemRef){ + + rc = SecKeychainItemModifyAttributesAndData(itemRef, NULL, strlen(blob), blob); + if(!rc){ + dprint((10, "write_passfile: SecKeychainItemModifyAttributesAndData returned rc=%d\n", rc)); + } + } + else{ + dprint((10, "write_passfile: SecKeychainFindGenericPassword returned rc=%d\n", rc)); + } + } + } + +#else /* PASSFILE */ + + char tmp[MAILTMPLEN]; + int i, n; + FILE *fp; +#ifdef SMIME + char *text = NULL, tmp2[MAILTMPLEN]; + int len = 0; +#endif + + if(using_passfile == 0) + return; + + dprint((9, "write_passfile\n")); + + /* if there's no passfile to read, bag it!! */ + if(!passfile_name(pinerc, tmp, sizeof(tmp)) || !(fp = our_fopen(tmp, "wb"))){ + using_passfile = 0; + return; + } + +#ifdef SMIME + strcpy(tmp2, tmp); +#endif /* SMIME */ + + for(n = 0; l; l = l->next, n++){ + /*** do any necessary ENcryption here ***/ + snprintf(tmp, sizeof(tmp), "%s\t%s\t%s\t%d%s%s\n", l->passwd, l->user, + l->hosts->name, l->altflag, + (l->hosts->next && l->hosts->next->name) ? "\t" : "", + (l->hosts->next && l->hosts->next->name) ? l->hosts->next->name + : ""); + dprint((10, "write_passfile: %s", tmp ? tmp : "?")); + xlate_key = n; + for(i = 0; tmp[i]; i++) + tmp[i] = xlate_in(tmp[i]); + +#ifdef SMIME + if(len == 0){ + len = strlen(tmp) + 1; + text = fs_get(len*sizeof(char)); + *text = '\0'; + } + if(strlen(text) + strlen(tmp) > len){ + len = strlen(text) + strlen(tmp) + 1; + fs_resize((void **)&text, len*sizeof(char)); + } + strcat(text, tmp); +#else /* SMIME */ + fputs(tmp, fp); +#endif /* SMIME */ + } + + fclose(fp); +#ifdef SMIME + if(encrypt_file(tmp2, text) == 0){ + if((fp = our_fopen(tmp2, "wb")) != NULL){ + fputs(text, fp); + fclose(fp); + } + } + fs_give((void **)&text); +#endif /* SMIME */ +#endif /* PASSFILE */ +} + +#endif /* LOCAL_PASSWD_CACHE */ + + +#if (WINCRED > 0) +void +erase_windows_credentials(void) +{ + LPCTSTR lfilter = TEXT(TNAMESTAR); + DWORD count, k; + PCREDENTIAL *pcred; + + if(!g_CredInited){ + if(init_wincred_funcs() != 1) + return; + } + + if(g_CredEnumerateW(lfilter, 0, &count, &pcred)){ + if(pcred){ + for(k = 0; k < count; k++) + g_CredDeleteW(pcred[k]->TargetName, CRED_TYPE_GENERIC, 0); + + g_CredFree((PVOID) pcred); + } + } +} + +void +ask_erase_credentials(void) +{ + if(want_to(_("Erase previously preserved passwords"), 'y', 'x', NO_HELP, WT_NORM) == 'y'){ + erase_windows_credentials(); + q_status_message(SM_ORDER, 3, 3, "All preserved passwords have been erased"); + } + else + q_status_message(SM_ORDER, 3, 3, "Previously preserved passwords will not be erased"); +} + +#endif /* WINCRED */ + + +#ifdef LOCAL_PASSWD_CACHE + +/* + * get_passfile_passwd - return the password contained in the special passord + * cache. The file is assumed to be in the same directory + * as the pinerc with the name defined above. + */ +int +get_passfile_passwd(pinerc, passwd, user, hostlist, altflag) + char *pinerc, *passwd, *user; + STRLIST_S *hostlist; + int altflag; +{ + dprint((10, "get_passfile_passwd\n")); + return((passfile_cache || read_passfile(pinerc, &passfile_cache)) + ? imap_get_passwd(passfile_cache, passwd, + user, hostlist, altflag) + : 0); +} + +int +is_using_passfile() +{ + return(using_passfile == 1); +} + +/* + * Just trying to guess the username the user might want to use on this + * host, the user will confirm. + */ +char * +get_passfile_user(pinerc, hostlist) + char *pinerc; + STRLIST_S *hostlist; +{ + return((passfile_cache || read_passfile(pinerc, &passfile_cache)) + ? imap_get_user(passfile_cache, hostlist) + : NULL); +} + + +int +preserve_prompt(void) +{ +#ifdef WINCRED +# if (WINCRED > 0) + /* + * This prompt was going to be able to be turned on and off via a registry + * setting controlled from the config menu. We decided to always use the + * dialog for login, and there the prompt is unobtrusive enough to always + * be in there. As a result, windows should never reach this, but now + * OS X somewhat uses the behavior just described. + */ + if(mswin_store_pass_prompt() + && (want_to(_("Preserve password for next login"), + 'y', 'x', NO_HELP, WT_NORM) + == 'y')) + return(1); + else + return(0); +# else + return(0); +# endif + +#elif APPLEKEYCHAIN + + int rc; + if(rc = macos_store_pass_prompt()){ + if(want_to(_("Preserve password for next login"), + 'y', 'x', NO_HELP, WT_NORM) + == 'y'){ + if(rc == -1){ + macos_set_store_pass_prompt(1); + q_status_message(SM_ORDER, 4, 4, + _("Stop \"Preserve passwords?\" prompts by deleting Alpine Keychain entry")); + } + return(1); + } + else if(rc == -1){ + macos_set_store_pass_prompt(0); + q_status_message(SM_ORDER, 4, 4, + _("Restart \"Preserve passwords?\" prompts by deleting Alpine Keychain entry")); + } + return(0); + } + return(0); +#else /* PASSFILE */ + return(want_to(_("Preserve password on DISK for next login"), + 'y', 'x', NO_HELP, WT_NORM) + == 'y'); +#endif /* PASSFILE */ +} + +#endif /* LOCAL_PASSWD_CACHE */ + + +#ifdef APPLEKEYCHAIN + +/* + * Returns: + * 1 if store pass prompt is set in the "registry" to on + * 0 if set to off + * -1 if not set to anything + */ +int +macos_store_pass_prompt(void) +{ + char *data = NULL; + UInt32 len = 0; + int rc = -1; + int val; + + if(storepassprompt == -1){ + if(!(rc=SecKeychainFindGenericPassword(NULL, 0, NULL, + strlen(TNAMEPROMPT), + TNAMEPROMPT, &len, + (void **) &data, NULL))){ + val = (len == 1 && data && data[0] == '1'); + } + } + + if(storepassprompt == -1 && !rc){ + if(val) + storepassprompt = 1; + else + storepassprompt = 0; + } + + return(storepassprompt); +} + + +void +macos_set_store_pass_prompt(int val) +{ + storepassprompt = val ? 1 : 0; + + SecKeychainAddGenericPassword(NULL, 0, NULL, strlen(TNAMEPROMPT), + TNAMEPROMPT, 1, val ? "1" : "0", NULL); +} + + +void +macos_erase_keychain(void) +{ + SecKeychainAttributeList attrList; + SecKeychainSearchRef searchRef = NULL; + SecKeychainAttribute attrs1[] = { + { kSecAccountItemAttr, strlen(TNAME), TNAME } + }; + SecKeychainAttribute attrs2[] = { + { kSecAccountItemAttr, strlen(TNAMEPROMPT), TNAMEPROMPT } + }; + + dprint((9, "macos_erase_keychain\n")); + + /* + * Seems like we ought to be able to combine attrs1 and attrs2 + * into a single array, but I couldn't get it to work. + */ + + /* search for only our items in the keychain */ + attrList.count = 1; + attrList.attr = attrs1; + + if(!SecKeychainSearchCreateFromAttributes(NULL, + kSecGenericPasswordItemClass, + &attrList, + &searchRef)){ + if(searchRef){ + SecKeychainItemRef itemRef = NULL; + + /* + * Go through each item we found and put it + * into our list. + */ + while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){ + dprint((10, "read_passfile: SecKeychainSearchCopyNext got one\n")); + SecKeychainItemDelete(itemRef); + CFRelease(itemRef); + } + + CFRelease(searchRef); + } + } + + attrList.count = 1; + attrList.attr = attrs2; + + if(!SecKeychainSearchCreateFromAttributes(NULL, + kSecGenericPasswordItemClass, + &attrList, + &searchRef)){ + if(searchRef){ + SecKeychainItemRef itemRef = NULL; + + /* + * Go through each item we found and put it + * into our list. + */ + while(!SecKeychainSearchCopyNext(searchRef, &itemRef) && itemRef){ + SecKeychainItemDelete(itemRef); + CFRelease(itemRef); + } + + CFRelease(searchRef); + } + } +} + +#endif /* APPLEKEYCHAIN */ + +#ifdef LOCAL_PASSWD_CACHE + +/* + * set_passfile_passwd - set the password file entry associated with + * cache. The file is assumed to be in the same directory + * as the pinerc with the name defined above. + * already_prompted: 0 not prompted + * 1 prompted, answered yes + * 2 prompted, answered no + */ +void +set_passfile_passwd(pinerc, passwd, user, hostlist, altflag, already_prompted) + char *pinerc, *passwd, *user; + STRLIST_S *hostlist; + int altflag, already_prompted; +{ + dprint((10, "set_passfile_passwd\n")); + if((passfile_cache || read_passfile(pinerc, &passfile_cache)) + && !ps_global->nowrite_password_cache + && ((already_prompted == 0 && preserve_prompt()) + || already_prompted == 1)){ + imap_set_passwd(&passfile_cache, passwd, user, hostlist, altflag, 0, 0); + write_passfile(pinerc, passfile_cache); + } +} + + +/* + * Passfile lines are + * + * passwd TAB user TAB hostname TAB flags [ TAB orig_hostname ] \n + * + * In pine4.40 and before there was no orig_hostname. + * This routine attempts to repair that. + */ +void +update_passfile_hostlist(pinerc, user, hostlist, altflag) + char *pinerc; + char *user; + STRLIST_S *hostlist; + int altflag; +{ +#ifdef WINCRED + return; +#else /* !WINCRED */ + MMLOGIN_S *l; + STRLIST_S *h1, *h2; + + for(l = passfile_cache; l; l = l->next) + if(imap_same_host(l->hosts, hostlist) + && *user + && !strcmp(user, l->user) + && l->altflag == altflag){ + break; + } + + if(l && l->hosts && hostlist && !l->hosts->next && hostlist->next + && hostlist->next->name + && !ps_global->nowrite_password_cache){ + l->hosts->next = new_strlist(hostlist->next->name); + write_passfile(pinerc, passfile_cache); + } +#endif /* !WINCRED */ +} + +#endif /* LOCAL_PASSWD_CACHE */ + + +#if (WINCRED > 0) +/* + * Load and init the WinCred structure. + * This gives us a way to skip the WinCred code + * if the dll doesn't exist. + */ +int +init_wincred_funcs(void) +{ + if(!g_CredInited) + { + HMODULE hmod; + + /* Assume the worst. */ + g_CredInited = -1; + + hmod = LoadLibrary(TEXT("advapi32.dll")); + if(hmod) + { + FARPROC fpCredWriteW; + FARPROC fpCredEnumerateW; + FARPROC fpCredDeleteW; + FARPROC fpCredFree; + + fpCredWriteW = GetProcAddress(hmod, "CredWriteW"); + fpCredEnumerateW = GetProcAddress(hmod, "CredEnumerateW"); + fpCredDeleteW = GetProcAddress(hmod, "CredDeleteW"); + fpCredFree = GetProcAddress(hmod, "CredFree"); + + if(fpCredWriteW && fpCredEnumerateW && fpCredDeleteW && fpCredFree) + { + g_CredWriteW = (CREDWRITEW *)fpCredWriteW; + g_CredEnumerateW = (CREDENUMERATEW *)fpCredEnumerateW; + g_CredDeleteW = (CREDDELETEW *)fpCredDeleteW; + g_CredFree = (CREDFREE *)fpCredFree; + + g_CredInited = 1; + } + } + + mswin_set_erasecreds_callback(ask_erase_credentials); + } + + return g_CredInited; +} + +#endif /* WINCRED */ |