fixhead(): use ndup()
[s-mailx.git] / cmd3.c
blob1e005b91e12bf6ba1a2602ef473a79a5f64b8f41
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 */
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 #ifndef lint
41 #ifdef DOSCCS
42 static char sccsid[] = "@(#)cmd3.c 2.87 (gritter) 10/1/08";
43 #endif
44 #endif /* not lint */
46 #include <math.h>
47 #include <float.h>
48 #include "rcv.h"
49 #include "extern.h"
50 #include <unistd.h>
51 #include <errno.h>
54 * Mail -- a mail program
56 * Still more user commands.
59 static int bangexp(char **str, size_t *size);
60 static void make_ref_and_cs(struct message *mp, struct header *head);
61 static int (*respond_or_Respond(int c))(int *, int);
62 static int respond_internal(int *msgvec, int recipient_record);
63 static char *reedit(char *subj);
64 static char *fwdedit(char *subj);
65 static void onpipe(int signo);
66 static void asort(char **list);
67 static int diction(const void *a, const void *b);
68 static int file1(char *name);
69 static int shellecho(const char *cp);
70 static int Respond_internal(int *msgvec, int recipient_record);
71 static int resend1(void *v, int add_resent);
72 static void list_shortcuts(void);
73 static enum okay delete_shortcut(const char *str);
74 static float huge(void);
77 * Process a shell escape by saving signals, ignoring signals,
78 * and forking a sh -c
80 int
81 shell(void *v)
83 char *str = v;
84 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
85 char *shell;
86 char *cmd;
87 size_t cmdsize;
89 cmd = smalloc(cmdsize = strlen(str) + 1);
90 strcpy(cmd, str);
91 if (bangexp(&cmd, &cmdsize) < 0)
92 return 1;
93 if ((shell = value("SHELL")) == NULL)
94 shell = SHELL;
95 run_command(shell, 0, -1, -1, "-c", cmd, NULL);
96 safe_signal(SIGINT, sigint);
97 printf("!\n");
98 free(cmd);
99 return 0;
103 * Fork an interactive shell.
105 /*ARGSUSED*/
106 int
107 dosh(void *v)
109 sighandler_type sigint = safe_signal(SIGINT, SIG_IGN);
110 char *shell;
111 (void)v;
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 (void)v;
179 /* Very ugly, but take care for compiler supported string lengths :( */
180 fprintf(stdout,
181 "%s commands:\n",
182 progname);
183 puts(
184 "type <message list> type messages\n"
185 "next goto and type next message\n"
186 "from <message list> give head lines of messages\n"
187 "headers print out active message headers\n"
188 "delete <message list> delete messages\n"
189 "undelete <message list> undelete messages\n");
190 puts(
191 "save <message list> folder append messages to folder and mark as saved\n"
192 "copy <message list> folder append messages to folder without marking them\n"
193 "write <message list> file append message texts to file, save attachments\n"
194 "preserve <message list> keep incoming messages in mailbox even if saved\n"
195 "Reply <message list> reply to message senders\n"
196 "reply <message list> reply to message senders and all recipients\n");
197 puts(
198 "mail addresses mail to specific recipients\n"
199 "file folder change to another folder\n"
200 "quit quit and apply changes to folder\n"
201 "xit quit and discard changes made to folder\n"
202 "! shell escape\n"
203 "cd <directory> chdir to directory or home if none given\n"
204 "list list names of all available commands\n");
205 fprintf(stdout,
206 "\nA <message list> consists of integers, ranges of same, or other criteria\n"
207 "separated by spaces. If omitted, %s uses the last message typed.\n",
208 progname);
209 return(0);
213 * Change user's working directory.
215 int
216 schdir(void *v)
218 char **arglist = v;
219 char *cp;
221 if (*arglist == NULL)
222 cp = homedir;
223 else
224 if ((cp = expand(*arglist)) == NULL)
225 return(1);
226 if (chdir(cp) < 0) {
227 perror(cp);
228 return(1);
230 return 0;
233 static void
234 make_ref_and_cs(struct message *mp, struct header *head)
236 char *oldref, *oldmsgid, *newref, *cp;
237 size_t reflen;
238 unsigned i;
239 struct name *n;
241 oldref = hfield("references", mp);
242 oldmsgid = hfield("message-id", mp);
243 if (oldmsgid == NULL || *oldmsgid == '\0') {
244 head->h_ref = NULL;
245 return;
247 reflen = 1;
248 if (oldref)
249 reflen += strlen(oldref) + 2;
250 if (oldmsgid)
251 reflen += strlen(oldmsgid);
252 newref = ac_alloc(reflen);
253 if (oldref) {
254 strcpy(newref, oldref);
255 if (oldmsgid) {
256 strcat(newref, ", ");
257 strcat(newref, oldmsgid);
259 } else if (oldmsgid)
260 strcpy(newref, oldmsgid);
261 n = extract(newref, GREF);
262 ac_free(newref);
264 * Limit the references to 21 entries.
266 while (n->n_flink != NULL)
267 n = n->n_flink;
268 for (i = 1; i < 21; i++) {
269 if (n->n_blink != NULL)
270 n = n->n_blink;
271 else
272 break;
274 n->n_blink = NULL;
275 head->h_ref = n;
276 if (value("reply-in-same-charset") != NULL &&
277 (cp = hfield("content-type", mp)) != NULL)
278 head->h_charset = mime_getparam("charset", cp);
281 static int
282 (*respond_or_Respond(int c))(int *, int)
284 int opt = 0;
286 opt += (value("Replyall") != NULL);
287 opt += (value("flipr") != NULL);
288 return ((opt == 1) ^ (c == 'R')) ? Respond_internal : respond_internal;
291 int
292 respond(void *v)
294 return (respond_or_Respond('r'))((int *)v, 0);
297 int
298 respondall(void *v)
300 return respond_internal((int *)v, 0);
303 int
304 respondsender(void *v)
306 return Respond_internal((int *)v, 0);
309 int
310 followup(void *v)
312 return (respond_or_Respond('r'))((int *)v, 1);
315 int
316 followupall(void *v)
318 return respond_internal((int *)v, 1);
321 int
322 followupsender(void *v)
324 return Respond_internal((int *)v, 1);
328 * Reply to a list of messages. Extract each name from the
329 * message header and send them off to mail1()
331 static int
332 respond_internal(int *msgvec, int recipient_record)
334 int Eflag;
335 struct message *mp;
336 char *cp, *rcv;
337 enum gfield gf = value("fullnames") ? GFULL : GSKIN;
338 struct name *np = NULL;
339 struct header head;
341 memset(&head, 0, sizeof head);
342 if (msgvec[1] != 0) {
343 printf(catgets(catd, CATSET, 37,
344 "Sorry, can't reply to multiple messages at once\n"));
345 return(1);
347 mp = &message[msgvec[0] - 1];
348 touch(mp);
349 setdot(mp);
350 if ((rcv = hfield("reply-to", mp)) == NULL)
351 if ((rcv = hfield("from", mp)) == NULL)
352 rcv = nameof(mp, 1);
353 if (rcv != NULL)
354 np = sextract(rcv, GTO|gf);
355 if (! value("recipients-in-cc") && (cp = hfield("to", mp)) != NULL)
356 np = cat(np, sextract(cp, GTO|gf));
357 np = elide(np);
359 * Delete my name from the reply list,
360 * and with it, all my alternate names.
362 np = delete_alternates(np);
363 if (np == NULL)
364 np = sextract(rcv, GTO|gf);
365 head.h_to = np;
366 if ((head.h_subject = hfield("subject", mp)) == NULL)
367 head.h_subject = hfield("subj", mp);
368 head.h_subject = reedit(head.h_subject);
369 /* Cc: */
370 np = NULL;
371 if (value("recipients-in-cc") && (cp = hfield("to", mp)) != NULL)
372 np = sextract(cp, GCC|gf);
373 if ((cp = hfield("cc", mp)) != NULL)
374 np = cat(np, sextract(cp, GCC|gf));
375 if (np != NULL)
376 head.h_cc = elide(delete_alternates(np));
377 make_ref_and_cs(mp, &head);
378 Eflag = value("skipemptybody") != NULL;
379 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
380 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
381 mp->m_flag |= MANSWER|MANSWERED;
382 return(0);
386 * Modify the subject we are replying to to begin with Re: if
387 * it does not already.
389 static char *
390 reedit(char *subj)
392 char *newsubj;
393 struct str in, out;
395 if (subj == NULL || *subj == '\0')
396 return NULL;
397 in.s = subj;
398 in.l = strlen(subj);
399 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
400 if ((out.s[0] == 'r' || out.s[0] == 'R') &&
401 (out.s[1] == 'e' || out.s[1] == 'E') &&
402 out.s[2] == ':')
403 return out.s;
404 newsubj = salloc(out.l + 5);
405 strcpy(newsubj, "Re: ");
406 strcpy(newsubj + 4, out.s);
407 return newsubj;
411 * Forward a message to a new recipient, in the sense of RFC 2822.
413 static int
414 forward1(char *str, int recipient_record)
416 int Eflag;
417 int *msgvec, f;
418 char *recipient;
419 struct message *mp;
420 struct header head;
421 int forward_as_attachment;
423 forward_as_attachment = value("forward-as-attachment") != NULL;
424 msgvec = salloc((msgCount + 2) * sizeof *msgvec);
425 if ((recipient = laststring(str, &f, 0)) == NULL) {
426 puts(catgets(catd, CATSET, 47, "No recipient specified."));
427 return 1;
429 if (!f) {
430 *msgvec = first(0, MMNORM);
431 if (*msgvec == 0) {
432 if (inhook)
433 return 0;
434 printf("No messages to forward.\n");
435 return 1;
437 msgvec[1] = 0;
439 if (f && getmsglist(str, msgvec, 0) < 0)
440 return 1;
441 if (*msgvec == 0) {
442 if (inhook)
443 return 0;
444 printf("No applicable messages.\n");
445 return 1;
447 if (msgvec[1] != 0) {
448 printf("Cannot forward multiple messages at once\n");
449 return 1;
451 memset(&head, 0, sizeof head);
452 if ((head.h_to = sextract(recipient,
453 GTO | (value("fullnames") ? GFULL : GSKIN))) == NULL)
454 return 1;
455 mp = &message[*msgvec - 1];
456 if (forward_as_attachment) {
457 head.h_attach = csalloc(1, sizeof *head.h_attach);
458 head.h_attach->a_msgno = *msgvec;
459 } else {
460 touch(mp);
461 setdot(mp);
463 if ((head.h_subject = hfield("subject", mp)) == NULL)
464 head.h_subject = hfield("subj", mp);
465 head.h_subject = fwdedit(head.h_subject);
466 Eflag = value("skipemptybody") != NULL;
467 mail1(&head, 1, forward_as_attachment ? NULL : mp,
468 NULL, recipient_record, 1, 0, Eflag);
469 return 0;
473 * Modify the subject we are replying to to begin with Fwd:.
475 static char *
476 fwdedit(char *subj)
478 char *newsubj;
479 struct str in, out;
481 if (subj == NULL || *subj == '\0')
482 return NULL;
483 in.s = subj;
484 in.l = strlen(subj);
485 mime_fromhdr(&in, &out, TD_ISPR|TD_ICONV);
486 newsubj = salloc(strlen(out.s) + 6);
487 strcpy(newsubj, "Fwd: ");
488 strcpy(&newsubj[5], out.s);
489 free(out.s);
490 return newsubj;
494 * The 'forward' command.
497 forwardcmd(void *v)
499 return forward1(v, 0);
503 * Similar to forward, saving the message in a file named after the
504 * first recipient.
507 Forwardcmd(void *v)
509 return forward1(v, 1);
513 * Preserve the named messages, so that they will be sent
514 * back to the system mailbox.
516 int
517 preserve(void *v)
519 int *msgvec = v;
520 struct message *mp;
521 int *ip, mesg;
523 if (edit) {
524 printf(catgets(catd, CATSET, 39,
525 "Cannot \"preserve\" in edit mode\n"));
526 return(1);
528 for (ip = msgvec; *ip != 0; ip++) {
529 mesg = *ip;
530 mp = &message[mesg-1];
531 mp->m_flag |= MPRESERVE;
532 mp->m_flag &= ~MBOX;
533 setdot(mp);
535 * This is now Austin Group Request XCU #20.
537 did_print_dot = 1;
539 return(0);
543 * Mark all given messages as unread.
545 int
546 unread(void *v)
548 int *msgvec = v;
549 int *ip;
551 for (ip = msgvec; *ip != 0; ip++) {
552 setdot(&message[*ip-1]);
553 dot->m_flag &= ~(MREAD|MTOUCH);
554 dot->m_flag |= MSTATUS;
555 if (mb.mb_type == MB_IMAP || mb.mb_type == MB_CACHE)
556 imap_unread(&message[*ip-1], *ip);
558 * The "unread" command is not part of POSIX mailx.
560 did_print_dot = 1;
562 return(0);
566 * Mark all given messages as read.
569 seen(void *v)
571 int *msgvec = v;
572 int *ip;
574 for (ip = msgvec; *ip; ip++) {
575 setdot(&message[*ip-1]);
576 touch(&message[*ip-1]);
578 return 0;
582 * Print the size of each message.
584 int
585 messize(void *v)
587 int *msgvec = v;
588 struct message *mp;
589 int *ip, mesg;
591 for (ip = msgvec; *ip != 0; ip++) {
592 mesg = *ip;
593 mp = &message[mesg-1];
594 printf("%d: ", mesg);
595 if (mp->m_xlines > 0)
596 printf("%ld", mp->m_xlines);
597 else
598 putchar(' ');
599 printf("/%lu\n", (unsigned long)mp->m_xsize);
601 return(0);
605 * Quit quickly. If we are sourcing, just pop the input level
606 * by returning an error.
608 /*ARGSUSED*/
609 int
610 rexit(void *v)
612 (void)v;
613 if (sourcing)
614 return(1);
615 exit(0);
616 /*NOTREACHED*/
619 static sigjmp_buf pipejmp;
621 /*ARGSUSED*/
622 static void
623 onpipe(int signo)
625 (void)signo;
626 siglongjmp(pipejmp, 1);
630 * Set or display a variable value. Syntax is similar to that
631 * of sh.
633 int
634 set(void *v)
636 char **arglist = v;
637 struct var *vp;
638 char *cp, *cp2;
639 char **ap, **p;
640 int errs, h, s;
641 FILE *obuf = stdout;
642 int bsdset = value("bsdcompat") != NULL || value("bsdset") != NULL;
644 if (*arglist == NULL) {
645 for (h = 0, s = 1; h < HSHSIZE; h++)
646 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
647 s++;
648 /*LINTED*/
649 ap = (char **)salloc(s * sizeof *ap);
650 for (h = 0, p = ap; h < HSHSIZE; h++)
651 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
652 *p++ = vp->v_name;
653 *p = NULL;
654 asort(ap);
655 if (is_a_tty[0] && is_a_tty[1] && (cp = value("crt")) != NULL) {
656 if (s > (*cp == '\0' ? screensize() : atoi(cp)) + 3) {
657 cp = get_pager();
658 /* TODO should be below the Popen?
659 * TODO Problem: Popen doesn't encapsulate it,
660 * TODO may leave child run if fdopen() fails!
661 * TODO even more such stuff in this file! */
662 if (sigsetjmp(pipejmp, 1))
663 goto endpipe;
664 if ((obuf = Popen(cp, "w", NULL, 1)) == NULL) {
665 perror(cp);
666 obuf = stdout;
667 } else
668 safe_signal(SIGPIPE, onpipe);
671 for (p = ap; *p != NULL; p++) {
672 if (bsdset)
673 fprintf(obuf, "%s\t%s\n", *p, value(*p));
674 else {
675 if ((cp = value(*p)) != NULL && *cp)
676 fprintf(obuf, "%s=\"%s\"\n",
677 *p, value(*p));
678 else
679 fprintf(obuf, "%s\n", *p);
682 endpipe:
683 if (obuf != stdout) {
684 safe_signal(SIGPIPE, SIG_IGN);
685 Pclose(obuf);
686 safe_signal(SIGPIPE, dflpipe);
688 return(0);
690 errs = 0;
691 for (ap = arglist; *ap != NULL; ap++) {
692 char *varbuf;
694 varbuf = ac_alloc(strlen(*ap) + 1);
695 cp = *ap;
696 cp2 = varbuf;
697 while (*cp != '=' && *cp != '\0')
698 *cp2++ = *cp++;
699 *cp2 = '\0';
700 if (*cp == '\0')
701 cp = "";
702 else
703 cp++;
704 if (equal(varbuf, "")) {
705 printf(catgets(catd, CATSET, 41,
706 "Non-null variable name required\n"));
707 errs++;
708 ac_free(varbuf);
709 continue;
711 if (varbuf[0] == 'n' && varbuf[1] == 'o')
712 errs += unset_internal(&varbuf[2]);
713 else
714 assign(varbuf, cp);
715 ac_free(varbuf);
717 return(errs);
721 * Unset a bunch of variable values.
723 int
724 unset(void *v)
726 int errs;
727 char **ap;
729 errs = 0;
730 for (ap = (char **)v; *ap != NULL; ap++)
731 errs += unset_internal(*ap);
732 return(errs);
736 * Put add users to a group.
738 int
739 group(void *v)
741 char **argv = v;
742 struct grouphead *gh;
743 struct group *gp;
744 int h;
745 int s;
746 char **ap, *gname, **p;
748 if (*argv == NULL) {
749 for (h = 0, s = 1; h < HSHSIZE; h++)
750 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
751 s++;
752 /*LINTED*/
753 ap = (char **)salloc(s * sizeof *ap);
754 for (h = 0, p = ap; h < HSHSIZE; h++)
755 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
756 *p++ = gh->g_name;
757 *p = NULL;
758 asort(ap);
759 for (p = ap; *p != NULL; p++)
760 printgroup(*p);
761 return(0);
763 if (argv[1] == NULL) {
764 printgroup(*argv);
765 return(0);
767 gname = *argv;
768 h = hash(gname);
769 if ((gh = findgroup(gname)) == NULL) {
770 gh = (struct grouphead *)scalloc(1, sizeof *gh);
771 gh->g_name = vcopy(gname);
772 gh->g_list = NULL;
773 gh->g_link = groups[h];
774 groups[h] = gh;
778 * Insert names from the command list into the group.
779 * Who cares if there are duplicates? They get tossed
780 * later anyway.
783 for (ap = argv+1; *ap != NULL; ap++) {
784 gp = (struct group *)scalloc(1, sizeof *gp);
785 gp->ge_name = vcopy(*ap);
786 gp->ge_link = gh->g_list;
787 gh->g_list = gp;
789 return(0);
793 * Delete the passed groups.
795 int
796 ungroup(void *v)
798 char **argv = v;
800 if (*argv == NULL) {
801 printf(catgets(catd, CATSET, 209,
802 "Must specify alias or group to remove\n"));
803 return 1;
806 remove_group(*argv);
807 while (*++argv != NULL);
808 return 0;
812 * Sort the passed string vecotor into ascending dictionary
813 * order.
815 static void
816 asort(char **list)
818 char **ap;
820 for (ap = list; *ap != NULL; ap++)
822 if (ap-list < 2)
823 return;
824 qsort(list, ap-list, sizeof(*list), diction);
828 * Do a dictionary order comparison of the arguments from
829 * qsort.
831 static int
832 diction(const void *a, const void *b)
834 return(strcmp(*(char **)a, *(char **)b));
838 * Change to another file. With no argument, print information about
839 * the current file.
841 int
842 cfile(void *v)
844 char **argv = v;
846 if (argv[0] == NULL) {
847 newfileinfo();
848 return 0;
850 strncpy(mboxname, expand("&"), sizeof mboxname)[sizeof mboxname-1]='\0';
851 return file1(*argv);
854 static int
855 file1(char *name)
857 int i;
859 if (inhook) {
860 fprintf(stderr, "Cannot change folder from within a hook.\n");
861 return 1;
863 i = setfile(name, 0);
864 if (i < 0)
865 return 1;
866 callhook(mailname, 0);
867 if (i > 0 && value("emptystart") == NULL)
868 return 1;
869 announce(value("bsdcompat") != NULL || value("bsdannounce") != NULL);
870 return 0;
873 static int
874 shellecho(const char *cp)
876 int cflag = 0, n;
877 char c;
879 while (*cp) {
880 if (*cp == '\\') {
881 switch (*++cp) {
882 case '\0':
883 return cflag;
884 case 'a':
885 putchar('\a');
886 break;
887 case 'b':
888 putchar('\b');
889 break;
890 case 'c':
891 cflag = 1;
892 break;
893 case 'f':
894 putchar('\f');
895 break;
896 case 'n':
897 putchar('\n');
898 break;
899 case 'r':
900 putchar('\r');
901 break;
902 case 't':
903 putchar('\t');
904 break;
905 case 'v':
906 putchar('\v');
907 break;
908 default:
909 putchar(*cp&0377);
910 break;
911 case '0':
912 c = 0;
913 n = 3;
914 while (n-- && octalchar(cp[1]&0377)) {
915 c <<= 3;
916 c |= cp[1] - '0';
917 cp++;
919 putchar(c);
921 } else
922 putchar(*cp & 0377);
923 cp++;
925 return cflag;
929 * Expand file names like echo
931 int
932 echo(void *v)
934 char **argv = v;
935 char **ap;
936 char *cp;
937 int cflag = 0;
939 for (ap = argv; *ap != NULL; ap++) {
940 cp = *ap;
941 if ((cp = expand(cp)) != NULL) {
942 if (ap != argv)
943 putchar(' ');
944 cflag |= shellecho(cp);
947 if (!cflag)
948 putchar('\n');
949 return 0;
952 int
953 Respond(void *v)
955 return (respond_or_Respond('R'))((int *)v, 0);
958 int
959 Followup(void *v)
961 return (respond_or_Respond('R'))((int *)v, 1);
965 * Reply to a series of messages by simply mailing to the senders
966 * and not messing around with the To: and Cc: lists as in normal
967 * reply.
969 static int
970 Respond_internal(int *msgvec, int recipient_record)
972 int Eflag;
973 struct header head;
974 struct message *mp;
975 enum gfield gf = value("fullnames") ? GFULL : GSKIN;
976 int *ap;
977 char *cp;
979 memset(&head, 0, sizeof head);
980 for (ap = msgvec; *ap != 0; ap++) {
981 mp = &message[*ap - 1];
982 touch(mp);
983 setdot(mp);
984 if ((cp = hfield("reply-to", mp)) == NULL)
985 if ((cp = hfield("from", mp)) == NULL)
986 cp = nameof(mp, 2);
987 head.h_to = cat(head.h_to, sextract(cp, GTO|gf));
989 if (head.h_to == NULL)
990 return 0;
991 mp = &message[msgvec[0] - 1];
992 if ((head.h_subject = hfield("subject", mp)) == NULL)
993 head.h_subject = hfield("subj", mp);
994 head.h_subject = reedit(head.h_subject);
995 make_ref_and_cs(mp, &head);
996 Eflag = value("skipemptybody") != NULL;
997 if (mail1(&head, 1, mp, NULL, recipient_record, 0, 0, Eflag) == OKAY &&
998 value("markanswered") && (mp->m_flag & MANSWERED) == 0)
999 mp->m_flag |= MANSWER|MANSWERED;
1000 return 0;
1004 * Conditional commands. These allow one to parameterize one's
1005 * .mailrc and do some things if sending, others if receiving.
1007 int
1008 ifcmd(void *v)
1010 char **argv = v;
1011 char *cp;
1013 if (cond != CANY) {
1014 printf(catgets(catd, CATSET, 42, "Illegal nested \"if\"\n"));
1015 return(1);
1017 cond = CANY;
1018 cp = argv[0];
1019 switch (*cp) {
1020 case 'r': case 'R':
1021 cond = CRCV;
1022 break;
1024 case 's': case 'S':
1025 cond = CSEND;
1026 break;
1028 case 't': case 'T':
1029 cond = CTERM;
1030 break;
1032 default:
1033 printf(catgets(catd, CATSET, 43,
1034 "Unrecognized if-keyword: \"%s\"\n"), cp);
1035 return(1);
1037 return(0);
1041 * Implement 'else'. This is pretty simple -- we just
1042 * flip over the conditional flag.
1044 /*ARGSUSED*/
1045 int
1046 elsecmd(void *v)
1048 (void)v;
1050 switch (cond) {
1051 case CANY:
1052 printf(catgets(catd, CATSET, 44,
1053 "\"Else\" without matching \"if\"\n"));
1054 return(1);
1056 case CSEND:
1057 cond = CRCV;
1058 break;
1060 case CRCV:
1061 cond = CSEND;
1062 break;
1064 case CTERM:
1065 cond = CNONTERM;
1066 break;
1068 default:
1069 printf(catgets(catd, CATSET, 45,
1070 "Mail's idea of conditions is screwed up\n"));
1071 cond = CANY;
1072 break;
1074 return(0);
1078 * End of if statement. Just set cond back to anything.
1080 /*ARGSUSED*/
1081 int
1082 endifcmd(void *v)
1084 (void)v;
1086 if (cond == CANY) {
1087 printf(catgets(catd, CATSET, 46,
1088 "\"Endif\" without matching \"if\"\n"));
1089 return(1);
1091 cond = CANY;
1092 return(0);
1096 * Set the list of alternate names.
1098 int
1099 alternates(void *v)
1101 char **namelist = v;
1102 int c;
1103 char **ap, **ap2, *cp;
1105 c = argcount(namelist) + 1;
1106 if (c == 1) {
1107 if (altnames == 0)
1108 return(0);
1109 for (ap = altnames; *ap; ap++)
1110 printf("%s ", *ap);
1111 printf("\n");
1112 return(0);
1114 if (altnames != 0)
1115 free(altnames);
1116 altnames = scalloc(c, sizeof (char *));
1117 for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
1118 cp = scalloc(strlen(*ap) + 1, sizeof (char));
1119 strcpy(cp, *ap);
1120 *ap2 = cp;
1122 *ap2 = 0;
1123 return(0);
1127 * Do the real work of resending.
1129 static int
1130 resend1(void *v, int add_resent)
1132 char *name, *str;
1133 struct name *to;
1134 struct name *sn;
1135 int f, *ip, *msgvec;
1137 str = (char *)v;
1138 /*LINTED*/
1139 msgvec = (int *)salloc((msgCount + 2) * sizeof *msgvec);
1140 name = laststring(str, &f, 1);
1141 if (name == NULL) {
1142 puts(catgets(catd, CATSET, 47, "No recipient specified."));
1143 return 1;
1145 if (!f) {
1146 *msgvec = first(0, MMNORM);
1147 if (*msgvec == 0) {
1148 if (inhook)
1149 return 0;
1150 puts(catgets(catd, CATSET, 48,
1151 "No applicable messages."));
1152 return 1;
1154 msgvec[1] = 0;
1155 } else if (getmsglist(str, msgvec, 0) < 0)
1156 return 1;
1157 if (*msgvec == 0) {
1158 if (inhook)
1159 return 0;
1160 printf("No applicable messages.\n");
1161 return 1;
1163 sn = nalloc(name, GTO);
1164 to = usermap(sn);
1165 for (ip = msgvec; *ip && ip - msgvec < msgCount; ip++) {
1166 if (resend_msg(&message[*ip - 1], to, add_resent) != OKAY)
1167 return 1;
1169 return 0;
1173 * Resend a message list to a third person.
1175 int
1176 resendcmd(void *v)
1178 return resend1(v, 1);
1182 * Resend a message list to a third person without adding headers.
1184 int
1185 Resendcmd(void *v)
1187 return resend1(v, 0);
1191 * 'newmail' or 'inc' command: Check for new mail without writing old
1192 * mail back.
1194 /*ARGSUSED*/
1195 int
1196 newmail(void *v)
1198 int val = 1, mdot;
1199 (void)v;
1201 if ((mb.mb_type != MB_IMAP || imap_newmail(1)) &&
1202 (val = setfile(mailname, 1)) == 0) {
1203 mdot = getmdot(1);
1204 setdot(&message[mdot - 1]);
1206 return val;
1209 static void
1210 list_shortcuts(void)
1212 struct shortcut *s;
1214 for (s = shortcuts; s; s = s->sh_next)
1215 printf("%s=%s\n", s->sh_short, s->sh_long);
1218 int
1219 shortcut(void *v)
1221 char **args = (char **)v;
1222 struct shortcut *s;
1224 if (args[0] == NULL) {
1225 list_shortcuts();
1226 return 0;
1228 if (args[1] == NULL) {
1229 fprintf(stderr, catgets(catd, CATSET, 220,
1230 "expansion name for shortcut missing\n"));
1231 return 1;
1233 if (args[2] != NULL) {
1234 fprintf(stderr, catgets(catd, CATSET, 221,
1235 "too many arguments\n"));
1236 return 1;
1238 if ((s = get_shortcut(args[0])) != NULL) {
1239 free(s->sh_long);
1240 s->sh_long = sstrdup(args[1]);
1241 } else {
1242 s = scalloc(1, sizeof *s);
1243 s->sh_short = sstrdup(args[0]);
1244 s->sh_long = sstrdup(args[1]);
1245 s->sh_next = shortcuts;
1246 shortcuts = s;
1248 return 0;
1251 struct shortcut *
1252 get_shortcut(const char *str)
1254 struct shortcut *s;
1256 for (s = shortcuts; s; s = s->sh_next)
1257 if (strcmp(str, s->sh_short) == 0)
1258 break;
1259 return s;
1262 static enum okay
1263 delete_shortcut(const char *str)
1265 struct shortcut *sp, *sq;
1267 for (sp = shortcuts, sq = NULL; sp; sq = sp, sp = sp->sh_next) {
1268 if (strcmp(sp->sh_short, str) == 0) {
1269 free(sp->sh_short);
1270 free(sp->sh_long);
1271 if (sq)
1272 sq->sh_next = sp->sh_next;
1273 if (sp == shortcuts)
1274 shortcuts = sp->sh_next;
1275 free(sp);
1276 return OKAY;
1279 return STOP;
1282 int
1283 unshortcut(void *v)
1285 char **args = (char **)v;
1286 int errs = 0;
1288 if (args[0] == NULL) {
1289 fprintf(stderr, catgets(catd, CATSET, 222,
1290 "need shortcut names to remove\n"));
1291 return 1;
1293 while (*args != NULL) {
1294 if (delete_shortcut(*args) != OKAY) {
1295 errs = 1;
1296 fprintf(stderr, catgets(catd, CATSET, 223,
1297 "%s: no such shortcut\n"), *args);
1299 args++;
1301 return errs;
1304 struct oldaccount {
1305 struct oldaccount *ac_next; /* next account in list */
1306 char *ac_name; /* name of account */
1307 char **ac_vars; /* variables to set */
1310 static struct oldaccount *oldaccounts;
1312 struct oldaccount *
1313 get_oldaccount(const char *name)
1315 struct oldaccount *a;
1317 for (a = oldaccounts; a; a = a->ac_next)
1318 if (a->ac_name && strcmp(name, a->ac_name) == 0)
1319 break;
1320 return a;
1323 int
1324 account(void *v)
1326 char **args = (char **)v;
1327 struct oldaccount *a;
1328 char *cp;
1329 int i, mc, oqf, nqf;
1330 FILE *fp = stdout;
1332 if (args[0] == NULL) {
1333 if ((fp = Ftemp(&cp, "Ra", "w+", 0600, 1)) == NULL) {
1334 perror("tmpfile");
1335 return 1;
1337 rm(cp);
1338 Ftfree(&cp);
1339 mc = listaccounts(fp);
1340 for (a = oldaccounts; a; a = a->ac_next)
1341 if (a->ac_name) {
1342 if (mc++)
1343 fputc('\n', fp);
1344 fprintf(fp, "%s:\n", a->ac_name);
1345 for (i = 0; a->ac_vars[i]; i++)
1346 fprintf(fp, "\t%s\n", a->ac_vars[i]);
1348 if (mc)
1349 try_pager(fp);
1350 Fclose(fp);
1351 return 0;
1353 if (args[1] && args[1][0] == '{' && args[1][1] == '\0') {
1354 if (args[2] != NULL) {
1355 fprintf(stderr, "Syntax is: account <name> {\n");
1356 return 1;
1358 if ((a = get_oldaccount(args[0])) != NULL)
1359 a->ac_name = NULL;
1360 return define1(args[0], 1);
1362 strncpy(mboxname, expand("&"), sizeof mboxname)[sizeof mboxname-1]='\0';
1363 oqf = savequitflags();
1364 if ((a = get_oldaccount(args[0])) == NULL) {
1365 if (args[1]) {
1366 a = scalloc(1, sizeof *a);
1367 a->ac_next = oldaccounts;
1368 oldaccounts = a;
1369 } else {
1370 if ((i = callaccount(args[0])) != CBAD)
1371 goto setf;
1372 printf("Account %s does not exist.\n", args[0]);
1373 return 1;
1376 if (args[1]) {
1377 delaccount(args[0]);
1378 a->ac_name = sstrdup(args[0]);
1379 for (i = 1; args[i]; i++);
1380 a->ac_vars = scalloc(i, sizeof *a->ac_vars);
1381 for (i = 0; args[i+1]; i++)
1382 a->ac_vars[i] = sstrdup(args[i+1]);
1383 } else {
1384 unset_allow_undefined = 1;
1385 set(a->ac_vars);
1386 unset_allow_undefined = 0;
1387 setf: if (!starting) {
1388 nqf = savequitflags();
1389 restorequitflags(oqf);
1390 i = file1("%");
1391 restorequitflags(nqf);
1392 return i;
1395 return 0;
1398 int
1399 cflag(void *v)
1401 struct message *m;
1402 int *msgvec = v;
1403 int *ip;
1405 for (ip = msgvec; *ip != 0; ip++) {
1406 m = &message[*ip-1];
1407 setdot(m);
1408 if ((m->m_flag & (MFLAG|MFLAGGED)) == 0)
1409 m->m_flag |= MFLAG|MFLAGGED;
1411 return 0;
1414 int
1415 cunflag(void *v)
1417 struct message *m;
1418 int *msgvec = v;
1419 int *ip;
1421 for (ip = msgvec; *ip != 0; ip++) {
1422 m = &message[*ip-1];
1423 setdot(m);
1424 if (m->m_flag & (MFLAG|MFLAGGED)) {
1425 m->m_flag &= ~(MFLAG|MFLAGGED);
1426 m->m_flag |= MUNFLAG;
1429 return 0;
1432 int
1433 canswered(void *v)
1435 struct message *m;
1436 int *msgvec = v;
1437 int *ip;
1439 for (ip = msgvec; *ip != 0; ip++) {
1440 m = &message[*ip-1];
1441 setdot(m);
1442 if ((m->m_flag & (MANSWER|MANSWERED)) == 0)
1443 m->m_flag |= MANSWER|MANSWERED;
1445 return 0;
1448 int
1449 cunanswered(void *v)
1451 struct message *m;
1452 int *msgvec = v;
1453 int *ip;
1455 for (ip = msgvec; *ip != 0; ip++) {
1456 m = &message[*ip-1];
1457 setdot(m);
1458 if (m->m_flag & (MANSWER|MANSWERED)) {
1459 m->m_flag &= ~(MANSWER|MANSWERED);
1460 m->m_flag |= MUNANSWER;
1463 return 0;
1466 int
1467 cdraft(void *v)
1469 struct message *m;
1470 int *msgvec = v;
1471 int *ip;
1473 for (ip = msgvec; *ip != 0; ip++) {
1474 m = &message[*ip-1];
1475 setdot(m);
1476 if ((m->m_flag & (MDRAFT|MDRAFTED)) == 0)
1477 m->m_flag |= MDRAFT|MDRAFTED;
1479 return 0;
1482 int
1483 cundraft(void *v)
1485 struct message *m;
1486 int *msgvec = v;
1487 int *ip;
1489 for (ip = msgvec; *ip != 0; ip++) {
1490 m = &message[*ip-1];
1491 setdot(m);
1492 if (m->m_flag & (MDRAFT|MDRAFTED)) {
1493 m->m_flag &= ~(MDRAFT|MDRAFTED);
1494 m->m_flag |= MUNDRAFT;
1497 return 0;
1500 static float
1501 huge(void)
1503 #if defined (_CRAY)
1505 * This is not perfect, but correct for machines with a 32-bit
1506 * IEEE float and a 32-bit unsigned long, and does at least not
1507 * produce SIGFPE on the Cray Y-MP.
1509 union {
1510 float f;
1511 unsigned long l;
1512 } u;
1514 u.l = 0xff800000; /* -inf */
1515 return u.f;
1516 #elif defined (INFINITY)
1517 return -INFINITY;
1518 #elif defined (HUGE_VALF)
1519 return -HUGE_VALF;
1520 #elif defined (FLT_MAX)
1521 return -FLT_MAX;
1522 #else
1523 return -1e10;
1524 #endif
1527 int
1528 ckill(void *v)
1530 struct message *m;
1531 int *msgvec = v;
1532 int *ip;
1534 for (ip = msgvec; *ip != 0; ip++) {
1535 m = &message[*ip-1];
1536 m->m_flag |= MKILL;
1537 m->m_score = huge();
1539 return 0;
1542 int
1543 cunkill(void *v)
1545 struct message *m;
1546 int *msgvec = v;
1547 int *ip;
1549 for (ip = msgvec; *ip != 0; ip++) {
1550 m = &message[*ip-1];
1551 m->m_flag &= ~MKILL;
1552 m->m_score = 0;
1554 return 0;
1557 int
1558 cscore(void *v)
1560 char *str = v;
1561 char *sscore, *xp;
1562 int f, *msgvec, *ip;
1563 double nscore;
1564 struct message *m;
1566 msgvec = salloc((msgCount+2) * sizeof *msgvec);
1567 if ((sscore = laststring(str, &f, 0)) == NULL) {
1568 fprintf(stderr, "No score given.\n");
1569 return 1;
1571 nscore = strtod(sscore, &xp);
1572 if (*xp) {
1573 fprintf(stderr, "Invalid score: \"%s\"\n", sscore);
1574 return 1;
1576 if (nscore > FLT_MAX)
1577 nscore = FLT_MAX;
1578 else if (nscore < -FLT_MAX)
1579 nscore = -FLT_MAX;
1580 if (!f) {
1581 *msgvec = first(0, MMNORM);
1582 if (*msgvec == 0) {
1583 if (inhook)
1584 return 0;
1585 fprintf(stderr, "No messages to score.\n");
1586 return 1;
1588 msgvec[1] = 0;
1589 } else if (getmsglist(str, msgvec, 0) < 0)
1590 return 1;
1591 if (*msgvec == 0) {
1592 if (inhook)
1593 return 0;
1594 fprintf(stderr, "No applicable messages.\n");
1595 return 1;
1597 for (ip = msgvec; *ip && ip-msgvec < msgCount; ip++) {
1598 m = &message[*ip-1];
1599 if (m->m_score != huge()) {
1600 m->m_score += nscore;
1601 if (m->m_score < 0)
1602 m->m_flag |= MKILL;
1603 else if (m->m_score > 0)
1604 m->m_flag &= ~MKILL;
1605 if (m->m_score >= 0)
1606 setdot(m);
1609 return 0;
1612 /*ARGSUSED*/
1613 int
1614 cnoop(void *v)
1616 (void)v;
1618 switch (mb.mb_type) {
1619 case MB_IMAP:
1620 imap_noop();
1621 break;
1622 case MB_POP3:
1623 pop3_noop();
1624 break;
1625 default:
1626 break;
1628 return 0;
1631 int
1632 cremove(void *v)
1634 char vb[LINESIZE];
1635 char **args = v;
1636 char *name;
1637 int ec = 0;
1639 if (*args == NULL) {
1640 fprintf(stderr, "Syntax is: remove mailbox ...\n");
1641 return 1;
1643 do {
1644 if ((name = expand(*args)) == NULL)
1645 continue;
1646 if (strcmp(name, mailname) == 0) {
1647 fprintf(stderr,
1648 "Cannot remove current mailbox \"%s\".\n",
1649 name);
1650 ec |= 1;
1651 continue;
1653 snprintf(vb, sizeof vb, "Remove \"%s\" (y/n) ? ", name);
1654 if (yorn(vb) == 0)
1655 continue;
1656 switch (which_protocol(name)) {
1657 case PROTO_FILE:
1658 if (unlink(name) < 0) { /* do not handle .gz .bz2 */
1659 perror(name);
1660 ec |= 1;
1662 break;
1663 case PROTO_POP3:
1664 fprintf(stderr, "Cannot remove POP3 mailbox \"%s\".\n",
1665 name);
1666 ec |= 1;
1667 break;
1668 case PROTO_IMAP:
1669 if (imap_remove(name) != OKAY)
1670 ec |= 1;
1671 break;
1672 case PROTO_MAILDIR:
1673 if (maildir_remove(name) != OKAY)
1674 ec |= 1;
1675 break;
1676 case PROTO_UNKNOWN:
1677 fprintf(stderr,
1678 "Unknown protocol in \"%s\". Not removed.\n",
1679 name);
1680 ec |= 1;
1682 } while (*++args);
1683 return ec;
1686 int
1687 crename(void *v)
1689 char **args = v, *old, *new;
1690 enum protocol oldp, newp;
1691 int ec = 0;
1693 if (args[0] == NULL || args[1] == NULL || args[2] != NULL) {
1694 fprintf(stderr, "Syntax: rename old new\n");
1695 return 1;
1697 old = expand(args[0]);
1698 oldp = which_protocol(old);
1699 new = expand(args[1]);
1700 newp = which_protocol(new);
1701 if (strcmp(old, mailname) == 0 || strcmp(new, mailname) == 0) {
1702 fprintf(stderr, "Cannot rename current mailbox \"%s\".\n", old);
1703 return 1;
1705 if ((oldp == PROTO_IMAP || newp == PROTO_IMAP) && oldp != newp) {
1706 fprintf(stderr, "Can only rename folders of same type.\n");
1707 return 1;
1709 if (newp == PROTO_POP3)
1710 goto nopop3;
1711 switch (oldp) {
1712 case PROTO_FILE:
1713 if (link(old, new) < 0) {
1714 switch (errno) {
1715 case EACCES:
1716 case EEXIST:
1717 case ENAMETOOLONG:
1718 case ENOENT:
1719 case ENOSPC:
1720 case EXDEV:
1721 perror(new);
1722 break;
1723 default:
1724 perror(old);
1726 ec |= 1;
1727 } else if (unlink(old) < 0) {
1728 perror(old);
1729 ec |= 1;
1731 break;
1732 case PROTO_MAILDIR:
1733 if (rename(old, new) < 0) {
1734 perror(old);
1735 ec |= 1;
1737 break;
1738 case PROTO_POP3:
1739 nopop3: fprintf(stderr, "Cannot rename POP3 mailboxes.\n");
1740 ec |= 1;
1741 break;
1742 case PROTO_IMAP:
1743 if (imap_rename(old, new) != OKAY)
1744 ec |= 1;
1745 break;
1746 case PROTO_UNKNOWN:
1747 fprintf(stderr, "Unknown protocol in \"%s\" and \"%s\". "
1748 "Not renamed.\n", old, new);
1749 ec |= 1;
1751 return ec;