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