Silence longjmp() warnings and one harmless unused warning
[s-mailx.git] / cmd3.c
blobe9a5289b3eaac61aa3ed738b8eb4083fd05e01ef
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 = hfield1("references", mp);
236 oldmsgid = hfield1("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 = hfield1("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 = hfield1("reply-to", mp)) == NULL)
345 if ((rcv = hfield1("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 = hfield1("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 head.h_subject = hfield1("subject", mp);
361 head.h_subject = reedit(head.h_subject);
362 /* Cc: */
363 np = NULL;
364 if (value("recipients-in-cc") && (cp = hfield1("to", mp)) != NULL)
365 np = sextract(cp, GCC|gf);
366 if ((cp = hfield1("cc", mp)) != NULL)
367 np = cat(np, sextract(cp, GCC|gf));
368 if (np != NULL)
369 head.h_cc = elide(delete_alternates(np));
370 make_ref_and_cs(mp, &head);
371 Eflag = value("skipemptybody") != NULL;
372 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
373 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
374 mp->m_flag |= MANSWER|MANSWERED;
375 return(0);
379 * Modify the subject we are replying to to begin with Re: if
380 * it does not already.
382 static char *
383 reedit(char *subj)
385 char *newsubj;
386 struct str in, out;
388 if (subj == NULL || *subj == '\0')
389 return NULL;
390 in.s = subj;
391 in.l = strlen(subj);
392 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
393 if ((out.s[0] == 'r' || out.s[0] == 'R') &&
394 (out.s[1] == 'e' || out.s[1] == 'E') &&
395 out.s[2] == ':')
396 return out.s;
397 newsubj = salloc(out.l + 5);
398 strcpy(newsubj, "Re: ");
399 strcpy(newsubj + 4, out.s);
400 return newsubj;
404 * Forward a message to a new recipient, in the sense of RFC 2822.
406 static int
407 forward1(char *str, int recipient_record)
409 int Eflag;
410 int *msgvec, f;
411 char *recipient;
412 struct message *mp;
413 struct header head;
414 int forward_as_attachment;
416 forward_as_attachment = value("forward-as-attachment") != NULL;
417 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
418 if ((recipient = laststring(str, &f, 0)) == NULL) {
419 puts(catgets(catd, CATSET, 47, "No recipient specified."));
420 return 1;
422 if (!f) {
423 *msgvec = first(0, MMNORM);
424 if (*msgvec == 0) {
425 if (inhook)
426 return 0;
427 printf("No messages to forward.\n");
428 return 1;
430 msgvec[1] = 0;
432 if (f && getmsglist(str, msgvec, 0) < 0)
433 return 1;
434 if (*msgvec == 0) {
435 if (inhook)
436 return 0;
437 printf("No applicable messages.\n");
438 return 1;
440 if (msgvec[1] != 0) {
441 printf("Cannot forward multiple messages at once\n");
442 return 1;
444 memset(&head, 0, sizeof head);
445 if ((head.h_to = sextract(recipient,
446 GTO | (value("fullnames") ? GFULL : GSKIN))) == NULL)
447 return 1;
448 mp = &message[*msgvec - 1];
449 if (forward_as_attachment) {
450 head.h_attach = csalloc(1, sizeof *head.h_attach);
451 head.h_attach->a_msgno = *msgvec;
452 } else {
453 touch(mp);
454 setdot(mp);
456 head.h_subject = hfield1("subject", mp);
457 head.h_subject = fwdedit(head.h_subject);
458 Eflag = value("skipemptybody") != NULL;
459 mail1(&head, 1, forward_as_attachment ? NULL : mp,
460 NULL, recipient_record, 1, 0, Eflag);
461 return 0;
465 * Modify the subject we are replying to to begin with Fwd:.
467 static char *
468 fwdedit(char *subj)
470 char *newsubj;
471 struct str in, out;
473 if (subj == NULL || *subj == '\0')
474 return NULL;
475 in.s = subj;
476 in.l = strlen(subj);
477 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
478 newsubj = salloc(strlen(out.s) + 6);
479 strcpy(newsubj, "Fwd: ");
480 strcpy(&newsubj[5], out.s);
481 free(out.s);
482 return newsubj;
486 * The 'forward' command.
489 forwardcmd(void *v)
491 return forward1(v, 0);
495 * Similar to forward, saving the message in a file named after the
496 * first recipient.
499 Forwardcmd(void *v)
501 return forward1(v, 1);
505 * Preserve the named messages, so that they will be sent
506 * back to the system mailbox.
508 int
509 preserve(void *v)
511 int *msgvec = v;
512 struct message *mp;
513 int *ip, mesg;
515 if (edit) {
516 printf(catgets(catd, CATSET, 39,
517 "Cannot \"preserve\" in edit mode\n"));
518 return(1);
520 for (ip = msgvec; *ip != 0; ip++) {
521 mesg = *ip;
522 mp = &message[mesg-1];
523 mp->m_flag |= MPRESERVE;
524 mp->m_flag &= ~MBOX;
525 setdot(mp);
527 * This is now Austin Group Request XCU #20.
529 did_print_dot = 1;
531 return(0);
535 * Mark all given messages as unread.
537 int
538 unread(void *v)
540 int *msgvec = v;
541 int *ip;
543 for (ip = msgvec; *ip != 0; ip++) {
544 setdot(&message[*ip-1]);
545 dot->m_flag &= ~(MREAD|MTOUCH);
546 dot->m_flag |= MSTATUS;
547 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
548 imap_unread(&message[*ip-1], *ip);
550 * The "unread" command is not part of POSIX mailx.
552 did_print_dot = 1;
554 return(0);
558 * Mark all given messages as read.
561 seen(void *v)
563 int *msgvec = v;
564 int *ip;
566 for (ip = msgvec; *ip; ip++) {
567 setdot(&message[*ip-1]);
568 touch(&message[*ip-1]);
570 return 0;
574 * Print the size of each message.
576 int
577 messize(void *v)
579 int *msgvec = v;
580 struct message *mp;
581 int *ip, mesg;
583 for (ip = msgvec; *ip != 0; ip++) {
584 mesg = *ip;
585 mp = &message[mesg-1];
586 printf("%d: ", mesg);
587 if (mp->m_xlines > 0)
588 printf("%ld", mp->m_xlines);
589 else
590 putchar(' ');
591 printf("/%lu\n", (unsigned long)mp->m_xsize);
593 return(0);
597 * Quit quickly. If we are sourcing, just pop the input level
598 * by returning an error.
600 /*ARGSUSED*/
601 int
602 rexit(void *v)
604 (void)v;
605 if (sourcing)
606 return(1);
607 exit(0);
608 /*NOTREACHED*/
611 static sigjmp_buf pipejmp;
613 /*ARGSUSED*/
614 static void
615 onpipe(int signo)
617 (void)signo;
618 siglongjmp(pipejmp, 1);
622 * Set or display a variable value. Syntax is similar to that
623 * of sh.
625 int
626 set(void *v)
628 char **arglist = v;
629 struct var *vp;
630 char *cp, *cp2;
631 char **ap, **p;
632 int errs, h, s;
633 FILE *volatile obuf = stdout;
634 int volatile bsdset = (value("bsdcompat") != NULL ||
635 value("bsdset") != NULL);
637 if (*arglist == NULL) {
638 for (h = 0, s = 1; h < HSHSIZE; h++)
639 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
640 s++;
641 /*LINTED*/
642 ap = (char **)salloc(s * sizeof *ap);
643 for (h = 0, p = ap; h < HSHSIZE; h++)
644 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
645 *p++ = vp->v_name;
646 *p = NULL;
647 asort(ap);
648 if (is_a_tty[0] && is_a_tty[1] && (cp = value("crt")) != NULL) {
649 if (s > (*cp == '\0' ? screensize() : atoi(cp)) + 3) {
650 cp = get_pager();
651 /* TODO should be below the Popen?
652 * TODO Problem: Popen doesn't encapsulate it,
653 * TODO may leave child run if fdopen() fails!
654 * TODO even more such stuff in this file! */
655 if (sigsetjmp(pipejmp, 1))
656 goto endpipe;
657 if ((obuf = Popen(cp, "w", NULL, 1)) == NULL) {
658 perror(cp);
659 obuf = stdout;
660 } else
661 safe_signal(SIGPIPE, onpipe);
664 for (p = ap; *p != NULL; p++) {
665 if (bsdset)
666 fprintf(obuf, "%s\t%s\n", *p, value(*p));
667 else {
668 if ((cp = value(*p)) != NULL && *cp)
669 fprintf(obuf, "%s=\"%s\"\n",
670 *p, value(*p));
671 else
672 fprintf(obuf, "%s\n", *p);
675 endpipe:
676 if (obuf != stdout) {
677 safe_signal(SIGPIPE, SIG_IGN);
678 Pclose(obuf);
679 safe_signal(SIGPIPE, dflpipe);
681 return(0);
683 errs = 0;
684 for (ap = arglist; *ap != NULL; ap++) {
685 char *varbuf;
687 varbuf = ac_alloc(strlen(*ap) + 1);
688 cp = *ap;
689 cp2 = varbuf;
690 while (*cp != '=' && *cp != '\0')
691 *cp2++ = *cp++;
692 *cp2 = '\0';
693 if (*cp == '\0')
694 cp = "";
695 else
696 cp++;
697 if (strcmp(varbuf, "") == 0) {
698 printf(tr(41, "Non-null variable name required\n"));
699 errs++;
700 ac_free(varbuf);
701 continue;
703 if (varbuf[0] == 'n' && varbuf[1] == 'o')
704 errs += unset_internal(&varbuf[2]);
705 else
706 assign(varbuf, cp);
707 ac_free(varbuf);
709 return(errs);
713 * Unset a bunch of variable values.
715 int
716 unset(void *v)
718 int errs;
719 char **ap;
721 errs = 0;
722 for (ap = (char **)v; *ap != NULL; ap++)
723 errs += unset_internal(*ap);
724 return(errs);
728 * Put add users to a group.
730 int
731 group(void *v)
733 char **argv = v;
734 struct grouphead *gh;
735 struct group *gp;
736 int h;
737 int s;
738 char **ap, *gname, **p;
740 if (*argv == NULL) {
741 for (h = 0, s = 1; h < HSHSIZE; h++)
742 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
743 s++;
744 /*LINTED*/
745 ap = (char **)salloc(s * sizeof *ap);
746 for (h = 0, p = ap; h < HSHSIZE; h++)
747 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
748 *p++ = gh->g_name;
749 *p = NULL;
750 asort(ap);
751 for (p = ap; *p != NULL; p++)
752 printgroup(*p);
753 return(0);
755 if (argv[1] == NULL) {
756 printgroup(*argv);
757 return(0);
759 gname = *argv;
760 h = hash(gname);
761 if ((gh = findgroup(gname)) == NULL) {
762 gh = (struct grouphead *)scalloc(1, sizeof *gh);
763 gh->g_name = vcopy(gname);
764 gh->g_list = NULL;
765 gh->g_link = groups[h];
766 groups[h] = gh;
770 * Insert names from the command list into the group.
771 * Who cares if there are duplicates? They get tossed
772 * later anyway.
775 for (ap = argv+1; *ap != NULL; ap++) {
776 gp = (struct group *)scalloc(1, sizeof *gp);
777 gp->ge_name = vcopy(*ap);
778 gp->ge_link = gh->g_list;
779 gh->g_list = gp;
781 return(0);
785 * Delete the passed groups.
787 int
788 ungroup(void *v)
790 char **argv = v;
792 if (*argv == NULL) {
793 printf(catgets(catd, CATSET, 209,
794 "Must specify alias or group to remove\n"));
795 return 1;
798 remove_group(*argv);
799 while (*++argv != NULL);
800 return 0;
804 * Sort the passed string vecotor into ascending dictionary
805 * order.
807 static void
808 asort(char **list)
810 char **ap;
812 for (ap = list; *ap != NULL; ap++)
814 if (ap-list < 2)
815 return;
816 qsort(list, ap-list, sizeof(*list), diction);
820 * Do a dictionary order comparison of the arguments from
821 * qsort.
823 static int
824 diction(const void *a, const void *b)
826 return(strcmp(*(char **)a, *(char **)b));
830 * Change to another file. With no argument, print information about
831 * the current file.
833 int
834 cfile(void *v)
836 char **argv = v;
838 if (argv[0] == NULL) {
839 newfileinfo();
840 return 0;
842 strncpy(mboxname, expand("&"), sizeof mboxname)[sizeof mboxname-1]='\0';
843 return file1(*argv);
846 static int
847 file1(char *name)
849 int i;
851 if (inhook) {
852 fprintf(stderr, "Cannot change folder from within a hook.\n");
853 return 1;
855 i = setfile(name, 0);
856 if (i < 0)
857 return 1;
858 callhook(mailname, 0);
859 if (i > 0 && value("emptystart") == NULL)
860 return 1;
861 announce(value("bsdcompat") != NULL || value("bsdannounce") != NULL);
862 return 0;
865 static int
866 shellecho(const char *cp)
868 int cflag = 0, n;
869 char c;
871 while (*cp) {
872 if (*cp == '\\') {
873 switch (*++cp) {
874 case '\0':
875 return cflag;
876 case 'a':
877 putchar('\a');
878 break;
879 case 'b':
880 putchar('\b');
881 break;
882 case 'c':
883 cflag = 1;
884 break;
885 case 'f':
886 putchar('\f');
887 break;
888 case 'n':
889 putchar('\n');
890 break;
891 case 'r':
892 putchar('\r');
893 break;
894 case 't':
895 putchar('\t');
896 break;
897 case 'v':
898 putchar('\v');
899 break;
900 default:
901 putchar(*cp&0377);
902 break;
903 case '0':
904 c = 0;
905 n = 3;
906 while (n-- && octalchar(cp[1]&0377)) {
907 c <<= 3;
908 c |= cp[1] - '0';
909 cp++;
911 putchar(c);
913 } else
914 putchar(*cp & 0377);
915 cp++;
917 return cflag;
921 * Expand file names like echo
923 int
924 echo(void *v)
926 char **argv = v;
927 char **ap;
928 char *cp;
929 int cflag = 0;
931 for (ap = argv; *ap != NULL; ap++) {
932 cp = *ap;
933 if ((cp = expand(cp)) != NULL) {
934 if (ap != argv)
935 putchar(' ');
936 cflag |= shellecho(cp);
939 if (!cflag)
940 putchar('\n');
941 return 0;
944 int
945 Respond(void *v)
947 return (respond_or_Respond('R'))((int *)v, 0);
950 int
951 Followup(void *v)
953 return (respond_or_Respond('R'))((int *)v, 1);
957 * Reply to a series of messages by simply mailing to the senders
958 * and not messing around with the To: and Cc: lists as in normal
959 * reply.
961 static int
962 Respond_internal(int *msgvec, int recipient_record)
964 int Eflag;
965 struct header head;
966 struct message *mp;
967 enum gfield gf = value("fullnames") ? GFULL : GSKIN;
968 int *ap;
969 char *cp;
971 memset(&head, 0, sizeof head);
972 for (ap = msgvec; *ap != 0; ap++) {
973 mp = &message[*ap - 1];
974 touch(mp);
975 setdot(mp);
976 if ((cp = hfield1("reply-to", mp)) == NULL)
977 if ((cp = hfield1("from", mp)) == NULL)
978 cp = nameof(mp, 2);
979 head.h_to = cat(head.h_to, sextract(cp, GTO|gf));
981 if (head.h_to == NULL)
982 return 0;
983 mp = &message[msgvec[0] - 1];
984 head.h_subject = hfield1("subject", mp);
985 head.h_subject = reedit(head.h_subject);
986 make_ref_and_cs(mp, &head);
987 Eflag = value("skipemptybody") != NULL;
988 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
989 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
990 mp->m_flag |= MANSWER|MANSWERED;
991 return 0;
995 * Conditional commands. These allow one to parameterize one's
996 * .mailrc and do some things if sending, others if receiving.
998 int
999 ifcmd(void *v)
1001 char **argv = v;
1002 char *cp;
1004 if (cond != CANY) {
1005 printf(catgets(catd, CATSET, 42, "Illegal nested \"if\"\n"));
1006 return(1);
1008 cond = CANY;
1009 cp = argv[0];
1010 switch (*cp) {
1011 case 'r': case 'R':
1012 cond = CRCV;
1013 break;
1015 case 's': case 'S':
1016 cond = CSEND;
1017 break;
1019 case 't': case 'T':
1020 cond = CTERM;
1021 break;
1023 default:
1024 printf(catgets(catd, CATSET, 43,
1025 "Unrecognized if-keyword: \"%s\"\n"), cp);
1026 return(1);
1028 return(0);
1032 * Implement 'else'. This is pretty simple -- we just
1033 * flip over the conditional flag.
1035 /*ARGSUSED*/
1036 int
1037 elsecmd(void *v)
1039 (void)v;
1041 switch (cond) {
1042 case CANY:
1043 printf(catgets(catd, CATSET, 44,
1044 "\"Else\" without matching \"if\"\n"));
1045 return(1);
1047 case CSEND:
1048 cond = CRCV;
1049 break;
1051 case CRCV:
1052 cond = CSEND;
1053 break;
1055 case CTERM:
1056 cond = CNONTERM;
1057 break;
1059 default:
1060 printf(catgets(catd, CATSET, 45,
1061 "Mail's idea of conditions is screwed up\n"));
1062 cond = CANY;
1063 break;
1065 return(0);
1069 * End of if statement. Just set cond back to anything.
1071 /*ARGSUSED*/
1072 int
1073 endifcmd(void *v)
1075 (void)v;
1077 if (cond == CANY) {
1078 printf(catgets(catd, CATSET, 46,
1079 "\"Endif\" without matching \"if\"\n"));
1080 return(1);
1082 cond = CANY;
1083 return(0);
1087 * Set the list of alternate names.
1089 int
1090 alternates(void *v)
1092 char **namelist = v;
1093 int c;
1094 char **ap, **ap2, *cp;
1096 c = argcount(namelist) + 1;
1097 if (c == 1) {
1098 if (altnames == 0)
1099 return(0);
1100 for (ap = altnames; *ap; ap++)
1101 printf("%s ", *ap);
1102 printf("\n");
1103 return(0);
1105 if (altnames != 0)
1106 free(altnames);
1107 altnames = scalloc(c, sizeof (char *));
1108 for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
1109 cp = scalloc(strlen(*ap) + 1, sizeof (char));
1110 strcpy(cp, *ap);
1111 *ap2 = cp;
1113 *ap2 = 0;
1114 return(0);
1118 * Do the real work of resending.
1120 static int
1121 resend1(void *v, int add_resent)
1123 char *name, *str;
1124 struct name *to;
1125 struct name *sn;
1126 int f, *ip, *msgvec;
1128 str = (char *)v;
1129 /*LINTED*/
1130 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1131 name = laststring(str, &f, 1);
1132 if (name == NULL) {
1133 puts(catgets(catd, CATSET, 47, "No recipient specified."));
1134 return 1;
1136 if (!f) {
1137 *msgvec = first(0, MMNORM);
1138 if (*msgvec == 0) {
1139 if (inhook)
1140 return 0;
1141 puts(catgets(catd, CATSET, 48,
1142 "No applicable messages."));
1143 return 1;
1145 msgvec[1] = 0;
1146 } else if (getmsglist(str, msgvec, 0) < 0)
1147 return 1;
1148 if (*msgvec == 0) {
1149 if (inhook)
1150 return 0;
1151 printf("No applicable messages.\n");
1152 return 1;
1154 sn = nalloc(name, GTO);
1155 to = usermap(sn);
1156 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
1157 if (resend_msg(&message[*ip - 1], to, add_resent) != OKAY)
1158 return 1;
1160 return 0;
1164 * Resend a message list to a third person.
1166 int
1167 resendcmd(void *v)
1169 return resend1(v, 1);
1173 * Resend a message list to a third person without adding headers.
1175 int
1176 Resendcmd(void *v)
1178 return resend1(v, 0);
1182 * 'newmail' or 'inc' command: Check for new mail without writing old
1183 * mail back.
1185 /*ARGSUSED*/
1186 int
1187 newmail(void *v)
1189 int val = 1, mdot;
1190 (void)v;
1192 if ((mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1193 (val = setfile(mailname, 1)) == 0) {
1194 mdot = getmdot(1);
1195 setdot(&message[mdot - 1]);
1197 return val;
1200 static void
1201 list_shortcuts(void)
1203 struct shortcut *s;
1205 for (s = shortcuts; s; s = s->sh_next)
1206 printf("%s=%s\n", s->sh_short, s->sh_long);
1209 int
1210 shortcut(void *v)
1212 char **args = (char **)v;
1213 struct shortcut *s;
1215 if (args[0] == NULL) {
1216 list_shortcuts();
1217 return 0;
1219 if (args[1] == NULL) {
1220 fprintf(stderr, catgets(catd, CATSET, 220,
1221 "expansion name for shortcut missing\n"));
1222 return 1;
1224 if (args[2] != NULL) {
1225 fprintf(stderr, catgets(catd, CATSET, 221,
1226 "too many arguments\n"));
1227 return 1;
1229 if ((s = get_shortcut(args[0])) != NULL) {
1230 free(s->sh_long);
1231 s->sh_long = sstrdup(args[1]);
1232 } else {
1233 s = scalloc(1, sizeof *s);
1234 s->sh_short = sstrdup(args[0]);
1235 s->sh_long = sstrdup(args[1]);
1236 s->sh_next = shortcuts;
1237 shortcuts = s;
1239 return 0;
1242 struct shortcut *
1243 get_shortcut(const char *str)
1245 struct shortcut *s;
1247 for (s = shortcuts; s; s = s->sh_next)
1248 if (strcmp(str, s->sh_short) == 0)
1249 break;
1250 return s;
1253 static enum okay
1254 delete_shortcut(const char *str)
1256 struct shortcut *sp, *sq;
1258 for (sp = shortcuts, sq = NULL; sp; sq = sp, sp = sp->sh_next) {
1259 if (strcmp(sp->sh_short, str) == 0) {
1260 free(sp->sh_short);
1261 free(sp->sh_long);
1262 if (sq)
1263 sq->sh_next = sp->sh_next;
1264 if (sp == shortcuts)
1265 shortcuts = sp->sh_next;
1266 free(sp);
1267 return OKAY;
1270 return STOP;
1273 int
1274 unshortcut(void *v)
1276 char **args = (char **)v;
1277 int errs = 0;
1279 if (args[0] == NULL) {
1280 fprintf(stderr, catgets(catd, CATSET, 222,
1281 "need shortcut names to remove\n"));
1282 return 1;
1284 while (*args != NULL) {
1285 if (delete_shortcut(*args) != OKAY) {
1286 errs = 1;
1287 fprintf(stderr, catgets(catd, CATSET, 223,
1288 "%s: no such shortcut\n"), *args);
1290 args++;
1292 return errs;
1295 struct oldaccount {
1296 struct oldaccount *ac_next; /* next account in list */
1297 char *ac_name; /* name of account */
1298 char **ac_vars; /* variables to set */
1301 static struct oldaccount *oldaccounts;
1303 struct oldaccount *
1304 get_oldaccount(const char *name)
1306 struct oldaccount *a;
1308 for (a = oldaccounts; a; a = a->ac_next)
1309 if (a->ac_name && strcmp(name, a->ac_name) == 0)
1310 break;
1311 return a;
1314 int
1315 account(void *v)
1317 char **args = (char **)v;
1318 struct oldaccount *a;
1319 char *cp;
1320 int i, mc, oqf, nqf;
1321 FILE *fp = stdout;
1323 if (args[0] == NULL) {
1324 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
1325 perror("tmpfile");
1326 return 1;
1328 rm(cp);
1329 Ftfree(&cp);
1330 mc = listaccounts(fp);
1331 for (a = oldaccounts; a; a = a->ac_next)
1332 if (a->ac_name) {
1333 if (mc++)
1334 fputc('\n', fp);
1335 fprintf(fp, "%s:\n", a->ac_name);
1336 for (i = 0; a->ac_vars[i]; i++)
1337 fprintf(fp, "\t%s\n", a->ac_vars[i]);
1339 if (mc)
1340 try_pager(fp);
1341 Fclose(fp);
1342 return 0;
1344 if (args[1] && args[1][0] == '{' && args[1][1] == '\0') {
1345 if (args[2] != NULL) {
1346 fprintf(stderr, "Syntax is: account <name> {\n");
1347 return 1;
1349 if ((a = get_oldaccount(args[0])) != NULL)
1350 a->ac_name = NULL;
1351 return define1(args[0], 1);
1353 strncpy(mboxname, expand("&"), sizeof mboxname)[sizeof mboxname-1]='\0';
1354 oqf = savequitflags();
1355 if ((a = get_oldaccount(args[0])) == NULL) {
1356 if (args[1]) {
1357 a = scalloc(1, sizeof *a);
1358 a->ac_next = oldaccounts;
1359 oldaccounts = a;
1360 } else {
1361 if ((i = callaccount(args[0])) != CBAD)
1362 goto setf;
1363 printf("Account %s does not exist.\n", args[0]);
1364 return 1;
1367 if (args[1]) {
1368 delaccount(args[0]);
1369 a->ac_name = sstrdup(args[0]);
1370 for (i = 1; args[i]; i++);
1371 a->ac_vars = scalloc(i, sizeof *a->ac_vars);
1372 for (i = 0; args[i+1]; i++)
1373 a->ac_vars[i] = sstrdup(args[i+1]);
1374 } else {
1375 unset_allow_undefined = 1;
1376 set(a->ac_vars);
1377 unset_allow_undefined = 0;
1378 setf: if (!starting) {
1379 nqf = savequitflags();
1380 restorequitflags(oqf);
1381 i = file1("%");
1382 restorequitflags(nqf);
1383 return i;
1386 return 0;
1389 int
1390 cflag(void *v)
1392 struct message *m;
1393 int *msgvec = v;
1394 int *ip;
1396 for (ip = msgvec; *ip != 0; ip++) {
1397 m = &message[*ip-1];
1398 setdot(m);
1399 if ((m->m_flag & (MFLAG|MFLAGGED)) == 0)
1400 m->m_flag |= MFLAG|MFLAGGED;
1402 return 0;
1405 int
1406 cunflag(void *v)
1408 struct message *m;
1409 int *msgvec = v;
1410 int *ip;
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);
1417 m->m_flag |= MUNFLAG;
1420 return 0;
1423 int
1424 canswered(void *v)
1426 struct message *m;
1427 int *msgvec = v;
1428 int *ip;
1430 for (ip = msgvec; *ip != 0; ip++) {
1431 m = &message[*ip-1];
1432 setdot(m);
1433 if ((m->m_flag & (MANSWER|MANSWERED)) == 0)
1434 m->m_flag |= MANSWER|MANSWERED;
1436 return 0;
1439 int
1440 cunanswered(void *v)
1442 struct message *m;
1443 int *msgvec = v;
1444 int *ip;
1446 for (ip = msgvec; *ip != 0; ip++) {
1447 m = &message[*ip-1];
1448 setdot(m);
1449 if (m->m_flag & (MANSWER|MANSWERED)) {
1450 m->m_flag &= ~(MANSWER|MANSWERED);
1451 m->m_flag |= MUNANSWER;
1454 return 0;
1457 int
1458 cdraft(void *v)
1460 struct message *m;
1461 int *msgvec = v;
1462 int *ip;
1464 for (ip = msgvec; *ip != 0; ip++) {
1465 m = &message[*ip-1];
1466 setdot(m);
1467 if ((m->m_flag & (MDRAFT|MDRAFTED)) == 0)
1468 m->m_flag |= MDRAFT|MDRAFTED;
1470 return 0;
1473 int
1474 cundraft(void *v)
1476 struct message *m;
1477 int *msgvec = v;
1478 int *ip;
1480 for (ip = msgvec; *ip != 0; ip++) {
1481 m = &message[*ip-1];
1482 setdot(m);
1483 if (m->m_flag & (MDRAFT|MDRAFTED)) {
1484 m->m_flag &= ~(MDRAFT|MDRAFTED);
1485 m->m_flag |= MUNDRAFT;
1488 return 0;
1491 static float
1492 huge(void)
1494 #if defined (_CRAY)
1496 * This is not perfect, but correct for machines with a 32-bit
1497 * IEEE float and a 32-bit unsigned long, and does at least not
1498 * produce SIGFPE on the Cray Y-MP.
1500 union {
1501 float f;
1502 unsigned long l;
1503 } u;
1505 u.l = 0xff800000; /* -inf */
1506 return u.f;
1507 #elif defined (INFINITY)
1508 return -INFINITY;
1509 #elif defined (HUGE_VALF)
1510 return -HUGE_VALF;
1511 #elif defined (FLT_MAX)
1512 return -FLT_MAX;
1513 #else
1514 return -1e10;
1515 #endif
1518 int
1519 ckill(void *v)
1521 struct message *m;
1522 int *msgvec = v;
1523 int *ip;
1525 for (ip = msgvec; *ip != 0; ip++) {
1526 m = &message[*ip-1];
1527 m->m_flag |= MKILL;
1528 m->m_score = huge();
1530 return 0;
1533 int
1534 cunkill(void *v)
1536 struct message *m;
1537 int *msgvec = v;
1538 int *ip;
1540 for (ip = msgvec; *ip != 0; ip++) {
1541 m = &message[*ip-1];
1542 m->m_flag &= ~MKILL;
1543 m->m_score = 0;
1545 return 0;
1548 int
1549 cscore(void *v)
1551 char *str = v;
1552 char *sscore, *xp;
1553 int f, *msgvec, *ip;
1554 double nscore;
1555 struct message *m;
1557 msgvec = salloc((msgCount+2) * sizeof *msgvec);
1558 if ((sscore = laststring(str, &f, 0)) == NULL) {
1559 fprintf(stderr, "No score given.\n");
1560 return 1;
1562 nscore = strtod(sscore, &xp);
1563 if (*xp) {
1564 fprintf(stderr, "Invalid score: \"%s\"\n", sscore);
1565 return 1;
1567 if (nscore > FLT_MAX)
1568 nscore = FLT_MAX;
1569 else if (nscore < -FLT_MAX)
1570 nscore = -FLT_MAX;
1571 if (!f) {
1572 *msgvec = first(0, MMNORM);
1573 if (*msgvec == 0) {
1574 if (inhook)
1575 return 0;
1576 fprintf(stderr, "No messages to score.\n");
1577 return 1;
1579 msgvec[1] = 0;
1580 } else if (getmsglist(str, msgvec, 0) < 0)
1581 return 1;
1582 if (*msgvec == 0) {
1583 if (inhook)
1584 return 0;
1585 fprintf(stderr, "No applicable messages.\n");
1586 return 1;
1588 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
1589 m = &message[*ip-1];
1590 if (m->m_score != huge()) {
1591 m->m_score += nscore;
1592 if (m->m_score < 0)
1593 m->m_flag |= MKILL;
1594 else if (m->m_score > 0)
1595 m->m_flag &= ~MKILL;
1596 if (m->m_score >= 0)
1597 setdot(m);
1600 return 0;
1603 /*ARGSUSED*/
1604 int
1605 cnoop(void *v)
1607 (void)v;
1609 switch (mb.mb_type) {
1610 case MB_IMAP:
1611 imap_noop();
1612 break;
1613 case MB_POP3:
1614 pop3_noop();
1615 break;
1616 default:
1617 break;
1619 return 0;
1622 int
1623 cremove(void *v)
1625 char vb[LINESIZE];
1626 char **args = v;
1627 char *name;
1628 int ec = 0;
1630 if (*args == NULL) {
1631 fprintf(stderr, "Syntax is: remove mailbox ...\n");
1632 return 1;
1634 do {
1635 if ((name = expand(*args)) == NULL)
1636 continue;
1637 if (strcmp(name, mailname) == 0) {
1638 fprintf(stderr,
1639 "Cannot remove current mailbox \"%s\".\n",
1640 name);
1641 ec |= 1;
1642 continue;
1644 snprintf(vb, sizeof vb, "Remove \"%s\" (y/n) ? ", name);
1645 if (yorn(vb) == 0)
1646 continue;
1647 switch (which_protocol(name)) {
1648 case PROTO_FILE:
1649 if (unlink(name) < 0) { /* do not handle .gz .bz2 */
1650 perror(name);
1651 ec |= 1;
1653 break;
1654 case PROTO_POP3:
1655 fprintf(stderr, "Cannot remove POP3 mailbox \"%s\".\n",
1656 name);
1657 ec |= 1;
1658 break;
1659 case PROTO_IMAP:
1660 if (imap_remove(name) != OKAY)
1661 ec |= 1;
1662 break;
1663 case PROTO_MAILDIR:
1664 if (maildir_remove(name) != OKAY)
1665 ec |= 1;
1666 break;
1667 case PROTO_UNKNOWN:
1668 fprintf(stderr,
1669 "Unknown protocol in \"%s\". Not removed.\n",
1670 name);
1671 ec |= 1;
1673 } while (*++args);
1674 return ec;
1677 int
1678 crename(void *v)
1680 char **args = v, *old, *new;
1681 enum protocol oldp, newp;
1682 int ec = 0;
1684 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1685 fprintf(stderr, "Syntax: rename old new\n");
1686 return 1;
1688 old = expand(args[0]);
1689 oldp = which_protocol(old);
1690 new = expand(args[1]);
1691 newp = which_protocol(new);
1692 if (strcmp(old, mailname) == 0 || strcmp(new, mailname) == 0) {
1693 fprintf(stderr, "Cannot rename current mailbox \"%s\".\n", old);
1694 return 1;
1696 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1697 fprintf(stderr, "Can only rename folders of same type.\n");
1698 return 1;
1700 if (newp == PROTO_POP3)
1701 goto nopop3;
1702 switch (oldp) {
1703 case PROTO_FILE:
1704 if (link(old, new) < 0) {
1705 switch (errno) {
1706 case EACCES:
1707 case EEXIST:
1708 case ENAMETOOLONG:
1709 case ENOENT:
1710 case ENOSPC:
1711 case EXDEV:
1712 perror(new);
1713 break;
1714 default:
1715 perror(old);
1717 ec |= 1;
1718 } else if (unlink(old) < 0) {
1719 perror(old);
1720 ec |= 1;
1722 break;
1723 case PROTO_MAILDIR:
1724 if (rename(old, new) < 0) {
1725 perror(old);
1726 ec |= 1;
1728 break;
1729 case PROTO_POP3:
1730 nopop3: fprintf(stderr, "Cannot rename POP3 mailboxes.\n");
1731 ec |= 1;
1732 break;
1733 case PROTO_IMAP:
1734 if (imap_rename(old, new) != OKAY)
1735 ec |= 1;
1736 break;
1737 case PROTO_UNKNOWN:
1738 fprintf(stderr, "Unknown protocol in \"%s\" and \"%s\". "
1739 "Not renamed.\n", old, new);
1740 ec |= 1;
1742 return ec;