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