#if !defined(lint) && !defined(DOS) static char rcsid[] = "$Id: reply.c 1074 2008-06-04 00:08:43Z hubert@u.washington.edu $"; #endif /* * ======================================================================== * Copyright 2013-2021 Eduardo Chappa * Copyright 2006-2008 University of Washington * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== */ #include "../pith/headers.h" #include "../pith/reply.h" #include "../pith/send.h" #include "../pith/init.h" #include "../pith/state.h" #include "../pith/conf.h" #include "../pith/remote.h" #include "../pith/status.h" #include "../pith/mailview.h" #include "../pith/filter.h" #include "../pith/newmail.h" #include "../pith/bldaddr.h" #include "../pith/mailindx.h" #include "../pith/mimedesc.h" #include "../pith/detach.h" #include "../pith/help.h" #include "../pith/pipe.h" #include "../pith/addrstring.h" #include "../pith/news.h" #include "../pith/util.h" #include "../pith/pattern.h" #include "../pith/detoken.h" #include "../pith/stream.h" #include "../pith/busy.h" #include "../pith/readfile.h" #include "../pith/text.h" #include "../pith/list.h" #include "../pith/ablookup.h" #include "../pith/mailcmd.h" #include "../pith/margin.h" #include "../pith/smime.h" /* * Internal prototypes */ void bounce_mask_header(char **, char *); int (*pith_opt_replyto_prompt)(void); int (*pith_opt_reply_to_all_prompt)(int *); /* * standard type of storage object used for body parts... */ #define PART_SO_TYPE CharStar char *(*pith_opt_user_agent_prefix)(void); /* compare two subjects and return if they are the same. We compare stripped subjects, that is, those that do not have re/fwd. Before we compare the subjects, we decode them in case they were encoded. Return value: 0 - not the same subject, 1 - same subject. */ int same_subject(char *s, char *t) { int rv = 0; int i, j, len; char *s1, *s2; /* holds decoded subjects from s and t */ char *u, *v; if (s == NULL || t == NULL) return s == t ? 1 : 0; i = strlen(s); j = strlen(t); len = i < j ? j : i; u = fs_get(6*len+1); v = fs_get(6*len+1); s1 = (char *) rfc1522_decode_to_utf8((unsigned char *) u, 6*len + 1, s); s2 = (char *) rfc1522_decode_to_utf8((unsigned char *) v, 6*len + 1, t); mail_strip_subject(s1, &u); mail_strip_subject(s2, &v); rv = !strucmp(u, v); fs_give((void **) &u); fs_give((void **) &v); return rv; } /* * reply_harvest - * * Returns: 1 if addresses successfully copied * 0 on user cancel or error * * Input flags: * RSF_FORCE_REPLY_TO * RSF_QUERY_REPLY_ALL * RSF_FORCE_REPLY_ALL * * Output flags: * RSF_FORCE_REPLY_ALL * */ int reply_harvest(struct pine *ps, long int msgno, char *section, ENVELOPE *env, struct mail_address **saved_from, struct mail_address **saved_to, struct mail_address **saved_cc, struct mail_address **saved_resent, int *flags) { ADDRESS *ap, *ap2, *rep_address; int ret = 0, sniff_resent = 0; char *rep_field; /* * If Reply-To is same as From just treat it like it was From. * Otherwise, always use the reply-to if we're replying to more * than one msg or say ok to using it, even if it's us. * If there's no reply-to or it's the same as the from, assume * that the user doesn't want to reply to himself, unless there's * nobody else. */ if(env->reply_to && !addr_lists_same(env->reply_to, env->from) && (F_ON(F_AUTO_REPLY_TO, ps) || ((*flags) & RSF_FORCE_REPLY_TO) || (pith_opt_replyto_prompt && (*pith_opt_replyto_prompt)() == 'y'))){ rep_field = "reply-to"; rep_address = env->reply_to; } else{ rep_field = "From"; rep_address = env->from; } ap = reply_cp_addr(ps, msgno, section, rep_field, *saved_from, (ADDRESS *) NULL, rep_address, RCA_NOT_US); if(ret == 'x') { cmd_cancelled("Reply"); return(0); } reply_append_addr(saved_from, ap); /*--------- check for other recipients ---------*/ if(((*flags) & (RSF_FORCE_REPLY_ALL | RSF_QUERY_REPLY_ALL))){ if((ap = reply_cp_addr(ps, msgno, section, "To", *saved_to, *saved_from, env->to, RCA_NOT_US)) != NULL) reply_append_addr(saved_to, ap); if((ap = reply_cp_addr(ps, msgno, section, "Cc", *saved_cc, *saved_from, env->cc, RCA_NOT_US)) != NULL) reply_append_addr(saved_cc, ap); /* * In these cases, we either need to look at the resent headers * to include in the reply-to-all, or to decide whether or not * we need to ask the reply-to-all question. */ if(((*flags) & RSF_FORCE_REPLY_ALL) || (((*flags) & RSF_QUERY_REPLY_ALL) && ((!(*saved_from) && !(*saved_cc)) || (*saved_from && !(*saved_to) && !(*saved_cc))))){ sniff_resent++; if((ap2 = reply_resent(ps, msgno, section)) != NULL){ /* * look for bogus addr entries and replace */ if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent, *saved_from, ap2, RCA_NOT_US)) != NULL) reply_append_addr(saved_resent, ap); mail_free_address(&ap2); } } /* * It makes sense to ask reply-to-all now. */ if(((*flags) & RSF_QUERY_REPLY_ALL) && ((*saved_from && (*saved_to || *saved_cc || *saved_resent)) || (*saved_cc || *saved_resent))){ *flags &= ~RSF_QUERY_REPLY_ALL; if(pith_opt_reply_to_all_prompt && (*pith_opt_reply_to_all_prompt)(flags) < 0){ cmd_cancelled("Reply"); return(0); } } /* * If we just answered yes to the reply-to-all question and * we still haven't collected the resent headers, do so now. */ if(((*flags) & RSF_FORCE_REPLY_ALL) && !sniff_resent && (ap2 = reply_resent(ps, msgno, section))){ /* * look for bogus addr entries and replace */ if((ap = reply_cp_addr(ps, 0, NULL, NULL, *saved_resent, *saved_from, ap2, RCA_NOT_US)) != NULL) reply_append_addr(saved_resent, ap); mail_free_address(&ap2); } } return(1); } /*---------------------------------------------------------------------- Return a pointer to a copy of the given address list filtering out those already in the "mask" lists and ourself. Args: mask1 -- Don't copy if in this list mask2 -- or if in this list source -- List to be copied us_too -- Don't filter out ourself. flags -- RCA_NOT_US copy all addrs except for our own RCA_ALL copy all addrs, including our own RCA_ONLY_US copy only addrs that are our own ---*/ ADDRESS * reply_cp_addr(struct pine *ps, long int msgno, char *section, char *field, struct mail_address *mask1, struct mail_address *mask2, struct mail_address *source, int flags) { ADDRESS *tmp1, *tmp2, *ret = NULL, **ret_tail; /* can only choose one of these flags values */ assert(!((flags & RCA_ALL && flags & RCA_ONLY_US) || (flags & RCA_ALL && flags & RCA_NOT_US) || (flags & RCA_ONLY_US && flags & RCA_NOT_US))); for(tmp1 = source; msgno && tmp1; tmp1 = tmp1->next) if(tmp1->host && tmp1->host[0] == '.'){ char *h, *fields[2]; fields[0] = field; fields[1] = NULL; if((h = pine_fetchheader_lines(ps ? ps->mail_stream : NULL, msgno, section, fields)) != NULL){ char *p, fname[32]; int q; q = strlen(h); strncpy(fname, field, sizeof(fname)-2); fname[sizeof(fname)-2] = '\0'; strncat(fname, ":", sizeof(fname)-strlen(fname)-1); fname[sizeof(fname)-1] = '\0'; for(p = h; (p = strstr(p, fname)) != NULL; ) rplstr(p, q-(p-h), strlen(fname), ""); /* strip field strings */ sqznewlines(h); /* blat out CR's & LF's */ for(p = h; (p = strchr(p, TAB)) != NULL; ) *p++ = ' '; /* turn TABs to whitespace */ if(*h){ long l; size_t ll; ret = (ADDRESS *) fs_get(sizeof(ADDRESS)); memset(ret, 0, sizeof(ADDRESS)); /* get rid of leading white space */ for(p = h; *p == SPACE; p++) ; if(p != h){ memmove(h, p, l = strlen(p)); h[l] = '\0'; } /* base64 armor plate the gunk to protect against * c-client quoting in output. */ p = (char *) rfc822_binary(h, strlen(h), (unsigned long *) &l); sqznewlines(p); fs_give((void **) &h); /* * Seems like the 4 ought to be a 2, but I'll leave it * to be safe, in case something else adds 2 chars later. */ ll = strlen(p) + 4; ret->mailbox = (char *) fs_get(ll * sizeof(char)); snprintf(ret->mailbox, ll, "&%s", p); ret->mailbox[ll-1] = '\0'; fs_give((void **) &p); ret->host = cpystr(RAWFIELD); } } return(ret); } ret_tail = &ret; for(source = first_addr(source); source; source = source->next){ for(tmp1 = first_addr(mask1); tmp1; tmp1 = tmp1->next) if(address_is_same(source, tmp1)) /* it is in mask1, skip it */ break; for(tmp2 = first_addr(mask2); !tmp1 && tmp2; tmp2 = tmp2->next) if(address_is_same(source, tmp2)) /* it is in mask2, skip it */ break; /* * If there's no match in masks and this address satisfies the * flags requirement, copy it. */ if(!tmp1 && !tmp2 /* no mask match */ && ((flags & RCA_ALL) /* including everybody */ || (flags & RCA_ONLY_US && address_is_us(source, ps)) || (flags & RCA_NOT_US && !address_is_us(source, ps)))){ tmp1 = source->next; source->next = NULL; /* only copy one addr! */ *ret_tail = rfc822_cpy_adr(source); ret_tail = &(*ret_tail)->next; source->next = tmp1; /* restore rest of list */ } } return(ret); } ACTION_S * set_role_from_msg(struct pine *ps, long int rflags, long int msgno, char *section) { ACTION_S *role = NULL; PAT_S *pat = NULL; SEARCHSET *ss = NULL; PAT_STATE pstate; if(!nonempty_patterns(rflags, &pstate)) return(role); if(msgno > 0L){ ss = mail_newsearchset(); ss->first = ss->last = (unsigned long)msgno; } /* Go through the possible roles one at a time until we get a match. */ pat = first_pattern(&pstate); /* calculate this message's score if needed */ if(ss && pat && scores_are_used(SCOREUSE_GET) & SCOREUSE_ROLES && get_msg_score(ps->mail_stream, msgno) == SCORE_UNDEF) (void)calculate_some_scores(ps->mail_stream, ss, 0); while(!role && pat){ if(match_pattern(pat->patgrp, ps->mail_stream, ss, section, get_msg_score, SE_NOSERVER|SE_NOPREFETCH)){ if(!pat->action || pat->action->bogus) break; role = pat->action; } else pat = next_pattern(&pstate); } if(ss) mail_free_searchset(&ss); return(role); } /* * reply_seed - fill in reply header * */ void reply_seed(struct pine *ps, ENVELOPE *outgoing, ENVELOPE *env, struct mail_address *saved_from, struct mail_address *saved_to, struct mail_address *saved_cc, struct mail_address *saved_resent, char **fcc, int replytoall, char **errmsg) { ADDRESS **to_tail, **cc_tail; to_tail = &outgoing->to; cc_tail = &outgoing->cc; if(saved_from){ /* Put Reply-To or From in To. */ *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to, (ADDRESS *) NULL, saved_from, RCA_ALL); if(replytoall){ if(ps->reply.preserve_fields){ while(*to_tail) to_tail = &(*to_tail)->next; *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to, (ADDRESS *) NULL, saved_to, RCA_ALL); while(*to_tail) to_tail = &(*to_tail)->next; *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc, outgoing->to, saved_resent, RCA_ALL); *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc, outgoing->to, saved_cc, RCA_ALL); } else{ /* and the rest in cc */ *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc, outgoing->to, saved_to, RCA_ALL); while(*cc_tail) /* stay on last address */ cc_tail = &(*cc_tail)->next; *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc, outgoing->to, saved_cc, RCA_ALL); while(*cc_tail) cc_tail = &(*cc_tail)->next; *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc, outgoing->to, saved_resent, RCA_ALL); } } } else if(saved_to){ /* No From (maybe from us), put To in To. */ *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to, (ADDRESS *)NULL, saved_to, RCA_ALL); /* and the rest in cc */ if(replytoall){ *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc, outgoing->to, saved_cc, RCA_ALL); while(*cc_tail) cc_tail = &(*cc_tail)->next; *cc_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->cc, outgoing->to, saved_resent, RCA_ALL); } } else{ /* No From or To, put everything else in To if replytoall, */ if(replytoall){ *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to, (ADDRESS *) NULL, saved_cc, RCA_ALL); while(*to_tail) to_tail = &(*to_tail)->next; *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to, (ADDRESS *) NULL, saved_resent, RCA_ALL); } /* else, reply to original From which must be us */ else{ /* * Put self in To if in original From. */ if(!outgoing->newsgroups) *to_tail = reply_cp_addr(ps, 0, NULL, NULL, outgoing->to, (ADDRESS *) NULL, env->from, RCA_ALL); } } /* add any missing personal data */ reply_fish_personal(outgoing, env); /* get fcc */ if(fcc && outgoing->to && outgoing->to->host[0] != '.'){ *fcc = get_fcc_based_on_to(outgoing->to); } else if(fcc && outgoing->newsgroups){ char *newsgroups_returned = NULL; int rv; rv = news_grouper(outgoing->newsgroups, &newsgroups_returned, errmsg, fcc, NULL); if(rv != -1 && strcmp(outgoing->newsgroups, newsgroups_returned)){ fs_give((void **)&outgoing->newsgroups); outgoing->newsgroups = newsgroups_returned; } else fs_give((void **) &newsgroups_returned); } } /*---------------------------------------------------------------------- Test the given address lists for equivalence Args: x -- First address list for comparison y -- Second address for comparison ---*/ int addr_lists_same(struct mail_address *x, struct mail_address *y) { for(x = first_addr(x), y = first_addr(y); x && y; x = first_addr(x->next), y = first_addr(y->next)){ if(!address_is_same(x, y)) return(0); } return(!x && !y); /* true if ran off both lists */ } /*---------------------------------------------------------------------- Test the given address against those in the given envelope's to, cc Args: addr -- address for comparison env -- envelope to compare against ---*/ int addr_in_env(struct mail_address *addr, ENVELOPE *env) { ADDRESS *ap; for(ap = env ? env->to : NULL; ap; ap = ap->next) if(address_is_same(addr, ap)) return(1); for(ap = env ? env->cc : NULL; ap; ap = ap->next) if(address_is_same(addr, ap)) return(1); return(0); /* not found! */ } /*---------------------------------------------------------------------- Add missing personal info dest from src envelope Args: dest -- envelope to add personal info to src -- envelope to get personal info from NOTE: This is just kind of a courtesy function. It's really not adding anything needed to get the mail thru, but it is nice for the user under some odd circumstances. ---*/ void reply_fish_personal(ENVELOPE *dest, ENVELOPE *src) { ADDRESS *da, *sa; for(da = dest ? dest->to : NULL; da; da = da->next){ if(da->personal && !da->personal[0]) fs_give((void **)&da->personal); for(sa = src ? src->to : NULL; sa && !da->personal ; sa = sa->next) if(address_is_same(da, sa) && sa->personal && sa->personal[0]) da->personal = cpystr(sa->personal); for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next) if(address_is_same(da, sa) && sa->personal && sa->personal[0]) da->personal = cpystr(sa->personal); } for(da = dest ? dest->cc : NULL; da; da = da->next){ if(da->personal && !da->personal[0]) fs_give((void **)&da->personal); for(sa = src ? src->to : NULL; sa && !da->personal; sa = sa->next) if(address_is_same(da, sa) && sa->personal && sa->personal[0]) da->personal = cpystr(sa->personal); for(sa = src ? src->cc : NULL; sa && !da->personal; sa = sa->next) if(address_is_same(da, sa) && sa->personal && sa->personal[0]) da->personal = cpystr(sa->personal); } } /*---------------------------------------------------------------------- Given a header field and envelope, build "References: " header data Args: Returns: ---*/ char * reply_build_refs(ENVELOPE *env) { int len, id_len, first_ref_len = 0, foldslop; char *p, *refs = NULL, *h = env->references; char *first_ref = NULL, *tail_refs = NULL; if(!(env->message_id && (id_len = strlen(env->message_id)))) return(NULL); if(h){ /* * The length we have to work with doesn't seem to appear in any * standards. Steve Jones says that in comp.news discussions he * has seen 1024 as the longest length of a header value. * In the inn news source we find MAXHEADERSIZE = 1024. It appears * that is the maximum length of the header value, including * newlines for folded lines (that is, the newlines are counted). * We'll be conservative and figure every reference will take up a * line of its own when we fold. We'll also count 2 for CRLF instead * of just one for LF just to be safe. hubert 2001-jan * J.B. Moreno says "The server limit is * more commonly encountered at 999/1000 bytes [...]". So we'll * back off to 999 instead of 1024. */ #define MAXHEADERSIZE (999) /* count the total number of potential folds, max of 2 bytes each */ for(foldslop = 2, p = h; (p = strstr(p+1, "> <")); ) foldslop += 2; if((len=strlen(h)) + 1+id_len + foldslop >= MAXHEADERSIZE && (p = strstr(h, "> <"))){ /* * If the references line is so long that we are going to have * to delete some of the references, delete the 2nd, 3rd, ... * We don't want to delete the first message in the thread. */ p[1] = '\0'; first_ref = cpystr(h); first_ref_len = strlen(first_ref)+1; /* len includes space */ p[1] = ' '; tail_refs = p+2; /* get rid of 2nd, 3rd, ... until it fits */ while((len=strlen(tail_refs)) + first_ref_len + 1+id_len + foldslop >= MAXHEADERSIZE && (p = strstr(tail_refs, "> <"))){ tail_refs = p + 2; foldslop -= 2; } /* * If the individual references are seriously long, somebody * is messing with us and we don't care if it works right. */ if((len=strlen(tail_refs)) + first_ref_len + 1+id_len + foldslop >= MAXHEADERSIZE){ first_ref_len = len = 0; tail_refs = NULL; if(first_ref) fs_give((void **)&first_ref); } } else tail_refs = h; refs = (char *)fs_get((first_ref_len + 1+id_len + len + 1) * sizeof(char)); snprintf(refs, first_ref_len + 1+id_len + len + 1, "%s%s%s%s%s", first_ref ? first_ref : "", first_ref ? " " : "", tail_refs ? tail_refs : "", tail_refs ? " " : "", env->message_id); refs[first_ref_len + 1+id_len + len] = '\0'; } if(!refs && id_len) refs = cpystr(env->message_id); if(first_ref) fs_give((void **)&first_ref); return(refs); } /*---------------------------------------------------------------------- Snoop for any Resent-* headers, and return an ADDRESS list Args: stream -- msgno -- Returns: either NULL if no Resent-* or parsed ADDRESS struct list ---*/ ADDRESS * reply_resent(struct pine *pine_state, long int msgno, char *section) { #define RESENTFROM 0 #define RESENTTO 1 #define RESENTCC 2 ADDRESS *rlist = NULL, **a, **b; char *hdrs, *values[RESENTCC+1]; int i; static char *fields[] = {"Resent-From", "Resent-To", "Resent-Cc", NULL}; static char *fakedomain = "@"; if((hdrs = pine_fetchheader_lines(pine_state->mail_stream, msgno, section, fields)) != NULL){ memset(values, 0, (RESENTCC+1) * sizeof(char *)); simple_header_parse(hdrs, fields, values); for(i = RESENTFROM; i <= RESENTCC; i++) rfc822_parse_adrlist(&rlist, values[i], (F_ON(F_COMPOSE_REJECTS_UNQUAL, pine_state)) ? fakedomain : pine_state->maildomain); /* pare dup's ... */ for(a = &rlist; *a; a = &(*a)->next) /* compare every address */ for(b = &(*a)->next; *b; ) /* to the others */ if(address_is_same(*a, *b)){ ADDRESS *t = *b; if(!(*a)->personal){ /* preserve personal name */ (*a)->personal = (*b)->personal; (*b)->personal = NULL; } *b = t->next; t->next = NULL; mail_free_address(&t); } else b = &(*b)->next; } if(hdrs) fs_give((void **) &hdrs); return(rlist); } /*---------------------------------------------------------------------- Format and return subject suitable for the reply command Args: subject -- subject to build reply subject for buf -- buffer to use for writing. If non supplied, alloc one. buflen -- length of buf if supplied, else ignored Returns: with either "Re:" prepended or not, if already there. returned string is allocated. ---*/ char * reply_subject(char *subject, char *buf, size_t buflen) { size_t l = (subject && *subject) ? 4*strlen(subject) : 10; char *tmp = fs_get(l + 1), *decoded, *p; if(!buf){ buflen = l + 5; buf = fs_get(buflen); } /* decode any 8bit into tmp buffer */ decoded = (char *) rfc1522_decode_to_utf8((unsigned char *)tmp, l+1, subject); buf[0] = '\0'; if(decoded /* already "re:" ? */ && (decoded[0] == 'R' || decoded[0] == 'r') && (decoded[1] == 'E' || decoded[1] == 'e')){ if(decoded[2] == ':') snprintf(buf, buflen, "%.*s", (int)(buflen-1), subject); else if((decoded[2] == '[') && (p = strchr(decoded, ']'))){ p++; while(*p && isspace((unsigned char)*p)) p++; if(p[0] == ':') snprintf(buf, buflen, "%.*s", (int)(buflen-1), subject); } } if(!buf[0]) snprintf(buf, buflen, "Re: %.*s", (int)(buflen-1), (subject && *subject) ? subject : "your mail"); buf[buflen-1] = '\0'; fs_give((void **) &tmp); return(buf); } /*---------------------------------------------------------------------- return initials for the given personal name Args: name -- Personal name to extract initials from Returns: pointer to name overwritten with initials ---*/ char * reply_quote_initials(char *name) { char *s = name, *w = name; int i, j; CBUF_S cbuf; UCS ucs; cbuf.cbuf[i = 0] = '\0'; cbuf.cbufp = cbuf.cbuf; cbuf.cbufend = cbuf.cbuf; /* while there are still characters to look at */ while(s && *s){ /* skip to next initial */ while(*s && (unsigned char) *s == ' ') s++; if(!utf8_to_ucs4_oneatatime((unsigned char) *s++ & 0xff, &cbuf, &ucs, NULL)){ i++; continue; } /* skip over non-alpha stuff */ if(i == 0 && !isalnum((unsigned char) cbuf.cbuf[0])) goto reset_cbuf; /* copy cbuf */ for(j = 0; j <= i; j++) *w++ = cbuf.cbuf[j]; /* skip to end of this piece of name */ while(*s && (unsigned char) *s != ' ') s++; reset_cbuf: cbuf.cbuf[i = 0] = '\0'; cbuf.cbufp = cbuf.cbuf; cbuf.cbufend = cbuf.cbuf; } if(w) *w = '\0'; return(name); } /* * There is an assumption that MAX_SUBSTITUTION is <= the size of the * tokens being substituted for (only in the size of buf below). */ #define MAX_SUBSTITUTION 6 #define MAX_PREFIX 63 static char *from_token = "_FROM_"; static char *nick_token = "_NICK_"; static char *init_token = "_INIT_"; /*---------------------------------------------------------------------- return a quoting string, "> " by default, for replied text Args: env -- envelope of message being replied to Returns: malloc'd array containing quoting string, freed by caller ---*/ char * reply_quote_str(ENVELOPE *env) { char *prefix, *repl, *p, buf[MAX_PREFIX+1], pbf[MAX_SUBSTITUTION+1]; strncpy(buf, ps_global->VAR_REPLY_STRING, sizeof(buf)-1); buf[sizeof(buf)-1] = '\0'; /* set up the prefix to quote included text */ if((p = strstr(buf, from_token)) != NULL){ repl = (env && env->from && env->from->mailbox) ? env->from->mailbox : ""; strncpy(pbf, repl, sizeof(pbf)-1); pbf[sizeof(pbf)-1] = '\0';; rplstr(p, sizeof(buf)-(p-buf), strlen(from_token), pbf); } if((p = strstr(buf, nick_token)) != NULL){ repl = (env && env->from && env->from && get_nickname_from_addr(env->from, tmp_20k_buf, 1000)) ? tmp_20k_buf : ""; strncpy(pbf, repl, sizeof(pbf)-1); pbf[sizeof(pbf)-1] = '\0';; rplstr(p, sizeof(buf)-(p-buf), strlen(nick_token), pbf); } if((p = strstr(buf, init_token)) != NULL){ char *q = NULL; char buftmp[MAILTMPLEN]; snprintf(buftmp, sizeof(buftmp), "%.200s", (env && env->from && env->from->personal) ? env->from->personal : ""); buftmp[sizeof(buftmp)-1] = '\0'; repl = (env && env->from && env->from->personal) ? reply_quote_initials(q = cpystr((char *)rfc1522_decode_to_utf8( (unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, buftmp))) : ""; istrncpy(pbf, repl, sizeof(pbf)-1); pbf[sizeof(pbf)-1] = '\0';; rplstr(p, sizeof(buf)-(p-buf), strlen(init_token), pbf); if(q) fs_give((void **)&q); } prefix = removing_quotes(cpystr(buf)); return(prefix); } int reply_quote_str_contains_tokens(void) { return(ps_global->VAR_REPLY_STRING && ps_global->VAR_REPLY_STRING[0] && (strstr(ps_global->VAR_REPLY_STRING, from_token) || strstr(ps_global->VAR_REPLY_STRING, nick_token) || strstr(ps_global->VAR_REPLY_STRING, init_token))); } /*---------------------------------------------------------------------- Build the body for the message number/part being replied to Args: Result: BODY structure suitable for sending ----------------------------------------------------------------------*/ BODY * reply_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body, long int msgno, char *sect_prefix, void *msgtext, char *prefix, int plustext, ACTION_S *role, int toplevel, REDRAFT_POS_S **redraft_pos) { #define SECTBUFLEN 256 char *p, *sig = NULL, *section, sect_buf[SECTBUFLEN]; BODY *body = NULL, *tmp_body = NULL; PART *part; gf_io_t pc; int impl, template_len = 0, leave_cursor_at_top = 0, reply_raw_body = 0; if(sect_prefix) /* SECTBUFLEN = sizeof(sect_buf) */ snprintf(section = sect_buf, sizeof(sect_buf), "%.*s.1", SECTBUFLEN-3, sect_prefix); else section = "1"; sect_buf[sizeof(sect_buf)-1] = '\0'; if(ps_global->full_header == 2 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global)) reply_raw_body = 1; gf_set_so_writec(&pc, (STORE_S *) msgtext); if(toplevel){ char *filtered; impl = 0; filtered = detoken(role, env, 0, ps_global->reply.signature_bottom, 0, redraft_pos, &impl); if(filtered){ if(*filtered){ so_puts((STORE_S *)msgtext, filtered); if(impl == 1) template_len = strlen(filtered); else if(impl == 2) leave_cursor_at_top++; } fs_give((void **)&filtered); } else impl = 1; } else impl = 1; if(toplevel && (sig = reply_signature(role, env, redraft_pos, &impl)) && !ps_global->reply.signature_bottom){ /* * If CURSORPOS was set explicitly in sig_file, and there was a * template file before that, we need to adjust the offset by the * length of the template file. However, if the template had * a set CURSORPOS in it then impl was 2 before getting to the * signature, so offset wouldn't have been reset by the signature * CURSORPOS and offset would already be correct. That case will * be ok here because template_len will be 0 and adding it does * nothing. If template * didn't have CURSORPOS in it, then impl was 1 and got set to 2 * by the CURSORPOS in the sig. In that case we have to adjust the * offset. That's what the next line does. It adjusts it if * template_len is nonzero and if CURSORPOS was set in sig_file. */ if(impl == 2) (*redraft_pos)->offset += template_len; if(*sig) so_puts((STORE_S *)msgtext, sig); /* * Set sig to NULL because we've already used it. If SIG_AT_BOTTOM * is set, we won't have used it yet and want it to be non-NULL. */ fs_give((void **)&sig); } /* * Only put cursor in sig if there is a cursorpos there but not * one in the template, and sig-at-bottom. */ if(!(sig && impl == 2 && !leave_cursor_at_top)) leave_cursor_at_top++; if(plustext){ if(!orig_body || orig_body->type == TYPETEXT || reply_raw_body || !ps_global->reply.keep_attach){ char *charset = NULL; /*------ Simple text-only message ----*/ body = mail_newbody(); body->type = TYPETEXT; body->contents.text.data = msgtext; reply_delimiter(env, role, pc); if(ps_global->reply.include_header) reply_forward_header(stream, msgno, sect_prefix, env, pc, prefix); if(!orig_body || reply_raw_body || reply_body_text(orig_body, &tmp_body)){ BODY *bodyp = NULL; bodyp = reply_raw_body ? NULL : tmp_body; /* * We set the charset in the outgoing message to the same * as the one in the message we're replying to unless it * is the unknown charset. We do that in order to attempt * to preserve the same charset in the reply if possible. * It may be safer to just set it to whatever we want instead * but then the receiver may not be able to read it. */ if(bodyp && (charset = parameter_val(bodyp->parameter, "charset")) && strucmp(charset, UNKNOWN_CHARSET)) set_parameter(&body->parameter, "charset", charset); if(charset) fs_give((void **) &charset); get_body_part_text(stream, bodyp, msgno, bodyp ? (p = body_partno(stream, msgno, bodyp)) : sect_prefix, 0L, pc, prefix, NULL, GBPT_NONE); if(bodyp && p) fs_give((void **) &p); } else{ gf_puts(NEWLINE, pc); gf_puts(" [NON-Text Body part not included]", pc); gf_puts(NEWLINE, pc); } } else if(orig_body->type == TYPEMULTIPART){ /*------ Message is Multipart ------*/ if(orig_body->subtype && (!strucmp(orig_body->subtype, "signed") #ifdef SMIME || !strucmp(orig_body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE) #endif /* SMIME */ ) && orig_body->nested.part){ /* operate only on the signed part */ body = reply_body(stream, env, &orig_body->nested.part->body, msgno, section, msgtext, prefix, plustext, role, 0, redraft_pos); } else if(orig_body->subtype && !strucmp(orig_body->subtype, "mixed") && orig_body->nested.part && orig_body->nested.part->body.type == TYPEMULTIPART && orig_body->nested.part->body.subtype && (!strucmp(orig_body->nested.part->body.subtype, "signed") #ifdef SMIME || !strucmp(orig_body->nested.part->body.subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE) #endif /* SMIME */ )){ /* we can call reply_body as in the call above with section * equal to "1", but that adds the multipart text to the * list of attachments. We do not want that, so we redo this * manually. */ body = copy_body(NULL, orig_body); /* * whatever subtype it is, demote it * to plain old MIXED. */ if(body->subtype) fs_give((void **) &body->subtype); body->subtype = cpystr("Mixed"); if(reply_body_text(&orig_body->nested.part->body.nested.part->body, &tmp_body)){ int partnum; reply_delimiter(env, role, pc); if(ps_global->reply.include_header) reply_forward_header(stream, msgno, sect_prefix, env, pc, prefix); /* SECTBUFLEN = sizeof(sect_buf) */ snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%.*s", SECTBUFLEN/2-2, sect_prefix ? sect_prefix : "", sect_prefix ? "." : "", SECTBUFLEN/2-2, p = partno(orig_body, tmp_body)); sect_buf[sizeof(sect_buf)-1] = '\0'; fs_give((void **) &p); get_body_part_text(stream, tmp_body, msgno, sect_buf, 0L, pc, prefix, NULL, GBPT_NONE); part = body->nested.part->next; body->nested.part->next = NULL; mail_free_body_part(&body->nested.part); body->nested.part = mail_newbody_part(); body->nested.part->body.type = TYPETEXT; body->nested.part->body.subtype = cpystr("Plain"); body->nested.part->body.contents.text.data = msgtext; body->nested.part->next = part; /* SECTBUFLEN = sizeof(sect_buf) */ for(partnum = 2; part != NULL; part = part->next){ snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%d", SECTBUFLEN/2, sect_prefix ? sect_prefix : "", sect_prefix ? "." : "", partnum++); sect_buf[sizeof(sect_buf)-1] = '\0'; if(!fetch_contents(stream, msgno, sect_buf, &part->body)){ break; } } } else { /*--- Fetch the original pieces ---*/ if(!fetch_contents(stream, msgno, sect_prefix, body)) q_status_message(SM_ORDER | SM_DING, 3, 4, _("Error including all message parts")); /*--- No text part, create a blank one ---*/ part = mail_newbody_part(); part->next = body->nested.part; body->nested.part = part; part->body.contents.text.data = msgtext; } } else if(orig_body->subtype && !strucmp(orig_body->subtype, "alternative") && orig_body->nested.part && orig_body->nested.part->next && orig_body->nested.part->next->body.type == TYPEMULTIPART && orig_body->nested.part->next->body.subtype && !strucmp(orig_body->nested.part->next->body.subtype, "MIXED")) body = reply_body(stream, env, &orig_body->nested.part->next->body, msgno, "2", msgtext, prefix, plustext, role, 0, redraft_pos); else if(orig_body->subtype && !strucmp(orig_body->subtype, "alternative")){ /* Set up the simple text reply */ body = mail_newbody(); body->type = TYPETEXT; body->contents.text.data = msgtext; if(reply_body_text(orig_body, &tmp_body)){ reply_delimiter(env, role, pc); if(ps_global->reply.include_header) reply_forward_header(stream, msgno, sect_prefix, env, pc, prefix); get_body_part_text(stream, tmp_body, msgno, p = body_partno(stream,msgno,tmp_body), 0L, pc, prefix, NULL, GBPT_NONE); if(p) fs_give((void **) &p); } else q_status_message(SM_ORDER | SM_DING, 3, 3, "No suitable multipart text found for inclusion!"); } else{ body = copy_body(NULL, orig_body); /* * whatever subtype it is, demote it * to plain old MIXED. */ if(body->subtype) fs_give((void **) &body->subtype); body->subtype = cpystr("Mixed"); if(body->nested.part && body->nested.part->body.type == TYPETEXT) { char *new_charset = NULL; /*---- First part of the message is text -----*/ body->nested.part->body.contents.text.data = msgtext; if(body->nested.part->body.subtype && strucmp(body->nested.part->body.subtype, "Plain")){ fs_give((void **)&body->nested.part->body.subtype); body->nested.part->body.subtype = cpystr("Plain"); } reply_delimiter(env, role, pc); if(ps_global->reply.include_header) reply_forward_header(stream, msgno, sect_prefix, env, pc, prefix); if(!(get_body_part_text(stream, &orig_body->nested.part->body, msgno, section, 0L, pc, prefix, &new_charset, GBPT_NONE) && fetch_contents(stream, msgno, sect_prefix, body))) q_status_message(SM_ORDER | SM_DING, 3, 4, _("Error including all message parts")); else if(new_charset) set_parameter(&body->nested.part->body.parameter, "charset", new_charset); } else if(orig_body->nested.part->body.type == TYPEMULTIPART && orig_body->nested.part->body.subtype && !strucmp(orig_body->nested.part->body.subtype, "alternative") && reply_body_text(&orig_body->nested.part->body, &tmp_body)){ int partnum; reply_delimiter(env, role, pc); if(ps_global->reply.include_header) reply_forward_header(stream, msgno, sect_prefix, env, pc, prefix); /* SECTBUFLEN = sizeof(sect_buf) */ snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%.*s", SECTBUFLEN/2-2, sect_prefix ? sect_prefix : "", sect_prefix ? "." : "", SECTBUFLEN/2-2, p = partno(orig_body, tmp_body)); sect_buf[sizeof(sect_buf)-1] = '\0'; fs_give((void **) &p); get_body_part_text(stream, tmp_body, msgno, sect_buf, 0L, pc, prefix, NULL, GBPT_NONE); part = body->nested.part->next; body->nested.part->next = NULL; mail_free_body_part(&body->nested.part); body->nested.part = mail_newbody_part(); body->nested.part->body.type = TYPETEXT; body->nested.part->body.subtype = cpystr("Plain"); body->nested.part->body.contents.text.data = msgtext; body->nested.part->next = part; /* SECTBUFLEN = sizeof(sect_buf) */ for(partnum = 2; part != NULL; part = part->next){ snprintf(sect_buf, sizeof(sect_buf), "%.*s%s%d", SECTBUFLEN/2, sect_prefix ? sect_prefix : "", sect_prefix ? "." : "", partnum++); sect_buf[sizeof(sect_buf)-1] = '\0'; if(!fetch_contents(stream, msgno, sect_buf, &part->body)){ break; } } } else { /*--- Fetch the original pieces ---*/ if(!fetch_contents(stream, msgno, sect_prefix, body)) q_status_message(SM_ORDER | SM_DING, 3, 4, _("Error including all message parts")); /*--- No text part, create a blank one ---*/ part = mail_newbody_part(); part->next = body->nested.part; body->nested.part = part; part->body.contents.text.data = msgtext; } } } else{ /*---- Single non-text message of some sort ----*/ body = mail_newbody(); body->type = TYPEMULTIPART; part = mail_newbody_part(); body->nested.part = part; /*--- The first part, a blank text part to be edited ---*/ part->body.type = TYPETEXT; part->body.contents.text.data = msgtext; /*--- The second part, what ever it is ---*/ part->next = mail_newbody_part(); part = part->next; part->body.id = generate_message_id(NULL); copy_body(&(part->body), orig_body); /* * the idea here is to fetch part into storage object */ if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE, NULL,EDIT_ACCESS)) != NULL){ if((p = pine_mail_fetch_body(stream, msgno, section, &part->body.size.bytes, NIL)) != NULL){ so_nputs((STORE_S *)part->body.contents.text.data, p, part->body.size.bytes); } else mail_free_body(&body); } else mail_free_body(&body); } } else{ /*--------- No text included --------*/ body = mail_newbody(); body->type = TYPETEXT; body->contents.text.data = msgtext; } if(!leave_cursor_at_top){ long cnt = 0L; unsigned char c; /* rewind and count chars to start of sig file */ so_seek((STORE_S *)msgtext, 0L, 0); while(so_readc(&c, (STORE_S *)msgtext)) cnt++; if(!*redraft_pos){ *redraft_pos = (REDRAFT_POS_S *)fs_get(sizeof(**redraft_pos)); memset((void *)*redraft_pos, 0,sizeof(**redraft_pos)); (*redraft_pos)->hdrname = cpystr(":"); } /* * If explicit cursor positioning in sig file, * add offset to start of sig file plus offset into sig file. * Else, just offset to start of sig file. */ (*redraft_pos)->offset += cnt; } if(sig){ if(*sig) so_puts((STORE_S *)msgtext, sig); fs_give((void **)&sig); } gf_clear_so_writec((STORE_S *) msgtext); return(body); } /* * reply_part - first replyable multipart of a multipart. */ int reply_body_text(struct mail_bodystruct *body, struct mail_bodystruct **new_body) { if(body){ switch(body->type){ case TYPETEXT : *new_body = body; return(1); case TYPEMULTIPART : if(body->subtype && !strucmp(body->subtype, "alternative")){ PART *part; int got_one = 0; if((part = body->nested.part) != NULL && part->body.type == TYPEMULTIPART) return reply_body_text(&body->nested.part->body, new_body); if(ps_global->force_prefer_plain || (!ps_global->force_no_prefer_plain && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){ for(part = body->nested.part; part; part = part->next) if((!part->body.type || part->body.type == TYPETEXT) && (!part->body.subtype || !strucmp(part->body.subtype, "plain"))){ *new_body = &part->body; return(1); } } /* * Else choose last alternative among plain or html parts. * Perhaps we should really be using mime_show() to make this * potentially more general than just plain or html. */ for(part = body->nested.part; part; part = part->next){ if((!part->body.type || part->body.type == TYPETEXT) && ((!part->body.subtype || !strucmp(part->body.subtype, "plain")) || (part->body.subtype && !strucmp(part->body.subtype, "html")))){ got_one++; *new_body = &part->body; } } if(got_one) return(1); } else if(body->nested.part) /* NOTE: we're only interested in "first" part of mixed */ return(reply_body_text(&body->nested.part->body, new_body)); break; default: break; } } return(0); } char * reply_signature(ACTION_S *role, ENVELOPE *env, REDRAFT_POS_S **redraft_pos, int *impl) { char *sig; size_t l; sig = detoken(role, env, 2, ps_global->reply.signature_bottom ? 0 : 1, 1, redraft_pos, impl); if(!ps_global->reply.signature_bottom && (!sig || !*sig)){ if(sig) fs_give((void **)&sig); l = 2 * strlen(NEWLINE); sig = (char *)fs_get((l+1) * sizeof(char)); strncpy(sig, NEWLINE, l); sig[l] = '\0'; strncat(sig, NEWLINE, l+1-1-strlen(sig)); sig[l] = '\0'; return(sig); } return(sig); } /* * Buf is at least size maxlen+1 */ void get_addr_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen) { ADDRESS *addr = NULL; ADDRESS *last_to = NULL; ADDRESS *first_addr = NULL, *second_addr = NULL; ADDRESS *third_addr = NULL, *fourth_addr = NULL; int cntaddr, l; size_t orig_maxlen; char *p; buf[0] = '\0'; switch(type){ case iFrom: addr = env ? env->from : NULL; break; case iTo: addr = env ? env->to : NULL; break; case iCc: addr = env ? env->cc : NULL; break; case iSender: addr = env ? env->sender : NULL; break; /* * Recips is To and Cc together. We hook the two adrlists together * temporarily. */ case iRecips: addr = env ? env->to : NULL; /* Find end of To list */ for(last_to = addr; last_to && last_to->next; last_to = last_to->next) ; /* Make the end of To list point to cc list */ if(last_to) last_to->next = (env ? env->cc : NULL); break; /* * Initials. */ case iInit: if(env && env->from && env->from->personal){ char *name, *initials = NULL; name = (char *)rfc1522_decode_to_utf8((unsigned char *)tmp_20k_buf, SIZEOF_20KBUF, env->from->personal); if(name == env->from->personal){ strncpy(tmp_20k_buf, name, SIZEOF_20KBUF-1); tmp_20k_buf[SIZEOF_20KBUF - 1] = '\0'; name = tmp_20k_buf; } if(name && *name){ initials = reply_quote_initials(name); iutf8ncpy(buf, initials, maxlen); buf[maxlen] = '\0'; } } return; default: break; } orig_maxlen = maxlen; first_addr = addr; /* skip over rest of c-client group addr */ if(first_addr && first_addr->mailbox && !first_addr->host){ for(second_addr = first_addr->next; second_addr && second_addr->host; second_addr = second_addr->next) ; if(second_addr && !second_addr->host) second_addr = second_addr->next; } else if(!(first_addr && first_addr->host && first_addr->host[0] == '.')) second_addr = first_addr ? first_addr->next : NULL; if(second_addr && second_addr->mailbox && !second_addr->host){ for(third_addr = second_addr->next; third_addr && third_addr->host; third_addr = third_addr->next) ; if(third_addr && !third_addr->host) third_addr = third_addr->next; } else if(!(second_addr && second_addr->host && second_addr->host[0] == '.')) third_addr = second_addr ? second_addr->next : NULL; if(third_addr && third_addr->mailbox && !third_addr->host){ for(fourth_addr = third_addr->next; fourth_addr && fourth_addr->host; fourth_addr = fourth_addr->next) ; if(fourth_addr && !fourth_addr->host) fourth_addr = fourth_addr->next; } else if(!(third_addr && third_addr->host && third_addr->host[0] == '.')) fourth_addr = third_addr ? third_addr->next : NULL; /* Just attempting to make a nice display */ if(first_addr && ((first_addr->personal && first_addr->personal[0]) || (first_addr->mailbox && first_addr->mailbox[0]))){ if(second_addr){ if((second_addr->personal && second_addr->personal[0]) || (second_addr->mailbox && second_addr->mailbox[0])){ if(third_addr){ if((third_addr->personal && third_addr->personal[0]) || (third_addr->mailbox && third_addr->mailbox[0])){ if(fourth_addr) cntaddr = 4; else cntaddr = 3; } else cntaddr = -1; } else cntaddr = 2; } else cntaddr = -1; } else cntaddr = 1; } else cntaddr = -1; p = buf; if(cntaddr == 1) a_little_addr_string(first_addr, p, maxlen); else if(cntaddr == 2){ a_little_addr_string(first_addr, p, maxlen); maxlen -= (l=strlen(p)); p += l; if(maxlen > 7){ strncpy(p, " and ", maxlen); maxlen -= 5; p += 5; a_little_addr_string(second_addr, p, maxlen); } } else if(cntaddr == 3){ a_little_addr_string(first_addr, p, maxlen); maxlen -= (l=strlen(p)); p += l; if(maxlen > 7){ strncpy(p, ", ", maxlen); maxlen -= 2; p += 2; a_little_addr_string(second_addr, p, maxlen); maxlen -= (l=strlen(p)); p += l; if(maxlen > 7){ strncpy(p, ", and ", maxlen); maxlen -= 6; p += 6; a_little_addr_string(third_addr, p, maxlen); } } } else if(cntaddr > 3){ a_little_addr_string(first_addr, p, maxlen); maxlen -= (l=strlen(p)); p += l; if(maxlen > 7){ strncpy(p, ", ", maxlen); maxlen -= 2; p += 2; a_little_addr_string(second_addr, p, maxlen); maxlen -= (l=strlen(p)); p += l; if(maxlen > 7){ strncpy(p, ", ", maxlen); maxlen -= 2; p += 2; a_little_addr_string(third_addr, p, maxlen); maxlen -= (l=strlen(p)); p += l; if(maxlen >= 12) strncpy(p, ", and others", maxlen); else if(maxlen >= 3) strncpy(p, "...", maxlen); } } } else if(addr){ char *a_string; a_string = addr_list_string(addr, NULL, 0); iutf8ncpy(buf, a_string, maxlen); fs_give((void **)&a_string); } if(last_to) last_to->next = NULL; buf[orig_maxlen] = '\0'; } /* * Buf is at least size maxlen+1 */ void get_news_data(ENVELOPE *env, IndexColType type, char *buf, size_t maxlen) { int cntnews = 0, orig_maxlen; char *news = NULL, *p, *q; switch(type){ case iNews: case iNewsAndTo: case iToAndNews: case iNewsAndRecips: case iRecipsAndNews: news = env ? env->newsgroups : NULL; break; case iCurNews: if(ps_global->mail_stream && IS_NEWS(ps_global->mail_stream)) news = ps_global->cur_folder; break; default: break; } orig_maxlen = maxlen; if(news){ q = news; while(isspace((unsigned char)*q)) q++; if(*q) cntnews++; while((q = strindex(q, ',')) != NULL){ q++; while(isspace((unsigned char)*q)) q++; if(*q) cntnews++; else break; } } if(cntnews == 1){ istrncpy(buf, news, maxlen); buf[maxlen] = '\0'; removing_leading_and_trailing_white_space(buf); } else if(cntnews == 2){ p = buf; q = news; while(isspace((unsigned char)*q)) q++; while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){ *p++ = *q++; maxlen--; } if(maxlen > 7){ strncpy(p, " and ", maxlen); p += 5; maxlen -= 5; } while(isspace((unsigned char)*q) || *q == ',') q++; while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){ *p++ = *q++; maxlen--; } *p = '\0'; istrncpy(tmp_20k_buf, buf, 10000); strncpy(buf, tmp_20k_buf, orig_maxlen); } else if(cntnews > 2){ char b[100]; p = buf; q = news; while(isspace((unsigned char)*q)) q++; while(maxlen > 0 && *q && !isspace((unsigned char)*q) && *q != ','){ *p++ = *q++; maxlen--; } *p = '\0'; snprintf(b, sizeof(b), " and %d other newsgroups", cntnews-1); b[sizeof(b)-1] = '\0'; if(maxlen >= strlen(b)) strncpy(p, b, maxlen); else if(maxlen >= 3) strncpy(p, "...", maxlen); buf[orig_maxlen] = '\0'; istrncpy(tmp_20k_buf, buf, 10000); tmp_20k_buf[10000-1] = '\0'; strncpy(buf, tmp_20k_buf, orig_maxlen); } buf[orig_maxlen] = '\0'; } void shorten_subject(char *origsubj) { char *s, *t, *u; int endlist = 0; if(origsubj == NULL || *origsubj == '\0') return; for(t=s=origsubj; *s ; s++){ switch(*s){ /* this transforms "A [B [C] D" into "A D" should this be * "A [B D"? */ case '[' : if((u = strchr(s+1,']')) != NULL){ s = u; endlist = 1; } else *t++ = *s; break; case ' ' : if(endlist == 0) *t++ = *s; break; default : endlist = 0; *t++ = *s; break; } } *t = '\0'; } /* * Buf is at least size maxlen+1 */ char * get_reply_data(ENVELOPE *env, ACTION_S *role, IndexColType type, char *buf, size_t maxlen) { char *space = NULL; IndexColType addrtype; buf[0] = '\0'; switch(type){ case iRDate: case iSDate: case iSTime: case iSTime24: case iS1Date: case iS2Date: case iS3Date: case iS4Date: case iSDateIso: case iSDateIsoS: case iSDateS1: case iSDateS2: case iSDateS3: case iSDateS4: case iSDateTime: case iSDateTimeIso: case iSDateTimeIsoS: case iSDateTimeS1: case iSDateTimeS2: case iSDateTimeS3: case iSDateTimeS4: case iSDateTime24: case iSDateTimeIso24: case iSDateTimeIsoS24: case iSDateTimeS124: case iSDateTimeS224: case iSDateTimeS324: case iSDateTimeS424: case iDateIso: case iDateIsoS: case iTime24: case iTime12: case iDay: case iDayOrdinal: case iDay2Digit: case iMonAbb: case iMonLong: case iMon: case iMon2Digit: case iYear: case iYear2Digit: case iDate: case iLDate: case iTimezone: case iDayOfWeekAbb: case iDayOfWeek: case iPrefDate: case iPrefTime: case iPrefDateTime: if(env && env->date && env->date[0] && maxlen >= 20) date_str((char *) env->date, type, 1, buf, maxlen+1, 0); break; case iCurDate: case iCurDateIso: case iCurDateIsoS: case iCurTime24: case iCurTime12: case iCurDay: case iCurDay2Digit: case iCurDayOfWeek: case iCurDayOfWeekAbb: case iCurMon: case iCurMon2Digit: case iCurMonLong: case iCurMonAbb: case iCurYear: case iCurYear2Digit: case iCurPrefDate: case iCurPrefDateTime: case iCurPrefTime: case iLstMon: case iLstMon2Digit: case iLstMonLong: case iLstMonAbb: case iLstMonYear: case iLstMonYear2Digit: case iLstYear: case iLstYear2Digit: if(maxlen >= 20) date_str(NULL, type, 1, buf, maxlen+1, 0); break; case iFrom: case iTo: case iCc: case iSender: case iRecips: case iInit: get_addr_data(env, type, buf, maxlen); break; case iRoleNick: if(role && role->nick){ strncpy(buf, role->nick, maxlen); buf[maxlen] = '\0'; } break; case iNewLine: if(maxlen >= strlen(NEWLINE)){ strncpy(buf, NEWLINE, maxlen); buf[maxlen] = '\0'; } break; case iAddress: case iMailbox: if(env && env->from && env->from->mailbox && env->from->mailbox[0] && strlen(env->from->mailbox) <= maxlen){ strncpy(buf, env->from->mailbox, maxlen); buf[maxlen] = '\0'; if(type == iAddress && env->from->host && env->from->host[0] && env->from->host[0] != '.' && strlen(buf) + strlen(env->from->host) + 1 <= maxlen){ strncat(buf, "@", maxlen+1-1-strlen(buf)); buf[maxlen] = '\0'; strncat(buf, env->from->host, maxlen+1-1-strlen(buf)); buf[maxlen] = '\0'; } } break; case iNews: case iCurNews: get_news_data(env, type, buf, maxlen); break; case iToAndNews: case iNewsAndTo: case iRecipsAndNews: case iNewsAndRecips: if(type == iToAndNews || type == iNewsAndTo) addrtype = iTo; else addrtype = iRecips; if(env && env->newsgroups){ space = (char *)fs_get((maxlen+1) * sizeof(char)); get_news_data(env, type, space, maxlen); } get_addr_data(env, addrtype, buf, maxlen); if(space && *space && *buf){ if(strlen(space) + strlen(buf) + 5 > maxlen){ if(strlen(space) > maxlen/2) get_news_data(env, type, space, maxlen - strlen(buf) - 5); else get_addr_data(env, addrtype, buf, maxlen - strlen(space) - 5); } if(type == iToAndNews || type == iRecipsAndNews) snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", buf, space); else snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s and %s", space, buf); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; strncpy(buf, tmp_20k_buf, maxlen); buf[maxlen] = '\0'; } else if(space && *space){ strncpy(buf, space, maxlen); buf[maxlen] = '\0'; } if(space) fs_give((void **)&space); break; case iSubject: case iShortSubject: if(env && env->subject){ size_t n, len; unsigned char *p, *tmp = NULL; if((n = 4*strlen(env->subject)) > SIZEOF_20KBUF-1){ len = n+1; p = tmp = (unsigned char *)fs_get(len * sizeof(char)); } else{ len = SIZEOF_20KBUF; p = (unsigned char *)tmp_20k_buf; } istrncpy(buf, (char *)rfc1522_decode_to_utf8(p, len, env->subject), maxlen); buf[maxlen] = '\0'; if(tmp) fs_give((void **)&tmp); if(type == iShortSubject) shorten_subject(buf); } break; case iMsgID: if(env && env->message_id){ strncpy(buf, env->message_id, maxlen); buf[maxlen] = '\0'; } break; default: break; } buf[maxlen] = '\0'; return(buf); } /* * reply_delimiter - output formatted reply delimiter for given envelope * with supplied character writing function. */ void reply_delimiter(ENVELOPE *env, ACTION_S *role, gf_io_t pc) { #define MAX_DELIM 2000 char buf[MAX_DELIM+1]; char *p; char *filtered = NULL; int contains_newline_token = 0; if(!env) return; strncpy(buf, ps_global->VAR_REPLY_INTRO, MAX_DELIM); buf[MAX_DELIM] = '\0'; /* preserve exact default behavior from before */ if(!strcmp(buf, DEFAULT_REPLY_INTRO)){ struct date d; int include_date; parse_date((char *) env->date, &d); include_date = !(d.day == -1 || d.month == -1 || d.year == -1); if(include_date){ gf_puts("On ", pc); /* All delims have... */ if(d.wkday != -1){ /* "On day, date month year" */ gf_puts(day_abbrev(d.wkday), pc); /* in common */ gf_puts(", ", pc); } gf_puts(int2string(d.day), pc); (*pc)(' '); gf_puts(month_abbrev(d.month), pc); (*pc)(' '); gf_puts(int2string(d.year), pc); } if(env->from && ((env->from->personal && env->from->personal[0]) || (env->from->mailbox && env->from->mailbox[0]))){ char buftmp[MAILTMPLEN]; a_little_addr_string(env->from, buftmp, sizeof(buftmp)-1); if(include_date) gf_puts(", ", pc); gf_puts(buftmp, pc); gf_puts(" wrote:", pc); } else{ if(include_date) gf_puts(", it was written", pc); else gf_puts("It was written", pc); } } else{ /* * This is here for backwards compatibility. There didn't used * to be a _NEWLINE_ token. The user would enter text that should * all fit on one line and then that was followed by two newlines. * Also, truncation occurs if it is long. * Now, if _NEWLINE_ is not in the text, same thing still works * the same. However, if _NEWLINE_ is in there, then all bets are * off and the user is on his or her own. No automatic newlines * are added, only those that come from the tokens. No truncation * is done, the user is trusted to get it right. Newlines may be * embedded so that the leadin is multi-line. */ contains_newline_token = (strstr(buf, "_NEWLINE_") != NULL); filtered = detoken_src(buf, FOR_REPLY_INTRO, env, role, NULL, NULL); /* try to truncate if too long */ if(!contains_newline_token && filtered && utf8_width(filtered) > 80){ int ended_with_colon = 0; int ended_with_quote = 0; int ended_with_quote_colon = 0; int l; l = strlen(filtered); if(filtered[l-1] == ':'){ ended_with_colon = ':'; if(filtered[l-2] == QUOTE || filtered[l-2] == '\'') ended_with_quote_colon = filtered[l-2]; } else if(filtered[l-1] == QUOTE || filtered[l-1] == '\'') ended_with_quote = filtered[l-1]; /* try to find space to break at */ for(p = &filtered[75]; p > &filtered[60] && !isspace((unsigned char)*p); p--) ; if(!isspace((unsigned char)*p)) p = &filtered[75]; *p++ = '.'; *p++ = '.'; *p++ = '.'; if(ended_with_quote_colon){ *p++ = ended_with_quote_colon; *p++ = ':'; } else if(ended_with_colon) *p++ = ended_with_colon; else if(ended_with_quote) *p++ = ended_with_quote; *p = '\0'; } if(filtered && *filtered) gf_puts(filtered, pc); } /* and end with two newlines unless no leadin at all */ if(!contains_newline_token){ if(!strcmp(buf, DEFAULT_REPLY_INTRO) || (filtered && *filtered)){ gf_puts(NEWLINE, pc); gf_puts(NEWLINE, pc); } } if(filtered) fs_give((void **)&filtered); } void free_redraft_pos(REDRAFT_POS_S **redraft_pos) { if(redraft_pos && *redraft_pos){ if((*redraft_pos)->hdrname) fs_give((void **)&(*redraft_pos)->hdrname); fs_give((void **)redraft_pos); } } /*---------------------------------------------------------------------- Build the body for the message number/part being forwarded as ATTACHMENT Args: Result: PARTS suitably attached to body ----------------------------------------------------------------------*/ int forward_mime_msg(MAILSTREAM *stream, long int msgno, char *section, ENVELOPE *env, struct mail_body_part **partp, void *msgtext) { char *tmp_text; unsigned long len; BODY *b; *partp = mail_newbody_part(); b = &(*partp)->body; b->type = TYPEMESSAGE; b->id = generate_message_id(NULL); b->description = cpystr("Forwarded Message"); b->nested.msg = mail_newmsg(); b->disposition.type = cpystr("inline"); /*---- Package each message in a storage object ----*/ if((b->contents.text.data = (void *) so_get(PART_SO_TYPE,NULL,EDIT_ACCESS)) && (tmp_text = mail_fetch_header(stream,msgno,section,NIL,NIL,FT_PEEK)) && *tmp_text){ so_puts((STORE_S *) b->contents.text.data, tmp_text); b->size.bytes = strlen(tmp_text); so_puts((STORE_S *) b->contents.text.data, "\015\012"); if((tmp_text = pine_mail_fetch_text (stream,msgno,section,&len,NIL)) != NULL){ so_nputs((STORE_S *)b->contents.text.data,tmp_text,(long) len); b->size.bytes += len; return(1); } } return(0); } /* * forward_delimiter - return delimiter for forwarded text */ void forward_delimiter(gf_io_t pc) { gf_puts(NEWLINE, pc); /* TRANSLATORS: When a message is forwarded by the user this is the text that shows where the forwarded part of the message begins. */ gf_puts(_("---------- Forwarded message ----------"), pc); gf_puts(NEWLINE, pc); } /*---------------------------------------------------------------------- Wrapper for header formatting tool Args: stream -- msgno -- env -- pc -- prefix -- Result: header suitable for reply/forward text written using "pc" ----------------------------------------------------------------------*/ void reply_forward_header(MAILSTREAM *stream, long int msgno, char *part, ENVELOPE *env, gf_io_t pc, char *prefix) { int rv; HEADER_S h; char **list, **new_list = NULL; list = ps_global->VAR_VIEW_HEADERS; /* * If VIEW_HEADERS is set, we should remove BCC from the list so that * the user doesn't inadvertently forward the BCC header. */ if(list && list[0]){ int i, cnt = 0; char **p; while(list[cnt++]) ; p = new_list = (char **) fs_get((cnt+1) * sizeof(char *)); for(i=0; list[i]; i++) if(strucmp(list[i], "bcc")) *p++ = cpystr(list[i]); *p = NULL; if(new_list && new_list[0]) list = new_list; } HD_INIT(&h, list, ps_global->view_all_except, FE_DEFAULT & ~FE_BCC); if((rv = format_header(stream, msgno, part, env, &h, prefix, NULL, FM_NOINDENT, NULL, pc)) != 0){ if(rv == 1) gf_puts(" [Error fetching message header data]", pc); } else gf_puts(NEWLINE, pc); /* write header delimiter */ if(new_list) free_list_array(&new_list); } /*---------------------------------------------------------------------- Build the subject for the message number being forwarded Args: pine_state -- The usual pine structure msgno -- The message number to build subject for Result: malloc'd string containing new subject or NULL on error ----------------------------------------------------------------------*/ char * forward_subject(ENVELOPE *env, int flags) { size_t l; char *p, buftmp[MAILTMPLEN]; if(!env) return(NULL); dprint((9, "checking subject: \"%s\"\n", env->subject ? env->subject : "NULL")); if(env->subject && env->subject[0]){ /* add (fwd)? */ snprintf(buftmp, sizeof(buftmp), "%s", env->subject); buftmp[sizeof(buftmp)-1] = '\0'; /* decode any 8bit (copy to the temp buffer if decoding doesn't) */ if(rfc1522_decode_to_utf8((unsigned char *) tmp_20k_buf, SIZEOF_20KBUF, buftmp) == (unsigned char *) buftmp) strncpy(tmp_20k_buf, buftmp, SIZEOF_20KBUF); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; removing_trailing_white_space(tmp_20k_buf); if((l = strlen(tmp_20k_buf)) < 1000 && (l < 5 || strcmp(tmp_20k_buf+l-5,"(fwd)"))){ snprintf(tmp_20k_buf+2000, SIZEOF_20KBUF-2000, "%s (fwd)", tmp_20k_buf); tmp_20k_buf[SIZEOF_20KBUF-2000-1] = '\0'; memmove(tmp_20k_buf, tmp_20k_buf+2000, strlen(tmp_20k_buf+2000)); tmp_20k_buf[strlen(tmp_20k_buf+2000)] = '\0'; } /* * HACK: composer can't handle embedded double quotes in attachment * comments so we substitute two single quotes. */ if(flags & FS_CONVERT_QUOTES) while((p = strchr(tmp_20k_buf, QUOTE)) != NULL) (void)rplstr(p, SIZEOF_20KBUF-(p-tmp_20k_buf), 1, "''"); return(cpystr(tmp_20k_buf)); } return(cpystr("Forwarded mail....")); } /*---------------------------------------------------------------------- Build the body for the message number/part being forwarded Args: Result: BODY structure suitable for sending ----------------------------------------------------------------------*/ BODY * forward_body(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body, long int msgno, char *sect_prefix, void *msgtext, int flags) { BODY *body = NULL, *text_body, *tmp_body; PART *part; gf_io_t pc; char *tmp_text, *section, sect_buf[256]; int forward_raw_body = 0; /* * Check to see if messages got expunged out from underneath us. This * could have happened during the prompt to the user asking whether to * include the message as an attachment. Either the message is gone or * it might be at a different sequence number. We'd better bail. */ if(ps_global->full_header == 2 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global)) forward_raw_body = 1; if(sp_expunge_count(stream)) return(NULL); if(sect_prefix && forward_raw_body == 0) snprintf(section = sect_buf, sizeof(sect_buf), "%s.1", sect_prefix); else if(sect_prefix && forward_raw_body) section = sect_prefix; else if(!sect_prefix && forward_raw_body) section = NULL; else section = "1"; sect_buf[sizeof(sect_buf)-1] = '\0'; gf_set_so_writec(&pc, (STORE_S *) msgtext); if(!orig_body || orig_body->type == TYPETEXT || forward_raw_body) { char *charset = NULL; /*---- Message has a single text part -----*/ body = mail_newbody(); body->type = TYPETEXT; body->contents.text.data = msgtext; if(orig_body && (charset = parameter_val(orig_body->parameter, "charset"))) set_parameter(&body->parameter, "charset", charset); if(charset) fs_give((void **) &charset); if(!(flags & FWD_ANON)){ forward_delimiter(pc); reply_forward_header(stream, msgno, sect_prefix, env, pc, ""); } if(!get_body_part_text(stream, forward_raw_body ? NULL : orig_body, msgno, section, 0L, pc, NULL, NULL, GBPT_NONE)){ mail_free_body(&body); return(NULL); } } else if(orig_body->type == TYPEMULTIPART) { if(orig_body->subtype && ((!strucmp(orig_body->subtype, "signed") && orig_body->nested.part) #ifdef SMIME || (!strucmp(orig_body->subtype, "mixed") && orig_body->nested.part && orig_body->nested.part->body.type == TYPEMULTIPART && orig_body->nested.part->body.subtype && (!strucmp(orig_body->nested.part->body.subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE) || !strucmp(orig_body->nested.part->body.subtype, "signed"))) || !strucmp(orig_body->subtype, OUR_PKCS7_ENCLOSURE_SUBTYPE) #endif /* SMIME */ )){ /* only operate on the signed data (not the signature) */ body = forward_body(stream, env, &orig_body->nested.part->body, msgno, orig_body->nested.part->body.type == TYPEMULTIPART ? section : sect_prefix, msgtext, flags); } /*---- Message is multipart ----*/ else if(!(orig_body->subtype && !strucmp(orig_body->subtype, "alternative") && (body = forward_multi_alt(stream, env, orig_body, msgno, sect_prefix, msgtext, pc, flags)))){ /*--- Copy the body and entire structure ---*/ body = copy_body(NULL, orig_body); /* * whatever subtype it is, demote it * to plain old MIXED. */ if(body->subtype) fs_give((void **) &body->subtype); body->subtype = cpystr("Mixed"); /*--- The text part of the message ---*/ if(!body->nested.part){ q_status_message(SM_ORDER | SM_DING, 3, 6, "Error referencing body part 1"); mail_free_body(&body); } else if(body->nested.part->body.type == TYPETEXT) { char *new_charset = NULL; /*--- The first part is text ----*/ text_body = &body->nested.part->body; text_body->contents.text.data = msgtext; if(text_body->subtype && strucmp(text_body->subtype, "Plain")){ /* this text is going to the composer, it should be Plain */ fs_give((void **)&text_body->subtype); text_body->subtype = cpystr("PLAIN"); } if(!(flags & FWD_ANON)){ forward_delimiter(pc); reply_forward_header(stream, msgno, sect_prefix, env, pc, ""); } if(!(get_body_part_text(stream, &orig_body->nested.part->body, msgno, section, 0L, pc, NULL, &new_charset, GBPT_NONE) && fetch_contents(stream, msgno, sect_prefix, body))) mail_free_body(&body); else if(new_charset) set_parameter(&text_body->parameter, "charset", new_charset); /* BUG: ? matter that we're not setting body.size.bytes */ } else if(body->nested.part->body.type == TYPEMULTIPART && body->nested.part->body.subtype && !strucmp(body->nested.part->body.subtype, "alternative") && (tmp_body = forward_multi_alt(stream, env, &body->nested.part->body, msgno, sect_prefix, msgtext, pc, flags | FWD_NESTED))){ /* for the forward_multi_alt call above, we want to pass * sect_prefix instead of section so we can obtain the header. * Set the FWD_NESTED flag so we fetch the right body_part. */ int partnum; part = body->nested.part->next; body->nested.part->next = NULL; mail_free_body_part(&body->nested.part); body->nested.part = mail_newbody_part(); body->nested.part->body = *tmp_body; body->nested.part->next = part; for(partnum = 2; part != NULL; part = part->next){ snprintf(sect_buf, sizeof(sect_buf), "%s%s%d", sect_prefix ? sect_prefix : "", sect_prefix ? "." : "", partnum++); sect_buf[sizeof(sect_buf)-1] = '\0'; if(!fetch_contents(stream, msgno, sect_buf, &part->body)){ mail_free_body(&body); break; } } } else { if(fetch_contents(stream, msgno, sect_prefix, body)){ /*--- Create a new blank text part ---*/ part = mail_newbody_part(); part->next = body->nested.part; body->nested.part = part; part->body.contents.text.data = msgtext; } else mail_free_body(&body); } } } else { /*---- A single part message, not of type text ----*/ body = mail_newbody(); body->type = TYPEMULTIPART; part = mail_newbody_part(); body->nested.part = part; /*--- The first part, a blank text part to be edited ---*/ part->body.type = TYPETEXT; part->body.contents.text.data = msgtext; /*--- The second part, what ever it is ---*/ part->next = mail_newbody_part(); part = part->next; part->body.id = generate_message_id(NULL); copy_body(&(part->body), orig_body); /* * the idea here is to fetch part into storage object */ if((part->body.contents.text.data = (void *) so_get(PART_SO_TYPE, NULL, EDIT_ACCESS)) != NULL){ if((tmp_text = pine_mail_fetch_body(stream, msgno, section, &part->body.size.bytes, NIL)) != NULL) so_nputs((STORE_S *)part->body.contents.text.data, tmp_text, part->body.size.bytes); else mail_free_body(&body); } else mail_free_body(&body); } gf_clear_so_writec((STORE_S *) msgtext); return(body); } /* * bounce_msg_body - build body from specified message suitable * for sending as bounced message */ char * bounce_msg_body(MAILSTREAM *stream, long int rawno, char *part, char **to, char *subject, ENVELOPE **outgoingp, BODY **bodyp, int *seenp) { char *h, *p, *errstr = NULL; int i; STORE_S *msgtext; gf_io_t pc; *outgoingp = mail_newenvelope(); (*outgoingp)->subject = cpystr(subject ? subject : "Resent mail...."); /* * Fill in destination if we were given one. If so, note that we * call p_s_s() below such that it won't prompt... */ if(to && *to){ static char *fakedomain = "@"; char *tmp_a_string; /* rfc822_parse_adrlist feels free to destroy input so copy */ tmp_a_string = cpystr(*to); rfc822_parse_adrlist(&(*outgoingp)->to, tmp_a_string, (F_ON(F_COMPOSE_REJECTS_UNQUAL, ps_global)) ? fakedomain : ps_global->maildomain); fs_give((void **) &tmp_a_string); } /* build remail'd header */ if((h = mail_fetch_header(stream, rawno, part, NULL, 0, FT_PEEK)) != NULL){ for(p = h, i = 0; (p = strchr(p, ':')) != NULL; p++) i++; /* allocate it */ (*outgoingp)->remail = (char *) fs_get(strlen(h) + (2 * i) + 1); /* * copy it, "X-"ing out transport headers bothersome to * software but potentially useful to the human recipient... */ p = (*outgoingp)->remail; bounce_mask_header(&p, h); do if(*h == '\015' && *(h+1) == '\012'){ *p++ = *h++; /* copy CR LF */ *p++ = *h++; bounce_mask_header(&p, h); } while((*p++ = *h++) != '\0'); } /* BUG: else complain? */ /* NOT bound for the composer, so no need for PicoText */ if(!(msgtext = so_get(CharStar, NULL, EDIT_ACCESS))){ mail_free_envelope(outgoingp); return(_("Error allocating message text")); } /* mark object for special handling */ so_attr(msgtext, "rawbody", "1"); /* * Build a fake body description. It's ignored by pine_rfc822_header, * but we need to set it to something that makes set_mime_types * not sniff it and pine_rfc822_output_body not re-encode it. * Setting the encoding to (ENCMAX + 1) will work and shouldn't cause * problems unless something tries to access body_encodings[] using * it without proper precautions. We don't want to use ENCOTHER * cause that tells set_mime_types to sniff it, and we don't want to * use ENC8BIT since that tells pine_rfc822_output_body to qp-encode * it. When there's time, it'd be nice to clean this interaction * up... */ *bodyp = mail_newbody(); (*bodyp)->type = TYPETEXT; (*bodyp)->encoding = ENCMAX + 1; (*bodyp)->subtype = cpystr("Plain"); (*bodyp)->contents.text.data = (void *) msgtext; gf_set_so_writec(&pc, msgtext); if(seenp && rawno > 0L && stream && rawno <= stream->nmsgs){ MESSAGECACHE *mc; if((mc = mail_elt(stream, rawno)) != NULL) *seenp = mc->seen; } /* pass NULL body to force mail_fetchtext */ if(!get_body_part_text(stream, NULL, rawno, part, 0L, pc, NULL, NULL, GBPT_NONE)) errstr = _("Error fetching message contents. Can't Bounce message"); gf_clear_so_writec(msgtext); return(errstr); } /*---------------------------------------------------------------------- Mask off any header entries we don't want xport software to see Args: d -- destination string pointer pointer s -- source string pointer pointer Postfix uses Delivered-To to detect loops. Received line counting is also used to detect loops in places. ----*/ void bounce_mask_header(char **d, char *s) { if(((*s == 'R' || *s == 'r') && (!struncmp(s+1, "esent-", 6) || !struncmp(s+1, "eceived:", 8) || !struncmp(s+1, "eturn-Path", 10))) || !struncmp(s, "Delivered-To:", 13)){ *(*d)++ = 'X'; /* write mask */ *(*d)++ = '-'; } } /*---------------------------------------------------------------------- Fetch and format text for forwarding Args: stream -- Mail stream to fetch text from body -- Body structure of message being forwarded msg_no -- Message number of text for forward part_no -- Part number of text to forward partial -- If this is > 0 a partial fetch will be done and it will be done using FT_PEEK so the message will remain unseen. pc -- Function to write to prefix -- Prefix for each line ret_charset -- If we translate to another charset return that new charset here Returns: true if OK, false if problem occurred while filtering If the text is richtext, it will be converted to plain text, since there's no rich text editing capabilities in Pine (yet). It's up to calling routines to plug in signature appropriately As with all internal text, NVT end-of-line conventions are observed. DOESN'T sanity check the prefix given!!! ----*/ int get_body_part_text(MAILSTREAM *stream, struct mail_bodystruct *body, long int msg_no, char *part_no, long partial, gf_io_t pc, char *prefix, char **ret_charset, unsigned flags) { int we_cancel = 0, dashdata, wrapflags = GFW_FORCOMPOSE, flow_res = 0; FILTLIST_S filters[12]; long len; char *err, *charset, *prefix_p = NULL; int filtcnt = 0; char *free_this = NULL; DELQ_S dq; memset(filters, 0, sizeof(filters)); if(ret_charset) *ret_charset = NULL; if(!pc_is_picotext(pc)) we_cancel = busy_cue(NULL, NULL, 1); /* if null body, we must be talking to a non-IMAP2bis server. * No MIME parsing provided, so we just grab the message text... */ if(body == NULL){ char *text, *decode_error; gf_io_t gc; SourceType src = CharStar; int rv = 0; (void) pine_mail_fetchstructure(stream, msg_no, NULL); if((text = pine_mail_fetch_text(stream, msg_no, part_no, NULL, 0)) != NULL){ gf_set_readc(&gc, text, (unsigned long)strlen(text), src, 0); gf_filter_init(); /* no filters needed */ if(prefix) gf_link_filter(gf_prefix, gf_prefix_opt(prefix)); if((decode_error = gf_pipe(gc, pc)) != NULL){ snprintf(tmp_20k_buf, SIZEOF_20KBUF, "%s%s [Formatting error: %s]%s", NEWLINE, NEWLINE, decode_error, NEWLINE); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; gf_puts(tmp_20k_buf, pc); rv++; } } else{ gf_puts(NEWLINE, pc); gf_puts(_(" [ERROR fetching text of message]"), pc); gf_puts(NEWLINE, pc); gf_puts(NEWLINE, pc); rv++; } if(we_cancel) cancel_busy_cue(-1); return(rv == 0); } charset = parameter_val(body->parameter, "charset"); if(charset && strucmp(charset, "utf-8") && strucmp(charset, "us-ascii")){ if(ret_charset) *ret_charset = "UTF-8"; } if(charset) fs_give((void **) &charset); /* * just use detach, but add an auxiliary filter to insert prefix, * and, perhaps, digest richtext */ if(ps_global->full_header != 2 && !ps_global->postpone_no_flow && (!body->subtype || !strucmp(body->subtype, "plain"))){ char *parmval; flow_res = (F_OFF(F_QUELL_FLOWED_TEXT, ps_global) && F_OFF(F_STRIP_WS_BEFORE_SEND, ps_global) && (!prefix || (strucmp(prefix,"> ") == 0) || strucmp(prefix, ">") == 0)); if((parmval = parameter_val(body->parameter, "format")) != NULL){ if(!strucmp(parmval, "flowed")){ wrapflags |= GFW_FLOWED; fs_give((void **) &parmval); if((parmval = parameter_val(body->parameter, "delsp")) != NULL){ if(!strucmp(parmval, "yes")){ filters[filtcnt++].filter = gf_preflow; wrapflags |= GFW_DELSP; } fs_give((void **) &parmval); } /* * if there's no prefix we're forwarding text * otherwise it's reply text. unless the user's * tied our hands, alter the prefix to continue flowed * formatting... */ if(flow_res && ps_global->reply.use_flowed) wrapflags |= GFW_FLOW_RESULT; filters[filtcnt].filter = gf_wrap; /* * The 80 will cause longer lines than what is likely * set by composer_fillcol, so we'll have longer * quoted and forwarded lines than the lines we type. * We're fine with that since the alternative is the * possible wrapping of lines we shouldn't have, which * is mitigated by this higher 80 limit. * * If we were to go back to using composer_fillcol, * the correct value is composer_fillcol + 1, pine * is off-by-one from pico. */ filters[filtcnt++].data = gf_wrap_filter_opt( MAX(MIN(ps_global->ttyo->screen_cols - (prefix ? strlen(prefix) : 0), 80 - (prefix ? strlen(prefix) : 0)), 30), /* doesn't have to be 30 */ 990, /* 998 is the SMTP limit */ NULL, 0, wrapflags); } } /* * if not flowed, remove trailing whitespace to reduce * confusion, since we're sending out as flowed if we * can. At some future point, we might try * plugging in a user-option-controlled heuristic * flowing filter * * We also want to fold "> " quotes so we get the * attributions correct. */ if(flow_res && ps_global->reply.use_flowed && prefix && !strucmp(prefix, "> ")) *(prefix_p = prefix + 1) = '\0'; ps_global->reply.use_flowed = 1; /* reset for next call */ if(!(wrapflags & GFW_FLOWED) && flow_res){ filters[filtcnt].filter = gf_line_test; filters[filtcnt++].data = gf_line_test_opt(twsp_strip, NULL); filters[filtcnt].filter = gf_line_test; filters[filtcnt++].data = gf_line_test_opt(quote_fold, NULL); } } else if(body->subtype){ int plain_opt = 1; if(strucmp(body->subtype,"richtext") == 0){ filters[filtcnt].filter = gf_rich2plain; filters[filtcnt++].data = gf_rich2plain_opt(&plain_opt); } else if(strucmp(body->subtype,"enriched") == 0){ filters[filtcnt].filter = gf_enriched2plain; filters[filtcnt++].data = gf_enriched2plain_opt(&plain_opt); } else if(strucmp(body->subtype,"html") == 0){ if((flags & GBPT_HTML_OK) != GBPT_HTML_OK){ filters[filtcnt].filter = gf_html2plain; filters[filtcnt++].data = gf_html2plain_opt(NULL, ps_global->ttyo->screen_cols, non_messageview_margin(), NULL, NULL, GFHP_STRIPPED); } } } if(prefix){ if(ps_global->reply.strip_signature){ dashdata = 0; filters[filtcnt].filter = gf_line_test; filters[filtcnt++].data = gf_line_test_opt(sigdash_strip, &dashdata); } filters[filtcnt].filter = gf_prefix; filters[filtcnt++].data = gf_prefix_opt(prefix); if(wrapflags & GFW_FLOWED || flow_res){ filters[filtcnt].filter = gf_line_test; filters[filtcnt++].data = gf_line_test_opt(post_quote_space, NULL); } } if(flags & GBPT_DELQUOTES){ memset(&dq, 0, sizeof(dq)); dq.lines = Q_DEL_ALL; dq.is_flowed = 0; dq.indent_length = 0; dq.saved_line = &free_this; dq.handlesp = NULL; dq.do_color = 0; dq.delete_all = 1; filters[filtcnt].filter = gf_line_test; filters[filtcnt++].data = gf_line_test_opt(delete_quotes, &dq); } err = detach(stream, msg_no, part_no, partial, &len, pc, filters[0].filter ? filters : NULL, ((flags & GBPT_PEEK) ? FT_PEEK : 0) | ((flags & GBPT_NOINTR) ? DT_NOINTR : 0)); if(free_this) fs_give((void **) &free_this); if(prefix_p) *prefix_p = ' '; if (err != (char *) NULL) /* TRANSLATORS: The first arg is error text, the %ld is the message number */ q_status_message2(SM_ORDER, 3, 4, "%s: message number %ld", err, (void *) msg_no); if(we_cancel) cancel_busy_cue(-1); return((int) len); } int quote_fold(long int linenum, char *line, LT_INS_S **ins, void *local) { char *p; if(*line == '>'){ for(p = line; *p; p++){ if(isspace((unsigned char) *p)){ if(*(p+1) == '>') ins = gf_line_test_new_ins(ins, p, "", -1); } else if(*p != '>') break; } } return(0); } int twsp_strip(long int linenum, char *line, LT_INS_S **ins, void *local) { char *p, *ws = NULL; for(p = line; *p; p++){ /* don't strip trailing space on signature line */ if(*line == '-' && *(line+1) == '-' && *(line+2) == ' ' && !*(line+3)) break; if(isspace((unsigned char) *p)){ if(!ws) ws = p; } else ws = NULL; } if(ws) ins = gf_line_test_new_ins(ins, ws, "", -(p - ws)); return(0); } int post_quote_space(long int linenum, char *line, LT_INS_S **ins, void *local) { char *p; for(p = line; *p; p++) if(*p != '>'){ if(p != line && *p != ' ') ins = gf_line_test_new_ins(ins, p, " ", 1); break; } return(0); } int sigdash_strip(long int linenum, char *line, LT_INS_S **ins, void *local) { if(*((int *)local) || (*line == '-' && *(line+1) == '-' && *(line+2) == ' ' && !*(line+3))){ *((int *) local) = 1; return(2); /* skip this line! */ } return(0); } /*---------------------------------------------------------------------- return the c-client reference name for the given end_body part ----*/ char * body_partno(MAILSTREAM *stream, long int msgno, struct mail_bodystruct *end_body) { BODY *body; (void) pine_mail_fetchstructure(stream, msgno, &body); return(partno(body, end_body)); } /*---------------------------------------------------------------------- return the c-client reference name for the given end_body part ----*/ char * partno(struct mail_bodystruct *body, struct mail_bodystruct *end_body) { #define PARTTMPLEN 64 PART *part; int num = 0; char tmp[PARTTMPLEN], *p = NULL; if(body && body->type == TYPEMULTIPART) { part = body->nested.part; /* first body part */ do { /* for each part */ num++; /* PARTTMPLEN = sizeof(tmp) */ if(&part->body == end_body || (p = partno(&part->body, end_body))){ snprintf(tmp, sizeof(tmp), "%d%s%.*s", num, (p) ? "." : "", PARTTMPLEN-12, (p) ? p : ""); tmp[sizeof(tmp)-1] = '\0'; if(p) fs_give((void **)&p); return(cpystr(tmp)); } } while ((part = part->next) != NULL); /* until done */ return(NULL); } else if(body && body->type == TYPEMESSAGE && body->subtype && !strucmp(body->subtype, "rfc822")){ return(partno(body->nested.msg->body, end_body)); } return((body == end_body) ? cpystr("1") : NULL); } /*---------------------------------------------------------------------- Fill in the contents of each body part Args: stream -- Stream the message is on msgno -- Message number the body structure is for section -- body section associated with body pointer body -- Body pointer to fill in Result: 1 if all went OK, 0 if there was a problem This function copies the contents from an original message/body to a new message/body. It recurses down all multipart levels. If one or more part (but not all) can't be fetched, a status message will be queued. ----*/ int fetch_contents(MAILSTREAM *stream, long int msgno, char *section, struct mail_bodystruct *body) { char *tp; int got_one = 0; if(!body->id) body->id = generate_message_id(NULL); if(body->type == TYPEMULTIPART){ char subsection[256], *subp; int n, last_one = 10; /* remember worst case */ PART *part = body->nested.part; if(!(part = body->nested.part)) return(0); subp = subsection; if(section && *section){ for(n = 0; n < sizeof(subsection)-20 && (*subp = section[n]); n++, subp++) ; *subp++ = '.'; } n = 1; do { snprintf(subp, sizeof(subsection)-(subp-subsection), "%d", n++); subsection[sizeof(subsection)-1] = '\0'; got_one = fetch_contents(stream, msgno, subsection, &part->body); last_one = MIN(last_one, got_one); } while((part = part->next) != NULL); return(last_one); } if(body->contents.text.data) return(1); /* already taken care of... */ if(body->type == TYPEMESSAGE){ if(body->subtype && strucmp(body->subtype,"external-body")){ /* * the idea here is to fetch everything into storage objects */ body->contents.text.data = (void *) so_get(PART_SO_TYPE, NULL, EDIT_ACCESS); if(body->contents.text.data && (tp = pine_mail_fetch_body(stream, msgno, section, &body->size.bytes, NIL))){ so_truncate((STORE_S *)body->contents.text.data, body->size.bytes + 2048); so_nputs((STORE_S *)body->contents.text.data, tp, body->size.bytes); got_one = 1; } else q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Error fetching part %s"), section); } else { got_one = 1; } } else { /* * the idea here is to fetch everything into storage objects * so, grab one, then fetch the body part */ body->contents.text.data = (void *)so_get(PART_SO_TYPE,NULL,EDIT_ACCESS); if(body->contents.text.data && (tp=pine_mail_fetch_body(stream, msgno, section, &body->size.bytes, NIL))){ so_truncate((STORE_S *)body->contents.text.data, body->size.bytes + 2048); so_nputs((STORE_S *)body->contents.text.data, tp, body->size.bytes); got_one = 1; } else q_status_message1(SM_ORDER | SM_DING, 3, 3, _("Error fetching part %s"), section); } return(got_one); } /*---------------------------------------------------------------------- Copy the body structure Args: new_body -- Pointer to already allocated body, or NULL, if none old_body -- The Body to copy This is traverses the body structure recursively copying all elements. The new_body parameter can be NULL in which case a new body is allocated. Alternatively it can point to an already allocated body structure. This is used when copying body parts since a PART includes a BODY. The contents fields are *not* filled in. ----*/ BODY * copy_body(struct mail_bodystruct *new_body, struct mail_bodystruct *old_body) { if(old_body == NULL) return(NULL); if(new_body == NULL) new_body = mail_newbody(); new_body->type = old_body->type; new_body->encoding = old_body->encoding; if(old_body->subtype) new_body->subtype = cpystr(old_body->subtype); new_body->parameter = copy_parameters(old_body->parameter); if(old_body->id) new_body->id = cpystr(old_body->id); if(old_body->description) new_body->description = cpystr(old_body->description); if(old_body->disposition.type) new_body->disposition.type = cpystr(old_body->disposition.type); new_body->disposition.parameter = copy_parameters(old_body->disposition.parameter); new_body->size = old_body->size; if(new_body->type == TYPEMESSAGE && new_body->subtype && !strucmp(new_body->subtype, "rfc822")){ new_body->nested.msg = mail_newmsg(); new_body->nested.msg->body = copy_body(NULL, old_body->nested.msg->body); } else if(new_body->type == TYPEMULTIPART) { PART **new_partp, *old_part; new_partp = &new_body->nested.part; for(old_part = old_body->nested.part; old_part != NULL; old_part = old_part->next){ *new_partp = mail_newbody_part(); copy_body(&(*new_partp)->body, &old_part->body); new_partp = &(*new_partp)->next; } } return(new_body); } /*---------------------------------------------------------------------- Copy the MIME parameter list Allocates storage for new part, and returns pointer to new parameter list. If old_p is NULL, NULL is returned. ----*/ PARAMETER * copy_parameters(PARAMETER *old_p) { PARAMETER *new_p, *p1, *p2; if(old_p == NULL) return((PARAMETER *)NULL); new_p = p2 = NULL; for(p1 = old_p; p1 != NULL; p1 = p1->next){ set_parameter(&p2, p1->attribute, p1->value); if(new_p == NULL) new_p = p2; } return(new_p); } /*---------------------------------------------------------------------- Make a complete copy of an envelope and all it's fields Args: e -- the envelope to copy Result: returns the new envelope, or NULL, if the given envelope was NULL ----*/ ENVELOPE * copy_envelope(register ENVELOPE *e) { register ENVELOPE *e2; if(!e) return(NULL); e2 = mail_newenvelope(); e2->remail = e->remail ? cpystr(e->remail) : NULL; e2->return_path = e->return_path ? rfc822_cpy_adr(e->return_path) : NULL; e2->date = e->date ? (unsigned char *)cpystr((char *) e->date) : NULL; e2->from = e->from ? rfc822_cpy_adr(e->from) : NULL; e2->sender = e->sender ? rfc822_cpy_adr(e->sender) : NULL; e2->reply_to = e->reply_to ? rfc822_cpy_adr(e->reply_to) : NULL; e2->subject = e->subject ? cpystr(e->subject) : NULL; e2->to = e->to ? rfc822_cpy_adr(e->to) : NULL; e2->cc = e->cc ? rfc822_cpy_adr(e->cc) : NULL; e2->bcc = e->bcc ? rfc822_cpy_adr(e->bcc) : NULL; e2->in_reply_to = e->in_reply_to ? cpystr(e->in_reply_to) : NULL; e2->newsgroups = e->newsgroups ? cpystr(e->newsgroups) : NULL; e2->message_id = e->message_id ? cpystr(e->message_id) : NULL; e2->references = e->references ? cpystr(e->references) : NULL; e2->followup_to = e->followup_to ? cpystr(e->references) : NULL; return(e2); } /*---------------------------------------------------------------------- Generate the "In-reply-to" text from message header Args: message -- Envelope of original message Result: returns an alloc'd string or NULL if there is a problem ----*/ char * reply_in_reply_to(ENVELOPE *env) { return((env && env->message_id) ? cpystr(env->message_id) : NULL); } /*---------------------------------------------------------------------- Generate a unique message id string. Args: ps -- The usual pine structure Result: Alloc'd unique string is returned Uniqueness is guaranteed by using the host name, process id, date to the second and a single unique character *----------------------------------------------------------------------*/ char * generate_message_id(ACTION_S *role) { char *id, *leftpart, *hostpart, *prehostpart; if(role && role->from) prehostpart = cpystr(role->from->host ? role->from->host : "huh"); else if(ps_global->maildomain) /* as in generate_from() */ prehostpart = cpystr(ps_global->maildomain); else prehostpart = cpystr(ps_global->hostname); if(F_ON(F_ROT13_MESSAGE_ID, ps_global)){ hostpart = rot13(prehostpart); leftpart = rot13(oauth2_generate_state()); } else { hostpart = prehostpart; leftpart = oauth2_generate_state(); } id = fs_get(strlen(leftpart) + strlen(hostpart) + 4); sprintf(id, "<%s@%s>", leftpart, hostpart); fs_give((void **) &hostpart); fs_give((void **) &leftpart); prehostpart = NULL; return(id); } char * generate_user_agent(void) { char buf[128]; char rev[128]; if(F_ON(F_QUELL_USERAGENT, ps_global)) return(NULL); snprintf(buf, sizeof(buf), "%sAlpine %s (%s %s)", (pith_opt_user_agent_prefix) ? (*pith_opt_user_agent_prefix)() : "", ALPINE_VERSION, SYSTYPE, get_alpine_revision_string(rev, sizeof(rev))); return(cpystr(buf)); } char * rot13(char *src) { char byte, cap, *p, *ret = NULL; if(src && *src){ ret = (char *) fs_get((strlen(src)+1) * sizeof(char)); p = ret; while((byte = *src++) != '\0'){ cap = byte & 32; byte &= ~cap; *p++ = ((byte >= 'A') && (byte <= 'Z') ? ((byte - 'A' + 13) % 26 + 'A') : byte) | cap; } *p = '\0'; } return(ret); } char * rot5n(char *src) { char byte, *p, *ret = NULL; if(src && *src){ ret = (char *) fs_get((strlen(src)+1) * sizeof(char)); p = ret; while((byte = *src++) != '\0') *p++ = ((byte >= '0') && (byte <= '9') ? ((byte - '0' + 5) % 10 + '0') : byte); *p = '\0'; } return(ret); } /*---------------------------------------------------------------------- Return the first true address pointer (modulo group syntax allowance) Args: addr -- Address list Result: First real address pointer, or NULL ----------------------------------------------------------------------*/ ADDRESS * first_addr(struct mail_address *addr) { while(addr && !addr->host) addr = addr->next; return(addr); } /*---------------------------------------------------------------------- lit -- this is the source prenewlines -- prefix the file contents with this many newlines postnewlines -- postfix the file contents with this many newlines is_sig -- this is a signature (not a template) decode_constants -- change C-style constants into their values ----*/ char * get_signature_lit(char *lit, int prenewlines, int postnewlines, int is_sig, int decode_constants) { char *sig = NULL; /* * Should make this smart enough not to do the copying and double * allocation of space. */ if(lit){ char *tmplit = NULL, *p, *q, *d, save; size_t len; if(decode_constants){ tmplit = (char *) fs_get((strlen(lit)+1) * sizeof(char)); tmplit[0] = '\0'; cstring_to_string(lit, tmplit); } else tmplit = cpystr(lit); len = strlen(tmplit) + 5 + (prenewlines+postnewlines) * strlen(NEWLINE); sig = (char *) fs_get((len+1) * sizeof(char)); memset(sig, 0, len+1); d = sig; while(prenewlines--) sstrncpy(&d, NEWLINE, len-(d-sig)); if(is_sig && F_ON(F_ENABLE_SIGDASHES, ps_global) && !sigdashes_are_present(tmplit)){ sstrncpy(&d, SIGDASHES, len-(d-sig)); sstrncpy(&d, NEWLINE, len-(d-sig)); } sig[len] = '\0'; p = tmplit; while(*p){ /* get a line */ q = strpbrk(p, "\n\r"); if(q){ save = *q; *q = '\0'; } /* * Strip trailing space if we are doing a signature and * this line is not sigdashes. */ if(is_sig && strcmp(p, SIGDASHES)) removing_trailing_white_space(p); while((d-sig) <= len && (*d = *p++) != '\0') d++; if(q){ if((d-sig) <= len) *d++ = save; p = q+1; } else break; } while(postnewlines--) sstrncpy(&d, NEWLINE, len-(d-sig)); sig[len] = '\0'; if((d-sig) <= len) *d = '\0'; if(tmplit) fs_give((void **) &tmplit); } return(sig); } int sigdashes_are_present(char *sig) { char *p; p = srchstr(sig, SIGDASHES); while(p && !((p == sig || (p[-1] == '\n' || p[-1] == '\r')) && (p[3] == '\0' || p[3] == '\n' || p[3] == '\r'))) p = srchstr(p+1, SIGDASHES); return(p ? 1 : 0); } /*---------------------------------------------------------------------- Acquire the pinerc defined signature file pathname ----*/ char * signature_path(char *sname, char *sbuf, size_t len) { *sbuf = '\0'; if(sname && *sname){ size_t spl = strlen(sname); if(IS_REMOTE(sname)){ if(spl < len - 1) strncpy(sbuf, sname, len-1); } else if(is_absolute_path(sname)){ strncpy(sbuf, sname, len-1); sbuf[len-1] = '\0'; fnexpand(sbuf, len); } else if(ps_global->VAR_OPER_DIR){ if(strlen(ps_global->VAR_OPER_DIR) + spl < len - 1) build_path(sbuf, ps_global->VAR_OPER_DIR, sname, len); } else{ char *lc = last_cmpnt(ps_global->pinerc); sbuf[0] = '\0'; if(lc != NULL){ strncpy(sbuf,ps_global->pinerc,MIN(len-1,lc-ps_global->pinerc)); sbuf[MIN(len-1,lc-ps_global->pinerc)] = '\0'; } strncat(sbuf, sname, MAX(len-1-strlen(sbuf), 0)); sbuf[len-1] = '\0'; } } return(*sbuf ? sbuf : NULL); } char * simple_read_remote_file(char *name, char *subtype) { int try_cache; REMDATA_S *rd; char *file = NULL; dprint((7, "simple_read_remote_file(%s, %s)\n", name ? name : "?", subtype ? subtype : "?")); /* * We could parse the name here to find what type it is. So far we * only have type RemImap. */ rd = rd_create_remote(RemImap, name, subtype, NULL, _("Error: "), _("Can't fetch remote configuration.")); if(!rd) goto bail_out; try_cache = rd_read_metadata(rd); if(rd->access == MaybeRorW){ if(rd->read_status == 'R') rd->access = ReadOnly; else rd->access = ReadWrite; } if(rd->access != NoExists){ rd_check_remvalid(rd, 1L); /* * If the cached info says it is readonly but * it looks like it's been fixed now, change it to readwrite. */ if(rd->read_status == 'R'){ /* * We go to this trouble since readonly sigfiles * are likely a mistake. They are usually supposed to be * readwrite so we open it and check if it's been fixed. */ rd_check_readonly_access(rd); if(rd->read_status == 'W'){ rd->access = ReadWrite; rd->flags |= REM_OUTOFDATE; } else rd->access = ReadOnly; } if(rd->flags & REM_OUTOFDATE){ if(rd_update_local(rd) != 0){ dprint((1, "simple_read_remote_file: rd_update_local failed\n")); /* * Don't give up altogether. We still may be * able to use a cached copy. */ } else{ dprint((7, "%s: copied remote to local (%ld)\n", rd->rn ? rd->rn : "?", (long)rd->last_use)); } } if(rd->access == ReadWrite) rd->flags |= DO_REMTRIM; } /* If we couldn't get to remote folder, try using the cached copy */ if(rd->access == NoExists || rd->flags & REM_OUTOFDATE){ if(try_cache){ rd->access = ReadOnly; rd->flags |= USE_OLD_CACHE; q_status_message(SM_ORDER, 3, 4, "Can't contact remote server, using cached copy"); dprint((2, "Can't open remote file %s, using local cached copy %s readonly\n", rd->rn ? rd->rn : "?", rd->lf ? rd->lf : "?")); } else{ rd->flags &= ~DO_REMTRIM; goto bail_out; } } file = read_file(rd->lf, READ_FROM_LOCALE); bail_out: if(rd) rd_close_remdata(&rd); return(file); } /* special handling for messages that contain a mixed part in the * multipart alternative section. */ BODY * forward_multi_alt_mixed(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body, long int msgno, char *sect_prefix, void *msgtext, gf_io_t pc, int flags) { #define FWDTMPLEN 256 BODY *body = NULL, *text_body = NULL; PART *part = NULL; char prefix_buf[FWDTMPLEN]; char *new_charset = NULL; int partnum; char *section, sect_buf[256]; int forward_raw_body = 0; if(orig_body && orig_body->type == TYPEMULTIPART && orig_body->subtype && !strucmp(orig_body->subtype, "alternative")) for(part = orig_body->nested.part, partnum = 1; part; part = part->next, partnum++) if(part->body.type == TYPEMULTIPART && part->body.subtype && !strucmp(part->body.subtype, "MIXED")) break; if(part == NULL) return NULL; snprintf(prefix_buf, sizeof(prefix_buf), "%.*s%s%s%d", FWDTMPLEN/2, sect_prefix ? sect_prefix : "", sect_prefix ? "." : "", flags & FWD_NESTED ? "1." : "", partnum); prefix_buf[sizeof(prefix_buf)-1] = '\0'; if(ps_global->full_header == 2 && F_ON(F_ENABLE_FULL_HDR_AND_TEXT, ps_global)) forward_raw_body = 1; if(sp_expunge_count(stream)) return(NULL); if(sect_prefix && forward_raw_body == 0) snprintf(section = sect_buf, sizeof(sect_buf), "%s.1", sect_prefix); else if(sect_prefix && forward_raw_body) section = sect_prefix; else if(!sect_prefix && forward_raw_body) section = NULL; else section = "1"; sect_buf[sizeof(sect_buf)-1] = '\0'; body = copy_body(NULL, &part->body); /*--- The text part of the message ---*/ if(!body->nested.part){ q_status_message(SM_ORDER | SM_DING, 3, 6, "Error referencing body part 1"); mail_free_body(&body); } else if(body->nested.part->body.type == TYPETEXT) { char *new_charset = NULL; /*--- The first part is text ----*/ text_body = &body->nested.part->body; text_body->contents.text.data = msgtext; if(text_body->subtype && strucmp(text_body->subtype, "Plain")){ /* this text is going to the composer, it should be Plain */ fs_give((void **)&text_body->subtype); text_body->subtype = cpystr("PLAIN"); } if(!(flags & FWD_ANON)){ forward_delimiter(pc); reply_forward_header(stream, msgno, sect_prefix, env, pc, ""); } if(!(get_body_part_text(stream, &part->body, msgno, section, 0L, pc, NULL, &new_charset, GBPT_NONE) && fetch_contents(stream, msgno, prefix_buf, body))) mail_free_body(&body); else if(new_charset) set_parameter(&text_body->parameter, "charset", new_charset); } return(body); } /*---------------------------------------------------------------------- Build the body for the multipart/alternative part Args: Result: ----------------------------------------------------------------------*/ BODY * forward_multi_alt(MAILSTREAM *stream, ENVELOPE *env, struct mail_bodystruct *orig_body, long int msgno, char *sect_prefix, void *msgtext, gf_io_t pc, int flags) { #define FWDTMPLEN 256 BODY *body = NULL; PART *part = NULL, *bestpart = NULL; char tmp_buf[FWDTMPLEN]; char *new_charset = NULL; int partnum, bestpartnum; /* try multipart mixed first */ if((body = forward_multi_alt_mixed(stream, env, orig_body, msgno, sect_prefix, msgtext, pc, flags)) != NULL) return body; if(ps_global->force_prefer_plain || (!ps_global->force_no_prefer_plain && F_ON(F_PREFER_PLAIN_TEXT, ps_global))){ for(part = orig_body->nested.part, partnum = 1; part; part = part->next, partnum++) if((!part->body.type || part->body.type == TYPETEXT) && (!part->body.subtype || !strucmp(part->body.subtype, "plain"))) break; } /* * Else choose last alternative among plain or html parts. * Perhaps we should really be using mime_show() to make this * potentially more general than just plain or html. */ if(!part){ for(part = orig_body->nested.part, partnum = 1; part; part = part->next, partnum++){ if((!part->body.type || part->body.type == TYPETEXT) && ((!part->body.subtype || !strucmp(part->body.subtype, "plain")) || (part->body.subtype && !strucmp(part->body.subtype, "html")))){ bestpart = part; bestpartnum = partnum; } } part = bestpart; partnum = bestpartnum; } /* * IF something's interesting insert it * AND forget the rest of the multipart */ if(part){ body = mail_newbody(); body->type = TYPETEXT; body->contents.text.data = msgtext; /* record character set, flowing, etc */ body->parameter = copy_parameters(part->body.parameter); body->size.bytes = part->body.size.bytes; if(!(flags & FWD_ANON)){ forward_delimiter(pc); reply_forward_header(stream, msgno, sect_prefix, env, pc, ""); } /* FWDTMPLEN = sizeof(tmp_buf) */ snprintf(tmp_buf, sizeof(tmp_buf), "%.*s%s%s%d", FWDTMPLEN/2, sect_prefix ? sect_prefix : "", sect_prefix ? "." : "", flags & FWD_NESTED ? "1." : "", partnum); tmp_buf[sizeof(tmp_buf)-1] = '\0'; get_body_part_text(stream, &part->body, msgno, tmp_buf, 0L, pc, NULL, &new_charset, GBPT_NONE); /* * get_body_part_text translated the data to a new charset. * We need to record that fact in body. */ if(new_charset) set_parameter(&body->parameter, "charset", new_charset); } else q_status_message(SM_ORDER | SM_DING, 3, 3, "No suitable part found. Forwarding as attachment"); return(body); } void reply_append_addr(struct mail_address **dest, struct mail_address *src) { for( ; *dest; dest = &(*dest)->next) ; *dest = src; }