Support CONFIG-sets for make(1) invocation..
[s-mailx.git] / cmd3.c
blobc8e4ecd395c43cc52e2ff0d9fe89304f4ba83a22
1 /*
2 * Heirloom mailx - 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 * All rights reserved.
7 */
8 /*
9 * Copyright (c) 1980, 1993
10 * The Regents of the University of California. All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
14 * are met:
15 * 1. Redistributions of source code must retain the above copyright
16 * notice, this list of conditions and the following disclaimer.
17 * 2. Redistributions in binary form must reproduce the above copyright
18 * notice, this list of conditions and the following disclaimer in the
19 * documentation and/or other materials provided with the distribution.
20 * 3. All advertising materials mentioning features or use of this software
21 * must display the following acknowledgement:
22 * This product includes software developed by the University of
23 * California, Berkeley and its contributors.
24 * 4. Neither the name of the University nor the names of its contributors
25 * may be used to endorse or promote products derived from this software
26 * without specific prior written permission.
28 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
29 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
30 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
31 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
32 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
33 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
34 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
35 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
36 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
37 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
38 * SUCH DAMAGE.
41 #ifndef lint
42 #ifdef DOSCCS
43 static char sccsid[] = "@(#)cmd3.c 2.87 (gritter) 10/1/08";
44 #endif
45 #endif /* not lint */
47 #include <math.h>
48 #include <float.h>
49 #include "rcv.h"
50 #include "extern.h"
51 #include <unistd.h>
52 #include <errno.h>
55 * Mail -- a mail program
57 * Still more user commands.
60 static int bangexp(char **str, size_t *size);
61 static void make_ref_and_cs(struct message *mp, struct header *head);
62 static int (*respond_or_Respond(int c))(int *, int);
63 static int respond_internal(int *msgvec, int recipient_record);
64 static char *reedit(char *subj);
65 static char *fwdedit(char *subj);
66 static void onpipe(int signo);
67 static void asort(char **list);
68 static int diction(const void *a, const void *b);
69 static int file1(char *name);
70 static int shellecho(const char *cp);
71 static int Respond_internal(int *msgvec, int recipient_record);
72 static int resend1(void *v, int add_resent);
73 static void list_shortcuts(void);
74 static enum okay delete_shortcut(const char *str);
75 static float huge(void);
78 * Process a shell escape by saving signals, ignoring signals,
79 * and forking a sh -c
81 int
82 shell(void *v)
84 char *str = v;
85 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
86 char *shell;
87 char *cmd;
88 size_t cmdsize;
90 cmd = smalloc(cmdsize = strlen(str) + 1);
91 strcpy(cmd, str);
92 if (bangexp(&cmd, &cmdsize) < 0)
93 return 1;
94 if ((shell = value("SHELL")) == NULL)
95 shell = SHELL;
96 run_command(shell, 0, -1, -1, "-c", cmd, NULL);
97 safe_signal(SIGINT, sigint);
98 printf("!\n");
99 free(cmd);
100 return 0;
104 * Fork an interactive shell.
106 /*ARGSUSED*/
107 int
108 dosh(void *v)
110 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
111 char *shell;
113 if ((shell = value("SHELL")) == NULL)
114 shell = SHELL;
115 run_command(shell, 0, -1, -1, NULL, NULL, NULL);
116 safe_signal(SIGINT, sigint);
117 putchar('\n');
118 return 0;
122 * Expand the shell escape by expanding unescaped !'s into the
123 * last issued command where possible.
126 static char *lastbang;
127 static size_t lastbangsize;
129 static int
130 bangexp(char **str, size_t *size)
132 char *bangbuf;
133 int changed = 0;
134 int dobang = value("bang") != NULL;
135 size_t sz, i, j, bangbufsize;
137 bangbuf = smalloc(bangbufsize = *size);
138 i = j = 0;
139 while ((*str)[i]) {
140 if (dobang) {
141 if ((*str)[i] == '!') {
142 sz = strlen(lastbang);
143 bangbuf = srealloc(bangbuf, bangbufsize += sz);
144 changed++;
145 strcpy(&bangbuf[j], lastbang);
146 j += sz;
147 i++;
148 continue;
151 if ((*str)[i] == '\\' && (*str)[i + 1] == '!') {
152 bangbuf[j++] = '!';
153 i += 2;
154 changed++;
156 bangbuf[j++] = (*str)[i++];
158 bangbuf[j] = '\0';
159 if (changed) {
160 printf("!%s\n", bangbuf);
161 fflush(stdout);
163 sz = j;
164 if (sz >= *size)
165 *str = srealloc(*str, *size = sz + 1);
166 strcpy(*str, bangbuf);
167 if (sz >= lastbangsize)
168 lastbang = srealloc(lastbang, lastbangsize = sz + 1);
169 strcpy(lastbang, bangbuf);
170 free(bangbuf);
171 return(0);
174 /*ARGSUSED*/
175 int
176 help(void *v)
178 const char *helptext =
179 " %s commands\n\
180 type <message list> type messages\n\
181 next goto and type next message\n\
182 from <message list> give head lines of messages\n\
183 headers print out active message headers\n\
184 delete <message list> delete messages\n\
185 undelete <message list> undelete messages\n\
186 save <message list> folder append messages to folder and mark as saved\n\
187 copy <message list> folder append messages to folder without marking them\n\
188 write <message list> file append message texts to file, save attachments\n\
189 preserve <message list> keep incoming messages in mailbox even if saved\n\
190 Reply <message list> reply to message senders\n\
191 reply <message list> reply to message senders and all recipients\n\
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\
200 A <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";
203 fprintf(stdout, helptext, progname, progname);
204 return(0);
208 * Change user's working directory.
210 int
211 schdir(void *v)
213 char **arglist = v;
214 char *cp;
216 if (*arglist == NULL)
217 cp = homedir;
218 else
219 if ((cp = expand(*arglist)) == NULL)
220 return(1);
221 if (chdir(cp) < 0) {
222 perror(cp);
223 return(1);
225 return 0;
228 static void
229 make_ref_and_cs(struct message *mp, struct header *head)
231 char *oldref, *oldmsgid, *newref, *cp;
232 size_t reflen;
233 unsigned i;
234 struct name *n;
236 oldref = hfield("references", mp);
237 oldmsgid = hfield("message-id", mp);
238 if (oldmsgid == NULL || *oldmsgid == '\0') {
239 head->h_ref = NULL;
240 return;
242 reflen = 1;
243 if (oldref)
244 reflen += strlen(oldref) + 2;
245 if (oldmsgid)
246 reflen += strlen(oldmsgid);
247 newref = ac_alloc(reflen);
248 if (oldref) {
249 strcpy(newref, oldref);
250 if (oldmsgid) {
251 strcat(newref, ", ");
252 strcat(newref, oldmsgid);
254 } else if (oldmsgid)
255 strcpy(newref, oldmsgid);
256 n = extract(newref, GREF);
257 ac_free(newref);
259 * Limit the references to 21 entries.
261 while (n->n_flink != NULL)
262 n = n->n_flink;
263 for (i = 1; i < 21; i++) {
264 if (n->n_blink != NULL)
265 n = n->n_blink;
266 else
267 break;
269 n->n_blink = NULL;
270 head->h_ref = n;
271 if (value("reply-in-same-charset") != NULL &&
272 (cp = hfield("content-type", mp)) != NULL)
273 head->h_charset = mime_getparam("charset", cp);
276 static int
277 (*respond_or_Respond(int c))(int *, int)
279 int opt = 0;
281 opt += (value("Replyall") != NULL);
282 opt += (value("flipr") != NULL);
283 return ((opt == 1) ^ (c == 'R')) ? Respond_internal : respond_internal;
286 int
287 respond(void *v)
289 return (respond_or_Respond('r'))((int *)v, 0);
292 int
293 respondall(void *v)
295 return respond_internal((int *)v, 0);
298 int
299 respondsender(void *v)
301 return Respond_internal((int *)v, 0);
304 int
305 followup(void *v)
307 return (respond_or_Respond('r'))((int *)v, 1);
310 int
311 followupall(void *v)
313 return respond_internal((int *)v, 1);
316 int
317 followupsender(void *v)
319 return Respond_internal((int *)v, 1);
323 * Reply to a list of messages. Extract each name from the
324 * message header and send them off to mail1()
326 static int
327 respond_internal(int *msgvec, int recipient_record)
329 int Eflag;
330 struct message *mp;
331 char *cp, *rcv;
332 enum gfield gf = value("fullnames") ? GFULL : GSKIN;
333 struct name *np = NULL;
334 struct header head;
336 memset(&head, 0, sizeof head);
337 if (msgvec[1] != 0) {
338 printf(catgets(catd, CATSET, 37,
339 "Sorry, can't reply to multiple messages at once\n"));
340 return(1);
342 mp = &message[msgvec[0] - 1];
343 touch(mp);
344 setdot(mp);
345 if ((rcv = hfield("reply-to", mp)) == NULL)
346 if ((rcv = hfield("from", mp)) == NULL)
347 rcv = nameof(mp, 1);
348 if (rcv != NULL)
349 np = sextract(rcv, GTO|gf);
350 if (! value("recipients-in-cc") && (cp = hfield("to", mp)) != NULL)
351 np = cat(np, sextract(cp, GTO|gf));
352 np = elide(np);
354 * Delete my name from the reply list,
355 * and with it, all my alternate names.
357 np = delete_alternates(np);
358 if (np == NULL)
359 np = sextract(rcv, GTO|gf);
360 head.h_to = np;
361 if ((head.h_subject = hfield("subject", mp)) == NULL)
362 head.h_subject = hfield("subj", mp);
363 head.h_subject = reedit(head.h_subject);
364 /* Cc: */
365 np = NULL;
366 if (value("recipients-in-cc") && (cp = hfield("to", mp)) != NULL)
367 np = sextract(cp, GCC|gf);
368 if ((cp = hfield("cc", mp)) != NULL)
369 np = cat(np, sextract(cp, GCC|gf));
370 if (np != NULL)
371 head.h_cc = elide(delete_alternates(np));
372 make_ref_and_cs(mp, &head);
373 Eflag = value("skipemptybody") != NULL;
374 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
375 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
376 mp->m_flag |= MANSWER|MANSWERED;
377 return(0);
381 * Modify the subject we are replying to to begin with Re: if
382 * it does not already.
384 static char *
385 reedit(char *subj)
387 char *newsubj;
388 struct str in, out;
390 if (subj == NULL || *subj == '\0')
391 return NULL;
392 in.s = subj;
393 in.l = strlen(subj);
394 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
395 if ((out.s[0] == 'r' || out.s[0] == 'R') &&
396 (out.s[1] == 'e' || out.s[1] == 'E') &&
397 out.s[2] == ':')
398 return out.s;
399 newsubj = salloc(out.l + 5);
400 strcpy(newsubj, "Re: ");
401 strcpy(newsubj + 4, out.s);
402 return newsubj;
406 * Forward a message to a new recipient, in the sense of RFC 2822.
408 static int
409 forward1(char *str, int recipient_record)
411 int Eflag;
412 int *msgvec, f;
413 char *recipient;
414 struct message *mp;
415 struct header head;
416 int forward_as_attachment;
418 forward_as_attachment = value("forward-as-attachment") != NULL;
419 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
420 if ((recipient = laststring(str, &f, 0)) == NULL) {
421 puts(catgets(catd, CATSET, 47, "No recipient specified."));
422 return 1;
424 if (!f) {
425 *msgvec = first(0, MMNORM);
426 if (*msgvec == 0) {
427 if (inhook)
428 return 0;
429 printf("No messages to forward.\n");
430 return 1;
432 msgvec[1] = 0;
434 if (f && getmsglist(str, msgvec, 0) < 0)
435 return 1;
436 if (*msgvec == 0) {
437 if (inhook)
438 return 0;
439 printf("No applicable messages.\n");
440 return 1;
442 if (msgvec[1] != 0) {
443 printf("Cannot forward multiple messages at once\n");
444 return 1;
446 memset(&head, 0, sizeof head);
447 if ((head.h_to = sextract(recipient,
448 GTO | (value("fullnames") ? GFULL : GSKIN))) == NULL)
449 return 1;
450 mp = &message[*msgvec - 1];
451 if (forward_as_attachment) {
452 head.h_attach = csalloc(1, sizeof *head.h_attach);
453 head.h_attach->a_msgno = *msgvec;
454 } else {
455 touch(mp);
456 setdot(mp);
458 if ((head.h_subject = hfield("subject", mp)) == NULL)
459 head.h_subject = hfield("subj", mp);
460 head.h_subject = fwdedit(head.h_subject);
461 Eflag = value("skipemptybody") != NULL;
462 mail1(&head, 1, forward_as_attachment ? NULL : mp,
463 NULL, recipient_record, 1, 0, Eflag);
464 return 0;
468 * Modify the subject we are replying to to begin with Fwd:.
470 static char *
471 fwdedit(char *subj)
473 char *newsubj;
474 struct str in, out;
476 if (subj == NULL || *subj == '\0')
477 return NULL;
478 in.s = subj;
479 in.l = strlen(subj);
480 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
481 newsubj = salloc(strlen(out.s) + 6);
482 strcpy(newsubj, "Fwd: ");
483 strcpy(&newsubj[5], out.s);
484 free(out.s);
485 return newsubj;
489 * The 'forward' command.
492 forwardcmd(void *v)
494 return forward1(v, 0);
498 * Similar to forward, saving the message in a file named after the
499 * first recipient.
502 Forwardcmd(void *v)
504 return forward1(v, 1);
508 * Preserve the named messages, so that they will be sent
509 * back to the system mailbox.
511 int
512 preserve(void *v)
514 int *msgvec = v;
515 struct message *mp;
516 int *ip, mesg;
518 if (edit) {
519 printf(catgets(catd, CATSET, 39,
520 "Cannot \"preserve\" in edit mode\n"));
521 return(1);
523 for (ip = msgvec; *ip != 0; ip++) {
524 mesg = *ip;
525 mp = &message[mesg-1];
526 mp->m_flag |= MPRESERVE;
527 mp->m_flag &= ~MBOX;
528 setdot(mp);
530 * This is now Austin Group Request XCU #20.
532 did_print_dot = 1;
534 return(0);
538 * Mark all given messages as unread.
540 int
541 unread(void *v)
543 int *msgvec = v;
544 int *ip;
546 for (ip = msgvec; *ip != 0; ip++) {
547 setdot(&message[*ip-1]);
548 dot->m_flag &= ~(MREAD|MTOUCH);
549 dot->m_flag |= MSTATUS;
550 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
551 imap_unread(&message[*ip-1], *ip);
553 * The "unread" command is not part of POSIX mailx.
555 did_print_dot = 1;
557 return(0);
561 * Mark all given messages as read.
564 seen(void *v)
566 int *msgvec = v;
567 int *ip;
569 for (ip = msgvec; *ip; ip++) {
570 setdot(&message[*ip-1]);
571 touch(&message[*ip-1]);
573 return 0;
577 * Print the size of each message.
579 int
580 messize(void *v)
582 int *msgvec = v;
583 struct message *mp;
584 int *ip, mesg;
586 for (ip = msgvec; *ip != 0; ip++) {
587 mesg = *ip;
588 mp = &message[mesg-1];
589 printf("%d: ", mesg);
590 if (mp->m_xlines > 0)
591 printf("%ld", mp->m_xlines);
592 else
593 putchar(' ');
594 printf("/%lu\n", (unsigned long)mp->m_xsize);
596 return(0);
600 * Quit quickly. If we are sourcing, just pop the input level
601 * by returning an error.
603 /*ARGSUSED*/
604 int
605 rexit(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 siglongjmp(pipejmp, 1);
623 * Set or display a variable value. Syntax is similar to that
624 * of sh.
626 int
627 set(void *v)
629 char **arglist = v;
630 struct var *vp;
631 char *cp, *cp2;
632 char **ap, **p;
633 int errs, h, s;
634 FILE *obuf = stdout;
635 int bsdset = value("bsdcompat") != NULL || value("bsdset") != NULL;
637 (void)&cp;
638 (void)&ap;
639 (void)&obuf;
640 (void)&bsdset;
641 if (*arglist == NULL) {
642 for (h = 0, s = 1; h < HSHSIZE; h++)
643 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
644 s++;
645 /*LINTED*/
646 ap = (char **)salloc(s * sizeof *ap);
647 for (h = 0, p = ap; h < HSHSIZE; h++)
648 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
649 *p++ = vp->v_name;
650 *p = NULL;
651 asort(ap);
652 if (is_a_tty[0] && is_a_tty[1] && (cp = value("crt")) != NULL) {
653 if (s > (*cp == '\0' ? screensize() : atoi(cp)) + 3) {
654 cp = get_pager();
655 if (sigsetjmp(pipejmp, 1))
656 goto endpipe;
657 if ((obuf = Popen(cp, "w", NULL, 1)) == NULL) {
658 perror(cp);
659 obuf = stdout;
660 } else
661 safe_signal(SIGPIPE, onpipe);
664 for (p = ap; *p != NULL; p++) {
665 if (bsdset)
666 fprintf(obuf, "%s\t%s\n", *p, value(*p));
667 else {
668 if ((cp = value(*p)) != NULL && *cp)
669 fprintf(obuf, "%s=\"%s\"\n",
670 *p, value(*p));
671 else
672 fprintf(obuf, "%s\n", *p);
675 endpipe:
676 if (obuf != stdout) {
677 safe_signal(SIGPIPE, SIG_IGN);
678 Pclose(obuf);
679 safe_signal(SIGPIPE, dflpipe);
681 return(0);
683 errs = 0;
684 for (ap = arglist; *ap != NULL; ap++) {
685 char *varbuf;
687 varbuf = ac_alloc(strlen(*ap) + 1);
688 cp = *ap;
689 cp2 = varbuf;
690 while (*cp != '=' && *cp != '\0')
691 *cp2++ = *cp++;
692 *cp2 = '\0';
693 if (*cp == '\0')
694 cp = "";
695 else
696 cp++;
697 if (equal(varbuf, "")) {
698 printf(catgets(catd, CATSET, 41,
699 "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)
1042 switch (cond) {
1043 case CANY:
1044 printf(catgets(catd, CATSET, 44,
1045 "\"Else\" without matching \"if\"\n"));
1046 return(1);
1048 case CSEND:
1049 cond = CRCV;
1050 break;
1052 case CRCV:
1053 cond = CSEND;
1054 break;
1056 case CTERM:
1057 cond = CNONTERM;
1058 break;
1060 default:
1061 printf(catgets(catd, CATSET, 45,
1062 "Mail's idea of conditions is screwed up\n"));
1063 cond = CANY;
1064 break;
1066 return(0);
1070 * End of if statement. Just set cond back to anything.
1072 /*ARGSUSED*/
1073 int
1074 endifcmd(void *v)
1077 if (cond == CANY) {
1078 printf(catgets(catd, CATSET, 46,
1079 "\"Endif\" without matching \"if\"\n"));
1080 return(1);
1082 cond = CANY;
1083 return(0);
1087 * Set the list of alternate names.
1089 int
1090 alternates(void *v)
1092 char **namelist = v;
1093 int c;
1094 char **ap, **ap2, *cp;
1096 c = argcount(namelist) + 1;
1097 if (c == 1) {
1098 if (altnames == 0)
1099 return(0);
1100 for (ap = altnames; *ap; ap++)
1101 printf("%s ", *ap);
1102 printf("\n");
1103 return(0);
1105 if (altnames != 0)
1106 free(altnames);
1107 altnames = scalloc(c, sizeof (char *));
1108 for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
1109 cp = scalloc(strlen(*ap) + 1, sizeof (char));
1110 strcpy(cp, *ap);
1111 *ap2 = cp;
1113 *ap2 = 0;
1114 return(0);
1118 * Do the real work of resending.
1120 static int
1121 resend1(void *v, int add_resent)
1123 char *name, *str;
1124 struct name *to;
1125 struct name *sn;
1126 int f, *ip, *msgvec;
1128 str = (char *)v;
1129 /*LINTED*/
1130 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1131 name = laststring(str, &f, 1);
1132 if (name == NULL) {
1133 puts(catgets(catd, CATSET, 47, "No recipient specified."));
1134 return 1;
1136 if (!f) {
1137 *msgvec = first(0, MMNORM);
1138 if (*msgvec == 0) {
1139 if (inhook)
1140 return 0;
1141 puts(catgets(catd, CATSET, 48,
1142 "No applicable messages."));
1143 return 1;
1145 msgvec[1] = 0;
1146 } else if (getmsglist(str, msgvec, 0) < 0)
1147 return 1;
1148 if (*msgvec == 0) {
1149 if (inhook)
1150 return 0;
1151 printf("No applicable messages.\n");
1152 return 1;
1154 sn = nalloc(name, GTO);
1155 to = usermap(sn);
1156 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
1157 if (resend_msg(&message[*ip - 1], to, add_resent) != OKAY)
1158 return 1;
1160 return 0;
1164 * Resend a message list to a third person.
1166 int
1167 resendcmd(void *v)
1169 return resend1(v, 1);
1173 * Resend a message list to a third person without adding headers.
1175 int
1176 Resendcmd(void *v)
1178 return resend1(v, 0);
1182 * 'newmail' or 'inc' command: Check for new mail without writing old
1183 * mail back.
1185 /*ARGSUSED*/
1186 int
1187 newmail(void *v)
1189 int val = 1, mdot;
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 switch (mb.mb_type) {
1607 case MB_IMAP:
1608 imap_noop();
1609 break;
1610 case MB_POP3:
1611 pop3_noop();
1612 break;
1613 default:
1614 break;
1616 return 0;
1619 int
1620 cremove(void *v)
1622 char vb[LINESIZE];
1623 char **args = v;
1624 char *name;
1625 int ec = 0;
1627 if (*args == NULL) {
1628 fprintf(stderr, "Syntax is: remove mailbox ...\n");
1629 return 1;
1631 do {
1632 if ((name = expand(*args)) == NULL)
1633 continue;
1634 if (strcmp(name, mailname) == 0) {
1635 fprintf(stderr,
1636 "Cannot remove current mailbox \"%s\".\n",
1637 name);
1638 ec |= 1;
1639 continue;
1641 snprintf(vb, sizeof vb, "Remove \"%s\" (y/n) ? ", name);
1642 if (yorn(vb) == 0)
1643 continue;
1644 switch (which_protocol(name)) {
1645 case PROTO_FILE:
1646 if (unlink(name) < 0) { /* do not handle .gz .bz2 */
1647 perror(name);
1648 ec |= 1;
1650 break;
1651 case PROTO_POP3:
1652 fprintf(stderr, "Cannot remove POP3 mailbox \"%s\".\n",
1653 name);
1654 ec |= 1;
1655 break;
1656 case PROTO_IMAP:
1657 if (imap_remove(name) != OKAY)
1658 ec |= 1;
1659 break;
1660 case PROTO_MAILDIR:
1661 if (maildir_remove(name) != OKAY)
1662 ec |= 1;
1663 break;
1664 case PROTO_UNKNOWN:
1665 fprintf(stderr,
1666 "Unknown protocol in \"%s\". Not removed.\n",
1667 name);
1668 ec |= 1;
1670 } while (*++args);
1671 return ec;
1674 int
1675 crename(void *v)
1677 char **args = v, *old, *new;
1678 enum protocol oldp, newp;
1679 int ec = 0;
1681 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1682 fprintf(stderr, "Syntax: rename old new\n");
1683 return 1;
1685 old = expand(args[0]);
1686 oldp = which_protocol(old);
1687 new = expand(args[1]);
1688 newp = which_protocol(new);
1689 if (strcmp(old, mailname) == 0 || strcmp(new, mailname) == 0) {
1690 fprintf(stderr, "Cannot rename current mailbox \"%s\".\n", old);
1691 return 1;
1693 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1694 fprintf(stderr, "Can only rename folders of same type.\n");
1695 return 1;
1697 if (newp == PROTO_POP3)
1698 goto nopop3;
1699 switch (oldp) {
1700 case PROTO_FILE:
1701 if (link(old, new) < 0) {
1702 switch (errno) {
1703 case EACCES:
1704 case EEXIST:
1705 case ENAMETOOLONG:
1706 case ENOENT:
1707 case ENOSPC:
1708 case EXDEV:
1709 perror(new);
1710 break;
1711 default:
1712 perror(old);
1714 ec |= 1;
1715 } else if (unlink(old) < 0) {
1716 perror(old);
1717 ec |= 1;
1719 break;
1720 case PROTO_MAILDIR:
1721 if (rename(old, new) < 0) {
1722 perror(old);
1723 ec |= 1;
1725 break;
1726 case PROTO_POP3:
1727 nopop3: fprintf(stderr, "Cannot rename POP3 mailboxes.\n");
1728 ec |= 1;
1729 break;
1730 case PROTO_IMAP:
1731 if (imap_rename(old, new) != OKAY)
1732 ec |= 1;
1733 break;
1734 case PROTO_UNKNOWN:
1735 fprintf(stderr, "Unknown protocol in \"%s\" and \"%s\". "
1736 "Not renamed.\n", old, new);
1737 ec |= 1;
1739 return ec;