THANKS: Predrag Punosevac
[s-mailx.git] / cmd3.c
bloba1cbde8a5047e9adef71c34ddf1d1d20eef4332b
1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Still more user commands.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 - 2015 Steffen (Daode) Nurpmeso <sdaoden@users.sf.net>.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. Neither the name of the University nor the names of its contributors
20 * may be used to endorse or promote products derived from this software
21 * without specific prior written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
26 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
35 #undef n_FILE
36 #define n_FILE cmd3
38 #ifndef HAVE_AMALGAMATION
39 # include "nail.h"
40 #endif
42 static char *_bang_buf;
43 static size_t _bang_size;
45 /* Modify subject we reply to to begin with Re: if it does not already */
46 static char * _reedit(char *subj);
48 /* Expand the shell escape by expanding unescaped !'s into the last issued
49 * command where possible */
50 static void _bangexp(char **str, size_t *size);
52 static void make_ref_and_cs(struct message *mp, struct header *head);
54 /* `reply' and `Lreply' workhorse */
55 static int _list_reply(int *msgvec, enum header_flags hf);
57 /* Get PTF to implementation of command c (i.e., take care for *flipr*) */
58 static int (* _reply_or_Reply(char c))(int *, bool_t);
60 /* Reply to a single message. Extract each name from the message header and
61 * send them off to mail1() */
62 static int _reply(int *msgvec, bool_t recipient_record);
64 /* Reply to a series of messages by simply mailing to the senders and not
65 * messing around with the To: and Cc: lists as in normal reply */
66 static int _Reply(int *msgvec, bool_t recipient_record);
68 /* Forward a message to a new recipient, in the sense of RFC 2822 */
69 static int _fwd(char *str, int recipient_record);
71 /* Modify the subject we are replying to to begin with Fwd: */
72 static char * __fwdedit(char *subj);
74 /* Do the real work of resending */
75 static int _resend1(void *v, bool_t add_resent);
77 /* c_file, c_File */
78 static int _c_file(void *v, enum fedit_mode fm);
80 static char *
81 _reedit(char *subj)
83 struct str in, out;
84 char *newsubj = NULL;
85 NYD_ENTER;
87 if (subj == NULL || *subj == '\0')
88 goto jleave;
90 in.s = subj;
91 in.l = strlen(subj);
92 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
94 if ((newsubj = subject_re_trim(out.s)) != out.s)
95 newsubj = savestr(out.s);
96 else {
97 /* RFC mandates english "Re: " */
98 newsubj = salloc(out.l + 4 +1);
99 sstpcpy(sstpcpy(newsubj, "Re: "), out.s);
102 free(out.s);
103 jleave:
104 NYD_LEAVE;
105 return newsubj;
108 static void
109 _bangexp(char **str, size_t *size)
111 char *bangbuf;
112 int changed = 0;
113 bool_t dobang;
114 size_t sz, i, j, bangbufsize;
115 NYD_ENTER;
117 dobang = ok_blook(bang);
119 bangbuf = smalloc(bangbufsize = *size);
120 i = j = 0;
121 while ((*str)[i]) {
122 if (dobang) {
123 if ((*str)[i] == '!') {
124 sz = strlen(_bang_buf);
125 bangbuf = srealloc(bangbuf, bangbufsize += sz);
126 ++changed;
127 memcpy(bangbuf + j, _bang_buf, sz + 1);
128 j += sz;
129 i++;
130 continue;
133 if ((*str)[i] == '\\' && (*str)[i + 1] == '!') {
134 bangbuf[j++] = '!';
135 i += 2;
136 ++changed;
138 bangbuf[j++] = (*str)[i++];
140 bangbuf[j] = '\0';
141 if (changed) {
142 printf("!%s\n", bangbuf);
143 fflush(stdout);
145 sz = j + 1;
146 if (sz > *size)
147 *str = srealloc(*str, *size = sz);
148 memcpy(*str, bangbuf, sz);
149 if (sz > _bang_size)
150 _bang_buf = srealloc(_bang_buf, _bang_size = sz);
151 memcpy(_bang_buf, bangbuf, sz);
152 free(bangbuf);
153 NYD_LEAVE;
156 static void
157 make_ref_and_cs(struct message *mp, struct header *head) /* TODO rewrite FAST */
159 char *oldref, *oldmsgid, *newref, *cp;
160 size_t oldreflen = 0, oldmsgidlen = 0, reflen;
161 unsigned i;
162 struct name *n;
163 NYD_ENTER;
165 oldref = hfield1("references", mp);
166 oldmsgid = hfield1("message-id", mp);
167 if (oldmsgid == NULL || *oldmsgid == '\0') {
168 head->h_ref = NULL;
169 goto jleave;
172 reflen = 1;
173 if (oldref) {
174 oldreflen = strlen(oldref);
175 reflen += oldreflen + 2;
177 if (oldmsgid) {
178 oldmsgidlen = strlen(oldmsgid);
179 reflen += oldmsgidlen;
182 newref = smalloc(reflen);
183 if (oldref != NULL) {
184 memcpy(newref, oldref, oldreflen +1);
185 if (oldmsgid != NULL) {
186 newref[oldreflen++] = ',';
187 newref[oldreflen++] = ' ';
188 memcpy(newref + oldreflen, oldmsgid, oldmsgidlen +1);
190 } else if (oldmsgid)
191 memcpy(newref, oldmsgid, oldmsgidlen +1);
192 n = extract(newref, GREF);
193 free(newref);
195 /* Limit number of references TODO better on parser side */
196 while (n->n_flink != NULL)
197 n = n->n_flink;
198 for (i = 1; i <= REFERENCES_MAX; ++i) {
199 if (n->n_blink != NULL)
200 n = n->n_blink;
201 else
202 break;
204 n->n_blink = NULL;
205 head->h_ref = n;
206 if (ok_blook(reply_in_same_charset) &&
207 (cp = hfield1("content-type", mp)) != NULL)
208 head->h_charset = mime_param_get("charset", cp);
209 jleave:
210 NYD_LEAVE;
213 static int
214 _list_reply(int *msgvec, enum header_flags hf)
216 struct header head;
217 struct message *mp;
218 char const *reply_to, *rcv, *cp;
219 enum gfield gf;
220 struct name *rt, *mft, *np;
221 int *save_msgvec;
222 NYD_ENTER;
224 /* TODO Since we may recur and do stuff with message lists we need to save
225 * TODO away the argument vector as long as that isn't done by machinery */
227 size_t i;
228 for (i = 0; msgvec[i] != 0; ++i)
230 ++i;
231 save_msgvec = ac_alloc(sizeof(*save_msgvec) * i);
232 while (i-- > 0)
233 save_msgvec[i] = msgvec[i];
234 msgvec = save_msgvec;
237 jnext_msg:
238 mp = message + *msgvec - 1;
239 touch(mp);
240 setdot(mp);
242 memset(&head, 0, sizeof head);
243 head.h_flags = hf;
245 head.h_subject = _reedit(hfield1("subject", mp));
246 gf = ok_blook(fullnames) ? GFULL : GSKIN;
247 rt = mft = NULL;
249 rcv = NULL;
250 if ((reply_to = hfield1("reply-to", mp)) != NULL &&
251 (cp = ok_vlook(reply_to_honour)) != NULL &&
252 (rt = checkaddrs(lextract(reply_to, GTO | gf), EACM_STRICT, NULL)
253 ) != NULL) {
254 char const *tr = _("Reply-To \"%s%s\"");
255 size_t l = strlen(tr) + strlen(rt->n_name) + 3 +1;
256 char *sp = salloc(l);
258 snprintf(sp, l, tr, rt->n_name, (rt->n_flink != NULL ? "..." : ""));
259 if (quadify(cp, UIZ_MAX, sp, TRU1) > FAL0)
260 rcv = reply_to;
263 if (rcv == NULL && (rcv = hfield1("from", mp)) == NULL)
264 rcv = nameof(mp, 1);
266 /* Cc: */
267 np = NULL;
268 if (ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
269 np = lextract(cp, GCC | gf);
270 if ((cp = hfield1("cc", mp)) != NULL)
271 np = cat(np, lextract(cp, GCC | gf));
272 if (np != NULL)
273 head.h_cc = delete_alternates(np);
275 /* To: */
276 np = NULL;
277 if (rcv != NULL)
278 np = (rcv == reply_to) ? namelist_dup(rt, GTO | gf)
279 : lextract(rcv, GTO | gf);
280 if (!ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
281 np = cat(np, lextract(cp, GTO | gf));
282 /* Delete my name from reply list, and with it, all my alternate names */
283 np = delete_alternates(np);
284 if (count(np) == 0)
285 np = lextract(rcv, GTO | gf);
286 head.h_to = np;
288 /* The user may have send this to himself, don't ignore that */
289 namelist_vaporise_head(&head, EACM_NORMAL, FAL0, NULL);
290 if (head.h_to == NULL)
291 head.h_to = np;
293 /* Mail-Followup-To: */
294 mft = NULL;
295 if (ok_vlook(followup_to_honour) != NULL &&
296 (cp = hfield1("mail-followup-to", mp)) != NULL &&
297 (mft = np = checkaddrs(lextract(cp, GTO | gf), EACM_STRICT, NULL)
298 ) != NULL) {
299 char const *tr = _("Followup-To \"%s%s\"");
300 size_t l = strlen(tr) + strlen(np->n_name) + 3 +1;
301 char *sp = salloc(l);
303 snprintf(sp, l, tr, np->n_name, (np->n_flink != NULL ? "..." : ""));
304 if (quadify(ok_vlook(followup_to_honour), UIZ_MAX, sp, TRU1) > FAL0) {
305 head.h_cc = NULL;
306 head.h_to = np;
307 head.h_mft =
308 mft = namelist_vaporise_head(&head, EACM_STRICT, FAL0, NULL);
309 } else
310 mft = NULL;
313 /* Special massage for list (follow-up) messages */
314 if (mft != NULL || (hf & HF_LIST_REPLY) || ok_blook(followup_to)) {
315 /* Learn about a possibly sending mailing list; use do for break; */
316 if ((cp = hfield1("list-post", mp)) != NULL) do {
317 struct name *x = lextract(cp, GEXTRA | GSKIN);
318 if (x == NULL || x->n_flink != NULL ||
319 !is_prefix("mailto:", x->n_name) ||
320 /* XXX the mailto: prefix causes failure (":" invalid character)
321 * XXX which is why need to recreate a struct name with an
322 * XXX updated name; this is terribly wasteful and can't we find
323 * XXX a way to mitigate that?? */
324 is_addr_invalid(x = nalloc(x->n_name + sizeof("mailto:") -1,
325 GEXTRA | GSKIN), EACM_STRICT)) {
326 if (options & OPT_D_V)
327 n_err(_("Message contains invalid \"List-Post:\" header\n"));
328 cp = NULL;
329 break;
331 cp = x->n_name;
333 /* A special case has been seen on e.g. ietf-announce@ietf.org:
334 * these usually post to multiple groups, with ietf-announce@
335 * in List-Post:, but with Reply-To: set to ietf@ietf.org (since
336 * -announce@ is only used for announcements, say).
337 * So our desire is to honour this request and actively overwrite
338 * List-Post: for our purpose; but only if its a single address.
339 * However, to avoid ambiguities with users that place themselve in
340 * Reply-To: and mailing lists which don't overwrite this (or only
341 * extend this, shall such exist), only do so if reply_to exists of
342 * a single address which points to the same domain as List-Post: */
343 if (reply_to != NULL && rt->n_flink == NULL &&
344 name_is_same_domain(x, rt))
345 cp = rt->n_name; /* rt is EACM_STRICT tested */
347 /* "Automatically `mlist'" the List-Post: address temporarily */
348 if (is_mlist(cp, FAL0) == MLIST_OTHER)
349 head.h_list_post = cp;
350 else
351 cp = NULL;
352 } while (0);
354 /* In case of list replies we actively sort out any non-list recipient,
355 * but _only_ if we did not honour a MFT:, assuming that members of MFT
356 * were there for a reason; cp is still List-Post:/eqivalent */
357 if ((hf & HF_LIST_REPLY) && mft == NULL) {
358 struct name *nhp = head.h_to;
359 head.h_to = NULL;
360 j_lt_redo:
361 while (nhp != NULL) {
362 np = nhp;
363 nhp = nhp->n_flink;
365 if ((cp != NULL && !asccasecmp(cp, np->n_name)) ||
366 is_mlist(np->n_name, FAL0) != MLIST_OTHER) {
367 np->n_type = (np->n_type & ~GMASK) | GTO;
368 np->n_flink = head.h_to;
369 head.h_to = np;
372 if ((nhp = head.h_cc) != NULL) {
373 head.h_cc = NULL;
374 goto j_lt_redo;
379 make_ref_and_cs(mp, &head);
381 if (ok_blook(quote_as_attachment)) {
382 head.h_attach = csalloc(1, sizeof *head.h_attach);
383 head.h_attach->a_msgno = *msgvec;
384 head.h_attach->a_content_description = _("Original message content");
387 if (mail1(&head, 1, mp, NULL, !!(hf & HF_RECIPIENT_RECORD), 0) == OKAY &&
388 ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
389 mp->m_flag |= MANSWER | MANSWERED;
391 if (*++msgvec != 0) {
392 /* TODO message (error) ring.., less sleep */
393 printf(_("Waiting a second before proceeding to the next message..\n"));
394 fflush(stdout);
395 sleep(1);
396 goto jnext_msg;
399 ac_free(save_msgvec);
400 NYD_LEAVE;
401 return 0;
404 static int
405 (*_reply_or_Reply(char c))(int *, bool_t)
407 int (*rv)(int*, bool_t);
408 NYD_ENTER;
410 rv = (ok_blook(flipr) ^ (c == 'R')) ? &_Reply : &_reply;
411 NYD_LEAVE;
412 return rv;
415 static int
416 _reply(int *msgvec, bool_t recipient_record)
418 int rv;
419 NYD_ENTER;
421 rv = _list_reply(msgvec, recipient_record ? HF_RECIPIENT_RECORD : HF_NONE);
422 NYD_LEAVE;
423 return rv;
426 static int
427 _Reply(int *msgvec, bool_t recipient_record)
429 struct header head;
430 struct message *mp;
431 int *ap;
432 char *cp;
433 enum gfield gf;
434 NYD_ENTER;
436 memset(&head, 0, sizeof head);
437 gf = ok_blook(fullnames) ? GFULL : GSKIN;
439 for (ap = msgvec; *ap != 0; ++ap) {
440 char const *rp;
441 struct name *rt;
443 mp = message + *ap - 1;
444 touch(mp);
445 setdot(mp);
447 if ((rp = hfield1("reply-to", mp)) != NULL &&
448 (cp = ok_vlook(reply_to_honour)) != NULL &&
449 (rt = checkaddrs(lextract(rp, GTO | gf), EACM_STRICT, NULL)
450 ) != NULL) {
451 char const *tr = _("Reply-To \"%s%s\"");
452 size_t l = strlen(tr) + strlen(rt->n_name) + 3 +1;
453 char *sp = salloc(l);
455 snprintf(sp, l, tr, rt->n_name, (rt->n_flink != NULL ? "..." : ""));
456 if (quadify(cp, UIZ_MAX, sp, TRU1) > FAL0) {
457 head.h_to = cat(head.h_to, rt);
458 continue;
462 if ((cp = hfield1("from", mp)) == NULL)
463 cp = nameof(mp, 2);
464 head.h_to = cat(head.h_to, lextract(cp, GTO | gf));
466 if (head.h_to == NULL)
467 goto jleave;
469 mp = message + msgvec[0] - 1;
470 head.h_subject = hfield1("subject", mp);
471 head.h_subject = _reedit(head.h_subject);
472 make_ref_and_cs(mp, &head);
474 if (ok_blook(quote_as_attachment)) {
475 head.h_attach = csalloc(1, sizeof *head.h_attach);
476 head.h_attach->a_msgno = *msgvec;
477 head.h_attach->a_content_description = _("Original message content");
480 if (mail1(&head, 1, mp, NULL, recipient_record, 0) == OKAY &&
481 ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
482 mp->m_flag |= MANSWER | MANSWERED;
483 jleave:
484 NYD_LEAVE;
485 return 0;
488 static int
489 _fwd(char *str, int recipient_record)
491 struct header head;
492 int *msgvec, rv = 1;
493 char *recipient;
494 struct message *mp;
495 bool_t f, forward_as_attachment;
496 NYD_ENTER;
498 if ((recipient = laststring(str, &f, TRU1)) == NULL) {
499 puts(_("No recipient specified."));
500 goto jleave;
503 forward_as_attachment = ok_blook(forward_as_attachment);
504 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
506 if (!f) {
507 *msgvec = first(0, MMNORM);
508 if (*msgvec == 0) {
509 if (pstate & PS_HOOK_MASK) {
510 rv = 0;
511 goto jleave;
513 printf(_("No messages to forward.\n"));
514 goto jleave;
516 msgvec[1] = 0;
517 } else if (getmsglist(str, msgvec, 0) < 0)
518 goto jleave;
520 if (*msgvec == 0) {
521 if (pstate & PS_HOOK_MASK) {
522 rv = 0;
523 goto jleave;
525 printf(_("No applicable messages.\n"));
526 goto jleave;
528 if (msgvec[1] != 0) {
529 printf(_("Cannot forward multiple messages at once\n"));
530 goto jleave;
533 memset(&head, 0, sizeof head);
534 if ((head.h_to = lextract(recipient,
535 (GTO | (ok_blook(fullnames) ? GFULL : GSKIN)))) == NULL)
536 goto jleave;
538 mp = message + *msgvec - 1;
540 if (forward_as_attachment) {
541 head.h_attach = csalloc(1, sizeof *head.h_attach);
542 head.h_attach->a_msgno = *msgvec;
543 head.h_attach->a_content_description = _("Forwarded message");
544 } else {
545 touch(mp);
546 setdot(mp);
548 head.h_subject = hfield1("subject", mp);
549 head.h_subject = __fwdedit(head.h_subject);
550 mail1(&head, 1, (forward_as_attachment ? NULL : mp), NULL, recipient_record,
552 rv = 0;
553 jleave:
554 NYD_LEAVE;
555 return rv;
558 static char *
559 __fwdedit(char *subj)
561 struct str in, out;
562 char *newsubj = NULL;
563 NYD_ENTER;
565 if (subj == NULL || *subj == '\0')
566 goto jleave;
568 in.s = subj;
569 in.l = strlen(subj);
570 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
572 newsubj = salloc(out.l + 6);
573 memcpy(newsubj, "Fwd: ", 5); /* XXX localizable */
574 memcpy(newsubj + 5, out.s, out.l +1);
575 free(out.s);
576 jleave:
577 NYD_LEAVE;
578 return newsubj;
581 static int
582 _resend1(void *v, bool_t add_resent)
584 char *name, *str;
585 struct name *to, *sn;
586 int *ip, *msgvec;
587 bool_t f = TRU1;
588 NYD_ENTER;
590 str = v;
591 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
592 name = laststring(str, &f, TRU1);
593 if (name == NULL) {
594 puts(_("No recipient specified."));
595 goto jleave;
598 if (!f) {
599 *msgvec = first(0, MMNORM);
600 if (*msgvec == 0) {
601 if (pstate & PS_HOOK_MASK) {
602 f = FAL0;
603 goto jleave;
605 puts(_("No applicable messages."));
606 goto jleave;
608 msgvec[1] = 0;
609 } else if (getmsglist(str, msgvec, 0) < 0)
610 goto jleave;
612 if (*msgvec == 0) {
613 if (pstate & PS_HOOK_MASK) {
614 f = FAL0;
615 goto jleave;
617 printf("No applicable messages.\n");
618 goto jleave;
621 sn = nalloc(name, GTO | GSKIN);
622 to = usermap(sn, FAL0);
623 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
624 ++ip)
625 if (resend_msg(message + *ip - 1, to, add_resent) != OKAY)
626 goto jleave;
627 f = FAL0;
628 jleave:
629 NYD_LEAVE;
630 return (f != FAL0);
633 static int
634 _c_file(void *v, enum fedit_mode fm)
636 char **argv = v;
637 int i;
638 NYD2_ENTER;
640 if (*argv == NULL) {
641 newfileinfo();
642 i = 0;
643 goto jleave;
646 if (pstate & PS_HOOK_MASK) {
647 n_err(_("Cannot change folder from within a hook\n"));
648 i = 1;
649 goto jleave;
652 save_mbox_for_possible_quitstuff();
654 i = setfile(*argv, fm);
655 if (i < 0) {
656 i = 1;
657 goto jleave;
659 assert(!(fm & FEDIT_NEWMAIL));
660 check_folder_hook(FAL0);
662 if (i > 0 && !ok_blook(emptystart)) {
663 i = 1;
664 goto jleave;
666 announce(ok_blook(bsdcompat) || ok_blook(bsdannounce));
667 i = 0;
668 jleave:
669 NYD2_LEAVE;
670 return i;
673 FL int
674 c_shell(void *v)
676 char const *sh = NULL;
677 char *str = v, *cmd;
678 size_t cmdsize;
679 sigset_t mask;
680 sighandler_type sigint;
681 NYD_ENTER;
683 cmd = smalloc(cmdsize = strlen(str) +1);
684 memcpy(cmd, str, cmdsize);
685 _bangexp(&cmd, &cmdsize);
686 if ((sh = ok_vlook(SHELL)) == NULL)
687 sh = XSHELL;
689 sigint = safe_signal(SIGINT, SIG_IGN);
690 sigemptyset(&mask);
691 run_command(sh, &mask, COMMAND_FD_PASS, COMMAND_FD_PASS, "-c", cmd, NULL,
692 NULL);
693 safe_signal(SIGINT, sigint);
694 printf("!\n");
696 free(cmd);
697 NYD_LEAVE;
698 return 0;
701 FL int
702 c_dosh(void *v)
704 sighandler_type sigint;
705 char const *sh;
706 NYD_ENTER;
707 UNUSED(v);
709 if ((sh = ok_vlook(SHELL)) == NULL)
710 sh = XSHELL;
712 sigint = safe_signal(SIGINT, SIG_IGN);
713 run_command(sh, 0, COMMAND_FD_PASS, COMMAND_FD_PASS, NULL, NULL, NULL, NULL);
714 safe_signal(SIGINT, sigint);
715 putchar('\n');
716 NYD_LEAVE;
717 return 0;
720 FL int
721 c_help(void *v)
723 int ret = 0;
724 char *arg;
725 NYD_ENTER;
727 arg = *(char**)v;
729 if (arg != NULL) {
730 #ifdef HAVE_DOCSTRINGS
731 ret = !print_comm_docstr(arg);
732 if (ret)
733 n_err(_("Unknown command: `%s'\n"), arg);
734 #else
735 ret = c_cmdnotsupp(NULL);
736 #endif
737 goto jleave;
740 /* Very ugly, but take care for compiler supported string lengths :( */
741 fputs(progname, stdout);
742 fputs(_(
743 " commands -- \"<msglist>\" denotes message specifications,\n"
744 "e.g., \"1-5\", \":n\" or \".\", separated by spaces:\n"), stdout);
745 fputs(_(
746 "\n"
747 "type <msglist> type (alias: `print') messages (honour `retain' etc.)\n"
748 "Type <msglist> like `type' but always show all headers\n"
749 "next goto and type next message\n"
750 "from <msglist> print header summary for the given list (\"search\")\n"
751 "headers header summary for messages surrounding \"dot\"\n"
752 "delete <msglist> delete messages (can be `undelete'd)\n"),
753 stdout);
755 fputs(_(
756 "\n"
757 "save <msglist> folder append messages to folder and mark as saved\n"
758 "copy <msglist> folder like `save', but don't mark them (`move' moves)\n"
759 "write <msglist> file write message contents to file (prompts for parts)\n"
760 "Reply <msglist> reply to message senders only\n"
761 "reply <msglist> like `Reply', but address all recipients\n"
762 "Lreply <msglist> forced mailing-list `reply' (see `mlist')\n"),
763 stdout);
765 fputs(_(
766 "\n"
767 "mail <recipients> compose a mail for the given recipients\n"
768 "file folder change to another mailbox\n"
769 "File folder like `file', but open readonly\n"
770 "quit quit and apply changes to the current mailbox\n"
771 "xit or exit like `quit', but discard changes\n"
772 "!shell command shell escape\n"
773 "list list names of all available commands\n"),
774 stdout);
776 jleave:
777 NYD_LEAVE;
778 return ret;
781 FL int
782 c_cwd(void *v)
784 char buf[PATH_MAX]; /* TODO getcwd(3) may return a larger value */
785 NYD_ENTER;
787 if (getcwd(buf, sizeof buf) != NULL) {
788 puts(buf);
789 v = (void*)0x1;
790 } else {
791 n_perr(_("getcwd"), 0);
792 v = NULL;
794 NYD_LEAVE;
795 return (v == NULL);
798 FL int
799 c_chdir(void *v)
801 char **arglist = v;
802 char const *cp;
803 NYD_ENTER;
805 if (*arglist == NULL)
806 cp = homedir;
807 else if ((cp = file_expand(*arglist)) == NULL)
808 goto jleave;
809 if (chdir(cp) == -1) {
810 n_perr(cp, 0);
811 cp = NULL;
813 jleave:
814 NYD_LEAVE;
815 return (cp == NULL);
818 FL int
819 c_reply(void *v)
821 int rv;
822 NYD_ENTER;
824 rv = (*_reply_or_Reply('r'))(v, FAL0);
825 NYD_LEAVE;
826 return rv;
829 FL int
830 c_replyall(void *v)
832 int rv;
833 NYD_ENTER;
835 rv = _reply(v, FAL0);
836 NYD_LEAVE;
837 return rv;
840 FL int
841 c_replysender(void *v)
843 int rv;
844 NYD_ENTER;
846 rv = _Reply(v, FAL0);
847 NYD_LEAVE;
848 return rv;
851 FL int
852 c_Reply(void *v)
854 int rv;
855 NYD_ENTER;
857 rv = (*_reply_or_Reply('R'))(v, FAL0);
858 NYD_LEAVE;
859 return rv;
862 FL int
863 c_Lreply(void *v)
865 int rv;
866 NYD_ENTER;
868 rv = _list_reply(v, HF_LIST_REPLY);
869 NYD_LEAVE;
870 return rv;
873 FL int
874 c_followup(void *v)
876 int rv;
877 NYD_ENTER;
879 rv = (*_reply_or_Reply('r'))(v, TRU1);
880 NYD_LEAVE;
881 return rv;
884 FL int
885 c_followupall(void *v)
887 int rv;
888 NYD_ENTER;
890 rv = _reply(v, TRU1);
891 NYD_LEAVE;
892 return rv;
895 FL int
896 c_followupsender(void *v)
898 int rv;
899 NYD_ENTER;
901 rv = _Reply(v, TRU1);
902 NYD_LEAVE;
903 return rv;
906 FL int
907 c_Followup(void *v)
909 int rv;
910 NYD_ENTER;
912 rv = (*_reply_or_Reply('R'))(v, TRU1);
913 NYD_LEAVE;
914 return rv;
917 FL int
918 c_forward(void *v)
920 int rv;
921 NYD_ENTER;
923 rv = _fwd(v, 0);
924 NYD_LEAVE;
925 return rv;
928 FL int
929 c_Forward(void *v)
931 int rv;
932 NYD_ENTER;
934 rv = _fwd(v, 1);
935 NYD_LEAVE;
936 return rv;
939 FL int
940 c_resend(void *v)
942 int rv;
943 NYD_ENTER;
945 rv = _resend1(v, TRU1);
946 NYD_LEAVE;
947 return rv;
950 FL int
951 c_Resend(void *v)
953 int rv;
954 NYD_ENTER;
956 rv = _resend1(v, FAL0);
957 NYD_LEAVE;
958 return rv;
961 FL int
962 c_preserve(void *v)
964 int *msgvec = v, *ip, mesg, rv = 1;
965 struct message *mp;
966 NYD_ENTER;
968 if (pstate & PS_EDIT) {
969 printf(_("Cannot \"preserve\" in a system mailbox\n"));
970 goto jleave;
973 for (ip = msgvec; *ip != 0; ++ip) {
974 mesg = *ip;
975 mp = message + mesg - 1;
976 mp->m_flag |= MPRESERVE;
977 mp->m_flag &= ~MBOX;
978 setdot(mp);
979 pstate |= PS_DID_PRINT_DOT;
981 rv = 0;
982 jleave:
983 NYD_LEAVE;
984 return rv;
987 FL int
988 c_unread(void *v)
990 int *msgvec = v, *ip;
991 NYD_ENTER;
993 for (ip = msgvec; *ip != 0; ++ip) {
994 setdot(message + *ip - 1);
995 dot->m_flag &= ~(MREAD | MTOUCH);
996 dot->m_flag |= MSTATUS;
997 #ifdef HAVE_IMAP
998 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
999 imap_unread(message + *ip - 1, *ip); /* TODO return? */
1000 #endif
1001 pstate |= PS_DID_PRINT_DOT;
1003 NYD_LEAVE;
1004 return 0;
1007 FL int
1008 c_seen(void *v)
1010 int *msgvec = v, *ip;
1011 NYD_ENTER;
1013 for (ip = msgvec; *ip != 0; ++ip) {
1014 struct message *mp = message + *ip - 1;
1015 setdot(mp);
1016 touch(mp);
1018 NYD_LEAVE;
1019 return 0;
1022 FL int
1023 c_messize(void *v)
1025 int *msgvec = v, *ip, mesg;
1026 struct message *mp;
1027 NYD_ENTER;
1029 for (ip = msgvec; *ip != 0; ++ip) {
1030 mesg = *ip;
1031 mp = message + mesg - 1;
1032 printf("%d: ", mesg);
1033 if (mp->m_xlines > 0)
1034 printf("%ld", mp->m_xlines);
1035 else
1036 putchar(' ');
1037 printf("/%lu\n", (ul_i)mp->m_xsize);
1039 NYD_LEAVE;
1040 return 0;
1043 FL int
1044 c_file(void *v)
1046 int rv;
1047 NYD_ENTER;
1049 rv = _c_file(v, FEDIT_NONE);
1050 NYD_LEAVE;
1051 return rv;
1054 FL int
1055 c_File(void *v)
1057 int rv;
1058 NYD_ENTER;
1060 rv = _c_file(v, FEDIT_RDONLY);
1061 NYD_LEAVE;
1062 return rv;
1065 FL int
1066 c_echo(void *v)
1068 char const **argv = v, **ap, *cp;
1069 int c;
1070 NYD_ENTER;
1072 for (ap = argv; *ap != NULL; ++ap) {
1073 cp = *ap;
1074 if ((cp = fexpand(cp, FEXP_NSHORTCUT)) != NULL) {
1075 if (ap != argv)
1076 putchar(' ');
1077 c = 0;
1078 while (*cp != '\0' && (c = n_shell_expand_escape(&cp, FAL0)) > 0)
1079 putchar(c);
1080 /* \c ends overall processing */
1081 if (c < 0)
1082 goto jleave;
1085 putchar('\n');
1086 jleave:
1087 NYD_LEAVE;
1088 return 0;
1091 FL int
1092 c_newmail(void *v)
1094 int val = 1, mdot;
1095 NYD_ENTER;
1096 UNUSED(v);
1098 if (
1099 #ifdef HAVE_IMAP
1100 (mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1101 #endif
1102 (val = setfile(mailname,
1103 FEDIT_NEWMAIL | ((mb.mb_perm & MB_DELE) ? 0 : FEDIT_RDONLY))
1104 ) == 0) {
1105 mdot = getmdot(1);
1106 setdot(message + mdot - 1);
1108 NYD_LEAVE;
1109 return val;
1112 FL int
1113 c_flag(void *v)
1115 struct message *m;
1116 int *msgvec = v, *ip;
1117 NYD_ENTER;
1119 for (ip = msgvec; *ip != 0; ++ip) {
1120 m = message + *ip - 1;
1121 setdot(m);
1122 if (!(m->m_flag & (MFLAG | MFLAGGED)))
1123 m->m_flag |= MFLAG | MFLAGGED;
1125 NYD_LEAVE;
1126 return 0;
1129 FL int
1130 c_unflag(void *v)
1132 struct message *m;
1133 int *msgvec = v, *ip;
1134 NYD_ENTER;
1136 for (ip = msgvec; *ip != 0; ++ip) {
1137 m = message + *ip - 1;
1138 setdot(m);
1139 if (m->m_flag & (MFLAG | MFLAGGED)) {
1140 m->m_flag &= ~(MFLAG | MFLAGGED);
1141 m->m_flag |= MUNFLAG;
1144 NYD_LEAVE;
1145 return 0;
1148 FL int
1149 c_answered(void *v)
1151 struct message *m;
1152 int *msgvec = v, *ip;
1153 NYD_ENTER;
1155 for (ip = msgvec; *ip != 0; ++ip) {
1156 m = message + *ip - 1;
1157 setdot(m);
1158 if (!(m->m_flag & (MANSWER | MANSWERED)))
1159 m->m_flag |= MANSWER | MANSWERED;
1161 NYD_LEAVE;
1162 return 0;
1165 FL int
1166 c_unanswered(void *v)
1168 struct message *m;
1169 int *msgvec = v, *ip;
1170 NYD_ENTER;
1172 for (ip = msgvec; *ip != 0; ++ip) {
1173 m = message + *ip - 1;
1174 setdot(m);
1175 if (m->m_flag & (MANSWER | MANSWERED)) {
1176 m->m_flag &= ~(MANSWER | MANSWERED);
1177 m->m_flag |= MUNANSWER;
1180 NYD_LEAVE;
1181 return 0;
1184 FL int
1185 c_draft(void *v)
1187 struct message *m;
1188 int *msgvec = v, *ip;
1189 NYD_ENTER;
1191 for (ip = msgvec; *ip != 0; ++ip) {
1192 m = message + *ip - 1;
1193 setdot(m);
1194 if (!(m->m_flag & (MDRAFT | MDRAFTED)))
1195 m->m_flag |= MDRAFT | MDRAFTED;
1197 NYD_LEAVE;
1198 return 0;
1201 FL int
1202 c_undraft(void *v)
1204 struct message *m;
1205 int *msgvec = v, *ip;
1206 NYD_ENTER;
1208 for (ip = msgvec; *ip != 0; ++ip) {
1209 m = message + *ip - 1;
1210 setdot(m);
1211 if (m->m_flag & (MDRAFT | MDRAFTED)) {
1212 m->m_flag &= ~(MDRAFT | MDRAFTED);
1213 m->m_flag |= MUNDRAFT;
1216 NYD_LEAVE;
1217 return 0;
1220 FL int
1221 c_noop(void *v)
1223 int rv = 0;
1224 NYD_ENTER;
1225 UNUSED(v);
1227 switch (mb.mb_type) {
1228 case MB_IMAP:
1229 #ifdef HAVE_IMAP
1230 imap_noop();
1231 #else
1232 rv = c_cmdnotsupp(NULL);
1233 #endif
1234 break;
1235 case MB_POP3:
1236 #ifdef HAVE_POP3
1237 pop3_noop();
1238 #else
1239 rv = c_cmdnotsupp(NULL);
1240 #endif
1241 break;
1242 default:
1243 break;
1245 NYD_LEAVE;
1246 return rv;
1249 FL int
1250 c_remove(void *v)
1252 char const *fmt;
1253 size_t fmt_len;
1254 char **args = v, *name;
1255 int ec = 0;
1256 NYD_ENTER;
1258 if (*args == NULL) {
1259 n_err(_("Synopsis: remove: <mailbox>...\n"));
1260 ec = 1;
1261 goto jleave;
1264 fmt = _("Remove \"%s\" (y/n) ? ");
1265 fmt_len = strlen(fmt);
1266 do {
1267 if ((name = expand(*args)) == NULL)
1268 continue;
1270 if (!strcmp(name, mailname)) {
1271 n_err(_("Cannot remove current mailbox \"%s\"\n"), name);
1272 ec |= 1;
1273 continue;
1276 size_t vl = strlen(name) + fmt_len +1;
1277 char *vb = ac_alloc(vl);
1278 bool_t asw;
1279 snprintf(vb, vl, fmt, name);
1280 asw = getapproval(vb, TRU1);
1281 ac_free(vb);
1282 if (!asw)
1283 continue;
1286 switch (which_protocol(name)) {
1287 case PROTO_FILE:
1288 if (unlink(name) == -1) { /* TODO do not handle .gz .bz2 .xz.. */
1289 n_perr(name, 0);
1290 ec |= 1;
1292 break;
1293 case PROTO_POP3:
1294 n_err(_("Cannot remove POP3 mailbox \"%s\"\n"),name);
1295 ec |= 1;
1296 break;
1297 case PROTO_IMAP:
1298 #ifdef HAVE_IMAP
1299 if (imap_remove(name) != OKAY)
1300 #endif
1301 ec |= 1;
1302 break;
1303 case PROTO_MAILDIR:
1304 if (maildir_remove(name) != OKAY)
1305 ec |= 1;
1306 break;
1307 case PROTO_UNKNOWN:
1308 n_err(_("Unknown protocol in \"%s\"; not removed\n"), name);
1309 ec |= 1;
1310 break;
1312 } while (*++args != NULL);
1313 jleave:
1314 NYD_LEAVE;
1315 return ec;
1318 FL int
1319 c_rename(void *v)
1321 char **args = v, *old, *new;
1322 enum protocol oldp, newp;
1323 int ec;
1324 NYD_ENTER;
1326 ec = 1;
1328 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1329 n_err(_("Synopsis: rename: <old> <new>\n"));
1330 goto jleave;
1333 if ((old = expand(args[0])) == NULL)
1334 goto jleave;
1335 oldp = which_protocol(old);
1336 if ((new = expand(args[1])) == NULL)
1337 goto jleave;
1338 newp = which_protocol(new);
1340 if (!strcmp(old, mailname) || !strcmp(new, mailname)) {
1341 n_err(_("Cannot rename current mailbox \"%s\"\n"), old);
1342 goto jleave;
1344 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1345 fprintf(stderr, _("Can only rename folders of same type.\n"));
1346 goto jleave;
1349 ec = 0;
1351 if (newp == PROTO_POP3)
1352 goto jnopop3;
1353 switch (oldp) {
1354 case PROTO_FILE:
1355 if (link(old, new) == -1) {
1356 switch (errno) {
1357 case EACCES:
1358 case EEXIST:
1359 case ENAMETOOLONG:
1360 case ENOENT:
1361 case ENOSPC:
1362 case EXDEV:
1363 n_perr(new, 0);
1364 break;
1365 default:
1366 n_perr(old, 0);
1367 break;
1369 ec |= 1;
1370 } else if (unlink(old) == -1) {
1371 n_perr(old, 0);
1372 ec |= 1;
1374 break;
1375 case PROTO_MAILDIR:
1376 if (rename(old, new) == -1) {
1377 n_perr(old, 0);
1378 ec |= 1;
1380 break;
1381 case PROTO_POP3:
1382 jnopop3:
1383 n_err(_("Cannot rename POP3 mailboxes\n"));
1384 ec |= 1;
1385 break;
1386 #ifdef HAVE_IMAP
1387 case PROTO_IMAP:
1388 if (imap_rename(old, new) != OKAY)
1389 ec |= 1;
1390 break;
1391 #endif
1392 case PROTO_UNKNOWN:
1393 default:
1394 n_err(_("Unknown protocol in \"%s\" and \"%s\"; not renamed\n"),
1395 old, new);
1396 ec |= 1;
1397 break;
1399 jleave:
1400 NYD_LEAVE;
1401 return ec;
1404 FL int
1405 c_urlencode(void *v) /* XXX IDNA?? */
1407 char **ap;
1408 NYD_ENTER;
1410 for (ap = v; *ap != NULL; ++ap) {
1411 char *in = *ap, *out = urlxenc(in, FAL0);
1413 printf(" in: <%s> (%" PRIuZ " bytes)\nout: <%s> (%" PRIuZ " bytes)\n",
1414 in, strlen(in), out, strlen(out));
1416 NYD_LEAVE;
1417 return 0;
1420 FL int
1421 c_urldecode(void *v) /* XXX IDNA?? */
1423 char **ap;
1424 NYD_ENTER;
1426 for (ap = v; *ap != NULL; ++ap) {
1427 char *in = *ap, *out = urlxdec(in);
1429 printf(" in: <%s> (%" PRIuZ " bytes)\nout: <%s> (%" PRIuZ " bytes)\n",
1430 in, strlen(in), out, strlen(out));
1432 NYD_LEAVE;
1433 return 0;
1436 /* s-it-mode */