junk.c: file_expand() may fail
[s-mailx.git] / cmd3.c
blob04ebeda36552614f2e0ad66638b0f4ac88b189ce
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 = file_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 = lextract(rcv, GTO|gf);
349 if (! value("recipients-in-cc") && (cp = hfield1("to", mp)) != NULL)
350 np = cat(np, lextract(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 = lextract(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 = lextract(cp, GCC|gf);
366 if ((cp = hfield1("cc", mp)) != NULL)
367 np = cat(np, lextract(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 = lextract(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 if (sigsetjmp(pipejmp, 1))
652 goto endpipe;
653 if ((obuf = Popen(cp, "w", NULL, 1)) == NULL) {
654 perror(cp);
655 obuf = stdout;
656 } else
657 safe_signal(SIGPIPE, onpipe);
660 for (p = ap; *p != NULL; p++) {
661 if (bsdset)
662 fprintf(obuf, "%s\t%s\n", *p, value(*p));
663 else {
664 if ((cp = value(*p)) != NULL && *cp)
665 fprintf(obuf, "%s=\"%s\"\n",
666 *p, value(*p));
667 else
668 fprintf(obuf, "%s\n", *p);
671 endpipe:
672 if (obuf != stdout) {
673 safe_signal(SIGPIPE, SIG_IGN);
674 Pclose(obuf);
675 safe_signal(SIGPIPE, dflpipe);
677 return(0);
679 errs = 0;
680 for (ap = arglist; *ap != NULL; ap++) {
681 char *varbuf;
683 varbuf = ac_alloc(strlen(*ap) + 1);
684 cp = *ap;
685 cp2 = varbuf;
686 while (*cp != '=' && *cp != '\0')
687 *cp2++ = *cp++;
688 *cp2 = '\0';
689 if (*cp == '\0')
690 cp = "";
691 else
692 cp++;
693 if (strcmp(varbuf, "") == 0) {
694 printf(tr(41, "Non-null variable name required\n"));
695 errs++;
696 ac_free(varbuf);
697 continue;
699 if (varbuf[0] == 'n' && varbuf[1] == 'o')
700 errs += unset_internal(&varbuf[2]);
701 else
702 assign(varbuf, cp);
703 ac_free(varbuf);
705 return(errs);
709 * Unset a bunch of variable values.
711 int
712 unset(void *v)
714 int errs;
715 char **ap;
717 errs = 0;
718 for (ap = (char **)v; *ap != NULL; ap++)
719 errs += unset_internal(*ap);
720 return(errs);
724 * Put add users to a group.
726 int
727 group(void *v)
729 char **argv = v;
730 struct grouphead *gh;
731 struct group *gp;
732 int h;
733 int s;
734 char **ap, *gname, **p;
736 if (*argv == NULL) {
737 for (h = 0, s = 1; h < HSHSIZE; h++)
738 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
739 s++;
740 /*LINTED*/
741 ap = (char **)salloc(s * sizeof *ap);
742 for (h = 0, p = ap; h < HSHSIZE; h++)
743 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
744 *p++ = gh->g_name;
745 *p = NULL;
746 asort(ap);
747 for (p = ap; *p != NULL; p++)
748 printgroup(*p);
749 return(0);
751 if (argv[1] == NULL) {
752 printgroup(*argv);
753 return(0);
755 gname = *argv;
756 h = hash(gname);
757 if ((gh = findgroup(gname)) == NULL) {
758 gh = (struct grouphead *)scalloc(1, sizeof *gh);
759 gh->g_name = vcopy(gname);
760 gh->g_list = NULL;
761 gh->g_link = groups[h];
762 groups[h] = gh;
766 * Insert names from the command list into the group.
767 * Who cares if there are duplicates? They get tossed
768 * later anyway.
771 for (ap = argv+1; *ap != NULL; ap++) {
772 gp = (struct group *)scalloc(1, sizeof *gp);
773 gp->ge_name = vcopy(*ap);
774 gp->ge_link = gh->g_list;
775 gh->g_list = gp;
777 return(0);
781 * Delete the passed groups.
783 int
784 ungroup(void *v)
786 char **argv = v;
788 if (*argv == NULL) {
789 printf(catgets(catd, CATSET, 209,
790 "Must specify alias or group to remove\n"));
791 return 1;
794 remove_group(*argv);
795 while (*++argv != NULL);
796 return 0;
800 * Sort the passed string vecotor into ascending dictionary
801 * order.
803 static void
804 asort(char **list)
806 char **ap;
808 for (ap = list; *ap != NULL; ap++)
810 if (ap-list < 2)
811 return;
812 qsort(list, ap-list, sizeof(*list), diction);
816 * Do a dictionary order comparison of the arguments from
817 * qsort.
819 static int
820 diction(const void *a, const void *b)
822 return(strcmp(*(char **)a, *(char **)b));
826 * Change to another file. With no argument, print information about
827 * the current file.
829 int
830 cfile(void *v)
832 char **argv = v, *exp;
834 if (argv[0] == NULL) {
835 newfileinfo();
836 return (0);
838 if ((exp = expand("&")) == NULL)
839 return (0);
840 strncpy(mboxname, exp, sizeof mboxname)[sizeof mboxname - 1] = '\0';
841 return (file1(*argv));
844 static int
845 file1(char *name)
847 int i;
849 if (inhook) {
850 fprintf(stderr, "Cannot change folder from within a hook.\n");
851 return 1;
853 i = setfile(name, 0);
854 if (i < 0)
855 return 1;
856 callhook(mailname, 0);
857 if (i > 0 && value("emptystart") == NULL)
858 return 1;
859 announce(value("bsdcompat") != NULL || value("bsdannounce") != NULL);
860 return 0;
863 static int
864 shellecho(const char *cp)
866 int cflag = 0, n;
867 char c;
869 while (*cp) {
870 if (*cp == '\\') {
871 switch (*++cp) {
872 case '\0':
873 return cflag;
874 case 'a':
875 putchar('\a');
876 break;
877 case 'b':
878 putchar('\b');
879 break;
880 case 'c':
881 cflag = 1;
882 break;
883 case 'f':
884 putchar('\f');
885 break;
886 case 'n':
887 putchar('\n');
888 break;
889 case 'r':
890 putchar('\r');
891 break;
892 case 't':
893 putchar('\t');
894 break;
895 case 'v':
896 putchar('\v');
897 break;
898 default:
899 putchar(*cp&0377);
900 break;
901 case '0':
902 c = 0;
903 n = 3;
904 while (n-- && octalchar(cp[1]&0377)) {
905 c <<= 3;
906 c |= cp[1] - '0';
907 cp++;
909 putchar(c);
911 } else
912 putchar(*cp & 0377);
913 cp++;
915 return cflag;
919 * Expand file names like echo
921 int
922 echo(void *v)
924 char **argv = v;
925 char **ap;
926 char *cp;
927 int cflag = 0;
929 for (ap = argv; *ap != NULL; ap++) {
930 cp = *ap;
931 if ((cp = expand(cp)) != NULL) {
932 if (ap != argv)
933 putchar(' ');
934 cflag |= shellecho(cp);
937 if (!cflag)
938 putchar('\n');
939 return 0;
942 int
943 Respond(void *v)
945 return (respond_or_Respond('R'))((int *)v, 0);
948 int
949 Followup(void *v)
951 return (respond_or_Respond('R'))((int *)v, 1);
955 * Reply to a series of messages by simply mailing to the senders
956 * and not messing around with the To: and Cc: lists as in normal
957 * reply.
959 static int
960 Respond_internal(int *msgvec, int recipient_record)
962 int Eflag;
963 struct header head;
964 struct message *mp;
965 enum gfield gf = value("fullnames") ? GFULL : GSKIN;
966 int *ap;
967 char *cp;
969 memset(&head, 0, sizeof head);
970 for (ap = msgvec; *ap != 0; ap++) {
971 mp = &message[*ap - 1];
972 touch(mp);
973 setdot(mp);
974 if ((cp = hfield1("reply-to", mp)) == NULL)
975 if ((cp = hfield1("from", mp)) == NULL)
976 cp = nameof(mp, 2);
977 head.h_to = cat(head.h_to, lextract(cp, GTO|gf));
979 if (head.h_to == NULL)
980 return 0;
981 mp = &message[msgvec[0] - 1];
982 head.h_subject = hfield1("subject", mp);
983 head.h_subject = reedit(head.h_subject);
984 make_ref_and_cs(mp, &head);
985 Eflag = value("skipemptybody") != NULL;
986 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
987 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
988 mp->m_flag |= MANSWER|MANSWERED;
989 return 0;
993 * Conditional commands. These allow one to parameterize one's
994 * .mailrc and do some things if sending, others if receiving.
996 int
997 ifcmd(void *v)
999 char **argv = v;
1000 char *cp;
1002 if (cond != CANY) {
1003 printf(catgets(catd, CATSET, 42, "Illegal nested \"if\"\n"));
1004 return(1);
1006 cond = CANY;
1007 cp = argv[0];
1008 switch (*cp) {
1009 case 'r': case 'R':
1010 cond = CRCV;
1011 break;
1013 case 's': case 'S':
1014 cond = CSEND;
1015 break;
1017 case 't': case 'T':
1018 cond = CTERM;
1019 break;
1021 default:
1022 printf(catgets(catd, CATSET, 43,
1023 "Unrecognized if-keyword: \"%s\"\n"), cp);
1024 return(1);
1026 return(0);
1030 * Implement 'else'. This is pretty simple -- we just
1031 * flip over the conditional flag.
1033 /*ARGSUSED*/
1034 int
1035 elsecmd(void *v)
1037 (void)v;
1039 switch (cond) {
1040 case CANY:
1041 printf(catgets(catd, CATSET, 44,
1042 "\"Else\" without matching \"if\"\n"));
1043 return(1);
1045 case CSEND:
1046 cond = CRCV;
1047 break;
1049 case CRCV:
1050 cond = CSEND;
1051 break;
1053 case CTERM:
1054 cond = CNONTERM;
1055 break;
1057 default:
1058 printf(catgets(catd, CATSET, 45,
1059 "Mail's idea of conditions is screwed up\n"));
1060 cond = CANY;
1061 break;
1063 return(0);
1067 * End of if statement. Just set cond back to anything.
1069 /*ARGSUSED*/
1070 int
1071 endifcmd(void *v)
1073 (void)v;
1075 if (cond == CANY) {
1076 printf(catgets(catd, CATSET, 46,
1077 "\"Endif\" without matching \"if\"\n"));
1078 return(1);
1080 cond = CANY;
1081 return(0);
1085 * Set the list of alternate names.
1087 int
1088 alternates(void *v)
1090 char **namelist = v;
1091 int c;
1092 char **ap, **ap2, *cp;
1094 c = argcount(namelist) + 1;
1095 if (c == 1) {
1096 if (altnames == 0)
1097 return(0);
1098 for (ap = altnames; *ap; ap++)
1099 printf("%s ", *ap);
1100 printf("\n");
1101 return(0);
1103 if (altnames != 0)
1104 free(altnames);
1105 altnames = scalloc(c, sizeof (char *));
1106 for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
1107 cp = scalloc(strlen(*ap) + 1, sizeof (char));
1108 strcpy(cp, *ap);
1109 *ap2 = cp;
1111 *ap2 = 0;
1112 return(0);
1116 * Do the real work of resending.
1118 static int
1119 resend1(void *v, int add_resent)
1121 char *name, *str;
1122 struct name *to;
1123 struct name *sn;
1124 int f, *ip, *msgvec;
1126 str = (char *)v;
1127 /*LINTED*/
1128 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1129 name = laststring(str, &f, 1);
1130 if (name == NULL) {
1131 puts(catgets(catd, CATSET, 47, "No recipient specified."));
1132 return 1;
1134 if (!f) {
1135 *msgvec = first(0, MMNORM);
1136 if (*msgvec == 0) {
1137 if (inhook)
1138 return 0;
1139 puts(catgets(catd, CATSET, 48,
1140 "No applicable messages."));
1141 return 1;
1143 msgvec[1] = 0;
1144 } else if (getmsglist(str, msgvec, 0) < 0)
1145 return 1;
1146 if (*msgvec == 0) {
1147 if (inhook)
1148 return 0;
1149 printf("No applicable messages.\n");
1150 return 1;
1152 sn = nalloc(name, GTO);
1153 to = usermap(sn);
1154 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
1155 if (resend_msg(&message[*ip - 1], to, add_resent) != OKAY)
1156 return 1;
1158 return 0;
1162 * Resend a message list to a third person.
1164 int
1165 resendcmd(void *v)
1167 return resend1(v, 1);
1171 * Resend a message list to a third person without adding headers.
1173 int
1174 Resendcmd(void *v)
1176 return resend1(v, 0);
1180 * 'newmail' or 'inc' command: Check for new mail without writing old
1181 * mail back.
1183 /*ARGSUSED*/
1184 int
1185 newmail(void *v)
1187 int val = 1, mdot;
1188 (void)v;
1190 if ((mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1191 (val = setfile(mailname, 1)) == 0) {
1192 mdot = getmdot(1);
1193 setdot(&message[mdot - 1]);
1195 return val;
1198 static void
1199 list_shortcuts(void)
1201 struct shortcut *s;
1203 for (s = shortcuts; s; s = s->sh_next)
1204 printf("%s=%s\n", s->sh_short, s->sh_long);
1207 int
1208 shortcut(void *v)
1210 char **args = (char **)v;
1211 struct shortcut *s;
1213 if (args[0] == NULL) {
1214 list_shortcuts();
1215 return 0;
1217 if (args[1] == NULL) {
1218 fprintf(stderr, catgets(catd, CATSET, 220,
1219 "expansion name for shortcut missing\n"));
1220 return 1;
1222 if (args[2] != NULL) {
1223 fprintf(stderr, catgets(catd, CATSET, 221,
1224 "too many arguments\n"));
1225 return 1;
1227 if ((s = get_shortcut(args[0])) != NULL) {
1228 free(s->sh_long);
1229 s->sh_long = sstrdup(args[1]);
1230 } else {
1231 s = scalloc(1, sizeof *s);
1232 s->sh_short = sstrdup(args[0]);
1233 s->sh_long = sstrdup(args[1]);
1234 s->sh_next = shortcuts;
1235 shortcuts = s;
1237 return 0;
1240 struct shortcut *
1241 get_shortcut(const char *str)
1243 struct shortcut *s;
1245 for (s = shortcuts; s; s = s->sh_next)
1246 if (strcmp(str, s->sh_short) == 0)
1247 break;
1248 return s;
1251 static enum okay
1252 delete_shortcut(const char *str)
1254 struct shortcut *sp, *sq;
1256 for (sp = shortcuts, sq = NULL; sp; sq = sp, sp = sp->sh_next) {
1257 if (strcmp(sp->sh_short, str) == 0) {
1258 free(sp->sh_short);
1259 free(sp->sh_long);
1260 if (sq)
1261 sq->sh_next = sp->sh_next;
1262 if (sp == shortcuts)
1263 shortcuts = sp->sh_next;
1264 free(sp);
1265 return OKAY;
1268 return STOP;
1271 int
1272 unshortcut(void *v)
1274 char **args = (char **)v;
1275 int errs = 0;
1277 if (args[0] == NULL) {
1278 fprintf(stderr, catgets(catd, CATSET, 222,
1279 "need shortcut names to remove\n"));
1280 return 1;
1282 while (*args != NULL) {
1283 if (delete_shortcut(*args) != OKAY) {
1284 errs = 1;
1285 fprintf(stderr, catgets(catd, CATSET, 223,
1286 "%s: no such shortcut\n"), *args);
1288 args++;
1290 return errs;
1293 struct oldaccount {
1294 struct oldaccount *ac_next; /* next account in list */
1295 char *ac_name; /* name of account */
1296 char **ac_vars; /* variables to set */
1299 static struct oldaccount *oldaccounts;
1301 struct oldaccount *
1302 get_oldaccount(const char *name)
1304 struct oldaccount *a;
1306 for (a = oldaccounts; a; a = a->ac_next)
1307 if (a->ac_name && strcmp(name, a->ac_name) == 0)
1308 break;
1309 return a;
1312 int
1313 account(void *v)
1315 char **args = (char **)v;
1316 struct oldaccount *a;
1317 char *cp;
1318 int i, mc, oqf, nqf;
1319 FILE *fp = stdout;
1321 if (args[0] == NULL) {
1322 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
1323 perror("tmpfile");
1324 return 1;
1326 rm(cp);
1327 Ftfree(&cp);
1328 mc = listaccounts(fp);
1329 for (a = oldaccounts; a; a = a->ac_next)
1330 if (a->ac_name) {
1331 if (mc++)
1332 fputc('\n', fp);
1333 fprintf(fp, "%s:\n", a->ac_name);
1334 for (i = 0; a->ac_vars[i]; i++)
1335 fprintf(fp, "\t%s\n", a->ac_vars[i]);
1337 if (mc)
1338 try_pager(fp);
1339 Fclose(fp);
1340 return 0;
1342 if (args[1] && args[1][0] == '{' && args[1][1] == '\0') {
1343 if (args[2] != NULL) {
1344 fprintf(stderr, "Syntax is: account <name> {\n");
1345 return 1;
1347 if ((a = get_oldaccount(args[0])) != NULL)
1348 a->ac_name = NULL;
1349 return define1(args[0], 1);
1352 if ((cp = expand("&")) == NULL)
1353 return (1);
1354 strncpy(mboxname, cp, sizeof mboxname)[sizeof mboxname - 1] = '\0';
1356 oqf = savequitflags();
1357 if ((a = get_oldaccount(args[0])) == NULL) {
1358 if (args[1]) {
1359 a = scalloc(1, sizeof *a);
1360 a->ac_next = oldaccounts;
1361 oldaccounts = a;
1362 } else {
1363 if ((i = callaccount(args[0])) != CBAD)
1364 goto setf;
1365 printf("Account %s does not exist.\n", args[0]);
1366 return 1;
1369 if (args[1]) {
1370 delaccount(args[0]);
1371 a->ac_name = sstrdup(args[0]);
1372 for (i = 1; args[i]; i++);
1373 a->ac_vars = scalloc(i, sizeof *a->ac_vars);
1374 for (i = 0; args[i+1]; i++)
1375 a->ac_vars[i] = sstrdup(args[i+1]);
1376 } else {
1377 unset_allow_undefined = 1;
1378 set(a->ac_vars);
1379 unset_allow_undefined = 0;
1380 setf: if (!starting) {
1381 nqf = savequitflags();
1382 restorequitflags(oqf);
1383 i = file1("%");
1384 restorequitflags(nqf);
1385 return i;
1388 return 0;
1391 int
1392 cflag(void *v)
1394 struct message *m;
1395 int *msgvec = v;
1396 int *ip;
1398 for (ip = msgvec; *ip != 0; ip++) {
1399 m = &message[*ip-1];
1400 setdot(m);
1401 if ((m->m_flag & (MFLAG|MFLAGGED)) == 0)
1402 m->m_flag |= MFLAG|MFLAGGED;
1404 return 0;
1407 int
1408 cunflag(void *v)
1410 struct message *m;
1411 int *msgvec = v;
1412 int *ip;
1414 for (ip = msgvec; *ip != 0; ip++) {
1415 m = &message[*ip-1];
1416 setdot(m);
1417 if (m->m_flag & (MFLAG|MFLAGGED)) {
1418 m->m_flag &= ~(MFLAG|MFLAGGED);
1419 m->m_flag |= MUNFLAG;
1422 return 0;
1425 int
1426 canswered(void *v)
1428 struct message *m;
1429 int *msgvec = v;
1430 int *ip;
1432 for (ip = msgvec; *ip != 0; ip++) {
1433 m = &message[*ip-1];
1434 setdot(m);
1435 if ((m->m_flag & (MANSWER|MANSWERED)) == 0)
1436 m->m_flag |= MANSWER|MANSWERED;
1438 return 0;
1441 int
1442 cunanswered(void *v)
1444 struct message *m;
1445 int *msgvec = v;
1446 int *ip;
1448 for (ip = msgvec; *ip != 0; ip++) {
1449 m = &message[*ip-1];
1450 setdot(m);
1451 if (m->m_flag & (MANSWER|MANSWERED)) {
1452 m->m_flag &= ~(MANSWER|MANSWERED);
1453 m->m_flag |= MUNANSWER;
1456 return 0;
1459 int
1460 cdraft(void *v)
1462 struct message *m;
1463 int *msgvec = v;
1464 int *ip;
1466 for (ip = msgvec; *ip != 0; ip++) {
1467 m = &message[*ip-1];
1468 setdot(m);
1469 if ((m->m_flag & (MDRAFT|MDRAFTED)) == 0)
1470 m->m_flag |= MDRAFT|MDRAFTED;
1472 return 0;
1475 int
1476 cundraft(void *v)
1478 struct message *m;
1479 int *msgvec = v;
1480 int *ip;
1482 for (ip = msgvec; *ip != 0; ip++) {
1483 m = &message[*ip-1];
1484 setdot(m);
1485 if (m->m_flag & (MDRAFT|MDRAFTED)) {
1486 m->m_flag &= ~(MDRAFT|MDRAFTED);
1487 m->m_flag |= MUNDRAFT;
1490 return 0;
1493 static float
1494 huge(void)
1496 #if defined (_CRAY)
1498 * This is not perfect, but correct for machines with a 32-bit
1499 * IEEE float and a 32-bit unsigned long, and does at least not
1500 * produce SIGFPE on the Cray Y-MP.
1502 union {
1503 float f;
1504 unsigned long l;
1505 } u;
1507 u.l = 0xff800000; /* -inf */
1508 return u.f;
1509 #elif defined (INFINITY)
1510 return -INFINITY;
1511 #elif defined (HUGE_VALF)
1512 return -HUGE_VALF;
1513 #elif defined (FLT_MAX)
1514 return -FLT_MAX;
1515 #else
1516 return -1e10;
1517 #endif
1520 int
1521 ckill(void *v)
1523 struct message *m;
1524 int *msgvec = v;
1525 int *ip;
1527 for (ip = msgvec; *ip != 0; ip++) {
1528 m = &message[*ip-1];
1529 m->m_flag |= MKILL;
1530 m->m_score = huge();
1532 return 0;
1535 int
1536 cunkill(void *v)
1538 struct message *m;
1539 int *msgvec = v;
1540 int *ip;
1542 for (ip = msgvec; *ip != 0; ip++) {
1543 m = &message[*ip-1];
1544 m->m_flag &= ~MKILL;
1545 m->m_score = 0;
1547 return 0;
1550 int
1551 cscore(void *v)
1553 char *str = v;
1554 char *sscore, *xp;
1555 int f, *msgvec, *ip;
1556 double nscore;
1557 struct message *m;
1559 msgvec = salloc((msgCount+2) * sizeof *msgvec);
1560 if ((sscore = laststring(str, &f, 0)) == NULL) {
1561 fprintf(stderr, "No score given.\n");
1562 return 1;
1564 nscore = strtod(sscore, &xp);
1565 if (*xp) {
1566 fprintf(stderr, "Invalid score: \"%s\"\n", sscore);
1567 return 1;
1569 if (nscore > FLT_MAX)
1570 nscore = FLT_MAX;
1571 else if (nscore < -FLT_MAX)
1572 nscore = -FLT_MAX;
1573 if (!f) {
1574 *msgvec = first(0, MMNORM);
1575 if (*msgvec == 0) {
1576 if (inhook)
1577 return 0;
1578 fprintf(stderr, "No messages to score.\n");
1579 return 1;
1581 msgvec[1] = 0;
1582 } else if (getmsglist(str, msgvec, 0) < 0)
1583 return 1;
1584 if (*msgvec == 0) {
1585 if (inhook)
1586 return 0;
1587 fprintf(stderr, "No applicable messages.\n");
1588 return 1;
1590 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
1591 m = &message[*ip-1];
1592 if (m->m_score != huge()) {
1593 m->m_score += nscore;
1594 if (m->m_score < 0)
1595 m->m_flag |= MKILL;
1596 else if (m->m_score > 0)
1597 m->m_flag &= ~MKILL;
1598 if (m->m_score >= 0)
1599 setdot(m);
1602 return 0;
1605 /*ARGSUSED*/
1606 int
1607 cnoop(void *v)
1609 (void)v;
1611 switch (mb.mb_type) {
1612 case MB_IMAP:
1613 imap_noop();
1614 break;
1615 case MB_POP3:
1616 pop3_noop();
1617 break;
1618 default:
1619 break;
1621 return 0;
1624 int
1625 cremove(void *v)
1627 char vb[LINESIZE];
1628 char **args = v;
1629 char *name;
1630 int ec = 0;
1632 if (*args == NULL) {
1633 fprintf(stderr, tr(290, "Syntax is: remove mailbox ...\n"));
1634 return (1);
1636 do {
1637 if ((name = expand(*args)) == NULL)
1638 continue;
1639 if (strcmp(name, mailname) == 0) {
1640 fprintf(stderr, tr(286,
1641 "Cannot remove current mailbox \"%s\".\n"),
1642 name);
1643 ec |= 1;
1644 continue;
1646 snprintf(vb, sizeof vb, tr(287, "Remove \"%s\" (y/n) ? "),
1647 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, tr(288,
1659 "Cannot remove POP3 mailbox \"%s\".\n"),
1660 name);
1661 ec |= 1;
1662 break;
1663 case PROTO_IMAP:
1664 if (imap_remove(name) != OKAY)
1665 ec |= 1;
1666 break;
1667 case PROTO_MAILDIR:
1668 if (maildir_remove(name) != OKAY)
1669 ec |= 1;
1670 break;
1671 case PROTO_UNKNOWN:
1672 fprintf(stderr, tr(289,
1673 "Unknown protocol in \"%s\". Not removed.\n"),
1674 name);
1675 ec |= 1;
1677 } while (*++args);
1678 return ec;
1681 int
1682 crename(void *v)
1684 char **args = v, *old, *new;
1685 enum protocol oldp, newp;
1686 int ec = 0;
1688 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1689 fprintf(stderr, "Syntax: rename old new\n");
1690 return (1);
1693 if ((old = expand(args[0])) == NULL)
1694 return (1);
1695 oldp = which_protocol(old);
1696 if ((new = expand(args[1])) == NULL)
1697 return (1);
1698 newp = which_protocol(new);
1700 if (strcmp(old, mailname) == 0 || strcmp(new, mailname) == 0) {
1701 fprintf(stderr, tr(291,
1702 "Cannot rename current mailbox \"%s\".\n"), old);
1703 return 1;
1705 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1706 fprintf(stderr, tr(292,
1707 "Can only rename folders of same type.\n"));
1708 return 1;
1710 if (newp == PROTO_POP3)
1711 goto nopop3;
1712 switch (oldp) {
1713 case PROTO_FILE:
1714 if (link(old, new) < 0) {
1715 switch (errno) {
1716 case EACCES:
1717 case EEXIST:
1718 case ENAMETOOLONG:
1719 case ENOENT:
1720 case ENOSPC:
1721 case EXDEV:
1722 perror(new);
1723 break;
1724 default:
1725 perror(old);
1727 ec |= 1;
1728 } else if (unlink(old) < 0) {
1729 perror(old);
1730 ec |= 1;
1732 break;
1733 case PROTO_MAILDIR:
1734 if (rename(old, new) < 0) {
1735 perror(old);
1736 ec |= 1;
1738 break;
1739 case PROTO_POP3:
1740 nopop3: fprintf(stderr, tr(293, "Cannot rename POP3 mailboxes.\n"));
1741 ec |= 1;
1742 break;
1743 case PROTO_IMAP:
1744 if (imap_rename(old, new) != OKAY)
1745 ec |= 1;
1746 break;
1747 case PROTO_UNKNOWN:
1748 fprintf(stderr, tr(294,
1749 "Unknown protocol in \"%s\" and \"%s\". "
1750 "Not renamed.\n"), old, new);
1751 ec |= 1;
1753 return ec;