1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2  *@ All sorts of `reply', `resend', `forward', and similar user commands.
3  *
4  * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5  * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
6  * SPDX-License-Identifier: BSD-3-Clause
7  */
8 /*
9  * Copyright (c) 1980, 1993
10  *      The Regents of the University of California.  All rights reserved.
11  *
12  * Redistribution and use in source and binary forms, with or without
13  * modification, are permitted provided that the following conditions
14  * are met:
15  * 1. Redistributions of source code must retain the above copyright
16  *    notice, this list of conditions and the following disclaimer.
17  * 2. Redistributions in binary form must reproduce the above copyright
18  *    notice, this list of conditions and the following disclaimer in the
19  *    documentation and/or other materials provided with the distribution.
20  * 3. Neither the name of the University nor the names of its contributors
21  *    may be used to endorse or promote products derived from this software
22  *    without specific prior written permission.
23  *
24  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 #undef su_FILE
37 #define su_FILE cmd_resend
38 #define mx_SOURCE
39 
40 #ifndef mx_HAVE_AMALGAMATION
41 # include "mx/nail.h"
42 #endif
43 
44 #include <su/cs.h>
45 #include <su/mem.h>
46 
47 #include "mx/attachments.h"
48 #include "mx/cmd.h"
49 #include "mx/cmd-charsetalias.h"
50 #include "mx/cmd-mlist.h"
51 #include "mx/names.h"
52 #include "mx/url.h"
53 #include "mx/tty.h"
54 
55 /* TODO fake */
56 #include "su/code-in.h"
57 
58 /* Modify subject we reply to to begin with Re: if it does not already */
59 static char *a_crese_reedit(char const *subj);
60 
61 /* Fetch these headers, as appropriate; *the_rt will be set to Reply-To:
62  * regardless of whether Reply-To: will be honoured or not */
63 static struct mx_name *a_crese_reply_to(struct message *mp,
64       struct mx_name **the_rt);
65 static struct mx_name *a_crese_mail_followup_to(struct message *mp);
66 
67 /* We honoured Reply-To: and/or Mail-Followup-To:, but *recipients-in-cc* is
68  * set so try to keep "secondary" addressees in Cc:, if possible, */
69 static void a_crese_polite_rt_mft_move(struct message *mp, struct header *hp,
70       struct mx_name *np);
71 
72 /* *reply-to-swap-in* */
73 static boole a_crese_do_rt_swap_in(struct header *hp, struct mx_name *the_rt);
74 static void a_crese_rt_swap_in(struct header *hp, struct mx_name *the_rt);
75 
76 /* References and charset, as appropriate */
77 static void a_crese_make_ref_and_cs(struct message *mp, struct header *head);
78 
79 /* `reply' and `Lreply' workhorse */
80 static int a_crese_list_reply(int *msgvec, enum header_flags hf);
81 
82 /* Get PTF to implementation of command c (i.e., take care for *flipr*) */
83 static int (*a_crese_reply_or_Reply(char c))(int *, boole);
84 
85 /* Reply to a single message.  Extract each name from the message header and
86  * send them off to mail1() */
87 static int a_crese_reply(int *msgvec, boole recipient_record);
88 
89 /* Reply to a series of messages by simply mailing to the senders and not
90  * messing around with the To: and Cc: lists as in normal reply */
91 static int a_crese_Reply(int *msgvec, boole recipient_record);
92 
93 /* Forward a message to a new recipient, in the sense of RFC 2822 */
94 static int a_crese_fwd(void *vp, boole recipient_record);
95 
96 /* Modify the subject we are replying to to begin with Fwd: */
97 static char *a_crese__fwdedit(char *subj);
98 
99 /* Do the real work of resending */
100 static int a_crese_resend1(void *v, boole add_resent);
101 
102 static char *
a_crese_reedit(char const * subj)103 a_crese_reedit(char const *subj){
104    char *newsubj;
105    NYD2_IN;
106 
107    newsubj = NULL;
108 
109    if(subj != NULL && *subj != '\0'){
110       struct str in, out;
111       uz i;
112       char const *cp;
113 
114       in.l = su_cs_len(in.s = n_UNCONST(subj));
115       mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
116 
117       i = su_cs_len(cp = subject_re_trim(out.s)) +1;
118       /* RFC mandates english "Re: " */
119       newsubj = n_autorec_alloc(sizeof("Re: ") -1 + i);
120       su_mem_copy(newsubj, "Re: ", sizeof("Re: ") -1);
121       su_mem_copy(&newsubj[sizeof("Re: ") -1], cp, i);
122 
123       n_free(out.s);
124    }
125    NYD2_OU;
126    return newsubj;
127 }
128 
129 static struct mx_name *
a_crese_reply_to(struct message * mp,struct mx_name ** the_rt)130 a_crese_reply_to(struct message *mp, struct mx_name **the_rt){
131    char const *cp;
132    struct mx_name *rt, *np;
133    enum gfield gf;
134    NYD2_IN;
135 
136    gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN;
137    rt = NIL;
138 
139    if((cp = hfield1("reply-to", mp)) != NIL)
140       rt = checkaddrs(lextract(cp, GTO | gf), EACM_STRICT, NIL);
141 
142    *the_rt = rt;
143 
144    if((cp = ok_vlook(reply_to_honour)) != NIL && rt != NIL){
145       char *lp;
146       uz l;
147       char const *tr;
148 
149       if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){
150          fprintf(n_stdout, _("Reply-To: header contains:"));
151          for(np = rt; np != NIL; np = np->n_flink)
152             fprintf(n_stdout, " %s", np->n_name);
153          putc('\n', n_stdout);
154       }
155 
156       tr = _("Reply-To %s%s");
157       l = su_cs_len(tr) + su_cs_len(rt->n_name) + 3 +1;
158       lp = n_lofi_alloc(l);
159 
160       snprintf(lp, l, tr, rt->n_name, (rt->n_flink != NIL ? "..." : su_empty));
161       if(n_quadify(cp, UZ_MAX, lp, TRU1) <= FAL0)
162          rt = NIL;
163 
164       n_lofi_free(lp);
165    }else
166       rt = NIL;
167 
168    NYD2_OU;
169    return rt;
170 }
171 
172 static struct mx_name *
a_crese_mail_followup_to(struct message * mp)173 a_crese_mail_followup_to(struct message *mp){
174    char const *cp, *cp2;
175    struct mx_name *mft, *np;
176    enum gfield gf;
177    NYD2_IN;
178 
179    gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN;
180    mft = NULL;
181 
182    if((cp = ok_vlook(followup_to_honour)) != NULL &&
183          (cp2 = hfield1("mail-followup-to", mp)) != NULL &&
184          (mft = checkaddrs(lextract(cp2, GTO | gf), EACM_STRICT, NULL)
185             ) != NULL){
186       char *lp;
187       uz l;
188       char const *tr;
189 
190       if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){
191          fprintf(n_stdout, _("Mail-Followup-To: header contains:"));
192          for(np = mft; np != NULL; np = np->n_flink)
193             fprintf(n_stdout, " %s", np->n_name);
194          putc('\n', n_stdout);
195       }
196 
197       tr = _("Followup-To %s%s");
198       l = su_cs_len(tr) + su_cs_len(mft->n_name) + 3 +1;
199       lp = n_lofi_alloc(l);
200 
201       snprintf(lp, l, tr, mft->n_name,
202          (mft->n_flink != NULL ? "..." : n_empty));
203       if(n_quadify(cp, UZ_MAX, lp, TRU1) <= FAL0)
204          mft = NULL;
205 
206       n_lofi_free(lp);
207    }
208    NYD2_OU;
209    return mft;
210 }
211 
212 static void
a_crese_polite_rt_mft_move(struct message * mp,struct header * hp,struct mx_name * np)213 a_crese_polite_rt_mft_move(struct message *mp, struct header *hp,
214       struct mx_name *np){
215    enum{
216       a_NONE,
217       a_ONCE = 1u<<0,
218       a_LIST_CLASSIFIED = 1u<<1,
219       a_SEEN_TO = 1u<<2,
220       a_ORIG_SEARCHED = 1u<<3,
221       a_ORIG_FOUND = 1u<<4
222    };
223 
224    struct mx_name *np_orig;
225    u32 f;
226    NYD2_IN;
227    UNUSED(mp);
228 
229    if(np == hp->h_to)
230       hp->h_to = NIL;
231    if(np == hp->h_cc)
232       hp->h_cc = NIL;
233 
234    /* We may find that in the end To: is empty but Cc: is not, in which case we
235     * upgrade Cc: to To: and jump back and redo the thing slightly different */
236    f = a_NONE;
237    np_orig = np;
238 jredo:
239    while(np != NIL){
240       enum gfield gf;
241       struct mx_name *nnp, **xpp, *xp;
242 
243       nnp = np;
244       np = np->n_flink;
245 
246       if(f & a_ONCE){
247          gf = GTO;
248          xpp = &hp->h_to;
249       }else{
250          gf = GCC;
251          xpp = &hp->h_cc;
252       }
253 
254       /* Try primary, then secondary */
255       for(xp = hp->h_mailx_orig_to; xp != NIL; xp = xp->n_flink)
256          if(mx_name_is_same_address(xp, nnp)){
257             if(!(f & a_LIST_CLASSIFIED)){
258                f |= a_SEEN_TO;
259                goto jclass_ok;
260             }
261             goto jlink;
262          }
263 
264       if(f & a_ONCE){
265          gf = GCC;
266          xpp = &hp->h_cc;
267       }
268 
269       for(xp = hp->h_mailx_orig_cc; xp != NIL; xp = xp->n_flink)
270          if(mx_name_is_same_address(xp, nnp))
271             goto jlink;
272 
273       /* If this receiver came in only via R-T: or M-F-T:, place her/him/it in
274        * To: due to lack of a better place.  But only if To: is not empty after
275        * all formerly present receivers have been worked, to avoid that yet
276        * unaddressed receivers propagate to To: whereas formerly addressed ones
277        * end in Cc: .. */
278       if(f & a_LIST_CLASSIFIED){
279          if(f & a_SEEN_TO){
280             /* .. with one exception: if we know the original sender, and if
281              * that no longer is a receiver, then assume the original sender
282              * desires to redirect to a different address */
283             if(!(f & a_ORIG_SEARCHED)){
284                f |= a_ORIG_SEARCHED;
285                if(hp->h_mailx_orig_sender != NIL){
286                   for(xp = np_orig; xp != NIL; xp = xp->n_flink)
287                      if(mx_name_is_same_address(xp, hp->h_mailx_orig_sender)){
288                         f |= a_ORIG_FOUND;
289                         break;
290                      }
291                }
292             }
293 
294             if(!(f & a_ORIG_FOUND))
295                goto juseto;
296             gf = GCC;
297             xpp = &hp->h_cc;
298          }else{
299 juseto:
300             gf = GTO;
301             xpp = &hp->h_to;
302          }
303       }
304 
305 jlink:
306       if(!(f & a_LIST_CLASSIFIED))
307          continue;
308 
309       /* Link it at the end to not loose original sort order */
310       if((xp = *xpp) != NIL)
311          while(xp->n_flink != NIL)
312             xp = xp->n_flink;
313 
314       if((nnp->n_blink = xp) != NIL)
315          xp->n_flink = nnp;
316       else
317          *xpp = nnp;
318       nnp->n_flink = NIL;
319       nnp->n_type = (nnp->n_type & ~GMASK) | gf;
320    }
321 
322    /* Include formerly unaddressed receivers at the right place */
323    if(!(f & a_LIST_CLASSIFIED)){
324 jclass_ok:
325       f |= a_LIST_CLASSIFIED;
326       np = np_orig;
327       goto jredo;
328    }
329 
330    /* If afterwards only Cc: data remains, upgrade all of it to To: */
331    if(hp->h_to == NIL){
332       np = hp->h_cc;
333       hp->h_cc = NIL;
334       if(!(f & a_ONCE)){
335          f |= a_ONCE;
336          hp->h_to = NIL;
337          goto jredo;
338       }else
339          for(hp->h_to = np; np != NIL; np = np->n_flink)
340             np->n_type = (np->n_type & ~GMASK) | GTO;
341    }
342    NYD2_OU;
343 }
344 
345 static boole
a_crese_do_rt_swap_in(struct header * hp,struct mx_name * the_rt)346 a_crese_do_rt_swap_in(struct header *hp, struct mx_name *the_rt){
347    struct mx_name *np;
348    char const *rtsi;
349    boole rv;
350    NYD2_IN;
351 
352    rv = FAL0;
353 
354    /* We only swap in Reply-To: if it contains only one address, because
355     * otherwise the From:/Sender: ambiguation comes into play */
356    if(the_rt != NIL && the_rt->n_flink == NIL &&
357          (rtsi = ok_vlook(reply_to_swap_in)) != NIL &&
358          (np = hp->h_mailx_orig_sender) != NIL){
359 
360       rv = TRU1;
361 
362       if(*rtsi != '\0'){
363          char *cp;
364 
365          for(cp = savestr(rtsi); (rtsi = su_cs_sep_c(&cp, ',', TRU1)) != NIL;)
366             if(!su_cs_cmp_case(rtsi, "mlist")){
367                if(mx_mlist_query(np->n_name, FAL0) == mx_MLIST_OTHER)
368                   rv = FAL0;
369             }else
370                n_err(_("*reply-to-swap-in*: unknown value: %s\n"),
371                   n_shexp_quote_cp(rtsi, FAL0));
372       }
373    }
374 
375    NYD2_OU;
376    return rv;
377 }
378 
379 static void
a_crese_rt_swap_in(struct header * hp,struct mx_name * the_rt)380 a_crese_rt_swap_in(struct header *hp, struct mx_name *the_rt){
381    NYD2_IN;
382 
383    if(a_crese_do_rt_swap_in(hp, the_rt)){
384       boole any;
385       struct mx_name *np, **xnpp, *xnp;
386 
387       np = hp->h_mailx_orig_sender;
388 
389       for(xnpp = &hp->h_from, any = FAL0;;){
390          for(xnp = *xnpp; xnp != NIL; xnp = xnp->n_flink)
391             if(mx_name_is_same_address(xnp, np)){
392                xnp->n_fullname = the_rt->n_fullname;
393                xnp->n_name = the_rt->n_name;
394                any = TRU1;
395             }
396          if(xnpp == &hp->h_from)
397             xnpp = &hp->h_sender;
398          else if(xnpp == &hp->h_sender)
399             xnpp = &hp->h_to;
400          else if(xnpp == &hp->h_to)
401             xnpp = &hp->h_cc;
402          else if(xnpp == &hp->h_cc)
403             xnpp = &hp->h_bcc;
404          else if(xnpp == &hp->h_bcc)
405             xnpp = &hp->h_reply_to;
406          else if(xnpp == &hp->h_reply_to)
407             xnpp = &hp->h_mft;
408          else
409             break;
410       }
411 
412       if(any){
413          np = ndup(np, GCC | GSKIN);
414          hp->h_cc = cat(hp->h_cc, np);
415       }
416    }
417 
418    NYD2_OU;
419 }
420 
421 static void
a_crese_make_ref_and_cs(struct message * mp,struct header * head)422 a_crese_make_ref_and_cs(struct message *mp, struct header *head) /* TODO ASAP*/
423 {
424    char const *ccp;
425    char *oldref, *oldmsgid, *newref;
426    uz oldreflen = 0, oldmsgidlen = 0, reflen;
427    unsigned i;
428    struct mx_name *n;
429    NYD2_IN;
430 
431    oldref = hfield1("references", mp);
432    oldmsgid = hfield1("message-id", mp);
433    if (oldmsgid == NULL || *oldmsgid == '\0') {
434       head->h_ref = NULL;
435       goto jleave;
436    }
437 
438    reflen = 1;
439    if (oldref) {
440       oldreflen = su_cs_len(oldref);
441       reflen += oldreflen + 2;
442    }
443    if (oldmsgid) {
444       oldmsgidlen = su_cs_len(oldmsgid);
445       reflen += oldmsgidlen;
446    }
447 
448    newref = n_alloc(reflen);
449    if (oldref != NULL) {
450       su_mem_copy(newref, oldref, oldreflen +1);
451       if (oldmsgid != NULL) {
452          newref[oldreflen++] = ',';
453          newref[oldreflen++] = ' ';
454          su_mem_copy(newref + oldreflen, oldmsgid, oldmsgidlen +1);
455       }
456    } else if (oldmsgid)
457       su_mem_copy(newref, oldmsgid, oldmsgidlen +1);
458    n = extract(newref, GREF);
459    n_free(newref);
460 
461    /* Limit number of references TODO better on parser side */
462    while (n->n_flink != NULL)
463       n = n->n_flink;
464    for (i = 1; i <= REFERENCES_MAX; ++i) {
465       if (n->n_blink != NULL)
466          n = n->n_blink;
467       else
468          break;
469    }
470    n->n_blink = NIL;
471    head->h_ref = n;
472 
473    if(ok_blook(reply_in_same_charset) &&
474          (ccp = hfield1("content-type", mp)) != NIL &&
475          (ccp = mime_param_get("charset", ccp)) != NIL)
476       head->h_charset = mx_charsetalias_expand(ccp, FAL0);
477 
478 jleave:
479    NYD2_OU;
480 }
481 
482 static int
a_crese_list_reply(int * msgvec,enum header_flags hf)483 a_crese_list_reply(int *msgvec, enum header_flags hf){
484    struct header head;
485    struct message *mp;
486    char const *cp, *cp2;
487    struct mx_name *rt, *the_rt, *mft, *np;
488    enum gfield gf;
489    NYD2_IN;
490 
491    n_autorec_relax_create();
492 
493    n_pstate_err_no = su_ERR_NONE;
494 
495    gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN;
496 
497 jwork_msg:
498    mp = &message[*msgvec - 1];
499    touch(mp);
500    setdot(mp);
501 
502    su_mem_set(&head, 0, sizeof head);
503    head.h_flags = hf;
504    head.h_subject = a_crese_reedit(hfield1("subject", mp));
505    head.h_mailx_command = (hf & HF_LIST_REPLY) ? "Lreply" : "reply";
506    /* XXX Why did i do it so, no fallback to n_header_senderfield_of()? */
507    head.h_mailx_orig_sender = mx_header_sender_of(mp, GIDENT | GFULL | gf);
508    head.h_mailx_orig_from = lextract(hfield1("from", mp), GIDENT | GFULL | gf);
509    head.h_mailx_orig_to = lextract(hfield1("to", mp), GTO | GFULL | gf);
510    head.h_mailx_orig_cc = lextract(hfield1("cc", mp), GCC | GFULL | gf);
511    head.h_mailx_orig_bcc = lextract(hfield1("bcc", mp), GBCC | GFULL | gf);
512 
513    /* First of all check for Reply-To: then Mail-Followup-To:, because these,
514     * if honoured, take precedence over anything else.  We will join the
515     * resulting list together if so desired.
516     * So if we shall honour R-T: or M-F-T:, then these are our receivers! */
517    rt = a_crese_reply_to(mp, &the_rt);
518    mft = a_crese_mail_followup_to(mp);
519 
520    if(rt != NIL || mft != NIL){
521       np = cat(rt, mft);
522       if(mft != NIL)
523          head.h_mft = n_namelist_dup(np, GTO | gf); /* xxx GTO: no "clone"! */
524 
525       /* Optionally do not propagate a receiver that originally was in
526        * secondary Cc: to the primary To: list */
527       if(ok_blook(recipients_in_cc)){
528          a_crese_polite_rt_mft_move(mp, &head, np);
529 
530          head.h_mailx_raw_cc = n_namelist_dup(head.h_cc, GCC | gf);
531          head.h_cc = mx_alternates_remove(head.h_cc, FAL0);
532       }else
533          head.h_to = np;
534 
535       head.h_mailx_raw_to = n_namelist_dup(head.h_to, GTO | gf);
536       head.h_to = mx_alternates_remove(head.h_to, FAL0);
537 #ifdef mx_HAVE_DEVEL
538       for(np = head.h_to; np != NULL; np = np->n_flink)
539          ASSERT((np->n_type & GMASK) == GTO);
540       for(np = head.h_cc; np != NULL; np = np->n_flink)
541          ASSERT((np->n_type & GMASK) == GCC);
542 #endif
543       goto jrecipients_done;
544    }
545 
546    /* Otherwise do the normal From: / To: / Cc: dance */
547 
548    if(head.h_mailx_orig_sender != NIL)
549       cp2 = head.h_mailx_orig_sender->n_fullname;
550    else
551       cp2 = n_header_senderfield_of(mp);
552 
553    /* Cc: */
554    np = NULL;
555    if(ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
556       np = lextract(cp, GCC | gf);
557    if((cp = hfield1("cc", mp)) != NULL){
558       struct mx_name *x;
559 
560       if((x = lextract(cp, GCC | gf)) != NULL)
561          np = cat(np, x);
562    }
563    if(np != NULL){
564       head.h_mailx_raw_cc = n_namelist_dup(np, GCC | gf);
565       head.h_cc = mx_alternates_remove(np, FAL0);
566    }
567 
568    /* To: */
569    np = NULL;
570    if(cp2 != NULL)
571       np = lextract(cp2, GTO | gf);
572    if(!ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL){
573       struct mx_name *x;
574 
575       if((x = lextract(cp, GTO | gf)) != NULL)
576          np = cat(np, x);
577    }
578    /* Delete my name from reply list, and with it, all my alternate names */
579    if(np != NULL){
580       head.h_mailx_raw_to = n_namelist_dup(np, GTO | gf);
581       np = mx_alternates_remove(np, FAL0);
582       /* The user may have send this to himself, don't ignore that */
583       if(count(np) == 0){
584          np = lextract(cp2, GTO | gf);
585          head.h_mailx_raw_to = n_namelist_dup(np, GTO | gf);
586       }
587    }
588    head.h_to = np;
589 
590 jrecipients_done:
591    a_crese_rt_swap_in(&head, the_rt);
592 
593    /* For list replies automatically recognize the list address given in the
594     * RFC 2369 List-Post: header, so that we will not throw away a possible
595     * corresponding receiver: temporarily "`mlist' the List-Post: address" */
596    if(hf & HF_LIST_REPLY){
597       struct mx_name *lpnp;
598 
599       if((lpnp = mx_header_list_post_of(mp)) != NIL){
600          if(lpnp == R(struct mx_name*,-1)){
601             /* Default is TRU1 because if there are still other addresses that
602              * seems to be ok, otherwise we fail anyway */
603             if(mx_tty_yesorno(_("List-Post: disallows posting; "
604                   "reply nonetheless"), TRU1))
605                lpnp = NIL;
606             else{
607                n_pstate_err_no = su_ERR_DESTADDRREQ;
608                msgvec = NIL;
609                goto jleave;
610             }
611          }
612 
613          /* A special case has been seen on e.g. ietf-announce@ietf.org:
614           * these usually post to multiple groups, with ietf-announce@
615           * in List-Post:, but with Reply-To: set to ietf@ietf.org (since
616           * -announce@ is only used for announcements, say).
617           * So our desire is to honour this request and actively overwrite
618           * List-Post: for our purpose; but only if its a single address.
619           * However, to avoid ambiguities with users that place themselves in
620           * Reply-To: and mailing lists which don't overwrite this (or only
621           * extend this, shall such exist), only do so if reply_to exists of
622           * a single address which points to the same domain as List-Post: */
623          if(rt != NIL && rt->n_flink == NIL &&
624                (lpnp == NIL || mx_name_is_same_domain(lpnp, rt)))
625             cp = rt->n_name; /* rt is EACM_STRICT tested */
626          else
627             cp = (lpnp == NIL) ? NIL : lpnp->n_name;
628 
629          /* XXX mx_mlist_query_mp()?? */
630          if(cp != NIL){
631             s8 mlt;
632 
633             if((mlt = mx_mlist_query(cp, FAL0)) == mx_MLIST_OTHER)
634                head.h_list_post = cp;
635          }
636       }
637    }
638 
639    /* In case of list replies we actively sort out any non-list recipient */
640    if(hf & HF_LIST_REPLY){
641       struct mx_name **nhpp, *nhp, *tail;
642 
643       cp = head.h_list_post;
644 
645       nhp = *(nhpp = &head.h_to);
646       head.h_to = NULL;
647 j_lt_redo:
648       for(tail = NULL; nhp != NULL;){
649          s8 mlt;
650 
651          np = nhp;
652          nhp = nhp->n_flink;
653 
654          /* XXX mx_mlist_query_mp()?? */
655          if((cp != NIL && !su_cs_cmp_case(cp, np->n_name)) ||
656                ((mlt = mx_mlist_query(np->n_name, FAL0)) != mx_MLIST_OTHER &&
657                 mlt != mx_MLIST_POSSIBLY)){
658             if((np->n_blink = tail) != NIL)
659                tail->n_flink = np;
660             else
661                *nhpp = np;
662             np->n_flink = NIL;
663             tail = np;
664          }
665       }
666       if(nhpp == &head.h_to){
667          nhp = *(nhpp = &head.h_cc);
668          head.h_cc = NULL;
669          goto j_lt_redo;
670       }
671 
672       /* For `Lreply' only, fail immediately with DESTADDRREQ if there are no
673        * receivers at all! */
674       if(head.h_to == NULL && head.h_cc == NULL){
675          n_err(_("No recipients specified for `Lreply'\n"));
676          if(msgvec[1] == 0){
677             n_pstate_err_no = su_ERR_DESTADDRREQ;
678             msgvec = NULL;
679             goto jleave;
680          }
681          goto jskip_to_next;
682       }
683    }
684 
685    /* Move Cc: to To: as appropriate! */
686    if(head.h_to == NULL && (np = head.h_cc) != NULL){
687       head.h_cc = NULL;
688       for(head.h_to = np; np != NULL; np = np->n_flink)
689          np->n_type = (np->n_type & ~GMASK) | GTO;
690    }
691 
692    a_crese_make_ref_and_cs(mp, &head);
693 
694    if(n_mail1((n_MAILSEND_HEADERS_PRINT |
695             (hf & HF_RECIPIENT_RECORD ? n_MAILSEND_RECORD_RECIPIENT : 0)),
696          &head, mp, NULL) != OKAY){
697       msgvec = NIL;
698       goto jleave;
699    }
700 
701    if(ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
702       mp->m_flag |= MANSWER | MANSWERED;
703 
704 jskip_to_next:
705 
706    if(*++msgvec != 0){
707       /* TODO message (error) ring.., less sleep */
708       if(n_psonce & n_PSO_INTERACTIVE){
709          fprintf(n_stdout,
710             _("Waiting a second before proceeding to the next message..\n"));
711          fflush(n_stdout);
712          n_msleep(1000, FAL0);
713       }
714       n_autorec_relax_unroll();
715       goto jwork_msg;
716    }
717 
718 jleave:
719    n_autorec_relax_gut();
720 
721    NYD2_OU;
722    return (msgvec == NIL ? n_EXIT_ERR : n_EXIT_OK);
723 }
724 
725 static int
a_crese_reply_or_Reply(char c)726 (*a_crese_reply_or_Reply(char c))(int *, boole){
727    int (*rv)(int*, boole);
728    NYD2_IN;
729 
730    rv = (ok_blook(flipr) ^ (c == 'R')) ? &a_crese_Reply : &a_crese_reply;
731    NYD2_OU;
732    return rv;
733 }
734 
735 static int
a_crese_reply(int * msgvec,boole recipient_record)736 a_crese_reply(int *msgvec, boole recipient_record){
737    int rv;
738    NYD2_IN;
739 
740    rv = a_crese_list_reply(msgvec,
741          (recipient_record ? HF_RECIPIENT_RECORD : HF_NONE));
742    NYD2_OU;
743    return rv;
744 }
745 
746 static int
a_crese_Reply(int * msgvec,boole recipient_record)747 a_crese_Reply(int *msgvec, boole recipient_record){
748    struct header head;
749    struct message *mp;
750    int *ap;
751    enum gfield gf;
752    NYD2_IN;
753 
754    n_pstate_err_no = su_ERR_NONE;
755 
756    su_mem_set(&head, 0, sizeof head);
757    gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN;
758 
759    mp = n_msgmark1;
760    ASSERT(mp != NIL);
761    head.h_subject = hfield1("subject", mp);
762    head.h_subject = a_crese_reedit(head.h_subject);
763    a_crese_make_ref_and_cs(mp, &head);
764    head.h_mailx_command = "Reply";
765    head.h_mailx_orig_sender = mx_header_sender_of(mp, GIDENT | GFULL | gf);
766    head.h_mailx_orig_from = lextract(hfield1("from", mp), GIDENT | GFULL | gf);
767    head.h_mailx_orig_to = lextract(hfield1("to", mp), GTO | GFULL | gf);
768    head.h_mailx_orig_cc = lextract(hfield1("cc", mp), GCC | GFULL | gf);
769    head.h_mailx_orig_bcc = lextract(hfield1("bcc", mp), GBCC | GFULL | gf);
770 
771    for(ap = msgvec; *ap != 0; ++ap){
772       struct mx_name *np, *the_rt;
773 
774       mp = &message[*ap - 1];
775       touch(mp);
776       setdot(mp);
777 
778       if((np = a_crese_reply_to(mp, &the_rt)) == NIL)
779          np = lextract(n_header_senderfield_of(mp), GTO | gf);
780 
781       if(a_crese_do_rt_swap_in(&head, the_rt)){
782          struct mx_name *np_save;
783 
784          for(np_save = np; np != NIL; np = np->n_flink)
785             if(mx_name_is_same_address(np, head.h_mailx_orig_sender)){
786                np->n_fullname = the_rt->n_fullname;
787                np->n_name = the_rt->n_name;
788             }
789          np = np_save;
790       }
791 
792       head.h_to = cat(head.h_to, np);
793    }
794 
795    mp = n_msgmark1;
796 
797    head.h_mailx_raw_to = n_namelist_dup(head.h_to, GTO | gf);
798    head.h_to = mx_alternates_remove(head.h_to, FAL0);
799 
800    if(n_mail1(((recipient_record ? n_MAILSEND_RECORD_RECIPIENT : 0) |
801             n_MAILSEND_HEADERS_PRINT), &head, mp, NULL) != OKAY){
802       msgvec = NIL;
803       goto jleave;
804    }
805 
806    if(ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
807       mp->m_flag |= MANSWER | MANSWERED;
808 
809 jleave:
810    NYD2_OU;
811    return (msgvec == NIL ? n_EXIT_ERR : n_EXIT_OK);
812 }
813 
814 static int
a_crese_fwd(void * vp,boole recipient_record)815 a_crese_fwd(void *vp, boole recipient_record){
816    struct header head;
817    struct message *mp;
818    struct mx_name *recp;
819    enum gfield gf;
820    boole forward_as_attachment;
821    int *msgvec, rv;
822    struct mx_cmd_arg *cap;
823    struct mx_cmd_arg_ctx *cacp;
824    NYD2_IN;
825 
826    n_pstate_err_no = su_ERR_NONE;
827 
828    cacp = vp;
829    cap = cacp->cac_arg;
830    msgvec = cap->ca_arg.ca_msglist;
831    cap = cap->ca_next;
832    rv = n_EXIT_ERR;
833 
834    if(cap->ca_arg.ca_str.s[0] == '\0'){
835       if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) ||
836             (n_poption & n_PO_D_V)){
837          n_err(_("No recipient specified.\n"));
838          mx_cmd_print_synopsis(mx_cmd_firstfit(cacp->cac_desc->cad_name), NIL);
839       }
840       su_err_set_no(n_pstate_err_no = su_ERR_DESTADDRREQ);
841       goto j_leave;
842    }
843 
844    forward_as_attachment = ok_blook(forward_as_attachment);
845    gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN;
846    recp = lextract(cap->ca_arg.ca_str.s, (GTO | GNOT_A_LIST | gf));
847 
848    n_autorec_relax_create();
849 
850 jwork_msg:
851    mp = &message[*msgvec - 1];
852    touch(mp);
853    setdot(mp);
854 
855    su_mem_set(&head, 0, sizeof head);
856    head.h_to = ndup(recp, (GTO | gf));
857    head.h_subject = hfield1("subject", mp);
858    head.h_subject = a_crese__fwdedit(head.h_subject);
859    head.h_mailx_command = "forward";
860    head.h_mailx_raw_to = n_namelist_dup(recp, GTO | gf);
861    head.h_mailx_orig_sender = mx_header_sender_of(mp, GIDENT | GFULL | gf);
862    head.h_mailx_orig_from = lextract(hfield1("from", mp), GIDENT | GFULL | gf);
863    head.h_mailx_orig_to = lextract(hfield1("to", mp), GTO | GFULL | gf);
864    head.h_mailx_orig_cc = lextract(hfield1("cc", mp), GCC | GFULL | gf);
865    head.h_mailx_orig_bcc = lextract(hfield1("bcc", mp), GBCC | GFULL | gf);
866 
867    if(forward_as_attachment){
868       head.h_attach = n_autorec_calloc(1, sizeof *head.h_attach);
869       head.h_attach->a_msgno = *msgvec;
870       head.h_attach->a_content_description =
871          ok_vlook(content_description_forwarded_message);
872 
873       if(head.h_mailx_orig_sender != NIL && ok_blook(forward_add_cc)){
874          gf = GCC | GSKIN;
875          if(ok_blook(fullnames))
876             gf |= GFULL;
877          head.h_cc = ndup(head.h_mailx_orig_sender, gf);
878       }
879    }
880 
881    if(n_mail1((n_MAILSEND_IS_FWD |
882             (recipient_record ? n_MAILSEND_RECORD_RECIPIENT : 0) |
883             n_MAILSEND_HEADERS_PRINT), &head,
884          (forward_as_attachment ? NIL : mp), NIL) != OKAY)
885       goto jleave;
886 
887    if(*++msgvec != 0){
888       /* TODO message (error) ring.., less sleep */
889       if(n_psonce & n_PSO_INTERACTIVE){
890          fprintf(n_stdout,
891             _("Waiting a second before proceeding to the next message..\n"));
892          fflush(n_stdout);
893          n_msleep(1000, FAL0);
894       }
895       n_autorec_relax_unroll();
896       goto jwork_msg;
897    }
898 
899    rv = n_EXIT_OK;
900 jleave:
901    n_autorec_relax_gut();
902 j_leave:
903    NYD2_OU;
904    return rv;
905 }
906 
907 static char *
a_crese__fwdedit(char * subj)908 a_crese__fwdedit(char *subj){
909    struct str in, out;
910    char *newsubj;
911    NYD2_IN;
912 
913    newsubj = NULL;
914 
915    if(subj == NULL || *subj == '\0')
916       goto jleave;
917 
918    in.s = subj;
919    in.l = su_cs_len(subj);
920    mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
921 
922    newsubj = n_autorec_alloc(out.l + 6);
923    if(!su_cs_cmp_case_n(out.s, "Fwd: ", sizeof("Fwd: ") -1)) /* TODO EXTEND */
924       su_mem_copy(newsubj, out.s, out.l +1);
925    else{
926       su_mem_copy(newsubj, "Fwd: ", 5); /* TODO ..a la subject_re_trim()! */
927       su_mem_copy(&newsubj[5], out.s, out.l +1);
928    }
929 
930    n_free(out.s);
931 jleave:
932    NYD2_OU;
933    return newsubj;
934 }
935 
936 static int
a_crese_resend1(void * vp,boole add_resent)937 a_crese_resend1(void *vp, boole add_resent){
938    struct mx_url url, *urlp = &url;
939    struct header head;
940    struct mx_name *myto, *myrawto;
941    boole mta_isexe;
942    enum gfield gf;
943    int *msgvec, rv, *ip;
944    struct mx_cmd_arg *cap;
945    struct mx_cmd_arg_ctx *cacp;
946    NYD2_IN;
947 
948    cacp = vp;
949    cap = cacp->cac_arg;
950    msgvec = cap->ca_arg.ca_msglist;
951    cap = cap->ca_next;
952    rv = 1;
953    n_pstate_err_no = su_ERR_DESTADDRREQ;
954 
955    if(cap->ca_arg.ca_str.s[0] == '\0'){
956       if(!(n_pstate & (n_PS_HOOK_MASK | n_PS_ROBOT)) || (n_poption & n_PO_D_V))
957 jedar:
958          n_err(_("No recipient specified.\n"));
959       goto jleave;
960    }
961 
962    if(!(mta_isexe = mx_sendout_mta_url(urlp))){
963       n_pstate_err_no = su_ERR_INVAL;
964       goto jleave;
965    }
966    mta_isexe = (mta_isexe != TRU1);
967 
968    gf = ok_blook(fullnames) ? GFULL | GSKIN : GSKIN;
969 
970    myrawto = nalloc(cap->ca_arg.ca_str.s, GTO | gf | GNOT_A_LIST | GNULL_OK);
971    if(myrawto == NIL)
972       goto jedar;
973 
974    su_mem_set(&head, 0, sizeof head);
975    head.h_to = n_namelist_dup(myrawto, myrawto->n_type);
976    /* C99 */{
977       s8 snderr;
978 
979       snderr = 0;
980       myto = n_namelist_vaporise_head(&head, FAL0, !ok_blook(posix),
981             (EACM_NORMAL | EACM_DOMAINCHECK |
982                (mta_isexe ? EACM_NONE : EACM_NONAME | EACM_NONAME_OR_FAIL)),
983             &snderr);
984 
985       if(snderr < 0){
986          n_err(_("Some addressees were classified as \"hard error\"\n"));
987          n_pstate_err_no = su_ERR_PERM;
988          goto jleave;
989       }
990       if(myto == NIL)
991          goto jedar;
992    }
993 
994    n_autorec_relax_create();
995    for(ip = msgvec; *ip != 0; ++ip){
996       struct message *mp;
997 
998       mp = &message[*ip - 1];
999       touch(mp);
1000       setdot(mp);
1001 
1002       su_mem_set(&head, 0, sizeof head);
1003       head.h_to = myto;
1004       head.h_mailx_command = "resend";
1005       head.h_mailx_raw_to = myrawto;
1006       head.h_mailx_orig_sender = mx_header_sender_of(mp, GIDENT | GFULL | gf);
1007       head.h_mailx_orig_from = lextract(hfield1("from", mp), GIDENT|GFULL|gf);
1008       head.h_mailx_orig_to = lextract(hfield1("to", mp), GTO | GFULL | gf);
1009       head.h_mailx_orig_cc = lextract(hfield1("cc", mp), GCC | GFULL | gf);
1010       head.h_mailx_orig_bcc = lextract(hfield1("bcc", mp), GBCC | GFULL | gf);
1011 
1012       if(n_resend_msg(mp, urlp, &head, add_resent) != OKAY){
1013          /* n_autorec_relax_gut(); XXX but is handled automatically? */
1014          goto jleave;
1015       }
1016       n_autorec_relax_unroll();
1017    }
1018    n_autorec_relax_gut();
1019 
1020    n_pstate_err_no = su_ERR_NONE;
1021    rv = 0;
1022 jleave:
1023    NYD2_OU;
1024    return rv;
1025 }
1026 
1027 FL int
c_reply(void * vp)1028 c_reply(void *vp){
1029    int rv;
1030    NYD_IN;
1031 
1032    rv = (*a_crese_reply_or_Reply('r'))(vp, FAL0);
1033    NYD_OU;
1034    return rv;
1035 }
1036 
1037 FL int
c_replyall(void * vp)1038 c_replyall(void *vp){ /* v15-compat */
1039    int rv;
1040    NYD_IN;
1041 
1042    rv = a_crese_reply(vp, FAL0);
1043    NYD_OU;
1044    return rv;
1045 }
1046 
1047 FL int
c_replysender(void * vp)1048 c_replysender(void *vp){ /* v15-compat */
1049    int rv;
1050    NYD_IN;
1051 
1052    rv = a_crese_Reply(vp, FAL0);
1053    NYD_OU;
1054    return rv;
1055 }
1056 
1057 FL int
c_Reply(void * vp)1058 c_Reply(void *vp){
1059    int rv;
1060    NYD_IN;
1061 
1062    rv = (*a_crese_reply_or_Reply('R'))(vp, FAL0);
1063    NYD_OU;
1064    return rv;
1065 }
1066 
1067 FL int
c_Lreply(void * vp)1068 c_Lreply(void *vp){
1069    int rv;
1070    NYD_IN;
1071 
1072    rv = a_crese_list_reply(vp, HF_LIST_REPLY);
1073    NYD_OU;
1074    return rv;
1075 }
1076 
1077 FL int
c_followup(void * vp)1078 c_followup(void *vp){
1079    int rv;
1080    NYD_IN;
1081 
1082    rv = (*a_crese_reply_or_Reply('r'))(vp, TRU1);
1083    NYD_OU;
1084    return rv;
1085 }
1086 
1087 FL int
c_followupall(void * vp)1088 c_followupall(void *vp){ /* v15-compat */
1089    int rv;
1090    NYD_IN;
1091 
1092    rv = a_crese_reply(vp, TRU1);
1093    NYD_OU;
1094    return rv;
1095 }
1096 
1097 FL int
c_followupsender(void * vp)1098 c_followupsender(void *vp){ /* v15-compat */
1099    int rv;
1100    NYD_IN;
1101 
1102    rv = a_crese_Reply(vp, TRU1);
1103    NYD_OU;
1104    return rv;
1105 }
1106 
1107 FL int
c_Followup(void * vp)1108 c_Followup(void *vp){
1109    int rv;
1110    NYD_IN;
1111 
1112    rv = (*a_crese_reply_or_Reply('R'))(vp, TRU1);
1113    NYD_OU;
1114    return rv;
1115 }
1116 
1117 FL int
c_Lfollowup(void * vp)1118 c_Lfollowup(void *vp){
1119    int rv;
1120    NYD_IN;
1121 
1122    rv = a_crese_list_reply(vp, HF_LIST_REPLY | HF_RECIPIENT_RECORD);
1123    NYD_OU;
1124    return rv;
1125 }
1126 
1127 FL int
c_forward(void * vp)1128 c_forward(void *vp){
1129    int rv;
1130    NYD_IN;
1131 
1132    rv = a_crese_fwd(vp, FAL0);
1133    NYD_OU;
1134    return rv;
1135 }
1136 
1137 FL int
c_Forward(void * vp)1138 c_Forward(void *vp){
1139    int rv;
1140    NYD_IN;
1141 
1142    rv = a_crese_fwd(vp, TRU1);
1143    NYD_OU;
1144    return rv;
1145 }
1146 
1147 FL int
c_resend(void * vp)1148 c_resend(void *vp){
1149    int rv;
1150    NYD_IN;
1151 
1152    rv = a_crese_resend1(vp, TRU1);
1153    NYD_OU;
1154    return rv;
1155 }
1156 
1157 FL int
c_Resend(void * vp)1158 c_Resend(void *vp){
1159    int rv;
1160    NYD_IN;
1161 
1162    rv = a_crese_resend1(vp, FAL0);
1163    NYD_OU;
1164    return rv;
1165 }
1166 
1167 #include "su/code-ou.h"
1168 /* s-it-mode */
1169