1 /*
2  *
3  * jk_procmailwrapper
4  * this program will simply execute procmail for users that are not in a jail
5  * and it will exit() for users that are in a jail (mail will *not* be delivered)
6  *
7  * this will probably extended in the near future
8  *
9 Copyright (c) 2003, 2004, 2005, 2006 Olivier Sessink
10 All rights reserved.
11 
12 Redistribution and use in source and binary forms, with or without
13 modification, are permitted provided that the following conditions
14 are met:
15   * Redistributions of source code must retain the above copyright
16     notice, this list of conditions and the following disclaimer.
17   * Redistributions in binary form must reproduce the above
18     copyright notice, this list of conditions and the following
19     disclaimer in the documentation and/or other materials provided
20     with the distribution.
21   * The names of its contributors may not be used to endorse or
22     promote products derived from this software without specific
23     prior written permission.
24 
25 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
35 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36 POSSIBILITY OF SUCH DAMAGE.
37 */
38 /* #define DEBUG */
39 
40 #include "config.h"
41 
42 #include <string.h>
43 #include <stdio.h>
44 #include <pwd.h>
45 #include <sys/types.h>
46 #include <unistd.h>
47 #include <stdlib.h>
48 #include <syslog.h>
49 #include <errno.h>
50 #include <grp.h>
51 
52 #include "jk_lib.h"
53 
54 #define PROGRAMNAME "jk_procmailwrapper"
55 
user_is_chrooted(const char * homedir)56 int user_is_chrooted(const char *homedir) {
57 	char *tmp;
58 	tmp = strstr(homedir, "/./");
59 	if (tmp != NULL) {
60 		return 1;
61 	}
62 	return 0;
63 }
64 
clean_exit(char * name,int error)65 void clean_exit(char * name, int error) {
66 	printf("%s, exiting with error %d\n", name, error);
67 	exit(error);
68 }
69 
main(int argc,char ** argv,char ** envp)70 int main (int argc, char **argv, char **envp) {
71 	int i;
72 	struct passwd *pw=NULL;
73 	struct group *gr=NULL;
74 	char *jaildir=NULL, *newhome=NULL;
75 
76 	DEBUG_MSG(PROGRAMNAME", started\n");
77 	pw = getpwuid(getuid());
78 	if (!user_is_chrooted(pw->pw_dir)) {
79 		/* if the user does not have a chroot homedir, we start the normal procmail now,
80 		but first we drop all privileges */
81 		if (setgid(getgid())) {
82 			syslog(LOG_ERR, "abort, failed to become gid %d", getgid());
83 			exit(34);
84 		}
85 		if (initgroups(pw->pw_name, getgid())) {
86 			syslog(LOG_ERR, "abort, failed to initgroups for user %s and group %d", pw->pw_name, getgid());
87 			exit(35);
88 		}
89 		if (setuid(getuid())) {
90 			syslog(LOG_ERR, "abort, failed to become uid %d", getuid());
91 			exit(36);
92 		}
93 		execve(PROCMAILPATH, argv, envp);
94 		/* if we get here, there is something wrong */
95 		exit(1);
96 	}
97 	/* OK, so the user is a jailed user, now we start checking things!! */
98 
99 	/* open the log facility */
100 	openlog(PROGRAMNAME, LOG_PID, LOG_AUTH);
101 
102 	/* check if it us that the user wants */
103 	{
104 		char *tmp = strrchr(argv[0], '/');
105 		if (!tmp) {
106 			tmp = argv[0];
107 		} else {
108 			tmp++;
109 		}
110 		if (strcmp(tmp, PROGRAMNAME) && (tmp[0] != '-' || strcmp(&tmp[1], PROGRAMNAME))) {
111 			DEBUG_MSG("wrong name, tmp=%s, &tmp[1]=%s\n", tmp, &tmp[1]);
112 			syslog(LOG_ERR, "abort, "PROGRAMNAME" is called as %s", argv[0]);
113 			exit(1);
114 		}
115 	}
116 	DEBUG_MSG("close filedescriptors\n");
117 	/* open file descriptors can be used to break out of a chroot, so we close all of them, except for stdin,stdout and stderr */
118 	for (i=getdtablesize();i>3;i--) {
119 		while (close(i) != 0 && errno == EINTR);
120 	}
121 
122 	/* now test if we are setuid root (the effective user id must be 0, and the real user id > 0 */
123 	if (geteuid() != 0 || getuid() == 0) {
124 		syslog(LOG_ERR, "abort, "PROGRAMNAME" is not setuid root, or is run by root");
125 		exit(11);
126 	}
127 
128 	DEBUG_MSG("get user info\n");
129 	gr = getgrgid(getgid());
130 	if (!pw || !gr) {
131 		syslog(LOG_ERR, "abort, failed to get user or group information for %d:%d", getuid(), getgid());
132 		exit(13);
133 	}
134 
135 	/* now we clear the environment */
136 	clearenv();
137 
138 	if (pw->pw_gid != getgid()) {
139 		syslog(LOG_ERR, "abort, the group ID from /etc/passwd (%d) does not match the group ID we run with (%d)", pw->pw_gid, getgid());
140 		exit(15);
141 	}
142 	if (!pw->pw_dir || strlen(pw->pw_dir) ==0 || strstr(pw->pw_dir, "/./") == NULL) {
143 		syslog(LOG_ERR, "abort, the homedir %s does not contain the jail pattern path/./path", pw->pw_dir?pw->pw_dir:"NULL");
144 		exit(17);
145 	}
146 	DEBUG_MSG("get jaildir\n");
147 	if (!getjaildir(pw->pw_dir, &jaildir, &newhome)) {
148 		syslog(LOG_ERR, "abort, failed to read the jail and the home from %s",pw->pw_dir);
149 		exit(17);
150 	}
151 	DEBUG_MSG("get chdir()\n");
152 	if (chdir(jaildir) != 0) {
153 		syslog(LOG_ERR, "abort, failed to chdir() to %s",jaildir);
154 		exit(19);
155 	} else {
156 		/* test if it really succeeded */
157 		char *test = get_current_dir_name();
158 		if (strcmp(jaildir, test) != 0) {
159 			syslog(LOG_ERR, "abort, current dir != %s after chdir()",jaildir);
160 			exit(21);
161 		}
162 		free(test);
163 	}
164 
165 	/* here do test the ownership of the jail and the homedir and such
166 	the function testsafepath doe exit itself on any failure */
167 	{
168 		int ret;
169 		DEBUG_MSG("test paths\n");
170 		ret = testsafepath(jaildir,0,0);
171 		if (ret != 0) {
172 			syslog(LOG_ERR, "abort, path %s is not a safe jail, check ownership and permissions", jaildir);
173 			exit(53);
174 		}
175 		ret = testsafepath(pw->pw_dir, getuid(), getgid());
176 		if ((ret & TESTPATH_NOREGPATH) ) {
177 			syslog(LOG_ERR, "abort, path %s is not a directory", pw->pw_dir);
178 			exit(53);
179 		}
180 		if ((ret & TESTPATH_OWNER) ) {
181 			syslog(LOG_ERR, "abort, path %s is not owned by %d", pw->pw_dir,getuid());
182 			exit(53);
183 		}
184 	}
185 	/* do a final log message */
186 	syslog(LOG_INFO, "now entering jail %s for user %d", jaildir, getuid());
187 
188 	DEBUG_MSG("chroot()\n");
189 	/* do the chroot() call */
190 	if (chroot(jaildir)) {
191 		syslog(LOG_ERR, "abort, failed to chroot() to %s", jaildir);
192 		exit(33);
193 	}
194 
195 	/* drop all privileges, it seems that we first have to setgid(),
196 		then we have to call initgroups(),
197 		then we call setuid() */
198 	if (setgid(getgid())) {
199 		syslog(LOG_ERR, "abort, failed to become gid %d", getgid());
200 		exit(34);
201 	}
202 	if (initgroups(pw->pw_name, getgid())) {
203 		syslog(LOG_ERR, "abort, failed to initgroups for user %s and group %d", pw->pw_name, getgid());
204 		exit(35);
205 	}
206 	if (setuid(getuid())) {
207 		syslog(LOG_ERR, "abort, failed to become uid %d", getuid());
208 		exit(36);
209 	}
210 
211 	/* test for user and group info, is it the same? checks username, groupname and home */
212 	{
213 		char *oldpw_name,*oldgr_name;
214 		oldpw_name = strdup(pw->pw_name);
215 		oldgr_name = strdup(gr->gr_name);
216 
217 		pw = getpwuid(getuid());
218 		gr = getgrgid(getgid());
219 		if (!pw || !gr) {
220 			syslog(LOG_ERR, "abort, failed to get user and group information in the jail for %d:%d", getuid(), getgid());
221 			exit(35);
222 		}
223 		if (strcmp(pw->pw_name, oldpw_name)!=0 || strcmp(gr->gr_name, oldgr_name)!=0) {
224 			syslog(LOG_ERR, "abort, user or group names differ inside the jail for %d:%d", getuid(), getgid());
225 			exit(37);
226 		}
227 		if (strcmp(pw->pw_dir, newhome)!=0) {
228 			syslog(LOG_ERR, "abort, home directory is incorrect inside the jail for %d:%d", getuid(), getgid());
229 			exit(39);
230 		}
231 		free(oldpw_name);
232 		free(oldgr_name);
233 	}
234 
235 	/* test procmail in the jail, it is not allowed to be setuid() or setgid()
236 	it is common to have procmail setuid() root and setgid() mail in the regular
237 	system, but it is for most situations not required, and therefore very much
238 	not recommended inside a jail. So we will simply exit because it is a
239 	security risk */
240 	testsafepath(PROCMAILPATH,0,0);
241 
242 	/* prepare the new environment */
243 	setenv("HOME",newhome,1);
244 	setenv("USER",pw->pw_name,1);
245 	if (chdir(newhome) != 0) {
246 		syslog(LOG_ERR, "abort, failed to chdir() inside the jail to %s",newhome);
247 		exit(41);
248 	}
249 
250 	/* cleanup before execution */
251 	free(newhome);
252 	free(jaildir);
253 
254 	/* now execute the jailed shell */
255 	/*execl(pw->pw_shell, pw->pw_shell, NULL);*/
256 	{
257 		char **newargv;
258 		int i;
259 		newargv = malloc0((argc+1)*sizeof(char *));
260 		newargv[0] = PROCMAILPATH;
261 		for (i=1;i<argc;i++) {
262 			newargv[i] = argv[i];
263 		}
264 		execv(PROCMAILPATH, newargv);
265 	}
266 	DEBUG_MSG(strerror(errno));
267 	syslog(LOG_ERR, "WARNING: could not execute %s for user %d:%d",PROCMAILPATH,getuid(),getgid());
268 
269 	exit(111);
270 }
271