*quote-as-attachment*: handle in compose-mode, very last..
[s-mailx.git] / collect.c
blob04a2c4a3cffcb52e2c7d8850332961998656fe08
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Collect input from standard input, handling ~ escapes.
3 *@ TODO This needs a complete rewrite, with carriers, etc.
5 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
6 * Copyright (c) 2012 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
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.
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.
36 #undef n_FILE
37 #define n_FILE collect
39 #ifndef HAVE_AMALGAMATION
40 # include "nail.h"
41 #endif
43 struct a_coll_fmt_ctx{ /* xxx This is temporary until v15 has objects */
44 char const *cfc_fmt;
45 FILE *cfc_fp;
46 struct message *cfc_mp;
47 char *cfc_cumul;
48 char *cfc_addr;
49 char *cfc_real;
50 char *cfc_full;
51 char *cfc_date;
52 char *cfc_msgid; /* Or NULL */
55 struct a_coll_ocs_arg{
56 sighandler_type coa_opipe;
57 sighandler_type coa_oint;
58 FILE *coa_stdin; /* The descriptor (pipe(2)+Fdopen()) we read from */
59 FILE *coa_stdout; /* The Popen()ed pipe through which we write to the hook */
60 int coa_pipe[2]; /* ..backing .coa_stdin */
61 si8_t *coa_senderr; /* Set to 1 on failure */
62 char coa_cmd[n_VFIELD_SIZE(0)];
65 /* The following hookiness with global variables is so that on receipt of an
66 * interrupt signal, the partial message can be salted away on *DEAD* */
68 static sighandler_type _coll_saveint; /* Previous SIGINT value */
69 static sighandler_type _coll_savehup; /* Previous SIGHUP value */
70 static FILE *_coll_fp; /* File for saving away */
71 static int volatile _coll_hadintr; /* Have seen one SIGINT so far */
72 static sigjmp_buf _coll_jmp; /* To get back to work */
73 static sigjmp_buf _coll_abort; /* To end collection with error */
74 static char const *a_coll_ocs__macname; /* *on-compose-splice* */
76 /* Handle `~:', `~_' and some hooks; hp may be NULL */
77 static void _execute_command(struct header *hp, char const *linebuf,
78 size_t linesize);
80 /* Return errno */
81 static si32_t a_coll_include_file(char const *name, bool_t indent,
82 bool_t writestat);
84 /* Execute cmd and insert its standard output into fp, return errno */
85 static si32_t a_coll_insert_cmd(FILE *fp, char const *cmd);
87 /* ~p command */
88 static void print_collf(FILE *collf, struct header *hp);
90 /* Write a file, ex-like if f set */
91 static si32_t a_coll_write(char const *name, FILE *fp, int f);
93 /* *message-inject-head* */
94 static bool_t a_coll_message_inject_head(FILE *fp);
96 /* With bells and whistles */
97 static bool_t a_coll_quote_message(FILE *fp, struct message *mp, bool_t isfwd);
99 /* *{forward,quote}-inject-{head,tail}*.
100 * fmt may be NULL or the empty string, in which case no output is produced */
101 static bool_t a_coll__fmt_inj(struct a_coll_fmt_ctx const *cfcp);
103 /* Parse off the message header from fp and store relevant fields in hp,
104 * replace _coll_fp with a shiny new version without any header */
105 static bool_t a_coll_makeheader(FILE *fp, struct header *hp,
106 si8_t *checkaddr_err, bool_t do_delayed_due_t);
108 /* Edit the message being collected on fp.
109 * If c=='|' pipecmd must be set and is passed through to n_run_editor().
110 * On successful return, make the edit file the new temp file; return errno */
111 static si32_t a_coll_edit(int c, struct header *hp, char const *pipecmd);
113 /* Pipe the message through the command. Old message is on stdin of command,
114 * new message collected from stdout. Shell must return 0 to accept new msg */
115 static si32_t a_coll_pipe(char const *cmd);
117 /* Interpolate the named messages into the current message, possibly doing
118 * indent stuff. The flag argument is one of the command escapes: [mMfFuU].
119 * Return errno */
120 static si32_t a_coll_forward(char const *ms, FILE *fp, int f);
122 /* ~^ mode */
123 static bool_t a_collect_plumbing(char const *ms, struct header *p);
125 static bool_t a_collect__plumb_header(char const *cp, struct header *p,
126 char const *cmd[4]);
127 static bool_t a_collect__plumb_attach(char const *cp, struct header *p,
128 char const *cmd[4]);
130 /* On interrupt, come here to save the partial message in ~/dead.letter.
131 * Then jump out of the collection loop */
132 static void _collint(int s);
134 static void collhup(int s);
136 /* ~[AaIi], *message-inject-**: put value, expand \[nt] if *posix* */
137 static bool_t a_coll_putesc(char const *s, bool_t addnl, FILE *stream);
139 /* *on-compose-splice* driver and *on-compose-splice(-shell)?* finalizer */
140 static int a_coll_ocs__mac(void);
141 static void a_coll_ocs__finalize(void *vp);
143 static void
144 _execute_command(struct header *hp, char const *linebuf, size_t linesize){
145 /* The problem arises if there are rfc822 message attachments and the
146 * user uses `~:' to change the current file. TODO Unfortunately we
147 * TODO cannot simply keep a pointer to, or increment a reference count
148 * TODO of the current `file' (mailbox that is) object, because the
149 * TODO codebase doesn't deal with that at all; so, until some far
150 * TODO later time, copy the name of the path, and warn the user if it
151 * TODO changed; we COULD use the AC_TMPFILE attachment type, i.e.,
152 * TODO copy the message attachments over to temporary files, but that
153 * TODO would require more changes so that the user still can recognize
154 * TODO in `~@' etc. that its a rfc822 message attachment; see below */
155 struct n_sigman sm;
156 struct attachment *ap;
157 char * volatile mnbuf;
158 NYD_ENTER;
160 n_UNUSED(linesize);
161 mnbuf = NULL;
163 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_HUP | n_SIGMAN_INT | n_SIGMAN_QUIT){
164 case 0:
165 break;
166 default:
167 n_pstate_err_no = n_ERR_INTR;
168 n_pstate_ex_no = 1;
169 goto jleave;
172 /* If the above todo is worked, remove or outsource to attachment.c! */
173 if(hp != NULL && (ap = hp->h_attach) != NULL) do
174 if(ap->a_msgno){
175 mnbuf = sstrdup(mailname);
176 break;
178 while((ap = ap->a_flink) != NULL);
180 n_go_command(n_GO_INPUT_CTX_COMPOSE, linebuf);
182 n_sigman_cleanup_ping(&sm);
183 jleave:
184 if(mnbuf != NULL){
185 if(strcmp(mnbuf, mailname))
186 n_err(_("Mailbox changed: it is likely that existing "
187 "rfc822 attachments became invalid!\n"));
188 n_free(mnbuf);
190 NYD_LEAVE;
191 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
194 static si32_t
195 a_coll_include_file(char const *name, bool_t indent, bool_t writestat){
196 FILE *fbuf;
197 char const *heredb, *indb;
198 size_t linesize, heredl, indl, cnt, linelen;
199 char *linebuf;
200 si64_t lc, cc;
201 si32_t rv;
202 NYD_ENTER;
204 rv = n_ERR_NONE;
205 lc = cc = 0;
206 linebuf = NULL; /* TODO line pool */
207 linesize = 0;
208 heredb = NULL;
209 heredl = 0;
211 /* The -M case is special */
212 if(name == (char*)-1){
213 fbuf = n_stdin;
214 name = n_hy;
215 }else if(name[0] == '-' &&
216 (name[1] == '\0' || blankspacechar(name[1]))){
217 fbuf = n_stdin;
218 if(name[1] == '\0'){
219 if(!(n_psonce & n_PSO_INTERACTIVE)){
220 n_err(_("~< -: HERE-delimiter required in non-interactive mode\n"));
221 rv = n_ERR_INVAL;
222 goto jleave;
224 }else{
225 for(heredb = &name[2]; *heredb != '\0' && blankspacechar(*heredb);
226 ++heredb)
228 if((heredl = strlen(heredb)) == 0){
229 jdelim_empty:
230 n_err(_("~< - HERE-delimiter: delimiter must not be empty\n"));
231 rv = n_ERR_INVAL;
232 goto jleave;
235 if(*heredb == '\''){
236 for(indb = ++heredb; *indb != '\0' && *indb != '\''; ++indb)
238 if(*indb == '\0'){
239 n_err(_("~< - HERE-delimiter: missing trailing quote\n"));
240 rv = n_ERR_INVAL;
241 goto jleave;
242 }else if(indb[1] != '\0'){
243 n_err(_("~< - HERE-delimiter: trailing characters after "
244 "quote\n"));
245 rv = n_ERR_INVAL;
246 goto jleave;
248 if((heredl = PTR2SIZE(indb - heredb)) == 0)
249 goto jdelim_empty;
250 heredb = savestrbuf(heredb, heredl);
253 name = n_hy;
254 }else if((fbuf = Fopen(name, "r")) == NULL){
255 n_perr(name, rv = n_err_no);
256 goto jleave;
259 indl = indent ? strlen(indb = ok_vlook(indentprefix)) : 0;
261 if(fbuf != n_stdin)
262 cnt = fsize(fbuf);
263 while(fgetline(&linebuf, &linesize, (fbuf == n_stdin ? NULL : &cnt),
264 &linelen, fbuf, 0) != NULL){
265 if(heredl > 0 && heredl == linelen - 1 &&
266 !memcmp(heredb, linebuf, heredl)){
267 heredb = NULL;
268 break;
271 if(indl > 0){
272 if(fwrite(indb, sizeof *indb, indl, _coll_fp) != indl){
273 rv = n_err_no;
274 goto jleave;
276 cc += indl;
279 if(fwrite(linebuf, sizeof *linebuf, linelen, _coll_fp) != linelen){
280 rv = n_err_no;
281 goto jleave;
283 cc += linelen;
284 ++lc;
286 if(fflush(_coll_fp)){
287 rv = n_err_no;
288 goto jleave;
291 if(heredb != NULL)
292 rv = n_ERR_NOTOBACCO;
293 jleave:
294 if(linebuf != NULL)
295 n_free(linebuf);
296 if(fbuf != NULL){
297 if(fbuf != n_stdin)
298 Fclose(fbuf);
299 else if(heredl > 0)
300 clearerr(n_stdin);
303 if(writestat)
304 fprintf(n_stdout, "%s%s %" PRId64 "/%" PRId64 "\n",
305 n_shexp_quote_cp(name, FAL0), (rv ? " " n_ERROR : n_empty), lc, cc);
306 NYD_LEAVE;
307 return rv;
310 static si32_t
311 a_coll_insert_cmd(FILE *fp, char const *cmd){
312 FILE *ibuf;
313 si64_t lc, cc;
314 si32_t rv;
315 NYD_ENTER;
317 rv = n_ERR_NONE;
318 lc = cc = 0;
320 if((ibuf = Popen(cmd, "r", ok_vlook(SHELL), NULL, 0)) != NULL){
321 int c;
323 while((c = getc(ibuf)) != EOF){ /* XXX bytewise, yuck! */
324 if(putc(c, fp) == EOF){
325 rv = n_err_no;
326 break;
328 ++cc;
329 if(c == '\n')
330 ++lc;
332 if(!feof(ibuf) || ferror(ibuf)){
333 if(rv == n_ERR_NONE)
334 rv = n_ERR_IO;
336 if(!Pclose(ibuf, TRU1)){
337 if(rv == n_ERR_NONE)
338 rv = n_ERR_IO;
340 }else
341 n_perr(cmd, rv = n_err_no);
343 fprintf(n_stdout, "CMD%s %" PRId64 "/%" PRId64 "\n",
344 (rv == n_ERR_NONE ? n_empty : " " n_ERROR), lc, cc);
345 NYD_LEAVE;
346 return rv;
349 static void
350 print_collf(FILE *cf, struct header *hp)
352 char *lbuf;
353 FILE *obuf;
354 size_t cnt, linesize, linelen;
355 NYD_ENTER;
357 fflush_rewind(cf);
358 cnt = (size_t)fsize(cf);
360 if((obuf = Ftmp(NULL, "collfp", OF_RDWR | OF_UNLINK | OF_REGISTER)) == NULL){
361 n_perr(_("Can't create temporary file for `~p' command"), 0);
362 goto jleave;
365 hold_all_sigs();
367 fprintf(obuf, _("-------\nMessage contains:\n")); /* xxx SEARCH112 */
368 n_puthead(TRU1, hp, obuf,
369 (GIDENT | GTO | GSUBJECT | GCC | GBCC | GBCC_IS_FCC | GNL | GFILES |
370 GCOMMA), SEND_TODISP, CONV_NONE, NULL, NULL);
372 lbuf = NULL;
373 linesize = 0;
374 while(fgetline(&lbuf, &linesize, &cnt, &linelen, cf, 1))
375 prout(lbuf, linelen, obuf);
376 if(lbuf != NULL)
377 n_free(lbuf);
379 if(hp->h_attach != NULL){
380 fputs(_("-------\nAttachments:\n"), obuf);
381 n_attachment_list_print(hp->h_attach, obuf);
384 rele_all_sigs();
386 page_or_print(obuf, 0);
388 Fclose(obuf);
389 jleave:
390 NYD_LEAVE;
393 static si32_t
394 a_coll_write(char const *name, FILE *fp, int f)
396 FILE *of;
397 int c;
398 si64_t lc, cc;
399 si32_t rv;
400 NYD_ENTER;
402 rv = n_ERR_NONE;
404 if(f) {
405 fprintf(n_stdout, "%s ", n_shexp_quote_cp(name, FAL0));
406 fflush(n_stdout);
409 if ((of = Fopen(name, "a")) == NULL) {
410 n_perr(name, rv = n_err_no);
411 goto jerr;
414 lc = cc = 0;
415 while ((c = getc(fp)) != EOF) {
416 ++cc;
417 if (c == '\n')
418 ++lc;
419 if (putc(c, of) == EOF) {
420 n_perr(name, rv = n_err_no);
421 goto jerr;
424 fprintf(n_stdout, "%" PRId64 "/%" PRId64 "\n", lc, cc);
426 jleave:
427 if(of != NULL)
428 Fclose(of);
429 fflush(n_stdout);
430 NYD_LEAVE;
431 return rv;
432 jerr:
433 putc('-', n_stdout);
434 putc('\n', n_stdout);
435 goto jleave;
438 static bool_t
439 a_coll_message_inject_head(FILE *fp){
440 bool_t rv;
441 char const *cp, *cp_obsolete;
442 NYD2_ENTER;
444 cp_obsolete = ok_vlook(NAIL_HEAD);
445 if(cp_obsolete != NULL)
446 n_OBSOLETE(_("please use *message-inject-head*, not *NAIL_HEAD*"));
448 if(((cp = ok_vlook(message_inject_head)) != NULL ||
449 (cp = cp_obsolete) != NULL) && !a_coll_putesc(cp, TRU1, fp))
450 rv = FAL0;
451 else
452 rv = TRU1;
453 NYD2_LEAVE;
454 return rv;
457 static bool_t
458 a_coll_quote_message(FILE *fp, struct message *mp, bool_t isfwd){
459 struct a_coll_fmt_ctx cfc;
460 char const *cp;
461 struct n_ignore const *quoteitp;
462 enum sendaction action;
463 bool_t rv;
464 NYD_ENTER;
466 rv = FAL0;
468 if(isfwd || (cp = ok_vlook(quote)) != NULL){
469 quoteitp = n_IGNORE_ALL;
470 action = SEND_QUOTE;
472 if(isfwd){
473 char const *cp_v15compat;
475 if((cp_v15compat = ok_vlook(fwdheading)) != NULL)
476 n_OBSOLETE(_("please use *forward-inject-head* instead of "
477 "*fwdheading*"));
478 if((cp = ok_vlook(forward_inject_head)) == NULL &&
479 (cp = cp_v15compat) == NULL)
480 cp = n_FORWARD_INJECT_HEAD;
481 quoteitp = n_IGNORE_FWD;
482 }else{
483 if(!strcmp(cp, "noheading")){
484 cp = NULL;
485 }else if(!strcmp(cp, "headers")){
486 quoteitp = n_IGNORE_TYPE;
487 cp = NULL;
488 }else if(!strcmp(cp, "allheaders")){
489 quoteitp = NULL;
490 action = SEND_QUOTE_ALL;
491 cp = NULL;
492 }else if((cp = ok_vlook(quote_inject_head)) == NULL)
493 cp = n_QUOTE_INJECT_HEAD;
495 /* We we pass through our formatter? */
496 if((cfc.cfc_fmt = cp) != NULL){
497 /* TODO In v15 [-textual_-]sender_info() should only create a list
498 * TODO of matching header objects, and the formatter should simply
499 * TODO iterate over this list and call OBJ->to_ui_str(FLAGS) or so.
500 * TODO For now fully initialize this thing once (grrrr!!) */
501 cfc.cfc_fp = fp;
502 cfc.cfc_mp = mp;
503 n_header_textual_sender_info(cfc.cfc_mp = mp, &cfc.cfc_cumul,
504 &cfc.cfc_addr, &cfc.cfc_real, &cfc.cfc_full, NULL);
505 cfc.cfc_date = n_header_textual_date_info(mp, NULL);
506 /* C99 */{
507 struct name *np;
509 if((cp = hfield1("message-id", mp)) != NULL &&
510 (np = lextract(cp, GREF)) != NULL)
511 cfc.cfc_msgid = np->n_name;
512 else
513 cp = (char*)-1;
516 if(!a_coll__fmt_inj(&cfc) || fflush(fp))
517 goto jleave;
520 if(sendmp(mp, fp, quoteitp, (isfwd ? NULL : ok_vlook(indentprefix)),
521 action, NULL) < 0)
522 goto jleave;
524 if(isfwd){
525 if((cp = ok_vlook(forward_inject_tail)) == NULL)
526 cp = n_FORWARD_INJECT_TAIL;
527 }else if(cp != NULL && (cp = ok_vlook(quote_inject_tail)) == NULL)
528 cp = n_QUOTE_INJECT_TAIL;
529 if((cfc.cfc_fmt = cp) != NULL && (!a_coll__fmt_inj(&cfc) || fflush(fp)))
530 goto jleave;
533 rv = TRU1;
534 jleave:
535 NYD_LEAVE;
536 return rv;
539 static bool_t
540 a_coll__fmt_inj(struct a_coll_fmt_ctx const *cfcp){
541 struct quoteflt qf;
542 struct n_string s_b, *sp;
543 char c;
544 char const *fmt;
545 NYD_ENTER;
547 if((fmt = cfcp->cfc_fmt) == NULL || *fmt == '\0')
548 goto jleave;
550 sp = n_string_book(n_string_creat_auto(&s_b), 127);
552 while((c = *fmt++) != '\0'){
553 if(c != '%' || (c = *fmt++) == '%'){
554 jwrite_char:
555 sp = n_string_push_c(sp, c);
556 }else switch(c){
557 case 'a':
558 sp = n_string_push_cp(sp, cfcp->cfc_addr);
559 break;
560 case 'd':
561 sp = n_string_push_cp(sp, cfcp->cfc_date);
562 break;
563 case 'f':
564 sp = n_string_push_cp(sp, cfcp->cfc_full);
565 break;
566 case 'i':
567 if(cfcp->cfc_msgid != NULL)
568 sp = n_string_push_cp(sp, cfcp->cfc_msgid);
569 break;
570 case 'n':
571 sp = n_string_push_cp(sp, cfcp->cfc_cumul);
572 break;
573 case 'r':
574 sp = n_string_push_cp(sp, cfcp->cfc_real);
575 break;
576 case '\0':
577 --fmt;
578 c = '%';
579 goto jwrite_char;
580 default:
581 n_err(_("*{forward,quote}-inject-{head,tail}*: "
582 "unknown format: %c (in: %s)\n"),
583 c, n_shexp_quote_cp(cfcp->cfc_fmt, FAL0));
584 goto jwrite_char;
588 quoteflt_init(&qf, NULL, FAL0);
589 quoteflt_reset(&qf, cfcp->cfc_fp);
590 if(quoteflt_push(&qf, sp->s_dat, sp->s_len) < 0 || quoteflt_flush(&qf) < 0)
591 cfcp = NULL;
592 quoteflt_destroy(&qf);
594 /*n_string_gut(sp);*/
595 jleave:
596 NYD_LEAVE;
597 return (cfcp != NULL);
600 static bool_t
601 a_coll_makeheader(FILE *fp, struct header *hp, si8_t *checkaddr_err,
602 bool_t do_delayed_due_t)
604 FILE *nf;
605 int c;
606 bool_t rv;
607 NYD_ENTER;
609 rv = FAL0;
611 if ((nf = Ftmp(NULL, "colhead", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL) {
612 n_perr(_("temporary mail edit file"), 0);
613 goto jleave;
616 n_header_extract(fp, hp, (do_delayed_due_t ? TRU1 : TRUM1), checkaddr_err);
617 if (checkaddr_err != NULL && *checkaddr_err != 0)
618 goto jleave;
620 /* In template mode some things have been delayed until the template has
621 * been read */
622 if(do_delayed_due_t){
623 char const *cp;
625 if((cp = ok_vlook(on_compose_enter)) != NULL){
626 setup_from_and_sender(hp);
627 temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset,
628 hp);
631 if(!a_coll_message_inject_head(nf))
632 goto jleave;
635 while ((c = getc(fp)) != EOF) /* XXX bytewise, yuck! */
636 putc(c, nf);
638 if (fp != _coll_fp)
639 Fclose(_coll_fp);
640 Fclose(fp);
641 _coll_fp = nf;
642 nf = NULL;
644 if (check_from_and_sender(hp->h_from, hp->h_sender) == NULL)
645 goto jleave;
646 rv = TRU1;
647 jleave:
648 if(nf != NULL)
649 Fclose(nf);
650 NYD_LEAVE;
651 return rv;
654 static si32_t
655 a_coll_edit(int c, struct header *hp, char const *pipecmd) /* TODO errret */
657 struct n_sigman sm;
658 FILE *nf;
659 sighandler_type volatile sigint;
660 struct name *saved_in_reply_to;
661 bool_t saved_filrec;
662 si32_t volatile rv;
663 NYD_ENTER;
665 rv = n_ERR_NONE;
666 n_UNINIT(sigint, SIG_ERR);
667 saved_filrec = ok_blook(add_file_recipients);
669 n_SIGMAN_ENTER_SWITCH(&sm, n_SIGMAN_ALL){
670 case 0:
671 sigint = safe_signal(SIGINT, SIG_IGN);
672 break;
673 default:
674 rv = n_ERR_INTR;
675 goto jleave;
678 if(!saved_filrec)
679 ok_bset(add_file_recipients);
681 saved_in_reply_to = NULL;
682 if(hp != NULL){
683 struct name *np;
685 if((np = hp->h_in_reply_to) == NULL)
686 hp->h_in_reply_to = np = n_header_setup_in_reply_to(hp);
687 if(np != NULL)
688 saved_in_reply_to = ndup(np, np->n_type);
691 rewind(_coll_fp);
692 nf = n_run_editor(_coll_fp, (off_t)-1, c, FAL0, hp, NULL, SEND_MBOX, sigint,
693 pipecmd);
694 if(nf != NULL){
695 if(hp != NULL){
696 if(!a_coll_makeheader(nf, hp, NULL, FAL0))
697 rv = n_ERR_INVAL;
698 /* Break the thread if In-Reply-To: has been modified */
699 if(hp->h_in_reply_to == NULL || (saved_in_reply_to != NULL &&
700 asccasecmp(hp->h_in_reply_to->n_fullname,
701 saved_in_reply_to->n_fullname)))
702 hp->h_ref = NULL;
703 }else{
704 fseek(nf, 0L, SEEK_END);
705 Fclose(_coll_fp);
706 _coll_fp = nf;
708 }else
709 rv = n_ERR_CHILD;
711 n_sigman_cleanup_ping(&sm);
712 jleave:
713 if(!saved_filrec)
714 ok_bclear(add_file_recipients);
715 safe_signal(SIGINT, sigint);
716 NYD_LEAVE;
717 n_sigman_leave(&sm, n_SIGMAN_VIPSIGS_NTTYOUT);
718 return rv;
721 static si32_t
722 a_coll_pipe(char const *cmd)
724 int ws;
725 FILE *nf;
726 sighandler_type sigint;
727 si32_t rv;
728 NYD_ENTER;
730 rv = n_ERR_NONE;
731 sigint = safe_signal(SIGINT, SIG_IGN);
733 if ((nf = Ftmp(NULL, "colpipe", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==NULL) {
734 jperr:
735 n_perr(_("temporary mail edit file"), rv = n_err_no);
736 goto jout;
739 /* stdin = current message. stdout = new message */
740 if(fflush(_coll_fp) == EOF)
741 goto jperr;
742 rewind(_coll_fp);
743 if (n_child_run(ok_vlook(SHELL), 0, fileno(_coll_fp), fileno(nf), "-c",
744 cmd, NULL, NULL, &ws) < 0 || WEXITSTATUS(ws) != 0) {
745 Fclose(nf);
746 rv = n_ERR_CHILD;
747 goto jout;
750 if (fsize(nf) == 0) {
751 n_err(_("No bytes from %s !?\n"), n_shexp_quote_cp(cmd, FAL0));
752 Fclose(nf);
753 rv = n_ERR_NODATA;
754 goto jout;
757 /* Take new files */
758 fseek(nf, 0L, SEEK_END);
759 Fclose(_coll_fp);
760 _coll_fp = nf;
761 jout:
762 safe_signal(SIGINT, sigint);
763 NYD_LEAVE;
764 return rv;
767 static si32_t
768 a_coll_forward(char const *ms, FILE *fp, int f)
770 int *msgvec, rv = 0;
771 struct n_ignore const *itp;
772 char const *tabst;
773 enum sendaction action;
774 NYD_ENTER;
776 if ((rv = n_getmsglist(ms, n_msgvec, 0, NULL)) < 0) {
777 rv = n_ERR_NOENT; /* XXX not really, should be handled there! */
778 goto jleave;
780 if (rv == 0) {
781 *n_msgvec = first(0, MMNORM);
782 if (*n_msgvec == 0) {
783 n_err(_("No appropriate messages\n"));
784 rv = n_ERR_NOENT;
785 goto jleave;
787 rv = 1;
789 msgvec = n_autorec_calloc(rv +1, sizeof *msgvec);
790 while(rv-- > 0)
791 msgvec[rv] = n_msgvec[rv];
792 rv = 0;
794 if (f == 'f' || f == 'F' || f == 'u')
795 tabst = NULL;
796 else
797 tabst = ok_vlook(indentprefix);
798 if (f == 'u' || f == 'U')
799 itp = n_IGNORE_ALL;
800 else
801 itp = upperchar(f) ? NULL : n_IGNORE_TYPE;
802 action = (upperchar(f) && f != 'U') ? SEND_QUOTE_ALL : SEND_QUOTE;
804 fprintf(n_stdout, A_("Interpolating:"));
805 srelax_hold();
806 for(; *msgvec != 0; ++msgvec){
807 struct message *mp;
809 mp = &message[*msgvec - 1];
810 touch(mp);
812 fprintf(n_stdout, " %d", *msgvec);
813 fflush(n_stdout);
814 if(f == 'Q'){
815 if(!a_coll_quote_message(fp, mp, FAL0)){
816 rv = n_ERR_IO;
817 break;
819 }else if(sendmp(mp, fp, itp, tabst, action, NULL) < 0){
820 n_perr(_("forward: temporary mail file"), 0);
821 rv = n_ERR_IO;
822 break;
824 srelax();
826 srelax_rele();
827 fprintf(n_stdout, "\n");
828 jleave:
829 NYD_LEAVE;
830 return rv;
833 static bool_t
834 a_collect_plumbing(char const *ms, struct header *hp){
835 /* TODO _collect_plumbing: instead of fields the basic headers should
836 * TODO be in an array and have IDs, like in termcap etc., so then this
837 * TODO could be simplified as table-walks. Also true for arg-checks! */
838 bool_t rv;
839 char const *cp, *cmd[4];
840 NYD2_ENTER;
842 /* Protcol version for *on-compose-splice** -- update manual on change! */
843 #define a_COLL_PLUMBING_VERSION "0 0 1"
844 cp = ms;
846 /* C99 */{
847 size_t i;
849 for(i = 0; i < n_NELEM(cmd); ++i){ /* TODO trim+strlist_split(_ifs?)() */
850 while(blankchar(*cp))
851 ++cp;
852 if(*cp == '\0')
853 cmd[i] = NULL;
854 else{
855 if(i < n_NELEM(cmd) - 1)
856 for(cmd[i] = cp++; *cp != '\0' && !blankchar(*cp); ++cp)
858 else{
859 /* Last slot takes all the rest of the line, less trailing WS */
860 for(cmd[i] = cp++; *cp != '\0'; ++cp)
862 while(blankchar(cp[-1]))
863 --cp;
865 cmd[i] = savestrbuf(cmd[i], PTR2SIZE(cp - cmd[i]));
870 if(n_UNLIKELY(cmd[0] == NULL))
871 goto jecmd;
872 if(is_asccaseprefix(cmd[0], "header"))
873 rv = a_collect__plumb_header(cp, hp, cmd);
874 else if(is_asccaseprefix(cmd[0], "attachment"))
875 rv = a_collect__plumb_attach(cp, hp, cmd);
876 else{
877 jecmd:
878 fputs("500\n", n_stdout);
879 rv = FAL0;
881 fflush(n_stdout);
883 NYD2_LEAVE;
884 return rv;
887 static bool_t
888 a_collect__plumb_header(char const *cp, struct header *hp,
889 char const *cmd[4]){
890 uiz_t i;
891 struct n_header_field *hfp;
892 struct name *np, **npp;
893 NYD2_ENTER;
895 if(cmd[1] == NULL)
896 goto jdefault;
898 if(is_asccaseprefix(cmd[1], "insert")){ /* TODO LOGIC BELONGS head.c
899 * TODO That is: Header::factory(string) -> object (blahblah).
900 * TODO I.e., as long as we don't have regular RFC compliant parsers
901 * TODO which differentiate in between structured and unstructured
902 * TODO header fields etc., a little workaround */
903 struct name *xnp;
904 si8_t aerr;
905 enum expand_addr_check_mode eacm;
906 enum gfield ntype;
907 bool_t mult_ok;
909 if(cmd[2] == NULL || cmd[3] == NULL)
910 goto jecmd;
912 /* Strip [\r\n] which would render a body invalid XXX all controls? */
913 /* C99 */{
914 char *xp, c;
916 cmd[3] = xp = savestr(cmd[3]);
917 for(; (c = *xp) != '\0'; ++xp)
918 if(c == '\n' || c == '\r')
919 *xp = ' ';
922 if(!asccasecmp(cmd[2], cp = "Subject")){
923 if(cmd[3][0] != '\0'){
924 if(hp->h_subject != NULL)
925 hp->h_subject = savecatsep(hp->h_subject, ' ', cmd[3]);
926 else
927 hp->h_subject = n_UNCONST(cmd[3]);
928 fprintf(n_stdout, "210 %s 1\n", cp);
929 goto jleave;
930 }else
931 goto j501cp;
934 mult_ok = TRU1;
935 ntype = GEXTRA | GFULL | GFULLEXTRA;
936 eacm = EACM_STRICT;
938 if(!asccasecmp(cmd[2], cp = "From")){
939 npp = &hp->h_from;
940 jins:
941 aerr = 0;
942 /* todo As said above, this should be table driven etc., but.. */
943 if(ntype & GBCC_IS_FCC){
944 np = nalloc_fcc(cmd[3]);
945 if(is_addr_invalid(np, eacm))
946 goto jins_505;
947 }else{
948 if((np = lextract(cmd[3], ntype)) == NULL)
949 goto j501cp;
951 if((np = checkaddrs(np, eacm, &aerr), aerr != 0)){
952 jins_505:
953 fprintf(n_stdout, "505 %s\n", cp);
954 goto jleave;
958 /* Go to the end of the list, track whether it contains any
959 * non-deleted entries */
960 i = 0;
961 if((xnp = *npp) != NULL)
962 for(;; xnp = xnp->n_flink){
963 if(!(xnp->n_type & GDEL))
964 ++i;
965 if(xnp->n_flink == NULL)
966 break;
969 if(!mult_ok && (i != 0 || np->n_flink != NULL))
970 fprintf(n_stdout, "506 %s\n", cp);
971 else{
972 if(xnp == NULL)
973 *npp = np;
974 else
975 xnp->n_flink = np;
976 np->n_blink = xnp;
977 fprintf(n_stdout, "210 %s %" PRIuZ "\n", cp, ++i);
979 goto jleave;
981 if(!asccasecmp(cmd[2], cp = "Sender")){
982 mult_ok = FAL0;
983 npp = &hp->h_sender;
984 goto jins;
986 if(!asccasecmp(cmd[2], cp = "To")){
987 npp = &hp->h_to;
988 ntype = GTO | GFULL;
989 eacm = EACM_NORMAL | EAF_NAME;
990 goto jins;
992 if(!asccasecmp(cmd[2], cp = "Cc")){
993 npp = &hp->h_cc;
994 ntype = GCC | GFULL;
995 eacm = EACM_NORMAL | EAF_NAME;
996 goto jins;
998 if(!asccasecmp(cmd[2], cp = "Bcc")){
999 npp = &hp->h_bcc;
1000 ntype = GBCC | GFULL;
1001 eacm = EACM_NORMAL | EAF_NAME;
1002 goto jins;
1004 if(!asccasecmp(cmd[2], cp = "Fcc")){
1005 npp = &hp->h_fcc;
1006 ntype = GBCC | GBCC_IS_FCC;
1007 eacm = EACM_NORMAL /* Not | EAF_FILE, depend on *expandaddr*! */;
1008 goto jins;
1010 if(!asccasecmp(cmd[2], cp = "Reply-To")){
1011 npp = &hp->h_reply_to;
1012 eacm = EACM_NONAME;
1013 goto jins;
1015 if(!asccasecmp(cmd[2], cp = "Mail-Followup-To")){
1016 npp = &hp->h_mft;
1017 eacm = EACM_NONAME;
1018 goto jins;
1020 if(!asccasecmp(cmd[2], cp = "Message-ID")){
1021 mult_ok = FAL0;
1022 npp = &hp->h_message_id;
1023 ntype = GREF;
1024 eacm = EACM_NONAME;
1025 goto jins;
1027 if(!asccasecmp(cmd[2], cp = "References")){
1028 npp = &hp->h_ref;
1029 ntype = GREF;
1030 eacm = EACM_NONAME;
1031 goto jins;
1033 if(!asccasecmp(cmd[2], cp = "In-Reply-To")){
1034 npp = &hp->h_in_reply_to;
1035 ntype = GREF;
1036 eacm = EACM_NONAME;
1037 goto jins;
1040 if((cp = n_header_is_known(cmd[2], UIZ_MAX)) != NULL){
1041 fprintf(n_stdout, "505 %s\n", cp);
1042 goto jleave;
1045 /* Free-form header fields */
1046 /* C99 */{
1047 size_t nl, bl;
1048 struct n_header_field **hfpp;
1050 for(cp = cmd[2]; *cp != '\0'; ++cp)
1051 if(!fieldnamechar(*cp)){
1052 cp = cmd[2];
1053 goto j501cp;
1056 for(i = 0, hfpp = &hp->h_user_headers; *hfpp != NULL; ++i)
1057 hfpp = &(*hfpp)->hf_next;
1059 nl = strlen(cp = cmd[2]) +1;
1060 bl = strlen(cmd[3]) +1;
1061 *hfpp = hfp = n_autorec_alloc(n_VSTRUCT_SIZEOF(struct n_header_field,
1062 hf_dat) + nl + bl);
1063 hfp->hf_next = NULL;
1064 hfp->hf_nl = nl - 1;
1065 hfp->hf_bl = bl - 1;
1066 memcpy(&hfp->hf_dat[0], cp, nl);
1067 memcpy(&hfp->hf_dat[nl], cmd[3], bl);
1068 fprintf(n_stdout, "210 %s %" PRIuZ "\n", &hfp->hf_dat[0], ++i);
1070 goto jleave;
1073 if(is_asccaseprefix(cmd[1], "list")){
1074 jdefault:
1075 if(cmd[2] == NULL){
1076 fputs("210", n_stdout);
1077 if(hp->h_subject != NULL) fputs(" Subject", n_stdout);
1078 if(hp->h_from != NULL) fputs(" From", n_stdout);
1079 if(hp->h_sender != NULL) fputs(" Sender", n_stdout);
1080 if(hp->h_to != NULL) fputs(" To", n_stdout);
1081 if(hp->h_cc != NULL) fputs(" Cc", n_stdout);
1082 if(hp->h_bcc != NULL) fputs(" Bcc", n_stdout);
1083 if(hp->h_fcc != NULL) fputs(" Fcc", n_stdout);
1084 if(hp->h_reply_to != NULL) fputs(" Reply-To", n_stdout);
1085 if(hp->h_mft != NULL) fputs(" Mail-Followup-To", n_stdout);
1086 if(hp->h_message_id != NULL) fputs(" Message-ID", n_stdout);
1087 if(hp->h_ref != NULL) fputs(" References", n_stdout);
1088 if(hp->h_in_reply_to != NULL) fputs(" In-Reply-To", n_stdout);
1089 if(hp->h_mailx_command != NULL) fputs(" Mailx-Command", n_stdout);
1090 if(hp->h_mailx_raw_to != NULL) fputs(" Mailx-Raw-To", n_stdout);
1091 if(hp->h_mailx_raw_cc != NULL) fputs(" Mailx-Raw-Cc", n_stdout);
1092 if(hp->h_mailx_raw_bcc != NULL) fputs(" Mailx-Raw-Bcc", n_stdout);
1093 if(hp->h_mailx_orig_from != NULL) fputs(" Mailx-Orig-From", n_stdout);
1094 if(hp->h_mailx_orig_to != NULL) fputs(" Mailx-Orig-To", n_stdout);
1095 if(hp->h_mailx_orig_cc != NULL) fputs(" Mailx-Orig-Cc", n_stdout);
1096 if(hp->h_mailx_orig_bcc != NULL) fputs(" Mailx-Orig-Bcc", n_stdout);
1098 /* Print only one instance of each free-form header */
1099 for(hfp = hp->h_user_headers; hfp != NULL; hfp = hfp->hf_next){
1100 struct n_header_field *hfpx;
1102 for(hfpx = hp->h_user_headers;; hfpx = hfpx->hf_next)
1103 if(hfpx == hfp){
1104 putc(' ', n_stdout);
1105 fputs(&hfp->hf_dat[0], n_stdout);
1106 break;
1107 }else if(!asccasecmp(&hfpx->hf_dat[0], &hfp->hf_dat[0]))
1108 break;
1110 putc('\n', n_stdout);
1111 goto jleave;
1114 if(cmd[3] != NULL)
1115 goto jecmd;
1117 if(!asccasecmp(cmd[2], cp = "Subject")){
1118 np = (hp->h_subject != NULL) ? (struct name*)-1 : NULL;
1119 goto jlist;
1121 if(!asccasecmp(cmd[2], cp = "From")){
1122 np = hp->h_from;
1123 jlist:
1124 fprintf(n_stdout, "%s %s\n", (np == NULL ? "501" : "210"), cp);
1125 goto jleave;
1127 if(!asccasecmp(cmd[2], cp = "Sender")){
1128 np = hp->h_sender;
1129 goto jlist;
1131 if(!asccasecmp(cmd[2], cp = "To")){
1132 np = hp->h_to;
1133 goto jlist;
1135 if(!asccasecmp(cmd[2], cp = "Cc")){
1136 np = hp->h_cc;
1137 goto jlist;
1139 if(!asccasecmp(cmd[2], cp = "Bcc")){
1140 np = hp->h_bcc;
1141 goto jlist;
1143 if(!asccasecmp(cmd[2], cp = "Fcc")){
1144 np = hp->h_fcc;
1145 goto jlist;
1147 if(!asccasecmp(cmd[2], cp = "Reply-To")){
1148 np = hp->h_reply_to;
1149 goto jlist;
1151 if(!asccasecmp(cmd[2], cp = "Mail-Followup-To")){
1152 np = hp->h_mft;
1153 goto jlist;
1155 if(!asccasecmp(cmd[2], cp = "Message-ID")){
1156 np = hp->h_message_id;
1157 goto jlist;
1159 if(!asccasecmp(cmd[2], cp = "References")){
1160 np = hp->h_ref;
1161 goto jlist;
1163 if(!asccasecmp(cmd[2], cp = "In-Reply-To")){
1164 np = hp->h_in_reply_to;
1165 goto jlist;
1168 if(!asccasecmp(cmd[2], cp = "Mailx-Command")){
1169 np = (hp->h_mailx_command != NULL) ? (struct name*)-1 : NULL;
1170 goto jlist;
1172 if(!asccasecmp(cmd[2], cp = "Mailx-Raw-To")){
1173 np = hp->h_mailx_raw_to;
1174 goto jlist;
1176 if(!asccasecmp(cmd[2], cp = "Mailx-Raw-Cc")){
1177 np = hp->h_mailx_raw_cc;
1178 goto jlist;
1180 if(!asccasecmp(cmd[2], cp = "Mailx-Raw-Bcc")){
1181 np = hp->h_mailx_raw_bcc;
1182 goto jlist;
1184 if(!asccasecmp(cmd[2], cp = "Mailx-Orig-From")){
1185 np = hp->h_mailx_orig_from;
1186 goto jlist;
1188 if(!asccasecmp(cmd[2], cp = "Mailx-Orig-To")){
1189 np = hp->h_mailx_orig_to;
1190 goto jlist;
1192 if(!asccasecmp(cmd[2], cp = "Mailx-Orig-Cc")){
1193 np = hp->h_mailx_orig_cc;
1194 goto jlist;
1196 if(!asccasecmp(cmd[2], cp = "Mailx-Orig-Bcc")){
1197 np = hp->h_mailx_orig_bcc;
1198 goto jlist;
1201 /* Free-form header fields */
1202 for(cp = cmd[2]; *cp != '\0'; ++cp)
1203 if(!fieldnamechar(*cp)){
1204 cp = cmd[2];
1205 goto j501cp;
1207 cp = cmd[2];
1208 for(hfp = hp->h_user_headers;; hfp = hfp->hf_next){
1209 if(hfp == NULL)
1210 goto j501cp;
1211 else if(!asccasecmp(cp, &hfp->hf_dat[0])){
1212 fprintf(n_stdout, "210 %s\n", &hfp->hf_dat[0]);
1213 break;
1216 goto jleave;
1219 if(is_asccaseprefix(cmd[1], "remove")){
1220 if(cmd[2] == NULL || cmd[3] != NULL)
1221 goto jecmd;
1223 if(!asccasecmp(cmd[2], cp = "Subject")){
1224 if(hp->h_subject != NULL){
1225 hp->h_subject = NULL;
1226 fprintf(n_stdout, "210 %s\n", cp);
1227 goto jleave;
1228 }else
1229 goto j501cp;
1232 if(!asccasecmp(cmd[2], cp = "From")){
1233 npp = &hp->h_from;
1234 jrem:
1235 if(*npp != NULL){
1236 *npp = NULL;
1237 fprintf(n_stdout, "210 %s\n", cp);
1238 goto jleave;
1239 }else
1240 goto j501cp;
1242 if(!asccasecmp(cmd[2], cp = "Sender")){
1243 npp = &hp->h_sender;
1244 goto jrem;
1246 if(!asccasecmp(cmd[2], cp = "To")){
1247 npp = &hp->h_to;
1248 goto jrem;
1250 if(!asccasecmp(cmd[2], cp = "Cc")){
1251 npp = &hp->h_cc;
1252 goto jrem;
1254 if(!asccasecmp(cmd[2], cp = "Bcc")){
1255 npp = &hp->h_bcc;
1256 goto jrem;
1258 if(!asccasecmp(cmd[2], cp = "Fcc")){
1259 npp = &hp->h_fcc;
1260 goto jrem;
1262 if(!asccasecmp(cmd[2], cp = "Reply-To")){
1263 npp = &hp->h_reply_to;
1264 goto jrem;
1266 if(!asccasecmp(cmd[2], cp = "Mail-Followup-To")){
1267 npp = &hp->h_mft;
1268 goto jrem;
1270 if(!asccasecmp(cmd[2], cp = "Message-ID")){
1271 npp = &hp->h_message_id;
1272 goto jrem;
1274 if(!asccasecmp(cmd[2], cp = "References")){
1275 npp = &hp->h_ref;
1276 goto jrem;
1278 if(!asccasecmp(cmd[2], cp = "In-Reply-To")){
1279 npp = &hp->h_in_reply_to;
1280 goto jrem;
1283 if((cp = n_header_is_known(cmd[2], UIZ_MAX)) != NULL){
1284 fprintf(n_stdout, "505 %s\n", cp);
1285 goto jleave;
1288 /* Free-form header fields (note j501cp may print non-normalized name) */
1289 /* C99 */{
1290 struct n_header_field **hfpp;
1291 bool_t any;
1293 for(cp = cmd[2]; *cp != '\0'; ++cp)
1294 if(!fieldnamechar(*cp)){
1295 cp = cmd[2];
1296 goto j501cp;
1298 cp = cmd[2];
1300 for(any = FAL0, hfpp = &hp->h_user_headers; (hfp = *hfpp) != NULL;){
1301 if(!asccasecmp(cp, &hfp->hf_dat[0])){
1302 *hfpp = hfp->hf_next;
1303 if(!any)
1304 fprintf(n_stdout, "210 %s\n", &hfp->hf_dat[0]);
1305 any = TRU1;
1306 }else
1307 hfpp = &hfp->hf_next;
1309 if(!any)
1310 goto j501cp;
1312 goto jleave;
1315 if(is_asccaseprefix(cmd[1], "remove-at")){
1316 if(cmd[2] == NULL || cmd[3] == NULL)
1317 goto jecmd;
1319 if((n_idec_uiz_cp(&i, cmd[3], 0, NULL
1320 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
1321 ) != n_IDEC_STATE_CONSUMED || i == 0){
1322 fputs("505\n", n_stdout);
1323 goto jleave;
1326 if(!asccasecmp(cmd[2], cp = "Subject")){
1327 if(hp->h_subject != NULL && i == 1){
1328 hp->h_subject = NULL;
1329 fprintf(n_stdout, "210 %s 1\n", cp);
1330 goto jleave;
1331 }else
1332 goto j501cp;
1335 if(!asccasecmp(cmd[2], cp = "From")){
1336 npp = &hp->h_from;
1337 jremat:
1338 if((np = *npp) == NULL)
1339 goto j501cp;
1340 while(--i != 0 && np != NULL)
1341 np = np->n_flink;
1342 if(np == NULL)
1343 goto j501cp;
1345 if(np->n_blink != NULL)
1346 np->n_blink->n_flink = np->n_flink;
1347 else
1348 *npp = np->n_flink;
1349 if(np->n_flink != NULL)
1350 np->n_flink->n_blink = np->n_blink;
1352 fprintf(n_stdout, "210 %s\n", cp);
1353 goto jleave;
1355 if(!asccasecmp(cmd[2], cp = "Sender")){
1356 npp = &hp->h_sender;
1357 goto jremat;
1359 if(!asccasecmp(cmd[2], cp = "To")){
1360 npp = &hp->h_to;
1361 goto jremat;
1363 if(!asccasecmp(cmd[2], cp = "Cc")){
1364 npp = &hp->h_cc;
1365 goto jremat;
1367 if(!asccasecmp(cmd[2], cp = "Bcc")){
1368 npp = &hp->h_bcc;
1369 goto jremat;
1371 if(!asccasecmp(cmd[2], cp = "Fcc")){
1372 npp = &hp->h_fcc;
1373 goto jremat;
1375 if(!asccasecmp(cmd[2], cp = "Reply-To")){
1376 npp = &hp->h_reply_to;
1377 goto jremat;
1379 if(!asccasecmp(cmd[2], cp = "Mail-Followup-To")){
1380 npp = &hp->h_mft;
1381 goto jremat;
1383 if(!asccasecmp(cmd[2], cp = "Message-ID")){
1384 npp = &hp->h_message_id;
1385 goto jremat;
1387 if(!asccasecmp(cmd[2], cp = "References")){
1388 npp = &hp->h_ref;
1389 goto jremat;
1391 if(!asccasecmp(cmd[2], cp = "In-Reply-To")){
1392 npp = &hp->h_in_reply_to;
1393 goto jremat;
1396 if((cp = n_header_is_known(cmd[2], UIZ_MAX)) != NULL){
1397 fprintf(n_stdout, "505 %s\n", cp);
1398 goto jleave;
1401 /* Free-form header fields */
1402 /* C99 */{
1403 struct n_header_field **hfpp;
1405 for(cp = cmd[2]; *cp != '\0'; ++cp)
1406 if(!fieldnamechar(*cp)){
1407 cp = cmd[2];
1408 goto j501cp;
1410 cp = cmd[2];
1412 for(hfpp = &hp->h_user_headers; (hfp = *hfpp) != NULL;){
1413 if(--i == 0){
1414 *hfpp = hfp->hf_next;
1415 fprintf(n_stdout, "210 %s %" PRIuZ "\n", &hfp->hf_dat[0], i);
1416 break;
1417 }else
1418 hfpp = &hfp->hf_next;
1420 if(hfp == NULL)
1421 goto j501cp;
1423 goto jleave;
1426 if(is_asccaseprefix(cmd[1], "show")){
1427 if(cmd[2] == NULL || cmd[3] != NULL)
1428 goto jecmd;
1430 if(!asccasecmp(cmd[2], cp = "Subject")){
1431 if(hp->h_subject == NULL)
1432 goto j501cp;
1433 fprintf(n_stdout, "212 %s\n%s\n\n", cp, hp->h_subject);
1434 goto jleave;
1437 if(!asccasecmp(cmd[2], cp = "From")){
1438 np = hp->h_from;
1439 jshow:
1440 if(np != NULL){
1441 fprintf(n_stdout, "211 %s\n", cp);
1442 do if(!(np->n_type & GDEL)){
1443 switch(np->n_flags & NAME_ADDRSPEC_ISMASK){
1444 case NAME_ADDRSPEC_ISFILE: cp = n_hy; break;
1445 case NAME_ADDRSPEC_ISPIPE: cp = "|"; break;
1446 case NAME_ADDRSPEC_ISNAME: cp = n_ns; break;
1447 default: cp = np->n_name; break;
1449 fprintf(n_stdout, "%s %s\n", cp, np->n_fullname);
1450 }while((np = np->n_flink) != NULL);
1451 putc('\n', n_stdout);
1452 goto jleave;
1453 }else
1454 goto j501cp;
1456 if(!asccasecmp(cmd[2], cp = "Sender")){
1457 np = hp->h_sender;
1458 goto jshow;
1460 if(!asccasecmp(cmd[2], cp = "To")){
1461 np = hp->h_to;
1462 goto jshow;
1464 if(!asccasecmp(cmd[2], cp = "Cc")){
1465 np = hp->h_cc;
1466 goto jshow;
1468 if(!asccasecmp(cmd[2], cp = "Bcc")){
1469 np = hp->h_bcc;
1470 goto jshow;
1472 if(!asccasecmp(cmd[2], cp = "Fcc")){
1473 np = hp->h_fcc;
1474 goto jshow;
1476 if(!asccasecmp(cmd[2], cp = "Reply-To")){
1477 np = hp->h_reply_to;
1478 goto jshow;
1480 if(!asccasecmp(cmd[2], cp = "Mail-Followup-To")){
1481 np = hp->h_mft;
1482 goto jshow;
1484 if(!asccasecmp(cmd[2], cp = "Message-ID")){
1485 np = hp->h_message_id;
1486 goto jshow;
1488 if(!asccasecmp(cmd[2], cp = "References")){
1489 np = hp->h_ref;
1490 goto jshow;
1492 if(!asccasecmp(cmd[2], cp = "In-Reply-To")){
1493 np = hp->h_in_reply_to;
1494 goto jshow;
1497 if(!asccasecmp(cmd[2], cp = "Mailx-Command")){
1498 if(hp->h_mailx_command == NULL)
1499 goto j501cp;
1500 fprintf(n_stdout, "212 %s\n%s\n\n", cp, hp->h_mailx_command);
1501 goto jleave;
1503 if(!asccasecmp(cmd[2], cp = "Mailx-Raw-To")){
1504 np = hp->h_mailx_raw_to;
1505 goto jshow;
1507 if(!asccasecmp(cmd[2], cp = "Mailx-Raw-Cc")){
1508 np = hp->h_mailx_raw_cc;
1509 goto jshow;
1511 if(!asccasecmp(cmd[2], cp = "Mailx-Raw-Bcc")){
1512 np = hp->h_mailx_raw_bcc;
1513 goto jshow;
1515 if(!asccasecmp(cmd[2], cp = "Mailx-Orig-From")){
1516 np = hp->h_mailx_orig_from;
1517 goto jshow;
1519 if(!asccasecmp(cmd[2], cp = "Mailx-Orig-To")){
1520 np = hp->h_mailx_orig_to;
1521 goto jshow;
1523 if(!asccasecmp(cmd[2], cp = "Mailx-Orig-Cc")){
1524 np = hp->h_mailx_orig_cc;
1525 goto jshow;
1527 if(!asccasecmp(cmd[2], cp = "Mailx-Orig-Bcc")){
1528 np = hp->h_mailx_orig_bcc;
1529 goto jshow;
1532 /* Free-form header fields */
1533 /* C99 */{
1534 bool_t any;
1536 for(cp = cmd[2]; *cp != '\0'; ++cp)
1537 if(!fieldnamechar(*cp)){
1538 cp = cmd[2];
1539 goto j501cp;
1541 cp = cmd[2];
1543 for(any = FAL0, hfp = hp->h_user_headers; hfp != NULL;
1544 hfp = hfp->hf_next){
1545 if(!asccasecmp(cp, &hfp->hf_dat[0])){
1546 if(!any)
1547 fprintf(n_stdout, "212 %s\n", &hfp->hf_dat[0]);
1548 any = TRU1;
1549 fprintf(n_stdout, "%s\n", &hfp->hf_dat[hfp->hf_nl +1]);
1552 if(any)
1553 putc('\n', n_stdout);
1554 else
1555 goto j501cp;
1557 goto jleave;
1560 jecmd:
1561 fputs("500\n", n_stdout);
1562 cp = NULL;
1563 jleave:
1564 NYD2_LEAVE;
1565 return (cp != NULL);
1567 j501cp:
1568 fputs("501 ", n_stdout);
1569 fputs(cp, n_stdout);
1570 putc('\n', n_stdout);
1571 goto jleave;
1574 static bool_t
1575 a_collect__plumb_attach(char const *cp, struct header *hp,
1576 char const *cmd[4]){
1577 bool_t status;
1578 struct attachment *ap;
1579 NYD2_ENTER;
1581 if(cmd[1] == NULL)
1582 goto jdefault;
1584 if(is_asccaseprefix(cmd[1], "attribute")){
1585 if(cmd[2] == NULL || cmd[3] != NULL)
1586 goto jecmd;
1588 if((ap = n_attachment_find(hp->h_attach, cmd[2], NULL)) != NULL){
1589 jatt_att:
1590 fprintf(n_stdout, "212 %s\n", cmd[2]);
1591 if(ap->a_msgno > 0)
1592 fprintf(n_stdout, "message-number %d\n\n", ap->a_msgno);
1593 else{
1594 fprintf(n_stdout,
1595 "creation-name %s\nopen-path %s\nfilename %s\n",
1596 ap->a_path_user, ap->a_path, ap->a_name);
1597 if(ap->a_content_description != NULL)
1598 fprintf(n_stdout, "content-description %s\n",
1599 ap->a_content_description);
1600 if(ap->a_content_id != NULL)
1601 fprintf(n_stdout, "content-id %s\n",
1602 ap->a_content_id->n_name);
1603 if(ap->a_content_type != NULL)
1604 fprintf(n_stdout, "content-type %s\n", ap->a_content_type);
1605 if(ap->a_content_disposition != NULL)
1606 fprintf(n_stdout, "content-disposition %s\n",
1607 ap->a_content_disposition);
1608 putc('\n', n_stdout);
1610 }else
1611 fputs("501\n", n_stdout);
1612 goto jleave;
1615 if(is_asccaseprefix(cmd[1], "attribute-at")){
1616 uiz_t i;
1618 if(cmd[2] == NULL || cmd[3] != NULL)
1619 goto jecmd;
1621 if((n_idec_uiz_cp(&i, cmd[2], 0, NULL
1622 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
1623 ) != n_IDEC_STATE_CONSUMED || i == 0)
1624 fputs("505\n", n_stdout);
1625 else{
1626 for(ap = hp->h_attach; ap != NULL && --i != 0; ap = ap->a_flink)
1628 if(ap != NULL)
1629 goto jatt_att;
1630 else
1631 fputs("501\n", n_stdout);
1633 goto jleave;
1636 if(is_asccaseprefix(cmd[1], "attribute-set")){
1637 if(cmd[2] == NULL || cmd[3] == NULL)
1638 goto jecmd;
1640 if((ap = n_attachment_find(hp->h_attach, cmd[2], NULL)) != NULL){
1641 jatt_attset:
1642 if(ap->a_msgno > 0)
1643 fputs("505\n", n_stdout);
1644 else{
1645 char c, *keyw;
1647 cp = cmd[3];
1648 while((c = *cp) != '\0' && !blankchar(c))
1649 ++cp;
1650 keyw = savestrbuf(cmd[3], PTR2SIZE(cp - cmd[3]));
1651 if(c != '\0'){
1652 for(; (c = *++cp) != '\0' && blankchar(c);)
1654 if(c != '\0'){
1655 char *xp;
1657 /* Strip [\r\n] which would render a parameter invalid XXX
1658 * XXX all controls? */
1659 cp = xp = savestr(cp);
1660 for(; (c = *xp) != '\0'; ++xp)
1661 if(c == '\n' || c == '\r')
1662 *xp = ' ';
1663 c = *cp;
1667 if(!asccasecmp(keyw, "filename"))
1668 ap->a_name = (c == '\0') ? ap->a_path_bname : cp;
1669 else if(!asccasecmp(keyw, "content-description"))
1670 ap->a_content_description = (c == '\0') ? NULL : cp;
1671 else if(!asccasecmp(keyw, "content-id")){
1672 ap->a_content_id = NULL;
1674 if(c != '\0'){
1675 struct name *np;
1677 np = checkaddrs(lextract(cp, GREF),
1678 /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG |
1679 EACM_NONAME, NULL);
1680 if(np != NULL && np->n_flink == NULL)
1681 ap->a_content_id = np;
1682 else
1683 cp = NULL;
1685 }else if(!asccasecmp(keyw, "content-type"))
1686 ap->a_content_type = (c == '\0') ? NULL : cp;
1687 else if(!asccasecmp(keyw, "content-disposition"))
1688 ap->a_content_disposition = (c == '\0') ? NULL : cp;
1689 else
1690 cp = NULL;
1692 if(cp != NULL){
1693 size_t i;
1695 for(i = 0; ap != NULL; ++i, ap = ap->a_blink)
1697 fprintf(n_stdout, "210 %" PRIuZ "\n", i);
1698 }else
1699 fputs("505\n", n_stdout);
1701 }else
1702 fputs("501\n", n_stdout);
1703 goto jleave;
1706 if(is_asccaseprefix(cmd[1], "attribute-set-at")){
1707 uiz_t i;
1709 if(cmd[2] == NULL || cmd[3] == NULL)
1710 goto jecmd;
1712 if((n_idec_uiz_cp(&i, cmd[2], 0, NULL
1713 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
1714 ) != n_IDEC_STATE_CONSUMED || i == 0)
1715 fputs("505\n", n_stdout);
1716 else{
1717 for(ap = hp->h_attach; ap != NULL && --i != 0; ap = ap->a_flink)
1719 if(ap != NULL)
1720 goto jatt_attset;
1721 else
1722 fputs("501\n", n_stdout);
1724 goto jleave;
1727 if(is_asccaseprefix(cmd[1], "insert")){
1728 enum n_attach_error aerr;
1730 if(cmd[2] == NULL || cmd[3] != NULL)
1731 goto jecmd;
1733 hp->h_attach = n_attachment_append(hp->h_attach, cmd[2], &aerr, &ap);
1734 switch(aerr){
1735 case n_ATTACH_ERR_FILE_OPEN: cp = "505\n"; goto jatt_ins;
1736 case n_ATTACH_ERR_ICONV_FAILED: cp = "506\n"; goto jatt_ins;
1737 case n_ATTACH_ERR_ICONV_NAVAIL:
1738 case n_ATTACH_ERR_OTHER:
1739 default:
1740 cp = "501\n";
1741 jatt_ins:
1742 fputs(cp, n_stdout);
1743 break;
1744 case n_ATTACH_ERR_NONE:{
1745 size_t i;
1747 for(i = 0; ap != NULL; ++i, ap = ap->a_blink)
1749 fprintf(n_stdout, "210 %" PRIuZ "\n", i);
1750 }break;
1752 goto jleave;
1755 if(is_asccaseprefix(cmd[1], "list")){
1756 jdefault:
1757 if(cmd[2] != NULL)
1758 goto jecmd;
1760 if((ap = hp->h_attach) != NULL){
1761 fputs("212\n", n_stdout);
1763 fprintf(n_stdout, "%s\n", ap->a_path_user);
1764 while((ap = ap->a_flink) != NULL);
1765 putc('\n', n_stdout);
1766 }else
1767 fputs("501\n", n_stdout);
1768 goto jleave;
1771 if(is_asccaseprefix(cmd[1], "remove")){
1772 if(cmd[2] == NULL || cmd[3] != NULL)
1773 goto jecmd;
1775 if((ap = n_attachment_find(hp->h_attach, cmd[2], &status)) != NULL){
1776 if(status == TRUM1)
1777 fputs("506\n", n_stdout);
1778 else{
1779 hp->h_attach = n_attachment_remove(hp->h_attach, ap);
1780 fprintf(n_stdout, "210 %s\n", cmd[2]);
1782 }else
1783 fputs("501\n", n_stdout);
1784 goto jleave;
1787 if(is_asccaseprefix(cmd[1], "remove-at")){
1788 uiz_t i;
1790 if(cmd[2] == NULL || cmd[3] != NULL)
1791 goto jecmd;
1793 if((n_idec_uiz_cp(&i, cmd[2], 0, NULL
1794 ) & (n_IDEC_STATE_EMASK | n_IDEC_STATE_CONSUMED)
1795 ) != n_IDEC_STATE_CONSUMED || i == 0)
1796 fputs("505\n", n_stdout);
1797 else{
1798 for(ap = hp->h_attach; ap != NULL && --i != 0; ap = ap->a_flink)
1800 if(ap != NULL){
1801 hp->h_attach = n_attachment_remove(hp->h_attach, ap);
1802 fprintf(n_stdout, "210 %s\n", cmd[2]);
1803 }else
1804 fputs("501\n", n_stdout);
1806 goto jleave;
1809 jecmd:
1810 fputs("500\n", n_stdout);
1811 cp = NULL;
1812 jleave:
1813 NYD2_LEAVE;
1814 return (cp != NULL);
1817 static void
1818 _collint(int s)
1820 NYD_X; /* Signal handler */
1822 /* the control flow is subtle, because we can be called from ~q */
1823 if (_coll_hadintr == 0) {
1824 if (ok_blook(ignore)) {
1825 fputs("@\n", n_stdout);
1826 fflush(n_stdout);
1827 clearerr(n_stdin);
1828 } else
1829 _coll_hadintr = 1;
1830 siglongjmp(_coll_jmp, 1);
1832 n_exit_status |= n_EXIT_SEND_ERROR;
1833 if (s != 0)
1834 savedeadletter(_coll_fp, TRU1);
1835 /* Aborting message, no need to fflush() .. */
1836 siglongjmp(_coll_abort, 1);
1839 static void
1840 collhup(int s)
1842 NYD_X; /* Signal handler */
1843 n_UNUSED(s);
1845 savedeadletter(_coll_fp, TRU1);
1846 /* Let's pretend nobody else wants to clean up, a true statement at
1847 * this time */
1848 exit(n_EXIT_ERR);
1851 static bool_t
1852 a_coll_putesc(char const *s, bool_t addnl, FILE *stream){
1853 char c1, c2;
1854 bool_t isposix;
1855 NYD2_ENTER;
1857 isposix = ok_blook(posix);
1859 while((c1 = *s++) != '\0'){
1860 if(c1 == '\\' && ((c2 = *s) == 't' || c2 == 'n')){
1861 if(!isposix){
1862 isposix = TRU1; /* TODO v15 OBSOLETE! */
1863 n_err(_("Compose mode warning: expanding \\t or \\n in variable "
1864 "without *posix*!"
1865 "\n Support remains only for ~A,~a,~I,~i in *posix* mode!\n"));
1867 ++s;
1868 c1 = (c2 == 't') ? '\t' : '\n';
1871 if(putc(c1, stream) == EOF)
1872 goto jleave;
1875 if(addnl && putc('\n', stream) == EOF)
1876 goto jleave;
1878 jleave:
1879 NYD2_LEAVE;
1880 return (c1 == '\0');
1883 static int
1884 a_coll_ocs__mac(void){
1885 /* Executes in a fork(2)ed child TODO if remains, global MASKs for those! */
1886 setvbuf(n_stdin, NULL, _IOLBF, 0);
1887 setvbuf(n_stdout, NULL, _IOLBF, 0);
1888 n_psonce &= ~(n_PSO_INTERACTIVE | n_PSO_TTYIN | n_PSO_TTYOUT);
1889 n_pstate |= n_PS_COMPOSE_FORKHOOK;
1890 n_readctl_overlay = NULL; /* TODO we need OnForkEvent! See c_readctl() */
1891 if(n_poption & n_PO_D_VV){
1892 char buf[128];
1894 snprintf(buf, sizeof buf, "[%d]%s", getpid(), ok_vlook(log_prefix));
1895 ok_vset(log_prefix, buf);
1897 /* TODO If that uses `!' it will effectively SIG_IGN SIGINT, ...and such */
1898 temporary_compose_mode_hook_call(a_coll_ocs__macname, NULL, NULL);
1899 return 0;
1902 static void
1903 a_coll_ocs__finalize(void *vp){
1904 /* Note we use this for destruction upon setup errors, thus */
1905 sighandler_type opipe;
1906 sighandler_type oint;
1907 struct a_coll_ocs_arg **coapp, *coap;
1908 NYD2_ENTER;
1910 temporary_compose_mode_hook_call((char*)-1, NULL, NULL);
1912 coap = *(coapp = vp);
1913 *coapp = (struct a_coll_ocs_arg*)-1;
1915 if(coap->coa_stdin != NULL)
1916 Fclose(coap->coa_stdin);
1917 else if(coap->coa_pipe[0] != -1)
1918 close(coap->coa_pipe[0]);
1920 if(coap->coa_stdout != NULL && !Pclose(coap->coa_stdout, TRU1))
1921 *coap->coa_senderr = 111;
1922 if(coap->coa_pipe[1] != -1)
1923 close(coap->coa_pipe[1]);
1925 opipe = coap->coa_opipe;
1926 oint = coap->coa_oint;
1928 n_lofi_free(coap);
1930 hold_all_sigs();
1931 safe_signal(SIGPIPE, opipe);
1932 safe_signal(SIGINT, oint);
1933 rele_all_sigs();
1934 NYD2_LEAVE;
1937 FL void
1938 n_temporary_compose_hook_varset(void *arg){ /* TODO v15: drop */
1939 struct header *hp;
1940 char const *val;
1941 NYD2_ENTER;
1943 hp = arg;
1945 if((val = hp->h_subject) == NULL)
1946 val = n_empty;
1947 ok_vset(mailx_subject, val);
1948 if((val = detract(hp->h_from, GNAMEONLY)) == NULL)
1949 val = n_empty;
1950 ok_vset(mailx_from, val);
1951 if((val = detract(hp->h_sender, GNAMEONLY)) == NULL)
1952 val = n_empty;
1953 ok_vset(mailx_sender, val);
1954 if((val = detract(hp->h_to, GNAMEONLY)) == NULL)
1955 val = n_empty;
1956 ok_vset(mailx_to, val);
1957 if((val = detract(hp->h_cc, GNAMEONLY)) == NULL)
1958 val = n_empty;
1959 ok_vset(mailx_cc, val);
1960 if((val = detract(hp->h_bcc, GNAMEONLY)) == NULL)
1961 val = n_empty;
1962 ok_vset(mailx_bcc, val);
1964 if((val = hp->h_mailx_command) == NULL)
1965 val = n_empty;
1966 ok_vset(mailx_command, val);
1968 if((val = detract(hp->h_mailx_raw_to, GNAMEONLY)) == NULL)
1969 val = n_empty;
1970 ok_vset(mailx_raw_to, val);
1971 if((val = detract(hp->h_mailx_raw_cc, GNAMEONLY)) == NULL)
1972 val = n_empty;
1973 ok_vset(mailx_raw_cc, val);
1974 if((val = detract(hp->h_mailx_raw_bcc, GNAMEONLY)) == NULL)
1975 val = n_empty;
1976 ok_vset(mailx_raw_bcc, val);
1978 if((val = detract(hp->h_mailx_orig_from, GNAMEONLY)) == NULL)
1979 val = n_empty;
1980 ok_vset(mailx_orig_from, val);
1981 if((val = detract(hp->h_mailx_orig_to, GNAMEONLY)) == NULL)
1982 val = n_empty;
1983 ok_vset(mailx_orig_to, val);
1984 if((val = detract(hp->h_mailx_orig_cc, GNAMEONLY)) == NULL)
1985 val = n_empty;
1986 ok_vset(mailx_orig_cc, val);
1987 if((val = detract(hp->h_mailx_orig_bcc, GNAMEONLY)) == NULL)
1988 val = n_empty;
1989 ok_vset(mailx_orig_bcc, val);
1990 NYD2_LEAVE;
1993 FL FILE *
1994 n_collect(struct header *hp, int printheaders, struct message *mp,
1995 char const *quotefile, bool_t is_fwding, si8_t *checkaddr_err)
1997 struct n_string s, * volatile sp;
1998 struct a_coll_ocs_arg *coap;
1999 int c;
2000 int volatile t, eofcnt, getfields;
2001 char volatile escape;
2002 enum{
2003 a_NONE,
2004 a_ERREXIT = 1u<<0,
2005 a_IGNERR = 1u<<1,
2006 a_COAP_NOSIGTERM = 1u<<8
2007 #define a_HARDERR() ((flags & (a_ERREXIT | a_IGNERR)) == a_ERREXIT)
2008 } volatile flags;
2009 char *linebuf;
2010 char const *cp, *cp_base, * volatile coapm, * volatile ifs_saved;
2011 size_t i, linesize; /* TODO line pool */
2012 long cnt;
2013 sigset_t oset, nset;
2014 FILE * volatile sigfp;
2015 NYD_ENTER;
2017 _coll_fp = NULL;
2018 sigfp = NULL;
2019 linesize = 0;
2020 linebuf = NULL;
2021 flags = a_NONE;
2022 eofcnt = 0;
2023 ifs_saved = coapm = NULL;
2024 coap = NULL;
2025 sp = NULL;
2027 /* Start catching signals from here, but we're still die on interrupts
2028 * until we're in the main loop */
2029 sigfillset(&nset);
2030 sigprocmask(SIG_BLOCK, &nset, &oset);
2031 if ((_coll_saveint = safe_signal(SIGINT, SIG_IGN)) != SIG_IGN)
2032 safe_signal(SIGINT, &_collint);
2033 if ((_coll_savehup = safe_signal(SIGHUP, SIG_IGN)) != SIG_IGN)
2034 safe_signal(SIGHUP, collhup);
2035 if (sigsetjmp(_coll_abort, 1))
2036 goto jerr;
2037 if (sigsetjmp(_coll_jmp, 1))
2038 goto jerr;
2039 n_pstate |= n_PS_COMPOSE_MODE;
2040 sigprocmask(SIG_SETMASK, &oset, (sigset_t*)NULL);
2042 if ((_coll_fp = Ftmp(NULL, "collect", OF_RDWR | OF_UNLINK | OF_REGISTER)) ==
2043 NULL) {
2044 n_perr(_("collect: temporary mail file"), 0);
2045 goto jerr;
2048 /* If we are going to prompt for a subject, refrain from printing a newline
2049 * after the headers (since some people mind) */
2050 getfields = 0;
2051 if(!(n_poption & n_PO_t_FLAG)){
2052 t = GTO | GSUBJECT | GCC | GNL;
2053 if(ok_blook(fullnames))
2054 t |= GCOMMA;
2056 if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){
2057 if(hp->h_subject == NULL && ok_blook(asksub)/* *ask* auto warped! */)
2058 t &= ~GNL, getfields |= GSUBJECT;
2060 if(hp->h_to == NULL)
2061 t &= ~GNL, getfields |= GTO;
2063 if(!ok_blook(bsdcompat) && !ok_blook(askatend)){
2064 if(ok_blook(askbcc))
2065 t &= ~GNL, getfields |= GBCC;
2066 if(ok_blook(askcc))
2067 t &= ~GNL, getfields |= GCC;
2070 }else{
2071 n_UNINIT(t, 0);
2074 _coll_hadintr = 0;
2076 if (!sigsetjmp(_coll_jmp, 1)) {
2077 /* Ask for some headers first, as necessary */
2078 if (getfields)
2079 grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, getfields, 1);
2081 /* Execute compose-enter; delayed for -t mode */
2082 if(!(n_poption & n_PO_t_FLAG) &&
2083 (cp = ok_vlook(on_compose_enter)) != NULL){
2084 setup_from_and_sender(hp);
2085 temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset,
2086 hp);
2089 /* TODO Mm: nope since it may require turning this into a multipart one */
2090 if(!(n_poption & (n_PO_Mm_FLAG | n_PO_t_FLAG))){
2091 if(!a_coll_message_inject_head(_coll_fp))
2092 goto jerr;
2094 /* Quote an original message */
2095 if(mp != NULL && !a_coll_quote_message(_coll_fp, mp, is_fwding))
2096 goto jerr;
2099 if (quotefile != NULL) {
2100 if((n_pstate_err_no = a_coll_include_file(quotefile, FAL0, FAL0)
2101 ) != n_ERR_NONE)
2102 goto jerr;
2105 if(n_psonce & n_PSO_INTERACTIVE){
2106 if(!(n_pstate & n_PS_SOURCING)){
2107 sp = n_string_creat_auto(&s);
2108 sp = n_string_reserve(sp, 80);
2111 if(!(n_poption & n_PO_Mm_FLAG) && !(n_pstate & n_PS_ROBOT)){
2112 /* Print what we have sofar also on the terminal (if useful) */
2113 if((cp = ok_vlook(editalong)) == NULL){
2114 if(printheaders)
2115 n_puthead(TRU1, hp, n_stdout, t, SEND_TODISP, CONV_NONE,
2116 NULL, NULL);
2118 rewind(_coll_fp);
2119 while ((c = getc(_coll_fp)) != EOF) /* XXX bytewise, yuck! */
2120 putc(c, n_stdout);
2121 if (fseek(_coll_fp, 0, SEEK_END))
2122 goto jerr;
2123 }else{
2124 if(a_coll_edit(((*cp == 'v') ? 'v' : 'e'), hp, NULL
2125 ) != n_ERR_NONE)
2126 goto jerr;
2127 /* Print msg mandated by the Mail Reference Manual */
2128 jcont:
2129 if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT))
2130 fputs(_("(continue)\n"), n_stdout);
2132 fflush(n_stdout);
2135 } else {
2136 /* Come here for printing the after-signal message. Duplicate messages
2137 * won't be printed because the write is aborted if we get a SIGTTOU */
2138 if(_coll_hadintr && (n_psonce & n_PSO_INTERACTIVE) &&
2139 !(n_pstate & n_PS_ROBOT))
2140 n_err(_("\n(Interrupt -- one more to kill letter)\n"));
2143 /* If not under shell hook control */
2144 if(coap == NULL){
2145 /* We're done with -M or -m TODO because: we are too stupid yet, above */
2146 if(n_poption & n_PO_Mm_FLAG)
2147 goto jout;
2148 /* No command escapes, interrupts not expected. Simply copy STDIN */
2149 if(!(n_psonce & n_PSO_INTERACTIVE) &&
2150 !(n_poption & (n_PO_t_FLAG | n_PO_TILDE_FLAG))){
2151 linebuf = n_realloc(linebuf, linesize = LINESIZE);
2152 while ((i = fread(linebuf, sizeof *linebuf, linesize, n_stdin)) > 0) {
2153 if (i != fwrite(linebuf, sizeof *linebuf, i, _coll_fp))
2154 goto jerr;
2156 goto jout;
2160 /* The interactive collect loop */
2161 if(coap == NULL)
2162 escape = *ok_vlook(escape);
2163 flags = ok_blook(errexit) ? a_ERREXIT : a_NONE;
2165 for(;;){
2166 enum {a_HIST_NONE, a_HIST_ADD = 1u<<0, a_HIST_GABBY = 1u<<1} hist;
2168 /* C99 */{
2169 enum n_go_input_flags gif;
2170 bool_t histadd;
2172 /* TODO optimize: no need to evaluate that anew for each loop tick! */
2173 gif = n_GO_INPUT_CTX_COMPOSE;
2174 histadd = (sp != NULL);
2175 if((n_psonce & n_PSO_INTERACTIVE) || (n_poption & n_PO_TILDE_FLAG)){
2176 if(!(n_poption & n_PO_t_FLAG) || (n_psonce & n_PSO_t_FLAG_DONE))
2177 gif |= n_GO_INPUT_NL_ESC;
2179 cnt = n_go_input(gif, n_empty, &linebuf, &linesize, NULL, &histadd);
2180 hist = histadd ? a_HIST_ADD | a_HIST_GABBY : a_HIST_NONE;
2183 if(cnt < 0){
2184 if(coap != NULL)
2185 break;
2186 if((n_poption & n_PO_t_FLAG) && !(n_psonce & n_PSO_t_FLAG_DONE)){
2187 fflush_rewind(_coll_fp);
2188 n_psonce |= n_PSO_t_FLAG_DONE;
2189 if(!a_coll_makeheader(_coll_fp, hp, checkaddr_err, TRU1))
2190 goto jerr;
2191 continue;
2192 }else if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
2193 ok_blook(ignoreeof) && ++eofcnt < 4){
2194 fprintf(n_stdout,
2195 _("*ignoreeof* set, use `~.' to terminate letter\n"));
2196 n_go_input_clearerr();
2197 continue;
2199 break;
2202 _coll_hadintr = 0;
2204 cp = linebuf;
2205 if(cnt == 0)
2206 goto jputnl;
2207 else if(coap == NULL){
2208 if(!(n_psonce & n_PSO_INTERACTIVE) && !(n_poption & n_PO_TILDE_FLAG))
2209 goto jputline;
2210 else if(cp[0] == '.'){
2211 if(cnt == 1 && (ok_blook(dot) ||
2212 (ok_blook(posix) && ok_blook(ignoreeof))))
2213 break;
2216 if(cp[0] != escape){
2217 jputline:
2218 if(fwrite(cp, sizeof *cp, cnt, _coll_fp) != (size_t)cnt)
2219 goto jerr;
2220 /* TODO n_PS_READLINE_NL is a terrible hack to ensure that _in_all_-
2221 * TODO _code_paths_ a file without trailing newline isn't modified
2222 * TODO to contain one; the "saw-newline" needs to be part of an
2223 * TODO I/O input machinery object */
2224 jputnl:
2225 if(n_pstate & n_PS_READLINE_NL){
2226 if(putc('\n', _coll_fp) == EOF)
2227 goto jerr;
2229 continue;
2232 c = *(cp_base = ++cp);
2233 if(--cnt == 0)
2234 goto jearg;
2236 /* Avoid history entry? */
2237 while(spacechar(c)){
2238 hist = a_HIST_NONE;
2239 c = *(cp_base = ++cp);
2240 if(--cnt == 0)
2241 goto jearg;
2244 /* It may just be an escaped escaped character, do that quick */
2245 if(c == escape)
2246 goto jputline;
2248 /* Avoid hard *errexit*? */
2249 flags &= ~a_IGNERR;
2250 if(c == '-'){
2251 flags ^= a_IGNERR;
2252 c = *++cp;
2253 if(--cnt == 0)
2254 goto jearg;
2257 /* Trim input, also to gain a somewhat normalized history entry */
2258 ++cp;
2259 if(--cnt > 0){
2260 struct str x;
2262 x.s = n_UNCONST(cp);
2263 x.l = (size_t)cnt;
2264 n_str_trim_ifs(&x, TRU1);
2265 x.s[x.l] = '\0';
2266 cp = x.s;
2267 cnt = (int)/*XXX*/x.l;
2270 if(hist != a_HIST_NONE){
2271 sp = n_string_assign_c(sp, escape);
2272 if(flags & a_IGNERR)
2273 sp = n_string_push_c(sp, '-');
2274 sp = n_string_push_c(sp, c);
2275 if(cnt > 0){
2276 sp = n_string_push_c(sp, ' ');
2277 sp = n_string_push_buf(sp, cp, cnt);
2281 /* Switch over all command escapes */
2282 switch(c){
2283 default:
2284 if(1){
2285 char buf[sizeof(n_UNIREPL)];
2287 if(asciichar(c))
2288 buf[0] = c, buf[1] = '\0';
2289 else if(n_psonce & n_PSO_UNICODE)
2290 memcpy(buf, n_unirepl, sizeof n_unirepl);
2291 else
2292 buf[0] = '?', buf[1] = '\0';
2293 n_err(_("Unknown command escape: `%c%s'\n"), escape, buf);
2294 }else
2295 jearg:
2296 n_err(_("Invalid command escape usage: %s\n"),
2297 n_shexp_quote_cp(linebuf, FAL0));
2298 if(a_HARDERR())
2299 goto jerr;
2300 n_pstate_err_no = n_ERR_INVAL;
2301 n_pstate_ex_no = 1;
2302 continue;
2303 case '!':
2304 /* Shell escape, send the balance of line to sh -c */
2305 if(cnt == 0 || coap != NULL)
2306 goto jearg;
2307 else{
2308 char const *argv[2];
2310 argv[0] = cp;
2311 argv[1] = NULL;
2312 n_pstate_ex_no = c_shell(argv); /* TODO history norm.; errexit? */
2314 goto jhistcont;
2315 case '.':
2316 /* Simulate end of file on input */
2317 if(cnt != 0 || coap != NULL)
2318 goto jearg;
2319 goto jout; /* TODO does not enter history, thus */
2320 case ':':
2321 case '_':
2322 /* Escape to command mode, but be nice! *//* TODO command expansion
2323 * TODO should be handled here so that we have unique history! */
2324 if(cnt == 0)
2325 goto jearg;
2326 _execute_command(hp, cp, cnt);
2327 if(ok_blook(errexit))
2328 flags |= a_ERREXIT;
2329 else
2330 flags &= ~a_ERREXIT;
2331 if(n_pstate_ex_no != 0 && a_HARDERR())
2332 goto jerr;
2333 if(coap == NULL)
2334 escape = *ok_vlook(escape);
2335 hist &= ~a_HIST_GABBY;
2336 break;
2337 /* case '<': <> 'd' */
2338 case '?':
2339 #ifdef HAVE_UISTRINGS
2340 fputs(_(
2341 "COMMAND ESCAPES (to be placed after a newline) excerpt:\n"
2342 "~. Commit and send message\n"
2343 "~: <command> Execute an internal command\n"
2344 "~< <file> Insert <file> (\"~<! <command>\": insert shell command)\n"
2345 "~@ [<files>] Edit [Add] attachments (file[=in-charset[#out-charset]])\n"
2346 "~c <users> Add users to Cc: list (`~b': to Bcc:)\n"
2347 "~e, ~v Edit message via $EDITOR / $VISUAL\n"
2348 ), n_stdout);
2349 fputs(_(
2350 "~F <msglist> Read in with headers, do not *indentprefix* lines\n"
2351 "~f <msglist> Like `~F', but honour `headerpick' configuration\n"
2352 "~H Edit From:, Reply-To: and Sender:\n"
2353 "~h Prompt for Subject:, To:, Cc: and Bcc:\n"
2354 "~i <variable> Insert a value and a newline (`~I': insert value)\n"
2355 "~M <msglist> Read in with headers, *indentprefix* (`~m': use `headerpick')\n"
2356 "~p Show current message compose buffer\n"
2357 "~Q <msglist> Read in using normal *quote* algorithm\n"
2358 ), n_stdout);
2359 fputs(_(
2360 "~r <file> Insert <file> (`~R': *indentprefix* lines)\n"
2361 " <file> may also be <- [HERE-DELIMITER]>\n"
2362 "~s <subject> Set Subject:\n"
2363 "~t <users> Add users to To: list\n"
2364 "~u <msglist> Read in without headers (`~U': *indentprefix* lines)\n"
2365 "~w <file> Write message onto file\n"
2366 "~x Abort composition, discard message (`~q': save in $DEAD)\n"
2367 "~| <command> Pipe message through shell filter (`~||': with headers)\n"
2368 ), n_stdout);
2369 #endif /* HAVE_UISTRINGS */
2370 if(cnt != 0)
2371 goto jearg;
2372 n_pstate_err_no = n_ERR_NONE;
2373 n_pstate_ex_no = 0;
2374 break;
2375 case '@':{
2376 struct attachment *aplist;
2378 /* Edit the attachment list */
2379 aplist = hp->h_attach;
2380 hp->h_attach = NULL;
2381 if(cnt != 0)
2382 hp->h_attach = n_attachment_append_list(aplist, cp);
2383 else
2384 hp->h_attach = n_attachment_list_edit(aplist,
2385 n_GO_INPUT_CTX_COMPOSE);
2386 n_pstate_err_no = n_ERR_NONE; /* XXX ~@ does NOT handle $!/$?! */
2387 n_pstate_ex_no = 0; /* XXX */
2388 }break;
2389 case '^':
2390 if(!a_collect_plumbing(cp, hp)){
2391 if(ferror(_coll_fp))
2392 goto jerr;
2393 goto jearg;
2395 n_pstate_err_no = n_ERR_NONE; /* XXX */
2396 n_pstate_ex_no = 0; /* XXX */
2397 hist &= ~a_HIST_GABBY;
2398 break;
2399 /* case '_': <> ':' */
2400 case '|':
2401 /* Pipe message through command. Collect output as new message */
2402 if(cnt == 0)
2403 goto jearg;
2404 /* Is this request to do a "stream equivalent" to 'e' and 'v'? */
2405 if(*cp == '|'){
2406 ++cp;
2407 goto jev_go;
2409 if((n_pstate_err_no = a_coll_pipe(cp)) == n_ERR_NONE)
2410 n_pstate_ex_no = 0;
2411 else if(ferror(_coll_fp))
2412 goto jerr;
2413 else if(a_HARDERR())
2414 goto jerr;
2415 else
2416 n_pstate_ex_no = 1;
2417 hist &= ~a_HIST_GABBY;
2418 goto jhistcont;
2419 case 'A':
2420 case 'a':
2421 /* Insert the contents of a sign variable */
2422 if(cnt != 0)
2423 goto jearg;
2424 cp = (c == 'a') ? ok_vlook(sign) : ok_vlook(Sign);
2425 goto jIi_putesc;
2426 case 'b':
2427 /* Add stuff to blind carbon copies list TODO join 'c' */
2428 if(cnt == 0)
2429 goto jearg;
2430 else{
2431 struct name *np;
2432 si8_t soe;
2434 soe = 0;
2435 if((np = checkaddrs(lextract(cp, GBCC | GFULL), EACM_NORMAL, &soe)
2436 ) != NULL)
2437 hp->h_bcc = cat(hp->h_bcc, np);
2438 if(soe == 0){
2439 n_pstate_err_no = n_ERR_NONE;
2440 n_pstate_ex_no = 0;
2441 }else{
2442 n_pstate_ex_no = 1;
2443 n_pstate_err_no = (soe < 0) ? n_ERR_PERM : n_ERR_INVAL;
2446 hist &= ~a_HIST_GABBY;
2447 break;
2448 case 'c':
2449 /* Add to the CC list TODO join 'b' */
2450 if(cnt == 0)
2451 goto jearg;
2452 else{
2453 struct name *np;
2454 si8_t soe;
2456 soe = 0;
2457 if((np = checkaddrs(lextract(cp, GCC | GFULL), EACM_NORMAL, &soe)
2458 ) != NULL)
2459 hp->h_cc = cat(hp->h_cc, np);
2460 if(soe == 0){
2461 n_pstate_err_no = n_ERR_NONE;
2462 n_pstate_ex_no = 0;
2463 }else{
2464 n_pstate_ex_no = 1;
2465 n_pstate_err_no = (soe < 0) ? n_ERR_PERM : n_ERR_INVAL;
2468 hist &= ~a_HIST_GABBY;
2469 break;
2470 case 'd':
2471 if(cnt != 0)
2472 goto jearg;
2473 cp = n_getdeadletter();
2474 if(0){
2475 case '<':
2476 case 'R':
2477 case 'r':
2478 /* Invoke a file: Search for the file name, then open it and copy
2479 * the contents to _coll_fp */
2480 if(cnt == 0){
2481 n_err(_("Interpolate what file?\n"));
2482 if(a_HARDERR())
2483 goto jerr;
2484 n_pstate_err_no = n_ERR_NOENT;
2485 n_pstate_ex_no = 1;
2486 break;
2488 if(*cp == '!' && c == '<'){
2489 /* TODO hist. normalization */
2490 if((n_pstate_err_no = a_coll_insert_cmd(_coll_fp, ++cp)
2491 ) != n_ERR_NONE){
2492 if(ferror(_coll_fp))
2493 goto jerr;
2494 if(a_HARDERR())
2495 goto jerr;
2496 n_pstate_ex_no = 1;
2497 break;
2499 goto jhistcont;
2501 /* Note this also expands things like
2502 * !:vput vexpr delim random 0
2503 * !< - $delim */
2504 if((cp = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO | FEXP_NSHELL)
2505 ) == NULL){
2506 if(a_HARDERR())
2507 goto jerr;
2508 n_pstate_err_no = n_ERR_INVAL;
2509 n_pstate_ex_no = 1;
2510 break;
2513 /* XXX race, and why not test everywhere, then? */
2514 if(n_is_dir(cp, FAL0)){
2515 n_err(_("%s: is a directory\n"), n_shexp_quote_cp(cp, FAL0));
2516 if(a_HARDERR())
2517 goto jerr;
2518 n_pstate_err_no = n_ERR_ISDIR;
2519 n_pstate_ex_no = 1;
2520 break;
2522 if((n_pstate_err_no = a_coll_include_file(cp, (c == 'R'), TRU1)
2523 ) != n_ERR_NONE){
2524 if(ferror(_coll_fp))
2525 goto jerr;
2526 if(a_HARDERR())
2527 goto jerr;
2528 n_pstate_ex_no = 1;
2529 break;
2531 n_pstate_err_no = n_ERR_NONE; /* XXX */
2532 n_pstate_ex_no = 0; /* XXX */
2533 break;
2534 case 'e':
2535 case 'v':
2536 /* Edit the current message. 'e' -> use EDITOR, 'v' -> use VISUAL */
2537 if(cnt != 0 || coap != NULL)
2538 goto jearg;
2539 jev_go:
2540 if((n_pstate_err_no = a_coll_edit(c,
2541 ((c == '|' || ok_blook(editheaders)) ? hp : NULL), cp)
2542 ) == n_ERR_NONE)
2543 n_pstate_ex_no = 0;
2544 else if(ferror(_coll_fp))
2545 goto jerr;
2546 else if(a_HARDERR())
2547 goto jerr;
2548 else
2549 n_pstate_ex_no = 1;
2550 goto jhistcont;
2551 case 'F':
2552 case 'f':
2553 case 'M':
2554 case 'm':
2555 case 'Q':
2556 case 'U':
2557 case 'u':
2558 /* Interpolate the named messages, if we are in receiving mail mode.
2559 * Does the standard list processing garbage. If ~f is given, we
2560 * don't shift over */
2561 if((n_pstate_err_no = a_coll_forward(cp, _coll_fp, c)) == n_ERR_NONE)
2562 n_pstate_ex_no = 0;
2563 else if(ferror(_coll_fp))
2564 goto jerr;
2565 else if(a_HARDERR())
2566 goto jerr;
2567 else
2568 n_pstate_ex_no = 1;
2569 break;
2570 case 'H':
2571 /* Grab extra headers */
2572 if(cnt != 0)
2573 goto jearg;
2575 grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, GEXTRA, 0);
2576 while(check_from_and_sender(hp->h_from, hp->h_sender) == NULL);
2577 n_pstate_err_no = n_ERR_NONE; /* XXX */
2578 n_pstate_ex_no = 0; /* XXX */
2579 break;
2580 case 'h':
2581 /* Grab a bunch of headers */
2582 if(cnt != 0)
2583 goto jearg;
2585 grab_headers(n_GO_INPUT_CTX_COMPOSE, hp,
2586 (GTO | GSUBJECT | GCC | GBCC),
2587 (ok_blook(bsdcompat) && ok_blook(bsdorder)));
2588 while(hp->h_to == NULL);
2589 n_pstate_err_no = n_ERR_NONE; /* XXX */
2590 n_pstate_ex_no = 0; /* XXX */
2591 break;
2592 case 'I':
2593 case 'i':
2594 /* Insert a variable into the file */
2595 if(cnt == 0)
2596 goto jearg;
2597 cp = n_var_vlook(cp, TRU1);
2598 jIi_putesc:
2599 if(cp == NULL || *cp == '\0')
2600 break;
2601 if(!a_coll_putesc(cp, (c != 'I'), _coll_fp))
2602 goto jerr;
2603 if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
2604 !a_coll_putesc(cp, (c != 'I'), n_stdout))
2605 goto jerr;
2606 n_pstate_err_no = n_ERR_NONE; /* XXX */
2607 n_pstate_ex_no = 0; /* XXX */
2608 break;
2609 /* case 'M': <> 'F' */
2610 /* case 'm': <> 'f' */
2611 case 'p':
2612 /* Print current state of the message without altering anything */
2613 if(cnt != 0)
2614 goto jearg;
2615 print_collf(_coll_fp, hp); /* XXX pstate_err_no ++ */
2616 if(ferror(_coll_fp))
2617 goto jerr;
2618 n_pstate_err_no = n_ERR_NONE; /* XXX */
2619 n_pstate_ex_no = 0; /* XXX */
2620 break;
2621 /* case 'Q': <> 'F' */
2622 case 'q':
2623 case 'x':
2624 /* Force a quit, act like an interrupt had happened */
2625 if(cnt != 0)
2626 goto jearg;
2627 /* If we are running a splice hook, assume it quits on its own now,
2628 * otherwise we (no true out-of-band IPC to signal this state, XXX sic)
2629 * have to SIGTERM it in order to stop this wild beast */
2630 flags |= a_COAP_NOSIGTERM;
2631 ++_coll_hadintr;
2632 _collint((c == 'x') ? 0 : SIGINT);
2633 exit(n_EXIT_ERR);
2634 /*NOTREACHED*/
2635 /* case 'R': <> 'd' */
2636 /* case 'r': <> 'd' */
2637 case 's':
2638 /* Set the Subject list */
2639 if(cnt == 0)
2640 goto jearg;
2641 /* Subject:; take care for Debian #419840 and strip any \r and \n */
2642 if(n_anyof_cp("\n\r", hp->h_subject = savestr(cp))){
2643 char *xp;
2645 n_err(_("-s: normalizing away invalid ASCII NL / CR bytes\n"));
2646 for(xp = hp->h_subject; *xp != '\0'; ++xp)
2647 if(*xp == '\n' || *xp == '\r')
2648 *xp = ' ';
2649 n_pstate_err_no = n_ERR_INVAL;
2650 n_pstate_ex_no = 1;
2651 }else{
2652 n_pstate_err_no = n_ERR_NONE;
2653 n_pstate_ex_no = 0;
2655 break;
2656 case 't':
2657 /* Add to the To: list TODO join 'b', 'c' */
2658 if(cnt == 0)
2659 goto jearg;
2660 else{
2661 struct name *np;
2662 si8_t soe;
2664 soe = 0;
2665 if((np = checkaddrs(lextract(cp, GTO | GFULL), EACM_NORMAL, &soe)
2666 ) != NULL)
2667 hp->h_to = cat(hp->h_to, np);
2668 if(soe == 0){
2669 n_pstate_err_no = n_ERR_NONE;
2670 n_pstate_ex_no = 0;
2671 }else{
2672 n_pstate_ex_no = 1;
2673 n_pstate_err_no = (soe < 0) ? n_ERR_PERM : n_ERR_INVAL;
2676 hist &= ~a_HIST_GABBY;
2677 break;
2678 /* case 'U': <> 'F' */
2679 /* case 'u': <> 'f' */
2680 /* case 'v': <> 'e' */
2681 case 'w':
2682 /* Write the message on a file */
2683 if(cnt == 0)
2684 goto jearg;
2685 if((cp = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
2686 n_err(_("Write what file!?\n"));
2687 if(a_HARDERR())
2688 goto jerr;
2689 n_pstate_err_no = n_ERR_INVAL;
2690 n_pstate_ex_no = 1;
2691 break;
2693 rewind(_coll_fp);
2694 if((n_pstate_err_no = a_coll_write(cp, _coll_fp, 1)) == n_ERR_NONE)
2695 n_pstate_ex_no = 0;
2696 else if(ferror(_coll_fp))
2697 goto jerr;
2698 else if(a_HARDERR())
2699 goto jerr;
2700 else
2701 n_pstate_ex_no = 1;
2702 break;
2703 /* case 'x': <> 'q' */
2706 /* Finally place an entry in history as applicable */
2707 if(0){
2708 jhistcont:
2709 c = '\1';
2710 }else
2711 c = '\0';
2712 if(hist & a_HIST_ADD){
2713 /* Do not add *escape* to the history in order to allow history search
2714 * to be handled generically in the MLE regardless of actual *escape*
2715 * settings etc. */
2716 n_tty_addhist(&n_string_cp(sp)[1], (n_GO_INPUT_CTX_COMPOSE |
2717 (hist & a_HIST_GABBY ? n_GO_INPUT_HIST_GABBY : n_GO_INPUT_NONE)));
2719 if(c != '\0')
2720 goto jcont;
2723 jout:
2724 /* Do we have *on-compose-splice-shell*, or *on-compose-splice*?
2725 * TODO Usual f...ed up state of signals and terminal etc. */
2726 if(coap == NULL && (cp = ok_vlook(on_compose_splice_shell)) != NULL) Jocs:{
2727 union {int (*ptf)(void); char const *sh;} u;
2728 char const *cmd;
2730 /* Reset *escape* and more to their defaults. On change update manual! */
2731 if(ifs_saved == NULL)
2732 ifs_saved = savestr(ok_vlook(ifs));
2733 escape = n_ESCAPE[0];
2734 ok_vclear(ifs);
2736 if(coapm != NULL){
2737 /* XXX Due Popen() fflush(NULL) in PTF mode, ensure nothing to flush */
2738 /*if(!n_real_seek(_coll_fp, 0, SEEK_END))
2739 * goto jerr;*/
2740 u.ptf = &a_coll_ocs__mac;
2741 cmd = (char*)-1;
2742 a_coll_ocs__macname = cp = coapm;
2743 }else{
2744 u.sh = ok_vlook(SHELL);
2745 cmd = cp;
2748 i = strlen(cp) +1;
2749 coap = n_lofi_alloc(n_VSTRUCT_SIZEOF(struct a_coll_ocs_arg, coa_cmd
2750 ) + i);
2751 coap->coa_pipe[0] = coap->coa_pipe[1] = -1;
2752 coap->coa_stdin = coap->coa_stdout = NULL;
2753 coap->coa_senderr = checkaddr_err;
2754 memcpy(coap->coa_cmd, cp, i);
2756 hold_all_sigs();
2757 coap->coa_opipe = safe_signal(SIGPIPE, SIG_IGN);
2758 coap->coa_oint = safe_signal(SIGINT, SIG_IGN);
2759 rele_all_sigs();
2761 if(pipe_cloexec(coap->coa_pipe) != -1 &&
2762 (coap->coa_stdin = Fdopen(coap->coa_pipe[0], "r", FAL0)) != NULL &&
2763 (coap->coa_stdout = Popen(cmd, "W", u.sh, NULL, coap->coa_pipe[1])
2764 ) != NULL){
2765 close(coap->coa_pipe[1]);
2766 coap->coa_pipe[1] = -1;
2768 temporary_compose_mode_hook_call(NULL, NULL, NULL);
2769 n_go_splice_hack(coap->coa_cmd, coap->coa_stdin, coap->coa_stdout,
2770 (n_psonce & ~(n_PSO_INTERACTIVE | n_PSO_TTYIN | n_PSO_TTYOUT)),
2771 &a_coll_ocs__finalize, &coap);
2772 /* Hook version protocol for ~^: update manual upon change! */
2773 fputs(a_COLL_PLUMBING_VERSION "\n", coap->coa_stdout);
2774 #undef a_COLL_PLUMBING_VERSION
2775 goto jcont;
2778 c = n_err_no;
2779 a_coll_ocs__finalize(coap);
2780 n_perr(_("Cannot invoke *on-compose-splice(-shell)?*"), c);
2781 goto jerr;
2783 if(*checkaddr_err != 0){
2784 *checkaddr_err = 0;
2785 goto jerr;
2787 if(coapm == NULL && (coapm = ok_vlook(on_compose_splice)) != NULL)
2788 goto Jocs;
2789 if(coap != NULL){
2790 ok_vset(ifs, ifs_saved);
2791 ifs_saved = NULL;
2794 /* Final chance to edit headers, if not already above */
2795 if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT)){
2796 if(ok_blook(bsdcompat) || ok_blook(askatend)){
2797 enum gfield gf;
2799 gf = GNONE;
2800 if(ok_blook(askcc))
2801 gf |= GCC;
2802 if(ok_blook(askbcc))
2803 gf |= GBCC;
2804 if(gf != 0)
2805 grab_headers(n_GO_INPUT_CTX_COMPOSE, hp, gf, 1);
2807 if(ok_blook(askattach))
2808 hp->h_attach = n_attachment_list_edit(hp->h_attach,
2809 n_GO_INPUT_CTX_COMPOSE);
2812 /* Execute compose-leave */
2813 if((cp = ok_vlook(on_compose_leave)) != NULL){
2814 setup_from_and_sender(hp);
2815 temporary_compose_mode_hook_call(cp, &n_temporary_compose_hook_varset,
2816 hp);
2819 /* Add automatic receivers */
2820 if ((cp = ok_vlook(autocc)) != NULL && *cp != '\0')
2821 hp->h_cc = cat(hp->h_cc, checkaddrs(lextract(cp, GCC |
2822 (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN)),
2823 EACM_NORMAL, checkaddr_err));
2824 if ((cp = ok_vlook(autobcc)) != NULL && *cp != '\0')
2825 hp->h_bcc = cat(hp->h_bcc, checkaddrs(lextract(cp, GBCC |
2826 (ok_blook(fullnames) ? GFULL | GSKIN : GSKIN)),
2827 EACM_NORMAL, checkaddr_err));
2828 if (*checkaddr_err != 0)
2829 goto jerr;
2831 if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
2832 ok_blook(asksend)){
2833 bool_t b;
2835 ifs_saved = coapm = NULL;
2836 coap = NULL;
2838 fprintf(n_stdout, _("-------\nEnvelope contains:\n")); /* xxx SEARCH112 */
2839 if(!n_puthead(TRU1, hp, n_stdout,
2840 GIDENT | GREF_IRT | GSUBJECT | GTO | GCC | GBCC | GBCC_IS_FCC |
2841 GCOMMA, SEND_TODISP, CONV_NONE, NULL, NULL))
2842 goto jerr;
2844 jreasksend:
2845 if(n_go_input(n_GO_INPUT_CTX_COMPOSE | n_GO_INPUT_NL_ESC,
2846 _("Send this message [yes/no, empty: recompose]? "),
2847 &linebuf, &linesize, NULL, NULL) < 0){
2848 if(!n_go_input_is_eof())
2849 goto jerr;
2850 cp = n_1;
2853 if((b = n_boolify(linebuf, UIZ_MAX, TRUM1)) < FAL0)
2854 goto jreasksend;
2855 if(b == TRU2)
2856 goto jcont;
2857 if(!b)
2858 goto jerr;
2861 /* TODO Cannot do since it may require turning this into a multipart one */
2862 if(n_poption & n_PO_Mm_FLAG)
2863 goto jskiptails;
2865 /* Place signature? */
2866 if((cp = ok_vlook(signature)) != NULL && *cp != '\0'){ /* TODO OBSOLETE */
2867 char const *cpq;
2869 n_OBSOLETE(_("please use *on-compose-{leave,splice}* and/or "
2870 "*message-inject-tail*, not *signature*"));
2872 if((cpq = fexpand(cp, FEXP_LOCAL | FEXP_NOPROTO)) == NULL){
2873 n_err(_("*signature* expands to invalid file: %s\n"),
2874 n_shexp_quote_cp(cp, FAL0));
2875 goto jerr;
2877 cpq = n_shexp_quote_cp(cp = cpq, FAL0);
2879 if((sigfp = Fopen(cp, "r")) == NULL){
2880 n_err(_("Can't open *signature* %s: %s\n"),
2881 cpq, n_err_to_doc(n_err_no));
2882 goto jerr;
2885 if(linebuf == NULL)
2886 linebuf = n_alloc(linesize = LINESIZE);
2887 c = '\0';
2888 while((i = fread(linebuf, sizeof *linebuf, linesize, n_UNVOLATILE(sigfp)))
2889 > 0){
2890 c = linebuf[i - 1];
2891 if(i != fwrite(linebuf, sizeof *linebuf, i, _coll_fp))
2892 goto jerr;
2895 /* C99 */{
2896 FILE *x = n_UNVOLATILE(sigfp);
2897 int e = n_err_no, ise = ferror(x);
2899 sigfp = NULL;
2900 Fclose(x);
2902 if(ise){
2903 n_err(_("Errors while reading *signature* %s: %s\n"),
2904 cpq, n_err_to_doc(e));
2905 goto jerr;
2909 if(c != '\0' && c != '\n')
2910 putc('\n', _coll_fp);
2913 { char const *cp_obsolete = ok_vlook(NAIL_TAIL);
2915 if(cp_obsolete != NULL)
2916 n_OBSOLETE(_("please use *message-inject-tail*, not *NAIL_TAIL*"));
2918 if((cp = ok_vlook(message_inject_tail)) != NULL ||
2919 (cp = cp_obsolete) != NULL){
2920 if(!a_coll_putesc(cp, TRU1, _coll_fp))
2921 goto jerr;
2922 if((n_psonce & n_PSO_INTERACTIVE) && !(n_pstate & n_PS_ROBOT) &&
2923 !a_coll_putesc(cp, TRU1, n_stdout))
2924 goto jerr;
2928 jskiptails:
2929 if(fflush(_coll_fp))
2930 goto jerr;
2931 rewind(_coll_fp);
2933 if(mp != NULL && ok_blook(quote_as_attachment)){
2934 struct attachment *ap;
2936 ap = n_autorec_calloc(1, sizeof *ap);
2937 if((ap->a_flink = hp->h_attach) != NULL)
2938 hp->h_attach->a_blink = ap;
2939 hp->h_attach = ap;
2940 ap->a_msgno = (int)PTR2SIZE(mp - message + 1);
2941 ap->a_content_description = _("Original message content");
2944 jleave:
2945 if (linebuf != NULL)
2946 n_free(linebuf);
2947 sigprocmask(SIG_BLOCK, &nset, NULL);
2948 n_pstate &= ~n_PS_COMPOSE_MODE;
2949 safe_signal(SIGINT, _coll_saveint);
2950 safe_signal(SIGHUP, _coll_savehup);
2951 sigprocmask(SIG_SETMASK, &oset, NULL);
2952 NYD_LEAVE;
2953 return _coll_fp;
2955 jerr:
2956 hold_all_sigs();
2958 if(coap != NULL && coap != (struct a_coll_ocs_arg*)-1){
2959 if(!(flags & a_COAP_NOSIGTERM))
2960 n_psignal(coap->coa_stdout, SIGTERM);
2961 n_go_splice_hack_remove_after_jump();
2962 coap = NULL;
2964 if(ifs_saved != NULL){
2965 ok_vset(ifs, ifs_saved);
2966 ifs_saved = NULL;
2968 if(sigfp != NULL){
2969 Fclose(n_UNVOLATILE(sigfp));
2970 sigfp = NULL;
2972 if(_coll_fp != NULL){
2973 Fclose(_coll_fp);
2974 _coll_fp = NULL;
2977 rele_all_sigs();
2979 assert(checkaddr_err != NULL);
2980 /* TODO We don't save in $DEAD upon error because msg not readily composed?
2981 * TODO But this no good, it should go ZOMBIE / DRAFT / POSTPONED or what! */
2982 if(*checkaddr_err != 0){
2983 if(*checkaddr_err == 111)
2984 n_err(_("Compose mode splice hook failure\n"));
2985 else
2986 n_err(_("Some addressees were classified as \"hard error\"\n"));
2987 }else if(_coll_hadintr == 0){
2988 *checkaddr_err = TRU1; /* TODO ugly: "sendout_error" now.. */
2989 n_err(_("Failed to prepare composed message\n"));
2991 goto jleave;
2993 #undef a_HARDERR
2996 /* s-it-mode */