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