Resort options, exact usage hint..
[s-mailx.git] / cmd3.c
blob476be64b982a5434127c7ae6803ab0d223af9c4b
1 /*
2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
6 */
7 /*
8 * Copyright (c) 1980, 1993
9 * The Regents of the University of California. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by the University of
22 * California, Berkeley and its contributors.
23 * 4. Neither the name of the University nor the names of its contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #include <math.h>
41 #include <float.h>
42 #include "rcv.h"
43 #include "extern.h"
44 #include <unistd.h>
45 #include <errno.h>
48 * Mail -- a mail program
50 * Still more user commands.
53 static int bangexp(char **str, size_t *size);
54 static void make_ref_and_cs(struct message *mp, struct header *head);
55 static int (*respond_or_Respond(int c))(int *, int);
56 static int respond_internal(int *msgvec, int recipient_record);
57 static char *reedit(char *subj);
58 static char *fwdedit(char *subj);
59 static void onpipe(int signo);
60 static void asort(char **list);
61 static int diction(const void *a, const void *b);
62 static int file1(char *name);
63 static int shellecho(const char *cp);
64 static int Respond_internal(int *msgvec, int recipient_record);
65 static int resend1(void *v, int add_resent);
66 static void list_shortcuts(void);
67 static enum okay delete_shortcut(const char *str);
68 static float huge(void);
71 * Process a shell escape by saving signals, ignoring signals,
72 * and forking a sh -c
74 int
75 shell(void *v)
77 char *str = v;
78 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
79 char *shell;
80 char *cmd;
81 size_t cmdsize;
83 cmd = smalloc(cmdsize = strlen(str) + 1);
84 strcpy(cmd, str);
85 if (bangexp(&cmd, &cmdsize) < 0)
86 return 1;
87 if ((shell = value("SHELL")) == NULL)
88 shell = SHELL;
89 run_command(shell, 0, -1, -1, "-c", cmd, NULL);
90 safe_signal(SIGINT, sigint);
91 printf("!\n");
92 free(cmd);
93 return 0;
97 * Fork an interactive shell.
99 /*ARGSUSED*/
100 int
101 dosh(void *v)
103 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
104 char *shell;
105 (void)v;
107 if ((shell = value("SHELL")) == NULL)
108 shell = SHELL;
109 run_command(shell, 0, -1, -1, NULL, NULL, NULL);
110 safe_signal(SIGINT, sigint);
111 putchar('\n');
112 return 0;
116 * Expand the shell escape by expanding unescaped !'s into the
117 * last issued command where possible.
120 static char *lastbang;
121 static size_t lastbangsize;
123 static int
124 bangexp(char **str, size_t *size)
126 char *bangbuf;
127 int changed = 0;
128 int dobang = value("bang") != NULL;
129 size_t sz, i, j, bangbufsize;
131 bangbuf = smalloc(bangbufsize = *size);
132 i = j = 0;
133 while ((*str)[i]) {
134 if (dobang) {
135 if ((*str)[i] == '!') {
136 sz = strlen(lastbang);
137 bangbuf = srealloc(bangbuf, bangbufsize += sz);
138 changed++;
139 strcpy(&bangbuf[j], lastbang);
140 j += sz;
141 i++;
142 continue;
145 if ((*str)[i] == '\\' && (*str)[i + 1] == '!') {
146 bangbuf[j++] = '!';
147 i += 2;
148 changed++;
150 bangbuf[j++] = (*str)[i++];
152 bangbuf[j] = '\0';
153 if (changed) {
154 printf("!%s\n", bangbuf);
155 fflush(stdout);
157 sz = j;
158 if (sz >= *size)
159 *str = srealloc(*str, *size = sz + 1);
160 strcpy(*str, bangbuf);
161 if (sz >= lastbangsize)
162 lastbang = srealloc(lastbang, lastbangsize = sz + 1);
163 strcpy(lastbang, bangbuf);
164 free(bangbuf);
165 return(0);
168 /*ARGSUSED*/
169 int
170 help(void *v)
172 (void)v;
173 /* Very ugly, but take care for compiler supported string lengths :( */
174 fprintf(stdout,
175 "%s commands:\n",
176 progname);
177 puts(
178 "type <message list> type messages\n"
179 "next goto and type next message\n"
180 "from <message list> give head lines of messages\n"
181 "headers print out active message headers\n"
182 "delete <message list> delete messages\n"
183 "undelete <message list> undelete messages\n");
184 puts(
185 "save <message list> folder append messages to folder and mark as saved\n"
186 "copy <message list> folder append messages to folder without marking them\n"
187 "write <message list> file append message texts to file, save attachments\n"
188 "preserve <message list> keep incoming messages in mailbox even if saved\n"
189 "Reply <message list> reply to message senders\n"
190 "reply <message list> reply to message senders and all recipients\n");
191 puts(
192 "mail addresses mail to specific recipients\n"
193 "file folder change to another folder\n"
194 "quit quit and apply changes to folder\n"
195 "xit quit and discard changes made to folder\n"
196 "! shell escape\n"
197 "cd <directory> chdir to directory or home if none given\n"
198 "list list names of all available commands\n");
199 fprintf(stdout,
200 "\nA <message list> consists of integers, ranges of same, or other criteria\n"
201 "separated by spaces. If omitted, %s uses the last message typed.\n",
202 progname);
203 return(0);
207 * Change user's working directory.
209 int
210 schdir(void *v)
212 char **arglist = v;
213 char *cp;
215 if (*arglist == NULL)
216 cp = homedir;
217 else
218 if ((cp = expand(*arglist)) == NULL)
219 return(1);
220 if (chdir(cp) < 0) {
221 perror(cp);
222 return(1);
224 return 0;
227 static void
228 make_ref_and_cs(struct message *mp, struct header *head)
230 char *oldref, *oldmsgid, *newref, *cp;
231 size_t reflen;
232 unsigned i;
233 struct name *n;
235 oldref = hfield("references", mp);
236 oldmsgid = hfield("message-id", mp);
237 if (oldmsgid == NULL || *oldmsgid == '\0') {
238 head->h_ref = NULL;
239 return;
241 reflen = 1;
242 if (oldref)
243 reflen += strlen(oldref) + 2;
244 if (oldmsgid)
245 reflen += strlen(oldmsgid);
246 newref = ac_alloc(reflen);
247 if (oldref) {
248 strcpy(newref, oldref);
249 if (oldmsgid) {
250 strcat(newref, ", ");
251 strcat(newref, oldmsgid);
253 } else if (oldmsgid)
254 strcpy(newref, oldmsgid);
255 n = extract(newref, GREF);
256 ac_free(newref);
258 * Limit the references to 21 entries.
260 while (n->n_flink != NULL)
261 n = n->n_flink;
262 for (i = 1; i < 21; i++) {
263 if (n->n_blink != NULL)
264 n = n->n_blink;
265 else
266 break;
268 n->n_blink = NULL;
269 head->h_ref = n;
270 if (value("reply-in-same-charset") != NULL &&
271 (cp = hfield("content-type", mp)) != NULL)
272 head->h_charset = mime_getparam("charset", cp);
275 static int
276 (*respond_or_Respond(int c))(int *, int)
278 int opt = 0;
280 opt += (value("Replyall") != NULL);
281 opt += (value("flipr") != NULL);
282 return ((opt == 1) ^ (c == 'R')) ? Respond_internal : respond_internal;
285 int
286 respond(void *v)
288 return (respond_or_Respond('r'))((int *)v, 0);
291 int
292 respondall(void *v)
294 return respond_internal((int *)v, 0);
297 int
298 respondsender(void *v)
300 return Respond_internal((int *)v, 0);
303 int
304 followup(void *v)
306 return (respond_or_Respond('r'))((int *)v, 1);
309 int
310 followupall(void *v)
312 return respond_internal((int *)v, 1);
315 int
316 followupsender(void *v)
318 return Respond_internal((int *)v, 1);
322 * Reply to a list of messages. Extract each name from the
323 * message header and send them off to mail1()
325 static int
326 respond_internal(int *msgvec, int recipient_record)
328 int Eflag;
329 struct message *mp;
330 char *cp, *rcv;
331 enum gfield gf = value("fullnames") ? GFULL : GSKIN;
332 struct name *np = NULL;
333 struct header head;
335 memset(&head, 0, sizeof head);
336 if (msgvec[1] != 0) {
337 printf(catgets(catd, CATSET, 37,
338 "Sorry, can't reply to multiple messages at once\n"));
339 return(1);
341 mp = &message[msgvec[0] - 1];
342 touch(mp);
343 setdot(mp);
344 if ((rcv = hfield("reply-to", mp)) == NULL)
345 if ((rcv = hfield("from", mp)) == NULL)
346 rcv = nameof(mp, 1);
347 if (rcv != NULL)
348 np = sextract(rcv, GTO|gf);
349 if (! value("recipients-in-cc") && (cp = hfield("to", mp)) != NULL)
350 np = cat(np, sextract(cp, GTO|gf));
351 np = elide(np);
353 * Delete my name from the reply list,
354 * and with it, all my alternate names.
356 np = delete_alternates(np);
357 if (np == NULL)
358 np = sextract(rcv, GTO|gf);
359 head.h_to = np;
360 if ((head.h_subject = hfield("subject", mp)) == NULL)
361 head.h_subject = hfield("subj", mp);
362 head.h_subject = reedit(head.h_subject);
363 /* Cc: */
364 np = NULL;
365 if (value("recipients-in-cc") && (cp = hfield("to", mp)) != NULL)
366 np = sextract(cp, GCC|gf);
367 if ((cp = hfield("cc", mp)) != NULL)
368 np = cat(np, sextract(cp, GCC|gf));
369 if (np != NULL)
370 head.h_cc = elide(delete_alternates(np));
371 make_ref_and_cs(mp, &head);
372 Eflag = value("skipemptybody") != NULL;
373 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
374 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
375 mp->m_flag |= MANSWER|MANSWERED;
376 return(0);
380 * Modify the subject we are replying to to begin with Re: if
381 * it does not already.
383 static char *
384 reedit(char *subj)
386 char *newsubj;
387 struct str in, out;
389 if (subj == NULL || *subj == '\0')
390 return NULL;
391 in.s = subj;
392 in.l = strlen(subj);
393 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
394 if ((out.s[0] == 'r' || out.s[0] == 'R') &&
395 (out.s[1] == 'e' || out.s[1] == 'E') &&
396 out.s[2] == ':')
397 return out.s;
398 newsubj = salloc(out.l + 5);
399 strcpy(newsubj, "Re: ");
400 strcpy(newsubj + 4, out.s);
401 return newsubj;
405 * Forward a message to a new recipient, in the sense of RFC 2822.
407 static int
408 forward1(char *str, int recipient_record)
410 int Eflag;
411 int *msgvec, f;
412 char *recipient;
413 struct message *mp;
414 struct header head;
415 int forward_as_attachment;
417 forward_as_attachment = value("forward-as-attachment") != NULL;
418 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
419 if ((recipient = laststring(str, &f, 0)) == NULL) {
420 puts(catgets(catd, CATSET, 47, "No recipient specified."));
421 return 1;
423 if (!f) {
424 *msgvec = first(0, MMNORM);
425 if (*msgvec == 0) {
426 if (inhook)
427 return 0;
428 printf("No messages to forward.\n");
429 return 1;
431 msgvec[1] = 0;
433 if (f && getmsglist(str, msgvec, 0) < 0)
434 return 1;
435 if (*msgvec == 0) {
436 if (inhook)
437 return 0;
438 printf("No applicable messages.\n");
439 return 1;
441 if (msgvec[1] != 0) {
442 printf("Cannot forward multiple messages at once\n");
443 return 1;
445 memset(&head, 0, sizeof head);
446 if ((head.h_to = sextract(recipient,
447 GTO | (value("fullnames") ? GFULL : GSKIN))) == NULL)
448 return 1;
449 mp = &message[*msgvec - 1];
450 if (forward_as_attachment) {
451 head.h_attach = csalloc(1, sizeof *head.h_attach);
452 head.h_attach->a_msgno = *msgvec;
453 } else {
454 touch(mp);
455 setdot(mp);
457 if ((head.h_subject = hfield("subject", mp)) == NULL)
458 head.h_subject = hfield("subj", mp);
459 head.h_subject = fwdedit(head.h_subject);
460 Eflag = value("skipemptybody") != NULL;
461 mail1(&head, 1, forward_as_attachment ? NULL : mp,
462 NULL, recipient_record, 1, 0, Eflag);
463 return 0;
467 * Modify the subject we are replying to to begin with Fwd:.
469 static char *
470 fwdedit(char *subj)
472 char *newsubj;
473 struct str in, out;
475 if (subj == NULL || *subj == '\0')
476 return NULL;
477 in.s = subj;
478 in.l = strlen(subj);
479 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
480 newsubj = salloc(strlen(out.s) + 6);
481 strcpy(newsubj, "Fwd: ");
482 strcpy(&newsubj[5], out.s);
483 free(out.s);
484 return newsubj;
488 * The 'forward' command.
491 forwardcmd(void *v)
493 return forward1(v, 0);
497 * Similar to forward, saving the message in a file named after the
498 * first recipient.
501 Forwardcmd(void *v)
503 return forward1(v, 1);
507 * Preserve the named messages, so that they will be sent
508 * back to the system mailbox.
510 int
511 preserve(void *v)
513 int *msgvec = v;
514 struct message *mp;
515 int *ip, mesg;
517 if (edit) {
518 printf(catgets(catd, CATSET, 39,
519 "Cannot \"preserve\" in edit mode\n"));
520 return(1);
522 for (ip = msgvec; *ip != 0; ip++) {
523 mesg = *ip;
524 mp = &message[mesg-1];
525 mp->m_flag |= MPRESERVE;
526 mp->m_flag &= ~MBOX;
527 setdot(mp);
529 * This is now Austin Group Request XCU #20.
531 did_print_dot = 1;
533 return(0);
537 * Mark all given messages as unread.
539 int
540 unread(void *v)
542 int *msgvec = v;
543 int *ip;
545 for (ip = msgvec; *ip != 0; ip++) {
546 setdot(&message[*ip-1]);
547 dot->m_flag &= ~(MREAD|MTOUCH);
548 dot->m_flag |= MSTATUS;
549 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
550 imap_unread(&message[*ip-1], *ip);
552 * The "unread" command is not part of POSIX mailx.
554 did_print_dot = 1;
556 return(0);
560 * Mark all given messages as read.
563 seen(void *v)
565 int *msgvec = v;
566 int *ip;
568 for (ip = msgvec; *ip; ip++) {
569 setdot(&message[*ip-1]);
570 touch(&message[*ip-1]);
572 return 0;
576 * Print the size of each message.
578 int
579 messize(void *v)
581 int *msgvec = v;
582 struct message *mp;
583 int *ip, mesg;
585 for (ip = msgvec; *ip != 0; ip++) {
586 mesg = *ip;
587 mp = &message[mesg-1];
588 printf("%d: ", mesg);
589 if (mp->m_xlines > 0)
590 printf("%ld", mp->m_xlines);
591 else
592 putchar(' ');
593 printf("/%lu\n", (unsigned long)mp->m_xsize);
595 return(0);
599 * Quit quickly. If we are sourcing, just pop the input level
600 * by returning an error.
602 /*ARGSUSED*/
603 int
604 rexit(void *v)
606 (void)v;
607 if (sourcing)
608 return(1);
609 exit(0);
610 /*NOTREACHED*/
613 static sigjmp_buf pipejmp;
615 /*ARGSUSED*/
616 static void
617 onpipe(int signo)
619 (void)signo;
620 siglongjmp(pipejmp, 1);
624 * Set or display a variable value. Syntax is similar to that
625 * of sh.
627 int
628 set(void *v)
630 char **arglist = v;
631 struct var *vp;
632 char *cp, *cp2;
633 char **ap, **p;
634 int errs, h, s;
635 FILE *obuf = stdout;
636 int bsdset = value("bsdcompat") != NULL || value("bsdset") != NULL;
638 if (*arglist == NULL) {
639 for (h = 0, s = 1; h < HSHSIZE; h++)
640 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
641 s++;
642 /*LINTED*/
643 ap = (char **)salloc(s * sizeof *ap);
644 for (h = 0, p = ap; h < HSHSIZE; h++)
645 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
646 *p++ = vp->v_name;
647 *p = NULL;
648 asort(ap);
649 if (is_a_tty[0] && is_a_tty[1] && (cp = value("crt")) != NULL) {
650 if (s > (*cp == '\0' ? screensize() : atoi(cp)) + 3) {
651 cp = get_pager();
652 /* TODO should be below the Popen?
653 * TODO Problem: Popen doesn't encapsulate it,
654 * TODO may leave child run if fdopen() fails!
655 * TODO even more such stuff in this file! */
656 if (sigsetjmp(pipejmp, 1))
657 goto endpipe;
658 if ((obuf = Popen(cp, "w", NULL, 1)) == NULL) {
659 perror(cp);
660 obuf = stdout;
661 } else
662 safe_signal(SIGPIPE, onpipe);
665 for (p = ap; *p != NULL; p++) {
666 if (bsdset)
667 fprintf(obuf, "%s\t%s\n", *p, value(*p));
668 else {
669 if ((cp = value(*p)) != NULL && *cp)
670 fprintf(obuf, "%s=\"%s\"\n",
671 *p, value(*p));
672 else
673 fprintf(obuf, "%s\n", *p);
676 endpipe:
677 if (obuf != stdout) {
678 safe_signal(SIGPIPE, SIG_IGN);
679 Pclose(obuf);
680 safe_signal(SIGPIPE, dflpipe);
682 return(0);
684 errs = 0;
685 for (ap = arglist; *ap != NULL; ap++) {
686 char *varbuf;
688 varbuf = ac_alloc(strlen(*ap) + 1);
689 cp = *ap;
690 cp2 = varbuf;
691 while (*cp != '=' && *cp != '\0')
692 *cp2++ = *cp++;
693 *cp2 = '\0';
694 if (*cp == '\0')
695 cp = "";
696 else
697 cp++;
698 if (strcmp(varbuf, "") == 0) {
699 printf(tr(41, "Non-null variable name required\n"));
700 errs++;
701 ac_free(varbuf);
702 continue;
704 if (varbuf[0] == 'n' && varbuf[1] == 'o')
705 errs += unset_internal(&varbuf[2]);
706 else
707 assign(varbuf, cp);
708 ac_free(varbuf);
710 return(errs);
714 * Unset a bunch of variable values.
716 int
717 unset(void *v)
719 int errs;
720 char **ap;
722 errs = 0;
723 for (ap = (char **)v; *ap != NULL; ap++)
724 errs += unset_internal(*ap);
725 return(errs);
729 * Put add users to a group.
731 int
732 group(void *v)
734 char **argv = v;
735 struct grouphead *gh;
736 struct group *gp;
737 int h;
738 int s;
739 char **ap, *gname, **p;
741 if (*argv == NULL) {
742 for (h = 0, s = 1; h < HSHSIZE; h++)
743 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
744 s++;
745 /*LINTED*/
746 ap = (char **)salloc(s * sizeof *ap);
747 for (h = 0, p = ap; h < HSHSIZE; h++)
748 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
749 *p++ = gh->g_name;
750 *p = NULL;
751 asort(ap);
752 for (p = ap; *p != NULL; p++)
753 printgroup(*p);
754 return(0);
756 if (argv[1] == NULL) {
757 printgroup(*argv);
758 return(0);
760 gname = *argv;
761 h = hash(gname);
762 if ((gh = findgroup(gname)) == NULL) {
763 gh = (struct grouphead *)scalloc(1, sizeof *gh);
764 gh->g_name = vcopy(gname);
765 gh->g_list = NULL;
766 gh->g_link = groups[h];
767 groups[h] = gh;
771 * Insert names from the command list into the group.
772 * Who cares if there are duplicates? They get tossed
773 * later anyway.
776 for (ap = argv+1; *ap != NULL; ap++) {
777 gp = (struct group *)scalloc(1, sizeof *gp);
778 gp->ge_name = vcopy(*ap);
779 gp->ge_link = gh->g_list;
780 gh->g_list = gp;
782 return(0);
786 * Delete the passed groups.
788 int
789 ungroup(void *v)
791 char **argv = v;
793 if (*argv == NULL) {
794 printf(catgets(catd, CATSET, 209,
795 "Must specify alias or group to remove\n"));
796 return 1;
799 remove_group(*argv);
800 while (*++argv != NULL);
801 return 0;
805 * Sort the passed string vecotor into ascending dictionary
806 * order.
808 static void
809 asort(char **list)
811 char **ap;
813 for (ap = list; *ap != NULL; ap++)
815 if (ap-list < 2)
816 return;
817 qsort(list, ap-list, sizeof(*list), diction);
821 * Do a dictionary order comparison of the arguments from
822 * qsort.
824 static int
825 diction(const void *a, const void *b)
827 return(strcmp(*(char **)a, *(char **)b));
831 * Change to another file. With no argument, print information about
832 * the current file.
834 int
835 cfile(void *v)
837 char **argv = v;
839 if (argv[0] == NULL) {
840 newfileinfo();
841 return 0;
843 strncpy(mboxname, expand("&"), sizeof mboxname)[sizeof mboxname-1]='\0';
844 return file1(*argv);
847 static int
848 file1(char *name)
850 int i;
852 if (inhook) {
853 fprintf(stderr, "Cannot change folder from within a hook.\n");
854 return 1;
856 i = setfile(name, 0);
857 if (i < 0)
858 return 1;
859 callhook(mailname, 0);
860 if (i > 0 && value("emptystart") == NULL)
861 return 1;
862 announce(value("bsdcompat") != NULL || value("bsdannounce") != NULL);
863 return 0;
866 static int
867 shellecho(const char *cp)
869 int cflag = 0, n;
870 char c;
872 while (*cp) {
873 if (*cp == '\\') {
874 switch (*++cp) {
875 case '\0':
876 return cflag;
877 case 'a':
878 putchar('\a');
879 break;
880 case 'b':
881 putchar('\b');
882 break;
883 case 'c':
884 cflag = 1;
885 break;
886 case 'f':
887 putchar('\f');
888 break;
889 case 'n':
890 putchar('\n');
891 break;
892 case 'r':
893 putchar('\r');
894 break;
895 case 't':
896 putchar('\t');
897 break;
898 case 'v':
899 putchar('\v');
900 break;
901 default:
902 putchar(*cp&0377);
903 break;
904 case '0':
905 c = 0;
906 n = 3;
907 while (n-- && octalchar(cp[1]&0377)) {
908 c <<= 3;
909 c |= cp[1] - '0';
910 cp++;
912 putchar(c);
914 } else
915 putchar(*cp & 0377);
916 cp++;
918 return cflag;
922 * Expand file names like echo
924 int
925 echo(void *v)
927 char **argv = v;
928 char **ap;
929 char *cp;
930 int cflag = 0;
932 for (ap = argv; *ap != NULL; ap++) {
933 cp = *ap;
934 if ((cp = expand(cp)) != NULL) {
935 if (ap != argv)
936 putchar(' ');
937 cflag |= shellecho(cp);
940 if (!cflag)
941 putchar('\n');
942 return 0;
945 int
946 Respond(void *v)
948 return (respond_or_Respond('R'))((int *)v, 0);
951 int
952 Followup(void *v)
954 return (respond_or_Respond('R'))((int *)v, 1);
958 * Reply to a series of messages by simply mailing to the senders
959 * and not messing around with the To: and Cc: lists as in normal
960 * reply.
962 static int
963 Respond_internal(int *msgvec, int recipient_record)
965 int Eflag;
966 struct header head;
967 struct message *mp;
968 enum gfield gf = value("fullnames") ? GFULL : GSKIN;
969 int *ap;
970 char *cp;
972 memset(&head, 0, sizeof head);
973 for (ap = msgvec; *ap != 0; ap++) {
974 mp = &message[*ap - 1];
975 touch(mp);
976 setdot(mp);
977 if ((cp = hfield("reply-to", mp)) == NULL)
978 if ((cp = hfield("from", mp)) == NULL)
979 cp = nameof(mp, 2);
980 head.h_to = cat(head.h_to, sextract(cp, GTO|gf));
982 if (head.h_to == NULL)
983 return 0;
984 mp = &message[msgvec[0] - 1];
985 if ((head.h_subject = hfield("subject", mp)) == NULL)
986 head.h_subject = hfield("subj", mp);
987 head.h_subject = reedit(head.h_subject);
988 make_ref_and_cs(mp, &head);
989 Eflag = value("skipemptybody") != NULL;
990 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
991 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
992 mp->m_flag |= MANSWER|MANSWERED;
993 return 0;
997 * Conditional commands. These allow one to parameterize one's
998 * .mailrc and do some things if sending, others if receiving.
1000 int
1001 ifcmd(void *v)
1003 char **argv = v;
1004 char *cp;
1006 if (cond != CANY) {
1007 printf(catgets(catd, CATSET, 42, "Illegal nested \"if\"\n"));
1008 return(1);
1010 cond = CANY;
1011 cp = argv[0];
1012 switch (*cp) {
1013 case 'r': case 'R':
1014 cond = CRCV;
1015 break;
1017 case 's': case 'S':
1018 cond = CSEND;
1019 break;
1021 case 't': case 'T':
1022 cond = CTERM;
1023 break;
1025 default:
1026 printf(catgets(catd, CATSET, 43,
1027 "Unrecognized if-keyword: \"%s\"\n"), cp);
1028 return(1);
1030 return(0);
1034 * Implement 'else'. This is pretty simple -- we just
1035 * flip over the conditional flag.
1037 /*ARGSUSED*/
1038 int
1039 elsecmd(void *v)
1041 (void)v;
1043 switch (cond) {
1044 case CANY:
1045 printf(catgets(catd, CATSET, 44,
1046 "\"Else\" without matching \"if\"\n"));
1047 return(1);
1049 case CSEND:
1050 cond = CRCV;
1051 break;
1053 case CRCV:
1054 cond = CSEND;
1055 break;
1057 case CTERM:
1058 cond = CNONTERM;
1059 break;
1061 default:
1062 printf(catgets(catd, CATSET, 45,
1063 "Mail's idea of conditions is screwed up\n"));
1064 cond = CANY;
1065 break;
1067 return(0);
1071 * End of if statement. Just set cond back to anything.
1073 /*ARGSUSED*/
1074 int
1075 endifcmd(void *v)
1077 (void)v;
1079 if (cond == CANY) {
1080 printf(catgets(catd, CATSET, 46,
1081 "\"Endif\" without matching \"if\"\n"));
1082 return(1);
1084 cond = CANY;
1085 return(0);
1089 * Set the list of alternate names.
1091 int
1092 alternates(void *v)
1094 char **namelist = v;
1095 int c;
1096 char **ap, **ap2, *cp;
1098 c = argcount(namelist) + 1;
1099 if (c == 1) {
1100 if (altnames == 0)
1101 return(0);
1102 for (ap = altnames; *ap; ap++)
1103 printf("%s ", *ap);
1104 printf("\n");
1105 return(0);
1107 if (altnames != 0)
1108 free(altnames);
1109 altnames = scalloc(c, sizeof (char *));
1110 for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
1111 cp = scalloc(strlen(*ap) + 1, sizeof (char));
1112 strcpy(cp, *ap);
1113 *ap2 = cp;
1115 *ap2 = 0;
1116 return(0);
1120 * Do the real work of resending.
1122 static int
1123 resend1(void *v, int add_resent)
1125 char *name, *str;
1126 struct name *to;
1127 struct name *sn;
1128 int f, *ip, *msgvec;
1130 str = (char *)v;
1131 /*LINTED*/
1132 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1133 name = laststring(str, &f, 1);
1134 if (name == NULL) {
1135 puts(catgets(catd, CATSET, 47, "No recipient specified."));
1136 return 1;
1138 if (!f) {
1139 *msgvec = first(0, MMNORM);
1140 if (*msgvec == 0) {
1141 if (inhook)
1142 return 0;
1143 puts(catgets(catd, CATSET, 48,
1144 "No applicable messages."));
1145 return 1;
1147 msgvec[1] = 0;
1148 } else if (getmsglist(str, msgvec, 0) < 0)
1149 return 1;
1150 if (*msgvec == 0) {
1151 if (inhook)
1152 return 0;
1153 printf("No applicable messages.\n");
1154 return 1;
1156 sn = nalloc(name, GTO);
1157 to = usermap(sn);
1158 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
1159 if (resend_msg(&message[*ip - 1], to, add_resent) != OKAY)
1160 return 1;
1162 return 0;
1166 * Resend a message list to a third person.
1168 int
1169 resendcmd(void *v)
1171 return resend1(v, 1);
1175 * Resend a message list to a third person without adding headers.
1177 int
1178 Resendcmd(void *v)
1180 return resend1(v, 0);
1184 * 'newmail' or 'inc' command: Check for new mail without writing old
1185 * mail back.
1187 /*ARGSUSED*/
1188 int
1189 newmail(void *v)
1191 int val = 1, mdot;
1192 (void)v;
1194 if ((mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1195 (val = setfile(mailname, 1)) == 0) {
1196 mdot = getmdot(1);
1197 setdot(&message[mdot - 1]);
1199 return val;
1202 static void
1203 list_shortcuts(void)
1205 struct shortcut *s;
1207 for (s = shortcuts; s; s = s->sh_next)
1208 printf("%s=%s\n", s->sh_short, s->sh_long);
1211 int
1212 shortcut(void *v)
1214 char **args = (char **)v;
1215 struct shortcut *s;
1217 if (args[0] == NULL) {
1218 list_shortcuts();
1219 return 0;
1221 if (args[1] == NULL) {
1222 fprintf(stderr, catgets(catd, CATSET, 220,
1223 "expansion name for shortcut missing\n"));
1224 return 1;
1226 if (args[2] != NULL) {
1227 fprintf(stderr, catgets(catd, CATSET, 221,
1228 "too many arguments\n"));
1229 return 1;
1231 if ((s = get_shortcut(args[0])) != NULL) {
1232 free(s->sh_long);
1233 s->sh_long = sstrdup(args[1]);
1234 } else {
1235 s = scalloc(1, sizeof *s);
1236 s->sh_short = sstrdup(args[0]);
1237 s->sh_long = sstrdup(args[1]);
1238 s->sh_next = shortcuts;
1239 shortcuts = s;
1241 return 0;
1244 struct shortcut *
1245 get_shortcut(const char *str)
1247 struct shortcut *s;
1249 for (s = shortcuts; s; s = s->sh_next)
1250 if (strcmp(str, s->sh_short) == 0)
1251 break;
1252 return s;
1255 static enum okay
1256 delete_shortcut(const char *str)
1258 struct shortcut *sp, *sq;
1260 for (sp = shortcuts, sq = NULL; sp; sq = sp, sp = sp->sh_next) {
1261 if (strcmp(sp->sh_short, str) == 0) {
1262 free(sp->sh_short);
1263 free(sp->sh_long);
1264 if (sq)
1265 sq->sh_next = sp->sh_next;
1266 if (sp == shortcuts)
1267 shortcuts = sp->sh_next;
1268 free(sp);
1269 return OKAY;
1272 return STOP;
1275 int
1276 unshortcut(void *v)
1278 char **args = (char **)v;
1279 int errs = 0;
1281 if (args[0] == NULL) {
1282 fprintf(stderr, catgets(catd, CATSET, 222,
1283 "need shortcut names to remove\n"));
1284 return 1;
1286 while (*args != NULL) {
1287 if (delete_shortcut(*args) != OKAY) {
1288 errs = 1;
1289 fprintf(stderr, catgets(catd, CATSET, 223,
1290 "%s: no such shortcut\n"), *args);
1292 args++;
1294 return errs;
1297 struct oldaccount {
1298 struct oldaccount *ac_next; /* next account in list */
1299 char *ac_name; /* name of account */
1300 char **ac_vars; /* variables to set */
1303 static struct oldaccount *oldaccounts;
1305 struct oldaccount *
1306 get_oldaccount(const char *name)
1308 struct oldaccount *a;
1310 for (a = oldaccounts; a; a = a->ac_next)
1311 if (a->ac_name && strcmp(name, a->ac_name) == 0)
1312 break;
1313 return a;
1316 int
1317 account(void *v)
1319 char **args = (char **)v;
1320 struct oldaccount *a;
1321 char *cp;
1322 int i, mc, oqf, nqf;
1323 FILE *fp = stdout;
1325 if (args[0] == NULL) {
1326 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
1327 perror("tmpfile");
1328 return 1;
1330 rm(cp);
1331 Ftfree(&cp);
1332 mc = listaccounts(fp);
1333 for (a = oldaccounts; a; a = a->ac_next)
1334 if (a->ac_name) {
1335 if (mc++)
1336 fputc('\n', fp);
1337 fprintf(fp, "%s:\n", a->ac_name);
1338 for (i = 0; a->ac_vars[i]; i++)
1339 fprintf(fp, "\t%s\n", a->ac_vars[i]);
1341 if (mc)
1342 try_pager(fp);
1343 Fclose(fp);
1344 return 0;
1346 if (args[1] && args[1][0] == '{' && args[1][1] == '\0') {
1347 if (args[2] != NULL) {
1348 fprintf(stderr, "Syntax is: account <name> {\n");
1349 return 1;
1351 if ((a = get_oldaccount(args[0])) != NULL)
1352 a->ac_name = NULL;
1353 return define1(args[0], 1);
1355 strncpy(mboxname, expand("&"), 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, "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,
1641 "Cannot remove current mailbox \"%s\".\n",
1642 name);
1643 ec |= 1;
1644 continue;
1646 snprintf(vb, sizeof vb, "Remove \"%s\" (y/n) ? ", name);
1647 if (yorn(vb) == 0)
1648 continue;
1649 switch (which_protocol(name)) {
1650 case PROTO_FILE:
1651 if (unlink(name) < 0) { /* do not handle .gz .bz2 */
1652 perror(name);
1653 ec |= 1;
1655 break;
1656 case PROTO_POP3:
1657 fprintf(stderr, "Cannot remove POP3 mailbox \"%s\".\n",
1658 name);
1659 ec |= 1;
1660 break;
1661 case PROTO_IMAP:
1662 if (imap_remove(name) != OKAY)
1663 ec |= 1;
1664 break;
1665 case PROTO_MAILDIR:
1666 if (maildir_remove(name) != OKAY)
1667 ec |= 1;
1668 break;
1669 case PROTO_UNKNOWN:
1670 fprintf(stderr,
1671 "Unknown protocol in \"%s\". Not removed.\n",
1672 name);
1673 ec |= 1;
1675 } while (*++args);
1676 return ec;
1679 int
1680 crename(void *v)
1682 char **args = v, *old, *new;
1683 enum protocol oldp, newp;
1684 int ec = 0;
1686 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1687 fprintf(stderr, "Syntax: rename old new\n");
1688 return 1;
1690 old = expand(args[0]);
1691 oldp = which_protocol(old);
1692 new = expand(args[1]);
1693 newp = which_protocol(new);
1694 if (strcmp(old, mailname) == 0 || strcmp(new, mailname) == 0) {
1695 fprintf(stderr, "Cannot rename current mailbox \"%s\".\n", old);
1696 return 1;
1698 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1699 fprintf(stderr, "Can only rename folders of same type.\n");
1700 return 1;
1702 if (newp == PROTO_POP3)
1703 goto nopop3;
1704 switch (oldp) {
1705 case PROTO_FILE:
1706 if (link(old, new) < 0) {
1707 switch (errno) {
1708 case EACCES:
1709 case EEXIST:
1710 case ENAMETOOLONG:
1711 case ENOENT:
1712 case ENOSPC:
1713 case EXDEV:
1714 perror(new);
1715 break;
1716 default:
1717 perror(old);
1719 ec |= 1;
1720 } else if (unlink(old) < 0) {
1721 perror(old);
1722 ec |= 1;
1724 break;
1725 case PROTO_MAILDIR:
1726 if (rename(old, new) < 0) {
1727 perror(old);
1728 ec |= 1;
1730 break;
1731 case PROTO_POP3:
1732 nopop3: fprintf(stderr, "Cannot rename POP3 mailboxes.\n");
1733 ec |= 1;
1734 break;
1735 case PROTO_IMAP:
1736 if (imap_rename(old, new) != OKAY)
1737 ec |= 1;
1738 break;
1739 case PROTO_UNKNOWN:
1740 fprintf(stderr, "Unknown protocol in \"%s\" and \"%s\". "
1741 "Not renamed.\n", old, new);
1742 ec |= 1;
1744 return ec;