prefixwrite(), TODO: change file tracking..
[s-mailx.git] / cmd3.c
blobd70fcf9b36396833da60981d8d74b6713f8d6ea6
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 *obuf = stdout;
634 int bsdset = value("bsdcompat") != NULL || value("bsdset") != NULL;
636 if (*arglist == NULL) {
637 for (h = 0, s = 1; h < HSHSIZE; h++)
638 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
639 s++;
640 /*LINTED*/
641 ap = (char **)salloc(s * sizeof *ap);
642 for (h = 0, p = ap; h < HSHSIZE; h++)
643 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
644 *p++ = vp->v_name;
645 *p = NULL;
646 asort(ap);
647 if (is_a_tty[0] && is_a_tty[1] && (cp = value("crt")) != NULL) {
648 if (s > (*cp == '\0' ? screensize() : atoi(cp)) + 3) {
649 cp = get_pager();
650 /* TODO should be below the Popen?
651 * TODO Problem: Popen doesn't encapsulate it,
652 * TODO may leave child run if fdopen() fails!
653 * TODO even more such stuff in this file! */
654 if (sigsetjmp(pipejmp, 1))
655 goto endpipe;
656 if ((obuf = Popen(cp, "w", NULL, 1)) == NULL) {
657 perror(cp);
658 obuf = stdout;
659 } else
660 safe_signal(SIGPIPE, onpipe);
663 for (p = ap; *p != NULL; p++) {
664 if (bsdset)
665 fprintf(obuf, "%s\t%s\n", *p, value(*p));
666 else {
667 if ((cp = value(*p)) != NULL && *cp)
668 fprintf(obuf, "%s=\"%s\"\n",
669 *p, value(*p));
670 else
671 fprintf(obuf, "%s\n", *p);
674 endpipe:
675 if (obuf != stdout) {
676 safe_signal(SIGPIPE, SIG_IGN);
677 Pclose(obuf);
678 safe_signal(SIGPIPE, dflpipe);
680 return(0);
682 errs = 0;
683 for (ap = arglist; *ap != NULL; ap++) {
684 char *varbuf;
686 varbuf = ac_alloc(strlen(*ap) + 1);
687 cp = *ap;
688 cp2 = varbuf;
689 while (*cp != '=' && *cp != '\0')
690 *cp2++ = *cp++;
691 *cp2 = '\0';
692 if (*cp == '\0')
693 cp = "";
694 else
695 cp++;
696 if (strcmp(varbuf, "") == 0) {
697 printf(tr(41, "Non-null variable name required\n"));
698 errs++;
699 ac_free(varbuf);
700 continue;
702 if (varbuf[0] == 'n' && varbuf[1] == 'o')
703 errs += unset_internal(&varbuf[2]);
704 else
705 assign(varbuf, cp);
706 ac_free(varbuf);
708 return(errs);
712 * Unset a bunch of variable values.
714 int
715 unset(void *v)
717 int errs;
718 char **ap;
720 errs = 0;
721 for (ap = (char **)v; *ap != NULL; ap++)
722 errs += unset_internal(*ap);
723 return(errs);
727 * Put add users to a group.
729 int
730 group(void *v)
732 char **argv = v;
733 struct grouphead *gh;
734 struct group *gp;
735 int h;
736 int s;
737 char **ap, *gname, **p;
739 if (*argv == NULL) {
740 for (h = 0, s = 1; h < HSHSIZE; h++)
741 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
742 s++;
743 /*LINTED*/
744 ap = (char **)salloc(s * sizeof *ap);
745 for (h = 0, p = ap; h < HSHSIZE; h++)
746 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
747 *p++ = gh->g_name;
748 *p = NULL;
749 asort(ap);
750 for (p = ap; *p != NULL; p++)
751 printgroup(*p);
752 return(0);
754 if (argv[1] == NULL) {
755 printgroup(*argv);
756 return(0);
758 gname = *argv;
759 h = hash(gname);
760 if ((gh = findgroup(gname)) == NULL) {
761 gh = (struct grouphead *)scalloc(1, sizeof *gh);
762 gh->g_name = vcopy(gname);
763 gh->g_list = NULL;
764 gh->g_link = groups[h];
765 groups[h] = gh;
769 * Insert names from the command list into the group.
770 * Who cares if there are duplicates? They get tossed
771 * later anyway.
774 for (ap = argv+1; *ap != NULL; ap++) {
775 gp = (struct group *)scalloc(1, sizeof *gp);
776 gp->ge_name = vcopy(*ap);
777 gp->ge_link = gh->g_list;
778 gh->g_list = gp;
780 return(0);
784 * Delete the passed groups.
786 int
787 ungroup(void *v)
789 char **argv = v;
791 if (*argv == NULL) {
792 printf(catgets(catd, CATSET, 209,
793 "Must specify alias or group to remove\n"));
794 return 1;
797 remove_group(*argv);
798 while (*++argv != NULL);
799 return 0;
803 * Sort the passed string vecotor into ascending dictionary
804 * order.
806 static void
807 asort(char **list)
809 char **ap;
811 for (ap = list; *ap != NULL; ap++)
813 if (ap-list < 2)
814 return;
815 qsort(list, ap-list, sizeof(*list), diction);
819 * Do a dictionary order comparison of the arguments from
820 * qsort.
822 static int
823 diction(const void *a, const void *b)
825 return(strcmp(*(char **)a, *(char **)b));
829 * Change to another file. With no argument, print information about
830 * the current file.
832 int
833 cfile(void *v)
835 char **argv = v;
837 if (argv[0] == NULL) {
838 newfileinfo();
839 return 0;
841 strncpy(mboxname, expand("&"), sizeof mboxname)[sizeof mboxname-1]='\0';
842 return file1(*argv);
845 static int
846 file1(char *name)
848 int i;
850 if (inhook) {
851 fprintf(stderr, "Cannot change folder from within a hook.\n");
852 return 1;
854 i = setfile(name, 0);
855 if (i < 0)
856 return 1;
857 callhook(mailname, 0);
858 if (i > 0 && value("emptystart") == NULL)
859 return 1;
860 announce(value("bsdcompat") != NULL || value("bsdannounce") != NULL);
861 return 0;
864 static int
865 shellecho(const char *cp)
867 int cflag = 0, n;
868 char c;
870 while (*cp) {
871 if (*cp == '\\') {
872 switch (*++cp) {
873 case '\0':
874 return cflag;
875 case 'a':
876 putchar('\a');
877 break;
878 case 'b':
879 putchar('\b');
880 break;
881 case 'c':
882 cflag = 1;
883 break;
884 case 'f':
885 putchar('\f');
886 break;
887 case 'n':
888 putchar('\n');
889 break;
890 case 'r':
891 putchar('\r');
892 break;
893 case 't':
894 putchar('\t');
895 break;
896 case 'v':
897 putchar('\v');
898 break;
899 default:
900 putchar(*cp&0377);
901 break;
902 case '0':
903 c = 0;
904 n = 3;
905 while (n-- && octalchar(cp[1]&0377)) {
906 c <<= 3;
907 c |= cp[1] - '0';
908 cp++;
910 putchar(c);
912 } else
913 putchar(*cp & 0377);
914 cp++;
916 return cflag;
920 * Expand file names like echo
922 int
923 echo(void *v)
925 char **argv = v;
926 char **ap;
927 char *cp;
928 int cflag = 0;
930 for (ap = argv; *ap != NULL; ap++) {
931 cp = *ap;
932 if ((cp = expand(cp)) != NULL) {
933 if (ap != argv)
934 putchar(' ');
935 cflag |= shellecho(cp);
938 if (!cflag)
939 putchar('\n');
940 return 0;
943 int
944 Respond(void *v)
946 return (respond_or_Respond('R'))((int *)v, 0);
949 int
950 Followup(void *v)
952 return (respond_or_Respond('R'))((int *)v, 1);
956 * Reply to a series of messages by simply mailing to the senders
957 * and not messing around with the To: and Cc: lists as in normal
958 * reply.
960 static int
961 Respond_internal(int *msgvec, int recipient_record)
963 int Eflag;
964 struct header head;
965 struct message *mp;
966 enum gfield gf = value("fullnames") ? GFULL : GSKIN;
967 int *ap;
968 char *cp;
970 memset(&head, 0, sizeof head);
971 for (ap = msgvec; *ap != 0; ap++) {
972 mp = &message[*ap - 1];
973 touch(mp);
974 setdot(mp);
975 if ((cp = hfield1("reply-to", mp)) == NULL)
976 if ((cp = hfield1("from", mp)) == NULL)
977 cp = nameof(mp, 2);
978 head.h_to = cat(head.h_to, sextract(cp, GTO|gf));
980 if (head.h_to == NULL)
981 return 0;
982 mp = &message[msgvec[0] - 1];
983 head.h_subject = hfield1("subject", mp);
984 head.h_subject = reedit(head.h_subject);
985 make_ref_and_cs(mp, &head);
986 Eflag = value("skipemptybody") != NULL;
987 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
988 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
989 mp->m_flag |= MANSWER|MANSWERED;
990 return 0;
994 * Conditional commands. These allow one to parameterize one's
995 * .mailrc and do some things if sending, others if receiving.
997 int
998 ifcmd(void *v)
1000 char **argv = v;
1001 char *cp;
1003 if (cond != CANY) {
1004 printf(catgets(catd, CATSET, 42, "Illegal nested \"if\"\n"));
1005 return(1);
1007 cond = CANY;
1008 cp = argv[0];
1009 switch (*cp) {
1010 case 'r': case 'R':
1011 cond = CRCV;
1012 break;
1014 case 's': case 'S':
1015 cond = CSEND;
1016 break;
1018 case 't': case 'T':
1019 cond = CTERM;
1020 break;
1022 default:
1023 printf(catgets(catd, CATSET, 43,
1024 "Unrecognized if-keyword: \"%s\"\n"), cp);
1025 return(1);
1027 return(0);
1031 * Implement 'else'. This is pretty simple -- we just
1032 * flip over the conditional flag.
1034 /*ARGSUSED*/
1035 int
1036 elsecmd(void *v)
1038 (void)v;
1040 switch (cond) {
1041 case CANY:
1042 printf(catgets(catd, CATSET, 44,
1043 "\"Else\" without matching \"if\"\n"));
1044 return(1);
1046 case CSEND:
1047 cond = CRCV;
1048 break;
1050 case CRCV:
1051 cond = CSEND;
1052 break;
1054 case CTERM:
1055 cond = CNONTERM;
1056 break;
1058 default:
1059 printf(catgets(catd, CATSET, 45,
1060 "Mail's idea of conditions is screwed up\n"));
1061 cond = CANY;
1062 break;
1064 return(0);
1068 * End of if statement. Just set cond back to anything.
1070 /*ARGSUSED*/
1071 int
1072 endifcmd(void *v)
1074 (void)v;
1076 if (cond == CANY) {
1077 printf(catgets(catd, CATSET, 46,
1078 "\"Endif\" without matching \"if\"\n"));
1079 return(1);
1081 cond = CANY;
1082 return(0);
1086 * Set the list of alternate names.
1088 int
1089 alternates(void *v)
1091 char **namelist = v;
1092 int c;
1093 char **ap, **ap2, *cp;
1095 c = argcount(namelist) + 1;
1096 if (c == 1) {
1097 if (altnames == 0)
1098 return(0);
1099 for (ap = altnames; *ap; ap++)
1100 printf("%s ", *ap);
1101 printf("\n");
1102 return(0);
1104 if (altnames != 0)
1105 free(altnames);
1106 altnames = scalloc(c, sizeof (char *));
1107 for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
1108 cp = scalloc(strlen(*ap) + 1, sizeof (char));
1109 strcpy(cp, *ap);
1110 *ap2 = cp;
1112 *ap2 = 0;
1113 return(0);
1117 * Do the real work of resending.
1119 static int
1120 resend1(void *v, int add_resent)
1122 char *name, *str;
1123 struct name *to;
1124 struct name *sn;
1125 int f, *ip, *msgvec;
1127 str = (char *)v;
1128 /*LINTED*/
1129 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1130 name = laststring(str, &f, 1);
1131 if (name == NULL) {
1132 puts(catgets(catd, CATSET, 47, "No recipient specified."));
1133 return 1;
1135 if (!f) {
1136 *msgvec = first(0, MMNORM);
1137 if (*msgvec == 0) {
1138 if (inhook)
1139 return 0;
1140 puts(catgets(catd, CATSET, 48,
1141 "No applicable messages."));
1142 return 1;
1144 msgvec[1] = 0;
1145 } else if (getmsglist(str, msgvec, 0) < 0)
1146 return 1;
1147 if (*msgvec == 0) {
1148 if (inhook)
1149 return 0;
1150 printf("No applicable messages.\n");
1151 return 1;
1153 sn = nalloc(name, GTO);
1154 to = usermap(sn);
1155 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
1156 if (resend_msg(&message[*ip - 1], to, add_resent) != OKAY)
1157 return 1;
1159 return 0;
1163 * Resend a message list to a third person.
1165 int
1166 resendcmd(void *v)
1168 return resend1(v, 1);
1172 * Resend a message list to a third person without adding headers.
1174 int
1175 Resendcmd(void *v)
1177 return resend1(v, 0);
1181 * 'newmail' or 'inc' command: Check for new mail without writing old
1182 * mail back.
1184 /*ARGSUSED*/
1185 int
1186 newmail(void *v)
1188 int val = 1, mdot;
1189 (void)v;
1191 if ((mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1192 (val = setfile(mailname, 1)) == 0) {
1193 mdot = getmdot(1);
1194 setdot(&message[mdot - 1]);
1196 return val;
1199 static void
1200 list_shortcuts(void)
1202 struct shortcut *s;
1204 for (s = shortcuts; s; s = s->sh_next)
1205 printf("%s=%s\n", s->sh_short, s->sh_long);
1208 int
1209 shortcut(void *v)
1211 char **args = (char **)v;
1212 struct shortcut *s;
1214 if (args[0] == NULL) {
1215 list_shortcuts();
1216 return 0;
1218 if (args[1] == NULL) {
1219 fprintf(stderr, catgets(catd, CATSET, 220,
1220 "expansion name for shortcut missing\n"));
1221 return 1;
1223 if (args[2] != NULL) {
1224 fprintf(stderr, catgets(catd, CATSET, 221,
1225 "too many arguments\n"));
1226 return 1;
1228 if ((s = get_shortcut(args[0])) != NULL) {
1229 free(s->sh_long);
1230 s->sh_long = sstrdup(args[1]);
1231 } else {
1232 s = scalloc(1, sizeof *s);
1233 s->sh_short = sstrdup(args[0]);
1234 s->sh_long = sstrdup(args[1]);
1235 s->sh_next = shortcuts;
1236 shortcuts = s;
1238 return 0;
1241 struct shortcut *
1242 get_shortcut(const char *str)
1244 struct shortcut *s;
1246 for (s = shortcuts; s; s = s->sh_next)
1247 if (strcmp(str, s->sh_short) == 0)
1248 break;
1249 return s;
1252 static enum okay
1253 delete_shortcut(const char *str)
1255 struct shortcut *sp, *sq;
1257 for (sp = shortcuts, sq = NULL; sp; sq = sp, sp = sp->sh_next) {
1258 if (strcmp(sp->sh_short, str) == 0) {
1259 free(sp->sh_short);
1260 free(sp->sh_long);
1261 if (sq)
1262 sq->sh_next = sp->sh_next;
1263 if (sp == shortcuts)
1264 shortcuts = sp->sh_next;
1265 free(sp);
1266 return OKAY;
1269 return STOP;
1272 int
1273 unshortcut(void *v)
1275 char **args = (char **)v;
1276 int errs = 0;
1278 if (args[0] == NULL) {
1279 fprintf(stderr, catgets(catd, CATSET, 222,
1280 "need shortcut names to remove\n"));
1281 return 1;
1283 while (*args != NULL) {
1284 if (delete_shortcut(*args) != OKAY) {
1285 errs = 1;
1286 fprintf(stderr, catgets(catd, CATSET, 223,
1287 "%s: no such shortcut\n"), *args);
1289 args++;
1291 return errs;
1294 struct oldaccount {
1295 struct oldaccount *ac_next; /* next account in list */
1296 char *ac_name; /* name of account */
1297 char **ac_vars; /* variables to set */
1300 static struct oldaccount *oldaccounts;
1302 struct oldaccount *
1303 get_oldaccount(const char *name)
1305 struct oldaccount *a;
1307 for (a = oldaccounts; a; a = a->ac_next)
1308 if (a->ac_name && strcmp(name, a->ac_name) == 0)
1309 break;
1310 return a;
1313 int
1314 account(void *v)
1316 char **args = (char **)v;
1317 struct oldaccount *a;
1318 char *cp;
1319 int i, mc, oqf, nqf;
1320 FILE *fp = stdout;
1322 if (args[0] == NULL) {
1323 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
1324 perror("tmpfile");
1325 return 1;
1327 rm(cp);
1328 Ftfree(&cp);
1329 mc = listaccounts(fp);
1330 for (a = oldaccounts; a; a = a->ac_next)
1331 if (a->ac_name) {
1332 if (mc++)
1333 fputc('\n', fp);
1334 fprintf(fp, "%s:\n", a->ac_name);
1335 for (i = 0; a->ac_vars[i]; i++)
1336 fprintf(fp, "\t%s\n", a->ac_vars[i]);
1338 if (mc)
1339 try_pager(fp);
1340 Fclose(fp);
1341 return 0;
1343 if (args[1] && args[1][0] == '{' && args[1][1] == '\0') {
1344 if (args[2] != NULL) {
1345 fprintf(stderr, "Syntax is: account <name> {\n");
1346 return 1;
1348 if ((a = get_oldaccount(args[0])) != NULL)
1349 a->ac_name = NULL;
1350 return define1(args[0], 1);
1352 strncpy(mboxname, expand("&"), sizeof mboxname)[sizeof mboxname-1]='\0';
1353 oqf = savequitflags();
1354 if ((a = get_oldaccount(args[0])) == NULL) {
1355 if (args[1]) {
1356 a = scalloc(1, sizeof *a);
1357 a->ac_next = oldaccounts;
1358 oldaccounts = a;
1359 } else {
1360 if ((i = callaccount(args[0])) != CBAD)
1361 goto setf;
1362 printf("Account %s does not exist.\n", args[0]);
1363 return 1;
1366 if (args[1]) {
1367 delaccount(args[0]);
1368 a->ac_name = sstrdup(args[0]);
1369 for (i = 1; args[i]; i++);
1370 a->ac_vars = scalloc(i, sizeof *a->ac_vars);
1371 for (i = 0; args[i+1]; i++)
1372 a->ac_vars[i] = sstrdup(args[i+1]);
1373 } else {
1374 unset_allow_undefined = 1;
1375 set(a->ac_vars);
1376 unset_allow_undefined = 0;
1377 setf: if (!starting) {
1378 nqf = savequitflags();
1379 restorequitflags(oqf);
1380 i = file1("%");
1381 restorequitflags(nqf);
1382 return i;
1385 return 0;
1388 int
1389 cflag(void *v)
1391 struct message *m;
1392 int *msgvec = v;
1393 int *ip;
1395 for (ip = msgvec; *ip != 0; ip++) {
1396 m = &message[*ip-1];
1397 setdot(m);
1398 if ((m->m_flag & (MFLAG|MFLAGGED)) == 0)
1399 m->m_flag |= MFLAG|MFLAGGED;
1401 return 0;
1404 int
1405 cunflag(void *v)
1407 struct message *m;
1408 int *msgvec = v;
1409 int *ip;
1411 for (ip = msgvec; *ip != 0; ip++) {
1412 m = &message[*ip-1];
1413 setdot(m);
1414 if (m->m_flag & (MFLAG|MFLAGGED)) {
1415 m->m_flag &= ~(MFLAG|MFLAGGED);
1416 m->m_flag |= MUNFLAG;
1419 return 0;
1422 int
1423 canswered(void *v)
1425 struct message *m;
1426 int *msgvec = v;
1427 int *ip;
1429 for (ip = msgvec; *ip != 0; ip++) {
1430 m = &message[*ip-1];
1431 setdot(m);
1432 if ((m->m_flag & (MANSWER|MANSWERED)) == 0)
1433 m->m_flag |= MANSWER|MANSWERED;
1435 return 0;
1438 int
1439 cunanswered(void *v)
1441 struct message *m;
1442 int *msgvec = v;
1443 int *ip;
1445 for (ip = msgvec; *ip != 0; ip++) {
1446 m = &message[*ip-1];
1447 setdot(m);
1448 if (m->m_flag & (MANSWER|MANSWERED)) {
1449 m->m_flag &= ~(MANSWER|MANSWERED);
1450 m->m_flag |= MUNANSWER;
1453 return 0;
1456 int
1457 cdraft(void *v)
1459 struct message *m;
1460 int *msgvec = v;
1461 int *ip;
1463 for (ip = msgvec; *ip != 0; ip++) {
1464 m = &message[*ip-1];
1465 setdot(m);
1466 if ((m->m_flag & (MDRAFT|MDRAFTED)) == 0)
1467 m->m_flag |= MDRAFT|MDRAFTED;
1469 return 0;
1472 int
1473 cundraft(void *v)
1475 struct message *m;
1476 int *msgvec = v;
1477 int *ip;
1479 for (ip = msgvec; *ip != 0; ip++) {
1480 m = &message[*ip-1];
1481 setdot(m);
1482 if (m->m_flag & (MDRAFT|MDRAFTED)) {
1483 m->m_flag &= ~(MDRAFT|MDRAFTED);
1484 m->m_flag |= MUNDRAFT;
1487 return 0;
1490 static float
1491 huge(void)
1493 #if defined (_CRAY)
1495 * This is not perfect, but correct for machines with a 32-bit
1496 * IEEE float and a 32-bit unsigned long, and does at least not
1497 * produce SIGFPE on the Cray Y-MP.
1499 union {
1500 float f;
1501 unsigned long l;
1502 } u;
1504 u.l = 0xff800000; /* -inf */
1505 return u.f;
1506 #elif defined (INFINITY)
1507 return -INFINITY;
1508 #elif defined (HUGE_VALF)
1509 return -HUGE_VALF;
1510 #elif defined (FLT_MAX)
1511 return -FLT_MAX;
1512 #else
1513 return -1e10;
1514 #endif
1517 int
1518 ckill(void *v)
1520 struct message *m;
1521 int *msgvec = v;
1522 int *ip;
1524 for (ip = msgvec; *ip != 0; ip++) {
1525 m = &message[*ip-1];
1526 m->m_flag |= MKILL;
1527 m->m_score = huge();
1529 return 0;
1532 int
1533 cunkill(void *v)
1535 struct message *m;
1536 int *msgvec = v;
1537 int *ip;
1539 for (ip = msgvec; *ip != 0; ip++) {
1540 m = &message[*ip-1];
1541 m->m_flag &= ~MKILL;
1542 m->m_score = 0;
1544 return 0;
1547 int
1548 cscore(void *v)
1550 char *str = v;
1551 char *sscore, *xp;
1552 int f, *msgvec, *ip;
1553 double nscore;
1554 struct message *m;
1556 msgvec = salloc((msgCount+2) * sizeof *msgvec);
1557 if ((sscore = laststring(str, &f, 0)) == NULL) {
1558 fprintf(stderr, "No score given.\n");
1559 return 1;
1561 nscore = strtod(sscore, &xp);
1562 if (*xp) {
1563 fprintf(stderr, "Invalid score: \"%s\"\n", sscore);
1564 return 1;
1566 if (nscore > FLT_MAX)
1567 nscore = FLT_MAX;
1568 else if (nscore < -FLT_MAX)
1569 nscore = -FLT_MAX;
1570 if (!f) {
1571 *msgvec = first(0, MMNORM);
1572 if (*msgvec == 0) {
1573 if (inhook)
1574 return 0;
1575 fprintf(stderr, "No messages to score.\n");
1576 return 1;
1578 msgvec[1] = 0;
1579 } else if (getmsglist(str, msgvec, 0) < 0)
1580 return 1;
1581 if (*msgvec == 0) {
1582 if (inhook)
1583 return 0;
1584 fprintf(stderr, "No applicable messages.\n");
1585 return 1;
1587 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
1588 m = &message[*ip-1];
1589 if (m->m_score != huge()) {
1590 m->m_score += nscore;
1591 if (m->m_score < 0)
1592 m->m_flag |= MKILL;
1593 else if (m->m_score > 0)
1594 m->m_flag &= ~MKILL;
1595 if (m->m_score >= 0)
1596 setdot(m);
1599 return 0;
1602 /*ARGSUSED*/
1603 int
1604 cnoop(void *v)
1606 (void)v;
1608 switch (mb.mb_type) {
1609 case MB_IMAP:
1610 imap_noop();
1611 break;
1612 case MB_POP3:
1613 pop3_noop();
1614 break;
1615 default:
1616 break;
1618 return 0;
1621 int
1622 cremove(void *v)
1624 char vb[LINESIZE];
1625 char **args = v;
1626 char *name;
1627 int ec = 0;
1629 if (*args == NULL) {
1630 fprintf(stderr, "Syntax is: remove mailbox ...\n");
1631 return 1;
1633 do {
1634 if ((name = expand(*args)) == NULL)
1635 continue;
1636 if (strcmp(name, mailname) == 0) {
1637 fprintf(stderr,
1638 "Cannot remove current mailbox \"%s\".\n",
1639 name);
1640 ec |= 1;
1641 continue;
1643 snprintf(vb, sizeof vb, "Remove \"%s\" (y/n) ? ", name);
1644 if (yorn(vb) == 0)
1645 continue;
1646 switch (which_protocol(name)) {
1647 case PROTO_FILE:
1648 if (unlink(name) < 0) { /* do not handle .gz .bz2 */
1649 perror(name);
1650 ec |= 1;
1652 break;
1653 case PROTO_POP3:
1654 fprintf(stderr, "Cannot remove POP3 mailbox \"%s\".\n",
1655 name);
1656 ec |= 1;
1657 break;
1658 case PROTO_IMAP:
1659 if (imap_remove(name) != OKAY)
1660 ec |= 1;
1661 break;
1662 case PROTO_MAILDIR:
1663 if (maildir_remove(name) != OKAY)
1664 ec |= 1;
1665 break;
1666 case PROTO_UNKNOWN:
1667 fprintf(stderr,
1668 "Unknown protocol in \"%s\". Not removed.\n",
1669 name);
1670 ec |= 1;
1672 } while (*++args);
1673 return ec;
1676 int
1677 crename(void *v)
1679 char **args = v, *old, *new;
1680 enum protocol oldp, newp;
1681 int ec = 0;
1683 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1684 fprintf(stderr, "Syntax: rename old new\n");
1685 return 1;
1687 old = expand(args[0]);
1688 oldp = which_protocol(old);
1689 new = expand(args[1]);
1690 newp = which_protocol(new);
1691 if (strcmp(old, mailname) == 0 || strcmp(new, mailname) == 0) {
1692 fprintf(stderr, "Cannot rename current mailbox \"%s\".\n", old);
1693 return 1;
1695 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1696 fprintf(stderr, "Can only rename folders of same type.\n");
1697 return 1;
1699 if (newp == PROTO_POP3)
1700 goto nopop3;
1701 switch (oldp) {
1702 case PROTO_FILE:
1703 if (link(old, new) < 0) {
1704 switch (errno) {
1705 case EACCES:
1706 case EEXIST:
1707 case ENAMETOOLONG:
1708 case ENOENT:
1709 case ENOSPC:
1710 case EXDEV:
1711 perror(new);
1712 break;
1713 default:
1714 perror(old);
1716 ec |= 1;
1717 } else if (unlink(old) < 0) {
1718 perror(old);
1719 ec |= 1;
1721 break;
1722 case PROTO_MAILDIR:
1723 if (rename(old, new) < 0) {
1724 perror(old);
1725 ec |= 1;
1727 break;
1728 case PROTO_POP3:
1729 nopop3: fprintf(stderr, "Cannot rename POP3 mailboxes.\n");
1730 ec |= 1;
1731 break;
1732 case PROTO_IMAP:
1733 if (imap_rename(old, new) != OKAY)
1734 ec |= 1;
1735 break;
1736 case PROTO_UNKNOWN:
1737 fprintf(stderr, "Unknown protocol in \"%s\" and \"%s\". "
1738 "Not renamed.\n", old, new);
1739 ec |= 1;
1741 return ec;