Drop SNAIL_ nonsense, use plain NAIL_..
[s-mailx.git] / cmd3.c
blob0fd2b11e4b006a179b31c30db78efdc161737f77
1 /*
2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
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 #include <math.h>
41 #include <float.h>
42 #include "rcv.h"
43 #include "extern.h"
44 #include <unistd.h>
45 #include <errno.h>
48 * Mail -- a mail program
50 * Still more user commands.
53 static int bangexp(char **str, size_t *size);
54 static void make_ref_and_cs(struct message *mp, struct header *head);
55 static int (*respond_or_Respond(int c))(int *, int);
56 static int respond_internal(int *msgvec, int recipient_record);
57 static char *reedit(char *subj);
58 static char *fwdedit(char *subj);
59 static void onpipe(int signo);
60 static void asort(char **list);
61 static int diction(const void *a, const void *b);
62 static int file1(char *name);
63 static int shellecho(const char *cp);
64 static int Respond_internal(int *msgvec, int recipient_record);
65 static int resend1(void *v, int add_resent);
66 static void list_shortcuts(void);
67 static enum okay delete_shortcut(const char *str);
68 static float huge(void);
71 * Process a shell escape by saving signals, ignoring signals,
72 * and forking a sh -c
74 int
75 shell(void *v)
77 char *str = v;
78 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
79 char *shell;
80 char *cmd;
81 size_t cmdsize;
83 cmd = smalloc(cmdsize = strlen(str) + 1);
84 strcpy(cmd, str);
85 if (bangexp(&cmd, &cmdsize) < 0)
86 return 1;
87 if ((shell = value("SHELL")) == NULL)
88 shell = SHELL;
89 run_command(shell, 0, -1, -1, "-c", cmd, NULL);
90 safe_signal(SIGINT, sigint);
91 printf("!\n");
92 free(cmd);
93 return 0;
97 * Fork an interactive shell.
99 /*ARGSUSED*/
100 int
101 dosh(void *v)
103 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
104 char *shell;
105 (void)v;
107 if ((shell = value("SHELL")) == NULL)
108 shell = SHELL;
109 run_command(shell, 0, -1, -1, NULL, NULL, NULL);
110 safe_signal(SIGINT, sigint);
111 putchar('\n');
112 return 0;
116 * Expand the shell escape by expanding unescaped !'s into the
117 * last issued command where possible.
120 static char *lastbang;
121 static size_t lastbangsize;
123 static int
124 bangexp(char **str, size_t *size)
126 char *bangbuf;
127 int changed = 0;
128 int dobang = value("bang") != NULL;
129 size_t sz, i, j, bangbufsize;
131 bangbuf = smalloc(bangbufsize = *size);
132 i = j = 0;
133 while ((*str)[i]) {
134 if (dobang) {
135 if ((*str)[i] == '!') {
136 sz = strlen(lastbang);
137 bangbuf = srealloc(bangbuf, bangbufsize += sz);
138 changed++;
139 strcpy(&bangbuf[j], lastbang);
140 j += sz;
141 i++;
142 continue;
145 if ((*str)[i] == '\\' && (*str)[i + 1] == '!') {
146 bangbuf[j++] = '!';
147 i += 2;
148 changed++;
150 bangbuf[j++] = (*str)[i++];
152 bangbuf[j] = '\0';
153 if (changed) {
154 printf("!%s\n", bangbuf);
155 fflush(stdout);
157 sz = j;
158 if (sz >= *size)
159 *str = srealloc(*str, *size = sz + 1);
160 strcpy(*str, bangbuf);
161 if (sz >= lastbangsize)
162 lastbang = srealloc(lastbang, lastbangsize = sz + 1);
163 strcpy(lastbang, bangbuf);
164 free(bangbuf);
165 return(0);
168 /*ARGSUSED*/
169 int
170 help(void *v)
172 (void)v;
173 /* Very ugly, but take care for compiler supported string lengths :( */
174 fprintf(stdout,
175 "%s commands:\n",
176 progname);
177 puts(
178 "type <message list> type messages\n"
179 "next goto and type next message\n"
180 "from <message list> give head lines of messages\n"
181 "headers print out active message headers\n"
182 "delete <message list> delete messages\n"
183 "undelete <message list> undelete messages\n");
184 puts(
185 "save <message list> folder append messages to folder and mark as saved\n"
186 "copy <message list> folder append messages to folder without marking them\n"
187 "write <message list> file append message texts to file, save attachments\n"
188 "preserve <message list> keep incoming messages in mailbox even if saved\n"
189 "Reply <message list> reply to message senders\n"
190 "reply <message list> reply to message senders and all recipients\n");
191 puts(
192 "mail addresses mail to specific recipients\n"
193 "file folder change to another folder\n"
194 "quit quit and apply changes to folder\n"
195 "xit quit and discard changes made to folder\n"
196 "! shell escape\n"
197 "cd <directory> chdir to directory or home if none given\n"
198 "list list names of all available commands\n");
199 fprintf(stdout,
200 "\nA <message list> consists of integers, ranges of same, or other criteria\n"
201 "separated by spaces. If omitted, %s uses the last message typed.\n",
202 progname);
203 return(0);
207 * Change user's working directory.
209 int
210 schdir(void *v)
212 char **arglist = v;
213 char *cp;
215 if (*arglist == NULL)
216 cp = homedir;
217 else
218 if ((cp = expand(*arglist)) == NULL)
219 return(1);
220 if (chdir(cp) < 0) {
221 perror(cp);
222 return(1);
224 return 0;
227 static void
228 make_ref_and_cs(struct message *mp, struct header *head)
230 char *oldref, *oldmsgid, *newref, *cp;
231 size_t reflen;
232 unsigned i;
233 struct name *n;
235 oldref = hfield("references", mp);
236 oldmsgid = hfield("message-id", mp);
237 if (oldmsgid == NULL || *oldmsgid == '\0') {
238 head->h_ref = NULL;
239 return;
241 reflen = 1;
242 if (oldref)
243 reflen += strlen(oldref) + 2;
244 if (oldmsgid)
245 reflen += strlen(oldmsgid);
246 newref = ac_alloc(reflen);
247 if (oldref) {
248 strcpy(newref, oldref);
249 if (oldmsgid) {
250 strcat(newref, ", ");
251 strcat(newref, oldmsgid);
253 } else if (oldmsgid)
254 strcpy(newref, oldmsgid);
255 n = extract(newref, GREF);
256 ac_free(newref);
258 * Limit the references to 21 entries.
260 while (n->n_flink != NULL)
261 n = n->n_flink;
262 for (i = 1; i < 21; i++) {
263 if (n->n_blink != NULL)
264 n = n->n_blink;
265 else
266 break;
268 n->n_blink = NULL;
269 head->h_ref = n;
270 if (value("reply-in-same-charset") != NULL &&
271 (cp = hfield("content-type", mp)) != NULL)
272 head->h_charset = mime_getparam("charset", cp);
275 static int
276 (*respond_or_Respond(int c))(int *, int)
278 int opt = 0;
280 opt += (value("Replyall") != NULL);
281 opt += (value("flipr") != NULL);
282 return ((opt == 1) ^ (c == 'R')) ? Respond_internal : respond_internal;
285 int
286 respond(void *v)
288 return (respond_or_Respond('r'))((int *)v, 0);
291 int
292 respondall(void *v)
294 return respond_internal((int *)v, 0);
297 int
298 respondsender(void *v)
300 return Respond_internal((int *)v, 0);
303 int
304 followup(void *v)
306 return (respond_or_Respond('r'))((int *)v, 1);
309 int
310 followupall(void *v)
312 return respond_internal((int *)v, 1);
315 int
316 followupsender(void *v)
318 return Respond_internal((int *)v, 1);
322 * Reply to a list of messages. Extract each name from the
323 * message header and send them off to mail1()
325 static int
326 respond_internal(int *msgvec, int recipient_record)
328 int Eflag;
329 struct message *mp;
330 char *cp, *rcv;
331 enum gfield gf = value("fullnames") ? GFULL : GSKIN;
332 struct name *np = NULL;
333 struct header head;
335 memset(&head, 0, sizeof head);
336 if (msgvec[1] != 0) {
337 printf(catgets(catd, CATSET, 37,
338 "Sorry, can't reply to multiple messages at once\n"));
339 return(1);
341 mp = &message[msgvec[0] - 1];
342 touch(mp);
343 setdot(mp);
344 if ((rcv = hfield("reply-to", mp)) == NULL)
345 if ((rcv = hfield("from", mp)) == NULL)
346 rcv = nameof(mp, 1);
347 if (rcv != NULL)
348 np = sextract(rcv, GTO|gf);
349 if (! value("recipients-in-cc") && (cp = hfield("to", mp)) != NULL)
350 np = cat(np, sextract(cp, GTO|gf));
351 np = elide(np);
353 * Delete my name from the reply list,
354 * and with it, all my alternate names.
356 np = delete_alternates(np);
357 if (np == NULL)
358 np = sextract(rcv, GTO|gf);
359 head.h_to = np;
360 if ((head.h_subject = hfield("subject", mp)) == NULL)
361 head.h_subject = hfield("subj", mp);
362 head.h_subject = reedit(head.h_subject);
363 /* Cc: */
364 np = NULL;
365 if (value("recipients-in-cc") && (cp = hfield("to", mp)) != NULL)
366 np = sextract(cp, GCC|gf);
367 if ((cp = hfield("cc", mp)) != NULL)
368 np = cat(np, sextract(cp, GCC|gf));
369 if (np != NULL)
370 head.h_cc = elide(delete_alternates(np));
371 make_ref_and_cs(mp, &head);
372 Eflag = value("skipemptybody") != NULL;
373 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
374 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
375 mp->m_flag |= MANSWER|MANSWERED;
376 return(0);
380 * Modify the subject we are replying to to begin with Re: if
381 * it does not already.
383 static char *
384 reedit(char *subj)
386 char *newsubj;
387 struct str in, out;
389 if (subj == NULL || *subj == '\0')
390 return NULL;
391 in.s = subj;
392 in.l = strlen(subj);
393 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
394 if ((out.s[0] == 'r' || out.s[0] == 'R') &&
395 (out.s[1] == 'e' || out.s[1] == 'E') &&
396 out.s[2] == ':')
397 return out.s;
398 newsubj = salloc(out.l + 5);
399 strcpy(newsubj, "Re: ");
400 strcpy(newsubj + 4, out.s);
401 return newsubj;
405 * Forward a message to a new recipient, in the sense of RFC 2822.
407 static int
408 forward1(char *str, int recipient_record)
410 int Eflag;
411 int *msgvec, f;
412 char *recipient;
413 struct message *mp;
414 struct header head;
415 int forward_as_attachment;
417 forward_as_attachment = value("forward-as-attachment") != NULL;
418 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
419 if ((recipient = laststring(str, &f, 0)) == NULL) {
420 puts(catgets(catd, CATSET, 47, "No recipient specified."));
421 return 1;
423 if (!f) {
424 *msgvec = first(0, MMNORM);
425 if (*msgvec == 0) {
426 if (inhook)
427 return 0;
428 printf("No messages to forward.\n");
429 return 1;
431 msgvec[1] = 0;
433 if (f && getmsglist(str, msgvec, 0) < 0)
434 return 1;
435 if (*msgvec == 0) {
436 if (inhook)
437 return 0;
438 printf("No applicable messages.\n");
439 return 1;
441 if (msgvec[1] != 0) {
442 printf("Cannot forward multiple messages at once\n");
443 return 1;
445 memset(&head, 0, sizeof head);
446 if ((head.h_to = sextract(recipient,
447 GTO | (value("fullnames") ? GFULL : GSKIN))) == NULL)
448 return 1;
449 mp = &message[*msgvec - 1];
450 if (forward_as_attachment) {
451 head.h_attach = csalloc(1, sizeof *head.h_attach);
452 head.h_attach->a_msgno = *msgvec;
453 } else {
454 touch(mp);
455 setdot(mp);
457 if ((head.h_subject = hfield("subject", mp)) == NULL)
458 head.h_subject = hfield("subj", mp);
459 head.h_subject = fwdedit(head.h_subject);
460 Eflag = value("skipemptybody") != NULL;
461 mail1(&head, 1, forward_as_attachment ? NULL : mp,
462 NULL, recipient_record, 1, 0, Eflag);
463 return 0;
467 * Modify the subject we are replying to to begin with Fwd:.
469 static char *
470 fwdedit(char *subj)
472 char *newsubj;
473 struct str in, out;
475 if (subj == NULL || *subj == '\0')
476 return NULL;
477 in.s = subj;
478 in.l = strlen(subj);
479 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
480 newsubj = salloc(strlen(out.s) + 6);
481 strcpy(newsubj, "Fwd: ");
482 strcpy(&newsubj[5], out.s);
483 free(out.s);
484 return newsubj;
488 * The 'forward' command.
491 forwardcmd(void *v)
493 return forward1(v, 0);
497 * Similar to forward, saving the message in a file named after the
498 * first recipient.
501 Forwardcmd(void *v)
503 return forward1(v, 1);
507 * Preserve the named messages, so that they will be sent
508 * back to the system mailbox.
510 int
511 preserve(void *v)
513 int *msgvec = v;
514 struct message *mp;
515 int *ip, mesg;
517 if (edit) {
518 printf(catgets(catd, CATSET, 39,
519 "Cannot \"preserve\" in edit mode\n"));
520 return(1);
522 for (ip = msgvec; *ip != 0; ip++) {
523 mesg = *ip;
524 mp = &message[mesg-1];
525 mp->m_flag |= MPRESERVE;
526 mp->m_flag &= ~MBOX;
527 setdot(mp);
529 * This is now Austin Group Request XCU #20.
531 did_print_dot = 1;
533 return(0);
537 * Mark all given messages as unread.
539 int
540 unread(void *v)
542 int *msgvec = v;
543 int *ip;
545 for (ip = msgvec; *ip != 0; ip++) {
546 setdot(&message[*ip-1]);
547 dot->m_flag &= ~(MREAD|MTOUCH);
548 dot->m_flag |= MSTATUS;
549 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
550 imap_unread(&message[*ip-1], *ip);
552 * The "unread" command is not part of POSIX mailx.
554 did_print_dot = 1;
556 return(0);
560 * Mark all given messages as read.
563 seen(void *v)
565 int *msgvec = v;
566 int *ip;
568 for (ip = msgvec; *ip; ip++) {
569 setdot(&message[*ip-1]);
570 touch(&message[*ip-1]);
572 return 0;
576 * Print the size of each message.
578 int
579 messize(void *v)
581 int *msgvec = v;
582 struct message *mp;
583 int *ip, mesg;
585 for (ip = msgvec; *ip != 0; ip++) {
586 mesg = *ip;
587 mp = &message[mesg-1];
588 printf("%d: ", mesg);
589 if (mp->m_xlines > 0)
590 printf("%ld", mp->m_xlines);
591 else
592 putchar(' ');
593 printf("/%lu\n", (unsigned long)mp->m_xsize);
595 return(0);
599 * Quit quickly. If we are sourcing, just pop the input level
600 * by returning an error.
602 /*ARGSUSED*/
603 int
604 rexit(void *v)
606 (void)v;
607 if (sourcing)
608 return(1);
609 exit(0);
610 /*NOTREACHED*/
613 static sigjmp_buf pipejmp;
615 /*ARGSUSED*/
616 static void
617 onpipe(int signo)
619 (void)signo;
620 siglongjmp(pipejmp, 1);
624 * Set or display a variable value. Syntax is similar to that
625 * of sh.
627 int
628 set(void *v)
630 char **arglist = v;
631 struct var *vp;
632 char *cp, *cp2;
633 char **ap, **p;
634 int errs, h, s;
635 FILE *obuf = stdout;
636 int bsdset = value("bsdcompat") != NULL || value("bsdset") != NULL;
638 if (*arglist == NULL) {
639 for (h = 0, s = 1; h < HSHSIZE; h++)
640 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
641 s++;
642 /*LINTED*/
643 ap = (char **)salloc(s * sizeof *ap);
644 for (h = 0, p = ap; h < HSHSIZE; h++)
645 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
646 *p++ = vp->v_name;
647 *p = NULL;
648 asort(ap);
649 if (is_a_tty[0] && is_a_tty[1] && (cp = value("crt")) != NULL) {
650 if (s > (*cp == '\0' ? screensize() : atoi(cp)) + 3) {
651 cp = get_pager();
652 /* TODO should be below the Popen?
653 * TODO Problem: Popen doesn't encapsulate it,
654 * TODO may leave child run if fdopen() fails!
655 * TODO even more such stuff in this file! */
656 if (sigsetjmp(pipejmp, 1))
657 goto endpipe;
658 if ((obuf = Popen(cp, "w", NULL, 1)) == NULL) {
659 perror(cp);
660 obuf = stdout;
661 } else
662 safe_signal(SIGPIPE, onpipe);
665 for (p = ap; *p != NULL; p++) {
666 if (bsdset)
667 fprintf(obuf, "%s\t%s\n", *p, value(*p));
668 else {
669 if ((cp = value(*p)) != NULL && *cp)
670 fprintf(obuf, "%s=\"%s\"\n",
671 *p, value(*p));
672 else
673 fprintf(obuf, "%s\n", *p);
676 endpipe:
677 if (obuf != stdout) {
678 safe_signal(SIGPIPE, SIG_IGN);
679 Pclose(obuf);
680 safe_signal(SIGPIPE, dflpipe);
682 return(0);
684 errs = 0;
685 for (ap = arglist; *ap != NULL; ap++) {
686 char *varbuf;
688 varbuf = ac_alloc(strlen(*ap) + 1);
689 cp = *ap;
690 cp2 = varbuf;
691 while (*cp != '=' && *cp != '\0')
692 *cp2++ = *cp++;
693 *cp2 = '\0';
694 if (*cp == '\0')
695 cp = "";
696 else
697 cp++;
698 if (equal(varbuf, "")) {
699 printf(catgets(catd, CATSET, 41,
700 "Non-null variable name required\n"));
701 errs++;
702 ac_free(varbuf);
703 continue;
705 if (varbuf[0] == 'n' && varbuf[1] == 'o')
706 errs += unset_internal(&varbuf[2]);
707 else
708 assign(varbuf, cp);
709 ac_free(varbuf);
711 return(errs);
715 * Unset a bunch of variable values.
717 int
718 unset(void *v)
720 int errs;
721 char **ap;
723 errs = 0;
724 for (ap = (char **)v; *ap != NULL; ap++)
725 errs += unset_internal(*ap);
726 return(errs);
730 * Put add users to a group.
732 int
733 group(void *v)
735 char **argv = v;
736 struct grouphead *gh;
737 struct group *gp;
738 int h;
739 int s;
740 char **ap, *gname, **p;
742 if (*argv == NULL) {
743 for (h = 0, s = 1; h < HSHSIZE; h++)
744 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
745 s++;
746 /*LINTED*/
747 ap = (char **)salloc(s * sizeof *ap);
748 for (h = 0, p = ap; h < HSHSIZE; h++)
749 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
750 *p++ = gh->g_name;
751 *p = NULL;
752 asort(ap);
753 for (p = ap; *p != NULL; p++)
754 printgroup(*p);
755 return(0);
757 if (argv[1] == NULL) {
758 printgroup(*argv);
759 return(0);
761 gname = *argv;
762 h = hash(gname);
763 if ((gh = findgroup(gname)) == NULL) {
764 gh = (struct grouphead *)scalloc(1, sizeof *gh);
765 gh->g_name = vcopy(gname);
766 gh->g_list = NULL;
767 gh->g_link = groups[h];
768 groups[h] = gh;
772 * Insert names from the command list into the group.
773 * Who cares if there are duplicates? They get tossed
774 * later anyway.
777 for (ap = argv+1; *ap != NULL; ap++) {
778 gp = (struct group *)scalloc(1, sizeof *gp);
779 gp->ge_name = vcopy(*ap);
780 gp->ge_link = gh->g_list;
781 gh->g_list = gp;
783 return(0);
787 * Delete the passed groups.
789 int
790 ungroup(void *v)
792 char **argv = v;
794 if (*argv == NULL) {
795 printf(catgets(catd, CATSET, 209,
796 "Must specify alias or group to remove\n"));
797 return 1;
800 remove_group(*argv);
801 while (*++argv != NULL);
802 return 0;
806 * Sort the passed string vecotor into ascending dictionary
807 * order.
809 static void
810 asort(char **list)
812 char **ap;
814 for (ap = list; *ap != NULL; ap++)
816 if (ap-list < 2)
817 return;
818 qsort(list, ap-list, sizeof(*list), diction);
822 * Do a dictionary order comparison of the arguments from
823 * qsort.
825 static int
826 diction(const void *a, const void *b)
828 return(strcmp(*(char **)a, *(char **)b));
832 * Change to another file. With no argument, print information about
833 * the current file.
835 int
836 cfile(void *v)
838 char **argv = v;
840 if (argv[0] == NULL) {
841 newfileinfo();
842 return 0;
844 strncpy(mboxname, expand("&"), sizeof mboxname)[sizeof mboxname-1]='\0';
845 return file1(*argv);
848 static int
849 file1(char *name)
851 int i;
853 if (inhook) {
854 fprintf(stderr, "Cannot change folder from within a hook.\n");
855 return 1;
857 i = setfile(name, 0);
858 if (i < 0)
859 return 1;
860 callhook(mailname, 0);
861 if (i > 0 && value("emptystart") == NULL)
862 return 1;
863 announce(value("bsdcompat") != NULL || value("bsdannounce") != NULL);
864 return 0;
867 static int
868 shellecho(const char *cp)
870 int cflag = 0, n;
871 char c;
873 while (*cp) {
874 if (*cp == '\\') {
875 switch (*++cp) {
876 case '\0':
877 return cflag;
878 case 'a':
879 putchar('\a');
880 break;
881 case 'b':
882 putchar('\b');
883 break;
884 case 'c':
885 cflag = 1;
886 break;
887 case 'f':
888 putchar('\f');
889 break;
890 case 'n':
891 putchar('\n');
892 break;
893 case 'r':
894 putchar('\r');
895 break;
896 case 't':
897 putchar('\t');
898 break;
899 case 'v':
900 putchar('\v');
901 break;
902 default:
903 putchar(*cp&0377);
904 break;
905 case '0':
906 c = 0;
907 n = 3;
908 while (n-- && octalchar(cp[1]&0377)) {
909 c <<= 3;
910 c |= cp[1] - '0';
911 cp++;
913 putchar(c);
915 } else
916 putchar(*cp & 0377);
917 cp++;
919 return cflag;
923 * Expand file names like echo
925 int
926 echo(void *v)
928 char **argv = v;
929 char **ap;
930 char *cp;
931 int cflag = 0;
933 for (ap = argv; *ap != NULL; ap++) {
934 cp = *ap;
935 if ((cp = expand(cp)) != NULL) {
936 if (ap != argv)
937 putchar(' ');
938 cflag |= shellecho(cp);
941 if (!cflag)
942 putchar('\n');
943 return 0;
946 int
947 Respond(void *v)
949 return (respond_or_Respond('R'))((int *)v, 0);
952 int
953 Followup(void *v)
955 return (respond_or_Respond('R'))((int *)v, 1);
959 * Reply to a series of messages by simply mailing to the senders
960 * and not messing around with the To: and Cc: lists as in normal
961 * reply.
963 static int
964 Respond_internal(int *msgvec, int recipient_record)
966 int Eflag;
967 struct header head;
968 struct message *mp;
969 enum gfield gf = value("fullnames") ? GFULL : GSKIN;
970 int *ap;
971 char *cp;
973 memset(&head, 0, sizeof head);
974 for (ap = msgvec; *ap != 0; ap++) {
975 mp = &message[*ap - 1];
976 touch(mp);
977 setdot(mp);
978 if ((cp = hfield("reply-to", mp)) == NULL)
979 if ((cp = hfield("from", mp)) == NULL)
980 cp = nameof(mp, 2);
981 head.h_to = cat(head.h_to, sextract(cp, GTO|gf));
983 if (head.h_to == NULL)
984 return 0;
985 mp = &message[msgvec[0] - 1];
986 if ((head.h_subject = hfield("subject", mp)) == NULL)
987 head.h_subject = hfield("subj", mp);
988 head.h_subject = reedit(head.h_subject);
989 make_ref_and_cs(mp, &head);
990 Eflag = value("skipemptybody") != NULL;
991 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
992 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
993 mp->m_flag |= MANSWER|MANSWERED;
994 return 0;
998 * Conditional commands. These allow one to parameterize one's
999 * .mailrc and do some things if sending, others if receiving.
1001 int
1002 ifcmd(void *v)
1004 char **argv = v;
1005 char *cp;
1007 if (cond != CANY) {
1008 printf(catgets(catd, CATSET, 42, "Illegal nested \"if\"\n"));
1009 return(1);
1011 cond = CANY;
1012 cp = argv[0];
1013 switch (*cp) {
1014 case 'r': case 'R':
1015 cond = CRCV;
1016 break;
1018 case 's': case 'S':
1019 cond = CSEND;
1020 break;
1022 case 't': case 'T':
1023 cond = CTERM;
1024 break;
1026 default:
1027 printf(catgets(catd, CATSET, 43,
1028 "Unrecognized if-keyword: \"%s\"\n"), cp);
1029 return(1);
1031 return(0);
1035 * Implement 'else'. This is pretty simple -- we just
1036 * flip over the conditional flag.
1038 /*ARGSUSED*/
1039 int
1040 elsecmd(void *v)
1042 (void)v;
1044 switch (cond) {
1045 case CANY:
1046 printf(catgets(catd, CATSET, 44,
1047 "\"Else\" without matching \"if\"\n"));
1048 return(1);
1050 case CSEND:
1051 cond = CRCV;
1052 break;
1054 case CRCV:
1055 cond = CSEND;
1056 break;
1058 case CTERM:
1059 cond = CNONTERM;
1060 break;
1062 default:
1063 printf(catgets(catd, CATSET, 45,
1064 "Mail's idea of conditions is screwed up\n"));
1065 cond = CANY;
1066 break;
1068 return(0);
1072 * End of if statement. Just set cond back to anything.
1074 /*ARGSUSED*/
1075 int
1076 endifcmd(void *v)
1078 (void)v;
1080 if (cond == CANY) {
1081 printf(catgets(catd, CATSET, 46,
1082 "\"Endif\" without matching \"if\"\n"));
1083 return(1);
1085 cond = CANY;
1086 return(0);
1090 * Set the list of alternate names.
1092 int
1093 alternates(void *v)
1095 char **namelist = v;
1096 int c;
1097 char **ap, **ap2, *cp;
1099 c = argcount(namelist) + 1;
1100 if (c == 1) {
1101 if (altnames == 0)
1102 return(0);
1103 for (ap = altnames; *ap; ap++)
1104 printf("%s ", *ap);
1105 printf("\n");
1106 return(0);
1108 if (altnames != 0)
1109 free(altnames);
1110 altnames = scalloc(c, sizeof (char *));
1111 for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
1112 cp = scalloc(strlen(*ap) + 1, sizeof (char));
1113 strcpy(cp, *ap);
1114 *ap2 = cp;
1116 *ap2 = 0;
1117 return(0);
1121 * Do the real work of resending.
1123 static int
1124 resend1(void *v, int add_resent)
1126 char *name, *str;
1127 struct name *to;
1128 struct name *sn;
1129 int f, *ip, *msgvec;
1131 str = (char *)v;
1132 /*LINTED*/
1133 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1134 name = laststring(str, &f, 1);
1135 if (name == NULL) {
1136 puts(catgets(catd, CATSET, 47, "No recipient specified."));
1137 return 1;
1139 if (!f) {
1140 *msgvec = first(0, MMNORM);
1141 if (*msgvec == 0) {
1142 if (inhook)
1143 return 0;
1144 puts(catgets(catd, CATSET, 48,
1145 "No applicable messages."));
1146 return 1;
1148 msgvec[1] = 0;
1149 } else if (getmsglist(str, msgvec, 0) < 0)
1150 return 1;
1151 if (*msgvec == 0) {
1152 if (inhook)
1153 return 0;
1154 printf("No applicable messages.\n");
1155 return 1;
1157 sn = nalloc(name, GTO);
1158 to = usermap(sn);
1159 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
1160 if (resend_msg(&message[*ip - 1], to, add_resent) != OKAY)
1161 return 1;
1163 return 0;
1167 * Resend a message list to a third person.
1169 int
1170 resendcmd(void *v)
1172 return resend1(v, 1);
1176 * Resend a message list to a third person without adding headers.
1178 int
1179 Resendcmd(void *v)
1181 return resend1(v, 0);
1185 * 'newmail' or 'inc' command: Check for new mail without writing old
1186 * mail back.
1188 /*ARGSUSED*/
1189 int
1190 newmail(void *v)
1192 int val = 1, mdot;
1193 (void)v;
1195 if ((mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1196 (val = setfile(mailname, 1)) == 0) {
1197 mdot = getmdot(1);
1198 setdot(&message[mdot - 1]);
1200 return val;
1203 static void
1204 list_shortcuts(void)
1206 struct shortcut *s;
1208 for (s = shortcuts; s; s = s->sh_next)
1209 printf("%s=%s\n", s->sh_short, s->sh_long);
1212 int
1213 shortcut(void *v)
1215 char **args = (char **)v;
1216 struct shortcut *s;
1218 if (args[0] == NULL) {
1219 list_shortcuts();
1220 return 0;
1222 if (args[1] == NULL) {
1223 fprintf(stderr, catgets(catd, CATSET, 220,
1224 "expansion name for shortcut missing\n"));
1225 return 1;
1227 if (args[2] != NULL) {
1228 fprintf(stderr, catgets(catd, CATSET, 221,
1229 "too many arguments\n"));
1230 return 1;
1232 if ((s = get_shortcut(args[0])) != NULL) {
1233 free(s->sh_long);
1234 s->sh_long = sstrdup(args[1]);
1235 } else {
1236 s = scalloc(1, sizeof *s);
1237 s->sh_short = sstrdup(args[0]);
1238 s->sh_long = sstrdup(args[1]);
1239 s->sh_next = shortcuts;
1240 shortcuts = s;
1242 return 0;
1245 struct shortcut *
1246 get_shortcut(const char *str)
1248 struct shortcut *s;
1250 for (s = shortcuts; s; s = s->sh_next)
1251 if (strcmp(str, s->sh_short) == 0)
1252 break;
1253 return s;
1256 static enum okay
1257 delete_shortcut(const char *str)
1259 struct shortcut *sp, *sq;
1261 for (sp = shortcuts, sq = NULL; sp; sq = sp, sp = sp->sh_next) {
1262 if (strcmp(sp->sh_short, str) == 0) {
1263 free(sp->sh_short);
1264 free(sp->sh_long);
1265 if (sq)
1266 sq->sh_next = sp->sh_next;
1267 if (sp == shortcuts)
1268 shortcuts = sp->sh_next;
1269 free(sp);
1270 return OKAY;
1273 return STOP;
1276 int
1277 unshortcut(void *v)
1279 char **args = (char **)v;
1280 int errs = 0;
1282 if (args[0] == NULL) {
1283 fprintf(stderr, catgets(catd, CATSET, 222,
1284 "need shortcut names to remove\n"));
1285 return 1;
1287 while (*args != NULL) {
1288 if (delete_shortcut(*args) != OKAY) {
1289 errs = 1;
1290 fprintf(stderr, catgets(catd, CATSET, 223,
1291 "%s: no such shortcut\n"), *args);
1293 args++;
1295 return errs;
1298 struct oldaccount {
1299 struct oldaccount *ac_next; /* next account in list */
1300 char *ac_name; /* name of account */
1301 char **ac_vars; /* variables to set */
1304 static struct oldaccount *oldaccounts;
1306 struct oldaccount *
1307 get_oldaccount(const char *name)
1309 struct oldaccount *a;
1311 for (a = oldaccounts; a; a = a->ac_next)
1312 if (a->ac_name && strcmp(name, a->ac_name) == 0)
1313 break;
1314 return a;
1317 int
1318 account(void *v)
1320 char **args = (char **)v;
1321 struct oldaccount *a;
1322 char *cp;
1323 int i, mc, oqf, nqf;
1324 FILE *fp = stdout;
1326 if (args[0] == NULL) {
1327 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
1328 perror("tmpfile");
1329 return 1;
1331 rm(cp);
1332 Ftfree(&cp);
1333 mc = listaccounts(fp);
1334 for (a = oldaccounts; a; a = a->ac_next)
1335 if (a->ac_name) {
1336 if (mc++)
1337 fputc('\n', fp);
1338 fprintf(fp, "%s:\n", a->ac_name);
1339 for (i = 0; a->ac_vars[i]; i++)
1340 fprintf(fp, "\t%s\n", a->ac_vars[i]);
1342 if (mc)
1343 try_pager(fp);
1344 Fclose(fp);
1345 return 0;
1347 if (args[1] && args[1][0] == '{' && args[1][1] == '\0') {
1348 if (args[2] != NULL) {
1349 fprintf(stderr, "Syntax is: account <name> {\n");
1350 return 1;
1352 if ((a = get_oldaccount(args[0])) != NULL)
1353 a->ac_name = NULL;
1354 return define1(args[0], 1);
1356 strncpy(mboxname, expand("&"), sizeof mboxname)[sizeof mboxname-1]='\0';
1357 oqf = savequitflags();
1358 if ((a = get_oldaccount(args[0])) == NULL) {
1359 if (args[1]) {
1360 a = scalloc(1, sizeof *a);
1361 a->ac_next = oldaccounts;
1362 oldaccounts = a;
1363 } else {
1364 if ((i = callaccount(args[0])) != CBAD)
1365 goto setf;
1366 printf("Account %s does not exist.\n", args[0]);
1367 return 1;
1370 if (args[1]) {
1371 delaccount(args[0]);
1372 a->ac_name = sstrdup(args[0]);
1373 for (i = 1; args[i]; i++);
1374 a->ac_vars = scalloc(i, sizeof *a->ac_vars);
1375 for (i = 0; args[i+1]; i++)
1376 a->ac_vars[i] = sstrdup(args[i+1]);
1377 } else {
1378 unset_allow_undefined = 1;
1379 set(a->ac_vars);
1380 unset_allow_undefined = 0;
1381 setf: if (!starting) {
1382 nqf = savequitflags();
1383 restorequitflags(oqf);
1384 i = file1("%");
1385 restorequitflags(nqf);
1386 return i;
1389 return 0;
1392 int
1393 cflag(void *v)
1395 struct message *m;
1396 int *msgvec = v;
1397 int *ip;
1399 for (ip = msgvec; *ip != 0; ip++) {
1400 m = &message[*ip-1];
1401 setdot(m);
1402 if ((m->m_flag & (MFLAG|MFLAGGED)) == 0)
1403 m->m_flag |= MFLAG|MFLAGGED;
1405 return 0;
1408 int
1409 cunflag(void *v)
1411 struct message *m;
1412 int *msgvec = v;
1413 int *ip;
1415 for (ip = msgvec; *ip != 0; ip++) {
1416 m = &message[*ip-1];
1417 setdot(m);
1418 if (m->m_flag & (MFLAG|MFLAGGED)) {
1419 m->m_flag &= ~(MFLAG|MFLAGGED);
1420 m->m_flag |= MUNFLAG;
1423 return 0;
1426 int
1427 canswered(void *v)
1429 struct message *m;
1430 int *msgvec = v;
1431 int *ip;
1433 for (ip = msgvec; *ip != 0; ip++) {
1434 m = &message[*ip-1];
1435 setdot(m);
1436 if ((m->m_flag & (MANSWER|MANSWERED)) == 0)
1437 m->m_flag |= MANSWER|MANSWERED;
1439 return 0;
1442 int
1443 cunanswered(void *v)
1445 struct message *m;
1446 int *msgvec = v;
1447 int *ip;
1449 for (ip = msgvec; *ip != 0; ip++) {
1450 m = &message[*ip-1];
1451 setdot(m);
1452 if (m->m_flag & (MANSWER|MANSWERED)) {
1453 m->m_flag &= ~(MANSWER|MANSWERED);
1454 m->m_flag |= MUNANSWER;
1457 return 0;
1460 int
1461 cdraft(void *v)
1463 struct message *m;
1464 int *msgvec = v;
1465 int *ip;
1467 for (ip = msgvec; *ip != 0; ip++) {
1468 m = &message[*ip-1];
1469 setdot(m);
1470 if ((m->m_flag & (MDRAFT|MDRAFTED)) == 0)
1471 m->m_flag |= MDRAFT|MDRAFTED;
1473 return 0;
1476 int
1477 cundraft(void *v)
1479 struct message *m;
1480 int *msgvec = v;
1481 int *ip;
1483 for (ip = msgvec; *ip != 0; ip++) {
1484 m = &message[*ip-1];
1485 setdot(m);
1486 if (m->m_flag & (MDRAFT|MDRAFTED)) {
1487 m->m_flag &= ~(MDRAFT|MDRAFTED);
1488 m->m_flag |= MUNDRAFT;
1491 return 0;
1494 static float
1495 huge(void)
1497 #if defined (_CRAY)
1499 * This is not perfect, but correct for machines with a 32-bit
1500 * IEEE float and a 32-bit unsigned long, and does at least not
1501 * produce SIGFPE on the Cray Y-MP.
1503 union {
1504 float f;
1505 unsigned long l;
1506 } u;
1508 u.l = 0xff800000; /* -inf */
1509 return u.f;
1510 #elif defined (INFINITY)
1511 return -INFINITY;
1512 #elif defined (HUGE_VALF)
1513 return -HUGE_VALF;
1514 #elif defined (FLT_MAX)
1515 return -FLT_MAX;
1516 #else
1517 return -1e10;
1518 #endif
1521 int
1522 ckill(void *v)
1524 struct message *m;
1525 int *msgvec = v;
1526 int *ip;
1528 for (ip = msgvec; *ip != 0; ip++) {
1529 m = &message[*ip-1];
1530 m->m_flag |= MKILL;
1531 m->m_score = huge();
1533 return 0;
1536 int
1537 cunkill(void *v)
1539 struct message *m;
1540 int *msgvec = v;
1541 int *ip;
1543 for (ip = msgvec; *ip != 0; ip++) {
1544 m = &message[*ip-1];
1545 m->m_flag &= ~MKILL;
1546 m->m_score = 0;
1548 return 0;
1551 int
1552 cscore(void *v)
1554 char *str = v;
1555 char *sscore, *xp;
1556 int f, *msgvec, *ip;
1557 double nscore;
1558 struct message *m;
1560 msgvec = salloc((msgCount+2) * sizeof *msgvec);
1561 if ((sscore = laststring(str, &f, 0)) == NULL) {
1562 fprintf(stderr, "No score given.\n");
1563 return 1;
1565 nscore = strtod(sscore, &xp);
1566 if (*xp) {
1567 fprintf(stderr, "Invalid score: \"%s\"\n", sscore);
1568 return 1;
1570 if (nscore > FLT_MAX)
1571 nscore = FLT_MAX;
1572 else if (nscore < -FLT_MAX)
1573 nscore = -FLT_MAX;
1574 if (!f) {
1575 *msgvec = first(0, MMNORM);
1576 if (*msgvec == 0) {
1577 if (inhook)
1578 return 0;
1579 fprintf(stderr, "No messages to score.\n");
1580 return 1;
1582 msgvec[1] = 0;
1583 } else if (getmsglist(str, msgvec, 0) < 0)
1584 return 1;
1585 if (*msgvec == 0) {
1586 if (inhook)
1587 return 0;
1588 fprintf(stderr, "No applicable messages.\n");
1589 return 1;
1591 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
1592 m = &message[*ip-1];
1593 if (m->m_score != huge()) {
1594 m->m_score += nscore;
1595 if (m->m_score < 0)
1596 m->m_flag |= MKILL;
1597 else if (m->m_score > 0)
1598 m->m_flag &= ~MKILL;
1599 if (m->m_score >= 0)
1600 setdot(m);
1603 return 0;
1606 /*ARGSUSED*/
1607 int
1608 cnoop(void *v)
1610 (void)v;
1612 switch (mb.mb_type) {
1613 case MB_IMAP:
1614 imap_noop();
1615 break;
1616 case MB_POP3:
1617 pop3_noop();
1618 break;
1619 default:
1620 break;
1622 return 0;
1625 int
1626 cremove(void *v)
1628 char vb[LINESIZE];
1629 char **args = v;
1630 char *name;
1631 int ec = 0;
1633 if (*args == NULL) {
1634 fprintf(stderr, "Syntax is: remove mailbox ...\n");
1635 return 1;
1637 do {
1638 if ((name = expand(*args)) == NULL)
1639 continue;
1640 if (strcmp(name, mailname) == 0) {
1641 fprintf(stderr,
1642 "Cannot remove current mailbox \"%s\".\n",
1643 name);
1644 ec |= 1;
1645 continue;
1647 snprintf(vb, sizeof vb, "Remove \"%s\" (y/n) ? ", name);
1648 if (yorn(vb) == 0)
1649 continue;
1650 switch (which_protocol(name)) {
1651 case PROTO_FILE:
1652 if (unlink(name) < 0) { /* do not handle .gz .bz2 */
1653 perror(name);
1654 ec |= 1;
1656 break;
1657 case PROTO_POP3:
1658 fprintf(stderr, "Cannot remove POP3 mailbox \"%s\".\n",
1659 name);
1660 ec |= 1;
1661 break;
1662 case PROTO_IMAP:
1663 if (imap_remove(name) != OKAY)
1664 ec |= 1;
1665 break;
1666 case PROTO_MAILDIR:
1667 if (maildir_remove(name) != OKAY)
1668 ec |= 1;
1669 break;
1670 case PROTO_UNKNOWN:
1671 fprintf(stderr,
1672 "Unknown protocol in \"%s\". Not removed.\n",
1673 name);
1674 ec |= 1;
1676 } while (*++args);
1677 return ec;
1680 int
1681 crename(void *v)
1683 char **args = v, *old, *new;
1684 enum protocol oldp, newp;
1685 int ec = 0;
1687 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1688 fprintf(stderr, "Syntax: rename old new\n");
1689 return 1;
1691 old = expand(args[0]);
1692 oldp = which_protocol(old);
1693 new = expand(args[1]);
1694 newp = which_protocol(new);
1695 if (strcmp(old, mailname) == 0 || strcmp(new, mailname) == 0) {
1696 fprintf(stderr, "Cannot rename current mailbox \"%s\".\n", old);
1697 return 1;
1699 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1700 fprintf(stderr, "Can only rename folders of same type.\n");
1701 return 1;
1703 if (newp == PROTO_POP3)
1704 goto nopop3;
1705 switch (oldp) {
1706 case PROTO_FILE:
1707 if (link(old, new) < 0) {
1708 switch (errno) {
1709 case EACCES:
1710 case EEXIST:
1711 case ENAMETOOLONG:
1712 case ENOENT:
1713 case ENOSPC:
1714 case EXDEV:
1715 perror(new);
1716 break;
1717 default:
1718 perror(old);
1720 ec |= 1;
1721 } else if (unlink(old) < 0) {
1722 perror(old);
1723 ec |= 1;
1725 break;
1726 case PROTO_MAILDIR:
1727 if (rename(old, new) < 0) {
1728 perror(old);
1729 ec |= 1;
1731 break;
1732 case PROTO_POP3:
1733 nopop3: fprintf(stderr, "Cannot rename POP3 mailboxes.\n");
1734 ec |= 1;
1735 break;
1736 case PROTO_IMAP:
1737 if (imap_rename(old, new) != OKAY)
1738 ec |= 1;
1739 break;
1740 case PROTO_UNKNOWN:
1741 fprintf(stderr, "Unknown protocol in \"%s\" and \"%s\". "
1742 "Not renamed.\n", old, new);
1743 ec |= 1;
1745 return ec;