nail.h, nail.1: add *smtp-hostname*
[s-mailx.git] / cmd3.c
blobbe71898211c7483caab1c547e8d378d9ae1331f5
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 - 2014 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. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #ifndef HAVE_AMALGAMATION
41 # include "nail.h"
42 #endif
44 struct cond_stack {
45 struct cond_stack *c_outer;
46 bool_t c_noop; /* Outer stack !c_go, entirely no-op */
47 bool_t c_go; /* Green light */
48 bool_t c_else; /* In `else' clause */
49 ui8_t __dummy[5];
52 static struct cond_stack *_cond_stack;
53 static char *_bang_buf;
54 static size_t _bang_size;
56 /* Modify subject we reply to to begin with Re: if it does not already */
57 static char * _reedit(char *subj);
59 /* Expand the shell escape by expanding unescaped !'s into the last issued
60 * command where possible */
61 static void _bangexp(char **str, size_t *size);
63 static void make_ref_and_cs(struct message *mp, struct header *head);
65 /* Get PTF to implementation of command `c' (i.e., take care for *flipr*) */
66 static int (* respond_or_Respond(int c))(int *, int);
68 /* Reply to a single message. Extract each name from the message header and
69 * send them off to mail1() */
70 static int respond_internal(int *msgvec, int recipient_record);
72 /* Reply to a series of messages by simply mailing to the senders and not
73 * messing around with the To: and Cc: lists as in normal reply */
74 static int Respond_internal(int *msgvec, int recipient_record);
76 /* Forward a message to a new recipient, in the sense of RFC 2822 */
77 static int forward1(char *str, int recipient_record);
79 /* Modify the subject we are replying to to begin with Fwd: */
80 static char * fwdedit(char *subj);
82 /* Sort the passed string vecotor into ascending dictionary order */
83 static void asort(char **list);
85 /* Do a dictionary order comparison of the arguments from qsort */
86 static int diction(void const *a, void const *b);
88 /* Do the real work of resending */
89 static int _resend1(void *v, bool_t add_resent);
91 /* ..to stdout */
92 static void list_shortcuts(void);
94 /* */
95 static enum okay delete_shortcut(char const *str);
97 static char *
98 _reedit(char *subj)
100 struct str in, out;
101 char *newsubj = NULL;
102 NYD_ENTER;
104 if (subj == NULL || *subj == '\0')
105 goto j_leave;
107 in.s = subj;
108 in.l = strlen(subj);
109 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
111 /* TODO _reedit: should be localizable (see cmd1.c:__subject_trim()!) */
112 if ((out.s[0] == 'r' || out.s[0] == 'R') &&
113 (out.s[1] == 'e' || out.s[1] == 'E') && out.s[2] == ':') {
114 newsubj = savestr(out.s);
115 goto jleave;
117 newsubj = salloc(out.l + 4 +1);
118 sstpcpy(sstpcpy(newsubj, "Re: "), out.s);
119 jleave:
120 free(out.s);
121 j_leave:
122 NYD_LEAVE;
123 return newsubj;
126 static void
127 _bangexp(char **str, size_t *size)
129 char *bangbuf;
130 int changed = 0;
131 bool_t dobang;
132 size_t sz, i, j, bangbufsize;
133 NYD_ENTER;
135 dobang = ok_blook(bang);
137 bangbuf = smalloc(bangbufsize = *size);
138 i = j = 0;
139 while ((*str)[i]) {
140 if (dobang) {
141 if ((*str)[i] == '!') {
142 sz = strlen(_bang_buf);
143 bangbuf = srealloc(bangbuf, bangbufsize += sz);
144 ++changed;
145 memcpy(bangbuf + j, _bang_buf, sz + 1);
146 j += sz;
147 i++;
148 continue;
151 if ((*str)[i] == '\\' && (*str)[i + 1] == '!') {
152 bangbuf[j++] = '!';
153 i += 2;
154 ++changed;
156 bangbuf[j++] = (*str)[i++];
158 bangbuf[j] = '\0';
159 if (changed) {
160 printf("!%s\n", bangbuf);
161 fflush(stdout);
163 sz = j + 1;
164 if (sz > *size)
165 *str = srealloc(*str, *size = sz);
166 memcpy(*str, bangbuf, sz);
167 if (sz > _bang_size)
168 _bang_buf = srealloc(_bang_buf, _bang_size = sz);
169 memcpy(_bang_buf, bangbuf, sz);
170 free(bangbuf);
171 NYD_LEAVE;
174 static void
175 make_ref_and_cs(struct message *mp, struct header *head)
177 char *oldref, *oldmsgid, *newref, *cp;
178 size_t oldreflen = 0, oldmsgidlen = 0, reflen;
179 unsigned i;
180 struct name *n;
181 NYD_ENTER;
183 oldref = hfield1("references", mp);
184 oldmsgid = hfield1("message-id", mp);
185 if (oldmsgid == NULL || *oldmsgid == '\0') {
186 head->h_ref = NULL;
187 goto jleave;
190 reflen = 1;
191 if (oldref) {
192 oldreflen = strlen(oldref);
193 reflen += oldreflen + 2;
195 if (oldmsgid) {
196 oldmsgidlen = strlen(oldmsgid);
197 reflen += oldmsgidlen;
200 newref = ac_alloc(reflen);
201 if (oldref != NULL) {
202 memcpy(newref, oldref, oldreflen +1);
203 if (oldmsgid != NULL) {
204 newref[oldreflen++] = ',';
205 newref[oldreflen++] = ' ';
206 memcpy(newref + oldreflen, oldmsgid, oldmsgidlen +1);
208 } else if (oldmsgid)
209 memcpy(newref, oldmsgid, oldmsgidlen +1);
210 n = extract(newref, GREF);
211 ac_free(newref);
213 /* Limit number of references */
214 while (n->n_flink != NULL)
215 n = n->n_flink;
216 for (i = 1; i < 21; ++i) { /* XXX no magics */
217 if (n->n_blink != NULL)
218 n = n->n_blink;
219 else
220 break;
222 n->n_blink = NULL;
223 head->h_ref = n;
224 if (ok_blook(reply_in_same_charset) &&
225 (cp = hfield1("content-type", mp)) != NULL)
226 head->h_charset = mime_getparam("charset", cp);
227 jleave:
228 NYD_LEAVE;
231 static int
232 (*respond_or_Respond(int c))(int *, int)
234 int opt;
235 int (*rv)(int*, int);
236 NYD_ENTER;
238 opt = ok_blook(Replyall);
239 opt += ok_blook(flipr);
240 rv = ((opt == 1) ^ (c == 'R')) ? &Respond_internal : &respond_internal;
241 NYD_LEAVE;
242 return rv;
245 static int
246 respond_internal(int *msgvec, int recipient_record)
248 struct header head;
249 struct message *mp;
250 char *cp, *rcv;
251 struct name *np = NULL;
252 enum gfield gf;
253 int rv = 1;
254 NYD_ENTER;
256 gf = ok_blook(fullnames) ? GFULL : GSKIN;
258 if (msgvec[1] != 0) {
259 fprintf(stderr, tr(37,
260 "Sorry, can't reply to multiple messages at once\n"));
261 goto jleave;
264 mp = message + msgvec[0] - 1;
265 touch(mp);
266 setdot(mp);
268 if ((rcv = hfield1("reply-to", mp)) == NULL)
269 if ((rcv = hfield1("from", mp)) == NULL)
270 rcv = nameof(mp, 1);
271 if (rcv != NULL)
272 np = lextract(rcv, GTO | gf);
273 if (!ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
274 np = cat(np, lextract(cp, GTO | gf));
275 /* Delete my name from reply list, and with it, all my alternate names */
276 np = elide(delete_alternates(np));
277 if (np == NULL)
278 np = lextract(rcv, GTO | gf);
280 memset(&head, 0, sizeof head);
281 head.h_to = np;
282 head.h_subject = hfield1("subject", mp);
283 head.h_subject = _reedit(head.h_subject);
285 /* Cc: */
286 np = NULL;
287 if (ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
288 np = lextract(cp, GCC | gf);
289 if ((cp = hfield1("cc", mp)) != NULL)
290 np = cat(np, lextract(cp, GCC | gf));
291 if (np != NULL)
292 head.h_cc = elide(delete_alternates(np));
293 make_ref_and_cs(mp, &head);
295 if (ok_blook(quote_as_attachment)) {
296 head.h_attach = csalloc(1, sizeof *head.h_attach);
297 head.h_attach->a_msgno = *msgvec;
298 head.h_attach->a_content_description = tr(512,
299 "Original message content");
302 if (mail1(&head, 1, mp, NULL, recipient_record, 0) == OKAY &&
303 ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
304 mp->m_flag |= MANSWER | MANSWERED;
305 rv = 0;
306 jleave:
307 NYD_LEAVE;
308 return rv;
311 static int
312 Respond_internal(int *msgvec, int recipient_record)
314 struct header head;
315 struct message *mp;
316 int *ap;
317 char *cp;
318 enum gfield gf;
319 NYD_ENTER;
321 memset(&head, 0, sizeof head);
322 gf = ok_blook(fullnames) ? GFULL : GSKIN;
324 for (ap = msgvec; *ap != 0; ++ap) {
325 mp = message + *ap - 1;
326 touch(mp);
327 setdot(mp);
328 if ((cp = hfield1("reply-to", mp)) == NULL)
329 if ((cp = hfield1("from", mp)) == NULL)
330 cp = nameof(mp, 2);
331 head.h_to = cat(head.h_to, lextract(cp, GTO | gf));
333 if (head.h_to == NULL)
334 goto jleave;
336 mp = message + msgvec[0] - 1;
337 head.h_subject = hfield1("subject", mp);
338 head.h_subject = _reedit(head.h_subject);
339 make_ref_and_cs(mp, &head);
341 if (ok_blook(quote_as_attachment)) {
342 head.h_attach = csalloc(1, sizeof *head.h_attach);
343 head.h_attach->a_msgno = *msgvec;
344 head.h_attach->a_content_description = tr(512,
345 "Original message content");
348 if (mail1(&head, 1, mp, NULL, recipient_record, 0) == OKAY &&
349 ok_blook(markanswered) && !(mp->m_flag & MANSWERED))
350 mp->m_flag |= MANSWER | MANSWERED;
351 jleave:
352 NYD_LEAVE;
353 return 0;
356 static int
357 forward1(char *str, int recipient_record)
359 struct header head;
360 int *msgvec, rv = 1;
361 char *recipient;
362 struct message *mp;
363 bool_t f, forward_as_attachment;
364 NYD_ENTER;
366 if ((recipient = laststring(str, &f, 0)) == NULL) {
367 puts(tr(47, "No recipient specified."));
368 goto jleave;
371 forward_as_attachment = ok_blook(forward_as_attachment);
372 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
374 if (!f) {
375 *msgvec = first(0, MMNORM);
376 if (*msgvec == 0) {
377 if (inhook) {
378 rv = 0;
379 goto jleave;
381 printf("No messages to forward.\n");
382 goto jleave;
384 msgvec[1] = 0;
385 } else if (getmsglist(str, msgvec, 0) < 0)
386 goto jleave;
388 if (*msgvec == 0) {
389 if (inhook) {
390 rv = 0;
391 goto jleave;
393 printf("No applicable messages.\n");
394 goto jleave;
396 if (msgvec[1] != 0) {
397 printf("Cannot forward multiple messages at once\n");
398 goto jleave;
401 memset(&head, 0, sizeof head);
402 if ((head.h_to = lextract(recipient,
403 (GTO | (ok_blook(fullnames) ? GFULL : GSKIN)))) == NULL)
404 goto jleave;
406 mp = message + *msgvec - 1;
408 if (forward_as_attachment) {
409 head.h_attach = csalloc(1, sizeof *head.h_attach);
410 head.h_attach->a_msgno = *msgvec;
411 head.h_attach->a_content_description = "Forwarded message";
412 } else {
413 touch(mp);
414 setdot(mp);
416 head.h_subject = hfield1("subject", mp);
417 head.h_subject = fwdedit(head.h_subject);
418 mail1(&head, 1, (forward_as_attachment ? NULL : mp), NULL, recipient_record,
420 rv = 0;
421 jleave:
422 NYD_LEAVE;
423 return rv;
426 static char *
427 fwdedit(char *subj)
429 struct str in, out;
430 char *newsubj = NULL;
431 NYD_ENTER;
433 if (subj == NULL || *subj == '\0')
434 goto jleave;
436 in.s = subj;
437 in.l = strlen(subj);
438 mime_fromhdr(&in, &out, TD_ISPR | TD_ICONV);
440 newsubj = salloc(out.l + 6);
441 memcpy(newsubj, "Fwd: ", 5); /* XXX localizable */
442 memcpy(newsubj + 5, out.s, out.l + 1);
443 free(out.s);
444 jleave:
445 NYD_LEAVE;
446 return newsubj;
449 static void
450 asort(char **list)
452 char **ap;
453 size_t i;
454 NYD_ENTER;
456 for (ap = list; *ap != NULL; ++ap)
458 if ((i = PTR2SIZE(ap - list)) >= 2)
459 qsort(list, i, sizeof *list, diction);
460 NYD_LEAVE;
463 static int
464 diction(void const *a, void const *b)
466 int rv;
467 NYD_ENTER;
469 rv = strcmp(*(char**)UNCONST(a), *(char**)UNCONST(b));
470 NYD_LEAVE;
471 return rv;
474 static int
475 _resend1(void *v, bool_t add_resent)
477 char *name, *str;
478 struct name *to, *sn;
479 int *ip, *msgvec;
480 bool_t f = TRU1;
481 NYD_ENTER;
483 str = v;
484 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
485 name = laststring(str, &f, 1);
486 if (name == NULL) {
487 puts(tr(47, "No recipient specified."));
488 goto jleave;
491 if (!f) {
492 *msgvec = first(0, MMNORM);
493 if (*msgvec == 0) {
494 if (inhook) {
495 f = FAL0;
496 goto jleave;
498 puts(tr(48, "No applicable messages."));
499 goto jleave;
501 msgvec[1] = 0;
502 } else if (getmsglist(str, msgvec, 0) < 0)
503 goto jleave;
505 if (*msgvec == 0) {
506 if (inhook) {
507 f = FAL0;
508 goto jleave;
510 printf("No applicable messages.\n");
511 goto jleave;
514 sn = nalloc(name, GTO | GSKIN);
515 to = usermap(sn, FAL0);
516 for (ip = msgvec; *ip != 0 && UICMP(z, PTR2SIZE(ip - msgvec), <, msgCount);
517 ++ip)
518 if (resend_msg(message + *ip - 1, to, add_resent) != OKAY)
519 goto jleave;
520 f = FAL0;
521 jleave:
522 NYD_LEAVE;
523 return (f != FAL0);
526 static void
527 list_shortcuts(void)
529 struct shortcut *s;
530 NYD_ENTER;
532 for (s = shortcuts; s != NULL; s = s->sh_next)
533 printf("%s=%s\n", s->sh_short, s->sh_long);
534 NYD_LEAVE;
537 static enum okay
538 delete_shortcut(char const *str)
540 struct shortcut *sp, *sq;
541 enum okay rv = STOP;
542 NYD_ENTER;
544 for (sp = shortcuts, sq = NULL; sp != NULL; sq = sp, sp = sp->sh_next) {
545 if (!strcmp(sp->sh_short, str)) {
546 free(sp->sh_short);
547 free(sp->sh_long);
548 if (sq != NULL)
549 sq->sh_next = sp->sh_next;
550 if (sp == shortcuts)
551 shortcuts = sp->sh_next;
552 free(sp);
553 rv = OKAY;
554 break;
557 NYD_LEAVE;
558 return rv;
561 FL int
562 c_shell(void *v)
564 char const *sh = NULL;
565 char *str = v, *cmd;
566 size_t cmdsize;
567 sigset_t mask;
568 sighandler_type sigint;
569 NYD_ENTER;
571 cmd = smalloc(cmdsize = strlen(str) +1);
572 memcpy(cmd, str, cmdsize);
573 _bangexp(&cmd, &cmdsize);
574 if ((sh = ok_vlook(SHELL)) == NULL)
575 sh = XSHELL;
577 sigint = safe_signal(SIGINT, SIG_IGN);
578 sigemptyset(&mask);
579 run_command(sh, &mask, -1, -1, "-c", cmd, NULL);
580 safe_signal(SIGINT, sigint);
581 printf("!\n");
583 free(cmd);
584 NYD_LEAVE;
585 return 0;
588 FL int
589 c_dosh(void *v)
591 sighandler_type sigint;
592 char const *sh;
593 NYD_ENTER;
594 UNUSED(v);
596 if ((sh = ok_vlook(SHELL)) == NULL)
597 sh = XSHELL;
599 sigint = safe_signal(SIGINT, SIG_IGN);
600 run_command(sh, 0, -1, -1, NULL, NULL, NULL);
601 safe_signal(SIGINT, sigint);
602 putchar('\n');
603 NYD_LEAVE;
604 return 0;
607 FL int
608 c_help(void *v)
610 int ret = 0;
611 char *arg;
612 NYD_ENTER;
614 arg = *(char**)v;
616 if (arg != NULL) {
617 #ifdef HAVE_DOCSTRINGS
618 ret = !print_comm_docstr(arg);
619 if (ret)
620 fprintf(stderr, tr(91, "Unknown command: `%s'\n"), arg);
621 #else
622 ret = c_cmdnotsupp(NULL);
623 #endif
624 goto jleave;
627 /* Very ugly, but take care for compiler supported string lengths :( */
628 printf(tr(295, "%s commands:\n"), progname);
629 puts(tr(296,
630 "type <message list> type messages\n"
631 "next goto and type next message\n"
632 "from <message list> give head lines of messages\n"
633 "headers print out active message headers\n"
634 "delete <message list> delete messages\n"
635 "undelete <message list> undelete messages\n"));
636 puts(tr(297,
637 "save <message list> folder append messages to folder and mark as saved\n"
638 "copy <message list> folder append messages to folder without marking them\n"
639 "write <message list> file append message texts to file, save attachments\n"
640 "preserve <message list> keep incoming messages in mailbox even if saved\n"
641 "Reply <message list> reply to message senders\n"
642 "reply <message list> reply to message senders and all recipients\n"));
643 puts(tr(298,
644 "mail addresses mail to specific recipients\n"
645 "file folder change to another folder\n"
646 "quit quit and apply changes to folder\n"
647 "xit quit and discard changes made to folder\n"
648 "! shell escape\n"
649 "cd <directory> chdir to directory or home if none given\n"
650 "list list names of all available commands\n"));
651 printf(tr(299,
652 "\nA <message list> consists of integers, ranges of same, or other criteria\n"
653 "separated by spaces. If omitted, %s uses the last message typed.\n"),
654 progname);
656 jleave:
657 NYD_LEAVE;
658 return ret;
661 FL int
662 c_cwd(void *v)
664 char buf[PATH_MAX]; /* TODO getcwd(3) may return a larger value */
665 NYD_ENTER;
667 if (getcwd(buf, sizeof buf) != NULL) {
668 puts(buf);
669 v = (void*)0x1;
670 } else {
671 perror("getcwd");
672 v = NULL;
674 NYD_LEAVE;
675 return (v == NULL);
678 FL int
679 c_chdir(void *v)
681 char **arglist = v;
682 char const *cp;
683 NYD_ENTER;
685 if (*arglist == NULL)
686 cp = homedir;
687 else if ((cp = file_expand(*arglist)) == NULL)
688 goto jleave;
689 if (chdir(cp) == -1) {
690 perror(cp);
691 cp = NULL;
693 jleave:
694 NYD_LEAVE;
695 return (cp == NULL);
698 FL int
699 c_respond(void *v)
701 int rv;
702 NYD_ENTER;
704 rv = (*respond_or_Respond('r'))(v, 0);
705 NYD_LEAVE;
706 return rv;
709 FL int
710 c_respondall(void *v)
712 int rv;
713 NYD_ENTER;
715 rv = respond_internal(v, 0);
716 NYD_LEAVE;
717 return rv;
720 FL int
721 c_respondsender(void *v)
723 int rv;
724 NYD_ENTER;
726 rv = Respond_internal(v, 0);
727 NYD_LEAVE;
728 return rv;
731 FL int
732 c_Respond(void *v)
734 int rv;
735 NYD_ENTER;
737 rv = (*respond_or_Respond('R'))(v, 0);
738 NYD_LEAVE;
739 return rv;
742 FL int
743 c_followup(void *v)
745 int rv;
746 NYD_ENTER;
748 rv = (*respond_or_Respond('r'))(v, 1);
749 NYD_LEAVE;
750 return rv;
753 FL int
754 c_followupall(void *v)
756 int rv;
757 NYD_ENTER;
759 rv = respond_internal(v, 1);
760 NYD_LEAVE;
761 return rv;
764 FL int
765 c_followupsender(void *v)
767 int rv;
768 NYD_ENTER;
770 rv = Respond_internal(v, 1);
771 NYD_LEAVE;
772 return rv;
775 FL int
776 c_Followup(void *v)
778 int rv;
779 NYD_ENTER;
781 rv = (*respond_or_Respond('R'))(v, 1);
782 NYD_LEAVE;
783 return rv;
786 FL int
787 c_forward(void *v)
789 int rv;
790 NYD_ENTER;
792 rv = forward1(v, 0);
793 NYD_LEAVE;
794 return rv;
797 FL int
798 c_Forward(void *v)
800 int rv;
801 NYD_ENTER;
803 rv = forward1(v, 1);
804 NYD_LEAVE;
805 return rv;
808 FL int
809 c_resend(void *v)
811 int rv;
812 NYD_ENTER;
814 rv = _resend1(v, TRU1);
815 NYD_LEAVE;
816 return rv;
819 FL int
820 c_Resend(void *v)
822 int rv;
823 NYD_ENTER;
825 rv = _resend1(v, FAL0);
826 NYD_LEAVE;
827 return rv;
830 FL int
831 c_preserve(void *v)
833 int *msgvec = v, *ip, mesg, rv = 1;
834 struct message *mp;
835 NYD_ENTER;
837 if (edit) {
838 printf(tr(39, "Cannot \"preserve\" in edit mode\n"));
839 goto jleave;
842 for (ip = msgvec; *ip != 0; ++ip) {
843 mesg = *ip;
844 mp = message + mesg - 1;
845 mp->m_flag |= MPRESERVE;
846 mp->m_flag &= ~MBOX;
847 setdot(mp);
848 did_print_dot = TRU1;
850 rv = 0;
851 jleave:
852 NYD_LEAVE;
853 return rv;
856 FL int
857 c_unread(void *v)
859 int *msgvec = v, *ip;
860 NYD_ENTER;
862 for (ip = msgvec; *ip != 0; ++ip) {
863 setdot(message + *ip - 1);
864 dot->m_flag &= ~(MREAD | MTOUCH);
865 dot->m_flag |= MSTATUS;
866 #ifdef HAVE_IMAP
867 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
868 imap_unread(message + *ip - 1, *ip); /* TODO return? */
869 #endif
870 did_print_dot = TRU1;
872 NYD_LEAVE;
873 return 0;
876 FL int
877 c_seen(void *v)
879 int *msgvec = v, *ip;
880 NYD_ENTER;
882 for (ip = msgvec; *ip != 0; ++ip) {
883 struct message *mp = message + *ip - 1;
884 setdot(mp);
885 touch(mp);
887 NYD_LEAVE;
888 return 0;
891 FL int
892 c_messize(void *v)
894 int *msgvec = v, *ip, mesg;
895 struct message *mp;
896 NYD_ENTER;
898 for (ip = msgvec; *ip != 0; ++ip) {
899 mesg = *ip;
900 mp = message + mesg - 1;
901 printf("%d: ", mesg);
902 if (mp->m_xlines > 0)
903 printf("%ld", mp->m_xlines);
904 else
905 putchar(' ');
906 printf("/%lu\n", (ul_it)mp->m_xsize);
908 NYD_LEAVE;
909 return 0;
912 FL int
913 c_rexit(void *v)
915 UNUSED(v);
916 NYD_ENTER;
918 if (!sourcing)
919 exit(0);
920 NYD_LEAVE;
921 return 1;
924 FL int
925 c_set(void *v)
927 char **ap = v, *cp, *cp2, *varbuf, c;
928 int errs = 0;
929 NYD_ENTER;
931 if (*ap == NULL) {
932 var_list_all();
933 goto jleave;
936 for (; *ap != NULL; ++ap) {
937 cp = *ap;
938 cp2 = varbuf = ac_alloc(strlen(cp) +1);
939 for (; (c = *cp) != '=' && c != '\0'; ++cp)
940 *cp2++ = c;
941 *cp2 = '\0';
942 if (c == '\0')
943 cp = UNCONST("");
944 else
945 ++cp;
946 if (varbuf == cp2) {
947 fprintf(stderr, tr(41, "Non-null variable name required\n"));
948 ++errs;
949 goto jnext;
951 if (varbuf[0] == 'n' && varbuf[1] == 'o')
952 errs += _var_vokclear(&varbuf[2]);
953 else
954 errs += _var_vokset(varbuf, (uintptr_t)cp);
955 jnext:
956 ac_free(varbuf);
958 jleave:
959 NYD_LEAVE;
960 return errs;
963 FL int
964 c_unset(void *v)
966 int errs;
967 char **ap;
968 NYD_ENTER;
970 errs = 0;
971 for (ap = v; *ap != NULL; ++ap)
972 errs += _var_vokclear(*ap);
973 NYD_LEAVE;
974 return errs;
977 FL int
978 c_group(void *v)
980 char **argv = v, **ap, *gname, **p;
981 struct grouphead *gh;
982 struct group *gp;
983 int h, s;
984 NYD_ENTER;
986 if (*argv == NULL) {
987 for (h = 0, s = 1; h < HSHSIZE; ++h)
988 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
989 ++s;
990 ap = salloc(s * sizeof *ap);
992 for (h = 0, p = ap; h < HSHSIZE; ++h)
993 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
994 *p++ = gh->g_name;
995 *p = NULL;
997 asort(ap);
999 for (p = ap; *p != NULL; ++p)
1000 printgroup(*p);
1001 goto jleave;
1004 if (argv[1] == NULL) {
1005 printgroup(*argv);
1006 goto jleave;
1009 gname = *argv;
1010 h = hash(gname);
1011 if ((gh = findgroup(gname)) == NULL) {
1012 gh = scalloc(1, sizeof *gh);
1013 gh->g_name = sstrdup(gname);
1014 gh->g_list = NULL;
1015 gh->g_link = groups[h];
1016 groups[h] = gh;
1019 /* Insert names from the command list into the group. Who cares if there
1020 * are duplicates? They get tossed later anyway */
1021 for (ap = argv + 1; *ap != NULL; ++ap) {
1022 gp = scalloc(1, sizeof *gp);
1023 gp->ge_name = sstrdup(*ap);
1024 gp->ge_link = gh->g_list;
1025 gh->g_list = gp;
1027 jleave:
1028 NYD_LEAVE;
1029 return 0;
1032 FL int
1033 c_ungroup(void *v)
1035 char **argv = v;
1036 int rv = 1;
1037 NYD_ENTER;
1039 if (*argv == NULL) {
1040 fprintf(stderr, tr(209, "Must specify alias to remove\n"));
1041 goto jleave;
1045 remove_group(*argv);
1046 while (*++argv != NULL);
1047 rv = 0;
1048 jleave:
1049 NYD_LEAVE;
1050 return rv;
1053 FL int
1054 c_file(void *v)
1056 char **argv = v;
1057 int i;
1058 NYD_ENTER;
1060 if (*argv == NULL) {
1061 newfileinfo();
1062 i = 0;
1063 goto jleave;
1066 if (inhook) {
1067 fprintf(stderr, tr(516, "Cannot change folder from within a hook.\n"));
1068 i = 1;
1069 goto jleave;
1072 save_mbox_for_possible_quitstuff();
1074 i = setfile(*argv, 0);
1075 if (i < 0) {
1076 i = 1;
1077 goto jleave;
1079 callhook(mailname, 0);
1080 if (i > 0 && !ok_blook(emptystart)) {
1081 i = 1;
1082 goto jleave;
1084 announce(ok_blook(bsdcompat) || ok_blook(bsdannounce));
1085 i = 0;
1086 jleave:
1087 NYD_LEAVE;
1088 return i;
1091 FL int
1092 c_echo(void *v)
1094 char const **argv = v, **ap, *cp;
1095 int c;
1096 NYD_ENTER;
1098 for (ap = argv; *ap != NULL; ++ap) {
1099 cp = *ap;
1100 if ((cp = fexpand(cp, FEXP_NSHORTCUT)) != NULL) {
1101 if (ap != argv)
1102 putchar(' ');
1103 c = 0;
1104 while (*cp != '\0' && (c = expand_shell_escape(&cp, FAL0)) > 0)
1105 putchar(c);
1106 /* \c ends overall processing */
1107 if (c < 0)
1108 goto jleave;
1111 putchar('\n');
1112 jleave:
1113 NYD_LEAVE;
1114 return 0;
1117 FL int
1118 c_if(void *v)
1120 struct cond_stack *csp;
1121 int rv = 1;
1122 char **argv = v, *cp, *op;
1123 NYD_ENTER;
1125 csp = smalloc(sizeof *csp);
1126 csp->c_outer = _cond_stack;
1127 csp->c_noop = condstack_isskip();
1128 csp->c_go = TRU1;
1129 csp->c_else = FAL0;
1130 _cond_stack = csp;
1132 cp = argv[0];
1133 if (*cp != '$' && argv[1] != NULL) {
1134 jesyn:
1135 fprintf(stderr, tr(528, "Invalid conditional expression \"%s %s %s\"\n"),
1136 argv[0], (argv[1] != NULL ? argv[1] : ""),
1137 (argv[2] != NULL ? argv[2] : ""));
1138 goto jleave;
1141 switch (*cp) {
1142 case '0':
1143 csp->c_go = FAL0;
1144 break;
1145 case 'R': case 'r':
1146 csp->c_go = !(options & OPT_SENDMODE);
1147 break;
1148 case 'S': case 's':
1149 csp->c_go = ((options & OPT_SENDMODE) != 0);
1150 break;
1151 case 'T': case 't':
1152 csp->c_go = ((options & OPT_TTYIN) != 0);
1153 break;
1154 case '$':
1155 /* Look up the value in question, we need it anyway */
1156 v = vok_vlook(++cp);
1158 /* Single argument, "implicit boolean" form? */
1159 if ((op = argv[1]) == NULL) {
1160 csp->c_go = (v != NULL);
1161 break;
1164 /* Three argument comparison form? */
1165 if (argv[2] == NULL || op[0] == '\0' || op[1] != '=' || op[2] != '\0')
1166 goto jesyn;
1167 /* A null value is treated as the empty string */
1168 if (v == NULL)
1169 v = UNCONST("");
1170 if (strcmp(v, argv[2]))
1171 v = NULL;
1172 switch (op[0]) {
1173 case '!':
1174 case '=':
1175 csp->c_go = ((op[0] == '=') ^ (v == NULL));
1176 break;
1177 default:
1178 goto jesyn;
1180 break;
1181 default:
1182 fprintf(stderr, tr(43, "Unrecognized if-keyword: \"%s\"\n"), cp);
1183 case '1':
1184 csp->c_go = TRU1;
1185 goto jleave;
1187 rv = 0;
1188 jleave:
1189 NYD_LEAVE;
1190 return rv;
1193 FL int
1194 c_else(void *v)
1196 int rv;
1197 NYD_ENTER;
1198 UNUSED(v);
1200 if (_cond_stack == NULL || _cond_stack->c_else) {
1201 fprintf(stderr, tr(44, "\"else\" without matching \"if\"\n"));
1202 rv = 1;
1203 } else {
1204 _cond_stack->c_go = !_cond_stack->c_go;
1205 _cond_stack->c_else = TRU1;
1206 rv = 0;
1208 NYD_LEAVE;
1209 return rv;
1212 FL int
1213 c_endif(void *v)
1215 struct cond_stack *csp;
1216 int rv;
1217 NYD_ENTER;
1218 UNUSED(v);
1220 if ((csp = _cond_stack) == NULL) {
1221 fprintf(stderr, tr(46, "\"endif\" without matching \"if\"\n"));
1222 rv = 1;
1223 } else {
1224 _cond_stack = csp->c_outer;
1225 free(csp);
1226 rv = 0;
1228 NYD_LEAVE;
1229 return rv;
1232 FL bool_t
1233 condstack_isskip(void)
1235 bool_t rv;
1236 NYD_ENTER;
1238 rv = (_cond_stack != NULL && (_cond_stack->c_noop || !_cond_stack->c_go));
1239 NYD_LEAVE;
1240 return rv;
1243 FL void *
1244 condstack_release(void)
1246 void *rv;
1247 NYD_ENTER;
1249 rv = _cond_stack;
1250 _cond_stack = NULL;
1251 NYD_LEAVE;
1252 return rv;
1255 FL bool_t
1256 condstack_take(void *self)
1258 struct cond_stack *csp;
1259 bool_t rv;
1260 NYD_ENTER;
1262 if (!(rv = ((csp = _cond_stack) == NULL)))
1263 do {
1264 _cond_stack = csp->c_outer;
1265 free(csp);
1266 } while ((csp = _cond_stack) != NULL);
1268 _cond_stack = self;
1269 NYD_LEAVE;
1270 return rv;
1273 FL int
1274 c_alternates(void *v)
1276 size_t l;
1277 char **namelist = v, **ap, **ap2, *cp;
1278 NYD_ENTER;
1280 l = argcount(namelist) + 1;
1281 if (l == 1) {
1282 if (altnames == NULL)
1283 goto jleave;
1284 for (ap = altnames; *ap != NULL; ++ap)
1285 printf("%s ", *ap);
1286 printf("\n");
1287 goto jleave;
1290 if (altnames != NULL) {
1291 for (ap = altnames; *ap != NULL; ++ap)
1292 free(*ap);
1293 free(altnames);
1295 altnames = smalloc(l * sizeof(char*));
1296 for (ap = namelist, ap2 = altnames; *ap != NULL; ++ap, ++ap2) {
1297 l = strlen(*ap) + 1;
1298 cp = smalloc(l);
1299 memcpy(cp, *ap, l);
1300 *ap2 = cp;
1302 *ap2 = NULL;
1303 jleave:
1304 NYD_LEAVE;
1305 return 0;
1308 FL int
1309 c_newmail(void *v)
1311 int val = 1, mdot;
1312 NYD_ENTER;
1313 UNUSED(v);
1315 if (
1316 #ifdef HAVE_IMAP
1317 (mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1318 #endif
1319 (val = setfile(mailname, 1)) == 0) {
1320 mdot = getmdot(1);
1321 setdot(message + mdot - 1);
1323 NYD_LEAVE;
1324 return val;
1327 FL int
1328 c_shortcut(void *v)
1330 char **args = v;
1331 struct shortcut *s;
1332 int rv;
1333 NYD_ENTER;
1335 if (args[0] == NULL) {
1336 list_shortcuts();
1337 rv = 0;
1338 goto jleave;
1341 rv = 1;
1342 if (args[1] == NULL) {
1343 fprintf(stderr, tr(220, "expansion name for shortcut missing\n"));
1344 goto jleave;
1346 if (args[2] != NULL) {
1347 fprintf(stderr, tr(221, "too many arguments\n"));
1348 goto jleave;
1351 if ((s = get_shortcut(args[0])) != NULL) {
1352 free(s->sh_long);
1353 s->sh_long = sstrdup(args[1]);
1354 } else {
1355 s = scalloc(1, sizeof *s);
1356 s->sh_short = sstrdup(args[0]);
1357 s->sh_long = sstrdup(args[1]);
1358 s->sh_next = shortcuts;
1359 shortcuts = s;
1361 rv = 0;
1362 jleave:
1363 NYD_LEAVE;
1364 return rv;
1367 FL struct shortcut *
1368 get_shortcut(char const *str)
1370 struct shortcut *s;
1371 NYD_ENTER;
1373 for (s = shortcuts; s != NULL; s = s->sh_next)
1374 if (!strcmp(str, s->sh_short))
1375 break;
1376 NYD_LEAVE;
1377 return s;
1380 FL int
1381 c_unshortcut(void *v)
1383 char **args = v;
1384 bool_t errs = FAL0;
1385 NYD_ENTER;
1387 if (args[0] == NULL) {
1388 fprintf(stderr, tr(222, "need shortcut names to remove\n"));
1389 errs = TRU1;
1390 goto jleave;
1393 while (*args != NULL) {
1394 if (delete_shortcut(*args) != OKAY) {
1395 errs = TRU1;
1396 fprintf(stderr, tr(223, "%s: no such shortcut\n"), *args);
1398 ++args;
1400 jleave:
1401 NYD_LEAVE;
1402 return errs;
1405 FL int
1406 c_flag(void *v)
1408 struct message *m;
1409 int *msgvec = v, *ip;
1410 NYD_ENTER;
1412 for (ip = msgvec; *ip != 0; ++ip) {
1413 m = message + *ip - 1;
1414 setdot(m);
1415 if (!(m->m_flag & (MFLAG | MFLAGGED)))
1416 m->m_flag |= MFLAG | MFLAGGED;
1418 NYD_LEAVE;
1419 return 0;
1422 FL int
1423 c_unflag(void *v)
1425 struct message *m;
1426 int *msgvec = v, *ip;
1427 NYD_ENTER;
1429 for (ip = msgvec; *ip != 0; ++ip) {
1430 m = message + *ip - 1;
1431 setdot(m);
1432 if (m->m_flag & (MFLAG | MFLAGGED)) {
1433 m->m_flag &= ~(MFLAG | MFLAGGED);
1434 m->m_flag |= MUNFLAG;
1437 NYD_LEAVE;
1438 return 0;
1441 FL int
1442 c_answered(void *v)
1444 struct message *m;
1445 int *msgvec = v, *ip;
1446 NYD_ENTER;
1448 for (ip = msgvec; *ip != 0; ++ip) {
1449 m = message + *ip - 1;
1450 setdot(m);
1451 if (!(m->m_flag & (MANSWER | MANSWERED)))
1452 m->m_flag |= MANSWER | MANSWERED;
1454 NYD_LEAVE;
1455 return 0;
1458 FL int
1459 c_unanswered(void *v)
1461 struct message *m;
1462 int *msgvec = v, *ip;
1463 NYD_ENTER;
1465 for (ip = msgvec; *ip != 0; ++ip) {
1466 m = message + *ip - 1;
1467 setdot(m);
1468 if (m->m_flag & (MANSWER | MANSWERED)) {
1469 m->m_flag &= ~(MANSWER | MANSWERED);
1470 m->m_flag |= MUNANSWER;
1473 NYD_LEAVE;
1474 return 0;
1477 FL int
1478 c_draft(void *v)
1480 struct message *m;
1481 int *msgvec = v, *ip;
1482 NYD_ENTER;
1484 for (ip = msgvec; *ip != 0; ++ip) {
1485 m = message + *ip - 1;
1486 setdot(m);
1487 if (!(m->m_flag & (MDRAFT | MDRAFTED)))
1488 m->m_flag |= MDRAFT | MDRAFTED;
1490 NYD_LEAVE;
1491 return 0;
1494 FL int
1495 c_undraft(void *v)
1497 struct message *m;
1498 int *msgvec = v, *ip;
1499 NYD_ENTER;
1501 for (ip = msgvec; *ip != 0; ++ip) {
1502 m = message + *ip - 1;
1503 setdot(m);
1504 if (m->m_flag & (MDRAFT | MDRAFTED)) {
1505 m->m_flag &= ~(MDRAFT | MDRAFTED);
1506 m->m_flag |= MUNDRAFT;
1509 NYD_LEAVE;
1510 return 0;
1513 FL int
1514 c_noop(void *v)
1516 int rv = 0;
1517 NYD_ENTER;
1518 UNUSED(v);
1520 switch (mb.mb_type) {
1521 case MB_IMAP:
1522 #ifdef HAVE_IMAP
1523 imap_noop();
1524 #else
1525 rv = c_cmdnotsupp(NULL);
1526 #endif
1527 break;
1528 case MB_POP3:
1529 #ifdef HAVE_POP3
1530 pop3_noop();
1531 #else
1532 rv = c_cmdnotsupp(NULL);
1533 #endif
1534 break;
1535 default:
1536 break;
1538 NYD_LEAVE;
1539 return rv;
1542 FL int
1543 c_remove(void *v)
1545 char const *fmt;
1546 size_t fmt_len;
1547 char **args = v, *name;
1548 int ec = 0;
1549 NYD_ENTER;
1551 if (*args == NULL) {
1552 fprintf(stderr, tr(290, "Syntax is: remove mailbox ...\n"));
1553 ec = 1;
1554 goto jleave;
1557 fmt = tr(287, "Remove \"%s\" (y/n) ? ");
1558 fmt_len = strlen(fmt);
1559 do {
1560 if ((name = expand(*args)) == NULL)
1561 continue;
1563 if (!strcmp(name, mailname)) {
1564 fprintf(stderr, tr(286, "Cannot remove current mailbox \"%s\".\n"),
1565 name);
1566 ec |= 1;
1567 continue;
1570 size_t vl = strlen(name) + fmt_len +1;
1571 char *vb = ac_alloc(vl);
1572 bool_t asw;
1573 snprintf(vb, vl, fmt, name);
1574 asw = getapproval(vb, TRU1);
1575 ac_free(vb);
1576 if (!asw)
1577 continue;
1580 switch (which_protocol(name)) {
1581 case PROTO_FILE:
1582 if (unlink(name) == -1) { /* TODO do not handle .gz .bz2 .xz.. */
1583 perror(name);
1584 ec |= 1;
1586 break;
1587 case PROTO_POP3:
1588 fprintf(stderr, tr(288, "Cannot remove POP3 mailbox \"%s\".\n"),name);
1589 ec |= 1;
1590 break;
1591 case PROTO_IMAP:
1592 #ifdef HAVE_IMAP
1593 if (imap_remove(name) != OKAY)
1594 #endif
1595 ec |= 1;
1596 break;
1597 case PROTO_MAILDIR:
1598 if (maildir_remove(name) != OKAY)
1599 ec |= 1;
1600 break;
1601 case PROTO_UNKNOWN:
1602 fprintf(stderr, tr(289, "Unknown protocol in \"%s\". Not removed.\n"),
1603 name);
1604 ec |= 1;
1605 break;
1607 } while (*++args != NULL);
1608 jleave:
1609 NYD_LEAVE;
1610 return ec;
1613 FL int
1614 c_rename(void *v)
1616 char **args = v, *old, *new;
1617 enum protocol oldp, newp;
1618 int ec;
1619 NYD_ENTER;
1621 ec = 1;
1623 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1624 fprintf(stderr, "Syntax: rename old new\n");
1625 goto jleave;
1628 if ((old = expand(args[0])) == NULL)
1629 goto jleave;
1630 oldp = which_protocol(old);
1631 if ((new = expand(args[1])) == NULL)
1632 goto jleave;
1633 newp = which_protocol(new);
1635 if (!strcmp(old, mailname) || !strcmp(new, mailname)) {
1636 fprintf(stderr, tr(291, "Cannot rename current mailbox \"%s\".\n"), old);
1637 goto jleave;
1639 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1640 fprintf(stderr, tr(292, "Can only rename folders of same type.\n"));
1641 goto jleave;
1644 ec = 0;
1646 if (newp == PROTO_POP3)
1647 goto jnopop3;
1648 switch (oldp) {
1649 case PROTO_FILE:
1650 if (link(old, new) == -1) {
1651 switch (errno) {
1652 case EACCES:
1653 case EEXIST:
1654 case ENAMETOOLONG:
1655 case ENOENT:
1656 case ENOSPC:
1657 case EXDEV:
1658 perror(new);
1659 break;
1660 default:
1661 perror(old);
1663 ec |= 1;
1664 } else if (unlink(old) == -1) {
1665 perror(old);
1666 ec |= 1;
1668 break;
1669 case PROTO_MAILDIR:
1670 if (rename(old, new) == -1) {
1671 perror(old);
1672 ec |= 1;
1674 break;
1675 case PROTO_POP3:
1676 jnopop3:
1677 fprintf(stderr, tr(293, "Cannot rename POP3 mailboxes.\n"));
1678 ec |= 1;
1679 break;
1680 #ifdef HAVE_IMAP
1681 case PROTO_IMAP:
1682 if (imap_rename(old, new) != OKAY)
1683 ec |= 1;
1684 break;
1685 #endif
1686 case PROTO_UNKNOWN:
1687 default:
1688 fprintf(stderr, tr(294,
1689 "Unknown protocol in \"%s\" and \"%s\". Not renamed.\n"), old, new);
1690 ec |= 1;
1691 break;
1693 jleave:
1694 NYD_LEAVE;
1695 return ec;
1698 /* vim:set fenc=utf-8:s-it-mode */