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/send.c | |
download | alpine-094ca96844842928810f14844413109fc6cdd890.tar.xz |
Initial Alpine Version
Diffstat (limited to 'alpine/send.c')
-rw-r--r-- | alpine/send.c | 7155 |
1 files changed, 7155 insertions, 0 deletions
diff --git a/alpine/send.c b/alpine/send.c new file mode 100644 index 00000000..3d670f02 --- /dev/null +++ b/alpine/send.c @@ -0,0 +1,7155 @@ +#if !defined(lint) && !defined(DOS) +static char rcsid[] = "$Id: send.c 1142 2008-08-13 17:22:21Z hubert@u.washington.edu $"; +#endif + +/* + * ======================================================================== + * Copyright 2006-2008 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 + * + * ======================================================================== + */ + +/*====================================================================== + Functions for composing and sending mail + + ====*/ + + +#include "headers.h" +#include "send.h" +#include "status.h" +#include "mailview.h" +#include "mailindx.h" +#include "dispfilt.h" +#include "keymenu.h" +#include "folder.h" +#include "radio.h" +#include "addrbook.h" +#include "reply.h" +#include "titlebar.h" +#include "signal.h" +#include "mailcmd.h" +#include "roleconf.h" +#include "adrbkcmd.h" +#include "busy.h" +#include "../pith/debug.h" +#include "../pith/state.h" +#include "../pith/conf.h" +#include "../pith/flag.h" +#include "../pith/bldaddr.h" +#include "../pith/copyaddr.h" +#include "../pith/detach.h" +#include "../pith/mimedesc.h" +#include "../pith/pipe.h" +#include "../pith/addrstring.h" +#include "../pith/news.h" +#include "../pith/detoken.h" +#include "../pith/util.h" +#include "../pith/init.h" +#include "../pith/mailcmd.h" +#include "../pith/ablookup.h" +#include "../pith/reply.h" +#include "../pith/hist.h" +#include "../pith/list.h" +#include "../pith/icache.h" +#include "../pith/busy.h" +#include "../pith/mimetype.h" +#include "../pith/send.h" +#include "../pith/smime.h" + + +typedef struct body_particulars { + unsigned short type, encoding, had_csp; + char *subtype, *charset; + PARAMETER *parameter; +} BODY_PARTICULARS_S; + +#define PHONE_HOME_VERSION "-count" +#define PHONE_HOME_HOST "docserver.cac.washington.edu" + +/* + * macro to bind pico's headerentry pointer to PINEFIELD "extdata" hook + */ +#define HE(PF) ((struct headerentry *)((PF)->extdata)) + + +/* + * Internal Prototypes + */ +int redraft(MAILSTREAM **, ENVELOPE **, BODY **, char **, char **, REPLY_S **, + REDRAFT_POS_S **, PINEFIELD **, ACTION_S **, int); +int redraft_prompt(char *, char *, int); +int check_for_subject(METAENV *); +int check_for_fcc(char *); +void free_prompts(PINEFIELD *); +int postpone_prompt(void); +METAENV *pine_simple_send_header(ENVELOPE *, char **, char ***); +void call_mailer_file_result(char *, int); +void mark_address_failure_for_pico(METAENV *); +BODY_PARTICULARS_S + *save_body_particulars(BODY *); +void reset_body_particulars(BODY_PARTICULARS_S *, BODY *); +void free_body_particulars(BODY_PARTICULARS_S *); +long message_format_for_pico(long, int (*)(int)); +int send_exit_for_pico(struct headerentry *, void (*)(void), int, char **); +void new_thread_on_blank_subject(void); +char *choose_a_priority(char *); +int dont_flow_this_time(void); +int mime_type_for_pico(char *); +char *cancel_for_pico(void (*)(void)); +int filter_message_text(char *, ENVELOPE *, BODY *, STORE_S **, METAENV *); +void pine_send_newsgroup_name(char *, char*, size_t); +void outgoing2strings(METAENV *, BODY *, void **, PATMT **, int); +void strings2outgoing(METAENV *, BODY **, PATMT *, int); +void create_message_body_text(BODY *, int); +void set_body_size(BODY *); +int view_as_rich(char *, int); +int background_posting(int); +int valid_subject(char *, char **, char **,BUILDER_ARG *,int *); +int build_addr_lcc(char *, char **, char **, BUILDER_ARG *, int *); +int news_build(char *, char **, char **, BUILDER_ARG *, int *); +void news_build_busy(void); +#if defined(DOS) || defined(OS2) +int dos_valid_from(void); +#endif /* defined(DOS) || defined(OS2) */ + + +/* + * Pointer to buffer to hold pointers into pine data that's needed by pico. + */ +static PICO *pbf; + + +static char *g_rolenick = NULL; + + +static char *sending_filter_requested; +static char background_requested, flowing_requested; +static unsigned call_mailer_flags; +static char *priority_requested; + +/* local global to save busy_cue state */ +static int news_busy_cue = 0; + + +/* + * Various useful strings + */ +#define INTRPT_PMT \ + _("Continue INTERRUPTED composition (answering \"n\" won't erase it)") +#define PSTPND_PMT \ + _("Continue postponed composition (answering \"No\" won't erase it)") +#define FORM_PMT \ + _("Start composition from Form Letter Folder") +#define PSTPN_FORM_PMT \ + _("Save to Postponed or Form letter folder? ") +#define POST_PMT \ + _("Posted message may go to thousands of readers. Really post") +#define INTR_DEL_PMT \ + _("Deleted messages will be removed from folder after use. Proceed") + + +/* + * Macros to help sort out posting results + */ +#define P_MAIL_WIN 0x01 +#define P_MAIL_LOSE 0x02 +#define P_MAIL_BITS 0x03 +#define P_NEWS_WIN 0x04 +#define P_NEWS_LOSE 0x08 +#define P_NEWS_BITS 0x0C +#define P_FCC_WIN 0x10 +#define P_FCC_LOSE 0x20 +#define P_FCC_BITS 0x30 + + +#define COMPOSE_MAIL_TITLE "COMPOSE MESSAGE" + + +/* + * For check_for_subject and check_for_fcc + */ +#define CF_OK 0x1 +#define CF_MISSING 0x2 + + +/*---------------------------------------------------------------------- + Compose screen (not forward or reply). Set up envelope, call composer + + Args: pine_state -- The usual pine structure + + Little front end for the compose screen + ---*/ +void +compose_screen(struct pine *pine_state) +{ + void (*prev_screen)(struct pine *) = pine_state->prev_screen, + (*redraw)(void) = pine_state->redrawer; + + pine_state->redrawer = NULL; + ps_global->next_screen = SCREEN_FUN_NULL; + mailcap_free(); /* free resources we won't be using for a while */ + compose_mail(NULL, NULL, NULL, NULL, NULL); + pine_state->next_screen = prev_screen; + pine_state->redrawer = redraw; +} + + +/*---------------------------------------------------------------------- + Alternate compose screen. Set up role and call regular compose. + + Args: pine_state -- The usual pine structure + ---*/ +void +alt_compose_screen(struct pine *pine_state) +{ + ACTION_S *role = NULL; + void (*prev_screen)(struct pine *) = pine_state->prev_screen, + (*redraw)(void) = pine_state->redrawer; + + pine_state->redrawer = NULL; + ps_global->next_screen = SCREEN_FUN_NULL; + mailcap_free(); /* free resources we won't be using for a while */ + + /* Setup role */ + if(role_select_screen(pine_state, &role, MC_COMPOSE) < 0){ + cmd_cancelled("Composition"); + pine_state->next_screen = prev_screen; + pine_state->redrawer = redraw; + return; + } + + /* + * If default role was selected (NULL) we need to make up a role which + * won't do anything, but will cause compose_mail to think there's + * already a role so that it won't try to confirm the default. + */ + if(role) + role = combine_inherited_role(role); + else{ + role = (ACTION_S *)fs_get(sizeof(*role)); + memset((void *)role, 0, sizeof(*role)); + role->nick = cpystr("Default Role"); + } + + pine_state->redrawer = NULL; + compose_mail(NULL, NULL, role, NULL, NULL); + free_action(&role); + pine_state->next_screen = prev_screen; + pine_state->redrawer = redraw; +} + + +/*---------------------------------------------------------------------- + Format envelope for outgoing message and call editor + + Args: given_to -- An address to send mail to (usually from command line + invocation) + fcc_arg -- The fcc that goes with this address. + + If a "To" line is given format that into the envelope and get ready to call + the composer + If there's a message postponed, offer to continue it, and set it up, + otherwise just fill in the outgoing envelope as blank. + + NOTE: we ignore postponed and interrupted messages in nr mode + ----*/ +void +compose_mail(char *given_to, char *fcc_arg, ACTION_S *role_arg, + PATMT *attach, gf_io_t inc_text_getc) +{ + BODY *body = NULL; + ENVELOPE *outgoing = NULL; + PINEFIELD *custom = NULL; + REPLY_S *reply = NULL; + REDRAFT_POS_S *redraft_pos = NULL; + ACTION_S *role = NULL; + MAILSTREAM *stream; + char *fcc_to_free, + *fcc = NULL, + *lcc = NULL, + *sig = NULL; + int fcc_is_sticky = 0, + to_is_sticky = 0, + intrptd = 0, + postponed = 0, + form = 0; + + dprint((1, + "\n\n ---- COMPOSE SCREEN (not in pico yet) ----\n")); + + /*-- Check for INTERRUPTED mail --*/ + if(!role_arg && !(given_to || attach)){ + char file_path[MAXPATH+1]; + + /* build filename and see if it exists. build_path creates + * an explicit local path name, so all c-client access is thru + * local drivers. + */ + file_path[0] = '\0'; + build_path(file_path, + ps_global->VAR_OPER_DIR ? ps_global->VAR_OPER_DIR + : ps_global->home_dir, + INTERRUPTED_MAIL, sizeof(file_path)); + + /* check to see if the folder exists, the user wants to continue + * and that we can actually read something in... + */ + if(folder_exists(NULL, file_path) & FEX_ISFILE) + intrptd = 1; + } + + /*-- Check for postponed mail --*/ + if(!role_arg + && !outgoing /* not replying/forwarding */ + && !(given_to || attach) /* not command line send */ + && ps_global->VAR_POSTPONED_FOLDER /* folder to look in */ + && ps_global->VAR_POSTPONED_FOLDER[0]) + postponed = 1; + + /*-- Check for form letter folder --*/ + if(!role_arg + && !outgoing /* not replying/forwarding */ + && !(given_to || attach) /* not command line send */ + && ps_global->VAR_FORM_FOLDER /* folder to look in */ + && ps_global->VAR_FORM_FOLDER[0]) + form = 1; + + if(!outgoing && !(given_to || attach) + && !role_arg && F_ON(F_ALT_COMPOSE_MENU, ps_global)){ + char prompt[80]; + char letters[30]; + char chosen_task; + char *new = "New"; + char *intrpt = "Interrupted"; + char *postpnd = "Postponed"; + char *formltr = "FormLetter"; + char *roles = "setRole"; + HelpType help = h_composer_browse; + ESCKEY_S compose_style[6]; + unsigned which_help; + int ekey_num; + + ekey_num = 0; + compose_style[ekey_num].ch = 'n'; + compose_style[ekey_num].rval = 'n'; + compose_style[ekey_num].name = "N"; + compose_style[ekey_num++].label = new; + + if(intrptd){ + compose_style[ekey_num].ch = 'i'; + compose_style[ekey_num].rval = 'i'; + compose_style[ekey_num].name = "I"; + compose_style[ekey_num++].label = intrpt; + } + + if(postponed){ + compose_style[ekey_num].ch = 'p'; + compose_style[ekey_num].rval = 'p'; + compose_style[ekey_num].name = "P"; + compose_style[ekey_num++].label = postpnd; + } + + if(form){ + compose_style[ekey_num].ch = 'f'; + compose_style[ekey_num].rval = 'f'; + compose_style[ekey_num].name = "F"; + compose_style[ekey_num++].label = formltr; + } + + compose_style[ekey_num].ch = 'r'; + compose_style[ekey_num].rval = 'r'; + compose_style[ekey_num].name = "R"; + compose_style[ekey_num++].label = roles; + + compose_style[ekey_num].ch = -1; + + if(F_ON(F_BLANK_KEYMENU,ps_global)){ + char *p; + + p = letters; + *p = '\0'; + for(ekey_num = 0; compose_style[ekey_num].ch != -1; ekey_num++){ + if(p - letters < sizeof(letters)) + *p++ = (char) compose_style[ekey_num].ch; + + if(compose_style[ekey_num + 1].ch != -1 && p - letters < sizeof(letters)) + *p++ = ','; + } + + if(p - letters < sizeof(letters)) + *p = '\0'; + } + + which_help = intrptd + 2 * postponed + 4 * form; + switch(which_help){ + case 1: + help = h_compose_intrptd; + break; + case 2: + help = h_compose_postponed; + break; + case 3: + help = h_compose_intrptd_postponed; + break; + case 4: + help = h_compose_form; + break; + case 5: + help = h_compose_intrptd_form; + break; + case 6: + help = h_compose_postponed_form; + break; + case 7: + help = h_compose_intrptd_postponed_form; + break; + default: + help = h_compose_default; + break; + } + + snprintf(prompt, sizeof(prompt), + "Choose a compose method from %s : ", + F_ON(F_BLANK_KEYMENU,ps_global) ? letters : "the menu below"); + prompt[sizeof(prompt)-1] = '\0'; + + chosen_task = radio_buttons(prompt, -FOOTER_ROWS(ps_global), + compose_style, 'n', 'x', help, RB_NORM); + intrptd = postponed = form = 0; + + switch(chosen_task){ + case 'i': + intrptd = 1; + break; + case 'p': + postponed = 1; + break; + case 'r': + { + void (*prev_screen)(struct pine *) = ps_global->prev_screen, + (*redraw)(void) = ps_global->redrawer; + + ps_global->redrawer = NULL; + ps_global->next_screen = SCREEN_FUN_NULL; + if(role_select_screen(ps_global, &role, MC_COMPOSE) < 0){ + cmd_cancelled("Composition"); + ps_global->next_screen = prev_screen; + ps_global->redrawer = redraw; + return; + } + + ps_global->next_screen = prev_screen; + ps_global->redrawer = redraw; + if(role) + role = combine_inherited_role(role); + } + break; + + case 'f': + form = 1; + break; + + case 'x': + q_status_message(SM_ORDER, 0, 3, + "Composition cancelled"); + return; + break; + + default: + break; + } + } + + if(intrptd && !outgoing){ + char file_path[MAXPATH+1]; + int ret = 'n'; + + file_path[0] = '\0'; + build_path(file_path, + ps_global->VAR_OPER_DIR ? ps_global->VAR_OPER_DIR + : ps_global->home_dir, + INTERRUPTED_MAIL, sizeof(file_path)); + if(folder_exists(NULL, file_path) & FEX_ISFILE){ + if((stream = pine_mail_open(NULL, file_path, + SP_USEPOOL|SP_TEMPUSE, NULL)) + && !stream->halfopen){ + + if(F_ON(F_ALT_COMPOSE_MENU, ps_global) || + (ret = redraft_prompt("Interrupted",INTRPT_PMT,'n')) =='y'){ + if(!redraft(&stream, &outgoing, &body, &fcc, &lcc, &reply, + &redraft_pos, &custom, &role, REDRAFT_DEL)){ + if(stream) + pine_mail_close(stream); + + return; + } + + to_is_sticky++; + + /* redraft() may or may not have closed stream */ + if(stream) + pine_mail_close(stream); + + postponed = form = 0; + } + else{ + pine_mail_close(stream); + if(ret == 'x'){ + q_status_message(SM_ORDER, 0, 3, + _("Composition cancelled")); + return; + } + } + } + else{ + q_status_message1(SM_ORDER | SM_DING, 3, 3, + _("Can't open Interrupted mailbox: %s"), + file_path); + if(stream) + pine_mail_close(stream); + } + } + } + + if(postponed && !outgoing){ + int ret = 'n', done = 0; + int exists; + + if((exists=postponed_stream(&stream, + ps_global->VAR_POSTPONED_FOLDER, + "Postponed", 0)) & FEX_ISFILE){ + if(F_ON(F_ALT_COMPOSE_MENU, ps_global) || + (ret = redraft_prompt("Postponed",PSTPND_PMT,'n')) == 'y'){ + if(!redraft(&stream, &outgoing, &body, &fcc, &lcc, &reply, + &redraft_pos, &custom, &role, + REDRAFT_DEL | REDRAFT_PPND)) + done++; + + /* stream may or may not be closed in redraft() */ + if(stream && (stream != ps_global->mail_stream)) + pine_mail_close(stream); + + to_is_sticky++; + intrptd = form = 0; + } + else{ + if(stream != ps_global->mail_stream) + pine_mail_close(stream); + + if(ret == 'x'){ + q_status_message(SM_ORDER, 0, 3, + _("Composition cancelled")); + done++; + } + } + } + else if(F_ON(F_ALT_COMPOSE_MENU, ps_global)) + done++; + + if(done) + return; + } + + if(form && !outgoing){ + int ret = 'n', done = 0; + int exists; + + if((exists=postponed_stream(&stream, + ps_global->VAR_FORM_FOLDER, + "Form letter", 1)) & FEX_ISFILE){ + if(F_ON(F_ALT_COMPOSE_MENU, ps_global) || + (ret = want_to(FORM_PMT,'y','x',NO_HELP,WT_NORM))=='y'){ + if(!redraft(&stream, &outgoing, &body, &fcc, &lcc, &reply, + &redraft_pos, &custom, NULL, REDRAFT_NONE)) + done++; + + /* stream may or may not be closed in redraft() */ + if(stream && (stream != ps_global->mail_stream)) + pine_mail_close(stream); + + to_is_sticky++; + intrptd = postponed = 0; + } + else{ + if(stream != ps_global->mail_stream) + pine_mail_close(stream); + + if(ret == 'x'){ + q_status_message(SM_ORDER, 0, 3, + _("Composition cancelled")); + done++; + } + } + } + else{ + if(F_ON(F_ALT_COMPOSE_MENU, ps_global)){ + q_status_message(SM_ORDER | SM_DING, 3, 3, + _("Form letter folder doesn't exist!")); + return; + } + } + + if(done) + return; + } + + /*-- normal composition --*/ + if(!outgoing){ + int impl, template_len = 0; + long rflags = ROLE_COMPOSE; + PAT_STATE dummy; + + /*================= Compose new message ===============*/ + body = mail_newbody(); + outgoing = mail_newenvelope(); + + if(given_to) + rfc822_parse_adrlist(&outgoing->to, given_to, ps_global->maildomain); + + outgoing->message_id = generate_message_id(); + + /* + * Setup possible role + */ + if(role_arg) + role = copy_action(role_arg); + + if(!role){ + /* Setup possible compose role */ + if(nonempty_patterns(rflags, &dummy)){ + /* + * setup default role + * Msgno = -1 means there is no msg. + * This will match roles which have the Compose Use turned + * on, and have no patterns set, and match the Current + * Folder Type. + */ + role = set_role_from_msg(ps_global, rflags, -1L, NULL); + + if(confirm_role(rflags, &role)) + role = combine_inherited_role(role); + else{ /* cancel reply */ + role = NULL; + cmd_cancelled("Composition"); + return; + } + } + } + + if(role) + q_status_message1(SM_ORDER, 3, 4, _("Composing using role \"%s\""), + role->nick); + + /* + * The type of storage object allocated below is vitally + * important. See SIMPLIFYING ASSUMPTION #37 + */ + if((body->contents.text.data = (void *) so_get(PicoText, + NULL, EDIT_ACCESS)) != NULL){ + char ch; + + if(inc_text_getc){ + while((*inc_text_getc)(&ch)) + if(!so_writec(ch, (STORE_S *)body->contents.text.data)){ + break; + } + } + } + else{ + q_status_message(SM_ORDER | SM_DING, 3, 4, + _("Problem creating space for message text.")); + return; + } + + if(role && role->template){ + char *filtered; + + impl = 1; /* leave cursor in header if not explicit */ + filtered = detoken(role, NULL, 0, 0, 0, &redraft_pos, &impl); + if(filtered){ + if(*filtered){ + so_puts((STORE_S *)body->contents.text.data, filtered); + if(impl == 1) + template_len = strlen(filtered); + } + + fs_give((void **)&filtered); + } + } + else + impl = 1; + + if((sig = detoken(role, NULL, 2, 0, 1, &redraft_pos, &impl)) != NULL){ + if(impl == 2) + redraft_pos->offset += template_len; + + if(*sig) + so_puts((STORE_S *)body->contents.text.data, sig); + + fs_give((void **)&sig); + } + + body->type = TYPETEXT; + + if(attach) + create_message_body(&body, attach, 0); + } + + ps_global->prev_screen = compose_screen; + if(!(fcc_to_free = fcc) && !(role && role->fcc)) + fcc = fcc_arg; /* Didn't pick up fcc, use given */ + + /* + * check whether a build_address-produced fcc is different from + * fcc. If same, do nothing, if different, set sticky bit in pine_send. + */ + if(fcc){ + char *tmp_fcc = NULL; + + if(outgoing->to){ + tmp_fcc = get_fcc_based_on_to(outgoing->to); + if(strcmp(fcc, tmp_fcc ? tmp_fcc : "")) + fcc_is_sticky++; /* cause sticky bit to get set */ + + } + else if((tmp_fcc = get_fcc(NULL)) != NULL && + !strcmp(fcc, tmp_fcc)){ + /* not sticky */ + } + else + fcc_is_sticky++; + + if(tmp_fcc) + fs_give((void **)&tmp_fcc); + } + + pine_send(outgoing, &body, COMPOSE_MAIL_TITLE, role, fcc, + reply, redraft_pos, lcc, custom, + (fcc_is_sticky ? PS_STICKY_FCC : 0) | (to_is_sticky ? PS_STICKY_TO : 0)); + + if(reply){ + if(reply->mailbox) + fs_give((void **) &reply->mailbox); + if(reply->origmbox) + fs_give((void **) &reply->origmbox); + if(reply->prefix) + fs_give((void **) &reply->prefix); + if(reply->data.uid.msgs) + fs_give((void **) &reply->data.uid.msgs); + fs_give((void **) &reply); + } + + if(fcc_to_free) + fs_give((void **)&fcc_to_free); + + if(lcc) + fs_give((void **)&lcc); + + mail_free_envelope(&outgoing); + pine_free_body(&body); + free_redraft_pos(&redraft_pos); + free_action(&role); +} + + +/*---------------------------------------------------------------------- + Args: stream -- This is where we get the postponed messages from + We'll expunge and close it here unless it is mail_stream. + + These are all return values: + ================ + outgoing -- + body -- + fcc -- + lcc -- + reply -- + redraft_pos -- + custom -- + role -- + ================ + + flags -- + + ----*/ +int +redraft(MAILSTREAM **streamp, ENVELOPE **outgoing, struct mail_bodystruct **body, + char **fcc, char **lcc, REPLY_S **reply, REDRAFT_POS_S **redraft_pos, + PINEFIELD **custom, ACTION_S **role, int flags) +{ + MAILSTREAM *stream; + long cont_msg = 1L; + STORE_S *so; + + if(!(streamp && *streamp)) + return(0); + + stream = *streamp; + + /* + * If we're manipulating the current folder, don't bother + * with index + */ + if(!stream->nmsgs){ + if(REDRAFT_PPND&flags) + q_status_message(SM_ORDER | SM_DING, 3, 5, _("Empty folder! No messages really postponed!")); + else + q_status_message(SM_ORDER | SM_DING, 3, 5, _("Empty folder! No messages really interrupted!")); + + return(redraft_cleanup(streamp, FALSE, flags)); + } + else if(stream == ps_global->mail_stream + && ps_global->prev_screen == mail_index_screen){ + /* + * Since the user's got this folder already opened and they're + * on a selected message, pick that one rather than rebuild + * another index screen... + */ + cont_msg = mn_m2raw(ps_global->msgmap, mn_get_cur(ps_global->msgmap)); + } + else if(stream->nmsgs > 1L){ /* offer browser ? */ + int rv; + + if(REDRAFT_PPND&flags){ /* set to last message postponed */ + mn_set_cur(sp_msgmap(stream), + mn_get_revsort(sp_msgmap(stream)) + ? 1L : mn_get_total(sp_msgmap(stream))); + } + else{ /* set to top form letter */ + mn_set_cur(sp_msgmap(stream), 1L); + } + + clear_index_cache(stream, 0); + while(1){ + void *ti; + + ti = stop_threading_temporarily(); + rv = index_lister(ps_global, NULL, stream->mailbox, + stream, sp_msgmap(stream)); + restore_threading(&ti); + + cont_msg = mn_m2raw(sp_msgmap(stream), mn_get_cur(sp_msgmap(stream))); + if(count_flagged(stream, F_DEL) + && want_to(INTR_DEL_PMT, 'n', 0, NO_HELP, WT_NORM) == 'n'){ + if(REDRAFT_PPND&flags) + q_status_message(SM_ORDER, 3, 3, _("Undelete messages to remain postponed, and then continue message")); + else + q_status_message(SM_ORDER, 3, 3, _("Undelete form letters you want to keep, and then continue message")); + + continue; + } + + break; + } + + clear_index_cache(stream, 0); + + if(rv){ + q_status_message(SM_ORDER, 0, 3, _("Composition cancelled")); + (void) redraft_cleanup(streamp, FALSE, flags); + + if(!*streamp && !ps_global->mail_stream){ + q_status_message2(SM_ORDER, 3, 7, + "No more %.200s, returning to \"%.200s\"", + (REDRAFT_PPND&flags) ? "postponed messages" + : "form letters", + ps_global->inbox_name); + if(ps_global && ps_global->ttyo){ + blank_keymenu(ps_global->ttyo->screen_rows - 2, 0); + ps_global->mangled_footer = 1; + } + + do_broach_folder(ps_global->inbox_name, + ps_global->context_list, NULL, DB_INBOXWOCNTXT); + + ps_global->next_screen = mail_index_screen; + } + + return(0); /* special case */ + } + } + + if((so = (void *) so_get(PicoText, NULL, EDIT_ACCESS)) != NULL) + return(redraft_work(streamp, cont_msg, outgoing, body, + fcc, lcc, reply, redraft_pos, custom, + role, flags, so)); + else + return(0); +} + + +int +redraft_prompt(char *type, char *prompt, int failure) +{ + if(background_posting(FALSE)){ + q_status_message1(SM_ORDER, 0, 3, + _("%s folder unavailable while background posting"), + type); + return(failure); + } + + return(want_to(prompt, 'y', 'x', NO_HELP, WT_NORM)); +} + + +/* this is for initializing the fixed header elements in pine_send() */ +/* +prompt::name::help::prwid::maxlen::realaddr:: +builder::affected_entry::next_affected::selector::key_label::fileedit:: +display_it::break_on_comma::is_attach::rich_header::only_file_chars:: +single_space::sticky::dirty::start_here::blank::sticky_special::KS_ODATAVAR +*/ +static struct headerentry he_template[]={ + {"", "X-Auth-Received", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"From : ", "From", h_composer_from, 10, 0, NULL, + build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, + 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}, + {"Reply-To: ", "Reply To", h_composer_reply_to, 10, 0, NULL, + build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, + 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}, + {"To : ", "To", h_composer_to, 10, 0, NULL, + build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 1, KS_TOADDRBOOK}, + {"Cc : ", "Cc", h_composer_cc, 10, 0, NULL, + build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, + 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}, + {"Bcc : ", "Bcc", h_composer_bcc, 10, 0, NULL, + build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, + 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}, + {"Newsgrps: ", "Newsgroups", h_composer_news, 10, 0, NULL, + news_build, NULL, NULL, news_group_selector, "To NwsGrps", NULL, NULL, + 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"Fcc : ", "Fcc", h_composer_fcc, 10, 0, NULL, + NULL, NULL, NULL, folders_for_fcc, "To Fldrs", NULL, NULL, + 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"Lcc : ", "Lcc", h_composer_lcc, 10, 0, NULL, + build_addr_lcc, NULL, NULL, addr_book_compose_lcc,"To AddrBk", NULL, abook_nickname_complete, + 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"Attchmnt: ", "Attchmnt", h_composer_attachment, 10, 0, NULL, + NULL, NULL, NULL, NULL, "To Files", NULL, NULL, + 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, KS_NONE}, + {"Subject : ", "Subject", h_composer_subject, 10, 0, NULL, + valid_subject, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "References", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "Date", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "In-Reply-To", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "Message-ID", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "X-Priority", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "User-Agent", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "To", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "X-Post-Error",NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "X-Reply-UID", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "X-Reply-Mbox", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "X-SMTP-Server", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "X-Cursor-Pos", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "X-Our-ReplyTo", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", OUR_HDRS_LIST, NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, + {"", "X-Auth-Received", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE}, +#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH) + {"", "Sender", NO_HELP, 10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, KS_NONE} +#endif +}; + + +static struct headerentry he_custom_addr_templ={ + NULL, NULL, h_composer_custom_addr,10, 0, NULL, + build_address, NULL, NULL, addr_book_compose, "To AddrBk", NULL, abook_nickname_complete, + 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_TOADDRBOOK}; + +static struct headerentry he_custom_free_templ={ + NULL, NULL, h_composer_custom_free,10, 0, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, KS_NONE}; + + +/*---------------------------------------------------------------------- + Get addressee for message, then post message + + Args: outgoing -- Partially formatted outgoing ENVELOPE + body -- Body of outgoing message + prmpt_who -- Optional prompt for optionally_enter call + prmpt_cnf -- Optional prompt for confirmation call + used_tobufval -- The string that the to was eventually set equal to. + This gets passed back if non-NULL on entry. + flagsarg -- SS_PROMPTFORTO - Allow user to change recipient + SS_NULLRP - Use null return-path so we'll send an + SMTP MAIL FROM: <> + + Result: message "To: " field is provided and message is sent or cancelled. + + Fields: + remail - + return_path - + date added here + from added here + sender - + reply_to - + subject passed in, NOT edited but maybe canonized here + to possibly passed in, edited and canonized here + cc - + bcc - + in_reply_to - + message_id - + +Can only deal with message bodies that are either TYPETEXT or TYPEMULTIPART +with the first part TYPETEXT! All newlines in the text here also end with +CRLF. + +Returns 0 on success, -1 on failure. + ----*/ +int +pine_simple_send(ENVELOPE *outgoing, /* envelope for outgoing message */ + struct mail_bodystruct **body, + ACTION_S *role, + char *prmpt_who, + char *prmpt_cnf, + char **used_tobufval, + int flagsarg) +{ + char **tobufp, *p; + void *messagebuf; + int done = 0, retval = 0, x; + int lastrc, rc = 0, ku, i, resize_len, result, fcc_result; + int og2s_done = 0; + HelpType help; + static HISTORY_S *history = NULL; + ESCKEY_S ekey[5]; + BUILDER_ARG ba_fcc; + METAENV *header; + + dprint((1,"\n === simple send called === \n")); + + memset(&ba_fcc, 0, sizeof(BUILDER_ARG)); + + init_hist(&history, HISTSIZE); + + header = pine_simple_send_header(outgoing, &ba_fcc.tptr, &tobufp); + + /*----- Fill in a few general parts of the envelope ----*/ + if(!outgoing->date){ + if(F_ON(F_QUELL_TIMEZONE, ps_global)) + mail_parameters(NULL, SET_DISABLE822TZTEXT, (void *) TRUE); + + rfc822_date(tmp_20k_buf); /* format and copy new date */ + if(F_ON(F_QUELL_TIMEZONE, ps_global)) + mail_parameters(NULL, SET_DISABLE822TZTEXT, (void *) FALSE); + + outgoing->date = (unsigned char *) cpystr(tmp_20k_buf); + } + + if(!outgoing->from){ + if(role && role->from){ + if(ps_global->never_allow_changing_from) + q_status_message(SM_ORDER, 3, 3, _("Site policy doesn't allow changing From address so role's From has no effect")); + else + outgoing->from = copyaddrlist(role->from); + } + else + outgoing->from = generate_from(); + } + + if(!(flagsarg & SS_NULLRP)) + outgoing->return_path = rfc822_cpy_adr(outgoing->from); + + ekey[i = 0].ch = ctrl('T'); + ekey[i].rval = 2; + ekey[i].name = "^T"; + ekey[i++].label = N_("To AddrBk"); + + if(F_ON(F_ENABLE_TAB_COMPLETE,ps_global)){ + ekey[i].ch = ctrl('I'); + ekey[i].rval = 11; + ekey[i].name = "TAB"; + ekey[i++].label = N_("Complete"); + } + + ekey[i].ch = KEY_UP; + ekey[i].rval = 30; + ekey[i].name = ""; + ku = i; + ekey[i++].label = ""; + + ekey[i].ch = KEY_DOWN; + ekey[i].rval = 31; + ekey[i].name = ""; + ekey[i++].label = ""; + + ekey[i].ch = -1; + + /*---------------------------------------------------------------------- + Loop editing the "To: " field until everything goes well + ----*/ + help = NO_HELP; + + while(!done){ + int flags; + + if(!og2s_done){ + og2s_done++; + outgoing2strings(header, *body, &messagebuf, NULL, 1); + } + + lastrc = rc; + if(flagsarg & SS_PROMPTFORTO){ + if(!*tobufp) + *tobufp = cpystr(""); + + resize_len = MAX(MAXPATH, strlen(*tobufp)); + fs_resize((void **) tobufp, resize_len+1); + + if(items_in_hist(history) > 0){ + ekey[ku].name = HISTORY_UP_KEYNAME; + ekey[ku].label = HISTORY_KEYLABEL; + ekey[ku+1].name = HISTORY_DOWN_KEYNAME; + ekey[ku+1].label = HISTORY_KEYLABEL; + } + else{ + ekey[ku].name = ""; + ekey[ku].label = ""; + ekey[ku+1].name = ""; + ekey[ku+1].label = ""; + } + + flags = OE_APPEND_CURRENT; + + rc = optionally_enter(*tobufp, -FOOTER_ROWS(ps_global), + 0, resize_len, + prmpt_who + ? prmpt_who + : outgoing->remail == NULL + ? _("FORWARD (as e-mail) to : ") + : _("BOUNCE (redirect) message to : "), + ekey, help, &flags); + } + else + rc = 0; + + switch(rc){ + case -1: + q_status_message(SM_ORDER | SM_DING, 3, 4, + "Internal problem encountered"); + retval = -1; + done++; + break; + + case 30 : + if((p = get_prev_hist(history, *tobufp, 0, NULL)) != NULL){ + strncpy(*tobufp, p, resize_len); + (*tobufp)[resize_len-1] = '\0'; + } + else + Writechar(BELL, 0); + + break; + + case 31 : + if((p = get_next_hist(history, *tobufp, 0, NULL)) != NULL){ + strncpy(*tobufp, p, resize_len); + (*tobufp)[resize_len-1] = '\0'; + } + else + Writechar(BELL, 0); + + break; + + case 2: /* ^T */ + case 0: + {void (*redraw) (void) = ps_global->redrawer; + char *returned_addr = NULL; + int len, l; + + if(rc == 2){ + int got_something = 0; + + push_titlebar_state(); + returned_addr = addr_book_bounce(); + + /* + * Just make it look like user typed this list in. + */ + if(returned_addr){ + got_something++; + if((l=resize_len) < (len = strlen(returned_addr)) + 1){ + l = len; + fs_resize((void **) tobufp, (size_t) (l+1)); + } + + strncpy(*tobufp, returned_addr, l); + (*tobufp)[l] = '\0'; + fs_give((void **)&returned_addr); + } + + ClearScreen(); + pop_titlebar_state(); + redraw_titlebar(); + if((ps_global->redrawer = redraw) != NULL) /* reset old value, and test */ + (*ps_global->redrawer)(); + + if(!got_something) + continue; + } + + if(*tobufp && **tobufp != '\0'){ + char *errbuf, *addr; + int tolen; + + save_hist(history, *tobufp, 0, NULL); + + errbuf = NULL; + + /* + * If role has an fcc, use it instead of what build_address + * tells us. + */ + if(role && role->fcc){ + if(ba_fcc.tptr) + fs_give((void **) &ba_fcc.tptr); + + ba_fcc.tptr = cpystr(role->fcc); + } + + if(build_address(*tobufp, &addr, &errbuf, + (role && role->fcc) ? NULL : &ba_fcc, NULL) >= 0){ + int sendit = 0; + + if(errbuf) + fs_give((void **)&errbuf); + + if((l=strlen(*tobufp)) < (tolen = strlen(addr)) + 1){ + l = tolen; + fs_resize((void **) tobufp, (size_t) (l+1)); + } + + strncpy(*tobufp, addr, l); + (*tobufp)[l] = '\0'; + if(used_tobufval) + *used_tobufval = cpystr(addr); + + /* confirm address */ + if(flagsarg & SS_PROMPTFORTO){ + char dsn_string[30]; + int dsn_label = 0, dsn_show, i; + int verbose_label = 0; + ESCKEY_S opts[13]; + + strings2outgoing(header, body, NULL, 0); + + if((flagsarg & SS_PROMPTFORTO) + && ((x = check_addresses(header)) == CA_BAD + || (x == CA_EMPTY && F_OFF(F_FCC_ON_BOUNCE, + ps_global)))) + /*--- Addresses didn't check out---*/ + continue; + + i = 0; + opts[i].ch = 'y'; + opts[i].rval = 'y'; + opts[i].name = "Y"; + opts[i++].label = N_("Yes"); + + opts[i].ch = 'n'; + opts[i].rval = 'n'; + opts[i].name = "N"; + opts[i++].label = N_("No"); + + call_mailer_flags &= ~CM_VERBOSE; /* clear verbose */ + if(F_ON(F_VERBOSE_POST, ps_global)){ + /* setup keymenu slot to toggle verbose mode */ + opts[i].ch = ctrl('W'); + opts[i].rval = 12; + opts[i].name = "^W"; + verbose_label = i++; + if(F_ON(F_DSN, ps_global)){ + opts[i].ch = 0; + opts[i].rval = 0; + opts[i].name = ""; + opts[i++].label = ""; + } + } + + /* clear DSN flags */ + call_mailer_flags &= ~(CM_DSN_NEVER | CM_DSN_DELAY | CM_DSN_SUCCESS | CM_DSN_FULL); + if(F_ON(F_DSN, ps_global)){ + /* setup keymenu slots to toggle dsn bits */ + opts[i].ch = 'd'; + opts[i].rval = 'd'; + opts[i].name = "D"; + opts[i].label = "DSNOpts"; + dsn_label = i++; + opts[i].ch = -2; + opts[i].rval = 's'; + opts[i].name = "S"; + opts[i++].label = ""; + opts[i].ch = -2; + opts[i].rval = 'x'; + opts[i].name = "X"; + opts[i++].label = ""; + opts[i].ch = -2; + opts[i].rval = 'h'; + opts[i].name = "H"; + opts[i++].label = ""; + } + + opts[i].ch = -1; + + while(1){ + int rv; + + dsn_show = (call_mailer_flags & CM_DSN_SHOW); + snprintf(tmp_20k_buf, SIZEOF_20KBUF, + "%s%s%s%s%s%sto \"%s\" ? ", + prmpt_cnf ? prmpt_cnf : "Send message ", + ((call_mailer_flags & CM_VERBOSE) + || (dsn_show)) + ? "(" : "", + (call_mailer_flags & CM_VERBOSE) + ? "in verbose mode" : "", + (dsn_show && (call_mailer_flags & CM_VERBOSE)) + ? ", " : "", + (dsn_show) ? dsn_string : "", + ((call_mailer_flags & CM_VERBOSE) || dsn_show) + ? ") " : "", + (addr && *addr) + ? addr + : (F_ON(F_FCC_ON_BOUNCE, ps_global) + && ba_fcc.tptr && ba_fcc.tptr[0]) + ? ba_fcc.tptr + : ""); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + if((strlen(tmp_20k_buf) > + ps_global->ttyo->screen_cols - 2) && + ps_global->ttyo->screen_cols >= 7) + strncpy(tmp_20k_buf+ps_global->ttyo->screen_cols-7, + "...? ", SIZEOF_20KBUF-ps_global->ttyo->screen_cols-7); + + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + if(verbose_label) + opts[verbose_label].label = + /* TRANSLATORS: several possible key labels follow */ + (call_mailer_flags & CM_VERBOSE) ? N_("Normal") : N_("Verbose"); + + if(F_ON(F_DSN, ps_global)){ + if(call_mailer_flags & CM_DSN_SHOW){ + opts[dsn_label].label = + (call_mailer_flags & CM_DSN_DELAY) + ? N_("NoDelay") : N_("Delay"); + opts[dsn_label+1].ch = 's'; + opts[dsn_label+1].label = + (call_mailer_flags & CM_DSN_SUCCESS) + ? N_("NoSuccess") : N_("Success"); + opts[dsn_label+2].ch = 'x'; + opts[dsn_label+2].label = + (call_mailer_flags & CM_DSN_NEVER) + ? N_("ErrRets") : N_("NoErrRets"); + opts[dsn_label+3].ch = 'h'; + opts[dsn_label+3].label = + (call_mailer_flags & CM_DSN_FULL) + ? N_("RetHdrs") : N_("RetFull"); + } + } + + rv = radio_buttons(tmp_20k_buf, + -FOOTER_ROWS(ps_global), opts, + 'y', 'z', NO_HELP, RB_NORM); + if(rv == 'y'){ /* user ACCEPTS! */ + sendit = 1; + break; + } + else if(rv == 'n'){ /* Declined! */ + break; + } + else if(rv == 'z'){ /* Cancelled! */ + break; + } + else if(rv == 12){ /* flip verbose bit */ + if(call_mailer_flags & CM_VERBOSE) + call_mailer_flags &= ~CM_VERBOSE; + else + call_mailer_flags |= CM_VERBOSE; + } + else if(call_mailer_flags & CM_DSN_SHOW){ + if(rv == 's'){ /* flip success bit */ + call_mailer_flags ^= CM_DSN_SUCCESS; + /* turn off related bits */ + if(call_mailer_flags & CM_DSN_SUCCESS) + call_mailer_flags &= ~(CM_DSN_NEVER); + } + else if(rv == 'd'){ /* flip delay bit */ + call_mailer_flags ^= CM_DSN_DELAY; + /* turn off related bits */ + if(call_mailer_flags & CM_DSN_DELAY) + call_mailer_flags &= ~(CM_DSN_NEVER); + } + else if(rv == 'x'){ /* flip never bit */ + call_mailer_flags ^= CM_DSN_NEVER; + /* turn off related bits */ + if(call_mailer_flags & CM_DSN_NEVER) + call_mailer_flags &= ~(CM_DSN_SUCCESS | CM_DSN_DELAY); + } + else if(rv == 'h'){ /* flip full bit */ + call_mailer_flags ^= CM_DSN_FULL; + } + } + else if(rv == 'd'){ /* show dsn options */ + call_mailer_flags |= (CM_DSN_SHOW | CM_DSN_SUCCESS | CM_DSN_DELAY | CM_DSN_FULL); + } + + snprintf(dsn_string, sizeof(dsn_string), _("DSN requested[%s%s%s%s]"), + (call_mailer_flags & CM_DSN_NEVER) + ? _("Never") : "F", + (call_mailer_flags & CM_DSN_DELAY) + ? "D" : "", + (call_mailer_flags & CM_DSN_SUCCESS) + ? "S" : "", + (call_mailer_flags & CM_DSN_NEVER) + ? "" + : (call_mailer_flags & CM_DSN_FULL) ? "-Full" + : "-Hdrs"); + dsn_string[sizeof(dsn_string)-1] = '\0'; + } + } + + if(addr) + fs_give((void **)&addr); + + if(!(flagsarg & SS_PROMPTFORTO) || sendit){ + char *fcc = NULL; + CONTEXT_S *fcc_cntxt = NULL; + + if(F_ON(F_FCC_ON_BOUNCE, ps_global)){ + if(ba_fcc.tptr) + fcc = cpystr(ba_fcc.tptr); + + set_last_fcc(fcc); + + /* + * If special name "inbox" then replace it with the + * real inbox path. + */ + if(ps_global->VAR_INBOX_PATH + && strucmp(fcc, ps_global->inbox_name) == 0){ + char *replace_fcc; + + replace_fcc = cpystr(ps_global->VAR_INBOX_PATH); + fs_give((void **) &fcc); + fcc = replace_fcc; + } + } + + /*---- Check out fcc -----*/ + if(fcc && *fcc){ + (void) commence_fcc(fcc, &fcc_cntxt, FALSE); + if(!lmc.so){ + dprint((4,"can't open fcc, cont\n")); + if(!(flagsarg & SS_PROMPTFORTO)){ + retval = -1; + fs_give((void **)&fcc); + fcc = NULL; + goto finish; + } + else + continue; + } + else + so_truncate(lmc.so, fcc_size_guess(*body) + 2048); + } + else + lmc.so = NULL; + + if(!(outgoing->to || outgoing->cc || outgoing->bcc + || lmc.so)){ + q_status_message(SM_ORDER, 3, 5, _("No recipients specified!")); + continue; + } + + if(outgoing->to || outgoing->cc || outgoing->bcc){ + char **alt_smtp = NULL; + + if(role && role->smtp){ + if(ps_global->FIX_SMTP_SERVER + && ps_global->FIX_SMTP_SERVER[0]) + q_status_message(SM_ORDER | SM_DING, 5, 5, _("Use of a role-defined smtp-server is administratively prohibited")); + else + alt_smtp = role->smtp; + } + + result = call_mailer(header, *body, alt_smtp, + call_mailer_flags, + call_mailer_file_result, + pipe_callback); + mark_address_failure_for_pico(header); + } + else + result = 0; + + if(result == 1 && !lmc.so) + q_status_message(SM_ORDER, 0, 3, _("Message sent")); + + /*----- Was there an fcc involved? -----*/ + if(lmc.so){ + if(result == 1 + || (result == 0 + && pine_rfc822_output(header, *body, NULL, NULL))){ + char label[50]; + + strncpy(label, "Fcc", sizeof(label)); + label[sizeof(label)-1] = '\0'; + if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){ + snprintf(label + 3, sizeof(label)-3, " to %s", fcc); + label[sizeof(label)-1] = '\0'; + } + + /* Now actually copy to fcc folder and close */ + fcc_result = + write_fcc(fcc, fcc_cntxt, lmc.so, NULL, + label, + F_ON(F_MARK_FCC_SEEN, ps_global) + ? "\\SEEN" : NULL); + } + else if(result == 0){ + q_status_message(SM_ORDER,3,5, + _("Fcc Failed!. No message saved.")); + retval = -1; + dprint((1, "explicit fcc write failed!\n")); + } + + so_give(&lmc.so); + } + + if(result < 0){ + dprint((1, "Bounce failed\n")); + if(!(flagsarg & SS_PROMPTFORTO)) + retval = -1; + else + continue; + } + else if(result == 1){ + if(!fcc) + q_status_message(SM_ORDER, 0, 3, + _("Message sent")); + else{ + int avail = ps_global->ttyo->screen_cols-2; + int need, fcclen; + char *part1 = "Message sent and "; + char *part2 = fcc_result ? "" : "NOT "; + char *part3 = "copied to "; + fcclen = strlen(fcc); + + need = 2 + strlen(part1) + strlen(part2) + + strlen(part3) + fcclen; + + if(need > avail && fcclen > 6) + fcclen -= MIN(fcclen-6, need-avail); + + q_status_message4(SM_ORDER, 0, 3, + "%s%s%s\"%s\"", + part1, part2, part3, + short_str(fcc, + (char *)tmp_20k_buf, + SIZEOF_20KBUF, + fcclen, FrontDots)); + } + } + + if(fcc) + fs_give((void **)&fcc); + } + else{ + q_status_message(SM_ORDER, 0, 3, _("Send cancelled")); + retval = -1; + } + } + else{ + q_status_message1(SM_ORDER | SM_DING, 3, 5, + _("Error in address: %s"), errbuf); + if(errbuf) + fs_give((void **)&errbuf); + + if(!(flagsarg & SS_PROMPTFORTO)) + retval = -1; + else + continue; + } + + } + else{ + q_status_message(SM_ORDER | SM_DING, 3, 5, + _("No addressee! No e-mail sent.")); + retval = -1; + } + } + + done++; + break; + + case 1: + q_status_message(SM_ORDER, 0, 3, _("Send cancelled")); + done++; + retval = -1; + break; + + case 3: + help = (help == NO_HELP) + ? (outgoing->remail == NULL + ? h_anon_forward + : h_bounce) + : NO_HELP; + break; + + case 11: + if(**tobufp){ + char *new_nickname = NULL; + int l; + int ambiguity; + + ambiguity = abook_nickname_complete(*tobufp, &new_nickname, + (lastrc==rc && !(flags & OE_USER_MODIFIED)), ANC_AFTERCOMMA); + if(new_nickname){ + if(*new_nickname){ + if((l=strlen(new_nickname)) > resize_len){ + resize_len = l; + fs_resize((void **) tobufp, resize_len+1); + } + + strncpy(*tobufp, new_nickname, l); + (*tobufp)[l] = '\0'; + } + + fs_give((void **) &new_nickname); + } + + if(ambiguity != 2) + Writechar(BELL, 0); + } + + break; + + case 4: /* can't suspend */ + default: + break; + } + } + +finish: + if(ba_fcc.tptr) + fs_give((void **)&ba_fcc.tptr); + + pine_free_env(&header); + + return(retval); +} + + +/* + * pine_simple_send_header - generate header suitable for simple_sending + */ +METAENV * +pine_simple_send_header(ENVELOPE *outgoing, char **fccp, char ***tobufpp) +{ + METAENV *header; + PINEFIELD *pf; + static struct headerentry he_dummy; + + header = pine_new_env(outgoing, fccp, tobufpp, NULL); + + /* assign he_dummy to "To:" field "he" for strings2outgoing */ + for(pf = header->local; pf && pf->name; pf = pf->next) + if(pf->type == Address && !strucmp(pf->name, "to")){ + memset((void *) &he_dummy, 0, sizeof(he_dummy)); + pf->extdata = (void *) &he_dummy; + HE(pf)->dirty = 1; + break; + } + + return(header); +} + + + +/*---------------------------------------------------------------------- + Prepare data structures for pico, call pico, then post message + + Args: outgoing -- Partially formatted outgoing ENVELOPE + body -- Body of outgoing message + editor_title -- Title for anchor line in composer + fcc_arg -- The file carbon copy field + reply -- Struct describing set of msgs being replied-to + lcc_arg -- + custom -- custom header list. + sticky_fcc -- + + Result: message is edited, then postponed, cancelled or sent. + + Fields: + remail - + return_path - + date added here + from added here + sender - + reply_to - + subject passed in, edited and cannonized here + to possibly passed in, edited and cannonized here + cc possibly passed in, edited and cannonized here + bcc edited and cannonized here + in_reply_to generated in reply() and passed in + message_id - + + Storage for these fields comes from anywhere outside. It is remalloced + here so the composer can realloc them if needed. The copies here are also + freed here. + +Can only deal with message bodies that are either TYPETEXT or TYPEMULTIPART +with the first part TYPETEXT! All newlines in the text here also end with +CRLF. + +There's a further assumption that the text in the TYPETEXT part is +stored in a storage object (see filter.c). + ----*/ +void +pine_send(ENVELOPE *outgoing, struct mail_bodystruct **body, + char *editor_title, ACTION_S *role, char *fcc_arg, + REPLY_S *reply, REDRAFT_POS_S *redraft_pos, char *lcc_arg, + PINEFIELD *custom, int flags) +{ + int i, fixed_cnt, total_cnt, index, + editor_result = 0, body_start = 0, use_news_order = 0; + char *p, *addr, *fcc, *fcc_to_free = NULL; + char *start_here_name = NULL; + char *suggested_nntp_server = NULL; + char *title = NULL; + struct headerentry *he, *headents, *he_to, *he_fcc, *he_news = NULL, *he_lcc = NULL, + *he_from = NULL; + PINEFIELD *pfields, *pf, *pf_nobody = NULL, *pf_to = NULL, + *pf_smtp_server, *pf_nntp_server, + *pf_fcc = NULL, *pf_err, *pf_uid, *pf_mbox, *pf_curpos, + *pf_ourrep, *pf_ourhdrs, **sending_order; + METAENV header; + ADDRESS *lcc_addr = NULL; + ADDRESS *nobody_addr = NULL; + BODY_PARTICULARS_S *bp; + STORE_S *orig_so = NULL; + PICO pbuf1, *save_previous_pbuf; + CustomType ct; + REDRAFT_POS_S *local_redraft_pos = NULL; + + dprint((1,"\n=== send called ===\n")); + + save_previous_pbuf = pbf; + pbf = &pbuf1; + standard_picobuf_setup(pbf); + + /* + * Cancel any pending initial commands since pico uses a different + * input routine. If we didn't cancel them, they would happen after + * we returned from the editor, which would be confusing. + */ + if(ps_global->in_init_seq){ + ps_global->in_init_seq = 0; + ps_global->save_in_init_seq = 0; + clear_cursor_pos(); + if(ps_global->initial_cmds){ + if(ps_global->free_initial_cmds) + fs_give((void **)&(ps_global->free_initial_cmds)); + + ps_global->initial_cmds = 0; + } + + F_SET(F_USE_FK,ps_global,ps_global->orig_use_fkeys); + } + +#if defined(DOS) || defined(OS2) + if(!dos_valid_from()){ + pbf = save_previous_pbuf; + return; + } + + pbf->upload = NULL; +#else + pbf->upload = (ps_global->VAR_UPLOAD_CMD + && ps_global->VAR_UPLOAD_CMD[0]) + ? upload_msg_to_pico : NULL; +#endif + + pbf->msgntext = message_format_for_pico; + pbf->mimetype = mime_type_for_pico; + pbf->exittest = send_exit_for_pico; + pbf->user_says_noflow = dont_flow_this_time; + pbf->newthread = new_thread_on_blank_subject; + ps_global->newthread = 0; /* reset this value */ + if(F_OFF(F_CANCEL_CONFIRM, ps_global)) + pbf->canceltest = cancel_for_pico; + + pbf->alt_ed = (ps_global->VAR_EDITOR && ps_global->VAR_EDITOR[0] && + ps_global->VAR_EDITOR[0][0]) + ? ps_global->VAR_EDITOR : NULL; + pbf->alt_spell = (ps_global->VAR_SPELLER && ps_global->VAR_SPELLER[0]) + ? ps_global->VAR_SPELLER : NULL; + pbf->always_spell_check = F_ON(F_ALWAYS_SPELL_CHECK, ps_global); + pbf->quote_str = reply && reply->prefix ? reply->prefix : "> "; + /* We actually want to set this only if message we're sending is flowed */ + pbf->strip_ws_before_send = F_ON(F_STRIP_WS_BEFORE_SEND, ps_global); + pbf->allow_flowed_text = (F_OFF(F_QUELL_FLOWED_TEXT, ps_global) + && F_OFF(F_STRIP_WS_BEFORE_SEND, ps_global) + && (strcmp(pbf->quote_str, "> ") == 0 + || strcmp(pbf->quote_str, ">") == 0)); + pbf->edit_offset = 0; + title = cpystr(set_titlebar(editor_title, + ps_global->mail_stream, + ps_global->context_current, + ps_global->cur_folder,ps_global->msgmap, + 0, FolderName, 0, 0, NULL)); + pbf->pine_anchor = title; + +#if defined(DOS) || defined(OS2) + if(!pbf->oper_dir && ps_global->VAR_FILE_DIR){ + pbf->oper_dir = ps_global->VAR_FILE_DIR; + } +#endif + + if(redraft_pos && editor_title && !strcmp(editor_title, COMPOSE_MAIL_TITLE)) + pbf->pine_flags |= P_CHKPTNOW; + + /* NOTE: initial cursor position set below */ + + dprint((9, "flags: %x\n", pbf->pine_flags)); + + /* + * When user runs compose and the current folder is a newsgroup, + * offer to post to the current newsgroup. + */ + if(!(outgoing->to || (outgoing->newsgroups && *outgoing->newsgroups)) + && IS_NEWS(ps_global->mail_stream)){ + char prompt[200], news_group[MAILTMPLEN]; + + pine_send_newsgroup_name(ps_global->mail_stream->mailbox, news_group, + sizeof(news_group)); + + /* + * Replies don't get this far because To or Newsgroups will already + * be filled in. So must be either ordinary compose or forward. + * Forward sets subject, so use that to tell the difference. + */ + if(news_group[0] && !outgoing->subject){ + int ch = 'y'; + int ret_val; + char *errmsg = NULL; + BUILDER_ARG *fcc_build = NULL; + + if(F_OFF(F_COMPOSE_TO_NEWSGRP,ps_global)){ + snprintf(prompt, sizeof(prompt), + _("Post to current newsgroup (%s)"), news_group); + prompt[sizeof(prompt)-1] = '\0'; + ch = want_to(prompt, 'y', 'x', NO_HELP, WT_NORM); + } + + switch(ch){ + case 'y': + if(outgoing->newsgroups) + fs_give((void **)&outgoing->newsgroups); + + if(!fcc_arg && !(role && role->fcc)){ + fcc_build = (BUILDER_ARG *)fs_get(sizeof(BUILDER_ARG)); + memset((void *)fcc_build, 0, sizeof(BUILDER_ARG)); + fcc_build->tptr = fcc_to_free; + } + + ret_val = news_build(news_group, &outgoing->newsgroups, + &errmsg, fcc_build, NULL); + + if(ret_val == -1){ + if(outgoing->newsgroups) + fs_give((void **)&outgoing->newsgroups); + + outgoing->newsgroups = cpystr(news_group); + } + + if(!fcc_arg && !(role && role->fcc)){ + fcc_arg = fcc_to_free = fcc_build->tptr; + fs_give((void **)&fcc_build); + } + + if(errmsg){ + if(*errmsg){ + q_status_message(SM_ORDER, 3, 3, errmsg); + display_message(NO_OP_COMMAND); + } + + fs_give((void **)&errmsg); + } + + break; + + case 'x': /* ^C */ + q_status_message(SM_ORDER, 0, 3, _("Message cancelled")); + dprint((4, "=== send: cancelled\n")); + pbf = save_previous_pbuf; + return; + + case 'n': + break; + + default: + break; + } + } + } + if(F_ON(F_PREDICT_NNTP_SERVER, ps_global) + && outgoing->newsgroups && *outgoing->newsgroups + && IS_NEWS(ps_global->mail_stream)){ + NETMBX news_mb; + + if(mail_valid_net_parse(ps_global->mail_stream->original_mailbox, + &news_mb)) + if(!strucmp(news_mb.service, "nntp")){ + if(*ps_global->mail_stream->original_mailbox == '{'){ + char *svcp = NULL, *psvcp; + + suggested_nntp_server = + cpystr(ps_global->mail_stream->original_mailbox + 1); + if((p = strindex(suggested_nntp_server, '}')) != NULL) + *p = '\0'; + for(p = strindex(suggested_nntp_server, '/'); p && *p; + p = strindex(p, '/')){ + /* take out /nntp, which gets added in nntp_open */ + if(!struncmp(p, "/nntp", 5)) + svcp = p + 5; + else if(!struncmp(p, "/service=nntp", 13)) + svcp = p + 13; + else if(!struncmp(p, "/service=\"nntp\"", 15)) + svcp = p + 15; + else + p++; + if(svcp){ + if(*svcp == '\0') + *p = '\0'; + else if(*svcp == '/' || *svcp == ':'){ + for(psvcp = p; *svcp; svcp++, psvcp++) + *psvcp = *svcp; + *psvcp = '\0'; + } + svcp = NULL; + } + } + } + else + suggested_nntp_server = cpystr(news_mb.orighost); + } + } + + /* + * If we don't already have custom headers set and the role has custom + * headers, then incorporate those custom headers into "custom". + */ + if(!custom){ + PINEFIELD *dflthdrs = NULL, *rolehdrs = NULL; + + dflthdrs = parse_custom_hdrs(ps_global->VAR_CUSTOM_HDRS, UseAsDef); +/* + * If we allow the Combine argument here, we're saying that we want to + * combine the values from the envelope and the role for the fields To, + * Cc, Bcc, and Newsgroups. For example, if we are replying to a message + * we'll have a To in the envelope because we're replying. If our role also + * has a To action, then Combine would combine those two and offer both + * to the user. We've decided against doing this. Instead, we always use + * Replace, and the role's header value replaces the value from the + * envelope. It might also make sense in some cases to do the opposite, + * which would be treating the role headers as defaults, just like + * customized-hdrs. + */ +#ifdef WANT_TO_COMBINE_ADDRESSES + if(role && role->cstm) + rolehdrs = parse_custom_hdrs(role->cstm, Combine); +#else + if(role && role->cstm) + rolehdrs = parse_custom_hdrs(role->cstm, Replace); +#endif + + if(rolehdrs){ + custom = combine_custom_headers(dflthdrs, rolehdrs); + if(dflthdrs){ + free_prompts(dflthdrs); + free_customs(dflthdrs); + } + + if(rolehdrs){ + free_prompts(rolehdrs); + free_customs(rolehdrs); + } + } + else + custom = dflthdrs; + } + + g_rolenick = role ? role->nick : NULL; + + /* how many fixed fields are there? */ + for(fixed_cnt = 0; pf_template && pf_template[fixed_cnt].name; fixed_cnt++) + ; + + total_cnt = fixed_cnt + count_custom_hdrs_pf(custom,1); + + /* the fixed part of the PINEFIELDs */ + i = fixed_cnt * sizeof(PINEFIELD); + pfields = (PINEFIELD *)fs_get((size_t) i); + memset(pfields, 0, (size_t) i); + + /* temporary headerentry array for pico */ + i = (total_cnt + 1) * sizeof(struct headerentry); + headents = (struct headerentry *)fs_get((size_t) i); + memset(headents, 0, (size_t) i); + + i = total_cnt * sizeof(PINEFIELD *); + sending_order = (PINEFIELD **)fs_get((size_t) i); + memset(sending_order, 0, (size_t) i); + + pbf->headents = headents; + header.env = outgoing; + header.local = pfields; + header.sending_order = sending_order; + + /* custom part of PINEFIELDs */ + header.custom = custom; + + he = headents; + pf = pfields; + + /* + * For Address types, pf->addr points to an ADDRESS *. + * If that address is in the "outgoing" envelope, it will + * be freed by the caller, otherwise, it should be freed here. + * Pf->textbuf for an Address is used a little to set up a default, + * but then is freed right away below. Pf->scratch is used for a + * pointer to some alloced space for pico to edit in. Addresses in + * the custom area are freed by free_customs(). + * + * For FreeText types, pf->addr is not used. Pf->text points to a + * pointer that points to the text. Pf->textbuf points to a copy of + * the text that must be freed before we leave, otherwise, it is + * probably a pointer into the envelope and that gets freed by the + * caller. + * + * He->realaddr is the pointer to the text that pico actually edits. + */ + +#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH) +# define NN 4 +#else +# define NN 3 +#endif + + if(outgoing->newsgroups && *outgoing->newsgroups) + use_news_order++; + + /* initialize the fixed header elements of the two temp arrays */ + for(i=0; i < fixed_cnt; i++, pf++){ + static int news_order[] = { + N_AUTHRCVD,N_FROM, N_REPLYTO, N_NEWS, N_TO, N_CC, N_BCC, + N_FCC, N_LCC, N_ATTCH, N_SUBJ, N_REF, N_DATE, N_INREPLY, + N_MSGID, N_PRIORITY, N_USERAGENT, N_NOBODY, N_POSTERR, N_RPLUID, N_RPLMBOX, + N_SMTP, N_NNTP, N_CURPOS, N_OURREPLYTO, N_OURHDRS +#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH) + , N_SENDER +#endif + }; + + index = i; + /* slightly different editing order if sending to news */ + if(use_news_order && + index >= 0 && index < sizeof(news_order)/sizeof(news_order[0])) + index = news_order[i]; + + /* copy the templates */ + *he = he_template[index]; + + pf->name = cpystr(pf_template[index].name); + if(index == N_SENDER && F_ON(F_USE_SENDER_NOT_X, ps_global)) + /* slide string over so it is Sender instead of X-X-Sender */ + for(p=pf->name; *(p+1); p++) + *p = *(p+4); + + pf->type = pf_template[index].type; + pf->canedit = pf_template[index].canedit; + pf->rcptto = pf_template[index].rcptto; + pf->writehdr = pf_template[index].writehdr; + pf->localcopy = pf_template[index].localcopy; + pf->extdata = he; + pf->next = pf + 1; + + he->rich_header = view_as_rich(pf->name, he->rich_header); + if(F_OFF(F_ENABLE_TAB_COMPLETE,ps_global)) + he->nickcmpl = NULL; + + switch(pf->type){ + case FreeText: /* realaddr points to c-client env */ + if(index == N_NEWS){ + sending_order[1] = pf; + he->realaddr = &outgoing->newsgroups; + he_news = he; + + switch(set_default_hdrval(pf, custom)){ + case Replace: + if(*he->realaddr) + fs_give((void **)he->realaddr); + + *he->realaddr = pf->textbuf; + pf->textbuf = NULL; + he->sticky = 1; + break; + + case Combine: + if(*he->realaddr){ /* combine values */ + if(pf->textbuf && *pf->textbuf){ + char *combined_hdr; + size_t l; + + l = strlen(*he->realaddr) + strlen(pf->textbuf) + 1; + combined_hdr = (char *) fs_get((l+1) * sizeof(char)); + strncpy(combined_hdr, *he->realaddr, l); + combined_hdr[l] = '\0'; + strncat(combined_hdr, ",", l+1-1-strlen(combined_hdr)); + combined_hdr[l] = '\0'; + strncat(combined_hdr, pf->textbuf, l+1-1-strlen(combined_hdr)); + combined_hdr[l] = '\0'; + + fs_give((void **)he->realaddr); + *he->realaddr = combined_hdr; + q_status_message(SM_ORDER, 3, 3, + "Adding newsgroup from role"); + he->sticky = 1; + } + } + else{ + *he->realaddr = pf->textbuf; + pf->textbuf = NULL; + } + + break; + + case UseAsDef: + /* if no value, use default */ + if(!*he->realaddr){ + *he->realaddr = pf->textbuf; + pf->textbuf = NULL; + } + + break; + + case NoMatch: + break; + } + + /* If there is a newsgroup, we'd better show it */ + if(outgoing->newsgroups && *outgoing->newsgroups) + he->rich_header = 0; /* force on by default */ + + if(pf->textbuf) + fs_give((void **)&pf->textbuf); + + pf->text = he->realaddr; + } + else if(index == N_DATE){ + sending_order[2] = pf; + pf->text = (char **) &outgoing->date; + pf->extdata = NULL; + } + else if(index == N_INREPLY){ + sending_order[NN+9] = pf; + pf->text = &outgoing->in_reply_to; + pf->extdata = NULL; + } + else if(index == N_MSGID){ + sending_order[NN+10] = pf; + pf->text = &outgoing->message_id; + pf->extdata = NULL; + } + else if(index == N_REF){ + sending_order[NN+11] = pf; + pf->text = &outgoing->references; + pf->extdata = NULL; + } + else if(index == N_PRIORITY){ + sending_order[NN+12] = pf; + pf->text = &pf->textbuf; + pf->extdata = NULL; + } + else if(index == N_USERAGENT){ + sending_order[NN+13] = pf; + pf->text = &pf->textbuf; + pf->textbuf = generate_user_agent(); + pf->extdata = NULL; + } + else if(index == N_POSTERR){ + sending_order[NN+14] = pf; + pf_err = pf; + pf->text = &pf->textbuf; + pf->extdata = NULL; + } + else if(index == N_RPLUID){ + sending_order[NN+15] = pf; + pf_uid = pf; + pf->text = &pf->textbuf; + pf->extdata = NULL; + } + else if(index == N_RPLMBOX){ + sending_order[NN+16] = pf; + pf_mbox = pf; + pf->text = &pf->textbuf; + pf->extdata = NULL; + } + else if(index == N_SMTP){ + sending_order[NN+17] = pf; + pf_smtp_server = pf; + pf->text = &pf->textbuf; + pf->extdata = NULL; + } + else if(index == N_NNTP){ + sending_order[NN+18] = pf; + pf_nntp_server = pf; + pf->text = &pf->textbuf; + pf->extdata = NULL; + } + else if(index == N_CURPOS){ + sending_order[NN+19] = pf; + pf_curpos = pf; + pf->text = &pf->textbuf; + pf->extdata = NULL; + } + else if(index == N_OURREPLYTO){ + sending_order[NN+20] = pf; + pf_ourrep = pf; + pf->text = &pf->textbuf; + pf->extdata = NULL; + } + else if(index == N_OURHDRS){ + sending_order[NN+21] = pf; + pf_ourhdrs = pf; + pf->text = &pf->textbuf; + pf->extdata = NULL; + } + else if(index == N_AUTHRCVD){ + sending_order[0] = pf; + pf_ourhdrs = pf; + pf->text = &pf->textbuf; + pf->extdata = NULL; + } + else{ + q_status_message(SM_ORDER | SM_DING, 3, 7, + "Botched: Unmatched FreeText header in pine_send"); + } + + break; + + /* can't do a default for this one */ + case Attachment: + /* If there is an attachment already, we'd better show them */ + if(body && *body && (*body)->type != TYPETEXT) + he->rich_header = 0; /* force on by default */ + + break; + + case Address: + switch(index){ + case N_FROM: + sending_order[3] = pf; + pf->addr = &outgoing->from; + if(role && role->from){ + if(ps_global->never_allow_changing_from) + q_status_message(SM_ORDER, 3, 3, _("Site policy doesn't allow changing From address so role's From has no effect")); + else{ + outgoing->from = copyaddrlist(role->from); + he->display_it = 1; /* show it */ + he->rich_header = 0; + } + } + + he_from = he; + break; + + case N_TO: + sending_order[NN+2] = pf; + pf->addr = &outgoing->to; + /* If already set, make it act like we typed it in */ + if(outgoing->to + && outgoing->to->mailbox + && outgoing->to->mailbox[0] + && flags & PS_STICKY_TO) + he->sticky = 1; + + he_to = he; + pf_to = pf; + break; + + case N_NOBODY: + sending_order[NN+5] = pf; + pf_nobody = pf; + if(ps_global->VAR_EMPTY_HDR_MSG + && !ps_global->VAR_EMPTY_HDR_MSG[0]){ + pf->addr = NULL; + } + else{ + nobody_addr = mail_newaddr(); + nobody_addr->next = mail_newaddr(); + nobody_addr->mailbox = cpystr(rfc1522_encode(tmp_20k_buf, + SIZEOF_20KBUF, + (unsigned char *)(ps_global->VAR_EMPTY_HDR_MSG + ? ps_global->VAR_EMPTY_HDR_MSG + : "undisclosed-recipients"), + ps_global->posting_charmap)); + pf->addr = &nobody_addr; + } + + break; + + case N_CC: + sending_order[NN+3] = pf; + pf->addr = &outgoing->cc; + break; + + case N_BCC: + sending_order[NN+4] = pf; + pf->addr = &outgoing->bcc; + /* if bcc exists, make sure it's exposed so nothing's + * sent by mistake... + */ + if(outgoing->bcc) + he->display_it = 1; + + break; + + case N_REPLYTO: + sending_order[NN+1] = pf; + pf->addr = &outgoing->reply_to; + if(role && role->replyto){ + if(outgoing->reply_to) + mail_free_address(&outgoing->reply_to); + + outgoing->reply_to = copyaddrlist(role->replyto); + he->display_it = 1; /* show it */ + he->rich_header = 0; + } + + break; + + case N_LCC: + sending_order[NN+7] = pf; + pf->addr = &lcc_addr; + he_lcc = he; + if(lcc_arg){ + build_address(lcc_arg, &addr, NULL, NULL, NULL); + rfc822_parse_adrlist(&lcc_addr, addr, + ps_global->maildomain); + fs_give((void **)&addr); + he->display_it = 1; + } + + break; + +#if !(defined(DOS) || defined(OS2)) || defined(NOAUTH) + case N_SENDER: + sending_order[4] = pf; + pf->addr = &outgoing->sender; + break; +#endif + + default: + q_status_message1(SM_ORDER,3,7, + "Internal error: Address header %s", comatose(index)); + break; + } + + /* + * If this is a reply to news, don't show the regular email + * recipient headers (unless they are non-empty). + */ + if((outgoing->newsgroups && *outgoing->newsgroups) + && (index == N_TO || index == N_CC + || index == N_BCC || index == N_LCC) + && (pf->addr && !*pf->addr)){ + if((ct=set_default_hdrval(pf, custom)) >= UseAsDef && + pf->textbuf && *pf->textbuf){ + removing_trailing_white_space(pf->textbuf); + (void)removing_double_quotes(pf->textbuf); + build_address(pf->textbuf, &addr, NULL, NULL, NULL); + rfc822_parse_adrlist(pf->addr, addr, + ps_global->maildomain); + fs_give((void **)&addr); + if(ct > UseAsDef) + he->sticky = 1; + } + else + he->rich_header = 1; /* hide */ + } + + /* + * If this address doesn't already have a value, then we check + * for a default value assigned by the user. + */ + else if(pf->addr && !*pf->addr){ + if((ct=set_default_hdrval(pf, custom)) >= UseAsDef && + (index != N_FROM || + (!ps_global->never_allow_changing_from && + F_ON(F_ALLOW_CHANGING_FROM, ps_global))) && + pf->textbuf && *pf->textbuf){ + + removing_trailing_white_space(pf->textbuf); + (void)removing_double_quotes(pf->textbuf); + + /* + * Try to set To based on Lcc. Don't attempt Fcc. + */ + if(index == N_LCC && !he_to->sticky && pf_to && pf_to->addr){ + BUILDER_ARG *barg = NULL; + char *ppp = NULL; + + if(*pf_to->addr) + ppp = addr_list_string(*pf_to->addr, NULL, 1); + + if(!ppp) + ppp = cpystr(""); + + barg = (BUILDER_ARG *) fs_get(sizeof(*barg)); + memset(barg, 0, sizeof(*barg)); + barg->me = &(he->bldr_private); + barg->aff = &(he_to->bldr_private); + barg->tptr = cpystr(ppp); + + build_addr_lcc(pf->textbuf, &addr, NULL, barg, NULL); + he->display_it = 1; + + rfc822_parse_adrlist(pf->addr, addr, + ps_global->maildomain); + if(addr) + fs_give((void **) &addr); + + if(ct > UseAsDef) + he->sticky = 1; + + if(barg && barg->tptr && strcmp(ppp, barg->tptr)){ + ADDRESS *a = NULL; + + rfc822_parse_adrlist(&a, barg->tptr, + ps_global->maildomain); + if(a){ + if(pf_to->addr) + mail_free_address(pf_to->addr); + + *pf_to->addr = a; + } + } + + if(barg){ + if(barg->tptr) + fs_give((void **) &barg->tptr); + + fs_give((void **) &barg); + } + + if(ppp) + fs_give((void **) &ppp); + } + else{ + build_address(pf->textbuf, &addr, NULL, NULL, NULL); + rfc822_parse_adrlist(pf->addr, addr, + ps_global->maildomain); + if(addr) + fs_give((void **) &addr); + + if(ct > UseAsDef) + he->sticky = 1; + } + } + + /* if we still don't have a from */ + if(index == N_FROM && !*pf->addr) + *pf->addr = generate_from(); + } + + /* + * Addr is already set in the rest of the cases. + */ + else if((index == N_FROM || index == N_REPLYTO) && pf->addr){ + ADDRESS *adr = NULL; + + /* + * We get to this case of the ifelse if the from or reply-to + * addr was set by a role above. + */ + + /* figure out the default value */ + (void)set_default_hdrval(pf, custom); + if(pf->textbuf && *pf->textbuf){ + removing_trailing_white_space(pf->textbuf); + (void)removing_double_quotes(pf->textbuf); + build_address(pf->textbuf, &addr, NULL, NULL, NULL); + rfc822_parse_adrlist(&adr, addr, + ps_global->maildomain); + fs_give((void **)&addr); + } + + /* if value set by role is different from default, show it */ + if(adr && !address_is_same(*pf->addr, adr)) + he->display_it = 1; /* start this off showing */ + + /* malformed */ + if(!(*pf->addr)->mailbox){ + fs_give((void **)pf->addr); + he->display_it = 1; + } + + if(adr) + mail_free_address(&adr); + } + else if((index == N_TO || index == N_CC || index == N_BCC) + && pf->addr){ + ADDRESS *a = NULL, **tail; + + /* + * These three are different from the others because we + * might add the addresses to what is already there instead + * of replacing. + */ + + switch(set_default_hdrval(pf, custom)){ + case Replace: + if(*pf->addr) + mail_free_address(pf->addr); + + removing_trailing_white_space(pf->textbuf); + (void)removing_double_quotes(pf->textbuf); + build_address(pf->textbuf, &addr, NULL, NULL, NULL); + rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain); + fs_give((void **)&addr); + he->sticky = 1; + break; + + case Combine: + removing_trailing_white_space(pf->textbuf); + (void)removing_double_quotes(pf->textbuf); + build_address(pf->textbuf, &addr, NULL, NULL, NULL); + rfc822_parse_adrlist(&a, addr, ps_global->maildomain); + fs_give((void **)&addr); + he->sticky = 1; + if(a){ + for(tail = pf->addr; *tail; tail = &(*tail)->next) + ; + *tail = reply_cp_addr(ps_global, 0, NULL, NULL, + *pf->addr, NULL, a, RCA_ALL); + q_status_message(SM_ORDER, 3, 3, + "Adding addresses from role"); + mail_free_address(&a); + } + + break; + + case UseAsDef: + case NoMatch: + break; + } + + he->display_it = 1; /* start this off showing */ + } + else if(pf->addr){ + switch(set_default_hdrval(pf, custom)){ + case Replace: + case Combine: + if(*pf->addr) + mail_free_address(pf->addr); + + removing_trailing_white_space(pf->textbuf); + (void)removing_double_quotes(pf->textbuf); + build_address(pf->textbuf, &addr, NULL, NULL, NULL); + rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain); + fs_give((void **)&addr); + he->sticky = 1; + break; + + case UseAsDef: + case NoMatch: + break; + } + + he->display_it = 1; + } + + if(pf->addr && *pf->addr && !(*pf->addr)->mailbox){ + mail_free_address(pf->addr); + he->display_it = 1; /* start this off showing */ + } + + if(pf->textbuf) /* free default value in any case */ + fs_give((void **)&pf->textbuf); + + /* outgoing2strings will alloc the string pf->scratch below */ + he->realaddr = &pf->scratch; + break; + + case Fcc: + sending_order[NN+8] = pf; + pf_fcc = pf; + if(role && role->fcc) + fcc = role->fcc; + else + fcc = get_fcc(fcc_arg); + + if(fcc_to_free){ + fs_give((void **)&fcc_to_free); + fcc_arg = NULL; + } + + if(((flags & PS_STICKY_FCC) && fcc[0]) || (role && role->fcc)) + he->sticky = 1; + + if(role) + role->fcc = NULL; + + if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC) != 0) + he->display_it = 1; /* start this off showing */ + + he->realaddr = &fcc; + pf->text = &fcc; + he_fcc = he; + break; + + case Subject : + sending_order[NN+6] = pf; + + switch(set_default_hdrval(pf, custom)){ + case Replace: + case Combine: + pf->scratch = pf->textbuf; + pf->textbuf = NULL; + he->sticky = 1; + if(outgoing->subject) + fs_give((void **)&outgoing->subject); + + break; + + case UseAsDef: + case NoMatch: + /* if no value, use default */ + if(outgoing->subject){ + pf->scratch = cpystr(outgoing->subject); + } + else{ + pf->scratch = pf->textbuf; + pf->textbuf = NULL; + } + + break; + } + + he->realaddr = &pf->scratch; + pf->text = &outgoing->subject; + break; + + default: + q_status_message1(SM_ORDER,3,7, + "Unknown header type %d in pine_send", + (void *)pf->type); + break; + } + + /* + * We may or may not want to give the user the chance to edit + * the From and Reply-To lines. If they are listed in either + * Default-composer-hdrs or Customized-hdrs, then they can edit + * them, else no. + * If canedit is not set, that means that this header is not in + * the user's customized-hdrs. If rich_header is set, that + * means that this header is not in the user's + * default-composer-hdrs (since From and Reply-To are rich + * by default). So, don't give it an he to edit with in that case. + * + * For other types, just not setting canedit will cause it to be + * uneditable, regardless of what the user does. + */ + switch(index){ + case N_FROM: + /* to allow it, we let this fall through to the reply-to case below */ + if(ps_global->never_allow_changing_from || + (F_OFF(F_ALLOW_CHANGING_FROM, ps_global) && + !(role && role->from))){ + if(pf->canedit || !he->rich_header) + q_status_message(SM_ORDER, 3, 3, + _("Not allowed to change header \"From\"")); + + memset(he, 0, (size_t)sizeof(*he)); + pf->extdata = NULL; + break; + } + + case N_REPLYTO: + if(!pf->canedit && he->rich_header){ + memset(he, 0, (size_t)sizeof(*he)); + pf->extdata = NULL; + } + else{ + pf->canedit = 1; + he++; + } + + break; + + default: + if(!pf->canedit){ + memset(he, 0, (size_t)sizeof(*he)); + pf->extdata = NULL; + } + else + he++; + + break; + } + } + + /* + * This is so the builder can tell the composer to fill the affected + * field based on the value in the field on the left. + * + * Note that this mechanism isn't completely general. Each entry has + * only a single next_affected, so if some other entry points an + * affected entry at an entry with a next_affected, they all inherit + * that next_affected. Since this isn't used much a careful ordering + * of the affected fields should make it a sufficient mechanism. + */ + he_to->affected_entry = he_fcc; + he_news->affected_entry = he_fcc; + he_lcc->affected_entry = he_to; + he_to->next_affected = he_fcc; + + (--pf)->next = (total_cnt != fixed_cnt) ? header.custom : NULL; + + i--; /* subtract one because N_ATTCH doesn't get a sending_order slot */ + /* + * Set up headerentries for custom fields. + * NOTE: "i" is assumed to now index first custom field in sending + * order. + */ + for(pf = pf->next; pf && pf->name; pf = pf->next){ + char *addr; + + if(pf->standard) + continue; + + pf->extdata = he; + pf->canedit = 1; + pf->rcptto = 0; + pf->writehdr = 1; + pf->localcopy = 1; + + switch(pf->type){ + case Address: + if(pf->addr){ /* better be set */ + sending_order[i++] = pf; + *he = he_custom_addr_templ; + /* change default text into an ADDRESS */ + /* strip quotes around whole default */ + removing_trailing_white_space(pf->textbuf); + (void)removing_double_quotes(pf->textbuf); + build_address(pf->textbuf, &addr, NULL, NULL, NULL); + rfc822_parse_adrlist(pf->addr, addr, ps_global->maildomain); + fs_give((void **)&addr); + if(pf->textbuf) + fs_give((void **)&pf->textbuf); + + he->realaddr = &pf->scratch; + if(F_OFF(F_ENABLE_TAB_COMPLETE,ps_global)) + he->nickcmpl = NULL; + } + + break; + + case FreeText: + sending_order[i++] = pf; + *he = he_custom_free_templ; + he->realaddr = &pf->textbuf; + pf->text = &pf->textbuf; + if(((!pf->val || !pf->val[0]) && pf->textbuf && pf->textbuf[0]) || + (pf->val && (!pf->textbuf || strcmp(pf->textbuf, pf->val)))) + he->display_it = 1; /* show it */ + + break; + + default: + q_status_message1(SM_ORDER,0,7,"Unknown custom header type %d", + (void *)pf->type); + break; + } + + he->name = pf->name; + + /* use first 8 characters for prompt */ + he->prompt = cpystr(" : "); + strncpy(he->prompt, he->name, MIN(strlen(he->name), he->prwid - 2)); + + he->rich_header = view_as_rich(he->name, he->rich_header); + he++; + } + + /* + * Make sure at least *one* field is displayable... + */ + for(index = -1, i=0, pf=header.local; pf && pf->name; pf=pf->next, i++) + if(HE(pf) && !HE(pf)->rich_header){ + index = i; + break; + } + + /* + * None displayable!!! Warn and display defaults. + */ + if(index == -1){ + q_status_message(SM_ORDER,0,5, + "No default-composer-hdrs matched, displaying defaults"); + for(i = 0, pf = header.local; pf; pf = pf->next, i++) + if((i == N_TO || i == N_CC || i == N_SUBJ || i == N_ATTCH) + && HE(pf)) + HE(pf)->rich_header = 0; + } + + /* + * Save information about body which set_mime_type_by_grope might change. + * Then, if we get an error sending, we reset these things so that + * grope can do it's thing again after we edit some more. + */ + if ((*body)->type == TYPEMULTIPART) + bp = save_body_particulars(&(*body)->nested.part->body); + else + bp = save_body_particulars(*body); + + + local_redraft_pos = redraft_pos; + + /*---------------------------------------------------------------------- + Loop calling the editor until everything goes well + ----*/ + while(1){ + int saved_user_timeout; + + /* Reset body to what it was when we started. */ + if ((*body)->type == TYPEMULTIPART) + reset_body_particulars(bp, &(*body)->nested.part->body); + else + reset_body_particulars(bp,*body); + /* + * set initial cursor position based on how many times we've been + * thru the loop... + */ + if(reply && reply->pseudo){ + pbf->pine_flags |= reply->data.pico_flags; + } + else if(body_start){ + pbf->pine_flags |= P_BODY; + body_start = 0; /* maybe not next time */ + } + else if(local_redraft_pos){ + pbf->edit_offset = local_redraft_pos->offset; + /* set the start_here bit in correct header */ + for(pf = header.local; pf && pf->name; pf = pf->next) + if(strcmp(pf->name, local_redraft_pos->hdrname) == 0 + && HE(pf)){ + HE(pf)->start_here = 1; + break; + } + + /* If didn't find it, we start in body. */ + if(!pf || !pf->name) + pbf->pine_flags |= P_BODY; + } + else if(reply && (!reply->forw && !reply->forwarded)){ + pbf->pine_flags |= P_BODY; + } + + /* in case these were turned on in previous pass through loop */ + if(pf_nobody){ + pf_nobody->writehdr = 0; + pf_nobody->localcopy = 0; + } + + if(pf_fcc) + pf_fcc->localcopy = 0; + + /* + * If a sending attempt failed after we passed the message text + * thru a user-defined filter, "orig_so" points to the original + * text. Replace the body's encoded data with the original... + */ + if(orig_so){ + STORE_S **so = (STORE_S **)(((*body)->type == TYPEMULTIPART) + ? &(*body)->nested.part->body.contents.text.data + : &(*body)->contents.text.data); + so_give(so); + *so = orig_so; + orig_so = NULL; + } + + /* + * Convert the envelope and body to the string format that + * pico can edit + */ + outgoing2strings(&header, *body, &pbf->msgtext, &pbf->attachments, 0); + + for(pf = header.local; pf && pf->name; pf = pf->next){ + /* + * If this isn't the first time through this loop, we may have + * freed some of the FreeText headers below so that they wouldn't + * show up as empty headers in the finished message. Need to + * alloc them again here so they can be edited. + */ + if(pf->type == FreeText && HE(pf) && !*HE(pf)->realaddr) + *HE(pf)->realaddr = cpystr(""); + + if(pf->type != Attachment && HE(pf) && *HE(pf)->realaddr) + HE(pf)->maxlen = strlen(*HE(pf)->realaddr); + } + + /* + * If From is exposed, probably by a role, then start the cursor + * on the first line which isn't filled in. If it isn't, then we + * don't move the cursor, mostly for back-compat. + */ + if((!reply || reply->forw || reply->forwarded) && + !local_redraft_pos && !(pbf->pine_flags & P_BODY) && he_from && + (he_from->display_it || !he_from->rich_header)){ + for(pf = header.local; pf && pf->name; pf = pf->next) + if(HE(pf) && + (HE(pf)->display_it || !HE(pf)->rich_header) && + HE(pf)->realaddr && + (!*HE(pf)->realaddr || !**HE(pf)->realaddr)){ + HE(pf)->start_here = 1; + break; + } + } + +#ifdef _WINDOWS + mswin_setwindowmenu (MENU_COMPOSER); +#endif + + cancel_busy_cue(-1); + flush_status_messages(1); + + /* turn off user input timeout when in composer */ + saved_user_timeout = ps_global->hours_to_timeout; + ps_global->hours_to_timeout = 0; + dprint((1, "\n ---- COMPOSER ----\n")); + editor_result = pico(pbf); + dprint((4, "... composer returns (0x%x)\n", editor_result)); + ps_global->hours_to_timeout = saved_user_timeout; + +#ifdef _WINDOWS + mswin_setwindowmenu (MENU_DEFAULT); +#endif + fix_windsize(ps_global); + + /* + * Only reinitialize signals if we didn't receive an interesting + * one while in pico, since pico's return is part of processing that + * signal and it should continue to be ignored. + */ + if(!(editor_result & COMP_GOTHUP)) + init_signals(); /* Pico has it's own signal stuff */ + + /* + * We're going to save in DEADLETTER. Dump attachments first. + */ + if(editor_result & COMP_CANCEL) + free_attachment_list(&pbf->attachments); + + /* Turn strings back into structures */ + strings2outgoing(&header, body, pbf->attachments, flowing_requested); + + /* Make newsgroups NULL if it is "" (so won't show up in headers) */ + if(outgoing->newsgroups){ + sqzspaces(outgoing->newsgroups); + if(!outgoing->newsgroups[0]) + fs_give((void **)&(outgoing->newsgroups)); + } + + /* Make subject NULL if it is "" (so won't show up in headers) */ + if(outgoing->subject && !outgoing->subject[0]) + fs_give((void **)&(outgoing->subject)); + + /* remove custom fields that are empty */ + for(pf = header.local; pf && pf->name; pf = pf->next){ + if(pf->type == FreeText && pf->textbuf){ + if(pf->textbuf[0] == '\0'){ + fs_give((void **)&pf->textbuf); + pf->text = NULL; + } + } + } + + removing_trailing_white_space(fcc); + + /*-------- Stamp it with a current date -------*/ + if(outgoing->date) /* update old date */ + fs_give((void **)&(outgoing->date)); + + if(F_ON(F_QUELL_TIMEZONE, ps_global)) + mail_parameters(NULL, SET_DISABLE822TZTEXT, (void *) TRUE); + + rfc822_date(tmp_20k_buf); /* format and copy new date */ + if(F_ON(F_QUELL_TIMEZONE, ps_global)) + mail_parameters(NULL, SET_DISABLE822TZTEXT, (void *) FALSE); + + outgoing->date = (unsigned char *) cpystr(tmp_20k_buf); + + /* Set return_path based on From which is going to be used */ + if(outgoing->return_path) + mail_free_address(&outgoing->return_path); + + outgoing->return_path = rfc822_cpy_adr(outgoing->from); + + /* + * Don't ever believe the sender that is there. + * If From doesn't look quite right, generate our own sender. + */ + if(outgoing->sender) + mail_free_address(&outgoing->sender); + + /* + * If the LHS of the address doesn't match, or the RHS + * doesn't match one of localdomain or hostname, + * then add a sender line (really X-X-Sender). + * + * Don't add a personal_name since the user can change that. + */ + if(F_OFF(F_DISABLE_SENDER, ps_global) + && + (!outgoing->from + || !outgoing->from->mailbox + || strucmp(outgoing->from->mailbox, ps_global->VAR_USER_ID) != 0 + || !outgoing->from->host + || !(strucmp(outgoing->from->host, ps_global->localdomain) == 0 + || strucmp(outgoing->from->host, ps_global->hostname) == 0))){ + + outgoing->sender = mail_newaddr(); + outgoing->sender->mailbox = cpystr(ps_global->VAR_USER_ID); + outgoing->sender->host = cpystr(ps_global->hostname); + } + + if(ps_global->newthread){ + if(outgoing->in_reply_to) fs_give((void **)&outgoing->in_reply_to); + if(outgoing->references) fs_give((void **)&outgoing->references); + } + + /*----- Message is edited, now decide what to do with it ----*/ + if(editor_result & (COMP_SUSPEND | COMP_GOTHUP | COMP_CANCEL)){ + /*=========== Postpone or Interrupted message ============*/ + CONTEXT_S *fcc_cntxt = NULL; + char folder[MAXPATH+1]; + int fcc_result = 0; + char label[50]; + + dprint((4, "pine_send:%s handling\n", + (editor_result & COMP_SUSPEND) + ? "SUSPEND" + : (editor_result & COMP_GOTHUP) + ? "HUP" + : (editor_result & COMP_CANCEL) + ? "CANCEL" : "HUH?")); + if((editor_result & COMP_CANCEL) + && (F_ON(F_QUELL_DEAD_LETTER, ps_global) + || ps_global->deadlets == 0)){ + q_status_message(SM_ORDER, 0, 3, "Message cancelled"); + break; + } + + /* + * The idea here is to use the Fcc: writing facility + * to append to the special postponed message folder... + * + * NOTE: the strategy now is to write the message and + * all attachments as they exist at composition time. + * In other words, attachments are postponed by value + * and not reference. This may change later, but we'll + * need a local "message/external-body" type that + * outgoing2strings knows how to properly set up for + * the composer. Maybe later... + */ + + label[0] = '\0'; + + if(F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global) + && (editor_result & COMP_SUSPEND) + && (check_addresses(&header) == CA_BAD)){ + /*--- Addresses didn't check out---*/ + q_status_message(SM_ORDER, 7, 7, + _("Not allowed to postpone message until addresses are qualified")); + continue; + } + + /* + * Build the local message copy so. + * + * In the HUP case, we'll write the bezerk delimiter by + * hand and output the message directly into the folder. + * It's not only faster, we don't have to worry about + * c-client reentrance and less hands paw over the data so + * there's less chance of a problem. + * + * In the Postpone case, just create it if the user wants to + * and create a temporary storage object to write into. */ + fake_hup: + lmc.all_written = lmc.text_only = lmc.text_written = 0; + if(editor_result & (COMP_GOTHUP | COMP_CANCEL)){ + int newfile = 1; + time_t now = time((time_t *)0); + +#if defined(DOS) || defined(OS2) + /* + * we can't assume anything about root or home dirs, so + * just plunk it down in the same place as the pinerc + */ + if(!getenv("HOME")){ + char *lc = last_cmpnt(ps_global->pinerc); + folder[0] = '\0'; + if(lc != NULL){ + strncpy(folder,ps_global->pinerc, + MIN(lc-ps_global->pinerc,sizeof(folder)-1)); + folder[MIN(lc-ps_global->pinerc,sizeof(folder)-1)]='\0'; + } + + strncat(folder, (editor_result & COMP_GOTHUP) + ? INTERRUPTED_MAIL : DEADLETTER, + sizeof(folder)-strlen(folder)-1); + } + else +#endif + build_path(folder, + ps_global->VAR_OPER_DIR + ? ps_global->VAR_OPER_DIR : ps_global->home_dir, + (editor_result & COMP_GOTHUP) + ? INTERRUPTED_MAIL : DEADLETTER, + sizeof(folder)); + + if(editor_result & COMP_CANCEL){ + char filename[MAXPATH+1], newfname[MAXPATH+1], nbuf[5]; + + if(strlen(folder) + 1 < sizeof(filename)) + for(i = ps_global->deadlets - 1; i > 0 && i < 9; i--){ + strncpy(filename, folder, sizeof(filename)); + filename[sizeof(filename)-1] = '\0'; + strncpy(newfname, filename, sizeof(newfname)); + newfname[sizeof(newfname)-1] = '\0'; + + if(i > 1){ + snprintf(nbuf, sizeof(nbuf), "%d", i); + nbuf[sizeof(nbuf)-1] = '\0'; + strncat(filename, nbuf, + sizeof(filename)-strlen(filename)-1); + filename[sizeof(filename)-1] = '\0'; + } + + snprintf(nbuf, sizeof(nbuf), "%d", i+1); + nbuf[sizeof(nbuf)-1] = '\0'; + strncat(newfname, nbuf, + sizeof(newfname)-strlen(newfname)-1); + newfname[sizeof(newfname)-1] = '\0'; + (void) rename_file(filename, newfname); + } + + our_unlink(folder); + } + else + newfile = can_access(folder, ACCESS_EXISTS); + + if((lmc.so = so_get(FCC_SOURCE, NULL, WRITE_ACCESS)) != NULL){ + if (outgoing->from){ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%sFrom %s@%s %.24s\015\012", + newfile ? "" : "\015\012", + outgoing->from->mailbox, + outgoing->from->host, ctime(&now)); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + if(!so_puts(lmc.so, tmp_20k_buf)){ + if(editor_result & COMP_CANCEL) + q_status_message2(SM_ORDER | SM_DING, 3, 3, + "Can't write \"%s\": %s", + folder, error_description(errno)); + else + dprint((1, "* * * CAN'T WRITE %s: %s\n", + folder ? folder : "?", + error_description(errno))); + } + } + } + } + else{ /* Must be COMP_SUSPEND */ + if(!ps_global->VAR_POSTPONED_FOLDER + || !ps_global->VAR_POSTPONED_FOLDER[0]){ + q_status_message(SM_ORDER | SM_DING, 3, 3, + _("No postponed file defined")); + continue; + } + + /* + * Store the cursor position + * + * First find the header entry with the start_here + * bit set, if any. This means the editor is telling + * us to start on this header field next time. + */ + start_here_name = NULL; + for(pf = header.local; pf && pf->name; pf = pf->next) + if(HE(pf) && HE(pf)->start_here){ + start_here_name = pf->name; + break; + } + + /* If there wasn't one, ":" means we start in the body. */ + if(!start_here_name || !*start_here_name) + start_here_name = ":"; + + if(ps_global->VAR_FORM_FOLDER + && ps_global->VAR_FORM_FOLDER[0] + && postpone_prompt() == 'f'){ + strncpy(folder, ps_global->VAR_FORM_FOLDER, + sizeof(folder)-1); + folder[sizeof(folder)-1] = '\0'; + strncpy(label, "form letter", sizeof(label)); + label[sizeof(label)-1] = '\0'; + } + else{ + strncpy(folder, ps_global->VAR_POSTPONED_FOLDER, + sizeof(folder)-1); + folder[sizeof(folder)-1] = '\0'; + strncpy(label, "postponed message", sizeof(label)); + label[sizeof(label)-1] = '\0'; + } + + lmc.so = open_fcc(folder,&fcc_cntxt, 1, NULL, NULL); + } + + if(lmc.so){ + size_t sz; + char *lmq = NULL; + + /* copy fcc line to postponed or interrupted folder */ + if(pf_fcc) + pf_fcc->localcopy = 1; + + /* plug error into header for later display to user */ + if((editor_result & ~0xff) && (lmq = last_message_queued()) != NULL){ + pf_err->writehdr = 1; + pf_err->localcopy = 1; + pf_err->textbuf = lmq; + } + + /* + * if reply, write (UID)folder header field so we can + * later flag the replied-to message \\ANSWERED + * DON'T save MSGNO's. + */ + if(reply && reply->uid){ + char uidbuf[MAILTMPLEN], *p; + long i; + + for(i = 0L, p = tmp_20k_buf; reply->data.uid.msgs[i]; i++){ + if(i) + sstrncpy(&p, ",", SIZEOF_20KBUF-(p-tmp_20k_buf)); + + sstrncpy(&p,ulong2string(reply->data.uid.msgs[i]),SIZEOF_20KBUF-(p-tmp_20k_buf)); + } + + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + pf_uid->writehdr = 1; + pf_uid->localcopy = 1; + snprintf(uidbuf, sizeof(uidbuf), "(%s%s%s)(%ld %lu %s)%s", + reply->prefix ? int2string(strlen(reply->prefix)) + : (reply->forwarded) ? "": "0 ", + reply->prefix ? " " : "", + reply->prefix ? reply->prefix : "", + i, reply->data.uid.validity, + tmp_20k_buf, reply->mailbox); + uidbuf[sizeof(uidbuf)-1] = '\0'; + pf_uid->textbuf = cpystr(uidbuf); + + /* + * Logically, this ought to be part of pf_uid, but this + * was added later and so had to be in a separate header + * for backwards compatibility. + */ + pf_mbox->writehdr = 1; + pf_mbox->localcopy = 1; + pf_mbox->textbuf = cpystr(reply->origmbox + ? reply->origmbox + : reply->mailbox); + } + + /* Save cursor position */ + if(start_here_name && *start_here_name){ + char curposbuf[MAILTMPLEN]; + + pf_curpos->writehdr = 1; + pf_curpos->localcopy = 1; + snprintf(curposbuf, sizeof(curposbuf), "%s %ld", start_here_name, + pbf->edit_offset); + curposbuf[sizeof(curposbuf)-1] = '\0'; + pf_curpos->textbuf = cpystr(curposbuf); + } + + /* + * Work around c-client reply-to bug. C-client will + * return a reply_to in an envelope even if there is + * no reply-to header field. We want to note here whether + * the reply-to is real or not. + */ + if(outgoing->reply_to || hdr_is_in_list("reply-to", custom)){ + pf_ourrep->writehdr = 1; + pf_ourrep->localcopy = 1; + if(outgoing->reply_to) + pf_ourrep->textbuf = cpystr("Full"); + else + pf_ourrep->textbuf = cpystr("Empty"); + } + + /* Save the role-specific smtp server */ + if(role && role->smtp && role->smtp[0]){ + char *q, *smtp = NULL; + char **lp; + size_t len = 0; + + /* + * Turn the list of smtp servers into a space- + * delimited list in a single string. + */ + for(lp = role->smtp; (q = *lp) != NULL; lp++) + len += (strlen(q) + 1); + + if(len){ + smtp = (char *) fs_get(len * sizeof(char)); + smtp[0] = '\0'; + for(lp = role->smtp; (q = *lp) != NULL; lp++){ + if(lp != role->smtp) + strncat(smtp, " ", len-strlen(smtp)-1); + + strncat(smtp, q, len-strlen(smtp)-1); + } + + smtp[len-1] = '\0'; + } + + pf_smtp_server->writehdr = 1; + pf_smtp_server->localcopy = 1; + if(smtp) + pf_smtp_server->textbuf = smtp; + else + pf_smtp_server->textbuf = cpystr(""); + } + + /* Save the role-specific nntp server */ + if(suggested_nntp_server || + (role && role->nntp && role->nntp[0])){ + char *q, *nntp = NULL; + char **lp; + size_t len = 0; + + if(role && role->nntp && role->nntp[0]){ + /* + * Turn the list of nntp servers into a space- + * delimited list in a single string. + */ + for(lp = role->nntp; (q = *lp) != NULL; lp++) + len += (strlen(q) + 1); + + if(len){ + nntp = (char *) fs_get(len * sizeof(char)); + nntp[0] = '\0'; + for(lp = role->nntp; (q = *lp) != NULL; lp++){ + if(lp != role->nntp) + strncat(nntp, " ", len-strlen(nntp)-1); + + strncat(nntp, q, len-strlen(nntp)-1); + } + + nntp[len-1] = '\0'; + } + } + else + nntp = cpystr(suggested_nntp_server); + + pf_nntp_server->writehdr = 1; + pf_nntp_server->localcopy = 1; + if(nntp) + pf_nntp_server->textbuf = nntp; + else + pf_nntp_server->textbuf = cpystr(""); + } + + /* + * Write the list of custom headers to the + * X-Our-Headers header so that we can recover the + * list in redraft. + */ + sz = 0; + for(pf = header.custom; pf && pf->name; pf = pf->next) + sz += strlen(pf->name) + 1; + + if(sz){ + char *q; + + pf_ourhdrs->writehdr = 1; + pf_ourhdrs->localcopy = 1; + pf_ourhdrs->textbuf = (char *)fs_get(sz); + memset(pf_ourhdrs->textbuf, 0, sz); + q = pf_ourhdrs->textbuf; + for(pf = header.custom; pf && pf->name; pf = pf->next){ + if(pf != header.custom) + sstrncpy(&q, ",", sz-(q-pf_ourhdrs->textbuf)); + + sstrncpy(&q, pf->name, sz-(q-pf_ourhdrs->textbuf)); + } + + pf_ourhdrs->textbuf[sz-1] = '\0';; + } + + /* + * We need to make sure any header values that got cleared + * get written to the postponed message (they won't if + * pf->text is NULL). Otherwise, we can't tell previously + * non-existent custom headers or default values from + * custom (or other) headers that got blanked in the + * composer... + */ + for(pf = header.local; pf && pf->name; pf = pf->next) + if(pf->type == FreeText && HE(pf) && !*(HE(pf)->realaddr)) + *(HE(pf)->realaddr) = cpystr(""); + + /* + * We're saving the message for use later. It may be that the + * characters in the message are not all convertible to the + * user's posting_charmap. We'll save it as UTF-8 instead + * and worry about that the next time they try to send it. + * Use a different save pointer just to be sure we don't + * mess up the other stuff. We should probably make the + * charset an argument. + * + * We also need to fix the charset of the body part + * the user is editing so that we can read it back + * successfully when we resume the composition. + */ + ps_global->post_utf8 = 1; + + { + PARAMETER *pm; + BODY *bp; + if((*body)->type == TYPEMULTIPART) + bp = &(*body)->nested.part->body; + else + bp = *body; + + for(pm = bp->parameter; + pm && strucmp(pm->attribute, "charset") != 0; + pm = pm->next) + ; + + if(pm){ + if(pm->value) + fs_give((void **) &pm->value); + + pm->value = cpystr("UTF-8"); + } + } + + if(pine_rfc822_output(&header,*body,NULL,NULL) >= 0L){ + if(editor_result & (COMP_GOTHUP | COMP_CANCEL)){ + char *err; + STORE_S *hup_so; + gf_io_t gc, pc; + int we_cancel = 0; + + if(editor_result & COMP_CANCEL){ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, + "Saving to \"%s\"", folder); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + we_cancel = busy_cue((char *)tmp_20k_buf, NULL, 1); + } + + if((hup_so = + so_get(FileStar, folder, WRITE_ACCESS|OWNER_ONLY)) != NULL){ + gf_set_so_readc(&gc, lmc.so); + gf_set_so_writec(&pc, hup_so); + so_seek(lmc.so, 0L, 0); /* read msg copy and */ + so_seek(hup_so, 0L, 2); /* append to folder */ + gf_filter_init(); + gf_link_filter(gf_nvtnl_local, NULL); + if(!(fcc_result = !(err = gf_pipe(gc, pc)))) + dprint((1, "*** PIPE FAILED: %s\n", + err ? err : "?")); + + gf_clear_so_readc(lmc.so); + gf_clear_so_writec(hup_so); + so_give(&hup_so); + } + else + dprint((1, "*** CAN'T CREATE %s: %s\n", + folder ? folder : "?", + error_description(errno))); + + if(we_cancel) + cancel_busy_cue(-1); + } + else + fcc_result = write_fcc(folder, fcc_cntxt, + lmc.so, NULL, label, NULL); + } + + /* discontinue coerced UTF-8 posting */ + ps_global->post_utf8 = 0; + + so_give(&lmc.so); + } + else + dprint((1, "***CAN'T ALLOCATE temp store: %s ", + error_description(errno))); + + if(editor_result & COMP_GOTHUP){ + /* + * Special Hack #291: if any hi-byte bits are set in + * editor's result, we put them there. + */ + if(editor_result & 0xff00) + exit(editor_result >> 8); + + dprint((1, "Save composition on HUP %sED\n", + fcc_result ? "SUCCEED" : "FAIL")); + hup_signal(); /* Do what we normally do on SIGHUP */ + } + else if((editor_result & COMP_SUSPEND) && fcc_result){ + if(ps_global->VAR_FORM_FOLDER + && ps_global->VAR_FORM_FOLDER[0] + && !strcmp(folder, ps_global->VAR_FORM_FOLDER)) + q_status_message(SM_ORDER, 0, 3, + _("Composition saved to Form Letter Folder. Select Compose to send.")); + else + q_status_message(SM_ORDER, 0, 3, + _("Composition postponed. Select Compose to resume.")); + + break; /* postpone went OK, get out of here */ + } + else if(editor_result & COMP_CANCEL){ + char *lc = NULL; + + if(fcc_result && folder) + lc = last_cmpnt(folder); + + q_status_message3(SM_ORDER, 0, 3, + _("Message cancelled%s%s%s"), + (lc && *lc) ? " and copied to \"" : "", + (lc && *lc) ? lc : "", + (lc && *lc) ? "\" file" : ""); + break; + } + else{ + q_status_message(SM_ORDER, 0, 4, + _("Continuing composition. Message not postponed or sent")); + body_start = 1; + continue; /* postpone failed, jump back in to composer */ + } + } + else{ + /*------ Must be sending mail or posting ! -----*/ + int result, valid_addr, continue_with_only_fcc = 0; + CONTEXT_S *fcc_cntxt = NULL; + + result = 0; + dprint((4, "=== sending: ")); + + /* --- If posting, confirm with user ----*/ + if(outgoing->newsgroups && *outgoing->newsgroups + && F_OFF(F_QUELL_EXTRA_POST_PROMPT, ps_global) + && want_to(POST_PMT, 'n', 'n', NO_HELP, WT_NORM) == 'n'){ + q_status_message(SM_ORDER, 0, 3, _("Message not posted")); + dprint((4, "no post, continuing\n")); + continue; + } + + if(!(outgoing->to || outgoing->cc || outgoing->bcc || lcc_addr + || outgoing->newsgroups)){ + if(fcc && fcc[0]){ + if(F_OFF(F_AUTO_FCC_ONLY, ps_global) && + want_to(_("No recipients, really copy only to Fcc "), + 'n', 'n', h_send_fcc_only, WT_NORM) != 'y') + continue; + + continue_with_only_fcc++; + } + else{ + q_status_message(SM_ORDER, 3, 4, + _("No recipients specified!")); + dprint((4, "no recip, continuing\n")); + continue; + } + } + + if((valid_addr = check_addresses(&header)) == CA_BAD){ + /*--- Addresses didn't check out---*/ + dprint((4, "addrs failed, continuing\n")); + continue; + } + + if(F_ON(F_WARN_ABOUT_NO_TO_OR_CC, ps_global) + && !continue_with_only_fcc + && !(outgoing->to || outgoing->cc || lcc_addr + || outgoing->newsgroups) + && (want_to(_("No To, Cc, or Newsgroup specified, send anyway "), + 'n', 'n', h_send_check_to_cc, WT_NORM) != 'y')){ + dprint((4, "No To or CC or Newsgroup, continuing\n")); + if(local_redraft_pos && local_redraft_pos != redraft_pos) + free_redraft_pos(&local_redraft_pos); + + local_redraft_pos + = (REDRAFT_POS_S *) fs_get(sizeof(*local_redraft_pos)); + memset((void *) local_redraft_pos,0,sizeof(*local_redraft_pos)); + local_redraft_pos->hdrname = cpystr(TONAME); + continue; + } + + if(F_ON(F_WARN_ABOUT_NO_SUBJECT, ps_global) + && check_for_subject(&header) == CF_MISSING){ + dprint((4, "No subject, continuing\n")); + if(local_redraft_pos && local_redraft_pos != redraft_pos) + free_redraft_pos(&local_redraft_pos); + + local_redraft_pos + = (REDRAFT_POS_S *) fs_get(sizeof(*local_redraft_pos)); + memset((void *) local_redraft_pos,0,sizeof(*local_redraft_pos)); + local_redraft_pos->hdrname = cpystr(SUBJNAME); + continue; + } + + if(F_ON(F_WARN_ABOUT_NO_FCC, ps_global) + && check_for_fcc(fcc) == CF_MISSING){ + dprint((4, "No fcc, continuing\n")); + if(local_redraft_pos && local_redraft_pos != redraft_pos) + free_redraft_pos(&local_redraft_pos); + + local_redraft_pos + = (REDRAFT_POS_S *) fs_get(sizeof(*local_redraft_pos)); + memset((void *) local_redraft_pos,0,sizeof(*local_redraft_pos)); + local_redraft_pos->hdrname = cpystr("Fcc"); + continue; + } + + set_last_fcc(fcc); + + /*---- Check out fcc -----*/ + if(fcc && *fcc){ + /* + * If special name "inbox" then replace it with the + * real inbox path. + */ + if(ps_global->VAR_INBOX_PATH + && strucmp(fcc, ps_global->inbox_name) == 0){ + char *replace_fcc; + + replace_fcc = cpystr(ps_global->VAR_INBOX_PATH); + fs_give((void **)&fcc); + fcc = replace_fcc; + } + + lmc.all_written = lmc.text_written = 0; + /* lmc.text_only set on command line */ + if(!(lmc.so = open_fcc(fcc, &fcc_cntxt, 0, NULL, NULL))){ + /* ---- Open or allocation of fcc failed ----- */ + dprint((4,"can't open/allocate fcc, cont'g\n")); + + /* + * Find field entry associated with fcc, and start + * composer on it... + */ + for(pf = header.local; pf && pf->name; pf = pf->next) + if(pf->type == Fcc && HE(pf)) + HE(pf)->start_here = 1; + + continue; + } + else + so_truncate(lmc.so, fcc_size_guess(*body) + 2048); + } + else + lmc.so = NULL; + + /*---- Take care of any requested prefiltering ----*/ + if(sending_filter_requested + && !filter_message_text(sending_filter_requested, outgoing, + *body, &orig_so, &header)){ + q_status_message1(SM_ORDER, 3, 3, + _("Problem filtering! Nothing sent%s."), + fcc ? " or saved to fcc" : ""); + continue; + } + + /*------ Actually post -------*/ + if(outgoing->newsgroups){ + char **alt_nntp = NULL, *alt_nntp_p[2]; + if(((role && role->nntp) + || suggested_nntp_server)){ + if(ps_global->FIX_NNTP_SERVER + && ps_global->FIX_NNTP_SERVER[0]) + q_status_message(SM_ORDER | SM_DING, 5, 5, + "Using nntp-server that is administratively fixed"); + else if(role && role->nntp) + alt_nntp = role->nntp; + else{ + alt_nntp_p[0] = suggested_nntp_server; + alt_nntp_p[1] = NULL; + alt_nntp = alt_nntp_p; + } + } + if(news_poster(&header, *body, alt_nntp, pipe_callback) < 0){ + dprint((1, "Post failed, continuing\n")); + if(outgoing->message_id) + fs_give((void **) &outgoing->message_id); + + outgoing->message_id = generate_message_id(); + + continue; + } + else + result |= P_NEWS_WIN; + } + + /* + * BUG: IF we've posted the message *and* an fcc was specified + * then we've already got a neatly formatted message in the + * lmc.so. It'd be nice not to have to re-encode everything + * to insert it into the smtp slot... + */ + + /* + * Turn on "undisclosed recipients" header if no To or cc. + */ + if(!(outgoing->to || outgoing->cc) + && (outgoing->bcc || lcc_addr) && pf_nobody && pf_nobody->addr){ + pf_nobody->writehdr = 1; + pf_nobody->localcopy = 1; + } + + if(priority_requested){ + (void) set_priority_header(&header, priority_requested); + fs_give((void **) &priority_requested); + } + +#if defined(BACKGROUND_POST) && defined(SIGCHLD) + /* + * If requested, launch backgroud posting... + */ + if(background_requested && !(call_mailer_flags & CM_VERBOSE)){ + ps_global->post = (POST_S *)fs_get(sizeof(POST_S)); + memset(ps_global->post, 0, sizeof(POST_S)); + if(fcc) + ps_global->post->fcc = cpystr(fcc); + + if((ps_global->post->pid = fork()) == 0){ + /* + * Put us in new process group... + */ + setpgrp(0, ps_global->post->pid); + + /* BUG: should fix argv[0] to indicate what we're up to */ + + /* + * If there are any live streams, pretend we never + * knew them. Problem is two processes writing + * same server process. + * This is not clean but we're just going to exit + * right away anyway. We just want to be sure to leave + * the stuff that the parent is going to use alone. + * The next three lines will disable the re-use of the + * existing streams and cause us to open a new one if + * needed. + */ + ps_global->mail_stream = NULL; + ps_global->s_pool.streams = NULL; + ps_global->s_pool.nstream = 0; + + /* quell any display output */ + ps_global->in_init_seq = 1; + + /*------- Actually mail the message ------*/ + if(valid_addr == CA_OK + && (outgoing->to || outgoing->cc + || outgoing->bcc || lcc_addr)){ + char **alt_smtp = NULL; + + if(role && role->smtp){ + if(ps_global->FIX_SMTP_SERVER + && ps_global->FIX_SMTP_SERVER[0]) + q_status_message(SM_ORDER | SM_DING, 5, 5, "Use of a role-defined smtp-server is administratively prohibited"); + else + alt_smtp = role->smtp; + } + + result |= (call_mailer(&header, *body, alt_smtp, + call_mailer_flags, + call_mailer_file_result, + pipe_callback) > 0) + ? P_MAIL_WIN : P_MAIL_LOSE; + + if(result & P_MAIL_LOSE) + mark_address_failure_for_pico(&header); + } + + /*----- Was there an fcc involved? -----*/ + if(lmc.so){ + /*------ Write it if at least something worked ------*/ + if((result & (P_MAIL_WIN | P_NEWS_WIN)) + || (!(result & (P_MAIL_BITS | P_NEWS_BITS)) + && pine_rfc822_output(&header, *body, + NULL, NULL))){ + char label[50]; + + strncpy(label, "Fcc", sizeof(label)); + label[sizeof(label)-1] = '\0'; + if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){ + snprintf(label + 3, sizeof(label)-3, " to %s", fcc); + label[sizeof(label)-1] = '\0'; + } + + /*-- Now actually copy to fcc folder and close --*/ + result |= (write_fcc(fcc, fcc_cntxt, lmc.so, + NULL, label, + F_ON(F_MARK_FCC_SEEN, ps_global) + ? "\\SEEN" : NULL)) + ? P_FCC_WIN : P_FCC_LOSE; + } + else if(!(result & (P_MAIL_BITS | P_NEWS_BITS))){ + q_status_message(SM_ORDER, 3, 5, + _("Fcc Failed!. No message saved.")); + dprint((1, + "explicit fcc write failed!\n")); + result |= P_FCC_LOSE; + } + + so_give(&lmc.so); + } + + if(result & (P_MAIL_LOSE | P_NEWS_LOSE | P_FCC_LOSE)){ + /* + * Encode child's result in hi-byte of + * editor's result + */ + editor_result = ((result << 8) | COMP_GOTHUP); + goto fake_hup; + } + + exit(result); + } + + if(ps_global->post->pid > 0){ + q_status_message(SM_ORDER, 3, 3, + _("Message handed off for posting")); + break; /* up to our child now */ + } + else{ + q_status_message1(SM_ORDER | SM_DING, 3, 3, + "Can't fork for send: %s", + error_description(errno)); + if(ps_global->post->fcc) + fs_give((void **) &ps_global->post->fcc); + + fs_give((void **) &ps_global->post); + } + + if(lmc.so) /* throw away unused store obj */ + so_give(&lmc.so); + + if(outgoing->message_id) + fs_give((void **) &outgoing->message_id); + + outgoing->message_id = generate_message_id(); + + continue; /* if we got here, there was a prob */ + } +#endif /* BACKGROUND_POST */ + + /*------- Actually mail the message ------*/ + if(valid_addr == CA_OK + && (outgoing->to || outgoing->cc || outgoing->bcc || lcc_addr)){ + char **alt_smtp = NULL; + + if(role && role->smtp){ + if(ps_global->FIX_SMTP_SERVER + && ps_global->FIX_SMTP_SERVER[0]) + q_status_message(SM_ORDER | SM_DING, 5, 5, "Use of a role-defined smtp-server is administratively prohibited"); + else + alt_smtp = role->smtp; + } + + result |= (call_mailer(&header, *body, alt_smtp, + call_mailer_flags, + call_mailer_file_result, + pipe_callback) > 0) + ? P_MAIL_WIN : P_MAIL_LOSE; + + if(result & P_MAIL_LOSE) + mark_address_failure_for_pico(&header); + } + + /*----- Was there an fcc involved? -----*/ + if(lmc.so){ + /*------ Write it if at least something worked ------*/ + if((result & (P_MAIL_WIN | P_NEWS_WIN)) + || (!(result & (P_MAIL_BITS | P_NEWS_BITS)) + && pine_rfc822_output(&header, *body, NULL, NULL))){ + char label[50]; + + strncpy(label, "Fcc", sizeof(label)); + label[sizeof(label)-1] = '\0'; + if(strcmp(fcc, ps_global->VAR_DEFAULT_FCC)){ + snprintf(label + 3, sizeof(label)-3, " to %s", fcc); + label[sizeof(label)-1] = '\0'; + } + + /*-- Now actually copy to fcc folder and close --*/ + result |= (write_fcc(fcc, fcc_cntxt, lmc.so, NULL, label, + F_ON(F_MARK_FCC_SEEN, ps_global) + ? "\\SEEN" : NULL)) + ? P_FCC_WIN : P_FCC_LOSE; + } + else if(!(result & (P_MAIL_BITS | P_NEWS_BITS))){ + q_status_message(SM_ORDER,3,5, + _("Fcc Failed!. No message saved.")); + dprint((1, "explicit fcc write failed!\n")); + result |= P_FCC_LOSE; + } + + so_give(&lmc.so); + } + + /*----- Mail Post FAILED, back to composer -----*/ + if(result & (P_MAIL_LOSE | P_FCC_LOSE)){ + dprint((1, "Send failed, continuing\n")); + + if(result & P_FCC_LOSE){ + /* + * Find field entry associated with fcc, and start + * composer on it... + */ + for(pf = header.local; pf && pf->name; pf = pf->next) + if(pf->type == Fcc && HE(pf)) + HE(pf)->start_here = 1; + + q_status_message(SM_ORDER | SM_DING, 3, 3, + pine_send_status(result, fcc, + tmp_20k_buf, SIZEOF_20KBUF, NULL)); + } + + if(outgoing->message_id) + fs_give((void **) &outgoing->message_id); + + outgoing->message_id = generate_message_id(); + + continue; + } + + /* + * If message sent *completely* successfully, there's a + * reply struct AND we're allowed to write back state, do it. + * But also protect against shifted message numbers due + * to new mail arrival. Since the number passed is based + * on the real imap msg no, AND we're sure no expunge has + * been done, just fix up the sorted number... + */ + update_answered_flags(reply); + + /*----- Signed, sealed, delivered! ------*/ + q_status_message(SM_ORDER, 0, 3, + pine_send_status(result, fcc, tmp_20k_buf, SIZEOF_20KBUF, NULL)); + + break; /* All's well, pop out of here */ + } + } + + if(orig_so) + so_give(&orig_so); + + if(fcc) + fs_give((void **)&fcc); + + free_body_particulars(bp); + + free_attachment_list(&pbf->attachments); + + standard_picobuf_teardown(pbf); + + for(i=0; i < fixed_cnt; i++){ + if(pfields[i].textbuf) + fs_give((void **)&pfields[i].textbuf); + + fs_give((void **)&pfields[i].name); + } + + if(lcc_addr) + mail_free_address(&lcc_addr); + + if(nobody_addr) + mail_free_address(&nobody_addr); + + free_prompts(header.custom); + free_customs(header.custom); + fs_give((void **)&pfields); + free_headents(&headents); + fs_give((void **)&sending_order); + if(suggested_nntp_server) + fs_give((void **)&suggested_nntp_server); + if(title) + fs_give((void **)&title); + + if(local_redraft_pos && local_redraft_pos != redraft_pos) + free_redraft_pos(&local_redraft_pos); + + pbf = save_previous_pbuf; + g_rolenick = NULL; + + dprint((4, "=== send returning ===\n")); +} + + +/* + * Check for subject in outgoing message. + * + * Asks user whether to proceed with no subject. + */ +int +check_for_subject(METAENV *header) +{ + PINEFIELD *pf; + int rv = CF_OK; + + for(pf = header->local; pf && pf->name; pf = pf->next) + if(pf->type == Subject){ + if(pf->text && *pf->text && **pf->text) + rv = CF_OK; + else{ + if(want_to("No Subject, send anyway ", + 'n', 'n', h_send_check_subj, WT_NORM) == 'y') + rv = CF_OK; + else + rv = CF_MISSING; + } + + break; + } + + + return(rv); +} + + +/* + * Check for fcc in outgoing message. + * + * Asks user whether to proceed with no fcc. + */ +int +check_for_fcc(char *fcc) +{ + int rv = CF_OK; + + if(fcc && *fcc) + rv = CF_OK; + else{ + if(want_to("No Fcc, send anyway ", 'n', 'n', h_send_check_fcc, WT_NORM) == 'y') + rv = CF_OK; + else + rv = CF_MISSING; + } + + return(rv); +} + + +/* + * Confirm that the user wants to send to MAILER-DAEMON + */ +int +confirm_daemon_send(void) +{ + return(want_to("Really send this message to the MAILER-DAEMON", + 'n', 'n', NO_HELP, WT_NORM) == 'y'); +} + + +void +free_prompts(PINEFIELD *head) +{ + PINEFIELD *pf; + + for(pf = head; pf && pf->name; pf = pf->next){ + if(HE(pf) && HE(pf)->prompt) + fs_give((void **)& HE(pf)->prompt); + } +} + + +int +postpone_prompt(void) +{ + static ESCKEY_S pstpn_form_opt[] = { {'p', 'p', "P", N_("Postponed Folder")}, + {'f', 'f', "F", N_("Form Letter Folder")}, + {-1, 0, NULL, NULL} }; + + return(radio_buttons(PSTPN_FORM_PMT, -FOOTER_ROWS(ps_global), + pstpn_form_opt, 'p', 0, NO_HELP, RB_FLUSH_IN)); +} + + +/* + * call__mailer_file_result - some results from call_mailer might be in a file. + * dislplay that file. + */ +void +call_mailer_file_result(char *filename, int style) +{ + if(filename){ + if(style & CM_BR_VERBOSE){ + display_output_file(filename, "Verbose SMTP Interaction", NULL, DOF_BRIEF); + } + else{ + display_output_file(filename, "POSTING ERRORS", "Posting Error", DOF_EMPTY); + } + } +} + +void +mark_address_failure_for_pico(METAENV *header) +{ + PINEFIELD *pf; + ADDRESS *a; + int error_count = 0; + struct headerentry *last_he = NULL; + + for(pf = header->local; pf && pf->name; pf = pf->next) + if(pf->type == Address && pf->rcptto && pf->addr && *pf->addr) + for(a = *pf->addr; a != NULL; a = a->next) + if(a->error != NULL + && error_count++ < MAX_ADDR_ERROR + && (HE(pf))){ + if(last_he) /* start last reported err */ + last_he->start_here = 0; + + (last_he = HE(pf))->start_here = 1; + } +} + + + +/* + * This is specialized routine. It assumes that the only things that we + * care about restoring are the body type, subtype, encoding and the + * state of the charset parameter. It also assumes that if the charset + * parameter exists when we save it, it won't be removed later. + */ +BODY_PARTICULARS_S * +save_body_particulars(struct mail_bodystruct *body) +{ + BODY_PARTICULARS_S *bp; + PARAMETER *pm; + + bp = (BODY_PARTICULARS_S *)fs_get(sizeof(BODY_PARTICULARS_S)); + + bp->type = body->type; + bp->encoding = body->encoding; + bp->subtype = body->subtype ? cpystr(body->subtype) : NULL; + bp->parameter = body->parameter; + for(pm = bp->parameter; + pm && strucmp(pm->attribute, "charset") != 0; + pm = pm->next) + ;/* searching for possible charset parameter */ + + if(pm){ /* found one */ + bp->had_csp = 1; /* saved body had charset parameter */ + bp->charset = pm->value ? cpystr(pm->value) : NULL; + } + else{ + bp->had_csp = 0; + bp->charset = NULL; + } + + return(bp); +} + + +void +reset_body_particulars(BODY_PARTICULARS_S *bp, struct mail_bodystruct *body) +{ + body->type = bp->type; + body->encoding = bp->encoding; + if(body->subtype) + fs_give((void **)&body->subtype); + + body->subtype = bp->subtype ? cpystr(bp->subtype) : NULL; + + if(bp->parameter){ + PARAMETER *pm, *pm_prev = NULL; + + for(pm = body->parameter; + pm && strucmp(pm->attribute, "charset") != 0; + pm = pm->next) + pm_prev = pm; + + if(pm){ /* body has charset parameter */ + if(bp->had_csp){ /* reset to what it used to be */ + if(pm->value) + fs_give((void **)&pm->value); + + pm->value = bp->charset ? cpystr(bp->charset) : NULL; + } + else{ /* remove charset parameter */ + if(pm_prev) + pm_prev->next = pm->next; + else + body->parameter = pm->next; + + mail_free_body_parameter(&pm); + } + } + else{ + if(bp->had_csp){ + /* + * This can't happen because grope never removes + * the charset parameter. + */ + q_status_message(SM_ORDER | SM_DING, 5, 5, +"Programmer error: saved charset but no current charset param in pine_send"); + } + /* + else{ + ok, still no parameter + } + */ + } + } + else{ + if(body->parameter) + mail_free_body_parameter(&body->parameter); + + body->parameter = NULL; + } +} + + +void +free_body_particulars(BODY_PARTICULARS_S *bp) +{ + if(bp){ + if(bp->subtype) + fs_give((void **)&bp->subtype); + + if(bp->charset) + fs_give((void **)&bp->charset); + + fs_give((void **)&bp); + } +} + + +/*---------------------------------------------------------------------- + Build a status message suitable for framing + +Returns: pointer to resulting buffer + ---*/ +char * +pine_send_status(int result, char *fcc_name, char *buf, size_t buflen, int *goodorbad) +{ + int avail = ps_global->ttyo->screen_cols - 2; + int fixedneed, need, lenfcc; + char *part1, *part2, *part3, *part4, *part5; + char fbuf[MAILTMPLEN+1]; + + part1 = (result & P_NEWS_WIN) + ? "posted" + : (result & P_NEWS_LOSE) + ? "NOT POSTED" + : ""; + part2 = ((result & P_NEWS_BITS) && (result & P_MAIL_BITS) + && (result & P_FCC_BITS)) + ? ", " + : ((result & P_NEWS_BITS) && (result & (P_MAIL_BITS | P_FCC_BITS))) + ? " and " + : ""; + part3 = (result & P_MAIL_WIN) + ? "sent" + : (result & P_MAIL_LOSE) + ? "NOT SENT" + : ""; + part4 = ((result & P_MAIL_BITS) && (result & P_FCC_BITS)) + ? " and " + : ""; + part5 = ((result & P_FCC_WIN) && !(result & (P_MAIL_WIN | P_NEWS_WIN))) + ? "ONLY copied to " + : (result & P_FCC_WIN) + ? "copied to " + : (result & P_FCC_LOSE) + ? "NOT copied to " + : ""; + lenfcc = MIN(sizeof(fbuf)-1, (result & P_FCC_BITS) ? strlen(fcc_name) : 0); + + fixedneed = 9 + strlen(part1) + strlen(part2) + strlen(part3) + + strlen(part4) + strlen(part5); + need = fixedneed + ((result & P_FCC_BITS) ? 2 : 0) + lenfcc; + + if(need > avail && fixedneed + 3 >= avail){ + /* dots on end of fixed, no fcc */ + snprintf(fbuf, sizeof(fbuf), "Message %s%s%s%s%s ", + part1, part2, part3, part4, part5); + short_str(fbuf, buf, buflen, avail, EndDots); + } + else if(need > avail){ + /* include whole fixed part, quotes and dots at end of fcc name */ + if(lenfcc > 0) + lenfcc = MAX(1, lenfcc-(need-avail)); + + snprintf(buf, buflen, "Message %s%s%s%s%s%s%s%s.", + part1, part2, part3, part4, part5, + (result & P_FCC_BITS) ? "\"" : "", + short_str((result & P_FCC_BITS) ? fcc_name : "", + fbuf, sizeof(fbuf), lenfcc, FrontDots), + (result & P_FCC_BITS) ? "\"" : ""); + } + else{ + /* whole thing */ + snprintf(buf, buflen, "Message %s%s%s%s%s%s%s%s.", + part1, part2, part3, part4, part5, + (result & P_FCC_BITS) ? "\"" : "", + (result & P_FCC_BITS) ? fcc_name : "", + (result & P_FCC_BITS) ? "\"" : ""); + } + + if(goodorbad) + *goodorbad = (result & (P_MAIL_LOSE | P_NEWS_LOSE | P_FCC_LOSE)) == 0; + + return(buf); +} + +/* Callback from Pico to set the conditions for Alpine to start a new thread + */ + +void +new_thread_on_blank_subject(void) +{ + ps_global->newthread = F_ON(F_NEW_THREAD_ON_BLANK_SUBJECT, ps_global); +} + + + +/*---------------------------------------------------------------------- + Call back for pico to insert the specified message's text + +Args: n -- message number to format + f -- function to use to output the formatted message + + +Returns: returns msg number formatted on success, zero on error. +----*/ +long +message_format_for_pico(long int n, int (*f) (int)) +{ + ENVELOPE *e; + BODY *b; + char *old_quote = NULL; + long rv = n; + + if(!(n > 0L && n <= mn_get_total(ps_global->msgmap) + && (e = pine_mail_fetchstructure(ps_global->mail_stream, + mn_m2raw(ps_global->msgmap, n), &b)))){ + q_status_message(SM_ORDER|SM_DING,3,3,"Error inserting Message"); + flush_status_messages(0); + return(0L); + } + + /* temporarily assign a new quote string */ + old_quote = pbf->quote_str; + pbf->quote_str = reply_quote_str(e); + + /* build separator line */ + reply_delimiter(e, NULL, f); + + /* actually write message text */ + if(!format_message(mn_m2raw(ps_global->msgmap, n), e, b, NULL, + FM_NEW_MESS | FM_DISPLAY | FM_NOCOLOR | FM_NOINDENT, f)){ + q_status_message(SM_ORDER|SM_DING,3,3,"Error inserting Message"); + flush_status_messages(0); + rv = 0L; + } + + fs_give((void **)&pbf->quote_str); + pbf->quote_str = old_quote; + return(rv); +} + + +/*---------------------------------------------------------------------- + Call back for pico to prompt the user for exit confirmation + +Args: dflt -- default answer for confirmation prompt + +Returns: either NULL if the user accepts exit, or string containing + reason why the user declined. +----*/ +int +send_exit_for_pico(struct headerentry *he, void (*redraw_pico)(void), int allow_flowed, + char **result) +{ + int i, rv, c, verbose_label = 0, bg_label = 0, old_suspend; + int dsn_label = 0, fcc_label = 0, lparen; + int flowing_label = 0, double_rad; + char *rstr = NULL, *p, *lc, *optp; + char dsn_string[30]; + void (*redraw)(void) = ps_global->redrawer; + ESCKEY_S opts[18]; + struct filters { + char *filter; + int index; + struct filters *prev, *next; + } *filters = NULL, *fp; + + sending_filter_requested = NULL; + call_mailer_flags = 0; + background_requested = 0; + flowing_requested = allow_flowed ? 1 : 0; + lmc.text_only = F_ON(F_NO_FCC_ATTACH, ps_global) != 0; + if(priority_requested) + fs_give((void **) &priority_requested); + + if(background_posting(FALSE)){ + if(result) + *result = "Can't send while background posting. Use postpone."; + + return(1); + } + + if(F_ON(F_SEND_WO_CONFIRM, ps_global)){ + if(result) + *result = NULL; + + return(0); + } + + ps_global->redrawer = redraw_pico; + + if((old_suspend = F_ON(F_CAN_SUSPEND, ps_global)) != 0) + (void) F_SET(F_CAN_SUSPEND, ps_global, 0); + + /* + * Build list of available filters... + */ + for(i=0; ps_global->VAR_SEND_FILTER && ps_global->VAR_SEND_FILTER[i]; i++){ + for(p = ps_global->VAR_SEND_FILTER[i]; + *p && !isspace((unsigned char)*p); p++) + ; + + c = *p; + *p = '\0'; + if(!(is_absolute_path(ps_global->VAR_SEND_FILTER[i]) + && can_access(ps_global->VAR_SEND_FILTER[i],EXECUTE_ACCESS) ==0)){ + *p = c; + continue; + } + + fp = (struct filters *)fs_get(sizeof(struct filters)); + fp->index = i; + if((lc = last_cmpnt(ps_global->VAR_SEND_FILTER[i])) != NULL){ + fp->filter = cpystr(lc); + } + else if((p - ps_global->VAR_SEND_FILTER[i]) > 20){ + snprintf(tmp_20k_buf, SIZEOF_20KBUF, "...%s", p - 17); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + fp->filter = cpystr(tmp_20k_buf); + } + else + fp->filter = cpystr(ps_global->VAR_SEND_FILTER[i]); + + *p = c; + + if(filters){ + fp->next = filters; + fp->prev = filters->prev; + fp->prev->next = filters->prev = fp; + } + else{ + filters = (struct filters *)fs_get(sizeof(struct filters)); + filters->index = -1; + filters->filter = NULL; + filters->next = filters->prev = fp; + fp->next = fp->prev = filters; + } + } + + i = 0; + opts[i].ch = 'y'; + opts[i].rval = 'y'; + opts[i].name = "Y"; + opts[i++].label = N_("Yes"); + + opts[i].ch = 'n'; + opts[i].rval = 'n'; + opts[i].name = "N"; + opts[i++].label = N_("No"); + + if(filters){ + /* set global_filter_pointer to desired filter or NULL if none */ + /* prepare two keymenu slots for selecting filter */ + opts[i].ch = ctrl('P'); + opts[i].rval = 10; + opts[i].name = "^P"; + opts[i++].label = N_("Prev Filter"); + + opts[i].ch = ctrl('N'); + opts[i].rval = 11; + opts[i].name = "^N"; + opts[i++].label = N_("Next Filter"); + + if(F_ON(F_FIRST_SEND_FILTER_DFLT, ps_global)) + filters = filters->next; + } + + if(F_ON(F_VERBOSE_POST, ps_global)){ + /* setup keymenu slot to toggle verbose mode */ + opts[i].ch = ctrl('W'); + opts[i].rval = 12; + opts[i].name = "^W"; + verbose_label = i++; + } + + if(allow_flowed){ + /* setup keymenu slot to toggle flowed mode */ + opts[i].ch = ctrl('V'); + opts[i].rval = 22; + opts[i].name = "^V"; + flowing_label = i++; + flowing_requested = 1; + } + + if(F_ON(F_NO_FCC_ATTACH, ps_global)){ + /* setup keymenu slot to toggle attacment on fcc */ + opts[i].ch = ctrl('F'); + opts[i].rval = 21; + opts[i].name = "^F"; + fcc_label = i++; + } + +#if defined(BACKGROUND_POST) && defined(SIGCHLD) + if(F_ON(F_BACKGROUND_POST, ps_global)){ + opts[i].ch = ctrl('R'); + opts[i].rval = 15; + opts[i].name = "^R"; + bg_label = i++; + } +#endif + + opts[i].ch = 'p'; + opts[i].rval = 'p'; + opts[i].name = "P"; + opts[i++].label = N_("Priority"); + +#ifdef SMIME + if(F_OFF(F_DONT_DO_SMIME, ps_global)){ + opts[i].ch = 'e'; + opts[i].rval = 'e'; + opts[i].name = "E"; + opts[i++].label = "Encrypt"; + + opts[i].ch = 'g'; + opts[i].rval = 'g'; + opts[i].name = "G"; + opts[i++].label = "Sign"; + + if(ps_global->smime){ + ps_global->smime->do_encrypt = F_ON(F_ENCRYPT_DEFAULT_ON, ps_global); + ps_global->smime->do_sign = F_ON(F_SIGN_DEFAULT_ON, ps_global); + } + } +#endif + + double_rad = i; + + if(F_ON(F_DSN, ps_global)){ + /* setup keymenu slots to toggle dsn bits */ + opts[i].ch = 'd'; + opts[i].rval = 'd'; + opts[i].name = "D"; + opts[i].label = N_("DSNOpts"); + dsn_label = i++; + opts[i].ch = -2; + opts[i].rval = 's'; + opts[i].name = "S"; + opts[i++].label = ""; + opts[i].ch = -2; + opts[i].rval = 'x'; + opts[i].name = "X"; + opts[i++].label = ""; + opts[i].ch = -2; + opts[i].rval = 'h'; + opts[i].name = "H"; + opts[i++].label = ""; + } + + if(filters){ + opts[i].ch = KEY_UP; + opts[i].rval = 10; + opts[i].name = ""; + opts[i++].label = ""; + + opts[i].ch = KEY_DOWN; + opts[i].rval = 11; + opts[i].name = ""; + opts[i++].label = ""; + } + + opts[i].ch = -1; + + fix_windsize(ps_global); + + while(1){ + if(filters && filters->filter && (p = strindex(filters->filter, ' '))) + *p = '\0'; + else + p = NULL; + + lparen = 0; + strncpy(tmp_20k_buf, "Send message", SIZEOF_20KBUF); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + optp = tmp_20k_buf + strlen(tmp_20k_buf); + + if(F_ON(F_NO_FCC_ATTACH, ps_global) && !lmc.text_only) + sstrncpy(&optp, " and Fcc Atmts", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + + if(allow_flowed && !flowing_requested){ + if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){ + if(!lparen){ + *optp++ = ' '; + *optp++ = '('; + lparen++; + } + else + *optp++ = ' '; + } + + sstrncpy(&optp, "not flowed", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + } + + if(filters){ + if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){ + if(!lparen){ + *optp++ = ' '; + *optp++ = '('; + lparen++; + } + else{ + *optp++ = ','; + *optp++ = ' '; + } + } + + if(filters->filter){ + sstrncpy(&optp, "filtered thru \"", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + sstrncpy(&optp, filters->filter, SIZEOF_20KBUF-(optp-tmp_20k_buf)); + if((optp-tmp_20k_buf) < SIZEOF_20KBUF) + *optp++ = '\"'; + } + else + sstrncpy(&optp, "unfiltered", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + } + + if((call_mailer_flags & CM_VERBOSE) || background_requested){ + if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){ + if(!lparen){ + *optp++ = ' '; + *optp++ = '('; + lparen++; + } + else + *optp++ = ' '; + } + + sstrncpy(&optp, "in ", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + if(call_mailer_flags & CM_VERBOSE) + sstrncpy(&optp, "verbose ", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + + if(background_requested) + sstrncpy(&optp, "background ", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + + sstrncpy(&optp, "mode", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + } + + if(g_rolenick && !(he && he[N_FROM].dirty)){ + if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){ + if(!lparen){ + *optp++ = ' '; + *optp++ = '('; + lparen++; + } + else + *optp++ = ' '; + } + + sstrncpy(&optp, "as \"", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + sstrncpy(&optp, g_rolenick, SIZEOF_20KBUF-(optp-tmp_20k_buf)); + sstrncpy(&optp, "\"", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + } + + if(call_mailer_flags & CM_DSN_SHOW){ + if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){ + if(!lparen){ + *optp++ = ' '; + *optp++ = '('; + lparen++; + } + else{ + *optp++ = ','; + *optp++ = ' '; + } + } + + sstrncpy(&optp, dsn_string, SIZEOF_20KBUF-(optp-tmp_20k_buf)); + } + +#ifdef SMIME + if(ps_global->smime && ps_global->smime->do_encrypt){ + if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){ + if(!lparen){ + *optp++ = ' '; + *optp++ = '('; + lparen++; + } + else{ + *optp++ = ','; + *optp++ = ' '; + } + } + + sstrncpy(&optp, "Encrypted", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + } + + if(ps_global->smime && ps_global->smime->do_sign){ + if((optp-tmp_20k_buf)+2 < SIZEOF_20KBUF){ + if(!lparen){ + *optp++ = ' '; + *optp++ = '('; + lparen++; + } + else{ + *optp++ = ','; + *optp++ = ' '; + } + } + + sstrncpy(&optp, "Signed", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + } +#endif + + if(lparen && (optp-tmp_20k_buf) < SIZEOF_20KBUF) + *optp++ = ')'; + + sstrncpy(&optp, "? ", SIZEOF_20KBUF-(optp-tmp_20k_buf)); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + + if(p) + *p = ' '; + + if(flowing_label) + opts[flowing_label].label = flowing_requested ? N_("NoFlow") : N_("Flow"); + + if(verbose_label) + opts[verbose_label].label = (call_mailer_flags & CM_VERBOSE) ? N_("Normal") : N_("Verbose"); + + if(bg_label) + opts[bg_label].label = background_requested + ? N_("Foreground") : N_("Background"); + + if(fcc_label) + opts[fcc_label].label = lmc.text_only ? N_("Fcc Attchmnts") + : N_("No Fcc Atmts "); + + if(F_ON(F_DSN, ps_global)){ + if(call_mailer_flags & CM_DSN_SHOW){ + opts[dsn_label].label = (call_mailer_flags & CM_DSN_DELAY) + ? N_("NoDelay") : N_("Delay"); + opts[dsn_label+1].ch = 's'; + opts[dsn_label+1].label = (call_mailer_flags & CM_DSN_SUCCESS) + ? N_("NoSuccess") : N_("Success"); + opts[dsn_label+2].ch = 'x'; + opts[dsn_label+2].label = (call_mailer_flags & CM_DSN_NEVER) + ? N_("ErrRets") : N_("NoErrRets"); + opts[dsn_label+3].ch = 'h'; + opts[dsn_label+3].label = (call_mailer_flags & CM_DSN_FULL) + ? N_("RetHdrs") : N_("RetFull"); + } + } + + if(double_rad + + ((call_mailer_flags & CM_DSN_SHOW) + ? 4 : F_ON(F_DSN, ps_global) ? 1 : 0) > 11) + rv = double_radio_buttons(tmp_20k_buf, -FOOTER_ROWS(ps_global), opts, + 'y', 'z', + (F_ON(F_DSN, ps_global) && allow_flowed) + ? h_send_prompt_dsn_flowed : + F_ON(F_DSN, ps_global) ? h_send_prompt_dsn : + allow_flowed ? h_send_prompt_flowed : + h_send_prompt, + RB_NORM); + else + rv = radio_buttons(tmp_20k_buf, -FOOTER_ROWS(ps_global), opts, + 'y', 'z', + (double_rad + + ((call_mailer_flags & CM_DSN_SHOW) + ? 4 : F_ON(F_DSN, ps_global) ? 1 : 0) == 11) + ? NO_HELP : + (F_ON(F_DSN, ps_global) && allow_flowed) + ? h_send_prompt_dsn_flowed : + F_ON(F_DSN, ps_global) ? h_send_prompt_dsn : + allow_flowed ? h_send_prompt_flowed : + h_send_prompt, + RB_NORM); + + if(rv == 'y'){ /* user ACCEPTS! */ + break; + } + else if(rv == 'n'){ /* Declined! */ + rstr = _("No Message Sent"); + break; + } + else if(rv == 'z'){ /* Cancelled! */ + rstr = _("Send Cancelled"); + break; + } + else if(rv == 10){ /* PREVIOUS filter */ + filters = filters->prev; + } + else if(rv == 11){ /* NEXT filter */ + filters = filters->next; + } + else if(rv == 21){ + lmc.text_only = !lmc.text_only; + } + else if(rv == 12){ /* flip verbose bit */ + if(call_mailer_flags & CM_VERBOSE) + call_mailer_flags &= ~CM_VERBOSE; + else + call_mailer_flags |= CM_VERBOSE; + + if((call_mailer_flags & CM_VERBOSE) && background_requested) + background_requested = 0; + } + else if(rv == 22){ /* flip flowing bit */ + flowing_requested = !flowing_requested; + } + else if(rv == 15){ + if((background_requested = !background_requested) + && (call_mailer_flags & CM_VERBOSE)) + call_mailer_flags &= ~CM_VERBOSE; /* clear verbose */ + } + else if(call_mailer_flags & CM_DSN_SHOW){ + if(rv == 's'){ /* flip success bit */ + call_mailer_flags ^= CM_DSN_SUCCESS; + /* turn off related bits */ + if(call_mailer_flags & CM_DSN_SUCCESS) + call_mailer_flags &= ~(CM_DSN_NEVER); + } + else if(rv == 'd'){ /* flip delay bit */ + call_mailer_flags ^= CM_DSN_DELAY; + /* turn off related bits */ + if(call_mailer_flags & CM_DSN_DELAY) + call_mailer_flags &= ~(CM_DSN_NEVER); + } + else if(rv == 'x'){ /* flip never bit */ + call_mailer_flags ^= CM_DSN_NEVER; + /* turn off related bits */ + if(call_mailer_flags & CM_DSN_NEVER) + call_mailer_flags &= ~(CM_DSN_SUCCESS | CM_DSN_DELAY); + } + else if(rv == 'h'){ /* flip full bit */ + call_mailer_flags ^= CM_DSN_FULL; + } + } + else if(rv == 'd'){ /* show dsn options */ + /* + * When you turn on DSN, the default is to notify on + * failure, success, or delay; and to return the whole + * body on failure. + */ + call_mailer_flags |= (CM_DSN_SHOW | CM_DSN_SUCCESS | CM_DSN_DELAY | CM_DSN_FULL); + } + else if(rv == 'p'){ /* choose X-Priority */ + char *prio; + + prio = choose_a_priority(priority_requested); + if((ps_global->redrawer = redraw_pico) != NULL){ + (*ps_global->redrawer)(); + fix_windsize(ps_global); + } + + if(prio){ + if(priority_requested) + fs_give((void **) &priority_requested); + + if(prio[0]) + priority_requested = prio; + else + fs_give((void **) &prio); + } + } +#ifdef SMIME + else if(rv=='e'){ + if(ps_global->smime) + ps_global->smime->do_encrypt = !ps_global->smime->do_encrypt; + } + else if(rv=='g'){ + if(ps_global->smime) + ps_global->smime->do_sign = !ps_global->smime->do_sign; + } +#endif + + snprintf(dsn_string, sizeof(dsn_string), "DSN requested[%s%s%s%s]", + (call_mailer_flags & CM_DSN_NEVER) + ? "Never" : "F", + (call_mailer_flags & CM_DSN_DELAY) + ? "D" : "", + (call_mailer_flags & CM_DSN_SUCCESS) + ? "S" : "", + (call_mailer_flags & CM_DSN_NEVER) + ? "" + : (call_mailer_flags & CM_DSN_FULL) ? "-Full" + : "-Hdrs"); + dsn_string[sizeof(dsn_string)-1] = '\0'; + } + + /* remember selection */ + if(filters && filters->index > -1) + sending_filter_requested = ps_global->VAR_SEND_FILTER[filters->index]; + + if(filters){ + filters->prev->next = NULL; /* tie off list */ + while(filters){ /* then free it */ + fp = filters->next; + if(filters->filter) + fs_give((void **)&filters->filter); + + fs_give((void **)&filters); + filters = fp; + } + } + + if(old_suspend) + (void) F_SET(F_CAN_SUSPEND, ps_global, 1); + + if(result) + *result = rstr; + + ps_global->redrawer = redraw; + + return((rstr == NULL) ? 0 : 1); +} + + +/* + * Allow user to choose a priority for sending. + * + * Returns an allocated priority on success, NULL otherwise. + */ +char * +choose_a_priority(char *default_val) +{ + char *choice = NULL; + char **priority_list, **lp; + char *starting_val = NULL; + char *none; + int i, cnt; + PRIORITY_S *p; + + for(cnt = 0, p = priorities; p && p->desc; p++) + cnt++; + + cnt++; /* for NONE entry */ + lp = priority_list = (char **) fs_get((cnt + 1) * sizeof(*priority_list)); + memset(priority_list, 0, (cnt+1) * sizeof(*priority_list)); + + for(i = 0, p = priorities; p && p->desc; p++){ + *lp = cpystr(p->desc); + if(default_val && !strcmp(default_val, p->desc)) + starting_val = (*lp); + + lp++; + } + + none = _("NONE - No X-Priority header included"); + *lp = cpystr(none); + if(!starting_val) + starting_val = (*lp); + + /* TRANSLATORS: SELECT A PRIORITY is a screen title + TRANSLATORS: Print something1 using something2. + "priorities" is something1 */ + choice = choose_item_from_list(priority_list, NULL, _("SELECT A PRIORITY"), + _("priorities"), h_select_priority_screen, + _("HELP FOR SELECTING A PRIORITY"), + starting_val); + + if(!choice) + q_status_message(SM_ORDER, 1, 4, _("No change")); + else if(!strcmp(choice, none)) + choice[0] = '\0'; + + free_list_array(&priority_list); + + return(choice); +} + + +int +dont_flow_this_time(void) +{ + return(flowing_requested ? 0 : 1); +} + + +/*---------------------------------------------------------------------- + Call back for pico to display mime type of attachment + +Args: file -- filename being attached + +Returns: returns 1 on success (message queued), zero otherwise (don't know + type so nothing queued). +----*/ +int +mime_type_for_pico(char *file) +{ + BODY *body; + int rv; + void *file_contents; + + body = mail_newbody(); + body->type = TYPEOTHER; + body->encoding = ENCOTHER; + + /* don't know where the cursor's been, reset it */ + clear_cursor_pos(); + if(!set_mime_type_by_extension(body, file)){ + if((file_contents=(void *)so_get(FileStar,file,READ_ACCESS|READ_FROM_LOCALE)) != NULL){ + body->contents.text.data = file_contents; + set_mime_type_by_grope(body); + } + } + + if(body->type != TYPEOTHER){ + rv = 1; + q_status_message3(SM_ORDER, 0, 3, + _("File %s attached as type %s/%s"), file, + body_types[body->type], + body->subtype ? body->subtype : rfc822_default_subtype(body->type)); + } + else + rv = 0; + + pine_free_body(&body); + return(rv); +} + + +/*---------------------------------------------------------------------- + Call back for pico to receive an uploaded message + + Args: fname -- name for uploaded file (empty if they want us to assign it) + size -- pointer to long to hold the attachment's size + + Notes: the attachment is uploaded to a temp file, and + + Returns: TRUE on success, FALSE otherwise +----*/ +int +upload_msg_to_pico(char *fname, size_t fnlen, long int *size) +{ + char cmd[MAXPATH+1], *fnp = NULL; + char *locale_name = NULL; + long l; + PIPE_S *syspipe; + + dprint((1, "Upload cmd called to xfer \"%s\"\n", + fname ? fname : "<NO FILE>")); + + if(!fname) /* no place for file name */ + return(0); + + if(!*fname){ /* caller wants temp file */ + if((fnp = temp_nam(NULL, "pu")) != NULL){ + strncpy(fname, fnp, fnlen); + fname[fnlen-1] = '\0'; + our_unlink(fnp); + fs_give((void **)&fnp); + } + } + else + locale_name = convert_to_locale(fname); + + build_updown_cmd(cmd, sizeof(cmd), ps_global->VAR_UPLOAD_CMD_PREFIX, + ps_global->VAR_UPLOAD_CMD, locale_name ? locale_name : fname); + if((syspipe = open_system_pipe(cmd, NULL, NULL, PIPE_USER | PIPE_RESET, + 0, pipe_callback, pipe_report_error)) != NULL){ + (void) close_system_pipe(&syspipe, NULL, pipe_callback); + if((l = name_file_size(locale_name ? locale_name : fname)) < 0L){ + q_status_message2(SM_ORDER | SM_DING, 3, 4, + "Error determining size of %s: %s", fname, + fnp = error_description(errno)); + dprint((1, + "!!! Upload cmd \"%s\" failed for \"%s\": %s\n", + cmd ? cmd : "?", + fname ? fname : "?", + fnp ? fnp : "?")); + } + else if(size) + *size = l; + + if(locale_name) + fs_give((void **) &locale_name); + + return(l >= 0); + } + else + q_status_message(SM_ORDER | SM_DING, 3, 4, _("Error opening pipe")); + + if(locale_name) + fs_give((void **) &locale_name); + + return(0); +} + + +char * +cancel_for_pico(void (*redraw_pico)(void)) +{ + int rv; + char *rstr = NULL; + char *prompt = + _("Cancel message (answering \"Confirm\" will abandon your mail message) ? "); + void (*redraw)(void) = ps_global->redrawer; + static ESCKEY_S opts[] = { + {'c', 'c', "C", N_("Confirm")}, + {'n', 'n', "N", N_("No")}, + {'y', 'y', "", ""}, + {-1, 0, NULL, NULL} + }; + + ps_global->redrawer = redraw_pico; + fix_windsize(ps_global); + + while(1){ + rv = radio_buttons(prompt, -FOOTER_ROWS(ps_global), opts, + 'n', 'x', h_confirm_cancel, RB_NORM); + if(rv == 'c'){ /* user ACCEPTS! */ + rstr = ""; + break; + } + else if(rv == 'y'){ + q_status_message(SM_INFO, 1, 3, _(" Type \"C\" to cancel message ")); + display_message('x'); + } + else + break; + } + + ps_global->redrawer = redraw; + return(rstr); +} + + +/*---------------------------------------------------------------------- + Pass the first text segment of the message thru the "send filter" + +Args: body pointer and address for storage object of old data + +Returns: returns 1 on success, zero on error. +----*/ +int +filter_message_text(char *fcmd, ENVELOPE *outgoing, struct mail_bodystruct *body, + STORE_S **old, METAENV *header) +{ + char *cmd, *tmpf = NULL, *resultf = NULL, *errstr = NULL, *mtf = NULL; + int key = 0, include_hdrs = 0; + gf_io_t gc, pc; + STORE_S **so = (STORE_S **)((body->type == TYPEMULTIPART) + ? &body->nested.part->body.contents.text.data + : &body->contents.text.data), + *tmp_so = NULL, *tmpf_so, + *save_local_so, *readthis_so, *our_tmpf_so = NULL; +#define DO_HEADERS 1 + + if(fcmd + && (cmd=expand_filter_tokens(fcmd, outgoing, &tmpf, &resultf, &mtf, + &key, &include_hdrs))){ + if(tmpf){ + /* + * We need WRITE_TO_LOCALE here because the user is going to + * be operating on tmpf. We need to change it back after they + * are done. + */ + if((tmpf_so = so_get(FileStar, tmpf, EDIT_ACCESS|OWNER_ONLY|WRITE_TO_LOCALE)) != NULL){ + if(key){ + so_puts(tmpf_so, filter_session_key()); + so_puts(tmpf_so, NEWLINE); + } + + /* + * If the headers are wanted for filtering, we can just + * stick them in the tmpf file that is already there before + * putting the body in. + */ + if(include_hdrs){ + save_local_so = lmc.so; + lmc.so = tmpf_so; /* write it to tmpf_so */ + lmc.all_written = lmc.text_written = lmc.text_only = 0; + pine_rfc822_header(header, body, NULL, NULL); + lmc.so = save_local_so; + } + + so_seek(*so, 0L, 0); + gf_set_so_readc(&gc, *so); + gf_set_so_writec(&pc, tmpf_so); + gf_filter_init(); + errstr = gf_pipe(gc, pc); + gf_clear_so_readc(*so); + gf_clear_so_writec(tmpf_so); + so_give(&tmpf_so); + } + else + errstr = "Can't create space for filter temporary file."; + } + else if(include_hdrs){ + /* + * Gf_filter wants a single storage object to read from. + * If headers are wanted for filtering we'll have to put them + * and the body into a temp file first and then use that + * as the storage object for gf_filter. + * We don't use WRITE_TO_LOCALE in this case because gf_filter + * takes care of that. + */ + if((our_tmpf_so = so_get(TmpFileStar, NULL, EDIT_ACCESS|OWNER_ONLY)) != NULL){ + /* put headers in our_tmpf_so */ + save_local_so = lmc.so; + lmc.so = our_tmpf_so; /* write it to our_tmpf_so */ + lmc.all_written = lmc.text_written = lmc.text_only = 0; + pine_rfc822_header(header, body, NULL, NULL); + lmc.so = save_local_so; + + /* put body in our_tmpf_so */ + so_seek(*so, 0L, 0); + gf_set_so_readc(&gc, *so); + gf_set_so_writec(&pc, our_tmpf_so); + gf_filter_init(); + errstr = gf_pipe(gc, pc); + gf_clear_so_readc(*so); + gf_clear_so_writec(our_tmpf_so); + + /* tell gf_filter to read from our_tmpf_so instead of *so */ + readthis_so = our_tmpf_so; + } + else + errstr = "Can't create space for temporary file."; + } + else + readthis_so = *so; + + if(!errstr){ + if((tmp_so = so_get(PicoText, NULL, EDIT_ACCESS)) != NULL){ + gf_set_so_writec(&pc, tmp_so); + ps_global->mangled_screen = 1; + suspend_busy_cue(); + ClearScreen(); + fflush(stdout); + if(tmpf){ + PIPE_S *fpipe; + + if((fpipe = open_system_pipe(cmd, NULL, NULL, + PIPE_NOSHELL | PIPE_RESET, + 0, pipe_callback, pipe_report_error)) != NULL){ + if(close_system_pipe(&fpipe, NULL, pipe_callback) == 0){ + + /* now we undo the WRITE_FROM_LOCALE change in tmpf */ + if((tmpf_so = so_get(FileStar, tmpf, READ_ACCESS|READ_FROM_LOCALE)) != NULL){ + gf_set_so_readc(&gc, tmpf_so); + gf_filter_init(); + errstr = gf_pipe(gc, pc); + gf_clear_so_readc(tmpf_so); + so_give(&tmpf_so); + } + else + errstr = "Can't open temp file filter wrote."; + } + else + errstr = "Filter command returned error."; + } + else + errstr = "Can't exec filter text."; + } + else + errstr = gf_filter(cmd, key ? filter_session_key() : NULL, + readthis_so, pc, NULL, 0, + pipe_callback); + + if(our_tmpf_so) + so_give(&our_tmpf_so); + + gf_clear_so_writec(tmp_so); + +#ifdef _WINDOWS + if(!errstr){ + /* + * Can't really be using stdout, so don't print message that + * gets printed in the else. Ideally the program being called + * will wait after showing the message, we might want to look + * into doing the waiting in console based apps... or not. + */ +#else + if(errstr){ + unsigned long ch; + + fprintf(stdout, "\r\n%s Hit return to continue.", errstr); + fflush(stdout); + while((ch = read_char(300)) != ctrl('M') + && ch != NO_OP_IDLE) + putchar(BELL); + } + else{ +#endif /* _WINDOWS */ + BODY *b = (body->type == TYPEMULTIPART) + ? &body->nested.part->body : body; + + *old = *so; /* save old so */ + *so = tmp_so; /* return new one */ + (*so)->attr = copy_parameters((*old)->attr); + + /* + * If the command said it would return new MIME + * mime type data, check it out... + */ + if(mtf){ + char buf[MAILTMPLEN], *s; + FILE *fp; + + if((fp = our_fopen(mtf, "rb")) != NULL){ + if(fgets(buf, sizeof(buf), fp) + && !struncmp(buf, "content-", 8) + && (s = strchr(buf+8, ':'))){ + BODY *nb = mail_newbody(); + + for(*s++ = '\0'; *s == ' '; s++) + ; + + rfc822_parse_content_header(nb, + (char *) ucase((unsigned char *) buf+8),s); + if(nb->type == TYPETEXT + && nb->subtype + && (!b->subtype + || strucmp(b->subtype, nb->subtype))){ + if(b->subtype) + fs_give((void **) &b->subtype); + + b->subtype = nb->subtype; + nb->subtype = NULL; + + mail_free_body_parameter(&b->parameter); + b->parameter = nb->parameter; + nb->parameter = NULL; + mail_free_body_parameter(&nb->parameter); + } + + mail_free_body(&nb); + } + + fclose(fp); + } + } + + /* + * Reevaluate the encoding in case form's changed... + */ + b->encoding = ENCOTHER; + set_mime_type_by_grope(b); + } + + ClearScreen(); + resume_busy_cue(0); + } + else + errstr = "Can't create space for filtered text."; + } + + fs_give((void **)&cmd); + } + else + return(0); + + if(tmpf){ + our_unlink(tmpf); + fs_give((void **)&tmpf); + } + + if(mtf){ + our_unlink(mtf); + fs_give((void **) &mtf); + } + + if(resultf){ + if(name_file_size(resultf) > 0L) + display_output_file(resultf, "Filter", NULL, DOF_BRIEF); + + fs_give((void **)&resultf); + } + else if(errstr){ + if(tmp_so) + so_give(&tmp_so); + + q_status_message1(SM_ORDER | SM_DING, 3, 6, _("Problem filtering: %s"), + errstr); + dprint((1, "Filter FAILED: %s\n", + errstr ? errstr : "?")); + } + + return(errstr == NULL); +} + + +/*---------------------------------------------------------------------- + Copy the newsgroup name of the given mailbox into the given buffer + +Args: + +Returns: +----*/ +void +pine_send_newsgroup_name(char *mailbox, char *group_name, size_t len) +{ + NETMBX mb; + + if(*mailbox == '#'){ /* Strip the leading "#news." */ + strncpy(group_name, mailbox + 6, len-1); + group_name[len-1] = '\0'; + } + else if(mail_valid_net_parse(mailbox, &mb)){ + pine_send_newsgroup_name(mb.mailbox, group_name, len); + } + else + *group_name = '\0'; +} + + +/*---------------------------------------------------------------------- + Generate and send a message back to the pine development team + +Args: none + +Returns: none +----*/ +void +phone_home(char *addr) +{ + char tmp[MAX_ADDRESS]; + ENVELOPE *outgoing; + BODY *body; + + outgoing = mail_newenvelope(); + if(!addr || !strindex(addr, '@')){ + snprintf(addr = tmp, sizeof(tmp), "alpine%s@%s", PHONE_HOME_VERSION, PHONE_HOME_HOST); + tmp[sizeof(tmp)-1] = '\0'; + } + + rfc822_parse_adrlist(&outgoing->to, addr, ps_global->maildomain); + + outgoing->message_id = generate_message_id(); + outgoing->subject = cpystr("Document Request"); + outgoing->from = phone_home_from(); + + body = mail_newbody(); + body->type = TYPETEXT; + + if((body->contents.text.data = (void *)so_get(PicoText,NULL,EDIT_ACCESS)) != NULL){ + so_puts((STORE_S *)body->contents.text.data, "Document request: "); + so_puts((STORE_S *)body->contents.text.data, "Alpine-"); + so_puts((STORE_S *)body->contents.text.data, ALPINE_VERSION); + if(ps_global->first_time_user) + so_puts((STORE_S *)body->contents.text.data, " for New Users"); + + if(ps_global->VAR_INBOX_PATH && ps_global->VAR_INBOX_PATH[0] == '{') + so_puts((STORE_S *)body->contents.text.data, " and IMAP"); + + if(ps_global->VAR_NNTP_SERVER && ps_global->VAR_NNTP_SERVER[0] + && ps_global->VAR_NNTP_SERVER[0][0]) + so_puts((STORE_S *)body->contents.text.data, " and NNTP"); + + (void)pine_simple_send(outgoing, &body, NULL,NULL,NULL,NULL, SS_NULLRP); + + q_status_message(SM_ORDER, 1, 3, "Thanks for being counted!"); + } + else + q_status_message(SM_ORDER | SM_DING, 3, 4, + "Problem creating space for message text."); + + mail_free_envelope(&outgoing); + pine_free_body(&body); + +} + + +/*---------------------------------------------------------------------- + Set up fields for passing to pico. Assumes first text part is + intended to be passed along for editing, and is in the form of + of a storage object brought into existence sometime before pico_send(). + -----*/ +void +outgoing2strings(METAENV *header, struct mail_bodystruct *bod, void **text, + PATMT **pico_a, int from_bounce) +{ + PINEFIELD *pf; + + /* + * SIMPLIFYING ASSUMPTION #37: the first TEXT part's storage object + * is guaranteed to be of type PicoText! + */ + if(bod->type == TYPETEXT){ + *text = so_text((STORE_S *) bod->contents.text.data); + + /* mark storage object as user edited */ + if(!from_bounce) + (void) so_attr((STORE_S *) bod->contents.text.data, "edited", "1"); + } + else if(bod->type == TYPEMULTIPART){ + PART *part; + PATMT **ppa; + char *type, *name, *p; + int name_l; + + /* + * We used to jump out the window if the first part wasn't text, + * but that may not be the case when bouncing a message with + * a leading non-text segment. So, IT'S UNDERSTOOD that the + * contents of the first part to send is still ALWAYS in a + * PicoText storage object, *AND* if that object doesn't contain + * data of type text, then it must contain THE ENCODED NON-TEXT + * DATA of the piece being sent. + * + * It's up to the programmer to make sure that such a message is + * sent via pine_simple_send and never get to the composer via + * pine_send. + * + * Make sense? + */ + *text = so_text((STORE_S *) bod->nested.part->body.contents.text.data); + + /* mark storage object as user edited */ + if(!from_bounce) + (void) so_attr((STORE_S *) bod->nested.part->body.contents.text.data, "edited", "1"); + + /* + * If we already had a list, blast it now, so we can build a new + * attachment list that reflects what's really there... + */ + if(pico_a){ + free_attachment_list(pico_a); + + + /* Simplifyihg assumption #28e. (see cross reference) + All parts in the body passed in here that are not already + in the attachments list are added to the end of the attachments + list. Attachment items not in the body list will be taken care + of in strings2outgoing, but they are unlikey to occur + */ + + for(part = bod->nested.part->next; part != NULL; part = part->next) { + /* Already in list? */ + for(ppa = pico_a; + *ppa && strcmp((*ppa)->id, part->body.id); + ppa = &(*ppa)->next) + ; + + if(!*ppa){ /* Not in the list! append it... */ + *ppa = (PATMT *)fs_get(sizeof(PATMT)); + + if(part->body.description){ + char *p; + size_t len; + + len = 4*strlen(part->body.description)+1; + p = (char *)fs_get(len*sizeof(char)); + if(rfc1522_decode_to_utf8((unsigned char *)p, + len, part->body.description) == (unsigned char *) p){ + (*ppa)->description = p; + } + else{ + fs_give((void **)&p); + (*ppa)->description = cpystr(part->body.description); + } + } + else + (*ppa)->description = cpystr(""); + + type = type_desc(part->body.type, part->body.subtype, + part->body.parameter, NULL, 0); + + /* + * If we can find a "name" parm, display that too... + */ + if((name = parameter_val(part->body.parameter, "name")) != NULL){ + /* Convert any [ or ]'s the name contained */ + for(p = name; *p ; p++) + if(*p == '[') + *p = '('; + else if(*p == ']') + *p = ')'; + + name_l = p - name; + } + else + name_l = 0; + + (*ppa)->filename = fs_get(strlen(type) + name_l + 5); + + snprintf((*ppa)->filename, strlen(type) + name_l + 5, "[%s%s%s]", type, + name ? ": " : "", name ? name : ""); + (*ppa)->filename[strlen(type) + name_l + 5 - 1] = '\0'; + + if(name) + fs_give((void **) &name); + + (*ppa)->flags = A_FLIT; + (*ppa)->size = cpystr(byte_string( + send_body_size(&part->body))); + if(!part->body.id) + part->body.id = generate_message_id(); + + (*ppa)->id = cpystr(part->body.id); + (*ppa)->next = NULL; + } + } + } + } + + + /*------------------------------------------------------------------ + Malloc strings to pass to composer editor because it expects + such strings so it can realloc them + -----------------------------------------------------------------*/ + /* + * turn any address fields into text strings + */ + /* + * SIMPLIFYING ASSUMPTION #116: all header strings are understood + * NOT to be RFC1522 decoded. Said differently, they're understood + * to be RFC1522 ENCODED as necessary. The intent is to preserve + * original charset tagging as far into the compose/send pipe as + * we can. + */ + for(pf = header->local; pf && pf->name; pf = pf->next) + if(pf->canedit) + switch(pf->type){ + case Address : + if(pf->addr){ + char *p, *t, *u; + long l; + + pf->scratch = addr_list_string(*pf->addr, NULL, 1); + + /* + * Scan for and fix-up patently bogus fields. + * + * NOTE: collaboration with this code and what's done in + * reply.c:reply_cp_addr to package up the bogus stuff + * is required. + */ + for(p = pf->scratch; (p = strstr(p, "@" RAWFIELD)); ) + for(t = p; ; t--) + if(*t == '&'){ /* find "leading" token */ + int replacelen; + + /* + * Rfc822_cat has been changed so that it now quotes + * this sometimes. So we have to look out for quotes + * which confuse the decoder. It was only quoting + * because we were putting \r \n in the input, I think. + */ + if(t > pf->scratch && t[-1] == '\"' && p[-1] == '\"') + t[-1] = p[-1] = ' '; + + *t++ = ' '; /* replace token */ + *p = '\0'; /* tie off string */ + u = rfc822_base64((unsigned char *) t, + (unsigned long) strlen(t), + (unsigned long *) &l); + if(!u) + u = ""; + + replacelen = strlen(t); + *p = '@'; /* restore 'p' */ + rplstr(p, strlen(p), 12, ""); /* clear special token */ + rplstr(t, strlen(u)-replacelen+1, replacelen, u); + if(u) + fs_give((void **) &u); + + if(HE(pf)) + HE(pf)->start_here = 1; + + break; + } + else if(t == pf->scratch) + break; + + removing_leading_white_space(pf->scratch); + if(pf->scratch){ + size_t l; + + /* + * Replace control characters with ^C notation, unless + * some conditions are met (see istrncpy). + * If user doesn't edit, then we switch back to the + * original version. If user does edit, then all bets + * are off. + */ + iutf8ncpy((char *)tmp_20k_buf, pf->scratch, SIZEOF_20KBUF-1); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + if((l=strlen((char *)tmp_20k_buf)) > strlen(pf->scratch)){ + fs_give((void **)&pf->scratch); + pf->scratch = (char *)fs_get((l+1) * sizeof(char)); + } + + strncpy(pf->scratch, (char *)tmp_20k_buf, l+1); + } + } + + break; + + case Subject : + if(pf->text){ + char *p, *src; + size_t len; + + src = pf->scratch ? pf->scratch + : (*pf->text) ? *pf->text : ""; + + len = 4*strlen(src)+1; + p = (char *)fs_get(len * sizeof(char)); + if(rfc1522_decode_to_utf8((unsigned char *)p, len, src) == (unsigned char *) p){ + if(pf->scratch) + fs_give((void **)&pf->scratch); + + pf->scratch = p; + } + else{ + fs_give((void **)&p); + if(!pf->scratch) + pf->scratch = cpystr(src); + } + + if(pf->scratch){ + size_t l; + + /* + * Replace control characters with ^C notation, unless + * some conditions are met (see istrncpy). + * If user doesn't edit, then we switch back to the + * original version. If user does edit, then all bets + * are off. + */ + iutf8ncpy((char *)tmp_20k_buf, pf->scratch, SIZEOF_20KBUF-1); + tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; + if((l=strlen((char *)tmp_20k_buf)) > strlen(pf->scratch)){ + fs_give((void **)&pf->scratch); + pf->scratch = (char *)fs_get((l+1) * sizeof(char)); + } + + strncpy(pf->scratch, (char *)tmp_20k_buf, l+1); + } + } + + break; + + default : + break; + } +} + + +/*---------------------------------------------------------------------- + Restore fields returned from pico to form useful to sending + routines. + -----*/ +void +strings2outgoing(METAENV *header, struct mail_bodystruct **bod, PATMT *attach, int flow_it) +{ + PINEFIELD *pf; + int we_cancel = 0; + + we_cancel = busy_cue(NULL, NULL, 1); + + /* + * turn any local address strings into address lists + */ + for(pf = header->local; pf && pf->name; pf = pf->next) + if(pf->scratch){ + char *the_address = NULL; + + switch(pf->type){ + case Address : + removing_trailing_white_space(pf->scratch); + + if((the_address || *pf->scratch) && pf->addr){ + ADDRESS *new_addr = NULL; + static char *fakedomain = "@"; + + if(!the_address) + the_address = pf->scratch; + + rfc822_parse_adrlist(&new_addr, the_address, + (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)) + ? fakedomain : ps_global->maildomain); + mail_free_address(pf->addr); /* free old addrs */ + *pf->addr = new_addr; /* assign new addr */ + } + else if(pf->addr) + mail_free_address(pf->addr); /* free old addrs */ + + break; + + case Subject : + if(*pf->text) + fs_give((void **)pf->text); + + if(*pf->scratch){ + *pf->text = cpystr(pf->scratch); + } + + break; + + default : + break; + } + + fs_give((void **)&pf->scratch); /* free now useless text */ + } + + create_message_body(bod, attach, flow_it); + + if(we_cancel) + cancel_busy_cue(-1); +} + + +/*---------------------------------------------------------------------- + + The head of the body list here is always either TEXT or MULTIPART. It may be +changed from TEXT to MULTIPART if there are attachments to be added +and it is not already multipart. + ----*/ +void +create_message_body(struct mail_bodystruct **b, PATMT *attach, int flow_it) +{ + PART *p, **pp; + PATMT *pa; + BODY *tmp_body, *text_body = NULL; + void *file_contents; + PARAMETER **parmp; + char *lc; + + TIME_STAMP("create_body start.", 1); + /* + * if conditions are met short circuit MIME wrapping + */ + if((*b)->type != TYPEMULTIPART && !attach){ + + /* only override assigned encoding if it might need upgrading */ + if((*b)->type == TYPETEXT && (*b)->encoding == ENC7BIT) + (*b)->encoding = ENCOTHER; + + create_message_body_text(*b, flow_it); + + if(F_ON(F_COMPOSE_ALWAYS_DOWNGRADE, ps_global) + || !((*b)->encoding == ENC8BIT + || (*b)->encoding == ENCBINARY)){ + TIME_STAMP("create_body end.", 1); + return; + } + else /* protect 8bit in multipart */ + text_body = *b; + } + + if((*b)->type == TYPETEXT) { + /*-- Current type is text, but there are attachments to add --*/ + /*-- Upgrade to a TYPEMULTIPART --*/ + tmp_body = (BODY *)mail_newbody(); + tmp_body->type = TYPEMULTIPART; + tmp_body->nested.part = mail_newbody_part(); + if(text_body){ + /* + * Why do we do this? + * The problem is that base64 or quoted-printable encoding is + * sensitive to having random data appended to it's end. If + * we use a single part TEXT message and something in between + * us and the end appends advertising without adjusting for + * the encoding, the message is screwed up. So we wrap the + * text part inside a multipart and then the appended data + * will come after the boundary. + * + * We wish we could do this on the way out the door in a + * child of post_rfc822_output because at that point we know + * the character set and the encoding being used. For example, + * iso-2022-jp is an encoding that is not sensitive to data + * appended to the end, so it wouldn't need to be wrapped. + * We could conceivably have post_rfc822_body inspect the + * body and change it before doing the output. It would work + * but would be very fragile. We'd be passed a body from + * c-client to output and instead of just doing the output + * we'd change the body and then output it. Not worth it + * since the multipart wrapping is completely correct for + * MIME-aware mailers. + */ + (void) copy_body(&(tmp_body->nested.part->body), *b); + /* move contents which were NOT copied */ + tmp_body->nested.part->body.contents.text.data = (*b)->contents.text.data; + (*b)->contents.text.data = NULL; + } + else{ + tmp_body->nested.part->body = **b; + + (*b)->subtype = (*b)->id = (*b)->description = NULL; + (*b)->parameter = NULL; + (*b)->contents.text.data = NULL; + } + + pine_free_body(b); + *b = tmp_body; + } + + if(!text_body){ + /*-- Now type must be MULTIPART with first part text --*/ + (*b)->nested.part->body.encoding = ENCOTHER; + create_message_body_text(&((*b)->nested.part->body), flow_it); + } + + /*------ Go through the parts list remove those to be deleted -----*/ + for(pp = &(*b)->nested.part->next; *pp;){ + for(pa = attach; pa && (*pp)->body.id; pa = pa->next) + /* already existed? */ + if(pa->id && strcmp(pa->id, (*pp)->body.id) == 0){ + char *orig_descp = NULL, *cs = NULL; + + /* + * decode original to see if it matches what was decoded + * when we sent it in. + */ + + if((*pp)->body.description) + orig_descp = (char *) rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf, + SIZEOF_20KBUF, (*pp)->body.description); + + if(!(*pp)->body.description /* update description? */ + || (pa->description && strcmp(pa->description, orig_descp))){ + if((*pp)->body.description) + fs_give((void **) &(*pp)->body.description); + + /* encoding happens as msg text is written */ + (*pp)->body.description = cpystr(pa->description); + } + + if(cs) + fs_give((void **) &cs); + + break; + } + + if(pa == NULL){ + p = *pp; /* prepare to zap *pp */ + *pp = p->next; /* pull next one in list up */ + p->next = NULL; /* tie off removed node */ + + pine_free_body_data(&p->body); /* clean up contained data */ + mail_free_body_part(&p); /* free up the part */ + } + else + pp = &(*pp)->next; + } + + /*---------- Now add any new attachments ---------*/ + for(p = (*b)->nested.part ; p->next != NULL; p = p->next); + for(pa = attach; pa != NULL; pa = pa->next) { + if(pa->id != NULL) + continue; /* Has an ID, it's old */ + + /* + * the idea is handle ALL attachments as open FILE *'s. Actual + * encoding and such is handled at the time the message + * is shoved into the mail slot or written to disk... + * + * Also, we never unlink a file, so it's up to whoever opens + * it to deal with tmpfile issues. + */ + if((file_contents = (void *)so_get(FileStar, pa->filename, + READ_ACCESS)) == NULL){ + q_status_message2(SM_ORDER | SM_DING, 3, 4, + _("Error \"%s\", couldn't attach file \"%s\""), + error_description(errno), pa->filename); + display_message('x'); + continue; + } + + p->next = mail_newbody_part(); + p = p->next; + p->body.id = generate_message_id(); + p->body.contents.text.data = file_contents; + + /* + * Set type to unknown and let set_mime_type_by_* figure it out. + * Always encode attachments we add as BINARY. + */ + p->body.type = TYPEOTHER; + p->body.encoding = ENCBINARY; + p->body.size.bytes = name_file_size(pa->filename); + if(!set_mime_type_by_extension(&p->body, pa->filename)){ + set_mime_type_by_grope(&p->body); + set_charset_possibly_to_ascii(&p->body, ps_global->keyboard_charmap); + } + + so_release((STORE_S *)p->body.contents.text.data); + + if(pa->description) /* encoding happens when msg written */ + p->body.description = cpystr(pa->description); + + /* Add name attribute for backward compatibility */ + for(parmp = &p->body.parameter; *parmp; ) + if(!struncmp((*parmp)->attribute, "name", 4) + && (!*((*parmp)->attribute + 4) + || *((*parmp)->attribute + 4) == '*')){ + PARAMETER *free_me = *parmp; + *parmp = (*parmp)->next; + free_me->next = NULL; + mail_free_body_parameter(&free_me); + } + else + parmp = &(*parmp)->next; + + set_parameter(parmp, "name", + pa->filename + ? ((lc = last_cmpnt(pa->filename)) ? lc : pa->filename) + : NULL); + + /* Then set the Content-Disposition ala RFC1806 */ + if(!p->body.disposition.type){ + p->body.disposition.type = cpystr("attachment"); + for(parmp = &p->body.disposition.parameter; *parmp; ) + if(!struncmp((*parmp)->attribute, "filename", 4) + && (!*((*parmp)->attribute + 4) + || *((*parmp)->attribute + 4) == '*')){ + PARAMETER *free_me = *parmp; + *parmp = (*parmp)->next; + free_me->next = NULL; + mail_free_body_parameter(&free_me); + } + else + parmp = &(*parmp)->next; + + set_parameter(parmp, "filename", + pa->filename + ? ((lc = last_cmpnt(pa->filename)) ? lc : pa->filename) + : NULL); + } + + p->next = NULL; + pa->id = cpystr(p->body.id); + } + + /* + * Now, if this multipart has but one text piece (that is, no + * attachments), then downgrade from a composite type to a discrete + * text/plain message if CTE is not 8bit. + */ + if(!(*b)->nested.part->next + && (*b)->nested.part->body.type == TYPETEXT + && (F_ON(F_COMPOSE_ALWAYS_DOWNGRADE, ps_global) + || !((*b)->nested.part->body.encoding == ENC8BIT + || (*b)->nested.part->body.encoding == ENCBINARY))){ + /* Clone the interesting body part */ + tmp_body = mail_newbody(); + *tmp_body = (*b)->nested.part->body; + /* and rub out what we don't want cleaned up when it's free'd */ + mail_initbody(&(*b)->nested.part->body); + mail_free_body(b); + *b = tmp_body; + } + + + TIME_STAMP("create_body end.", 1); +} + + +/* + * Fill in text BODY part's structure + * + */ +void +create_message_body_text(struct mail_bodystruct *b, int flow_it) +{ + set_mime_type_by_grope(b); + if(F_OFF(F_QUELL_FLOWED_TEXT, ps_global) + && F_OFF(F_STRIP_WS_BEFORE_SEND, ps_global) + && flow_it) + set_parameter(b ? &b->parameter : NULL, "format", "flowed"); + + set_body_size(b); +} + + +/* + * free_attachment_list - free attachments in given list + */ +void +free_attachment_list(PATMT **alist) +{ + PATMT *leading; + + while(alist && *alist){ /* pointer pointing to something */ + leading = (*alist)->next; + if((*alist)->description) + fs_give((void **)&(*alist)->description); + + if((*alist)->filename){ + if((*alist)->flags & A_TMP) + if(our_unlink((*alist)->filename) < 0) + dprint((1, "-- Can't unlink(%s): %s\n", + (*alist)->filename ? (*alist)->filename : "?", + error_description(errno))); + + fs_give((void **)&(*alist)->filename); + } + + if((*alist)->size) + fs_give((void **)&(*alist)->size); + + if((*alist)->id) + fs_give((void **)&(*alist)->id); + + fs_give((void **)alist); + + *alist = leading; + } +} + + +void +set_body_size(struct mail_bodystruct *b) +{ + unsigned char c; + int we_cancel = 0; + + we_cancel = busy_cue(NULL, NULL, 1); + so_seek((STORE_S *)b->contents.text.data, 0L, 0); + b->size.bytes = 0L; + while(so_readc(&c, (STORE_S *)b->contents.text.data)) + b->size.bytes++; + + if(we_cancel) + cancel_busy_cue(-1); +} + + +/* + * view_as_rich - set the rich_header flag + * + * name - name of the header field + * deflt - default value to return if user didn't set it + * + * Note: if the user tries to turn them all off with "", then + * we take that to mean default, since otherwise there is no + * way to get to the headers. + */ +int +view_as_rich(char *name, int deflt) +{ + char **p; + char *q; + + p = ps_global->VAR_COMP_HDRS; + + if(p && *p && **p){ + for(; (q = *p) != NULL; p++){ + if(!struncmp(q, name, strlen(name))) + return 0; /* 0 means we *do* view it by default */ + } + + return 1; /* 1 means it starts out hidden */ + } + return(deflt); +} + + +/* + * background_posting - return whether or not we're already in the process + * of posting + */ +int +background_posting(int gripe) +{ + if(ps_global->post){ + if(gripe) + q_status_message(SM_ORDER|SM_DING, 3, 3, + _("Can't post while posting!")); + return(1); + } + + return(0); +} + + +/*---------------------------------------------------------------------- + Validate the given subject relative to any news groups. + +Args: none + +Returns: always returns 1, but also returns error if +----*/ +int +valid_subject(char *given, char **expanded, char **error, BUILDER_ARG *fcc, int *mangled) +{ + struct headerentry *hp; + + if(expanded) + *expanded = cpystr(given); + + if(error){ + /* + * Now look for any header entry we passed to pico that has to do + * with news. If there's no subject, gripe. + */ + for(hp = pbf->headents; hp->prompt; hp++) + if(hp->help == h_composer_news){ + if(hp->hd_text->text[0] && !*given) + *error = cpystr( + _("News postings MUST have a subject! Please add one!")); + + break; + } + } + + return(0); +} + + +/* + * This is the build_address used by the composer to check for an address + * in the addrbook. + * + * Args: to -- the passed in line to parse + * full_to -- Address of a pointer to return the full address in. + * This will be allocated here and freed by the caller. + * error -- Address of a pointer to return an error message in. + * This will be allocated here and freed by the caller. + * barg -- Address of a pointer to return the fcc in is in + * fcc->tptr. It will have already been allocated by the + * caller but we may free it and reallocate if we wish. + * Caller will free it. + * + * Result: 0 is returned if address was OK, + * -1 if address wasn't OK. + * + * Side effect: Can flush addrbook entry cache entries so they need to be + * re-fetched afterwords. + */ +int +build_address(char *to, char **full_to, char **error, BUILDER_ARG *barg, int *mangled) +{ + char *p; + int ret_val, no_repo = 0, *save_nesting_level; + BuildTo bldto; + PrivateTop *pt = NULL; + PrivateAffector *af = NULL; + char *fcc_local = NULL; + jmp_buf save_jmp_buf; + + dprint((5, "- build_address - (%s)\n", to ? to : "nul")); + + /* check to see if to string is empty to avoid work */ + for(p = to; p && *p && isspace((unsigned char)*p); p++) + ;/* do nothing */ + + if(!p || !*p){ + if(full_to) + *full_to = cpystr(to ? to : ""); /* because pico does a strcmp() */ + + return 0; + } + + if(full_to != NULL) + *full_to = (char *)NULL; + + if(error != NULL) + *error = (char *)NULL; + + /* No guarantee cursor or status line is how we saved it */ + clear_cursor_pos(); + mark_status_unknown(); + + if(ps_global->remote_abook_validity > 0 && + adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0) && mangled) + *mangled |= BUILDER_SCREEN_MANGLED; + + /* + * If we end up jumping back here because somebody else changed one of + * our addrbooks out from underneath us, we may well leak some memory. + * That's probably ok since this will be very rare. + * + * The reason for the memcpy of the jmp_buf is that we may actually + * be indirectly calling this function from within the address book. + * For example, we may be in the address book screen and then run + * the ComposeTo command which puts us in the composer, then we call + * build_address from there which resets addrbook_changed_unexpectedly. + * Once we leave build_address we need to reset addrbook_changed_un... + * because this position on the stack will no longer be valid. + * Same is true of the other setjmp's in this file which are wrapped + * in memcpy calls. + */ + save_nesting_level = cpyint(ab_nesting_level); + memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); + if(setjmp(addrbook_changed_unexpectedly)){ + no_repo = 0; + pt = NULL; + af = NULL; + fcc_local = NULL; + if(error != NULL) + *error = (char *)NULL; + + if(full_to && *full_to) + fs_give((void **)full_to); + + q_status_message(SM_ORDER, 3, 5, "Resetting address book..."); + dprint((1, + "RESETTING address book... build_address(%s)!\n", to ? to : "?")); + addrbook_reset(); + ab_nesting_level = *save_nesting_level; + } + + ab_nesting_level++; + bldto.type = Str; + bldto.arg.str = to; + ret_val = build_address_internal(bldto, full_to, error, + barg ? &fcc_local : NULL, + &no_repo, NULL, save_and_restore, + 0, mangled); + ab_nesting_level--; + if(save_nesting_level) + fs_give((void **)&save_nesting_level); + + /* + * Have to rfc1522_decode the full_to string before sending it back. + */ + if(full_to && *full_to ){ + char *q; + size_t len; + + len = 4*strlen(*full_to)+1; + q = (char *)fs_get(len * sizeof(char)); + p = (char *)rfc1522_decode_to_utf8((unsigned char *)q, len, *full_to); + + /* p == q means that decoding happened, p is decoded *full_to */ + if(p == q){ + fs_give((void **)full_to); + *full_to = p; + } + else + fs_give((void **)&q); + } + + if(fcc_local){ + unsigned long csum; + + /* Pt will point to headents[Fcc].bldr_private */ + pt = NULL; + if(barg && barg->aff) + pt = (PrivateTop *)(*barg->aff); + + /* + * If *barg->aff is set, that means fcc was set from a list + * during some previous builder call. + * If the current To line contains the old expansion as a prefix, then + * we should leave things as they are. In order to decide that, + * we look at a hash value computed from the strings. + */ + if(pt && (af=pt->affector) && af->who == BP_To){ + int len; + + len = strlen(to); + if(len >= af->cksumlen){ + int save; + + save = to[af->cksumlen]; + to[af->cksumlen] = '\0'; + csum = line_hash(to); + to[af->cksumlen] = save; + } + else + csum = af->cksumval + 1; /* something not equal to cksumval */ + } + + if(!pt || + !pt->affector || + (pt->affector->who == BP_To && csum != pt->affector->cksumval)){ + + /* replace fcc value */ + if(barg->tptr) + fs_give((void **)&barg->tptr); + + barg->tptr = fcc_local; + + if(barg->aff){ + if(!pt){ + *barg->aff = (void *)fs_get(sizeof(PrivateTop)); + pt = (PrivateTop *)(*barg->aff); + memset((void *)pt, 0, sizeof(PrivateTop)); + } + + if(no_repo){ + if(!pt->affector) + pt->affector = + (PrivateAffector *)fs_get(sizeof(PrivateAffector)); + + af = pt->affector; + af->who = BP_To; + af->cksumlen = strlen(((full_to && *full_to) + ? *full_to : "")); + af->cksumval = line_hash(((full_to && *full_to) + ? *full_to : "")); + } + else{ + /* + * If result is reproducible, we don't keep track here. + */ + if(pt->affector) + fs_give((void **)&pt->affector); + } + } + } + else + fs_give((void **)&fcc_local); /* unused in this case */ + } + + /* This is so pico will erase the old message */ + if(error != NULL && *error == NULL) + *error = cpystr(""); + + memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); + flush_status_messages(1); + return(ret_val); +} + + +/* + * This is the builder used by the composer for the Lcc line. + * + * Args: lcc -- the passed in Lcc line to parse + * full_lcc -- Address of a pointer to return the full address in. + * This will be allocated here and freed by the caller. + * error -- Address of a pointer to return an error message in. + * This is not allocated so should not be freed by the caller. + * barg -- This is a pointer to text for affected entries which + * we may be changing. The first one in the list is the + * To entry. We may put the name of the list in empty + * group syntax form there (like List Name: ;). + * The second one in the list is the fcc field. + * The tptr members already point to text allocated in the + * caller. We may free and reallocate here, caller will + * free the result in any case. + * + * Result: 0 is returned if address was OK, + * -1 if address wasn't OK. + * + * Side effect: Can flush addrbook entry cache entries so they need to be + * re-fetched afterwords. + */ +int +build_addr_lcc(char *lcc, char **full_lcc, char **error, BUILDER_ARG *barg, int *mangled) +{ + int ret_val, + no_repo = 0; /* fcc or lcc not reproducible */ + int *save_nesting_level; + BuildTo bldlcc; + PrivateTop *pt = NULL; + PrivateAffector *af = NULL; + char *p, + *fcc_local = NULL, + *to = NULL, + *dummy; + jmp_buf save_jmp_buf; + + dprint((5, "- build_addr_lcc - (%s)\n", lcc ? lcc : "nul")); + + /* check to see if to string is empty to avoid work */ + for(p = lcc; p && *p && isspace((unsigned char)*p); p++) + ;/* do nothing */ + + if(!p || !*p){ + if(full_lcc) + *full_lcc = cpystr(lcc ? lcc : ""); /* because pico does a strcmp() */ + + return 0; + } + + if(error != NULL) + *error = (char *)NULL; + + if(ps_global->remote_abook_validity > 0 && + adrbk_check_and_fix_all(ab_nesting_level == 0, 0, 0) && mangled) + *mangled |= BUILDER_SCREEN_MANGLED; + + /* + * If we end up jumping back here because somebody else changed one of + * our addrbooks out from underneath us, we may well leak some memory. + * That's probably ok since this will be very rare. + */ + save_nesting_level = cpyint(ab_nesting_level); + memcpy(save_jmp_buf, addrbook_changed_unexpectedly, sizeof(jmp_buf)); + if(setjmp(addrbook_changed_unexpectedly)){ + no_repo = 0; + pt = NULL; + af = NULL; + fcc_local = NULL; + to = NULL; + if(error != NULL) + *error = (char *)NULL; + + if(full_lcc && *full_lcc) + fs_give((void **)full_lcc); + + q_status_message(SM_ORDER, 3, 5, "Resetting address book..."); + dprint((1, + "RESETTING address book... build_address(%s)!\n", lcc ? lcc : "?")); + addrbook_reset(); + ab_nesting_level = *save_nesting_level; + } + + ab_nesting_level++; + bldlcc.type = Str; + bldlcc.arg.str = lcc; + + /* + * To is first affected_entry and Fcc is second. + * The conditional stuff for the fcc argument says to only change the + * fcc if the fcc pointer is passed in non-null, and the To pointer + * is also non-null. If they are null, that means they've already been + * entered (are sticky). We don't affect fcc if either fcc or To has + * been typed in. + */ + ret_val = build_address_internal(bldlcc, + full_lcc, + error, + (barg && barg->next && barg->next->tptr && barg->tptr) + ? &fcc_local : NULL, + &no_repo, + (barg && barg->tptr) ? &to : NULL, + save_and_restore, 0, mangled); + + ab_nesting_level--; + if(save_nesting_level) + fs_give((void **)&save_nesting_level); + + /* full_lcc is what ends up in the Lcc: line */ + if(full_lcc && *full_lcc){ + size_t len; + + /* + * Have to rfc1522_decode the full_lcc string before sending it back. + */ + len = 4*strlen(*full_lcc)+1; + p = (char *)fs_get(len * sizeof(char)); + if(rfc1522_decode_to_utf8((unsigned char *)p, len, *full_lcc) == (unsigned char *)p){ + fs_give((void **)full_lcc); + *full_lcc = p; + } + else + fs_give((void **)&p); + } + + /* to is what ends up in the To: line */ + if(to && *to){ + unsigned long csum; + size_t len; + + /* + * Have to rfc1522_decode the full_to string before sending it back. + */ + len = 4*strlen(to)+1; + p = (char *)fs_get(len * sizeof(char)); + dummy = NULL; + if(rfc1522_decode_to_utf8((unsigned char *)p, len, to) == (unsigned char *)p){ + /* + * If the caller wants us to try to preserve the charset + * information (they set aff) we copy it into encoded->etext. + * We don't have to worry about pasting together pieces of + * etext like we do in build_address because whenever the + * Lcc line is setting the To line it will be setting the + * whole line, not modifying it. + * Pt will point to headents[To].bldr_private. + */ + if(barg && barg->aff){ + pt = (PrivateTop *)(*barg->aff); + + if(!pt){ + *barg->aff = (void *)fs_get(sizeof(PrivateTop)); + pt = (PrivateTop *)(*barg->aff); + memset((void *)pt, 0, sizeof(PrivateTop)); + } + } + + fs_give((void **)&to); + to = p; + } + else + fs_give((void **)&p); + + if(dummy) + fs_give((void **)&dummy); + + + /* + * This part is recording the fact that the To line was set to + * what it is by entering something on the Lcc line. In particular, + * if a list alias was entered here then the fullname of the list + * goes in the To line. We save this affector information so that + * we can tell it shouldn't be modified if we call build_addr_lcc + * again unless we actually modified what's in the Lcc line so that + * it doesn't start with the same thing. The problem we're solving + * is that the contents of the Lcc line no longer look like the + * list they were derived from. + * Pt will point to headents[To].bldr_private. + */ + if(barg && barg->aff) + pt = (PrivateTop *)(*barg->aff); + + if(pt && (af=pt->affector) && af->who == BP_Lcc){ + int len; + + len = strlen(lcc); + if(len >= af->cksumlen){ + int save; + + save = lcc[af->cksumlen]; + lcc[af->cksumlen] = '\0'; + csum = line_hash(lcc); + lcc[af->cksumlen] = save; + } + else + csum = af->cksumval + 1; /* so they aren't equal */ + } + + if(!pt || + !pt->affector || + pt->affector->who != BP_Lcc || + (pt->affector->who == BP_Lcc && csum != pt->affector->cksumval)){ + + /* replace to value */ + if(barg->tptr && barg->tptr[0]){ + size_t l; + char *t; + + l = strlen(barg->tptr) + strlen(to ? to : "") + 2; + t = (char *)fs_get((l+1) * sizeof(char)); + snprintf(t, l+1, "%s%s%s", + barg->tptr, + (to && *to) ? ", " : "", + (to && *to) ? to : ""); + fs_give((void **)&barg->tptr); + if(to) + fs_give((void **)&to); + + barg->tptr = t; + } + else{ + if(barg->tptr) + fs_give((void **)&barg->tptr); + + barg->tptr = to; + } + + if(barg->aff){ + if(!pt){ + *barg->aff = (void *)fs_get(sizeof(PrivateTop)); + pt = (PrivateTop *)(*barg->aff); + memset((void *)pt, 0, sizeof(PrivateTop)); + } + + if(no_repo){ + if(!pt->affector) + pt->affector = + (PrivateAffector *)fs_get(sizeof(PrivateAffector)); + + af = pt->affector; + af->who = BP_Lcc; + af->cksumlen = strlen(((full_lcc && *full_lcc) + ? *full_lcc : "")); + af->cksumval = line_hash(((full_lcc && *full_lcc) + ? *full_lcc : "")); + } + else{ + /* + * If result is reproducible, we don't keep track here. + */ + if(pt->affector) + fs_give((void **)&pt->affector); + } + } + } + else + fs_give((void **)&to); /* unused in this case */ + } + + if(fcc_local){ + unsigned long csum; + + /* + * If *barg->next->aff is set, that means fcc was set from a list + * during some previous builder call. If the current Lcc line + * contains the old expansion as a prefix, then we should leave + * things as they are. In order to decide that we look at a hash + * value computed from the strings. + * Pt will point to headents[Fcc].bldr_private + */ + pt = NULL; + if(barg && barg->next && barg->next->aff) + pt = (PrivateTop *)(*barg->next->aff); + + if(pt && (af=pt->affector) && af->who == BP_Lcc){ + int len; + + len = strlen(lcc); + if(len >= af->cksumlen){ + int save; + + save = lcc[af->cksumlen]; + lcc[af->cksumlen] = '\0'; + csum = line_hash(lcc); + lcc[af->cksumlen] = save; + } + else + csum = af->cksumval + 1; /* something not equal to cksumval */ + } + + if(!pt || + !pt->affector || + pt->affector->who != BP_Lcc || + (pt->affector->who == BP_Lcc && csum != pt->affector->cksumval)){ + + /* replace fcc value */ + if(barg->next->tptr) + fs_give((void **)&barg->next->tptr); + + barg->next->tptr = fcc_local; + + if(barg->next->aff){ + if(!pt){ + *barg->next->aff = (void *)fs_get(sizeof(PrivateTop)); + pt = (PrivateTop *)(*barg->next->aff); + memset((void *)pt, 0, sizeof(PrivateTop)); + } + + if(no_repo){ + if(!pt->affector) + pt->affector = + (PrivateAffector *)fs_get(sizeof(PrivateAffector)); + + af = pt->affector; + af->who = BP_Lcc; + af->cksumlen = strlen(((full_lcc && *full_lcc) + ? *full_lcc : "")); + af->cksumval = line_hash(((full_lcc && *full_lcc) + ? *full_lcc : "")); + } + else{ + /* + * If result is reproducible, we don't keep track here. + */ + if(pt->affector) + fs_give((void **)&pt->affector); + } + } + } + else + fs_give((void **)&fcc_local); /* unused in this case */ + } + + + if(error != NULL && *error == NULL) + *error = cpystr(""); + + memcpy(addrbook_changed_unexpectedly, save_jmp_buf, sizeof(jmp_buf)); + flush_status_messages(0); + return(ret_val); +} + + +/*---------------------------------------------------------------------- + Verify and canonicalize news groups names. + Called from the message composer + +Args: given_group -- List of groups typed by user + expanded_group -- pointer to point to expanded list, which will be + allocated here and freed in caller. If this is + NULL, don't attempt to validate. + error -- pointer to store error message + fcc -- pointer to point to fcc, which will be + allocated here and freed in caller + +Returns: 0 if all is OK + -1 if addresses weren't valid + +Test the given list of newstroups against those recognized by our nntp +servers. Testing by actually trying to open the list is much cheaper, both +in bandwidth and memory, than yanking the whole list across the wire. + ----*/ +int +news_build(char *given_group, char **expanded_group, char **error, BUILDER_ARG *fcc, int *mangled) +{ + int rv; + char *fccptr = NULL; + + if(fcc && fcc->tptr) + fccptr = cpystr(fcc->tptr); + + clear_cursor_pos(); + + rv = news_grouper(given_group, expanded_group, error, &fccptr, news_build_busy); + + /* assign any new fcc to the BUILDER_ARG */ + if(fccptr){ + if(fcc){ + /* it changed */ + if(fcc->tptr && strcmp(fcc->tptr, fccptr)){ + fs_give((void **) &fcc->tptr); + fcc->tptr = fccptr; + fccptr = NULL; + } + } + + if(fccptr) + fs_give((void **) &fccptr); + } + + /* deal with any busy indicator */ + if(news_busy_cue){ + news_busy_cue = 0; + cancel_busy_cue(0); + mark_status_dirty(); + display_message('x'); + if(mangled) + *mangled |= BUILDER_MESSAGE_DISPLAYED; + } + + + return(rv); +} + + +void +news_build_busy(void) +{ + news_busy_cue = busy_cue("Validating newsgroup(s)", NULL, 0); +} + + +#if defined(DOS) || defined(OS2) + +/*---------------------------------------------------------------------- + Verify that the necessary pieces are around to allow for + message sending under DOS + +Args: strict -- tells us if a remote stream is required before + sending is permitted. + +The idea is to make sure pine knows enough to put together a valid +from line. The things we MUST know are a user-id, user-domain and +smtp server to dump the message off on. Typically these are +provided in pine's configuration file, but if not, the user is +queried here. + ----*/ +int +dos_valid_from() +{ + char prompt[100], answer[80]; + int rc, i, flags; + HelpType help; + + /* + * query for user name portion of address, use IMAP login + * name as default + */ + if(!ps_global->VAR_USER_ID || ps_global->VAR_USER_ID[0] == '\0'){ + NETMBX mb; + int no_prompt_user_id = 0; + + if(ps_global->mail_stream && ps_global->mail_stream->mailbox + && mail_valid_net_parse(ps_global->mail_stream->mailbox, &mb) + && *mb.user){ + strncpy(answer, mb.user, sizeof(answer)-1); + answer[sizeof(answer)-1] = '\0'; + } + else if(F_ON(F_QUELL_USER_ID_PROMPT, ps_global)){ + /* no user-id prompting if set */ + no_prompt_user_id = 1; + rc = 0; + if(!ps_global->mail_stream) + do_broach_folder(ps_global->inbox_name, + ps_global->context_list, NULL, DB_INBOXWOCNTXT); + if(ps_global->mail_stream && ps_global->mail_stream->mailbox + && mail_valid_net_parse(ps_global->mail_stream->mailbox, &mb) + && *mb.user){ + strncpy(answer, mb.user, sizeof(answer)-1); + answer[sizeof(answer)-1] = '\0'; + } + else + answer[0] = '\0'; + } + else + answer[0] = '\0'; + + if(F_ON(F_QUELL_USER_ID_PROMPT, ps_global) && answer[0]){ + /* No prompt, just assume mailbox login is user-id */ + no_prompt_user_id = 1; + rc = 0; + } + + snprintf(prompt,sizeof(prompt),_("User-id for From address : ")); + prompt[sizeof(prompt)-1] = '\0'; + + help = NO_HELP; + while(!no_prompt_user_id) { + flags = OE_APPEND_CURRENT; + rc = optionally_enter(answer,-FOOTER_ROWS(ps_global),0, + sizeof(answer),prompt,NULL,help,&flags); + if(rc == 2) + continue; + + if(rc == 3){ + help = (help == NO_HELP) ? h_sticky_user_id : NO_HELP; + continue; + } + + if(rc != 4) + break; + } + + if(rc == 1 || (rc == 0 && !answer[0])) { + q_status_message(SM_ORDER, 3, 4, + _("Send cancelled (User-id must be provided before sending)")); + return(0); + } + + /* save the name */ + snprintf(prompt, sizeof(prompt), _("Preserve %.*s as \"user-id\" in PINERC"), + sizeof(prompt)-50, answer); + prompt[sizeof(prompt)-1] = '\0'; + if(ps_global->blank_user_id + && !no_prompt_user_id + && want_to(prompt, 'y', 'n', NO_HELP, WT_NORM) == 'y'){ + set_variable(V_USER_ID, answer, 1, 1, Main); + } + else{ + fs_give((void **)&(ps_global->VAR_USER_ID)); + ps_global->VAR_USER_ID = cpystr(answer); + } + } + + /* query for personal name */ + if(!ps_global->VAR_PERSONAL_NAME || ps_global->VAR_PERSONAL_NAME[0]=='\0' + && F_OFF(F_QUELL_PERSONAL_NAME_PROMPT, ps_global)){ + answer[0] = '\0'; + snprintf(prompt, sizeof(prompt), _("Personal name for From address : ")); + prompt[sizeof(prompt)-1] = '\0'; + + help = NO_HELP; + while(1) { + flags = OE_APPEND_CURRENT; + rc = optionally_enter(answer,-FOOTER_ROWS(ps_global),0, + sizeof(answer),prompt,NULL,help,&flags); + if(rc == 2) + continue; + + if(rc == 3){ + help = (help == NO_HELP) ? h_sticky_personal_name : NO_HELP; + continue; + } + + if(rc != 4) + break; + } + + if(rc == 0 && answer){ /* save the name */ + snprintf(prompt, sizeof(prompt), _("Preserve %.*s as \"personal-name\" in PINERC"), + sizeof(prompt)-50, answer); + prompt[sizeof(prompt)-1] = '\0'; + if(ps_global->blank_personal_name + && want_to(prompt, 'y', 'n', NO_HELP, WT_NORM) == 'y'){ + set_variable(V_PERSONAL_NAME, answer, 1, 1, Main); + } + else{ + fs_give((void **)&(ps_global->VAR_PERSONAL_NAME)); + ps_global->VAR_PERSONAL_NAME = cpystr(answer); + } + } + } + + /* + * query for host/domain portion of address, using IMAP + * host as default + */ + if(ps_global->blank_user_domain + || ps_global->maildomain == ps_global->localdomain + || ps_global->maildomain == ps_global->hostname){ + if(ps_global->inbox_name[0] == '{'){ + for(i=0; + i < sizeof(answer)-1 && ps_global->inbox_name[i+1] != '}'; i++) + answer[i] = ps_global->inbox_name[i+1]; + + answer[i] = '\0'; + } + else + answer[0] = '\0'; + + snprintf(prompt,sizeof(prompt),_("Host/domain for From address : ")); + prompt[sizeof(prompt)-1] = '\0'; + + help = NO_HELP; + while(1) { + flags = OE_APPEND_CURRENT; + rc = optionally_enter(answer,-FOOTER_ROWS(ps_global),0, + sizeof(answer),prompt,NULL,help,&flags); + if(rc == 2) + continue; + + if(rc == 3){ + help = (help == NO_HELP) ? h_sticky_domain : NO_HELP; + continue; + } + + if(rc != 4) + break; + } + + if(rc == 1 || (rc == 0 && !answer[0])) { + q_status_message(SM_ORDER, 3, 4, + _("Send cancelled (Host/domain name must be provided before sending)")); + return(0); + } + + /* save the name */ + snprintf(prompt, sizeof(prompt), _("Preserve %.*s as \"user-domain\" in PINERC"), + sizeof(prompt)-50, answer); + prompt[sizeof(prompt)-1] = '\0'; + if(!ps_global->userdomain && !ps_global->blank_user_domain + && want_to(prompt, 'y', 'n', NO_HELP, WT_NORM) == 'y'){ + set_variable(V_USER_DOMAIN, answer, 1, 1, Main); + fs_give((void **)&(ps_global->maildomain)); /* blast old val */ + ps_global->userdomain = cpystr(answer); + ps_global->maildomain = ps_global->userdomain; + } + else{ + fs_give((void **)&(ps_global->maildomain)); + ps_global->userdomain = cpystr(answer); + ps_global->maildomain = ps_global->userdomain; + } + } + + /* check for smtp server */ + if(!ps_global->VAR_SMTP_SERVER || + !ps_global->VAR_SMTP_SERVER[0] || + !ps_global->VAR_SMTP_SERVER[0][0]){ + char **list; + + if(ps_global->inbox_name[0] == '{'){ + for(i=0; + i < sizeof(answer)-1 && ps_global->inbox_name[i+1] != '}'; i++) + answer[i] = ps_global->inbox_name[i+1]; + + answer[i] = '\0'; + } + else + answer[0] = '\0'; + + snprintf(prompt,sizeof(prompt),_("SMTP server to forward message : ")); + prompt[sizeof(prompt)-1] = '\0'; + + help = NO_HELP; + while(1) { + flags = OE_APPEND_CURRENT; + rc = optionally_enter(answer,-FOOTER_ROWS(ps_global),0, + sizeof(answer),prompt,NULL,help,&flags); + if(rc == 2) + continue; + + if(rc == 3){ + help = (help == NO_HELP) ? h_sticky_smtp : NO_HELP; + continue; + } + + if(rc != 4) + break; + } + + if(rc == 1 || (rc == 0 && answer[0] == '\0')) { + q_status_message(SM_ORDER, 3, 4, + _("Send cancelled (SMTP server must be provided before sending)")); + return(0); + } + + /* save the name */ + list = (char **) fs_get(2 * sizeof(char *)); + list[0] = cpystr(answer); + list[1] = NULL; + set_variable_list(V_SMTP_SERVER, list, TRUE, Main); + fs_give((void *)&list[0]); + fs_give((void *)list); + } + + return(1); +} + +#endif /* defined(DOS) || defined(OS2) */ |