Reorder and "style-up" names.c..
[s-mailx.git] / cmd3.c
bloba8ff4290750443efd5a5f27331ace3d1b0f72541
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;
834 if (argv[0] == NULL) {
835 newfileinfo();
836 return 0;
838 strncpy(mboxname, expand("&"), sizeof mboxname)[sizeof mboxname-1]='\0';
839 return file1(*argv);
842 static int
843 file1(char *name)
845 int i;
847 if (inhook) {
848 fprintf(stderr, "Cannot change folder from within a hook.\n");
849 return 1;
851 i = setfile(name, 0);
852 if (i < 0)
853 return 1;
854 callhook(mailname, 0);
855 if (i > 0 && value("emptystart") == NULL)
856 return 1;
857 announce(value("bsdcompat") != NULL || value("bsdannounce") != NULL);
858 return 0;
861 static int
862 shellecho(const char *cp)
864 int cflag = 0, n;
865 char c;
867 while (*cp) {
868 if (*cp == '\\') {
869 switch (*++cp) {
870 case '\0':
871 return cflag;
872 case 'a':
873 putchar('\a');
874 break;
875 case 'b':
876 putchar('\b');
877 break;
878 case 'c':
879 cflag = 1;
880 break;
881 case 'f':
882 putchar('\f');
883 break;
884 case 'n':
885 putchar('\n');
886 break;
887 case 'r':
888 putchar('\r');
889 break;
890 case 't':
891 putchar('\t');
892 break;
893 case 'v':
894 putchar('\v');
895 break;
896 default:
897 putchar(*cp&0377);
898 break;
899 case '0':
900 c = 0;
901 n = 3;
902 while (n-- && octalchar(cp[1]&0377)) {
903 c <<= 3;
904 c |= cp[1] - '0';
905 cp++;
907 putchar(c);
909 } else
910 putchar(*cp & 0377);
911 cp++;
913 return cflag;
917 * Expand file names like echo
919 int
920 echo(void *v)
922 char **argv = v;
923 char **ap;
924 char *cp;
925 int cflag = 0;
927 for (ap = argv; *ap != NULL; ap++) {
928 cp = *ap;
929 if ((cp = expand(cp)) != NULL) {
930 if (ap != argv)
931 putchar(' ');
932 cflag |= shellecho(cp);
935 if (!cflag)
936 putchar('\n');
937 return 0;
940 int
941 Respond(void *v)
943 return (respond_or_Respond('R'))((int *)v, 0);
946 int
947 Followup(void *v)
949 return (respond_or_Respond('R'))((int *)v, 1);
953 * Reply to a series of messages by simply mailing to the senders
954 * and not messing around with the To: and Cc: lists as in normal
955 * reply.
957 static int
958 Respond_internal(int *msgvec, int recipient_record)
960 int Eflag;
961 struct header head;
962 struct message *mp;
963 enum gfield gf = value("fullnames") ? GFULL : GSKIN;
964 int *ap;
965 char *cp;
967 memset(&head, 0, sizeof head);
968 for (ap = msgvec; *ap != 0; ap++) {
969 mp = &message[*ap - 1];
970 touch(mp);
971 setdot(mp);
972 if ((cp = hfield1("reply-to", mp)) == NULL)
973 if ((cp = hfield1("from", mp)) == NULL)
974 cp = nameof(mp, 2);
975 head.h_to = cat(head.h_to, lextract(cp, GTO|gf));
977 if (head.h_to == NULL)
978 return 0;
979 mp = &message[msgvec[0] - 1];
980 head.h_subject = hfield1("subject", mp);
981 head.h_subject = reedit(head.h_subject);
982 make_ref_and_cs(mp, &head);
983 Eflag = value("skipemptybody") != NULL;
984 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
985 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
986 mp->m_flag |= MANSWER|MANSWERED;
987 return 0;
991 * Conditional commands. These allow one to parameterize one's
992 * .mailrc and do some things if sending, others if receiving.
994 int
995 ifcmd(void *v)
997 char **argv = v;
998 char *cp;
1000 if (cond != CANY) {
1001 printf(catgets(catd, CATSET, 42, "Illegal nested \"if\"\n"));
1002 return(1);
1004 cond = CANY;
1005 cp = argv[0];
1006 switch (*cp) {
1007 case 'r': case 'R':
1008 cond = CRCV;
1009 break;
1011 case 's': case 'S':
1012 cond = CSEND;
1013 break;
1015 case 't': case 'T':
1016 cond = CTERM;
1017 break;
1019 default:
1020 printf(catgets(catd, CATSET, 43,
1021 "Unrecognized if-keyword: \"%s\"\n"), cp);
1022 return(1);
1024 return(0);
1028 * Implement 'else'. This is pretty simple -- we just
1029 * flip over the conditional flag.
1031 /*ARGSUSED*/
1032 int
1033 elsecmd(void *v)
1035 (void)v;
1037 switch (cond) {
1038 case CANY:
1039 printf(catgets(catd, CATSET, 44,
1040 "\"Else\" without matching \"if\"\n"));
1041 return(1);
1043 case CSEND:
1044 cond = CRCV;
1045 break;
1047 case CRCV:
1048 cond = CSEND;
1049 break;
1051 case CTERM:
1052 cond = CNONTERM;
1053 break;
1055 default:
1056 printf(catgets(catd, CATSET, 45,
1057 "Mail's idea of conditions is screwed up\n"));
1058 cond = CANY;
1059 break;
1061 return(0);
1065 * End of if statement. Just set cond back to anything.
1067 /*ARGSUSED*/
1068 int
1069 endifcmd(void *v)
1071 (void)v;
1073 if (cond == CANY) {
1074 printf(catgets(catd, CATSET, 46,
1075 "\"Endif\" without matching \"if\"\n"));
1076 return(1);
1078 cond = CANY;
1079 return(0);
1083 * Set the list of alternate names.
1085 int
1086 alternates(void *v)
1088 char **namelist = v;
1089 int c;
1090 char **ap, **ap2, *cp;
1092 c = argcount(namelist) + 1;
1093 if (c == 1) {
1094 if (altnames == 0)
1095 return(0);
1096 for (ap = altnames; *ap; ap++)
1097 printf("%s ", *ap);
1098 printf("\n");
1099 return(0);
1101 if (altnames != 0)
1102 free(altnames);
1103 altnames = scalloc(c, sizeof (char *));
1104 for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
1105 cp = scalloc(strlen(*ap) + 1, sizeof (char));
1106 strcpy(cp, *ap);
1107 *ap2 = cp;
1109 *ap2 = 0;
1110 return(0);
1114 * Do the real work of resending.
1116 static int
1117 resend1(void *v, int add_resent)
1119 char *name, *str;
1120 struct name *to;
1121 struct name *sn;
1122 int f, *ip, *msgvec;
1124 str = (char *)v;
1125 /*LINTED*/
1126 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1127 name = laststring(str, &f, 1);
1128 if (name == NULL) {
1129 puts(catgets(catd, CATSET, 47, "No recipient specified."));
1130 return 1;
1132 if (!f) {
1133 *msgvec = first(0, MMNORM);
1134 if (*msgvec == 0) {
1135 if (inhook)
1136 return 0;
1137 puts(catgets(catd, CATSET, 48,
1138 "No applicable messages."));
1139 return 1;
1141 msgvec[1] = 0;
1142 } else if (getmsglist(str, msgvec, 0) < 0)
1143 return 1;
1144 if (*msgvec == 0) {
1145 if (inhook)
1146 return 0;
1147 printf("No applicable messages.\n");
1148 return 1;
1150 sn = nalloc(name, GTO);
1151 to = usermap(sn);
1152 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
1153 if (resend_msg(&message[*ip - 1], to, add_resent) != OKAY)
1154 return 1;
1156 return 0;
1160 * Resend a message list to a third person.
1162 int
1163 resendcmd(void *v)
1165 return resend1(v, 1);
1169 * Resend a message list to a third person without adding headers.
1171 int
1172 Resendcmd(void *v)
1174 return resend1(v, 0);
1178 * 'newmail' or 'inc' command: Check for new mail without writing old
1179 * mail back.
1181 /*ARGSUSED*/
1182 int
1183 newmail(void *v)
1185 int val = 1, mdot;
1186 (void)v;
1188 if ((mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1189 (val = setfile(mailname, 1)) == 0) {
1190 mdot = getmdot(1);
1191 setdot(&message[mdot - 1]);
1193 return val;
1196 static void
1197 list_shortcuts(void)
1199 struct shortcut *s;
1201 for (s = shortcuts; s; s = s->sh_next)
1202 printf("%s=%s\n", s->sh_short, s->sh_long);
1205 int
1206 shortcut(void *v)
1208 char **args = (char **)v;
1209 struct shortcut *s;
1211 if (args[0] == NULL) {
1212 list_shortcuts();
1213 return 0;
1215 if (args[1] == NULL) {
1216 fprintf(stderr, catgets(catd, CATSET, 220,
1217 "expansion name for shortcut missing\n"));
1218 return 1;
1220 if (args[2] != NULL) {
1221 fprintf(stderr, catgets(catd, CATSET, 221,
1222 "too many arguments\n"));
1223 return 1;
1225 if ((s = get_shortcut(args[0])) != NULL) {
1226 free(s->sh_long);
1227 s->sh_long = sstrdup(args[1]);
1228 } else {
1229 s = scalloc(1, sizeof *s);
1230 s->sh_short = sstrdup(args[0]);
1231 s->sh_long = sstrdup(args[1]);
1232 s->sh_next = shortcuts;
1233 shortcuts = s;
1235 return 0;
1238 struct shortcut *
1239 get_shortcut(const char *str)
1241 struct shortcut *s;
1243 for (s = shortcuts; s; s = s->sh_next)
1244 if (strcmp(str, s->sh_short) == 0)
1245 break;
1246 return s;
1249 static enum okay
1250 delete_shortcut(const char *str)
1252 struct shortcut *sp, *sq;
1254 for (sp = shortcuts, sq = NULL; sp; sq = sp, sp = sp->sh_next) {
1255 if (strcmp(sp->sh_short, str) == 0) {
1256 free(sp->sh_short);
1257 free(sp->sh_long);
1258 if (sq)
1259 sq->sh_next = sp->sh_next;
1260 if (sp == shortcuts)
1261 shortcuts = sp->sh_next;
1262 free(sp);
1263 return OKAY;
1266 return STOP;
1269 int
1270 unshortcut(void *v)
1272 char **args = (char **)v;
1273 int errs = 0;
1275 if (args[0] == NULL) {
1276 fprintf(stderr, catgets(catd, CATSET, 222,
1277 "need shortcut names to remove\n"));
1278 return 1;
1280 while (*args != NULL) {
1281 if (delete_shortcut(*args) != OKAY) {
1282 errs = 1;
1283 fprintf(stderr, catgets(catd, CATSET, 223,
1284 "%s: no such shortcut\n"), *args);
1286 args++;
1288 return errs;
1291 struct oldaccount {
1292 struct oldaccount *ac_next; /* next account in list */
1293 char *ac_name; /* name of account */
1294 char **ac_vars; /* variables to set */
1297 static struct oldaccount *oldaccounts;
1299 struct oldaccount *
1300 get_oldaccount(const char *name)
1302 struct oldaccount *a;
1304 for (a = oldaccounts; a; a = a->ac_next)
1305 if (a->ac_name && strcmp(name, a->ac_name) == 0)
1306 break;
1307 return a;
1310 int
1311 account(void *v)
1313 char **args = (char **)v;
1314 struct oldaccount *a;
1315 char *cp;
1316 int i, mc, oqf, nqf;
1317 FILE *fp = stdout;
1319 if (args[0] == NULL) {
1320 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
1321 perror("tmpfile");
1322 return 1;
1324 rm(cp);
1325 Ftfree(&cp);
1326 mc = listaccounts(fp);
1327 for (a = oldaccounts; a; a = a->ac_next)
1328 if (a->ac_name) {
1329 if (mc++)
1330 fputc('\n', fp);
1331 fprintf(fp, "%s:\n", a->ac_name);
1332 for (i = 0; a->ac_vars[i]; i++)
1333 fprintf(fp, "\t%s\n", a->ac_vars[i]);
1335 if (mc)
1336 try_pager(fp);
1337 Fclose(fp);
1338 return 0;
1340 if (args[1] && args[1][0] == '{' && args[1][1] == '\0') {
1341 if (args[2] != NULL) {
1342 fprintf(stderr, "Syntax is: account <name> {\n");
1343 return 1;
1345 if ((a = get_oldaccount(args[0])) != NULL)
1346 a->ac_name = NULL;
1347 return define1(args[0], 1);
1349 strncpy(mboxname, expand("&"), sizeof mboxname)[sizeof mboxname-1]='\0';
1350 oqf = savequitflags();
1351 if ((a = get_oldaccount(args[0])) == NULL) {
1352 if (args[1]) {
1353 a = scalloc(1, sizeof *a);
1354 a->ac_next = oldaccounts;
1355 oldaccounts = a;
1356 } else {
1357 if ((i = callaccount(args[0])) != CBAD)
1358 goto setf;
1359 printf("Account %s does not exist.\n", args[0]);
1360 return 1;
1363 if (args[1]) {
1364 delaccount(args[0]);
1365 a->ac_name = sstrdup(args[0]);
1366 for (i = 1; args[i]; i++);
1367 a->ac_vars = scalloc(i, sizeof *a->ac_vars);
1368 for (i = 0; args[i+1]; i++)
1369 a->ac_vars[i] = sstrdup(args[i+1]);
1370 } else {
1371 unset_allow_undefined = 1;
1372 set(a->ac_vars);
1373 unset_allow_undefined = 0;
1374 setf: if (!starting) {
1375 nqf = savequitflags();
1376 restorequitflags(oqf);
1377 i = file1("%");
1378 restorequitflags(nqf);
1379 return i;
1382 return 0;
1385 int
1386 cflag(void *v)
1388 struct message *m;
1389 int *msgvec = v;
1390 int *ip;
1392 for (ip = msgvec; *ip != 0; ip++) {
1393 m = &message[*ip-1];
1394 setdot(m);
1395 if ((m->m_flag & (MFLAG|MFLAGGED)) == 0)
1396 m->m_flag |= MFLAG|MFLAGGED;
1398 return 0;
1401 int
1402 cunflag(void *v)
1404 struct message *m;
1405 int *msgvec = v;
1406 int *ip;
1408 for (ip = msgvec; *ip != 0; ip++) {
1409 m = &message[*ip-1];
1410 setdot(m);
1411 if (m->m_flag & (MFLAG|MFLAGGED)) {
1412 m->m_flag &= ~(MFLAG|MFLAGGED);
1413 m->m_flag |= MUNFLAG;
1416 return 0;
1419 int
1420 canswered(void *v)
1422 struct message *m;
1423 int *msgvec = v;
1424 int *ip;
1426 for (ip = msgvec; *ip != 0; ip++) {
1427 m = &message[*ip-1];
1428 setdot(m);
1429 if ((m->m_flag & (MANSWER|MANSWERED)) == 0)
1430 m->m_flag |= MANSWER|MANSWERED;
1432 return 0;
1435 int
1436 cunanswered(void *v)
1438 struct message *m;
1439 int *msgvec = v;
1440 int *ip;
1442 for (ip = msgvec; *ip != 0; ip++) {
1443 m = &message[*ip-1];
1444 setdot(m);
1445 if (m->m_flag & (MANSWER|MANSWERED)) {
1446 m->m_flag &= ~(MANSWER|MANSWERED);
1447 m->m_flag |= MUNANSWER;
1450 return 0;
1453 int
1454 cdraft(void *v)
1456 struct message *m;
1457 int *msgvec = v;
1458 int *ip;
1460 for (ip = msgvec; *ip != 0; ip++) {
1461 m = &message[*ip-1];
1462 setdot(m);
1463 if ((m->m_flag & (MDRAFT|MDRAFTED)) == 0)
1464 m->m_flag |= MDRAFT|MDRAFTED;
1466 return 0;
1469 int
1470 cundraft(void *v)
1472 struct message *m;
1473 int *msgvec = v;
1474 int *ip;
1476 for (ip = msgvec; *ip != 0; ip++) {
1477 m = &message[*ip-1];
1478 setdot(m);
1479 if (m->m_flag & (MDRAFT|MDRAFTED)) {
1480 m->m_flag &= ~(MDRAFT|MDRAFTED);
1481 m->m_flag |= MUNDRAFT;
1484 return 0;
1487 static float
1488 huge(void)
1490 #if defined (_CRAY)
1492 * This is not perfect, but correct for machines with a 32-bit
1493 * IEEE float and a 32-bit unsigned long, and does at least not
1494 * produce SIGFPE on the Cray Y-MP.
1496 union {
1497 float f;
1498 unsigned long l;
1499 } u;
1501 u.l = 0xff800000; /* -inf */
1502 return u.f;
1503 #elif defined (INFINITY)
1504 return -INFINITY;
1505 #elif defined (HUGE_VALF)
1506 return -HUGE_VALF;
1507 #elif defined (FLT_MAX)
1508 return -FLT_MAX;
1509 #else
1510 return -1e10;
1511 #endif
1514 int
1515 ckill(void *v)
1517 struct message *m;
1518 int *msgvec = v;
1519 int *ip;
1521 for (ip = msgvec; *ip != 0; ip++) {
1522 m = &message[*ip-1];
1523 m->m_flag |= MKILL;
1524 m->m_score = huge();
1526 return 0;
1529 int
1530 cunkill(void *v)
1532 struct message *m;
1533 int *msgvec = v;
1534 int *ip;
1536 for (ip = msgvec; *ip != 0; ip++) {
1537 m = &message[*ip-1];
1538 m->m_flag &= ~MKILL;
1539 m->m_score = 0;
1541 return 0;
1544 int
1545 cscore(void *v)
1547 char *str = v;
1548 char *sscore, *xp;
1549 int f, *msgvec, *ip;
1550 double nscore;
1551 struct message *m;
1553 msgvec = salloc((msgCount+2) * sizeof *msgvec);
1554 if ((sscore = laststring(str, &f, 0)) == NULL) {
1555 fprintf(stderr, "No score given.\n");
1556 return 1;
1558 nscore = strtod(sscore, &xp);
1559 if (*xp) {
1560 fprintf(stderr, "Invalid score: \"%s\"\n", sscore);
1561 return 1;
1563 if (nscore > FLT_MAX)
1564 nscore = FLT_MAX;
1565 else if (nscore < -FLT_MAX)
1566 nscore = -FLT_MAX;
1567 if (!f) {
1568 *msgvec = first(0, MMNORM);
1569 if (*msgvec == 0) {
1570 if (inhook)
1571 return 0;
1572 fprintf(stderr, "No messages to score.\n");
1573 return 1;
1575 msgvec[1] = 0;
1576 } else if (getmsglist(str, msgvec, 0) < 0)
1577 return 1;
1578 if (*msgvec == 0) {
1579 if (inhook)
1580 return 0;
1581 fprintf(stderr, "No applicable messages.\n");
1582 return 1;
1584 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
1585 m = &message[*ip-1];
1586 if (m->m_score != huge()) {
1587 m->m_score += nscore;
1588 if (m->m_score < 0)
1589 m->m_flag |= MKILL;
1590 else if (m->m_score > 0)
1591 m->m_flag &= ~MKILL;
1592 if (m->m_score >= 0)
1593 setdot(m);
1596 return 0;
1599 /*ARGSUSED*/
1600 int
1601 cnoop(void *v)
1603 (void)v;
1605 switch (mb.mb_type) {
1606 case MB_IMAP:
1607 imap_noop();
1608 break;
1609 case MB_POP3:
1610 pop3_noop();
1611 break;
1612 default:
1613 break;
1615 return 0;
1618 int
1619 cremove(void *v)
1621 char vb[LINESIZE];
1622 char **args = v;
1623 char *name;
1624 int ec = 0;
1626 if (*args == NULL) {
1627 fprintf(stderr, "Syntax is: remove mailbox ...\n");
1628 return 1;
1630 do {
1631 if ((name = expand(*args)) == NULL)
1632 continue;
1633 if (strcmp(name, mailname) == 0) {
1634 fprintf(stderr,
1635 "Cannot remove current mailbox \"%s\".\n",
1636 name);
1637 ec |= 1;
1638 continue;
1640 snprintf(vb, sizeof vb, "Remove \"%s\" (y/n) ? ", name);
1641 if (yorn(vb) == 0)
1642 continue;
1643 switch (which_protocol(name)) {
1644 case PROTO_FILE:
1645 if (unlink(name) < 0) { /* do not handle .gz .bz2 */
1646 perror(name);
1647 ec |= 1;
1649 break;
1650 case PROTO_POP3:
1651 fprintf(stderr, "Cannot remove POP3 mailbox \"%s\".\n",
1652 name);
1653 ec |= 1;
1654 break;
1655 case PROTO_IMAP:
1656 if (imap_remove(name) != OKAY)
1657 ec |= 1;
1658 break;
1659 case PROTO_MAILDIR:
1660 if (maildir_remove(name) != OKAY)
1661 ec |= 1;
1662 break;
1663 case PROTO_UNKNOWN:
1664 fprintf(stderr,
1665 "Unknown protocol in \"%s\". Not removed.\n",
1666 name);
1667 ec |= 1;
1669 } while (*++args);
1670 return ec;
1673 int
1674 crename(void *v)
1676 char **args = v, *old, *new;
1677 enum protocol oldp, newp;
1678 int ec = 0;
1680 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1681 fprintf(stderr, "Syntax: rename old new\n");
1682 return 1;
1684 old = expand(args[0]);
1685 oldp = which_protocol(old);
1686 new = expand(args[1]);
1687 newp = which_protocol(new);
1688 if (strcmp(old, mailname) == 0 || strcmp(new, mailname) == 0) {
1689 fprintf(stderr, "Cannot rename current mailbox \"%s\".\n", old);
1690 return 1;
1692 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1693 fprintf(stderr, "Can only rename folders of same type.\n");
1694 return 1;
1696 if (newp == PROTO_POP3)
1697 goto nopop3;
1698 switch (oldp) {
1699 case PROTO_FILE:
1700 if (link(old, new) < 0) {
1701 switch (errno) {
1702 case EACCES:
1703 case EEXIST:
1704 case ENAMETOOLONG:
1705 case ENOENT:
1706 case ENOSPC:
1707 case EXDEV:
1708 perror(new);
1709 break;
1710 default:
1711 perror(old);
1713 ec |= 1;
1714 } else if (unlink(old) < 0) {
1715 perror(old);
1716 ec |= 1;
1718 break;
1719 case PROTO_MAILDIR:
1720 if (rename(old, new) < 0) {
1721 perror(old);
1722 ec |= 1;
1724 break;
1725 case PROTO_POP3:
1726 nopop3: fprintf(stderr, "Cannot rename POP3 mailboxes.\n");
1727 ec |= 1;
1728 break;
1729 case PROTO_IMAP:
1730 if (imap_rename(old, new) != OKAY)
1731 ec |= 1;
1732 break;
1733 case PROTO_UNKNOWN:
1734 fprintf(stderr, "Unknown protocol in \"%s\" and \"%s\". "
1735 "Not renamed.\n", old, new);
1736 ec |= 1;
1738 return ec;