Extend `if' conditionals ('[01]' and '$COND( [!=]= VAL)?')
[s-mailx.git] / cmd3.c
blobd08c5c1698ac9ec2b217f46ea7fe9a304f2e77c7
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 - 2013 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 /* Modify subject we reply to to begin with Re: if it does not already */
45 static char * _reedit(char *subj);
47 static int bangexp(char **str, size_t *size);
48 static void make_ref_and_cs(struct message *mp, struct header *head);
49 static int (* respond_or_Respond(int c))(int *, int);
50 static int respond_internal(int *msgvec, int recipient_record);
51 static char * fwdedit(char *subj);
52 static void asort(char **list);
53 static int diction(const void *a, const void *b);
54 static int Respond_internal(int *msgvec, int recipient_record);
55 static int resend1(void *v, int add_resent);
56 static void list_shortcuts(void);
57 static enum okay delete_shortcut(const char *str);
59 static char *
60 _reedit(char *subj)
62 struct str in, out;
63 char *newsubj = NULL;
65 if (subj == NULL || *subj == '\0')
66 goto j_leave;
68 in.s = subj;
69 in.l = strlen(subj);
70 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
72 if ((out.s[0] == 'r' || out.s[0] == 'R') &&
73 (out.s[1] == 'e' || out.s[1] == 'E') &&
74 out.s[2] == ':') {
75 newsubj = savestr(out.s);
76 goto jleave;
78 newsubj = salloc(out.l + 5);
79 sstpcpy(sstpcpy(newsubj, "Re: "), out.s);
80 jleave:
81 free(out.s);
82 j_leave:
83 return (newsubj);
87 * Process a shell escape by saving signals, ignoring signals,
88 * and forking a sh -c
90 FL int
91 shell(void *v)
93 char *str = v, *cmd;
94 char const *sh;
95 size_t cmdsize;
96 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
98 cmd = smalloc(cmdsize = strlen(str) + 1);
99 memcpy(cmd, str, cmdsize);
100 if (bangexp(&cmd, &cmdsize) < 0)
101 return 1;
102 if ((sh = ok_vlook(SHELL)) == NULL)
103 sh = XSHELL;
104 run_command(sh, 0, -1, -1, "-c", cmd, NULL);
105 safe_signal(SIGINT, sigint);
106 printf("!\n");
107 free(cmd);
108 return 0;
112 * Fork an interactive shell.
114 /*ARGSUSED*/
115 FL int
116 dosh(void *v)
118 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
119 char const *sh;
120 (void)v;
122 if ((sh = ok_vlook(SHELL)) == NULL)
123 sh = XSHELL;
124 run_command(sh, 0, -1, -1, NULL, NULL, NULL);
125 safe_signal(SIGINT, sigint);
126 putchar('\n');
127 return 0;
131 * Expand the shell escape by expanding unescaped !'s into the
132 * last issued command where possible.
135 static char *lastbang;
136 static size_t lastbangsize;
138 static int
139 bangexp(char **str, size_t *size)
141 char *bangbuf;
142 int changed = 0;
143 int dobang = ok_blook(bang);
144 size_t sz, i, j, bangbufsize;
146 bangbuf = smalloc(bangbufsize = *size);
147 i = j = 0;
148 while ((*str)[i]) {
149 if (dobang) {
150 if ((*str)[i] == '!') {
151 sz = strlen(lastbang);
152 bangbuf = srealloc(bangbuf, bangbufsize += sz);
153 changed++;
154 memcpy(bangbuf + j, lastbang, sz + 1);
155 j += sz;
156 i++;
157 continue;
160 if ((*str)[i] == '\\' && (*str)[i + 1] == '!') {
161 bangbuf[j++] = '!';
162 i += 2;
163 changed++;
165 bangbuf[j++] = (*str)[i++];
167 bangbuf[j] = '\0';
168 if (changed) {
169 printf("!%s\n", bangbuf);
170 fflush(stdout);
172 sz = j + 1;
173 if (sz > *size)
174 *str = srealloc(*str, *size = sz);
175 memcpy(*str, bangbuf, sz);
176 if (sz > lastbangsize)
177 lastbang = srealloc(lastbang, lastbangsize = sz);
178 memcpy(lastbang, bangbuf, sz);
179 free(bangbuf);
180 return 0;
183 /*ARGSUSED*/
184 FL int
185 help(void *v)
187 int ret = 0;
188 char *arg = *(char**)v;
190 if (arg != NULL) {
191 #ifdef HAVE_DOCSTRINGS
192 ret = ! print_comm_docstr(arg);
193 if (ret)
194 fprintf(stderr, tr(91, "Unknown command: `%s'\n"), arg);
195 #else
196 ret = ccmdnotsupp(NULL);
197 #endif
198 goto jleave;
201 /* Very ugly, but take care for compiler supported string lengths :( */
202 printf(tr(295, "%s commands:\n"), progname);
203 puts(tr(296,
204 "type <message list> type messages\n"
205 "next goto and type next message\n"
206 "from <message list> give head lines of messages\n"
207 "headers print out active message headers\n"
208 "delete <message list> delete messages\n"
209 "undelete <message list> undelete messages\n"));
210 puts(tr(297,
211 "save <message list> folder append messages to folder and mark as saved\n"
212 "copy <message list> folder append messages to folder without marking them\n"
213 "write <message list> file append message texts to file, save attachments\n"
214 "preserve <message list> keep incoming messages in mailbox even if saved\n"
215 "Reply <message list> reply to message senders\n"
216 "reply <message list> reply to message senders and all recipients\n"));
217 puts(tr(298,
218 "mail addresses mail to specific recipients\n"
219 "file folder change to another folder\n"
220 "quit quit and apply changes to folder\n"
221 "xit quit and discard changes made to folder\n"
222 "! shell escape\n"
223 "cd <directory> chdir to directory or home if none given\n"
224 "list list names of all available commands\n"));
225 printf(tr(299,
226 "\nA <message list> consists of integers, ranges of same, or other criteria\n"
227 "separated by spaces. If omitted, %s uses the last message typed.\n"),
228 progname);
230 jleave:
231 return ret;
234 FL int
235 c_cwd(void *v)
237 char buf[MAXPATHLEN]; /* TODO getcwd(3) may return a larger value */
239 if (getcwd(buf, sizeof buf) != NULL) {
240 puts(buf);
241 v = (void*)0x1;
242 } else {
243 perror("getcwd");
244 v = NULL;
246 return (v == NULL);
249 FL int
250 c_chdir(void *v)
252 char **arglist = v;
253 char const *cp;
255 if (*arglist == NULL)
256 cp = homedir;
257 else if ((cp = file_expand(*arglist)) == NULL)
258 goto jleave;
259 if (chdir(cp) < 0) {
260 perror(cp);
261 cp = NULL;
263 jleave:
264 return (cp == NULL);
267 static void
268 make_ref_and_cs(struct message *mp, struct header *head)
270 char *oldref, *oldmsgid, *newref, *cp;
271 size_t oldreflen = 0, oldmsgidlen = 0, reflen;
272 unsigned i;
273 struct name *n;
275 oldref = hfield1("references", mp);
276 oldmsgid = hfield1("message-id", mp);
277 if (oldmsgid == NULL || *oldmsgid == '\0') {
278 head->h_ref = NULL;
279 return;
281 reflen = 1;
282 if (oldref) {
283 oldreflen = strlen(oldref);
284 reflen += oldreflen + 2;
286 if (oldmsgid) {
287 oldmsgidlen = strlen(oldmsgid);
288 reflen += oldmsgidlen;
291 newref = ac_alloc(reflen);
292 if (oldref != NULL) {
293 memcpy(newref, oldref, oldreflen + 1);
294 if (oldmsgid != NULL) {
295 newref[oldreflen++] = ',';
296 newref[oldreflen++] = ' ';
297 memcpy(newref + oldreflen, oldmsgid, oldmsgidlen + 1);
299 } else if (oldmsgid)
300 memcpy(newref, oldmsgid, oldmsgidlen + 1);
301 n = extract(newref, GREF);
302 ac_free(newref);
305 * Limit the references to 21 entries.
307 while (n->n_flink != NULL)
308 n = n->n_flink;
309 for (i = 1; i < 21; i++) {
310 if (n->n_blink != NULL)
311 n = n->n_blink;
312 else
313 break;
315 n->n_blink = NULL;
316 head->h_ref = n;
317 if (ok_blook(reply_in_same_charset) &&
318 (cp = hfield1("content-type", mp)) != NULL)
319 head->h_charset = mime_getparam("charset", cp);
322 static int
323 (*respond_or_Respond(int c))(int *, int)
325 int opt = 0;
327 opt += ok_blook(Replyall);
328 opt += ok_blook(flipr);
329 return ((opt == 1) ^ (c == 'R')) ? Respond_internal : respond_internal;
332 FL int
333 respond(void *v)
335 return (respond_or_Respond('r'))((int *)v, 0);
338 FL int
339 respondall(void *v)
341 return respond_internal((int *)v, 0);
344 FL int
345 respondsender(void *v)
347 return Respond_internal((int *)v, 0);
350 FL int
351 followup(void *v)
353 return (respond_or_Respond('r'))((int *)v, 1);
356 FL int
357 followupall(void *v)
359 return respond_internal((int *)v, 1);
362 FL int
363 followupsender(void *v)
365 return Respond_internal((int *)v, 1);
369 * Reply to a single message. Extract each name from the
370 * message header and send them off to mail1()
372 static int
373 respond_internal(int *msgvec, int recipient_record)
375 struct header head;
376 struct message *mp;
377 char *cp, *rcv;
378 struct name *np = NULL;
379 enum gfield gf = ok_blook(fullnames) ? GFULL : GSKIN;
381 if (msgvec[1] != 0) {
382 fprintf(stderr, tr(37,
383 "Sorry, can't reply to multiple messages at once\n"));
384 return 1;
386 mp = &message[msgvec[0] - 1];
387 touch(mp);
388 setdot(mp);
390 if ((rcv = hfield1("reply-to", mp)) == NULL)
391 if ((rcv = hfield1("from", mp)) == NULL)
392 rcv = nameof(mp, 1);
393 if (rcv != NULL)
394 np = lextract(rcv, GTO|gf);
395 if (!ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
396 np = cat(np, lextract(cp, GTO | gf));
398 * Delete my name from the reply list,
399 * and with it, all my alternate names.
401 np = elide(delete_alternates(np));
402 if (np == NULL)
403 np = lextract(rcv, GTO | gf);
405 memset(&head, 0, sizeof head);
406 head.h_to = np;
407 head.h_subject = hfield1("subject", mp);
408 head.h_subject = _reedit(head.h_subject);
409 /* Cc: */
410 np = NULL;
411 if (ok_blook(recipients_in_cc) && (cp = hfield1("to", mp)) != NULL)
412 np = lextract(cp, GCC | gf);
413 if ((cp = hfield1("cc", mp)) != NULL)
414 np = cat(np, lextract(cp, GCC | gf));
415 if (np != NULL)
416 head.h_cc = elide(delete_alternates(np));
417 make_ref_and_cs(mp, &head);
419 if (ok_blook(quote_as_attachment)) {
420 head.h_attach = csalloc(1, sizeof *head.h_attach);
421 head.h_attach->a_msgno = *msgvec;
422 head.h_attach->a_content_description = tr(512,
423 "Original message content");
426 if (mail1(&head, 1, mp, NULL, recipient_record, 0) == OKAY &&
427 ok_blook(markanswered) &&
428 (mp->m_flag & MANSWERED) == 0)
429 mp->m_flag |= MANSWER | MANSWERED;
430 return 0;
434 * Forward a message to a new recipient, in the sense of RFC 2822.
436 static int
437 forward1(char *str, int recipient_record)
439 int *msgvec;
440 char *recipient;
441 struct message *mp;
442 struct header head;
443 bool_t f, forward_as_attachment;
445 forward_as_attachment = ok_blook(forward_as_attachment);
446 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
447 if ((recipient = laststring(str, &f, 0)) == NULL) {
448 puts(tr(47, "No recipient specified."));
449 return 1;
451 if (!f) {
452 *msgvec = first(0, MMNORM);
453 if (*msgvec == 0) {
454 if (inhook)
455 return 0;
456 printf("No messages to forward.\n");
457 return 1;
459 msgvec[1] = 0;
460 } else if (getmsglist(str, msgvec, 0) < 0)
461 return 1;
462 if (*msgvec == 0) {
463 if (inhook)
464 return 0;
465 printf("No applicable messages.\n");
466 return 1;
468 if (msgvec[1] != 0) {
469 printf("Cannot forward multiple messages at once\n");
470 return 1;
472 memset(&head, 0, sizeof head);
473 if ((head.h_to = lextract(recipient,
474 GTO | (ok_blook(fullnames) ? GFULL : GSKIN))) == NULL)
475 return 1;
476 mp = &message[*msgvec - 1];
477 if (forward_as_attachment) {
478 head.h_attach = csalloc(1, sizeof *head.h_attach);
479 head.h_attach->a_msgno = *msgvec;
480 head.h_attach->a_content_description = "Forwarded message";
481 } else {
482 touch(mp);
483 setdot(mp);
485 head.h_subject = hfield1("subject", mp);
486 head.h_subject = fwdedit(head.h_subject);
487 mail1(&head, 1, (forward_as_attachment ? NULL : mp),
488 NULL, recipient_record, 1);
489 return 0;
493 * Modify the subject we are replying to to begin with Fwd:.
495 static char *
496 fwdedit(char *subj)
498 struct str in, out;
499 char *newsubj;
501 if (subj == NULL || *subj == '\0')
502 return NULL;
503 in.s = subj;
504 in.l = strlen(subj);
505 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
507 newsubj = salloc(out.l + 6);
508 memcpy(newsubj, "Fwd: ", 5);
509 memcpy(newsubj + 5, out.s, out.l + 1);
510 free(out.s);
511 return newsubj;
515 * The 'forward' command.
517 FL int
518 forwardcmd(void *v)
520 return forward1(v, 0);
524 * Similar to forward, saving the message in a file named after the
525 * first recipient.
527 FL int
528 Forwardcmd(void *v)
530 return forward1(v, 1);
534 * Preserve the named messages, so that they will be sent
535 * back to the system mailbox.
537 FL int
538 preserve(void *v)
540 int *msgvec = v;
541 struct message *mp;
542 int *ip, mesg;
544 if (edit) {
545 printf(tr(39, "Cannot \"preserve\" in edit mode\n"));
546 return(1);
548 for (ip = msgvec; *ip != 0; ip++) {
549 mesg = *ip;
550 mp = &message[mesg-1];
551 mp->m_flag |= MPRESERVE;
552 mp->m_flag &= ~MBOX;
553 setdot(mp);
555 * This is now Austin Group Request XCU #20.
557 did_print_dot = TRU1;
559 return(0);
563 * Mark all given messages as unread.
565 FL int
566 unread(void *v)
568 int *msgvec = v;
569 int *ip;
571 for (ip = msgvec; *ip != 0; ip++) {
572 setdot(&message[*ip-1]);
573 dot->m_flag &= ~(MREAD|MTOUCH);
574 dot->m_flag |= MSTATUS;
575 #ifdef HAVE_IMAP
576 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
577 imap_unread(&message[*ip-1], *ip); /* TODO return? */
578 #endif
580 * The "unread" command is not part of POSIX mailx.
582 did_print_dot = TRU1;
584 return(0);
588 * Mark all given messages as read.
590 FL int
591 seen(void *v)
593 int *msgvec = v;
594 int *ip;
596 for (ip = msgvec; *ip; ip++) {
597 setdot(&message[*ip-1]);
598 touch(&message[*ip-1]);
600 return 0;
604 * Print the size of each message.
606 FL int
607 messize(void *v)
609 int *msgvec = v;
610 struct message *mp;
611 int *ip, mesg;
613 for (ip = msgvec; *ip != 0; ip++) {
614 mesg = *ip;
615 mp = &message[mesg-1];
616 printf("%d: ", mesg);
617 if (mp->m_xlines > 0)
618 printf("%ld", mp->m_xlines);
619 else
620 putchar(' ');
621 printf("/%lu\n", (unsigned long)mp->m_xsize);
623 return(0);
627 * Quit quickly. If we are sourcing, just pop the input level
628 * by returning an error.
630 /*ARGSUSED*/
631 FL int
632 rexit(void *v)
634 (void)v;
635 if (sourcing)
636 return(1);
637 exit(0);
638 /*NOTREACHED*/
641 FL int
642 set(void *v)
644 char **ap = v, *cp, *cp2, *varbuf, c;
645 int errs = 0;
647 if (*ap == NULL) {
648 var_list_all();
649 goto jleave;
652 for (; *ap != NULL; ++ap) {
653 cp = *ap;
654 cp2 = varbuf = ac_alloc(strlen(cp) + 1);
655 for (; (c = *cp) != '=' && c != '\0'; ++cp)
656 *cp2++ = c;
657 *cp2 = '\0';
658 if (c == '\0')
659 cp = UNCONST("");
660 else
661 ++cp;
662 if (varbuf == cp2) {
663 fprintf(stderr,
664 tr(41, "Non-null variable name required\n"));
665 ++errs;
666 goto jnext;
668 if (varbuf[0] == 'n' && varbuf[1] == 'o')
669 errs += _var_vokclear(&varbuf[2]);
670 else
671 errs += _var_vokset(varbuf, (uintptr_t)cp);
672 jnext:
673 ac_free(varbuf);
675 jleave:
676 return (errs);
680 * Unset a bunch of variable values.
682 FL int
683 unset(void *v)
685 int errs;
686 char **ap;
688 errs = 0;
689 for (ap = (char**)v; *ap != NULL; ap++)
690 errs += _var_vokclear(*ap);
691 return errs;
695 * Put add users to a group.
697 FL int
698 group(void *v)
700 char **argv = v;
701 struct grouphead *gh;
702 struct group *gp;
703 int h;
704 int s;
705 char **ap, *gname, **p;
707 if (*argv == NULL) {
708 for (h = 0, s = 1; h < HSHSIZE; h++)
709 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
710 s++;
711 /*LINTED*/
712 ap = (char **)salloc(s * sizeof *ap);
713 for (h = 0, p = ap; h < HSHSIZE; h++)
714 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
715 *p++ = gh->g_name;
716 *p = NULL;
717 asort(ap);
718 for (p = ap; *p != NULL; p++)
719 printgroup(*p);
720 return(0);
722 if (argv[1] == NULL) {
723 printgroup(*argv);
724 return(0);
726 gname = *argv;
727 h = hash(gname);
728 if ((gh = findgroup(gname)) == NULL) {
729 gh = (struct grouphead *)scalloc(1, sizeof *gh);
730 gh->g_name = sstrdup(gname);
731 gh->g_list = NULL;
732 gh->g_link = groups[h];
733 groups[h] = gh;
737 * Insert names from the command list into the group.
738 * Who cares if there are duplicates? They get tossed
739 * later anyway.
742 for (ap = argv+1; *ap != NULL; ap++) {
743 gp = (struct group *)scalloc(1, sizeof *gp);
744 gp->ge_name = sstrdup(*ap);
745 gp->ge_link = gh->g_list;
746 gh->g_list = gp;
748 return(0);
752 * Delete the passed groups.
754 FL int
755 ungroup(void *v)
757 char **argv = v;
759 if (*argv == NULL) {
760 fprintf(stderr, tr(209, "Must specify alias to remove\n"));
761 return 1;
764 remove_group(*argv);
765 while (*++argv != NULL);
766 return 0;
770 * Sort the passed string vecotor into ascending dictionary
771 * order.
773 static void
774 asort(char **list)
776 char **ap;
778 for (ap = list; *ap != NULL; ap++)
780 if (ap-list < 2)
781 return;
782 qsort(list, ap-list, sizeof(*list), diction);
786 * Do a dictionary order comparison of the arguments from
787 * qsort.
789 static int
790 diction(const void *a, const void *b)
792 return(strcmp(*(char**)UNCONST(a), *(char**)UNCONST(b)));
796 * Change to another file. With no argument, print information about
797 * the current file.
799 FL int
800 cfile(void *v)
802 char **argv = v;
803 int i;
805 if (*argv == NULL) {
806 newfileinfo();
807 return 0;
810 if (inhook) {
811 fprintf(stderr, tr(516,
812 "Cannot change folder from within a hook.\n"));
813 return 1;
816 save_mbox_for_possible_quitstuff();
818 i = setfile(*argv, 0);
819 if (i < 0)
820 return 1;
821 callhook(mailname, 0);
822 if (i > 0 && !ok_blook(emptystart))
823 return 1;
824 announce(ok_blook(bsdcompat) || ok_blook(bsdannounce));
825 return 0;
829 * Expand file names like echo
831 FL int
832 echo(void *v)
834 char const **argv = v, **ap, *cp;
835 int c;
837 for (ap = argv; *ap != NULL; ++ap) {
838 cp = *ap;
839 if ((cp = fexpand(cp, FEXP_NSHORTCUT)) != NULL) {
840 if (ap != argv)
841 putchar(' ');
842 c = 0;
843 while (*cp != '\0' &&
844 (c = expand_shell_escape(&cp, FAL0))
845 > 0)
846 putchar(c);
847 /* \c ends overall processing */
848 if (c < 0)
849 goto jleave;
852 putchar('\n');
853 jleave:
854 return 0;
857 FL int
858 Respond(void *v)
860 return (respond_or_Respond('R'))((int *)v, 0);
863 FL int
864 Followup(void *v)
866 return (respond_or_Respond('R'))((int *)v, 1);
870 * Reply to a series of messages by simply mailing to the senders
871 * and not messing around with the To: and Cc: lists as in normal
872 * reply.
874 static int
875 Respond_internal(int *msgvec, int recipient_record)
877 struct header head;
878 struct message *mp;
879 int *ap;
880 char *cp;
881 enum gfield gf = ok_blook(fullnames) ? GFULL : GSKIN;
883 memset(&head, 0, sizeof head);
885 for (ap = msgvec; *ap != 0; ap++) {
886 mp = &message[*ap - 1];
887 touch(mp);
888 setdot(mp);
889 if ((cp = hfield1("reply-to", mp)) == NULL)
890 if ((cp = hfield1("from", mp)) == NULL)
891 cp = nameof(mp, 2);
892 head.h_to = cat(head.h_to, lextract(cp, GTO | gf));
894 if (head.h_to == NULL)
895 return 0;
897 mp = &message[msgvec[0] - 1];
898 head.h_subject = hfield1("subject", mp);
899 head.h_subject = _reedit(head.h_subject);
900 make_ref_and_cs(mp, &head);
902 if (ok_blook(quote_as_attachment)) {
903 head.h_attach = csalloc(1, sizeof *head.h_attach);
904 head.h_attach->a_msgno = *msgvec;
905 head.h_attach->a_content_description = tr(512,
906 "Original message content");
909 if (mail1(&head, 1, mp, NULL, recipient_record, 0) == OKAY &&
910 ok_blook(markanswered) && (mp->m_flag & MANSWERED) == 0)
911 mp->m_flag |= MANSWER | MANSWERED;
912 return 0;
915 FL int
916 c_if(void *v)
918 int rv = 1;
919 char **argv = v, *cp, *op;
921 if (cond_state != COND_ANY) {
922 fprintf(stderr, tr(42, "Illegal nested \"if\"\n"));
923 goto jleave;
926 cp = argv[0];
927 if (*cp != '$' && argv[1] != NULL) {
928 jesyn:
929 fprintf(stderr, tr(528,
930 "Invalid conditional expression \"%s %s %s\"\n"),
931 argv[0], (argv[1] != NULL ? argv[1] : ""),
932 (argv[2] != NULL ? argv[2] : ""));
933 cond_state = COND_ANY;
934 goto jleave;
937 switch (*cp) {
938 case '0':
939 cond_state = COND_NOEXEC;
940 break;
941 case '1':
942 cond_state = COND_EXEC;
943 break;
944 case 'R': case 'r':
945 cond_state = COND_RCV;
946 break;
947 case 'S': case 's':
948 cond_state = COND_SEND;
949 break;
950 case 'T': case 't':
951 cond_state = COND_TERM;
952 break;
953 case '$':
954 /* Look up the value in question, we need it anyway */
955 v = vok_vlook(++cp);
957 /* Single argument, "implicit boolean" form? */
958 if ((op = argv[1]) == NULL) {
959 cond_state = (v == NULL) ? COND_NOEXEC : COND_EXEC;
960 break;
963 /* Three argument comparison form? */
964 if (argv[2] == NULL ||
965 op[0] == '\0' || op[1] != '=' || op[2] != '\0')
966 goto jesyn;
967 /* A null value is treated as the empty string */
968 if (v == NULL)
969 v = UNCONST("");
970 if (strcmp(v, argv[2]))
971 v = NULL;
972 switch (op[0]) {
973 case '!':
974 case '=':
975 cond_state = (((op[0] == '!') ^ (v == NULL))
976 ? COND_NOEXEC : COND_EXEC);
977 break;
978 default:
979 goto jesyn;
981 break;
982 default:
983 fprintf(stderr, tr(43, "Unrecognized if-keyword: \"%s\"\n"),
984 cp);
985 cond_state = COND_ANY;
986 goto jleave;
988 rv = 0;
989 jleave:
990 return rv;
993 FL int
994 c_else(void *v)
996 int rv = 1;
997 UNUSED(v);
999 switch (cond_state) {
1000 case COND_ANY:
1001 fprintf(stderr, tr(44, "\"Else\" without matching \"if\"\n"));
1002 goto jleave;
1003 case COND_SEND:
1004 cond_state = COND_RCV;
1005 break;
1006 case COND_RCV:
1007 cond_state = COND_SEND;
1008 break;
1009 case COND_TERM:
1010 cond_state = COND_NOTERM;
1011 break;
1012 case COND_EXEC:
1013 cond_state = COND_NOEXEC;
1014 break;
1015 case COND_NOEXEC:
1016 cond_state = COND_EXEC;
1017 break;
1018 default:
1019 fprintf(stderr, tr(45,
1020 "Mail's idea of conditions is screwed up\n"));
1021 cond_state = COND_ANY;
1022 goto jleave;
1024 rv = 0;
1025 jleave:
1026 return rv;
1029 FL int
1030 c_endif(void *v)
1032 int rv;
1033 UNUSED(v);
1035 if (cond_state == COND_ANY) {
1036 fprintf(stderr, tr(46, "\"Endif\" without matching \"if\"\n"));
1037 rv = 1;
1038 } else {
1039 cond_state = COND_ANY;
1040 rv = 0;
1042 return rv;
1046 * Set the list of alternate names.
1048 FL int
1049 alternates(void *v)
1051 size_t l;
1052 char **namelist = v, **ap, **ap2, *cp;
1054 l = argcount(namelist) + 1;
1056 if (l == 1) {
1057 if (altnames == NULL)
1058 goto jleave;
1059 for (ap = altnames; *ap != NULL; ++ap)
1060 printf("%s ", *ap);
1061 printf("\n");
1062 goto jleave;
1065 if (altnames != NULL) {
1066 for (ap = altnames; *ap != NULL; ++ap)
1067 free(*ap);
1068 free(altnames);
1070 altnames = smalloc(l * sizeof(char*));
1071 for (ap = namelist, ap2 = altnames; *ap; ++ap, ++ap2) {
1072 l = strlen(*ap) + 1;
1073 cp = smalloc(l);
1074 memcpy(cp, *ap, l);
1075 *ap2 = cp;
1077 *ap2 = NULL;
1078 jleave:
1079 return (0);
1083 * Do the real work of resending.
1085 static int
1086 resend1(void *v, int add_resent)
1088 char *name, *str;
1089 struct name *to;
1090 struct name *sn;
1091 int *ip, *msgvec;
1092 bool_t f;
1094 str = (char *)v;
1095 /*LINTED*/
1096 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1097 name = laststring(str, &f, 1);
1098 if (name == NULL) {
1099 puts(tr(47, "No recipient specified."));
1100 return 1;
1102 if (!f) {
1103 *msgvec = first(0, MMNORM);
1104 if (*msgvec == 0) {
1105 if (inhook)
1106 return 0;
1107 puts(tr(48, "No applicable messages."));
1108 return 1;
1110 msgvec[1] = 0;
1111 } else if (getmsglist(str, msgvec, 0) < 0)
1112 return 1;
1113 if (*msgvec == 0) {
1114 if (inhook)
1115 return 0;
1116 printf("No applicable messages.\n");
1117 return 1;
1119 sn = nalloc(name, GTO);
1120 to = usermap(sn, FAL0);
1121 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
1122 if (resend_msg(&message[*ip - 1], to, add_resent) != OKAY)
1123 return 1;
1125 return 0;
1129 * Resend a message list to a third person.
1131 FL int
1132 resendcmd(void *v)
1134 return resend1(v, 1);
1138 * Resend a message list to a third person without adding headers.
1140 FL int
1141 Resendcmd(void *v)
1143 return resend1(v, 0);
1147 * 'newmail' or 'inc' command: Check for new mail without writing old
1148 * mail back.
1150 /*ARGSUSED*/
1151 FL int
1152 newmail(void *v)
1154 int val = 1, mdot;
1155 (void)v;
1157 if (
1158 #ifdef HAVE_IMAP
1159 (mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1160 #endif
1161 (val = setfile(mailname, 1)) == 0) {
1162 mdot = getmdot(1);
1163 setdot(&message[mdot - 1]);
1165 return val;
1168 static void
1169 list_shortcuts(void)
1171 struct shortcut *s;
1173 for (s = shortcuts; s; s = s->sh_next)
1174 printf("%s=%s\n", s->sh_short, s->sh_long);
1177 FL int
1178 shortcut(void *v)
1180 char **args = (char **)v;
1181 struct shortcut *s;
1183 if (args[0] == NULL) {
1184 list_shortcuts();
1185 return 0;
1187 if (args[1] == NULL) {
1188 fprintf(stderr, tr(220,
1189 "expansion name for shortcut missing\n"));
1190 return 1;
1192 if (args[2] != NULL) {
1193 fprintf(stderr, tr(221, "too many arguments\n"));
1194 return 1;
1196 if ((s = get_shortcut(args[0])) != NULL) {
1197 free(s->sh_long);
1198 s->sh_long = sstrdup(args[1]);
1199 } else {
1200 s = scalloc(1, sizeof *s);
1201 s->sh_short = sstrdup(args[0]);
1202 s->sh_long = sstrdup(args[1]);
1203 s->sh_next = shortcuts;
1204 shortcuts = s;
1206 return 0;
1209 FL struct shortcut *
1210 get_shortcut(const char *str)
1212 struct shortcut *s;
1214 for (s = shortcuts; s; s = s->sh_next)
1215 if (strcmp(str, s->sh_short) == 0)
1216 break;
1217 return s;
1220 static enum okay
1221 delete_shortcut(const char *str)
1223 struct shortcut *sp, *sq;
1225 for (sp = shortcuts, sq = NULL; sp; sq = sp, sp = sp->sh_next) {
1226 if (strcmp(sp->sh_short, str) == 0) {
1227 free(sp->sh_short);
1228 free(sp->sh_long);
1229 if (sq)
1230 sq->sh_next = sp->sh_next;
1231 if (sp == shortcuts)
1232 shortcuts = sp->sh_next;
1233 free(sp);
1234 return OKAY;
1237 return STOP;
1240 FL int
1241 unshortcut(void *v)
1243 char **args = (char **)v;
1244 int errs = 0;
1246 if (args[0] == NULL) {
1247 fprintf(stderr, tr(222, "need shortcut names to remove\n"));
1248 return 1;
1250 while (*args != NULL) {
1251 if (delete_shortcut(*args) != OKAY) {
1252 errs = 1;
1253 fprintf(stderr, tr(223, "%s: no such shortcut\n"),
1254 *args);
1256 args++;
1258 return errs;
1261 FL int
1262 cflag(void *v)
1264 struct message *m;
1265 int *msgvec = v;
1266 int *ip;
1268 for (ip = msgvec; *ip != 0; ip++) {
1269 m = &message[*ip-1];
1270 setdot(m);
1271 if ((m->m_flag & (MFLAG|MFLAGGED)) == 0)
1272 m->m_flag |= MFLAG|MFLAGGED;
1274 return 0;
1277 FL int
1278 cunflag(void *v)
1280 struct message *m;
1281 int *msgvec = v;
1282 int *ip;
1284 for (ip = msgvec; *ip != 0; ip++) {
1285 m = &message[*ip-1];
1286 setdot(m);
1287 if (m->m_flag & (MFLAG|MFLAGGED)) {
1288 m->m_flag &= ~(MFLAG|MFLAGGED);
1289 m->m_flag |= MUNFLAG;
1292 return 0;
1295 FL int
1296 canswered(void *v)
1298 struct message *m;
1299 int *msgvec = v;
1300 int *ip;
1302 for (ip = msgvec; *ip != 0; ip++) {
1303 m = &message[*ip-1];
1304 setdot(m);
1305 if ((m->m_flag & (MANSWER|MANSWERED)) == 0)
1306 m->m_flag |= MANSWER|MANSWERED;
1308 return 0;
1311 FL int
1312 cunanswered(void *v)
1314 struct message *m;
1315 int *msgvec = v;
1316 int *ip;
1318 for (ip = msgvec; *ip != 0; ip++) {
1319 m = &message[*ip-1];
1320 setdot(m);
1321 if (m->m_flag & (MANSWER|MANSWERED)) {
1322 m->m_flag &= ~(MANSWER|MANSWERED);
1323 m->m_flag |= MUNANSWER;
1326 return 0;
1329 FL int
1330 cdraft(void *v)
1332 struct message *m;
1333 int *msgvec = v;
1334 int *ip;
1336 for (ip = msgvec; *ip != 0; ip++) {
1337 m = &message[*ip-1];
1338 setdot(m);
1339 if ((m->m_flag & (MDRAFT|MDRAFTED)) == 0)
1340 m->m_flag |= MDRAFT|MDRAFTED;
1342 return 0;
1345 FL int
1346 cundraft(void *v)
1348 struct message *m;
1349 int *msgvec = v;
1350 int *ip;
1352 for (ip = msgvec; *ip != 0; ip++) {
1353 m = &message[*ip-1];
1354 setdot(m);
1355 if (m->m_flag & (MDRAFT|MDRAFTED)) {
1356 m->m_flag &= ~(MDRAFT|MDRAFTED);
1357 m->m_flag |= MUNDRAFT;
1360 return 0;
1363 /*ARGSUSED*/
1364 FL int
1365 cnoop(void *v)
1367 (void)v;
1369 switch (mb.mb_type) {
1370 case MB_IMAP:
1371 #ifdef HAVE_IMAP
1372 imap_noop();
1373 break;
1374 #else
1375 return (ccmdnotsupp(NULL));
1376 #endif
1377 case MB_POP3:
1378 #ifdef HAVE_POP3
1379 pop3_noop();
1380 break;
1381 #else
1382 return (ccmdnotsupp(NULL));
1383 #endif
1384 default:
1385 break;
1387 return 0;
1390 FL int
1391 cremove(void *v)
1393 char vb[LINESIZE];
1394 char **args = v;
1395 char *name;
1396 int ec = 0;
1398 if (*args == NULL) {
1399 fprintf(stderr, tr(290, "Syntax is: remove mailbox ...\n"));
1400 return (1);
1402 do {
1403 if ((name = expand(*args)) == NULL)
1404 continue;
1405 if (strcmp(name, mailname) == 0) {
1406 fprintf(stderr, tr(286,
1407 "Cannot remove current mailbox \"%s\".\n"),
1408 name);
1409 ec |= 1;
1410 continue;
1412 snprintf(vb, sizeof vb, tr(287, "Remove \"%s\" (y/n) ? "),
1413 name);
1414 if (yorn(vb) == 0)
1415 continue;
1416 switch (which_protocol(name)) {
1417 case PROTO_FILE:
1418 if (unlink(name) < 0) { /* do not handle .gz .bz2 */
1419 perror(name);
1420 ec |= 1;
1422 break;
1423 case PROTO_POP3:
1424 fprintf(stderr, tr(288,
1425 "Cannot remove POP3 mailbox \"%s\".\n"),
1426 name);
1427 ec |= 1;
1428 break;
1429 case PROTO_IMAP:
1430 #ifdef HAVE_IMAP
1431 if (imap_remove(name) != OKAY)
1432 #endif
1433 ec |= 1;
1434 break;
1435 case PROTO_MAILDIR:
1436 if (maildir_remove(name) != OKAY)
1437 ec |= 1;
1438 break;
1439 case PROTO_UNKNOWN:
1440 fprintf(stderr, tr(289,
1441 "Unknown protocol in \"%s\". Not removed.\n"),
1442 name);
1443 ec |= 1;
1445 } while (*++args);
1446 return ec;
1449 FL int
1450 crename(void *v)
1452 char **args = v, *old, *new;
1453 enum protocol oldp, newp;
1454 int ec = 0;
1456 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1457 fprintf(stderr, "Syntax: rename old new\n");
1458 return (1);
1461 if ((old = expand(args[0])) == NULL)
1462 return (1);
1463 oldp = which_protocol(old);
1464 if ((new = expand(args[1])) == NULL)
1465 return (1);
1466 newp = which_protocol(new);
1468 if (strcmp(old, mailname) == 0 || strcmp(new, mailname) == 0) {
1469 fprintf(stderr, tr(291,
1470 "Cannot rename current mailbox \"%s\".\n"), old);
1471 return 1;
1473 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1474 fprintf(stderr, tr(292,
1475 "Can only rename folders of same type.\n"));
1476 return 1;
1478 if (newp == PROTO_POP3)
1479 goto nopop3;
1480 switch (oldp) {
1481 case PROTO_FILE:
1482 if (link(old, new) < 0) {
1483 switch (errno) {
1484 case EACCES:
1485 case EEXIST:
1486 case ENAMETOOLONG:
1487 case ENOENT:
1488 case ENOSPC:
1489 case EXDEV:
1490 perror(new);
1491 break;
1492 default:
1493 perror(old);
1495 ec |= 1;
1496 } else if (unlink(old) < 0) {
1497 perror(old);
1498 ec |= 1;
1500 break;
1501 case PROTO_MAILDIR:
1502 if (rename(old, new) < 0) {
1503 perror(old);
1504 ec |= 1;
1506 break;
1507 case PROTO_POP3:
1508 nopop3: fprintf(stderr, tr(293, "Cannot rename POP3 mailboxes.\n"));
1509 ec |= 1;
1510 break;
1511 #ifdef HAVE_IMAP
1512 case PROTO_IMAP:
1513 if (imap_rename(old, new) != OKAY)
1514 ec |= 1;
1515 break;
1516 #endif
1517 case PROTO_UNKNOWN:
1518 default:
1519 fprintf(stderr, tr(294,
1520 "Unknown protocol in \"%s\" and \"%s\". "
1521 "Not renamed.\n"), old, new);
1522 ec |= 1;
1524 return ec;