1 /* $NetBSD: cmd3.c,v 1.40 2009/04/10 13:08:24 christos Exp $ */
4 * Copyright (c) 1980, 1993
5 * The Regents of the University of California. All rights reserved.
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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.
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
32 #include <sys/cdefs.h>
35 static char sccsid
[] = "@(#)cmd3.c 8.2 (Berkeley) 4/20/95";
37 __RCSID("$NetBSD: cmd3.c,v 1.40 2009/04/10 13:08:24 christos Exp $");
50 * Mail -- a mail program
52 * Still more user commands.
57 * Do a dictionary order comparison of the arguments from
61 diction(const void *a
, const void *b
)
64 return strcmp(*(const char *const *)a
, *(const char *const *)b
);
68 * Sort the passed string vector into ascending dictionary
72 sort(const char **list
)
76 for (ap
= list
; *ap
!= NULL
; ap
++)
80 qsort(list
, (size_t)(ap
- list
), sizeof(*list
), diction
);
84 * Expand the shell escape by expanding unescaped !'s into the
85 * last issued command where possible.
90 static char lastbang
[128];
91 char bangbuf
[LINESIZE
];
99 n
= sizeof(bangbuf
); /* bytes left in bangbuf */
102 if (n
< (int)strlen(lastbang
)) {
104 (void)printf("Command buffer overflow\n");
108 (void)strcpy(cp2
, lastbang
);
109 cp2
+= strlen(lastbang
);
110 n
-= strlen(lastbang
);
114 if (*cp
== '\\' && cp
[1] == '!') {
127 (void)printf("!%s\n", bangbuf
);
128 (void)fflush(stdout
);
130 (void)strcpy(str
, bangbuf
);
131 (void)strlcpy(lastbang
, bangbuf
, sizeof(lastbang
));
136 * Process a shell escape by saving signals, ignoring signals,
137 * and forking a sh -c
142 struct sigaction osa
;
145 const char *shellcmd
;
150 (void)sig_ignore(SIGINT
, &osa
, &oset
);
151 (void)strcpy(cmd
, str
);
152 if (bangexp(cmd
) < 0)
154 if ((shellcmd
= value(ENAME_SHELL
)) == NULL
)
155 shellcmd
= _PATH_CSHELL
;
156 (void)run_command(shellcmd
, NULL
, 0, 1, "-c", cmd
, NULL
);
157 (void)sig_restore(SIGINT
, &osa
, &oset
);
164 * Fork an interactive shell.
168 dosh(void *v __unused
)
170 struct sigaction osa
;
172 const char *shellcmd
;
175 (void)sig_ignore(SIGINT
, &osa
, &oset
);
176 if ((shellcmd
= value(ENAME_SHELL
)) == NULL
)
177 shellcmd
= _PATH_CSHELL
;
178 (void)run_command(shellcmd
, NULL
, 0, 1, NULL
);
179 (void)sig_restore(SIGINT
, &osa
, &oset
);
186 * Print out a nice help message from some file or another.
191 help(void *v __unused
)
199 * Change user's working directory.
208 if (*arglist
== NULL
)
211 if ((cp
= expand(*arglist
)) == NULL
)
221 * Return the smopts field if "ReplyAsRecipient" is defined.
224 set_smopts(struct message
*mp
)
228 char *reply_as_recipient
;
231 reply_as_recipient
= value(ENAME_REPLYASRECIPIENT
);
232 if (reply_as_recipient
&&
233 (cp
= skin(hfield("to", mp
))) != NULL
&&
234 extract(cp
, GTO
)->n_flink
== NULL
) { /* check for one recipient */
236 size_t len
= strlen(cp
);
238 * XXX - perhaps we always want to ignore
239 * "undisclosed-recipients:;" ?
241 for (p
= q
= reply_as_recipient
; *p
; p
= q
) {
242 while (*q
!= '\0' && *q
!= ',' && !is_WSP(*q
))
244 if (p
+ len
== q
&& strncasecmp(cp
, p
, len
) == 0)
246 while (*q
== ',' || is_WSP(*q
))
249 np
= extract(__UNCONST("-f"), GSMOPTS
);
250 np
= cat(np
, extract(cp
, GSMOPTS
));
257 * Modify the subject we are replying to to begin with Re: if
258 * it does not already.
261 reedit(char *subj
, const char *pref
)
266 assert(pref
!= NULL
);
268 return __UNCONST(pref
);
269 preflen
= strlen(pref
);
270 if (strncasecmp(subj
, pref
, preflen
) == 0)
272 newsubj
= salloc(strlen(subj
) + preflen
+ 1 + 1);
273 (void)sprintf(newsubj
, "%s %s", pref
, subj
);
278 * Set the "In-Reply-To" and "References" header fields appropriately.
282 set_ident_fields(struct header
*hp
, struct message
*mp
)
287 in_reply_to
= hfield("message-id", mp
);
288 hp
->h_in_reply_to
= in_reply_to
;
290 references
= hfield("references", mp
);
291 hp
->h_references
= extract(references
, GMISC
);
292 hp
->h_references
= cat(hp
->h_references
, extract(in_reply_to
, GMISC
));
296 * Reply to a list of messages. Extract each name from the
297 * message header and send them off to mail1()
300 respond_core(int *msgvec
)
303 char *cp
, *rcv
, *replyto
;
308 /* ensure that all header fields are initially NULL */
309 (void)memset(&head
, 0, sizeof(head
));
311 if (msgvec
[1] != 0) {
312 (void)printf("Sorry, can't reply to multiple messages at once\n");
315 mp
= get_message(msgvec
[0]);
318 if ((rcv
= skin(hfield("from", mp
))) == NULL
)
319 rcv
= skin(nameof(mp
, 1));
320 if ((replyto
= skin(hfield("reply-to", mp
))) != NULL
)
321 np
= extract(replyto
, GTO
);
322 else if ((cp
= skin(hfield("to", mp
))) != NULL
)
323 np
= extract(cp
, GTO
);
328 * Delete my name from the reply list,
329 * and with it, all my alternate names.
331 np
= delname(np
, myname
);
333 for (ap
= altnames
; *ap
; ap
++)
334 np
= delname(np
, *ap
);
335 if (np
!= NULL
&& replyto
== NULL
)
336 np
= cat(np
, extract(rcv
, GTO
));
337 else if (np
== NULL
) {
339 (void)printf("Empty reply-to field -- replying to author\n");
340 np
= extract(rcv
, GTO
);
343 if ((head
.h_subject
= hfield("subject", mp
)) == NULL
)
344 head
.h_subject
= hfield("subj", mp
);
345 head
.h_subject
= reedit(head
.h_subject
, "Re:");
346 if (replyto
== NULL
&& (cp
= skin(hfield("cc", mp
))) != NULL
) {
347 np
= elide(extract(cp
, GCC
));
348 np
= delname(np
, myname
);
350 for (ap
= altnames
; *ap
; ap
++)
351 np
= delname(np
, *ap
);
356 head
.h_smopts
= set_smopts(mp
);
358 head
.h_attach
= NULL
;
360 set_ident_fields(&head
, mp
);
366 * Reply to a series of messages by simply mailing to the senders
367 * and not messing around with the To: and Cc: lists as in normal
371 Respond_core(int msgvec
[])
378 /* ensure that all header fields are initially NULL */
379 (void)memset(&head
, 0, sizeof(head
));
382 for (ap
= msgvec
; *ap
!= 0; ap
++) {
383 mp
= get_message(*ap
);
386 if ((cp
= skin(hfield("from", mp
))) == NULL
)
387 cp
= skin(nameof(mp
, 2));
388 head
.h_to
= cat(head
.h_to
, extract(cp
, GTO
));
390 if (head
.h_to
== NULL
)
392 mp
= get_message(msgvec
[0]);
393 if ((head
.h_subject
= hfield("subject", mp
)) == NULL
)
394 head
.h_subject
= hfield("subj", mp
);
395 head
.h_subject
= reedit(head
.h_subject
, "Re:");
398 head
.h_smopts
= set_smopts(mp
);
400 head
.h_attach
= NULL
;
402 set_ident_fields(&head
, mp
);
411 if (value(ENAME_REPLYALL
) == NULL
)
412 return respond_core(msgvec
);
414 return Respond_core(msgvec
);
421 if (value(ENAME_REPLYALL
) == NULL
)
422 return Respond_core(msgvec
);
424 return respond_core(msgvec
);
429 forward_one(int msgno
, struct name
*h_to
)
431 struct attachment attach
;
435 mp
= get_message(msgno
);
437 (void)printf("no such message %d\n", msgno
);
440 (void)printf("message %d\n", msgno
);
442 (void)memset(&attach
, 0, sizeof(attach
));
443 attach
.a_type
= ATTACH_MSG
;
445 attach
.a_Content
= get_mime_content(&attach
, 0);
447 (void)memset(&hdr
, 0, sizeof(hdr
));
449 if ((hdr
.h_subject
= hfield("subject", mp
)) == NULL
)
450 hdr
.h_subject
= hfield("subj", mp
);
451 hdr
.h_subject
= reedit(hdr
.h_subject
, "Fwd:");
452 hdr
.h_attach
= &attach
;
453 hdr
.h_smopts
= set_smopts(mp
);
455 set_ident_fields(&hdr
, mp
);
468 if (forwardtab
[0].i_count
== 0) {
469 /* setup the forward tab */
470 add_ignore("Status", forwardtab
);
472 (void)memset(&hdr
, 0, sizeof(hdr
));
473 if ((rval
= grabh(&hdr
, GTO
)) != 0)
476 if (hdr
.h_to
== NULL
) {
477 (void)printf("address missing!\n");
480 for (ip
= msgvec
; *ip
; ip
++) {
482 if ((e
= forward_one(*ip
, hdr
.h_to
)) != 0)
487 #endif /* MIME_SUPPORT */
490 bounce_one(int msgno
, const char **smargs
, struct name
*h_to
)
492 char mailtempname
[PATHSIZE
];
501 (void)snprintf(mailtempname
, sizeof(mailtempname
),
502 "%s/mail.RsXXXXXXXXXX", tmpdir
);
503 if ((fd
= mkstemp(mailtempname
)) == -1 ||
504 (obuf
= Fdopen(fd
, "w+")) == NULL
) {
507 warn("%s", mailtempname
);
511 (void)rm(mailtempname
);
513 mp
= get_message(msgno
);
516 (void)printf("no such message %d\n", msgno
);
527 * Construct and output a new "To:" field:
528 * Remove our address from anything in the old "To:" field
529 * and append that list to the bounce address(es).
532 if ((cp
= skin(hfield("to", mp
))) != NULL
)
533 np
= extract(cp
, GTO
);
534 np
= delname(np
, myname
);
536 for (ap
= altnames
; *ap
; ap
++)
537 np
= delname(np
, *ap
);
539 (void)memset(&hdr
, 0, sizeof(hdr
));
540 hdr
.h_to
= elide(np
);
541 (void)puthead(&hdr
, obuf
, GTO
| GCOMMA
);
543 if (sendmessage(mp
, obuf
, bouncetab
, NULL
, NULL
)) {
544 (void)printf("bounce failed for message %d\n", msgno
);
548 rewind(obuf
); /* XXX - here or inside mail2() */
566 if (bouncetab
[0].i_count
== 0) {
567 /* setup the bounce tab */
568 add_ignore("Status", bouncetab
);
569 add_ignore("Delivered-To", bouncetab
);
570 add_ignore("To", bouncetab
);
571 add_ignore("X-Original-To", bouncetab
);
573 (void)memset(&hdr
, 0, sizeof(hdr
));
574 if ((rval
= grabh(&hdr
, GTO
)) != 0)
577 if (hdr
.h_to
== NULL
)
580 smargs
= unpack(hdr
.h_to
);
581 for (ip
= msgvec
; *ip
; ip
++) {
583 if ((e
= bounce_one(*ip
, smargs
, hdr
.h_to
)) != 0)
590 * Preserve the named messages, so that they will be sent
591 * back to the system mailbox.
601 (void)printf("Cannot \"preserve\" in edit mode\n");
604 for (ip
= msgvec
; *ip
!= 0; ip
++)
605 dot
= set_m_flag(*ip
, ~(MBOX
| MPRESERVE
), MPRESERVE
);
611 * Mark all given messages as unread, preserving the new status.
620 for (ip
= msgvec
; *ip
!= 0; ip
++)
621 dot
= set_m_flag(*ip
, ~(MREAD
| MTOUCH
| MSTATUS
), MSTATUS
);
627 * Mark all given messages as read.
636 for (ip
= msgvec
; *ip
!= 0; ip
++)
637 dot
= set_m_flag(*ip
,
638 ~(MNEW
| MTOUCH
| MREAD
| MSTATUS
), MREAD
| MSTATUS
);
644 * Print the size of each message.
654 for (ip
= msgvec
; *ip
!= 0; ip
++) {
656 mp
= get_message(mesg
);
657 (void)printf("%d: %ld/%llu\n", mesg
, mp
->m_blines
,
658 (unsigned long long)mp
->m_size
);
664 * Quit quickly. If we are sourcing, just pop the input level
665 * by returning an error.
669 rexit(void *v __unused
)
678 * Set or display a variable value. Syntax is similar to that
684 const char **arglist
= v
;
687 char varbuf
[LINESIZE
];
688 const char **ap
, **p
;
692 if (*arglist
== NULL
) {
693 for (h
= 0, s
= 1; h
< HSHSIZE
; h
++)
694 for (vp
= variables
[h
]; vp
!= NULL
; vp
= vp
->v_link
)
696 ap
= salloc(s
* sizeof(*ap
));
697 for (h
= 0, p
= ap
; h
< HSHSIZE
; h
++)
698 for (vp
= variables
[h
]; vp
!= NULL
; vp
= vp
->v_link
)
702 for (p
= ap
; *p
!= NULL
; p
++)
703 (void)printf("%s\t%s\n", *p
, value(*p
));
707 for (ap
= arglist
; *ap
!= NULL
; ap
++) {
709 while (*cp
!= '=' && *cp
!= '\0')
712 if (l
>= sizeof(varbuf
))
713 l
= sizeof(varbuf
) - 1;
714 (void)strncpy(varbuf
, *ap
, l
);
720 if (equal(varbuf
, "")) {
721 (void)printf("Non-null variable name required\n");
731 * Unset a bunch of variable values.
737 struct var
*vp
, *vp2
;
742 for (ap
= arglist
; *ap
!= NULL
; ap
++) {
743 if ((vp2
= lookup(*ap
)) == NULL
) {
746 } else if (!sourcing
) {
747 (void)printf("\"%s\": undefined variable\n", *ap
);
753 if (vp2
== variables
[h
]) {
754 variables
[h
] = variables
[h
]->v_link
;
756 v_free(vp2
->v_value
);
760 for (vp
= variables
[h
]; vp
->v_link
!= vp2
; vp
= vp
->v_link
)
762 vp
->v_link
= vp2
->v_link
;
764 v_free(vp2
->v_value
);
771 * Show a variable value.
776 const char **arglist
= v
;
778 const char **ap
, **p
;
781 if (*arglist
== NULL
) {
782 for (h
= 0, s
= 1; h
< HSHSIZE
; h
++)
783 for (vp
= variables
[h
]; vp
!= NULL
; vp
= vp
->v_link
)
785 ap
= salloc(s
* sizeof(*ap
));
786 for (h
= 0, p
= ap
; h
< HSHSIZE
; h
++)
787 for (vp
= variables
[h
]; vp
!= NULL
; vp
= vp
->v_link
)
791 for (p
= ap
; *p
!= NULL
; p
++)
792 (void)printf("%s=%s\n", *p
, value(*p
));
796 for (ap
= arglist
; *ap
!= NULL
; ap
++) {
797 char *val
= value(*ap
);
798 (void)printf("%s=%s\n", *ap
, val
? val
: "<null>");
805 * Put add users to a group.
810 const char **argv
= v
;
811 struct grouphead
*gh
;
816 const char **ap
, **p
;
819 for (h
= 0, s
= 1; h
< HSHSIZE
; h
++)
820 for (gh
= groups
[h
]; gh
!= NULL
; gh
= gh
->g_link
)
822 ap
= salloc(s
* sizeof(*ap
));
823 for (h
= 0, p
= ap
; h
< HSHSIZE
; h
++)
824 for (gh
= groups
[h
]; gh
!= NULL
; gh
= gh
->g_link
)
828 for (p
= ap
; *p
!= NULL
; p
++)
832 if (argv
[1] == NULL
) {
838 if ((gh
= findgroup(gname
)) == NULL
) {
839 gh
= ecalloc(1, sizeof(*gh
));
840 gh
->g_name
= vcopy(gname
);
842 gh
->g_link
= groups
[h
];
847 * Insert names from the command list into the group.
848 * Who cares if there are duplicates? They get tossed
852 for (ap
= argv
+ 1; *ap
!= NULL
; ap
++) {
853 gp
= ecalloc(1, sizeof(*gp
));
854 gp
->ge_name
= vcopy(*ap
);
855 gp
->ge_link
= gh
->g_list
;
862 * Delete the named group alias. Return zero if the group was
863 * successfully deleted, or -1 if there was no such group.
866 delgroup(const char *name
)
868 struct grouphead
*gh
, *p
;
873 for (gh
= groups
[h
], p
= NULL
; gh
!= NULL
; p
= gh
, gh
= gh
->g_link
)
874 if (strcmp(gh
->g_name
, name
) == 0) {
876 groups
[h
] = gh
->g_link
;
878 p
->g_link
= gh
->g_link
;
879 while (gh
->g_list
!= NULL
) {
881 gh
->g_list
= g
->ge_link
;
893 * The unalias command takes a list of alises
894 * and discards the remembered groups of users.
901 for (ap
= v
; *ap
!= NULL
; ap
++)
907 * The do nothing command for comments.
911 null(void *v __unused
)
917 * Change to another file. With no argument, print information about
925 if (argv
[0] == NULL
) {
926 (void)newfileinfo(0);
929 if (setfile(*argv
) < 0)
937 * Expand file names like echo
946 for (ap
= argv
; *ap
!= NULL
; ap
++) {
948 if ((cp
= expand(cp
)) != NULL
) {
951 (void)printf("%s", cp
);
959 * Routines to push and pop the condition code to support nested
960 * if/else/endif statements.
963 push_cond(int c_cond
)
965 struct cond_stack_s
*csp
;
966 csp
= emalloc(sizeof(*csp
));
967 csp
->c_cond
= c_cond
;
968 csp
->c_next
= cond_stack
;
976 struct cond_stack_s
*csp
;
978 if ((csp
= cond_stack
) == NULL
)
981 c_cond
= csp
->c_cond
;
982 cond_stack
= csp
->c_next
;
988 * Conditional commands. These allow one to parameterize one's
989 * .mailrc and do some things if sending, others if receiving.
996 if ((cond
& (CIF
| CSKIP
)) == (CIF
| CSKIP
)) {
1007 char *keyword
= argv
[0];
1008 static const struct modetbl_s
{
1010 enum mailmode_e m_mode
;
1012 { "receiving", mm_receiving
},
1013 { "sending", mm_sending
},
1014 { "headersonly", mm_hdrsonly
},
1017 const struct modetbl_s
*mtp
;
1023 for (mtp
= modetbl
; mtp
->m_name
; mtp
++)
1024 if (strcasecmp(keyword
, mtp
->m_name
) == 0)
1027 if (mtp
->m_name
== NULL
) {
1029 (void)printf("Unrecognized if-keyword: \"%s\"\n", keyword
);
1032 if (mtp
->m_mode
!= mailmode
)
1047 if (value(argv
[0]) == NULL
)
1063 * Implement 'else'. This is pretty simple -- we just
1064 * flip over the conditional flag.
1068 elsecmd(void *v __unused
)
1070 if (cond_stack
== NULL
|| (cond
& (CIF
| CELSE
)) != CIF
) {
1071 (void)printf("\"else\" without matching \"if\"\n");
1075 if ((cond
& CIGN
) == 0) {
1083 * End of if statement. Just set cond back to anything.
1087 endifcmd(void *v __unused
)
1089 if (cond_stack
== NULL
|| (cond
& CIF
) != CIF
) {
1090 (void)printf("\"endif\" without matching \"if\"\n");
1099 * Set the list of alternate names.
1104 char **namelist
= v
;
1106 char **ap
, **ap2
, *cp
;
1108 c
= argcount(namelist
) + 1;
1112 for (ap
= altnames
; *ap
; ap
++)
1113 (void)printf("%s ", *ap
);
1119 altnames
= ecalloc(c
, sizeof(char *));
1120 for (ap
= namelist
, ap2
= altnames
; *ap
; ap
++, ap2
++) {
1121 cp
= ecalloc(strlen(*ap
) + 1, sizeof(char));
1122 (void)strcpy(cp
, *ap
);