1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2  *@ Dig message objects. TODO Very very restricted (especially non-compose)
3  *@ Protocol change: adjust mx-config.h:mx_DIG_MSG_PLUMBING_VERSION + `~^' man.
4  *@ TODO - a_dmsg_cmd() should generate string lists, not perform real I/O.
5  *@ TODO   I.e., drop FILE* arg, generate stringlist; up to callers...
6  *@ TODO - With our own I/O there should then be a StringListDevice as the
7  *@ TODO   owner and I/O overlay provider: NO temporary file (sic)!
8  *@ XXX - Multiple objects per message could be possible (a_dmsg_find()),
9  *@ XXX   except in compose mode
10  *
11  * Copyright (c) 2016 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
12  * SPDX-License-Identifier: ISC
13  *
14  * Permission to use, copy, modify, and/or distribute this software for any
15  * purpose with or without fee is hereby granted, provided that the above
16  * copyright notice and this permission notice appear in all copies.
17  *
18  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
19  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
20  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
21  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
22  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
23  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
24  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
25  */
26 #undef su_FILE
27 #define su_FILE dig_msg
28 #define mx_SOURCE
29 #define mx_SOURCE_DIG_MSG
30 
31 #ifndef mx_HAVE_AMALGAMATION
32 # include "mx/nail.h"
33 #endif
34 
35 #include <su/cs.h>
36 #include <su/icodec.h>
37 #include <su/mem.h>
38 
39 #include "mx/attachments.h"
40 #include "mx/cmd.h"
41 #include "mx/file-streams.h"
42 #include "mx/mime-type.h"
43 #include "mx/names.h"
44 
45 #include "mx/dig-msg.h"
46 #include "su/code-in.h"
47 
48 #define a_DMSG_QUOTE(S) n_shexp_quote_cp(S, FAL0)
49 
50 struct mx_dig_msg_ctx *mx_dig_msg_read_overlay; /* XXX HACK */
51 struct mx_dig_msg_ctx *mx_dig_msg_compose_ctx; /* Or NIL XXX HACK*/
52 
53 /* Try to convert cp into an unsigned number that corresponds to an existing
54  * message number (or ERR_INVAL), search for an existing object (ERR_EXIST if
55  * oexcl and exists; ERR_NOENT if not oexcl and does not exist).
56  * On oexcl success *dmcp will be n_alloc()ated with .dmc_msgno and .dmc_mp
57  * etc. set; but not linked into mb.mb_digmsg and .dmc_fp not created etc. */
58 static s32 a_dmsg_find(char const *cp, struct mx_dig_msg_ctx **dmcpp,
59       boole oexcl);
60 
61 /* Subcommand drivers */
62 static boole a_dmsg_cmd(FILE *fp, struct mx_dig_msg_ctx *dmcp,
63       struct mx_cmd_arg *cmd, struct mx_cmd_arg *args);
64 
65 static boole a_dmsg__header(FILE *fp, struct mx_dig_msg_ctx *dmcp,
66       struct mx_cmd_arg *args);
67 static boole a_dmsg__attach(FILE *fp, struct mx_dig_msg_ctx *dmcp,
68       struct mx_cmd_arg *args);
69 
70 static s32
a_dmsg_find(char const * cp,struct mx_dig_msg_ctx ** dmcpp,boole oexcl)71 a_dmsg_find(char const *cp, struct mx_dig_msg_ctx **dmcpp, boole oexcl){
72    struct mx_dig_msg_ctx *dmcp;
73    s32 rv;
74    u32 msgno;
75    NYD2_IN;
76 
77    if(cp[0] == '-' && cp[1] == '\0'){
78       if((dmcp = mx_dig_msg_compose_ctx) != NIL){
79          *dmcpp = dmcp;
80          if(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE_DIGGED)
81             rv = oexcl ? su_ERR_EXIST : su_ERR_NONE;
82          else
83             rv = oexcl ? su_ERR_NONE : su_ERR_NOENT;
84       }else
85          rv = su_ERR_INVAL;
86       goto jleave;
87    }
88 
89    if((su_idec_u32_cp(&msgno, cp, 0, NIL
90             ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
91          ) != su_IDEC_STATE_CONSUMED ||
92          msgno == 0 || UCMP(z, msgno, >, msgCount)){
93       rv = su_ERR_INVAL;
94       goto jleave;
95    }
96 
97    for(dmcp = mb.mb_digmsg; dmcp != NIL; dmcp = dmcp->dmc_next)
98       if(dmcp->dmc_msgno == msgno){
99          *dmcpp = dmcp;
100          rv = oexcl ? su_ERR_EXIST : su_ERR_NONE;
101          goto jleave;
102       }
103    if(!oexcl){
104       rv = su_ERR_NOENT;
105       goto jleave;
106    }
107 
108    *dmcpp = dmcp = n_calloc(1, Z_ALIGN(sizeof *dmcp) + sizeof(struct header));
109    dmcp->dmc_mp = &message[msgno - 1];
110    dmcp->dmc_flags = mx_DIG_MSG_OWN_MEMBAG |
111          ((TRU1/*TODO*/ || !(mb.mb_perm & MB_DELE))
112             ? mx_DIG_MSG_RDONLY : mx_DIG_MSG_NONE);
113    dmcp->dmc_msgno = msgno;
114    dmcp->dmc_hp = R(struct header*,P2UZ(&dmcp[1]));
115    dmcp->dmc_membag = su_mem_bag_create(&dmcp->dmc__membag_buf[0], 0);
116    /* Rest done by caller */
117    rv = su_ERR_NONE;
118 jleave:
119    NYD2_OU;
120    return rv;
121 }
122 
123 static boole
a_dmsg_cmd(FILE * fp,struct mx_dig_msg_ctx * dmcp,struct mx_cmd_arg * cmd,struct mx_cmd_arg * args)124 a_dmsg_cmd(FILE *fp, struct mx_dig_msg_ctx *dmcp, struct mx_cmd_arg *cmd,
125       struct mx_cmd_arg *args){
126    union {struct mx_cmd_arg *ca; char *c; struct str const *s; boole rv;} p;
127    NYD2_IN;
128 
129    if(cmd == NIL)
130       goto jecmd;
131 
132    p.s = &cmd->ca_arg.ca_str;
133    if(su_cs_starts_with_case_n("header", p.s->s, p.s->l))
134       p.rv = a_dmsg__header(fp, dmcp, args);
135    else if(su_cs_starts_with_case_n("attachment", p.s->s, p.s->l)){
136       if(!(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE)) /* TODO attachment support */
137          p.rv = (fprintf(fp,
138                "505 `digmsg attachment' only in compose mode (yet)\n") > 0);
139       else
140          p.rv = a_dmsg__attach(fp, dmcp, args);
141    }else if(su_cs_starts_with_case_n("version", p.s->s, p.s->l)){
142       if(args != NIL)
143          goto jecmd;
144       p.rv = (fputs("210 " mx_DIG_MSG_PLUMBING_VERSION "\n", fp) != EOF);
145    }else if((p.s->l == 1 && p.s->s[0] == '?') ||
146          su_cs_starts_with_case_n("help", p.s->s, p.s->l)){
147       if(args != NIL)
148          goto jecmd;
149       p.rv = (fputs(_("211 (Arguments undergo shell-style evaluation)\n"),
150                fp) != EOF &&
151 #ifdef mx_HAVE_UISTRINGS
152             fputs(_(
153                "attachment:\n"
154                "   attribute name (212; 501)\n"
155                "   attribute-at position\n"
156                "   attribute-set name key value (210; 505/501)\n"
157                "   attribute-set-at position key value\n"
158                "   insert file[=input-charset[#output-charset]] "
159                   "(210; 501/505/506)\n"
160                "   insert #message-number\n"
161                "   list (212; 501)\n"
162                "   remove name (210; 501/506)\n"
163                "   remove-at position (210; 501/505)\n"), fp) != EOF &&
164             fputs(_(
165                "header\n"
166                "   insert field content (210; 501/505/506)\n"
167                "   list [field] (210; [501]);\n"
168                "   remove field (210; 501/505)\n"
169                "   remove-at field position (210; 501/505)\n"
170                "   show field (211/212; 501)\n"
171                "help (211)\n"
172                "version (210)\n"), fp) != EOF &&
173 #endif
174             putc('\n', fp) != EOF);
175    }else{
176 jecmd:
177       fputs("500\n", fp);
178       p.rv = FAL0;
179    }
180    fflush(fp);
181 
182    NYD2_OU;
183    return p.rv;
184 }
185 
186 static boole
a_dmsg__header(FILE * fp,struct mx_dig_msg_ctx * dmcp,struct mx_cmd_arg * args)187 a_dmsg__header(FILE *fp, struct mx_dig_msg_ctx *dmcp,
188       struct mx_cmd_arg *args){
189    struct n_header_field *hfp;
190    struct mx_name *np, **npp;
191    uz i;
192    struct mx_cmd_arg *a3p;
193    char const *cp;
194    struct header *hp;
195    NYD2_IN;
196 
197    hp = dmcp->dmc_hp;
198    UNINIT(a3p, NIL);
199 
200    if(args == NIL){
201       cp = su_empty; /* xxx not NIL anyway */
202       goto jdefault;
203    }
204 
205    cp = args->ca_arg.ca_str.s;
206    args = args->ca_next;
207 
208    /* Strip the optional colon from header names */
209    if((a3p = args) != NIL){
210       char *xp;
211 
212       a3p = a3p->ca_next;
213 
214       for(xp = args->ca_arg.ca_str.s;; ++xp)
215          if(*xp == '\0')
216             break;
217          else if(*xp == ':'){
218             *xp = '\0';
219             break;
220          }
221    }
222 
223    /* TODO ERR_2BIG should happen on the cmd_arg parser side */
224    if(a3p != NIL && a3p->ca_next != NIL)
225       goto jecmd;
226 
227    if(su_cs_starts_with_case("insert", cp)){ /* TODO LOGIC BELONGS head.c
228        * TODO That is: Header::factory(string) -> object (blahblah).
229        * TODO I.e., as long as we don't have regular RFC compliant parsers
230        * TODO which differentiate in between structured and unstructured
231        * TODO header fields etc., a little workaround */
232       struct mx_name *xnp;
233       s8 aerr;
234       char const *mod_suff;
235       enum expand_addr_check_mode eacm;
236       enum gfield ntype;
237       boole mult_ok;
238 
239       if(args == NIL || a3p == NIL)
240          goto jecmd;
241       if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY)
242          goto j505r;
243 
244       /* Strip [\r\n] which would render a body invalid XXX all controls? */
245       /* C99 */{
246          char c;
247 
248          for(cp = a3p->ca_arg.ca_str.s; (c = *cp) != '\0'; ++cp)
249             if(c == '\n' || c == '\r')
250                *UNCONST(char*,cp) = ' ';
251       }
252 
253       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "Subject")){
254          if(a3p->ca_arg.ca_str.l == 0)
255             goto j501cp;
256 
257          if(hp->h_subject != NIL)
258             hp->h_subject = savecatsep(hp->h_subject, ' ',
259                   a3p->ca_arg.ca_str.s);
260          else
261             hp->h_subject = a3p->ca_arg.ca_str.s;
262          if(fprintf(fp, "210 %s 1\n", cp) < 0)
263             cp = NIL;
264          goto jleave;
265       }
266 
267       mult_ok = TRU1;
268       ntype = GEXTRA | GFULL | GFULLEXTRA;
269       eacm = EACM_STRICT;
270       mod_suff = NIL;
271 
272       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "From")){
273          npp = &hp->h_from;
274 jins:
275          aerr = 0;
276          /* todo As said above, this should be table driven etc., but.. */
277          if(ntype & GBCC_IS_FCC){
278             np = nalloc_fcc(a3p->ca_arg.ca_str.s);
279             if(is_addr_invalid(np, eacm))
280                goto jins_505;
281          }else{
282             if((np = (mult_ok > FAL0 ? lextract : n_extract_single
283                   )(a3p->ca_arg.ca_str.s, ntype | GNULL_OK)) == NIL)
284                goto j501cp;
285 
286             if((np = checkaddrs(np, eacm, &aerr), aerr != 0)){
287 jins_505:
288                if(fprintf(fp, "505 %s\n", cp) < 0)
289                   cp = NIL;
290                goto jleave;
291             }
292          }
293 
294          /* Go to the end of the list, track whether it contains any
295           * non-deleted entries */
296          i = 0;
297          if((xnp = *npp) != NIL)
298             for(;; xnp = xnp->n_flink){
299                if(!(xnp->n_type & GDEL))
300                   ++i;
301                if(xnp->n_flink == NIL)
302                   break;
303             }
304 
305          if(!mult_ok && (i != 0 || np->n_flink != NIL)){
306             if(fprintf(fp, "506 %s\n", cp) < 0)
307                cp = NIL;
308          }else{
309             if(xnp == NIL)
310                *npp = np;
311             else
312                xnp->n_flink = np;
313             np->n_blink = xnp;
314             if(fprintf(fp, "210 %s %" PRIuZ "\n", cp, ++i) < 0)
315                cp = NIL;
316          }
317          goto jleave;
318       }
319 
320 #undef a_X
321 #define a_X(F,H,INS) \
322    if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = F)) \
323       {npp = &hp->H; INS; goto jins;}
324 
325       if((cp = su_cs_find_c(args->ca_arg.ca_str.s, '?')) != NIL){
326          mod_suff = cp;
327          args->ca_arg.ca_str.s[P2UZ(cp - args->ca_arg.ca_str.s)] = '\0';
328          if(*++cp != '\0' && !su_cs_starts_with_case("single", cp)){
329             cp = mod_suff;
330             goto j501cp;
331          }
332          mult_ok = TRUM1;
333       }
334 
335       /* Just like with ~t,~c,~b, immediately test *expandaddr* compliance */
336       a_X("To", h_to, ntype = GTO|GFULL su_COMMA eacm = EACM_NORMAL);
337       a_X("Cc", h_cc, ntype = GCC|GFULL su_COMMA eacm = EACM_NORMAL);
338       a_X("Bcc", h_bcc, ntype = GBCC|GFULL su_COMMA eacm = EACM_NORMAL);
339 
340       if((cp = mod_suff) != NIL)
341          goto j501cp;
342 
343       /* Not | EAF_FILE, depend on *expandaddr*! */
344       a_X("Fcc", h_fcc, ntype = GBCC|GBCC_IS_FCC su_COMMA eacm = EACM_NORMAL);
345       a_X("Sender", h_sender, mult_ok = FAL0);
346       a_X("Reply-To", h_reply_to, eacm = EACM_NONAME);
347       a_X("Mail-Followup-To", h_mft, eacm = EACM_NONAME);
348       a_X("Message-ID", h_message_id,
349          mult_ok = FAL0 su_COMMA ntype = GREF su_COMMA eacm = EACM_NONAME);
350       a_X("References", h_ref, ntype = GREF su_COMMA eacm = EACM_NONAME);
351       a_X("In-Reply-To", h_in_reply_to, ntype = GREF su_COMMA
352          eacm = EACM_NONAME);
353 
354 #undef a_X
355 
356       if((cp = n_header_is_known(args->ca_arg.ca_str.s, UZ_MAX)) != NIL)
357          goto j505r;
358 
359       /* Free-form header fields */
360       /* C99 */{
361          uz nl, bl;
362          struct n_header_field **hfpp;
363 
364          for(cp = args->ca_arg.ca_str.s; *cp != '\0'; ++cp)
365             if(!fieldnamechar(*cp)){
366                cp = args->ca_arg.ca_str.s;
367                goto j501cp;
368             }
369 
370          for(i = 0, hfpp = &hp->h_user_headers; *hfpp != NIL; ++i)
371             hfpp = &(*hfpp)->hf_next;
372 
373          nl = su_cs_len(cp = args->ca_arg.ca_str.s) +1;
374          bl = su_cs_len(a3p->ca_arg.ca_str.s) +1;
375          *hfpp = hfp = n_autorec_alloc(VSTRUCT_SIZEOF(struct n_header_field,
376                hf_dat) + nl + bl);
377          hfp->hf_next = NIL;
378          hfp->hf_nl = nl - 1;
379          hfp->hf_bl = bl - 1;
380          su_mem_copy(&hfp->hf_dat[0], cp, nl);
381          su_mem_copy(&hfp->hf_dat[nl], a3p->ca_arg.ca_str.s, bl);
382          if(fprintf(fp, "210 %s %" PRIuZ "\n", &hfp->hf_dat[0], ++i) < 0)
383             cp = NIL;
384       }
385    }else if(su_cs_starts_with_case("list", cp)){
386 jdefault:
387       if(args == NIL){
388          if(fputs("210", fp) == EOF){
389             cp = NIL;
390             goto jleave;
391          }
392 
393 #undef a_X
394 #define a_X(F,S) \
395    if(su_CONCAT(hp->h_, F) != NIL && fputs(" " su_STRING(S), fp) == EOF){\
396       cp = NIL;\
397       goto jleave;\
398    }
399 
400          a_X(subject, Subject);
401          a_X(from, From);
402          a_X(sender, Sender);
403          a_X(to, To);
404          a_X(cc, Cc);
405          a_X(bcc, Bcc);
406          a_X(fcc, Fcc);
407          a_X(reply_to, Reply-To);
408          a_X(mft, Mail-Followup-To);
409          a_X(message_id, Message-ID);
410          a_X(ref, References);
411          a_X(in_reply_to, In-Reply-To);
412 
413          a_X(mailx_command, Mailx-Command);
414          a_X(mailx_raw_to, Mailx-Raw-To);
415          a_X(mailx_raw_cc, Mailx-Raw-Cc);
416          a_X(mailx_raw_bcc, Mailx-Raw-Bcc);
417          a_X(mailx_orig_sender, Mailx-Orig-Sender);
418          a_X(mailx_orig_from, Mailx-Orig-From);
419          a_X(mailx_orig_to, Mailx-Orig-To);
420          a_X(mailx_orig_cc, Mailx-Orig-Cc);
421          a_X(mailx_orig_bcc, Mailx-Orig-Bcc);
422 
423 #undef a_X
424 
425          /* Print only one instance of each free-form header */
426          for(hfp = hp->h_user_headers; hfp != NIL; hfp = hfp->hf_next){
427             struct n_header_field *hfpx;
428 
429             for(hfpx = hp->h_user_headers;; hfpx = hfpx->hf_next)
430                if(hfpx == hfp){
431                   putc(' ', fp);
432                   fputs(&hfp->hf_dat[0], fp);
433                   break;
434                }else if(!su_cs_cmp_case(&hfpx->hf_dat[0], &hfp->hf_dat[0]))
435                   break;
436          }
437          if(putc('\n', fp) == EOF)
438             cp = NIL;
439          goto jleave;
440       }
441 
442       if(a3p != NIL)
443          goto jecmd;
444 
445       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "Subject")){
446          np = (hp->h_subject != NIL) ? R(struct mx_name*,-1) : NIL;
447          goto jlist;
448       }
449       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "From")){
450          np = hp->h_from;
451 jlist:
452          fprintf(fp, "%s %s\n", (np == NIL ? "501" : "210"), cp);
453          goto jleave;
454       }
455 
456 #undef a_X
457 #define a_X(F,H) \
458    if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = su_STRING(F))){\
459       np = hp->su_CONCAT(h_,H);\
460       goto jlist;\
461    }
462 
463       a_X(Sender, sender);
464       a_X(To, to);
465       a_X(Cc, cc);
466       a_X(Bcc, bcc);
467       a_X(Fcc, fcc);
468       a_X(Reply-To, reply_to);
469       a_X(Mail-Followup-To, mft);
470       a_X(Message-ID, message_id);
471       a_X(References, ref);
472       a_X(In-Reply-To, in_reply_to);
473 
474       a_X(Mailx-Raw-To, mailx_raw_to);
475       a_X(Mailx-Raw-Cc, mailx_raw_cc);
476       a_X(Mailx-Raw-Bcc, mailx_raw_bcc);
477       a_X(Mailx-Orig-Sender, mailx_orig_sender);
478       a_X(Mailx-Orig-From, mailx_orig_from);
479       a_X(Mailx-Orig-To, mailx_orig_to);
480       a_X(Mailx-Orig-Cc, mailx_orig_cc);
481       a_X(Mailx-Orig-Bcc, mailx_orig_bcc);
482 
483 #undef a_X
484 
485       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "Mailx-Command")){
486          np = (hp->h_mailx_command != NIL) ? R(struct mx_name*,-1) : NIL;
487          goto jlist;
488       }
489 
490       /* Free-form header fields */
491       for(cp = args->ca_arg.ca_str.s; *cp != '\0'; ++cp)
492          if(!fieldnamechar(*cp)){
493             cp = args->ca_arg.ca_str.s;
494             goto j501cp;
495          }
496 
497       cp = args->ca_arg.ca_str.s;
498       for(hfp = hp->h_user_headers;; hfp = hfp->hf_next){
499          if(hfp == NIL)
500             goto j501cp;
501          else if(!su_cs_cmp_case(cp, &hfp->hf_dat[0])){
502             if(fprintf(fp, "210 %s\n", &hfp->hf_dat[0]) < 0)
503                cp = NIL;
504             break;
505          }
506       }
507    }else if(su_cs_starts_with_case("remove", cp)){
508       if(args == NIL || a3p != NIL)
509          goto jecmd;
510       if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY)
511          goto j505r;
512 
513       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "Subject")){
514          if(hp->h_subject == NIL)
515             goto j501cp;
516 
517          hp->h_subject = NIL;
518          if(fprintf(fp, "210 %s\n", cp) < 0)
519             cp = NIL;
520          goto jleave;
521       }
522 
523       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "From")){
524          npp = &hp->h_from;
525 jrem:
526          if(*npp != NIL){
527             *npp = NIL;
528             if(fprintf(fp, "210 %s\n", cp) < 0)
529                cp = NIL;
530             goto jleave;
531          }else
532             goto j501cp;
533       }
534 
535 #undef a_X
536 #define a_X(F,H) \
537    if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = su_STRING(F))){\
538       npp = &hp->su_CONCAT(h_,H);\
539       goto jrem;\
540    }
541 
542       a_X(Sender, sender);
543       a_X(To, to);
544       a_X(Cc, cc);
545       a_X(Bcc, bcc);
546       a_X(Fcc, fcc);
547       a_X(Reply-To, reply_to);
548       a_X(Mail-Followup-To, mft);
549       a_X(Message-ID, message_id);
550       a_X(References, ref);
551       a_X(In-Reply-To, in_reply_to);
552 
553 #undef a_X
554 
555       if((cp = n_header_is_known(args->ca_arg.ca_str.s, UZ_MAX)) != NIL)
556          goto j505r;
557 
558       /* Free-form header fields (note j501cp may print non-normalized name) */
559       /* C99 */{
560          struct n_header_field **hfpp;
561          boole any;
562 
563          for(cp = args->ca_arg.ca_str.s; *cp != '\0'; ++cp)
564             if(!fieldnamechar(*cp)){
565                cp = args->ca_arg.ca_str.s;
566                goto j501cp;
567             }
568          cp = args->ca_arg.ca_str.s;
569 
570          for(any = FAL0, hfpp = &hp->h_user_headers; (hfp = *hfpp) != NIL;){
571             if(!su_cs_cmp_case(cp, &hfp->hf_dat[0])){
572                *hfpp = hfp->hf_next;
573                if(!any){
574                   if(fprintf(fp, "210 %s\n", &hfp->hf_dat[0]) < 0){
575                      cp = NIL;
576                      goto jleave;
577                   }
578                }
579                any = TRU1;
580             }else
581                hfpp = &hfp->hf_next;
582          }
583          if(!any)
584             goto j501cp;
585       }
586    }else if(su_cs_starts_with_case("remove-at", cp)){
587       if(args == NIL || a3p == NIL)
588          goto jecmd;
589       if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY)
590          goto j505r;
591 
592       if((su_idec_uz_cp(&i, a3p->ca_arg.ca_str.s, 0, NIL
593                ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
594             ) != su_IDEC_STATE_CONSUMED || i == 0){
595          if(fprintf(fp, "505 invalid position: %s\n",
596                a3p->ca_arg.ca_str.s) < 0)
597             cp = NIL;
598          goto jleave;
599       }
600 
601       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "Subject")){
602          if(hp->h_subject != NIL && i == 1){
603             hp->h_subject = NIL;
604             if(fprintf(fp, "210 %s 1\n", cp) < 0)
605                cp = NIL;
606             goto jleave;
607          }else
608             goto j501cp;
609       }
610 
611       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "From")){
612          npp = &hp->h_from;
613 jremat:
614          if((np = *npp) == NIL)
615             goto j501cp;
616          while(--i != 0 && np != NIL)
617             np = np->n_flink;
618          if(np == NIL)
619             goto j501cp;
620 
621          if(np->n_blink != NIL)
622             np->n_blink->n_flink = np->n_flink;
623          else
624             *npp = np->n_flink;
625          if(np->n_flink != NIL)
626             np->n_flink->n_blink = np->n_blink;
627 
628          if(fprintf(fp, "210 %s\n", cp) < 0)
629             cp = NIL;
630          goto jleave;
631       }
632 
633 #undef a_X
634 #define a_X(F,H) \
635    if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = su_STRING(F))){\
636       npp = &hp->su_CONCAT(h_,H);\
637       goto jremat;\
638    }
639 
640       a_X(Sender, sender);
641       a_X(To, to);
642       a_X(Cc, cc);
643       a_X(Bcc, bcc);
644       a_X(Fcc, fcc);
645       a_X(Reply-To, reply_to);
646       a_X(Mail-Followup-To, mft);
647       a_X(Message-ID, message_id);
648       a_X(References, ref);
649       a_X(In-Reply-To, in_reply_to);
650 
651 #undef a_X
652 
653       if((cp = n_header_is_known(args->ca_arg.ca_str.s, UZ_MAX)) != NIL)
654          goto j505r;
655 
656       /* Free-form header fields */
657       /* C99 */{
658          struct n_header_field **hfpp;
659 
660          for(cp = args->ca_arg.ca_str.s; *cp != '\0'; ++cp)
661             if(!fieldnamechar(*cp)){
662                cp = args->ca_arg.ca_str.s;
663                goto j501cp;
664             }
665          cp = args->ca_arg.ca_str.s;
666 
667          for(hfpp = &hp->h_user_headers; (hfp = *hfpp) != NIL;){
668             if(--i == 0){
669                *hfpp = hfp->hf_next;
670                if(fprintf(fp, "210 %s %" PRIuZ "\n", &hfp->hf_dat[0], i) < 0){
671                   cp = NIL;
672                   goto jleave;
673                }
674                break;
675             }else
676                hfpp = &hfp->hf_next;
677          }
678          if(hfp == NIL)
679             goto j501cp;
680       }
681    }else if(su_cs_starts_with_case("show", cp)){
682       if(args == NIL || a3p != NIL)
683          goto jecmd;
684 
685       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "Subject")){
686          if(hp->h_subject == NIL)
687             goto j501cp;
688          if(fprintf(fp, "212 %s\n%s\n\n", cp, a_DMSG_QUOTE(hp->h_subject)) < 0)
689             cp = NIL;
690          goto jleave;
691       }
692 
693       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "From")){
694          np = hp->h_from;
695 jshow:
696          if(np == NIL)
697             goto j501cp;
698 
699          fprintf(fp, "211 %s\n", cp);
700          do if(!(np->n_type & GDEL)){
701             switch(np->n_flags & mx_NAME_ADDRSPEC_ISMASK){
702             case mx_NAME_ADDRSPEC_ISFILE: cp = n_hy; break;
703             case mx_NAME_ADDRSPEC_ISPIPE: cp = "|"; break;
704             case mx_NAME_ADDRSPEC_ISNAME: cp = n_ns; break;
705             default: cp = np->n_name; break;
706             }
707             fprintf(fp, "%s %s\n", cp, a_DMSG_QUOTE(np->n_fullname));
708          }while((np = np->n_flink) != NIL);
709          if(putc('\n', fp) == EOF)
710             cp = NIL;
711          goto jleave;
712       }
713 
714 #undef a_X
715 #define a_X(F,H) \
716    if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = su_STRING(F))){\
717       np = hp->su_CONCAT(h_,H);\
718       goto jshow;\
719    }
720 
721       a_X(Sender, sender);
722       a_X(To, to);
723       a_X(Cc, cc);
724       a_X(Bcc, bcc);
725       a_X(Fcc, fcc);
726       a_X(Reply-To, reply_to);
727       a_X(Mail-Followup-To, mft);
728       a_X(Message-ID, message_id);
729       a_X(References, ref);
730       a_X(In-Reply-To, in_reply_to);
731 
732       a_X(Mailx-Raw-To, mailx_raw_to);
733       a_X(Mailx-Raw-Cc, mailx_raw_cc);
734       a_X(Mailx-Raw-Bcc, mailx_raw_bcc);
735       a_X(Mailx-Orig-Sender, mailx_orig_sender);
736       a_X(Mailx-Orig-From, mailx_orig_from);
737       a_X(Mailx-Orig-To, mailx_orig_to);
738       a_X(Mailx-Orig-Cc, mailx_orig_cc);
739       a_X(Mailx-Orig-Bcc, mailx_orig_bcc);
740 
741 #undef a_X
742 
743       if(!su_cs_cmp_case(args->ca_arg.ca_str.s, cp = "Mailx-Command")){
744          if(hp->h_mailx_command == NIL)
745             goto j501cp;
746          if(fprintf(fp, "212 %s\n%s\n\n", cp, hp->h_mailx_command) < 0)
747             cp = NIL;
748          goto jleave;
749       }
750 
751       /* Free-form header fields */
752       /* C99 */{
753          boole any;
754 
755          for(cp = args->ca_arg.ca_str.s; *cp != '\0'; ++cp)
756             if(!fieldnamechar(*cp)){
757                cp = args->ca_arg.ca_str.s;
758                goto j501cp;
759             }
760          cp = args->ca_arg.ca_str.s;
761 
762          for(any = FAL0, hfp = hp->h_user_headers; hfp != NIL;
763                hfp = hfp->hf_next){
764             if(!su_cs_cmp_case(cp, &hfp->hf_dat[0])){
765                if(!any)
766                   fprintf(fp, "212 %s\n", &hfp->hf_dat[0]);
767                any = TRU1;
768                fprintf(fp, "%s\n", a_DMSG_QUOTE(&hfp->hf_dat[hfp->hf_nl +1]));
769             }
770          }
771          if(!any)
772             goto j501cp;
773          if(putc('\n', fp) == EOF)
774             cp = NIL;
775       }
776    }else
777       goto jecmd;
778 
779 jleave:
780    NYD2_OU;
781    return (cp != NIL);
782 
783 jecmd:
784    if(fputs("500\n", fp) == EOF)
785       cp = NIL;
786    cp = NIL;
787    goto jleave;
788 j505r:
789    if(fprintf(fp, "505 read-only: %s\n", cp) < 0)
790       cp = NIL;
791    goto jleave;
792 j501cp:
793    if(fprintf(fp, "501 %s\n", cp) < 0)
794       cp = NIL;
795    goto jleave;
796 }
797 
798 static boole
a_dmsg__attach(FILE * fp,struct mx_dig_msg_ctx * dmcp,struct mx_cmd_arg * args)799 a_dmsg__attach(FILE *fp, struct mx_dig_msg_ctx *dmcp,
800       struct mx_cmd_arg *args){
801    boole status;
802    struct mx_attachment *ap;
803    char const *cp;
804    struct header *hp;
805    NYD2_IN;
806 
807    hp = dmcp->dmc_hp;
808 
809    if(args == NIL){
810       cp = su_empty; /* xxx not NIL anyway */
811       goto jdefault;
812    }
813 
814    cp = args->ca_arg.ca_str.s;
815    args = args->ca_next;
816 
817    if(su_cs_starts_with_case("attribute", cp)){
818       if(args == NIL || args->ca_next != NIL)
819          goto jecmd;
820 
821       cp = args->ca_arg.ca_str.s;
822       if((ap = mx_attachments_find(hp->h_attach, cp, NIL)) == NIL)
823          goto j501;
824 
825 jatt_att:
826       fprintf(fp, "212 %s\n", a_DMSG_QUOTE(cp));
827       if(ap->a_msgno > 0){
828          if(fprintf(fp, "message-number %d\n\n", ap->a_msgno) < 0)
829             cp = NIL;
830       }else{
831          fprintf(fp, "creation-name %s\nopen-path %s\nfilename %s\n",
832             a_DMSG_QUOTE(ap->a_path_user), a_DMSG_QUOTE(ap->a_path),
833             a_DMSG_QUOTE(ap->a_name));
834          if((cp = ap->a_content_description) != NIL)
835             fprintf(fp, "content-description %s\n", a_DMSG_QUOTE(cp));
836          if(ap->a_content_id != NIL)
837             fprintf(fp, "content-id %s\n", ap->a_content_id->n_name);
838          if((cp = ap->a_content_type) != NIL)
839             fprintf(fp, "content-type %s\n", a_DMSG_QUOTE(cp));
840          if((cp = ap->a_content_disposition) != NIL)
841             fprintf(fp, "content-disposition %s\n", a_DMSG_QUOTE(cp));
842          cp = (putc('\n', fp) != EOF) ? su_empty : NIL;
843       }
844    }else if(su_cs_starts_with_case("attribute-at", cp)){
845       uz i;
846 
847       if(args == NIL || args->ca_next != NIL)
848          goto jecmd;
849 
850       if((su_idec_uz_cp(&i, cp = args->ca_arg.ca_str.s, 0, NIL
851                ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
852             ) != su_IDEC_STATE_CONSUMED || i == 0)
853          goto j505invpos;
854 
855       for(ap = hp->h_attach; ap != NIL && --i != 0; ap = ap->a_flink)
856          ;
857       if(ap != NIL)
858          goto jatt_att;
859       goto j501;
860    }else if(su_cs_starts_with_case("attribute-set", cp)){
861       /* ATT-ID KEYWORD VALUE */
862       if(args == NIL)
863          goto jecmd;
864 
865       cp = args->ca_arg.ca_str.s;
866       args = args->ca_next;
867 
868       if(args == NIL || args->ca_next == NIL || args->ca_next->ca_next != NIL)
869          goto jecmd;
870       if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY)
871          goto j505r;
872 
873       if((ap = mx_attachments_find(hp->h_attach, cp, NIL)) == NIL)
874          goto j501;
875 
876 jatt_attset:
877       if(ap->a_msgno > 0){
878          if(fprintf(fp, "505 RFC822 message attachment: %s\n", cp) < 0)
879             cp = NIL;
880       }else{
881          char c;
882          char const *keyw, *xcp;
883 
884          keyw = args->ca_arg.ca_str.s;
885          cp = args->ca_next->ca_arg.ca_str.s;
886 
887          for(xcp = cp; (c = *xcp) != '\0'; ++xcp)
888             if(su_cs_is_cntrl(c))
889                goto j505;
890          c = *cp;
891 
892          if(!su_cs_cmp_case(keyw, "filename"))
893             ap->a_name = (c == '\0') ? ap->a_path_bname : cp;
894          else if(!su_cs_cmp_case(keyw, "content-description"))
895             ap->a_content_description = (c == '\0') ? NIL : cp;
896          else if(!su_cs_cmp_case(keyw, "content-id")){
897             ap->a_content_id = NIL;
898 
899             if(c != '\0'){
900                struct mx_name *np;
901 
902                /* XXX lextract->extract_single() */
903                np = checkaddrs(lextract(cp, GREF),
904                      /*EACM_STRICT | TODO '/' valid!! */ EACM_NOLOG |
905                      EACM_NONAME, NIL);
906                if(np != NIL && np->n_flink == NIL)
907                   ap->a_content_id = np;
908                else
909                   cp = NIL;
910             }
911          }else if(!su_cs_cmp_case(keyw, "content-type")){
912             if((ap->a_content_type = (c == '\0') ? NIL : cp) != NIL){
913                char *cp2;
914 
915                for(cp2 = UNCONST(char*,cp); (c = *cp++) != '\0';)
916                   *cp2++ = su_cs_to_lower(c);
917 
918                if(!mx_mimetype_is_valid(ap->a_content_type, TRU1, FAL0)){
919                   ap->a_content_type = NIL;
920                   goto j505;
921                }
922             }
923          }else if(!su_cs_cmp_case(keyw, "content-disposition"))
924             ap->a_content_disposition = (c == '\0') ? NIL : cp;
925          else
926             cp = NIL;
927 
928          if(cp != NIL){
929             uz i;
930 
931             for(i = 0; ap != NIL; ++i, ap = ap->a_blink)
932                ;
933             if(fprintf(fp, "210 %" PRIuZ "\n", i) < 0)
934                cp = NIL;
935          }else{
936             cp = xcp;
937             goto j505; /* xxx jecmd; */
938          }
939       }
940    }else if(su_cs_starts_with_case("attribute-set-at", cp)){
941       uz i;
942 
943       cp = args->ca_arg.ca_str.s;
944       args = args->ca_next;
945 
946       if(args == NIL || args->ca_next == NIL || args->ca_next->ca_next != NIL)
947          goto jecmd;
948       if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY)
949          goto j505r;
950 
951       if((su_idec_uz_cp(&i, cp, 0, NIL
952                ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
953             ) != su_IDEC_STATE_CONSUMED || i == 0)
954          goto j505invpos;
955 
956       for(ap = hp->h_attach; ap != NIL && --i != 0; ap = ap->a_flink)
957          ;
958       if(ap != NIL)
959          goto jatt_attset;
960       goto j501;
961    }else if(su_cs_starts_with_case("insert", cp)){
962       BITENUM_IS(u32,mx_attach_error) aerr;
963 
964       if(args == NIL || args->ca_next != NIL)
965          goto jecmd;
966       if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY)
967          goto j505r;
968 
969       hp->h_attach = mx_attachments_append(hp->h_attach, args->ca_arg.ca_str.s,
970             &aerr, &ap);
971       switch(aerr){
972       case mx_ATTACHMENTS_ERR_FILE_OPEN: cp = "505"; goto jatt__ins;
973       case mx_ATTACHMENTS_ERR_ICONV_FAILED: cp = "506"; goto jatt__ins;
974       case mx_ATTACHMENTS_ERR_ICONV_NAVAIL: /* FALLTHRU */
975       case mx_ATTACHMENTS_ERR_OTHER: /* FALLTHRU */
976       default:
977          cp = "501";
978 jatt__ins:
979          if(fprintf(fp, "%s %s\n", cp, a_DMSG_QUOTE(args->ca_arg.ca_str.s)
980                ) < 0)
981             cp = NIL;
982          break;
983       case mx_ATTACHMENTS_ERR_NONE:{
984          uz i;
985 
986          for(i = 0; ap != NIL; ++i, ap = ap->a_blink)
987             ;
988          if(fprintf(fp, "210 %" PRIuZ "\n", i) < 0)
989             cp = NIL;
990          }break;
991       }
992    }else if(su_cs_starts_with_case("list", cp)){
993 jdefault:
994       if(args != NIL)
995          goto jecmd;
996 
997       if((ap = hp->h_attach) == NIL)
998          goto j501;
999 
1000       fputs("212\n", fp);
1001       do
1002          fprintf(fp, "%s\n", a_DMSG_QUOTE(ap->a_path_user));
1003       while((ap = ap->a_flink) != NIL);
1004       if(putc('\n', fp) == EOF)
1005          cp = NIL;
1006    }else if(su_cs_starts_with_case("remove", cp)){
1007       if(args == NIL || args->ca_next != NIL)
1008          goto jecmd;
1009       if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY)
1010          goto j505r;
1011 
1012       cp = args->ca_arg.ca_str.s;
1013       if((ap = mx_attachments_find(hp->h_attach, cp, &status)) == NIL)
1014          goto j501;
1015       if(status == TRUM1)
1016          goto j506;
1017 
1018       hp->h_attach = mx_attachments_remove(hp->h_attach, ap);
1019       if(fprintf(fp, "210 %s\n", a_DMSG_QUOTE(cp)) < 0)
1020          cp = NIL;
1021    }else if(su_cs_starts_with_case("remove-at", cp)){
1022       uz i;
1023 
1024       if(args == NIL || args->ca_next != NIL)
1025          goto jecmd;
1026       if(dmcp->dmc_flags & mx_DIG_MSG_RDONLY)
1027          goto j505r;
1028 
1029       if((su_idec_uz_cp(&i, cp = args->ca_arg.ca_str.s, 0, NIL
1030                ) & (su_IDEC_STATE_EMASK | su_IDEC_STATE_CONSUMED)
1031             ) != su_IDEC_STATE_CONSUMED || i == 0)
1032          goto j505invpos;
1033 
1034       for(ap = hp->h_attach; ap != NIL && --i != 0; ap = ap->a_flink)
1035          ;
1036       if(ap != NIL){
1037          hp->h_attach = mx_attachments_remove(hp->h_attach, ap);
1038          if(fprintf(fp, "210 %s\n", cp) < 0)
1039             cp = NIL;
1040       }else
1041          goto j501;
1042    }else
1043       goto jecmd;
1044 
1045 jleave:
1046    NYD2_OU;
1047    return (cp != NIL);
1048 
1049 jecmd:
1050    if(fputs("500\n", fp) == EOF)
1051       cp = NIL;
1052    cp = NIL;
1053    goto jleave;
1054 j501:
1055    if(fputs("501\n", fp) == EOF)
1056       cp = NIL;
1057    goto jleave;
1058 j505:
1059    if(fputs("505\n", fp) == EOF)
1060       cp = NIL;
1061    goto jleave;
1062 j505r:
1063    if(fprintf(fp, "505 read-only: %s\n", cp) < 0)
1064       cp = NIL;
1065    goto jleave;
1066 j505invpos:
1067    if(fprintf(fp, "505 invalid position: %s\n", cp) < 0)
1068       cp = NIL;
1069    goto jleave;
1070 j506:
1071    if(fputs("506\n", fp) == EOF)
1072       cp = NIL;
1073    goto jleave;
1074 }
1075 
1076 void
mx_dig_msg_on_mailbox_close(struct mailbox * mbp)1077 mx_dig_msg_on_mailbox_close(struct mailbox *mbp){ /* XXX HACK <- event! */
1078    struct mx_dig_msg_ctx *dmcp;
1079    NYD_IN;
1080 
1081    while((dmcp = mbp->mb_digmsg) != NIL){
1082       mbp->mb_digmsg = dmcp->dmc_next;
1083       if(dmcp->dmc_flags & mx_DIG_MSG_FCLOSE)
1084          fclose(dmcp->dmc_fp);
1085       if(dmcp->dmc_flags & mx_DIG_MSG_OWN_MEMBAG)
1086          su_mem_bag_gut(dmcp->dmc_membag);
1087       n_free(dmcp);
1088    }
1089    NYD_OU;
1090 }
1091 
1092 int
c_digmsg(void * vp)1093 c_digmsg(void *vp){
1094    char const *cp, *emsg;
1095    struct mx_dig_msg_ctx *dmcp;
1096    struct mx_cmd_arg *cap;
1097    struct mx_cmd_arg_ctx *cacp;
1098    NYD_IN;
1099 
1100    n_pstate_err_no = su_ERR_NONE;
1101    cacp = vp;
1102    cap = cacp->cac_arg;
1103 
1104    if(su_cs_starts_with_case("create", cp = cap->ca_arg.ca_str.s)){
1105       if(cacp->cac_no < 2 || cacp->cac_no > 3) /* XXX argparse is stupid */
1106          goto jesynopsis;
1107       cap = cap->ca_next;
1108 
1109       /* Request to use STDOUT? */
1110       if(cacp->cac_no == 3){
1111          cp = cap->ca_next->ca_arg.ca_str.s;
1112          if(*cp != '-' || cp[1] != '\0'){
1113             emsg = N_("digmsg: create: invalid I/O channel: %s\n");
1114             goto jeinval_quote;
1115          }
1116       }
1117 
1118       /* First of all, our context object */
1119       switch(a_dmsg_find(cp = cap->ca_arg.ca_str.s, &dmcp, TRU1)){
1120       case su_ERR_INVAL:
1121          emsg = N_("digmsg: create: message number invalid: %s\n");
1122          goto jeinval_quote;
1123       case su_ERR_EXIST:
1124          emsg = N_("digmsg: create: message object already exists: %s\n");
1125          goto jeinval_quote;
1126       default:
1127          break;
1128       }
1129 
1130       if(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE)
1131          dmcp->dmc_flags = mx_DIG_MSG_COMPOSE | mx_DIG_MSG_COMPOSE_DIGGED;
1132       else{
1133          FILE *fp;
1134 
1135          if((fp = setinput(&mb, dmcp->dmc_mp, NEED_HEADER)) == NIL){
1136             /* XXX Should have panicked before.. */
1137             n_free(dmcp);
1138             emsg = N_("digmsg: create: mailbox I/O error for message: %s\n");
1139             goto jeinval_quote;
1140          }
1141 
1142          su_mem_bag_push(n_go_data->gdc_membag, dmcp->dmc_membag);
1143          /* XXX n_header_extract error!! */
1144          n_header_extract((n_HEADER_EXTRACT_FULL |
1145                n_HEADER_EXTRACT_PREFILL_RECEIVERS |
1146                n_HEADER_EXTRACT_IGNORE_FROM_), fp, dmcp->dmc_hp, NIL);
1147          su_mem_bag_pop(n_go_data->gdc_membag, dmcp->dmc_membag);
1148       }
1149 
1150       if(cacp->cac_no == 3)
1151          dmcp->dmc_fp = n_stdout;
1152       /* For compose mode simply use FS_O_REGISTER, the number of dangling
1153        * deleted files with open descriptors until next fs_close_all()
1154        * should be very small; if this paradigm is changed
1155        * DIG_MSG_COMPOSE_GUT() needs to be adjusted */
1156       else if((dmcp->dmc_fp = mx_fs_tmp_open("digmsg", (mx_FS_O_RDWR |
1157                mx_FS_O_UNLINK | (dmcp->dmc_flags & mx_DIG_MSG_COMPOSE
1158                   ? mx_FS_O_REGISTER : 0)),
1159                NIL)) != NIL)
1160          dmcp->dmc_flags |= mx_DIG_MSG_HAVE_FP |
1161                (dmcp->dmc_flags & mx_DIG_MSG_COMPOSE ? 0 : mx_DIG_MSG_FCLOSE);
1162       else{
1163          n_err(_("digmsg: create: cannot create temporary file: %s\n"),
1164             su_err_doc(n_pstate_err_no = su_err_no()));
1165          vp = NIL;
1166          goto jeremove;
1167       }
1168 
1169       if(!(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE)){
1170          dmcp->dmc_last = NIL;
1171          if((dmcp->dmc_next = mb.mb_digmsg) != NIL)
1172             dmcp->dmc_next->dmc_last = dmcp;
1173          mb.mb_digmsg = dmcp;
1174       }
1175    }else if(su_cs_starts_with_case("remove", cp)){
1176       if(cacp->cac_no != 2)
1177          goto jesynopsis;
1178       cap = cap->ca_next;
1179 
1180       switch(a_dmsg_find(cp = cap->ca_arg.ca_str.s, &dmcp, FAL0)){
1181       case su_ERR_INVAL:
1182          emsg = N_("digmsg: remove: message number invalid: %s\n");
1183          goto jeinval_quote;
1184       default:
1185          if(!(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE) ||
1186                (dmcp->dmc_flags & mx_DIG_MSG_COMPOSE_DIGGED))
1187             break;
1188          /* FALLTHRU */
1189       case su_ERR_NOENT:
1190          emsg = N_("digmsg: remove: no such message object: %s\n");
1191          goto jeinval_quote;
1192       }
1193 
1194       if(!(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE)){
1195          if(dmcp->dmc_last != NIL)
1196             dmcp->dmc_last->dmc_next = dmcp->dmc_next;
1197          else{
1198             ASSERT(dmcp == mb.mb_digmsg);
1199             mb.mb_digmsg = dmcp->dmc_next;
1200          }
1201          if(dmcp->dmc_next != NIL)
1202             dmcp->dmc_next->dmc_last = dmcp->dmc_last;
1203       }
1204 
1205       if((dmcp->dmc_flags & mx_DIG_MSG_HAVE_FP) &&
1206             mx_dig_msg_read_overlay == dmcp)
1207          mx_dig_msg_read_overlay = NIL;
1208 
1209       if(dmcp->dmc_flags & mx_DIG_MSG_FCLOSE)
1210          fclose(dmcp->dmc_fp);
1211 jeremove:
1212       if(dmcp->dmc_flags & mx_DIG_MSG_OWN_MEMBAG)
1213          su_mem_bag_gut(dmcp->dmc_membag);
1214 
1215       if(dmcp->dmc_flags & mx_DIG_MSG_COMPOSE)
1216          dmcp->dmc_flags = mx_DIG_MSG_COMPOSE;
1217       else
1218          n_free(dmcp);
1219    }else{
1220       switch(a_dmsg_find(cp, &dmcp, FAL0)){
1221       case su_ERR_INVAL:
1222          emsg = N_("digmsg: message number invalid: %s\n");
1223          goto jeinval_quote;
1224       case su_ERR_NOENT:
1225          emsg = N_("digmsg: no such message object: %s\n");
1226          goto jeinval_quote;
1227       default:
1228          break;
1229       }
1230       cap = cap->ca_next;
1231 
1232       if(dmcp->dmc_flags & mx_DIG_MSG_HAVE_FP){
1233          rewind(dmcp->dmc_fp);
1234          ftruncate(fileno(dmcp->dmc_fp), 0);
1235       }
1236 
1237       su_mem_bag_push(n_go_data->gdc_membag, dmcp->dmc_membag);
1238       if(!a_dmsg_cmd(dmcp->dmc_fp, dmcp, cap,
1239             ((cap != NIL) ? cap->ca_next : NIL)))
1240          vp = NIL;
1241       su_mem_bag_pop(n_go_data->gdc_membag, dmcp->dmc_membag);
1242 
1243       if(dmcp->dmc_flags & mx_DIG_MSG_HAVE_FP){
1244          rewind(dmcp->dmc_fp);
1245          /* This will be reset by go_input() _if_ we read to EOF */
1246          mx_dig_msg_read_overlay = dmcp;
1247       }
1248    }
1249 
1250 jleave:
1251    NYD_OU;
1252    return (vp == NIL);
1253 
1254 jesynopsis:
1255    mx_cmd_print_synopsis(mx_cmd_firstfit("digmsg"), NIL);
1256    goto jeinval;
1257 jeinval_quote:
1258    emsg = V_(emsg);
1259    n_err(emsg, n_shexp_quote_cp(cp, FAL0));
1260 jeinval:
1261    n_pstate_err_no = su_ERR_INVAL;
1262    vp = NIL;
1263    goto jleave;
1264 }
1265 
1266 boole
mx_dig_msg_circumflex(struct mx_dig_msg_ctx * dmcp,FILE * fp,char const * cmd)1267 mx_dig_msg_circumflex(struct mx_dig_msg_ctx *dmcp, FILE *fp, char const *cmd){
1268    /* Identical to (subset of) c_digmsg() cmd-tab */
1269    mx_CMD_ARG_DESC_SUBCLASS_DEF_NAME(dm, "digmsg", 5, pseudo_cad){
1270       {mx_CMD_ARG_DESC_SHEXP | mx_CMD_ARG_DESC_HONOUR_STOP,
1271          n_SHEXP_PARSE_IGNORE_EMPTY | n_SHEXP_PARSE_TRIM_IFSSPACE},
1272       {mx_CMD_ARG_DESC_SHEXP | mx_CMD_ARG_DESC_OPTION |
1273             mx_CMD_ARG_DESC_HONOUR_STOP,
1274          n_SHEXP_PARSE_TRIM_IFSSPACE}, /* arg1 */
1275       {mx_CMD_ARG_DESC_SHEXP | mx_CMD_ARG_DESC_OPTION |
1276             mx_CMD_ARG_DESC_HONOUR_STOP,
1277          n_SHEXP_PARSE_TRIM_IFSSPACE}, /* arg2 */
1278       {mx_CMD_ARG_DESC_SHEXP | mx_CMD_ARG_DESC_OPTION |
1279             mx_CMD_ARG_DESC_HONOUR_STOP,
1280          n_SHEXP_PARSE_TRIM_IFSSPACE}, /* arg3 */
1281       {mx_CMD_ARG_DESC_SHEXP | mx_CMD_ARG_DESC_OPTION |
1282             mx_CMD_ARG_DESC_HONOUR_STOP |
1283             mx_CMD_ARG_DESC_GREEDY | mx_CMD_ARG_DESC_GREEDY_JOIN,
1284          n_SHEXP_PARSE_TRIM_IFSSPACE} /* arg4 */
1285    }mx_CMD_ARG_DESC_SUBCLASS_DEF_END;
1286 
1287    struct mx_cmd_arg_ctx cac;
1288    boole rv;
1289    NYD_IN;
1290 
1291    cac.cac_desc = mx_CMD_ARG_DESC_SUBCLASS_CAST(&pseudo_cad);
1292    cac.cac_indat = cmd;
1293    cac.cac_inlen = UZ_MAX;
1294    cac.cac_msgflag = cac.cac_msgmask = 0;
1295 
1296    if((rv = mx_cmd_arg_parse(&cac)))
1297       rv = a_dmsg_cmd(fp, dmcp, cac.cac_arg, cac.cac_arg->ca_next);
1298 
1299    NYD_OU;
1300    return rv;
1301 }
1302 
1303 #include "su/code-ou.h"
1304 /* s-it-mode */
1305