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