cmd3.c revision 1.32 1 /* $NetBSD: cmd3.c,v 1.32 2006/10/31 20:07:32 christos Exp $ */
2
3 /*
4 * Copyright (c) 1980, 1993
5 * The Regents of the University of California. All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
15 * 3. Neither the name of the University nor the names of its contributors
16 * may be used to endorse or promote products derived from this software
17 * without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29 * SUCH DAMAGE.
30 */
31
32 #include <sys/cdefs.h>
33 #ifndef lint
34 #if 0
35 static char sccsid[] = "@(#)cmd3.c 8.2 (Berkeley) 4/20/95";
36 #else
37 __RCSID("$NetBSD: cmd3.c,v 1.32 2006/10/31 20:07:32 christos Exp $");
38 #endif
39 #endif /* not lint */
40
41 #include "rcv.h"
42 #include <util.h>
43 #include "extern.h"
44 #include "mime.h"
45
46 /*
47 * Mail -- a mail program
48 *
49 * Still more user commands.
50 */
51 static int delgroup(const char *);
52 static int diction(const void *, const void *);
53
54 /*
55 * Process a shell escape by saving signals, ignoring signals,
56 * and forking a sh -c
57 */
58 int
59 shell(void *v)
60 {
61 char *str = v;
62 sig_t sigint = signal(SIGINT, SIG_IGN);
63 const char *shellcmd;
64 char cmd[BUFSIZ];
65
66 (void)strcpy(cmd, str);
67 if (bangexp(cmd) < 0)
68 return 1;
69 if ((shellcmd = value("SHELL")) == NULL)
70 shellcmd = _PATH_CSHELL;
71 (void)run_command(shellcmd, 0, 0, 1, "-c", cmd, NULL);
72 (void)signal(SIGINT, sigint);
73 (void)printf("!\n");
74 return 0;
75 }
76
77 /*
78 * Fork an interactive shell.
79 */
80 /*ARGSUSED*/
81 int
82 dosh(void *v __unused)
83 {
84 sig_t sigint = signal(SIGINT, SIG_IGN);
85 const char *shellcmd;
86
87 if ((shellcmd = value("SHELL")) == NULL)
88 shellcmd = _PATH_CSHELL;
89 (void)run_command(shellcmd, 0, 0, 1, NULL);
90 (void)signal(SIGINT, sigint);
91 (void)putchar('\n');
92 return 0;
93 }
94
95 /*
96 * Expand the shell escape by expanding unescaped !'s into the
97 * last issued command where possible.
98 */
99
100 char lastbang[128];
101
102 int
103 bangexp(char *str)
104 {
105 char bangbuf[BUFSIZ];
106 char *cp, *cp2;
107 int n;
108 int changed = 0;
109
110 cp = str;
111 cp2 = bangbuf;
112 n = BUFSIZ;
113 while (*cp) {
114 if (*cp == '!') {
115 if (n < strlen(lastbang)) {
116 overf:
117 (void)printf("Command buffer overflow\n");
118 return(-1);
119 }
120 changed++;
121 (void)strcpy(cp2, lastbang);
122 cp2 += strlen(lastbang);
123 n -= strlen(lastbang);
124 cp++;
125 continue;
126 }
127 if (*cp == '\\' && cp[1] == '!') {
128 if (--n <= 1)
129 goto overf;
130 *cp2++ = '!';
131 cp += 2;
132 changed++;
133 }
134 if (--n <= 1)
135 goto overf;
136 *cp2++ = *cp++;
137 }
138 *cp2 = 0;
139 if (changed) {
140 (void)printf("!%s\n", bangbuf);
141 (void)fflush(stdout);
142 }
143 (void)strcpy(str, bangbuf);
144 (void)strncpy(lastbang, bangbuf, 128);
145 lastbang[127] = 0;
146 return(0);
147 }
148
149 /*
150 * Print out a nice help message from some file or another.
151 */
152
153 int
154 /*ARGSUSED*/
155 help(void *v __unused)
156 {
157 int c;
158 FILE *f;
159
160 if ((f = Fopen(_PATH_HELP, "r")) == NULL) {
161 warn(_PATH_HELP);
162 return(1);
163 }
164 while ((c = getc(f)) != EOF)
165 (void)putchar(c);
166 (void)Fclose(f);
167 return(0);
168 }
169
170 /*
171 * Change user's working directory.
172 */
173 int
174 schdir(void *v)
175 {
176 char **arglist = v;
177 const char *cp;
178
179 if (*arglist == NULL)
180 cp = homedir;
181 else
182 if ((cp = expand(*arglist)) == NULL)
183 return(1);
184 if (chdir(cp) < 0) {
185 warn("%s", cp);
186 return(1);
187 }
188 return 0;
189 }
190
191 int
192 respond(void *v)
193 {
194 int *msgvec = v;
195 if (value("Replyall") == NULL)
196 return (_respond(msgvec));
197 else
198 return (_Respond(msgvec));
199 }
200
201 static struct name *
202 set_smopts(struct message *mp)
203 {
204 char *cp;
205 struct name *np = NULL;
206 char *reply_as_recipient = value("ReplyAsRecipient");
207
208 if (reply_as_recipient &&
209 (cp = skin(hfield("to", mp))) != NULL &&
210 extract(cp, GTO)->n_flink == NULL) { /* check for one recipient */
211 char *p, *q;
212 size_t len = strlen(cp);
213 /*
214 * XXX - perhaps we always want to ignore
215 * "undisclosed-recipients:;" ?
216 */
217 for (p = q = reply_as_recipient; *p; p = q) {
218 while (*q != '\0' && *q != ',' && !isblank((unsigned char)*q))
219 q++;
220 if (p + len == q && strncasecmp(cp, p, len) == 0)
221 return np;
222 while (*q == ',' || isblank((unsigned char)*q))
223 q++;
224 }
225 np = extract(__UNCONST("-f"), GSMOPTS);
226 np = cat(np, extract(cp, GSMOPTS));
227 }
228
229 return np;
230 }
231
232 /*
233 * Reply to a list of messages. Extract each name from the
234 * message header and send them off to mail1()
235 */
236 int
237 _respond(int *msgvec)
238 {
239 struct message *mp;
240 char *cp, *rcv, *replyto;
241 char **ap;
242 struct name *np;
243 struct header head;
244
245 if (msgvec[1] != 0) {
246 (void)printf("Sorry, can't reply to multiple messages at once\n");
247 return(1);
248 }
249 mp = &message[msgvec[0] - 1];
250 touch(mp);
251 dot = mp;
252 if ((rcv = skin(hfield("from", mp))) == NULL)
253 rcv = skin(nameof(mp, 1));
254 if ((replyto = skin(hfield("reply-to", mp))) != NULL)
255 np = extract(replyto, GTO);
256 else if ((cp = skin(hfield("to", mp))) != NULL)
257 np = extract(cp, GTO);
258 else
259 np = NULL;
260 np = elide(np);
261 /*
262 * Delete my name from the reply list,
263 * and with it, all my alternate names.
264 */
265 np = delname(np, myname);
266 if (altnames)
267 for (ap = altnames; *ap; ap++)
268 np = delname(np, *ap);
269 if (np != NULL && replyto == NULL)
270 np = cat(np, extract(rcv, GTO));
271 else if (np == NULL) {
272 if (replyto != NULL)
273 (void)printf("Empty reply-to field -- replying to author\n");
274 np = extract(rcv, GTO);
275 }
276 head.h_to = np;
277 if ((head.h_subject = hfield("subject", mp)) == NULL)
278 head.h_subject = hfield("subj", mp);
279 head.h_subject = reedit(head.h_subject);
280 if (replyto == NULL && (cp = skin(hfield("cc", mp))) != NULL) {
281 np = elide(extract(cp, GCC));
282 np = delname(np, myname);
283 if (altnames != 0)
284 for (ap = altnames; *ap; ap++)
285 np = delname(np, *ap);
286 head.h_cc = np;
287 } else
288 head.h_cc = NULL;
289 head.h_bcc = NULL;
290 head.h_smopts = set_smopts(mp);
291 #ifdef MIME_SUPPORT
292 head.h_attach = NULL;
293 #endif
294 mail1(&head, 1);
295 return(0);
296 }
297
298 /*
299 * Modify the subject we are replying to to begin with Re: if
300 * it does not already.
301 */
302 char *
303 reedit(char *subj)
304 {
305 char *newsubj;
306
307 if (subj == NULL)
308 return NULL;
309 if ((subj[0] == 'r' || subj[0] == 'R') &&
310 (subj[1] == 'e' || subj[1] == 'E') &&
311 subj[2] == ':')
312 return subj;
313 newsubj = salloc(strlen(subj) + 5);
314 (void)strcpy(newsubj, "Re: ");
315 (void)strcpy(newsubj + 4, subj);
316 return newsubj;
317 }
318
319 /*
320 * Preserve the named messages, so that they will be sent
321 * back to the system mailbox.
322 */
323 int
324 preserve(void *v)
325 {
326 int *msgvec = v;
327 struct message *mp;
328 int *ip, mesg;
329
330 if (edit) {
331 (void)printf("Cannot \"preserve\" in edit mode\n");
332 return(1);
333 }
334 for (ip = msgvec; *ip != 0; ip++) {
335 mesg = *ip;
336 mp = &message[mesg - 1];
337 mp->m_flag |= MPRESERVE;
338 mp->m_flag &= ~MBOX;
339 dot = mp;
340 }
341 return(0);
342 }
343
344 /*
345 * Mark all given messages as unread.
346 */
347 int
348 unread(void *v)
349 {
350 int *msgvec = v;
351 int *ip;
352
353 for (ip = msgvec; *ip != 0; ip++) {
354 dot = &message[*ip - 1];
355 dot->m_flag &= ~(MREAD|MTOUCH);
356 dot->m_flag |= MSTATUS;
357 }
358 return(0);
359 }
360
361 /*
362 * Mark all given messages as read.
363 */
364 int
365 markread(void *v)
366 {
367 int *msgvec = v;
368 int *ip;
369
370 for (ip = msgvec; *ip != 0; ip++) {
371 dot = &message[*ip - 1];
372 dot->m_flag &= ~(MNEW|MTOUCH);
373 dot->m_flag |= MREAD|MSTATUS;
374 }
375 return(0);
376 }
377
378 /*
379 * Print the size of each message.
380 */
381 int
382 messize(void *v)
383 {
384 int *msgvec = v;
385 struct message *mp;
386 int *ip, mesg;
387
388 for (ip = msgvec; *ip != 0; ip++) {
389 mesg = *ip;
390 mp = &message[mesg - 1];
391 (void)printf("%d: %ld/%llu\n", mesg, mp->m_blines,
392 (unsigned long long)mp->m_size);
393 }
394 return(0);
395 }
396
397 /*
398 * Quit quickly. If we are sourcing, just pop the input level
399 * by returning an error.
400 */
401 int
402 /*ARGSUSED*/
403 rexit(void *v __unused)
404 {
405 if (sourcing)
406 return(1);
407 exit(0);
408 /*NOTREACHED*/
409 }
410
411 /*
412 * Set or display a variable value. Syntax is similar to that
413 * of csh.
414 */
415 int
416 set(void *v)
417 {
418 char **arglist = v;
419 struct var *vp;
420 const char *cp;
421 char varbuf[BUFSIZ], **ap, **p;
422 int errs, h, s;
423 size_t l;
424
425 if (*arglist == NULL) {
426 for (h = 0, s = 1; h < HSHSIZE; h++)
427 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
428 s++;
429 ap = salloc(s * sizeof *ap);
430 for (h = 0, p = ap; h < HSHSIZE; h++)
431 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
432 *p++ = vp->v_name;
433 *p = NULL;
434 sort(ap);
435 for (p = ap; *p != NULL; p++)
436 (void)printf("%s\t%s\n", *p, value(*p));
437 return(0);
438 }
439 errs = 0;
440 for (ap = arglist; *ap != NULL; ap++) {
441 cp = *ap;
442 while (*cp != '=' && *cp != '\0')
443 ++cp;
444 l = cp - *ap;
445 if (l >= sizeof varbuf)
446 l = sizeof varbuf - 1;
447 (void)strncpy(varbuf, *ap, l);
448 varbuf[l] = '\0';
449 if (*cp == '\0')
450 cp = "";
451 else
452 cp++;
453 if (equal(varbuf, "")) {
454 (void)printf("Non-null variable name required\n");
455 errs++;
456 continue;
457 }
458 assign(varbuf, cp);
459 }
460 return(errs);
461 }
462
463 /*
464 * Unset a bunch of variable values.
465 */
466 int
467 unset(void *v)
468 {
469 char **arglist = v;
470 struct var *vp, *vp2;
471 int errs, h;
472 char **ap;
473
474 errs = 0;
475 for (ap = arglist; *ap != NULL; ap++) {
476 if ((vp2 = lookup(*ap)) == NULL) {
477 if (getenv(*ap)) {
478 (void)unsetenv(*ap);
479 } else if (!sourcing) {
480 (void)printf("\"%s\": undefined variable\n", *ap);
481 errs++;
482 }
483 continue;
484 }
485 h = hash(*ap);
486 if (vp2 == variables[h]) {
487 variables[h] = variables[h]->v_link;
488 v_free(vp2->v_name);
489 v_free(vp2->v_value);
490 free(vp2);
491 continue;
492 }
493 for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link)
494 ;
495 vp->v_link = vp2->v_link;
496 v_free(vp2->v_name);
497 v_free(vp2->v_value);
498 free(vp2);
499 }
500 return(errs);
501 }
502
503 /*
504 * Show a variable value.
505 */
506 int
507 show(void *v)
508 {
509 char **arglist = v;
510 struct var *vp;
511 char **ap, **p;
512 int h, s;
513
514 if (*arglist == NULL) {
515 for (h = 0, s = 1; h < HSHSIZE; h++)
516 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
517 s++;
518 ap = salloc(s * sizeof *ap);
519 for (h = 0, p = ap; h < HSHSIZE; h++)
520 for (vp = variables[h]; vp != NULL; vp = vp->v_link)
521 *p++ = vp->v_name;
522 *p = NULL;
523 sort(ap);
524 for (p = ap; *p != NULL; p++)
525 (void)printf("%s=%s\n", *p, value(*p));
526 return(0);
527 }
528
529 for (ap = arglist; *ap != NULL; ap++) {
530 char *val = value(*ap);
531 (void)printf("%s=%s\n", *ap, val ? val : "<null>");
532 }
533 return 0;
534 }
535
536
537 /*
538 * Put add users to a group.
539 */
540 int
541 group(void *v)
542 {
543 char **argv = v;
544 struct grouphead *gh;
545 struct group *gp;
546 int h;
547 int s;
548 char **ap, *gname, **p;
549
550 if (*argv == NULL) {
551 for (h = 0, s = 1; h < HSHSIZE; h++)
552 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
553 s++;
554 ap = salloc(s * sizeof *ap);
555 for (h = 0, p = ap; h < HSHSIZE; h++)
556 for (gh = groups[h]; gh != NULL; gh = gh->g_link)
557 *p++ = gh->g_name;
558 *p = NULL;
559 sort(ap);
560 for (p = ap; *p != NULL; p++)
561 printgroup(*p);
562 return(0);
563 }
564 if (argv[1] == NULL) {
565 printgroup(*argv);
566 return(0);
567 }
568 gname = *argv;
569 h = hash(gname);
570 if ((gh = findgroup(gname)) == NULL) {
571 gh = (struct grouphead *) ecalloc(1, sizeof *gh);
572 gh->g_name = vcopy(gname);
573 gh->g_list = NULL;
574 gh->g_link = groups[h];
575 groups[h] = gh;
576 }
577
578 /*
579 * Insert names from the command list into the group.
580 * Who cares if there are duplicates? They get tossed
581 * later anyway.
582 */
583
584 for (ap = argv + 1; *ap != NULL; ap++) {
585 gp = (struct group *) ecalloc(1, sizeof *gp);
586 gp->ge_name = vcopy(*ap);
587 gp->ge_link = gh->g_list;
588 gh->g_list = gp;
589 }
590 return(0);
591 }
592
593 /*
594 * The unalias command takes a list of alises
595 * and discards the remembered groups of users.
596 */
597 int
598 unalias(void *v)
599 {
600 char **ap;
601
602 for (ap = v; *ap != NULL; ap++)
603 (void)delgroup(*ap);
604 return 0;
605 }
606
607 /*
608 * Delete the named group alias. Return zero if the group was
609 * successfully deleted, or -1 if there was no such group.
610 */
611 static int
612 delgroup(const char *name)
613 {
614 struct grouphead *gh, *p;
615 struct group *g;
616 int h;
617
618 h = hash(name);
619 for (gh = groups[h], p = NULL; gh != NULL; p = gh, gh = gh->g_link)
620 if (strcmp(gh->g_name, name) == 0) {
621 if (p == NULL)
622 groups[h] = gh->g_link;
623 else
624 p->g_link = gh->g_link;
625 while (gh->g_list != NULL) {
626 g = gh->g_list;
627 gh->g_list = g->ge_link;
628 free(g->ge_name);
629 free(g);
630 }
631 free(gh->g_name);
632 free(gh);
633 return 0;
634 }
635 return -1;
636 }
637
638 /*
639 * Sort the passed string vecotor into ascending dictionary
640 * order.
641 */
642 void
643 sort(char **list)
644 {
645 char **ap;
646
647 for (ap = list; *ap != NULL; ap++)
648 ;
649 if (ap-list < 2)
650 return;
651 qsort(list, (size_t)(ap-list), sizeof(*list), diction);
652 }
653
654 /*
655 * Do a dictionary order comparison of the arguments from
656 * qsort.
657 */
658 static int
659 diction(const void *a, const void *b)
660 {
661 return(strcmp(*(const char *const *)a, *(const char *const *)b));
662 }
663
664 /*
665 * The do nothing command for comments.
666 */
667
668 /*ARGSUSED*/
669 int
670 null(void *v __unused)
671 {
672 return 0;
673 }
674
675 /*
676 * Change to another file. With no argument, print information about
677 * the current file.
678 */
679 int
680 file(void *v)
681 {
682 char **argv = v;
683
684 if (argv[0] == NULL) {
685 (void)newfileinfo(0);
686 return 0;
687 }
688 if (setfile(*argv) < 0)
689 return 1;
690 announce();
691 return 0;
692 }
693
694 /*
695 * Expand file names like echo
696 */
697 int
698 echo(void *v)
699 {
700 char **argv = v;
701 char **ap;
702 const char *cp;
703
704 for (ap = argv; *ap != NULL; ap++) {
705 cp = *ap;
706 if ((cp = expand(cp)) != NULL) {
707 if (ap != argv)
708 (void)putchar(' ');
709 (void)printf("%s", cp);
710 }
711 }
712 (void)putchar('\n');
713 return 0;
714 }
715
716 int
717 Respond(void *v)
718 {
719 int *msgvec = v;
720 if (value("Replyall") == NULL)
721 return (_Respond(msgvec));
722 else
723 return (_respond(msgvec));
724 }
725
726 /*
727 * Reply to a series of messages by simply mailing to the senders
728 * and not messing around with the To: and Cc: lists as in normal
729 * reply.
730 */
731 int
732 _Respond(int msgvec[])
733 {
734 struct header head;
735 struct message *mp;
736 int *ap;
737 char *cp;
738
739 head.h_to = NULL;
740 for (ap = msgvec; *ap != 0; ap++) {
741 mp = &message[*ap - 1];
742 touch(mp);
743 dot = mp;
744 if ((cp = skin(hfield("from", mp))) == NULL)
745 cp = skin(nameof(mp, 2));
746 head.h_to = cat(head.h_to, extract(cp, GTO));
747 }
748 if (head.h_to == NULL)
749 return 0;
750 mp = &message[msgvec[0] - 1];
751 if ((head.h_subject = hfield("subject", mp)) == NULL)
752 head.h_subject = hfield("subj", mp);
753 head.h_subject = reedit(head.h_subject);
754 head.h_cc = NULL;
755 head.h_bcc = NULL;
756 head.h_smopts = set_smopts(mp);
757 #ifdef MIME_SUPPORT
758 head.h_attach = NULL;
759 #endif
760 mail1(&head, 1);
761 return 0;
762 }
763
764 /*
765 * Conditional commands. These allow one to parameterize one's
766 * .mailrc and do some things if sending, others if receiving.
767 */
768 int
769 ifcmd(void *v)
770 {
771 char **argv = v;
772 char *cp;
773
774 if (cond != CANY) {
775 (void)printf("Illegal nested \"if\"\n");
776 return(1);
777 }
778 cond = CANY;
779 cp = argv[0];
780 switch (*cp) {
781 case 'r': case 'R':
782 cond = CRCV;
783 break;
784
785 case 's': case 'S':
786 cond = CSEND;
787 break;
788
789 default:
790 (void)printf("Unrecognized if-keyword: \"%s\"\n", cp);
791 return(1);
792 }
793 return(0);
794 }
795
796 /*
797 * Implement 'else'. This is pretty simple -- we just
798 * flip over the conditional flag.
799 */
800 int
801 /*ARGSUSED*/
802 elsecmd(void *v __unused)
803 {
804
805 switch (cond) {
806 case CANY:
807 (void)printf("\"Else\" without matching \"if\"\n");
808 return(1);
809
810 case CSEND:
811 cond = CRCV;
812 break;
813
814 case CRCV:
815 cond = CSEND;
816 break;
817
818 default:
819 (void)printf("Mail's idea of conditions is screwed up\n");
820 cond = CANY;
821 break;
822 }
823 return(0);
824 }
825
826 /*
827 * End of if statement. Just set cond back to anything.
828 */
829 int
830 /*ARGSUSED*/
831 endifcmd(void *v __unused)
832 {
833
834 if (cond == CANY) {
835 (void)printf("\"Endif\" without matching \"if\"\n");
836 return(1);
837 }
838 cond = CANY;
839 return(0);
840 }
841
842 /*
843 * Set the list of alternate names.
844 */
845 int
846 alternates(void *v)
847 {
848 char **namelist = v;
849 int c;
850 char **ap, **ap2, *cp;
851
852 c = argcount(namelist) + 1;
853 if (c == 1) {
854 if (altnames == 0)
855 return(0);
856 for (ap = altnames; *ap; ap++)
857 (void)printf("%s ", *ap);
858 (void)printf("\n");
859 return(0);
860 }
861 if (altnames != 0)
862 free(altnames);
863 altnames = (char **) ecalloc((unsigned) c, sizeof (char *));
864 for (ap = namelist, ap2 = altnames; *ap; ap++, ap2++) {
865 cp = ecalloc((unsigned) strlen(*ap) + 1, sizeof (char));
866 (void)strcpy(cp, *ap);
867 *ap2 = cp;
868 }
869 *ap2 = 0;
870 return(0);
871 }
872