1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ All sorts of `reply', `resend', `forward', and similar user commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2017 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 #define n_FILE cmd_resend
38 #ifndef HAVE_AMALGAMATION
42 /* Modify subject we reply to to begin with Re: if it does not already */
43 static char *a_crese_reedit(char const *subj
);
45 static void make_ref_and_cs(struct message
*mp
, struct header
*head
);
47 /* `reply' and `Lreply' workhorse */
48 static int a_crese_list_reply(int *msgvec
, enum header_flags hf
);
50 /* Get PTF to implementation of command c (i.e., take care for *flipr*) */
51 static int (* _reply_or_Reply(char c
))(int *, bool_t
);
53 /* Reply to a single message. Extract each name from the message header and
54 * send them off to mail1() */
55 static int _reply(int *msgvec
, bool_t recipient_record
);
57 /* Reply to a series of messages by simply mailing to the senders and not
58 * messing around with the To: and Cc: lists as in normal reply */
59 static int _Reply(int *msgvec
, bool_t recipient_record
);
61 /* Forward a message to a new recipient, in the sense of RFC 2822 */
62 static int _fwd(char *str
, int recipient_record
);
64 /* Modify the subject we are replying to to begin with Fwd: */
65 static char * __fwdedit(char *subj
);
67 /* Do the real work of resending */
68 static int _resend1(void *v
, bool_t add_resent
);
71 a_crese_reedit(char const *subj
){
77 if(subj
!= NULL
&& *subj
!= '\0'){
82 in
.l
= strlen(in
.s
= n_UNCONST(subj
));
83 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
85 i
= strlen(cp
= subject_re_trim(out
.s
)) +1;
86 /* RFC mandates english "Re: " */
87 newsubj
= salloc(sizeof("Re: ") -1 + i
);
88 memcpy(newsubj
, "Re: ", sizeof("Re: ") -1);
89 memcpy(&newsubj
[sizeof("Re: ") -1], cp
, i
);
98 make_ref_and_cs(struct message
*mp
, struct header
*head
) /* TODO rewrite FAST */
100 char *oldref
, *oldmsgid
, *newref
, *cp
;
101 size_t oldreflen
= 0, oldmsgidlen
= 0, reflen
;
106 oldref
= hfield1("references", mp
);
107 oldmsgid
= hfield1("message-id", mp
);
108 if (oldmsgid
== NULL
|| *oldmsgid
== '\0') {
115 oldreflen
= strlen(oldref
);
116 reflen
+= oldreflen
+ 2;
119 oldmsgidlen
= strlen(oldmsgid
);
120 reflen
+= oldmsgidlen
;
123 newref
= smalloc(reflen
);
124 if (oldref
!= NULL
) {
125 memcpy(newref
, oldref
, oldreflen
+1);
126 if (oldmsgid
!= NULL
) {
127 newref
[oldreflen
++] = ',';
128 newref
[oldreflen
++] = ' ';
129 memcpy(newref
+ oldreflen
, oldmsgid
, oldmsgidlen
+1);
132 memcpy(newref
, oldmsgid
, oldmsgidlen
+1);
133 n
= extract(newref
, GREF
);
136 /* Limit number of references TODO better on parser side */
137 while (n
->n_flink
!= NULL
)
139 for (i
= 1; i
<= REFERENCES_MAX
; ++i
) {
140 if (n
->n_blink
!= NULL
)
147 if (ok_blook(reply_in_same_charset
) &&
148 (cp
= hfield1("content-type", mp
)) != NULL
){
149 if((head
->h_charset
= cp
= mime_param_get("charset", cp
)) != NULL
){
152 for(cpo
= cp
; (c
= *cpo
) != '\0'; ++cpo
)
155 head
->h_charset
= n_charsetalias_expand(cp
);
163 a_crese_list_reply(int *msgvec
, enum header_flags hf
)
167 char const *reply_to
, *rcv
, *cp
;
169 struct name
*rt
, *mft
, *np
;
173 /* TODO Since we may recur and do stuff with message lists we need to save
174 * TODO away the argument vector as long as that isn't done by machinery */
177 for (i
= 0; msgvec
[i
] != 0; ++i
)
180 save_msgvec
= ac_alloc(sizeof(*save_msgvec
) * i
);
182 save_msgvec
[i
] = msgvec
[i
];
183 msgvec
= save_msgvec
;
186 gf
= ok_blook(fullnames
) ? GFULL
| GSKIN
: GSKIN
;
189 n_autorec_relax_create();
190 mp
= &message
[*msgvec
- 1];
194 memset(&head
, 0, sizeof head
);
196 head
.h_subject
= a_crese_reedit(hfield1("subject", mp
));
197 head
.h_mailx_command
= "reply";
198 head
.h_mailx_orig_from
= lextract(hfield1("from", mp
), GIDENT
| gf
);
199 head
.h_mailx_orig_to
= lextract(hfield1("to", mp
), GTO
| gf
);
200 head
.h_mailx_orig_cc
= lextract(hfield1("cc", mp
), GCC
| gf
);
201 head
.h_mailx_orig_bcc
= lextract(hfield1("bcc", mp
), GBCC
| gf
);
205 if ((reply_to
= hfield1("reply-to", mp
)) != NULL
&&
206 (cp
= ok_vlook(reply_to_honour
)) != NULL
&&
207 (rt
= checkaddrs(lextract(reply_to
, GTO
| gf
), EACM_STRICT
, NULL
)
213 tr
= _("Reply-To %s%s");
214 l
= strlen(tr
) + strlen(rt
->n_name
) + 3 +1;
215 sp
= n_autorec_alloc(l
);
216 snprintf(sp
, l
, tr
, rt
->n_name
, (rt
->n_flink
!= NULL
? "..." : n_empty
));
218 if(quadify(cp
, UIZ_MAX
, sp
, TRU1
) > FAL0
)
222 if (rcv
== NULL
&& (rcv
= hfield1("from", mp
)) == NULL
)
227 if (ok_blook(recipients_in_cc
) && (cp
= hfield1("to", mp
)) != NULL
)
228 np
= lextract(cp
, GCC
| gf
);
229 if ((cp
= hfield1("cc", mp
)) != NULL
) {
232 if((x
= lextract(cp
, GCC
| gf
)) != NULL
)
236 head
.h_mailx_raw_cc
= namelist_dup(np
, GCC
| gf
);
237 head
.h_cc
= n_alternates_remove(np
, FAL0
);
243 np
= (rcv
== reply_to
) ? namelist_dup(rt
, GTO
| gf
)
244 : lextract(rcv
, GTO
| gf
);
245 if (!ok_blook(recipients_in_cc
) && (cp
= hfield1("to", mp
)) != NULL
) {
248 if((x
= lextract(cp
, GTO
| gf
)) != NULL
)
251 /* Delete my name from reply list, and with it, all my alternate names */
253 head
.h_mailx_raw_to
= namelist_dup(np
, GTO
| gf
);
254 np
= n_alternates_remove(np
, FAL0
);
256 np
= lextract(rcv
, GTO
| gf
);
257 head
.h_mailx_raw_to
= namelist_dup(np
, GTO
| gf
);
262 /* The user may have send this to himself, don't ignore that */
263 namelist_vaporise_head(&head
, EACM_NORMAL
, FAL0
, NULL
);
264 if (head
.h_to
== NULL
)
267 /* Mail-Followup-To: */
269 if (ok_vlook(followup_to_honour
) != NULL
&&
270 (cp
= hfield1("mail-followup-to", mp
)) != NULL
&&
271 (mft
= np
= checkaddrs(lextract(cp
, GTO
| gf
), EACM_STRICT
, NULL
)
277 tr
= _("Followup-To %s%s");
278 l
= strlen(tr
) + strlen(np
->n_name
) + 3 +1;
279 sp
= n_autorec_alloc(l
);
280 snprintf(sp
, l
, tr
, np
->n_name
, (np
->n_flink
!= NULL
? "..." : n_empty
));
282 if(quadify(ok_vlook(followup_to_honour
), UIZ_MAX
, sp
, TRU1
) > FAL0
){
286 mft
= namelist_vaporise_head(&head
, EACM_STRICT
, FAL0
, NULL
);
291 /* Special massage for list (follow-up) messages */
292 if (mft
!= NULL
|| (hf
& HF_LIST_REPLY
) || ok_blook(followup_to
)) {
293 /* Learn about a possibly sending mailing list; use do for break; */
294 if ((cp
= hfield1("list-post", mp
)) != NULL
) do {
297 if ((x
= lextract(cp
, GEXTRA
| GSKIN
)) == NULL
|| x
->n_flink
!= NULL
||
298 (cp
= url_mailto_to_address(x
->n_name
)) == NULL
||
299 /* XXX terribly wasteful to create a new name, and can't we find
300 * XXX a way to mitigate that?? */
301 is_addr_invalid(x
= nalloc(cp
, GEXTRA
| GSKIN
), EACM_STRICT
)) {
302 if(n_poption
& n_PO_D_V
)
303 n_err(_("Message contains invalid List-Post: header\n"));
309 /* A special case has been seen on e.g. ietf-announce@ietf.org:
310 * these usually post to multiple groups, with ietf-announce@
311 * in List-Post:, but with Reply-To: set to ietf@ietf.org (since
312 * -announce@ is only used for announcements, say).
313 * So our desire is to honour this request and actively overwrite
314 * List-Post: for our purpose; but only if its a single address.
315 * However, to avoid ambiguities with users that place themselve in
316 * Reply-To: and mailing lists which don't overwrite this (or only
317 * extend this, shall such exist), only do so if reply_to exists of
318 * a single address which points to the same domain as List-Post: */
319 if (rt
!= NULL
&& rt
->n_flink
== NULL
&&
320 name_is_same_domain(x
, rt
))
321 cp
= rt
->n_name
; /* rt is EACM_STRICT tested */
323 /* "Automatically `mlist'" the List-Post: address temporarily */
324 if (is_mlist(cp
, FAL0
) == MLIST_OTHER
)
325 head
.h_list_post
= cp
;
330 /* In case of list replies we actively sort out any non-list recipient,
331 * but _only_ if we did not honour a MFT:, assuming that members of MFT
332 * were there for a reason; cp is still List-Post:/eqivalent */
333 if ((hf
& HF_LIST_REPLY
) && mft
== NULL
) {
334 struct name
*nhp
= head
.h_to
;
337 while (nhp
!= NULL
) {
341 if ((cp
!= NULL
&& !asccasecmp(cp
, np
->n_name
)) ||
342 is_mlist(np
->n_name
, FAL0
) != MLIST_OTHER
) {
343 np
->n_type
= (np
->n_type
& ~GMASK
) | GTO
;
344 np
->n_flink
= head
.h_to
;
348 if ((nhp
= head
.h_cc
) != NULL
) {
355 make_ref_and_cs(mp
, &head
);
357 if (ok_blook(quote_as_attachment
)) {
358 head
.h_attach
= csalloc(1, sizeof *head
.h_attach
);
359 head
.h_attach
->a_msgno
= *msgvec
;
360 head
.h_attach
->a_content_description
= _("Original message content");
363 if (mail1(&head
, 1, mp
, NULL
, !!(hf
& HF_RECIPIENT_RECORD
), 0) != OKAY
) {
367 if(ok_blook(markanswered
) && !(mp
->m_flag
& MANSWERED
))
368 mp
->m_flag
|= MANSWER
| MANSWERED
;
369 n_autorec_relax_gut();
371 if (*++msgvec
!= 0) {
372 /* TODO message (error) ring.., less sleep */
373 if(n_psonce
& n_PSO_INTERACTIVE
){
375 _("Waiting a second before proceeding to the next message..\n"));
377 n_msleep(1000, FAL0
);
383 ac_free(save_msgvec
);
385 return (msgvec
== NULL
);
389 (*_reply_or_Reply(char c
))(int *, bool_t
)
391 int (*rv
)(int*, bool_t
);
394 rv
= (ok_blook(flipr
) ^ (c
== 'R')) ? &_Reply
: &_reply
;
400 _reply(int *msgvec
, bool_t recipient_record
)
405 rv
= a_crese_list_reply(msgvec
,
406 (recipient_record
? HF_RECIPIENT_RECORD
: HF_NONE
));
412 _Reply(int *msgvec
, bool_t recipient_record
)
421 memset(&head
, 0, sizeof head
);
422 gf
= ok_blook(fullnames
) ? GFULL
| GSKIN
: GSKIN
;
424 for (ap
= msgvec
; *ap
!= 0; ++ap
) {
428 mp
= &message
[*ap
- 1];
432 if ((rp
= hfield1("reply-to", mp
)) != NULL
&&
433 (cp
= ok_vlook(reply_to_honour
)) != NULL
&&
434 (rt
= checkaddrs(lextract(rp
, GTO
| gf
), EACM_STRICT
, NULL
)
440 tr
= _("Reply-To %s%s");
441 l
= strlen(tr
) + strlen(rt
->n_name
) + 3 +1;
442 sp
= n_autorec_alloc(l
);
443 snprintf(sp
, l
, tr
, rt
->n_name
, (rt
->n_flink
!= NULL
? "..."
446 if(quadify(cp
, UIZ_MAX
, sp
, TRU1
) > FAL0
){
447 head
.h_to
= cat(head
.h_to
, rt
);
452 if ((cp
= hfield1("from", mp
)) == NULL
)
454 head
.h_to
= cat(head
.h_to
, lextract(cp
, GTO
| gf
));
456 if (head
.h_to
== NULL
) /* XXX no recipients? */
458 head
.h_mailx_raw_to
= namelist_dup(head
.h_to
, GTO
| gf
);
460 mp
= &message
[msgvec
[0] - 1];
461 head
.h_subject
= hfield1("subject", mp
);
462 head
.h_subject
= a_crese_reedit(head
.h_subject
);
463 make_ref_and_cs(mp
, &head
);
464 head
.h_mailx_command
= "reply";
465 head
.h_mailx_orig_from
= lextract(hfield1("from", mp
), GIDENT
| gf
);
466 head
.h_mailx_orig_to
= lextract(hfield1("to", mp
), GTO
| gf
);
467 head
.h_mailx_orig_cc
= lextract(hfield1("cc", mp
), GCC
| gf
);
468 head
.h_mailx_orig_bcc
= lextract(hfield1("bcc", mp
), GBCC
| gf
);
470 if (ok_blook(quote_as_attachment
)) {
471 head
.h_attach
= csalloc(1, sizeof *head
.h_attach
);
472 head
.h_attach
->a_msgno
= *msgvec
;
473 head
.h_attach
->a_content_description
= _("Original message content");
476 if (mail1(&head
, 1, mp
, NULL
, recipient_record
, 0) != OKAY
) {
481 if(ok_blook(markanswered
) && !(mp
->m_flag
& MANSWERED
))
482 mp
->m_flag
|= MANSWER
| MANSWERED
;
485 return (msgvec
== NULL
);
489 _fwd(char *str
, int recipient_record
)
495 bool_t f
, forward_as_attachment
;
499 if ((recipient
= laststring(str
, &f
, TRU1
)) == NULL
) {
500 fputs(_("No recipient specified.\n"), n_stdout
);
504 forward_as_attachment
= ok_blook(forward_as_attachment
);
505 gf
= ok_blook(fullnames
) ? GFULL
| GSKIN
: GSKIN
;
506 msgvec
= salloc((msgCount
+ 2) * sizeof *msgvec
);
509 *msgvec
= first(0, MMNORM
);
511 if (n_pstate
& (n_PS_HOOK_MASK
| n_PS_ROBOT
)) {
515 fprintf(n_stdout
, _("No messages to forward.\n"));
519 } else if (getmsglist(str
, msgvec
, 0) < 0)
523 if (n_pstate
& (n_PS_HOOK_MASK
| n_PS_ROBOT
)) {
527 fprintf(n_stdout
, _("No applicable messages.\n"));
530 if (msgvec
[1] != 0) {
531 fprintf(n_stdout
, _("Cannot forward multiple messages at once\n"));
535 memset(&head
, 0, sizeof head
);
536 if ((head
.h_to
= lextract(recipient
,
537 (GTO
| (ok_blook(fullnames
) ? GFULL
: GSKIN
)))) == NULL
)
540 mp
= &message
[*msgvec
- 1];
543 head
.h_subject
= hfield1("subject", mp
);
544 head
.h_subject
= __fwdedit(head
.h_subject
);
545 head
.h_mailx_command
= "forward";
546 head
.h_mailx_raw_to
= namelist_dup(head
.h_to
, GTO
| gf
);
547 head
.h_mailx_orig_from
= lextract(hfield1("from", mp
), GIDENT
| gf
);
548 head
.h_mailx_orig_to
= lextract(hfield1("to", mp
), GTO
| gf
);
549 head
.h_mailx_orig_cc
= lextract(hfield1("cc", mp
), GCC
| gf
);
550 head
.h_mailx_orig_bcc
= lextract(hfield1("bcc", mp
), GBCC
| gf
);
552 if (forward_as_attachment
) {
553 head
.h_attach
= csalloc(1, sizeof *head
.h_attach
);
554 head
.h_attach
->a_msgno
= *msgvec
;
555 head
.h_attach
->a_content_description
= _("Forwarded message");
557 rv
= (mail1(&head
, 1, (forward_as_attachment
? NULL
: mp
), NULL
,
558 recipient_record
, 1) != OKAY
); /* reverse! */
565 __fwdedit(char *subj
)
568 char *newsubj
= NULL
;
571 if (subj
== NULL
|| *subj
== '\0')
576 mime_fromhdr(&in
, &out
, TD_ISPR
| TD_ICONV
);
578 newsubj
= salloc(out
.l
+ 6);
579 memcpy(newsubj
, "Fwd: ", 5); /* XXX localizable */
580 memcpy(newsubj
+ 5, out
.s
, out
.l
+1);
588 _resend1(void *v
, bool_t add_resent
)
591 struct name
*myto
, *myrawto
;
599 msgvec
= salloc((msgCount
+ 2) * sizeof *msgvec
);
600 name
= laststring(str
, &f
, TRU1
);
602 fputs(_("No recipient specified.\n"), n_stdout
);
607 *msgvec
= first(0, MMNORM
);
609 if (n_pstate
& (n_PS_HOOK_MASK
| n_PS_ROBOT
)) {
613 fputs(_("No applicable messages.\n"), n_stdout
);
617 } else if (getmsglist(str
, msgvec
, 0) < 0)
621 if (n_pstate
& (n_PS_HOOK_MASK
| n_PS_ROBOT
)) {
625 fprintf(n_stdout
, "No applicable messages.\n");
630 gf
= ok_blook(fullnames
) ? GFULL
| GSKIN
: GSKIN
;
632 myrawto
= nalloc(name
, GTO
| gf
);
633 myto
= usermap(namelist_dup(myrawto
, myrawto
->n_type
), FAL0
);
634 myto
= n_alternates_remove(myto
, TRU1
);
636 n_err(_("No recipients specified\n"));
640 n_autorec_relax_create();
641 for (ip
= msgvec
; *ip
!= 0 && UICMP(z
, PTR2SIZE(ip
- msgvec
), <, msgCount
);
645 mp
= &message
[*ip
- 1];
649 memset(&head
, 0, sizeof head
);
651 head
.h_mailx_command
= "resend";
652 head
.h_mailx_raw_to
= myrawto
;
653 head
.h_mailx_orig_from
= lextract(hfield1("from", mp
), GIDENT
| gf
);
654 head
.h_mailx_orig_to
= lextract(hfield1("to", mp
), GTO
| gf
);
655 head
.h_mailx_orig_cc
= lextract(hfield1("cc", mp
), GCC
| gf
);
656 head
.h_mailx_orig_bcc
= lextract(hfield1("bcc", mp
), GBCC
| gf
);
658 if(resend_msg(mp
, &head
, add_resent
) != OKAY
){
659 /* n_autorec_relax_gut(); XXX but is handled automatically? */
662 n_autorec_relax_unroll();
664 n_autorec_relax_gut();
677 rv
= (*_reply_or_Reply('r'))(v
, FAL0
);
688 rv
= _reply(v
, FAL0
);
694 c_replysender(void *v
)
699 rv
= _Reply(v
, FAL0
);
710 rv
= (*_reply_or_Reply('R'))(v
, FAL0
);
721 rv
= a_crese_list_reply(v
, HF_LIST_REPLY
);
732 rv
= (*_reply_or_Reply('r'))(v
, TRU1
);
738 c_followupall(void *v
)
743 rv
= _reply(v
, TRU1
);
749 c_followupsender(void *v
)
754 rv
= _Reply(v
, TRU1
);
765 rv
= (*_reply_or_Reply('R'))(v
, TRU1
);
798 rv
= _resend1(v
, TRU1
);
809 rv
= _resend1(v
, FAL0
);