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