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