Merge branch 'vendor/LESS'
[dragonfly.git] / usr.bin / mail / cmd3.c
1 /*
2  * Copyright (c) 1980, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  * 3. All advertising materials mentioning features or use of this software
14  *    must display the following acknowledgement:
15  *      This product includes software developed by the University of
16  *      California, Berkeley and its contributors.
17  * 4. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * @(#)cmd3.c   8.2 (Berkeley) 4/20/95
34  * $FreeBSD: src/usr.bin/mail/cmd3.c,v 1.4.6.4 2003/01/06 05:46:03 mikeh Exp $
35  * $DragonFly: src/usr.bin/mail/cmd3.c,v 1.4 2004/09/08 03:01:11 joerg Exp $
36  */
37
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
52 shell(char *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         run_command(sh, 0, -1, -1, "-c", cmd, NULL);
65         signal(SIGINT, sigint);
66         printf("!\n");
67         return (0);
68 }
69
70 /*
71  * Fork an interactive shell.
72  */
73 /*ARGSUSED*/
74 int
75 dosh(char *str)
76 {
77         sig_t sigint = signal(SIGINT, SIG_IGN);
78         char *sh;
79
80         if ((sh = value("SHELL")) == NULL)
81                 sh = _PATH_CSHELL;
82         run_command(sh, 0, -1, -1, NULL, NULL, NULL);
83         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
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 < 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                 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
147 help(void)
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         Fclose(f);
159         return (0);
160 }
161
162 /*
163  * Change user's working directory.
164  */
165 int
166 schdir(char **arglist)
167 {
168         char *cp;
169
170         if (*arglist == NULL) {
171                 if (homedir == NULL)
172                         return (1);
173                 cp = homedir;
174         } else
175                 if ((cp = expand(*arglist)) == NULL)
176                         return (1);
177         if (chdir(cp) < 0) {
178                 warn("%s", cp);
179                 return (1);
180         }
181         return (0);
182 }
183
184 int
185 respond(int *msgvec)
186 {
187         if (value("Replyall") == NULL && value("flipr") == NULL)
188                 return (dorespond(msgvec));
189         else
190                 return (doRespond(msgvec));
191 }
192
193 /*
194  * Reply to a list of messages.  Extract each name from the
195  * message header and send them off to mail1()
196  */
197 int
198 dorespond(int *msgvec)
199 {
200         struct message *mp;
201         char *cp, *rcv, *replyto;
202         char **ap;
203         struct name *np;
204         struct header head;
205
206         if (msgvec[1] != 0) {
207                 printf("Sorry, can't reply to multiple messages at once\n");
208                 return (1);
209         }
210         mp = &message[msgvec[0] - 1];
211         touch(mp);
212         dot = mp;
213         if ((rcv = skin(hfield("from", mp))) == NULL)
214                 rcv = skin(nameof(mp, 1));
215         if ((replyto = skin(hfield("reply-to", mp))) != NULL)
216                 np = extract(replyto, GTO);
217         else if ((cp = skin(hfield("to", mp))) != NULL)
218                 np = extract(cp, GTO);
219         else
220                 np = NULL;
221         np = elide(np);
222         /*
223          * Delete my name from the reply list,
224          * and with it, all my alternate names.
225          */
226         np = delname(np, myname);
227         if (altnames)
228                 for (ap = altnames; *ap != NULL; ap++)
229                         np = delname(np, *ap);
230         if (np != NULL && replyto == NULL)
231                 np = cat(np, extract(rcv, GTO));
232         else if (np == NULL) {
233                 if (replyto != NULL)
234                         printf("Empty reply-to field -- replying to author\n");
235                 np = extract(rcv, GTO);
236         }
237         head.h_to = np;
238         if ((head.h_subject = hfield("subject", mp)) == NULL)
239                 head.h_subject = hfield("subj", mp);
240         head.h_subject = reedit(head.h_subject);
241         if (replyto == NULL && (cp = skin(hfield("cc", mp))) != NULL) {
242                 np = elide(extract(cp, GCC));
243                 np = delname(np, myname);
244                 if (altnames != 0)
245                         for (ap = altnames; *ap != NULL; ap++)
246                                 np = delname(np, *ap);
247                 head.h_cc = np;
248         } else
249                 head.h_cc = NULL;
250         head.h_bcc = NULL;
251         head.h_smopts = NULL;
252         head.h_replyto = value("REPLYTO");
253         head.h_inreplyto = skin(hfield("message-id", mp));
254         mail1(&head, 1);
255         return (0);
256 }
257
258 /*
259  * Modify the subject we are replying to to begin with Re: if
260  * it does not already.
261  */
262 char *
263 reedit(char *subj)
264 {
265         char *newsubj;
266
267         if (subj == NULL)
268                 return (NULL);
269         if ((subj[0] == 'r' || subj[0] == 'R') &&
270             (subj[1] == 'e' || subj[1] == 'E') &&
271             subj[2] == ':')
272                 return (subj);
273         newsubj = salloc(strlen(subj) + 5);
274         sprintf(newsubj, "Re: %s", subj);
275         return (newsubj);
276 }
277
278 /*
279  * Preserve the named messages, so that they will be sent
280  * back to the system mailbox.
281  */
282 int
283 preserve(int *msgvec)
284 {
285         int *ip, mesg;
286         struct message *mp;
287
288         if (edit) {
289                 printf("Cannot \"preserve\" in edit mode\n");
290                 return (1);
291         }
292         for (ip = msgvec; *ip != 0; ip++) {
293                 mesg = *ip;
294                 mp = &message[mesg-1];
295                 mp->m_flag |= MPRESERVE;
296                 mp->m_flag &= ~MBOX;
297                 dot = mp;
298         }
299         return (0);
300 }
301
302 /*
303  * Mark all given messages as unread.
304  */
305 int
306 unread(int *msgvec)
307 {
308         int *ip;
309
310         for (ip = msgvec; *ip != 0; ip++) {
311                 dot = &message[*ip-1];
312                 dot->m_flag &= ~(MREAD|MTOUCH);
313                 dot->m_flag |= MSTATUS;
314         }
315         return (0);
316 }
317
318 /*
319  * Print the size of each message.
320  */
321 int
322 messize(int *msgvec)
323 {
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
340 rexit(int e)
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
353 set(char **arglist)
354 {
355         struct var *vp;
356         char *cp, *cp2;
357         char varbuf[BUFSIZ], **ap, **p;
358         int errs, h, s;
359
360         if (*arglist == NULL) {
361                 for (h = 0, s = 1; h < HSHSIZE; h++)
362                         for (vp = variables[h]; vp != NULL; vp = vp->v_link)
363                                 s++;
364                 ap = (char **)salloc(s * sizeof(*ap));
365                 for (h = 0, p = ap; h < HSHSIZE; h++)
366                         for (vp = variables[h]; vp != NULL; vp = vp->v_link)
367                                 *p++ = vp->v_name;
368                 *p = NULL;
369                 sort(ap);
370                 for (p = ap; *p != NULL; p++)
371                         printf("%s\t%s\n", *p, value(*p));
372                 return (0);
373         }
374         errs = 0;
375         for (ap = arglist; *ap != NULL; ap++) {
376                 cp = *ap;
377                 cp2 = varbuf;
378                 while (cp2 < varbuf + sizeof(varbuf) - 1 && *cp != '=' && *cp != '\0')
379                         *cp2++ = *cp++;
380                 *cp2 = '\0';
381                 if (*cp == '\0')
382                         cp = "";
383                 else
384                         cp++;
385                 if (equal(varbuf, "")) {
386                         printf("Non-null variable name required\n");
387                         errs++;
388                         continue;
389                 }
390                 assign(varbuf, cp);
391         }
392         return (errs);
393 }
394
395 /*
396  * Unset a bunch of variable values.
397  */
398 int
399 unset(char **arglist)
400 {
401         struct var *vp, *vp2;
402         int errs, h;
403         char **ap;
404
405         errs = 0;
406         for (ap = arglist; *ap != NULL; ap++) {
407                 if ((vp2 = lookup(*ap)) == NULL) {
408                         if (getenv(*ap)) 
409                                 unsetenv(*ap);
410                         else if (!sourcing) {
411                                 printf("\"%s\": undefined variable\n", *ap);
412                                 errs++;
413                         }
414                         continue;
415                 }
416                 h = hash(*ap);
417                 if (vp2 == variables[h]) {
418                         variables[h] = variables[h]->v_link;
419                         vfree(vp2->v_name);
420                         vfree(vp2->v_value);
421                         free(vp2);
422                         continue;
423                 }
424                 for (vp = variables[h]; vp->v_link != vp2; vp = vp->v_link)
425                         ;
426                 vp->v_link = vp2->v_link;
427                 vfree(vp2->v_name);
428                 vfree(vp2->v_value);
429                 free(vp2);
430         }
431         return (errs);
432 }
433
434 /*
435  * Put add users to a group.
436  */
437 int
438 group(char **argv)
439 {
440         struct grouphead *gh;
441         struct group *gp;
442         char **ap, *gname, **p;
443         int h, s;
444
445         if (*argv == NULL) {
446                 for (h = 0, s = 1; h < HSHSIZE; h++)
447                         for (gh = groups[h]; gh != NULL; gh = gh->g_link)
448                                 s++;
449                 ap = (char **)salloc(s * sizeof(*ap));
450                 for (h = 0, p = ap; h < HSHSIZE; h++)
451                         for (gh = groups[h]; gh != NULL; gh = gh->g_link)
452                                 *p++ = gh->g_name;
453                 *p = NULL;
454                 sort(ap);
455                 for (p = ap; *p != NULL; p++)
456                         printgroup(*p);
457                 return (0);
458         }
459         if (argv[1] == NULL) {
460                 printgroup(*argv);
461                 return (0);
462         }
463         gname = *argv;
464         h = hash(gname);
465         if ((gh = findgroup(gname)) == NULL) {
466                 gh = calloc(sizeof(*gh), 1);
467                 gh->g_name = vcopy(gname);
468                 gh->g_list = NULL;
469                 gh->g_link = groups[h];
470                 groups[h] = gh;
471         }
472
473         /*
474          * Insert names from the command list into the group.
475          * Who cares if there are duplicates?  They get tossed
476          * later anyway.
477          */
478
479         for (ap = argv+1; *ap != NULL; ap++) {
480                 gp = calloc(sizeof(*gp), 1);
481                 gp->ge_name = vcopy(*ap);
482                 gp->ge_link = gh->g_list;
483                 gh->g_list = gp;
484         }
485         return (0);
486 }
487
488 /*
489  * Sort the passed string vecotor into ascending dictionary
490  * order.
491  */
492 void
493 sort(char **list)
494 {
495         char **ap;
496
497         for (ap = list; *ap != NULL; ap++)
498                 ;
499         if (ap-list < 2)
500                 return;
501         qsort(list, ap-list, sizeof(*list), diction);
502 }
503
504 /*
505  * Do a dictionary order comparison of the arguments from
506  * qsort.
507  */
508 int
509 diction(const void *a, const void *b)
510 {
511         return (strcmp(*(const char **)a, *(const char **)b));
512 }
513
514 /*
515  * The do nothing command for comments.
516  */
517
518 /*ARGSUSED*/
519 int
520 null(int e)
521 {
522         return (0);
523 }
524
525 /*
526  * Change to another file.  With no argument, print information about
527  * the current file.
528  */
529 int
530 file(char **argv)
531 {
532         if (argv[0] == NULL) {
533                 newfileinfo(0);
534                 return (0);
535         }
536         if (setfile(*argv) < 0)
537                 return (1);
538         announce();
539         return (0);
540 }
541
542 /*
543  * Expand file names like echo
544  */
545 int
546 echo(char **argv)
547 {
548         char **ap, *cp;
549
550         for (ap = argv; *ap != NULL; ap++) {
551                 cp = *ap;
552                 if ((cp = expand(cp)) != NULL) {
553                         if (ap != argv)
554                                 printf(" ");
555                         printf("%s", cp);
556                 }
557         }
558         printf("\n");
559         return (0);
560 }
561
562 int
563 Respond(int *msgvec)
564 {
565         if (value("Replyall") == NULL && value("flipr") == NULL)
566                 return (doRespond(msgvec));
567         else
568                 return (dorespond(msgvec));
569 }
570
571 /*
572  * Reply to a series of messages by simply mailing to the senders
573  * and not messing around with the To: and Cc: lists as in normal
574  * reply.
575  */
576 int
577 doRespond(int *msgvec)
578 {
579         struct header head;
580         struct message *mp;
581         int *ap;
582         char *cp, *mid;
583
584         head.h_to = NULL;
585         for (ap = msgvec; *ap != 0; ap++) {
586                 mp = &message[*ap - 1];
587                 touch(mp);
588                 dot = mp;
589                 if ((cp = skin(hfield("from", mp))) == NULL)
590                         cp = skin(nameof(mp, 2));
591                 head.h_to = cat(head.h_to, extract(cp, GTO));
592                 mid = skin(hfield("message-id", mp));
593         }
594         if (head.h_to == NULL)
595                 return (0);
596         mp = &message[msgvec[0] - 1];
597         if ((head.h_subject = hfield("subject", mp)) == NULL)
598                 head.h_subject = hfield("subj", mp);
599         head.h_subject = reedit(head.h_subject);
600         head.h_cc = NULL;
601         head.h_bcc = NULL;
602         head.h_smopts = NULL;
603         head.h_replyto = value("REPLYTO");
604         head.h_inreplyto = mid;
605         mail1(&head, 1);
606         return (0);
607 }
608
609 /*
610  * Conditional commands.  These allow one to parameterize one's
611  * .mailrc and do some things if sending, others if receiving.
612  */
613 int
614 ifcmd(char **argv)
615 {
616         char *cp;
617
618         if (cond != CANY) {
619                 printf("Illegal nested \"if\"\n");
620                 return (1);
621         }
622         cond = CANY;
623         cp = argv[0];
624         switch (*cp) {
625         case 'r': case 'R':
626                 cond = CRCV;
627                 break;
628
629         case 's': case 'S':
630                 cond = CSEND;
631                 break;
632
633         default:
634                 printf("Unrecognized if-keyword: \"%s\"\n", cp);
635                 return (1);
636         }
637         return (0);
638 }
639
640 /*
641  * Implement 'else'.  This is pretty simple -- we just
642  * flip over the conditional flag.
643  */
644 int
645 elsecmd(void)
646 {
647         switch (cond) {
648         case CANY:
649                 printf("\"Else\" without matching \"if\"\n");
650                 return (1);
651
652         case CSEND:
653                 cond = CRCV;
654                 break;
655
656         case CRCV:
657                 cond = CSEND;
658                 break;
659
660         default:
661                 printf("Mail's idea of conditions is screwed up\n");
662                 cond = CANY;
663                 break;
664         }
665         return (0);
666 }
667
668 /*
669  * End of if statement.  Just set cond back to anything.
670  */
671 int
672 endifcmd(void)
673 {
674         if (cond == CANY) {
675                 printf("\"Endif\" without matching \"if\"\n");
676                 return (1);
677         }
678         cond = CANY;
679         return (0);
680 }
681
682 /*
683  * Set the list of alternate names.
684  */
685 int
686 alternates(char **namelist)
687 {
688         int c;
689         char **ap, **ap2, *cp;
690
691         c = argcount(namelist) + 1;
692         if (c == 1) {
693                 if (altnames == 0)
694                         return (0);
695                 for (ap = altnames; *ap != NULL; ap++)
696                         printf("%s ", *ap);
697                 printf("\n");
698                 return (0);
699         }
700         if (altnames != 0)
701                 free(altnames);
702         altnames = calloc((unsigned)c, sizeof(char *));
703         for (ap = namelist, ap2 = altnames; *ap != NULL; ap++, ap2++) {
704                 cp = calloc((unsigned)strlen(*ap) + 1, sizeof(char));
705                 strcpy(cp, *ap);
706                 *ap2 = cp;
707         }
708         *ap2 = 0;
709         return (0);
710 }