1 /*
2  * Copyright (c) 1993 Eric P. Allman
3  * Copyright (c) 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * %sccs.include.redist.c%
7  */
8 
9 #ifndef lint
10 static char sccsid[] = "@(#)smrsh.c	8.2 (Berkeley) 03/08/95";
11 #endif /* not lint */
12 
13 /*
14 **  SMRSH -- sendmail restricted shell
15 **
16 **	This is a patch to get around the prog mailer bugs in most
17 **	versions of sendmail.
18 **
19 **	Use this in place of /bin/sh in the "prog" mailer definition
20 **	in your sendmail.cf file.  You then create CMDDIR (owned by
21 **	root, mode 755) and put links to any programs you want
22 **	available to prog mailers in that directory.  This should
23 **	include things like "vacation" and "procmail", but not "sed"
24 **	or "sh".
25 **
26 **	Leading pathnames are stripped from program names so that
27 **	existing .forward files that reference things like
28 **	"/usr/ucb/vacation" will continue to work.
29 **
30 **	The following characters are completely illegal:
31 **		<  >  |  ^  ;  &  $  `  (  ) \n \r
32 **	This is more restrictive than strictly necessary.
33 **
34 **	To use this, edit /etc/sendmail.cf, search for ^Mprog, and
35 **	change P=/bin/sh to P=/usr/etc/smrsh, where this compiled
36 **	binary is installed /usr/etc/smrsh.
37 **
38 **	This can be used on any version of sendmail.
39 **
40 **	In loving memory of RTM.  11/02/93.
41 */
42 
43 #include <unistd.h>
44 #include <stdio.h>
45 #include <sys/file.h>
46 #include <string.h>
47 #include <ctype.h>
48 #include <sysexits.h>
49 #include <syslog.h>
50 
51 /* directory in which all commands must reside */
52 #ifndef CMDDIR
53 # define CMDDIR		"/usr/adm/sm.bin"
54 #endif
55 
56 /* characters disallowed in the shell "-c" argument */
57 #define SPECIALS	"<|>^();&`$\r\n"
58 
59 /* default search path */
60 #ifndef PATH
61 # define PATH		"/bin:/usr/bin:/usr/ucb"
62 #endif
63 
64 main(argc, argv)
65 	int argc;
66 	char **argv;
67 {
68 	register char *p;
69 	register char *q;
70 	register char *cmd;
71 	int i;
72 	char *newenv[2];
73 	char cmdbuf[1000];
74 	char pathbuf[1000];
75 
76 #ifndef LOG_MAIL
77 	openlog("smrsh", 0);
78 #else
79 	openlog("smrsh", LOG_ODELAY|LOG_CONS, LOG_MAIL);
80 #endif
81 
82 	strcpy(pathbuf, "PATH=");
83 	strcat(pathbuf, PATH);
84 	newenv[0] = pathbuf;
85 	newenv[1] = NULL;
86 
87 	/*
88 	**  Do basic argv usage checking
89 	*/
90 
91 	if (argc != 3 || strcmp(argv[1], "-c") != 0)
92 	{
93 		fprintf(stderr, "Usage: %s -c command\n", argv[0]);
94 		syslog(LOG_ERR, "usage");
95 		exit(EX_USAGE);
96 	}
97 
98 	/*
99 	**  Disallow special shell syntax.  This is overly restrictive,
100 	**  but it should shut down all attacks.
101 	**  Be sure to include 8-bit versions, since many shells strip
102 	**  the address to 7 bits before checking.
103 	*/
104 
105 	strcpy(cmdbuf, SPECIALS);
106 	for (p = cmdbuf; *p != '\0'; p++)
107 		*p |= '\200';
108 	strcat(cmdbuf, SPECIALS);
109 	p = strpbrk(argv[2], cmdbuf);
110 	if (p != NULL)
111 	{
112 		fprintf(stderr, "%s: cannot use %c in command\n",
113 			argv[0], *p);
114 		syslog(LOG_CRIT, "uid %d: attempt to use %c in command: %s",
115 			getuid(), *p, argv[2]);
116 		exit(EX_UNAVAILABLE);
117 	}
118 
119 	/*
120 	**  Do a quick sanity check on command line length.
121 	*/
122 
123 	i = strlen(argv[2]);
124 	if (i > (sizeof cmdbuf - sizeof CMDDIR - 2))
125 	{
126 		fprintf(stderr, "%s: command too long: %s\n", argv[0], argv[2]);
127 		syslog(LOG_WARNING, "command too long: %.40s", argv[2]);
128 		exit(EX_UNAVAILABLE);
129 	}
130 
131 	/*
132 	**  Strip off a leading pathname on the command name.  For
133 	**  example, change /usr/ucb/vacation to vacation.
134 	*/
135 
136 	/* strip leading spaces */
137 	for (q = argv[2]; *q != '\0' && isascii(*q) && isspace(*q); )
138 		q++;
139 
140 	/* find the end of the command name */
141 	p = strpbrk(q, " \t");
142 	if (p == NULL)
143 		cmd = &q[strlen(q)];
144 	else
145 	{
146 		*p = '\0';
147 		cmd = p;
148 	}
149 
150 	/* search backwards for last / (allow for 0200 bit) */
151 	while (cmd > q)
152 	{
153 		if ((*--cmd & 0177) == '/')
154 		{
155 			cmd++;
156 			break;
157 		}
158 	}
159 
160 	/* cmd now points at final component of path name */
161 
162 	/*
163 	**  Check to see if the command name is legal.
164 	*/
165 
166 	(void) strcpy(cmdbuf, CMDDIR);
167 	(void) strcat(cmdbuf, "/");
168 	(void) strcat(cmdbuf, cmd);
169 #ifdef DEBUG
170 	printf("Trying %s\n", cmdbuf);
171 #endif
172 	if (access(cmdbuf, X_OK) < 0)
173 	{
174 		/* oops....  crack attack possiblity */
175 		fprintf(stderr, "%s: %s not available for sendmail programs\n",
176 			argv[0], cmd);
177 		if (p != NULL)
178 			*p = ' ';
179 		syslog(LOG_CRIT, "uid %d: attempt to use %s", getuid(), cmd);
180 		exit(EX_UNAVAILABLE);
181 	}
182 	if (p != NULL)
183 		*p = ' ';
184 
185 	/*
186 	**  Create the actual shell input.
187 	*/
188 
189 	strcpy(cmdbuf, CMDDIR);
190 	strcat(cmdbuf, "/");
191 	strcat(cmdbuf, cmd);
192 
193 	/*
194 	**  Now invoke the shell
195 	*/
196 
197 #ifdef DEBUG
198 	printf("%s\n", cmdbuf);
199 #endif
200 	execle("/bin/sh", "/bin/sh", "-c", cmdbuf, NULL, newenv);
201 	syslog(LOG_CRIT, "Cannot exec /bin/sh: %m");
202 	perror("/bin/sh");
203 	exit(EX_OSFILE);
204 }
205