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