1 /*
2 Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, 2011, 2013 Olivier Sessink
3 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   * Redistributions of source code must retain the above copyright
9     notice, this list of conditions and the following disclaimer.
10   * Redistributions in binary form must reproduce the above
11     copyright notice, this list of conditions and the following
12     disclaimer in the documentation and/or other materials provided
13     with the distribution.
14   * The names of its contributors may not be used to endorse or
15     promote products derived from this software without specific
16     prior written permission.
17 
18 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
21 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
22 COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
23 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24 BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
27 LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
28 ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 /*#define DEBUG*/
33 #include "config.h"
34 
35 #include <string.h>
36 #include <ctype.h>
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <syslog.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <errno.h>
44 
45 #include "jk_lib.h"
46 #include "utils.h"
47 
file_exists(const char * path)48 int file_exists(const char *path) {
49 	/* where is this function used? access() is more light than stat() but it
50 	does not equal a 'file exist', but 'file exists and can be accessed' */
51 	struct stat sb;
52 	if (stat(path, &sb) == -1 && errno == ENOENT) {
53 		return 0;
54 	}
55 	if (!S_ISREG(sb.st_mode)) {
56 		return 0;
57 	}
58 	return 1;
59 }
60 
61 /* creates a string from an array of strings, with the delimiter inbetween
62 use arrlen -1 if the array is NULL terminated.
63 the strings should all be '\0' terminated*/
implode_array(char ** arr,int arrlen,const char * delimiter)64 char *implode_array(char **arr, int arrlen, const char *delimiter) {
65 	int count=0,i=0,reqsize=1, delsize=strlen(delimiter);
66 	char **tmp = arr;
67 	char *retval;
68 	/* find required memory length */
69 	while (*tmp && (count != arrlen)) {
70 		count++;
71 		reqsize += delsize + strlen(*tmp);
72 		tmp++;
73 	}
74 	retval = malloc(reqsize*sizeof(char));
75 	retval[0] = '\0';
76 	for (i=0;i<count;i++) {
77 		DEBUG_MSG("apending %s\n",arr[i]);
78 		if (i != 0) {
79 			retval = strcat(retval, delimiter);
80 		}
81 		retval = strcat(retval, arr[i]);
82 
83 	}
84 	return retval;
85 }
86 
ending_slash(const char * src)87 char *ending_slash(const char *src) {
88 	int len;
89 	if (!src) return NULL;
90 	len = strlen(src);
91 	if (src[len-1] == '/') {
92 		return strdup(src);
93 	} else {
94 		return strcat(strcat(malloc0((len+2)*sizeof(char)), src), "/");
95 	}
96 }
97 
98 
99 /*
100  * the path should be owned owner:group
101  * if it is a file it should not have any setuid or setgid bits set
102  * it should not be writable for group or others
103  * it should not be a symlink
104  */
testsafepath(const char * path,int owner,int group)105 int testsafepath(const char *path, int owner, int group) {
106 	struct stat sbuf;
107 	int retval=0;
108 	DEBUG_MSG("testsafepath %s\n",path);
109 	if (lstat(path, &sbuf) != 0) {
110 		return TESTPATH_NOREGPATH;
111 	}
112 	DEBUG_MSG("%s has mode %d  and owned %d:%d\n",path,sbuf.st_mode,sbuf.st_uid,sbuf.st_gid);
113 	if (S_ISLNK(sbuf.st_mode)) {
114 		syslog(LOG_ERR, "path %s is a symlink", path);
115 		retval |= TESTPATH_NOREGPATH;
116 	}
117 	if (sbuf.st_mode & S_ISUID) {
118 		syslog(LOG_ERR, "path %s is setuid", path);
119 		retval |= TESTPATH_SETUID;
120 	}
121 	if (sbuf.st_mode & S_ISGID) {
122 		syslog(LOG_ERR, "path %s is setgid", path);
123 		retval |= TESTPATH_SETGID;
124 	}
125 	if (sbuf.st_mode & S_IWGRP) {
126 		syslog(group==0 ? LOG_WARNING : LOG_NOTICE, "path %s is group writable", path);
127 		retval |= TESTPATH_GROUPW;
128 	}
129 	if (sbuf.st_mode & S_IWOTH) {
130 		syslog(owner==0 ? LOG_ERR : LOG_NOTICE, "path %s is writable for others", path);
131 		retval |= TESTPATH_OTHERW;
132 	}
133 	if (sbuf.st_uid != owner){
134 		syslog(owner==0 ? LOG_ERR : LOG_NOTICE, "path %s is not owned by user %d", path, owner);
135 		retval |= TESTPATH_OWNER;
136 	}
137 	if (sbuf.st_gid != group){
138 		syslog(group==0 ? LOG_ERR : LOG_NOTICE, "path %s is not owned by group %d", path, group);
139 		retval |= TESTPATH_GROUP;
140 	}
141 	return retval;
142 }
143 
basicjailissafe(const char * path)144 int basicjailissafe(const char *path) {
145 	char *tmp, *path_w_slash;
146 	int retval = 1;
147 	if (path && testsafepath(path, 0, 0) !=0) {
148 		return 0;
149 	}
150 	path_w_slash = ending_slash(path);
151 	tmp = malloc0(strlen(path_w_slash)+6);
152 	if (retval == 1 && (testsafepath(strcat(strcpy(tmp,path_w_slash), "dev/"), 0, 0) &~TESTPATH_NOREGPATH)!=0) retval = 0;
153 	if (retval == 1 && (testsafepath(strcat(strcpy(tmp,path_w_slash), "etc/"), 0, 0) &~TESTPATH_NOREGPATH)!=0) retval = 0;
154 	if (retval == 1 && (testsafepath(strcat(strcpy(tmp,path_w_slash), "lib/"), 0, 0) &~TESTPATH_NOREGPATH)!=0) retval = 0;
155 	if (retval == 1 && (testsafepath(strcat(strcpy(tmp,path_w_slash), "usr/"), 0, 0) &~TESTPATH_NOREGPATH)!=0) retval = 0;
156 	if (retval == 1 && (testsafepath(strcat(strcpy(tmp,path_w_slash), "bin/"), 0, 0) &~TESTPATH_NOREGPATH)!=0) retval = 0;
157 	if (retval == 1 && (testsafepath(strcat(strcpy(tmp,path_w_slash), "sbin/"), 0, 0)&~TESTPATH_NOREGPATH)!=0) retval = 0;
158 	free(tmp);
159 	free(path_w_slash);
160 	DEBUG_MSG("basicjailissafe, returning %d\n",retval);
161 	return retval;
162 }
163 
164 /* this function can handle differences in ending slash */
dirs_equal(const char * dir1,const char * dir2)165 int dirs_equal(const char *dir1, const char *dir2) {
166 	int d1len, d2len;
167 	d1len = strlen(dir1);
168 	d2len = strlen(dir2);
169 	DEBUG_MSG("dirs_equal, testing %s and %s\n",dir1,dir2);
170 	if (d1len == d2len) {
171 		return (strcmp(dir1,dir2)==0);
172 	} else if (d1len == d2len-1 && dir1[d1len-1]!='/' && dir2[d2len-1]=='/') {
173 		return (strncmp(dir1,dir2,d1len)==0);
174 	} else if (d1len-1 == d2len && dir1[d1len-1]=='/' && dir2[d2len-1]!='/') {
175 		return (strncmp(dir1,dir2,d2len)==0);
176 	}
177 	return 0;
178 }
179 
180 /* if it returns 1 it will allocate new memory for jaildir and newhomedir
181  * else it will return 0
182  */
getjaildir(const char * oldhomedir,char ** jaildir,char ** newhomedir)183 int getjaildir(const char *oldhomedir, char **jaildir, char **newhomedir) {
184 	int i=strlen(oldhomedir);
185 	/* we will not accept /./ as jail, so we continue looking while i > 4 (minimum then is /a/./ )
186 	 * we start at the end so if there are multiple /path/./path2/./path3 the user will be jailed in the most minimized path
187 	 */
188 	while (i > 4) {
189 /*		DEBUG_MSG("oldhomedir[%d]=%c\n",i,oldhomedir[i]);*/
190 		if (oldhomedir[i] == '/' && oldhomedir[i-1] == '.' && oldhomedir[i-2] == '/') {
191 			DEBUG_MSG("&oldhomedir[%d]=%s\n",i,&oldhomedir[i]);
192 			*jaildir = strndup(oldhomedir, i-2);
193 			*newhomedir = strdup(&oldhomedir[i]);
194 			return 1;
195 		}
196 		i--;
197 	}
198 	return 0;
199 }
200 
strip_string(char * string)201 char *strip_string(char * string) {
202 	int numstartspaces=0, endofcontent=strlen(string)-1;
203 	while (isspace(string[numstartspaces]) && numstartspaces < endofcontent)
204 		numstartspaces++;
205 	while (isspace(string[endofcontent]) && endofcontent > numstartspaces)
206 		endofcontent--;
207 	if (numstartspaces != 0)
208 		memmove(string, &string[numstartspaces], (endofcontent - numstartspaces+1)*sizeof(char));
209 	string[(endofcontent - numstartspaces+1)] = '\0';
210 	return string;
211 }
212 
count_char(const char * string,char lookfor)213 int count_char(const char *string, char lookfor) {
214 	int count=0;
215 	while (*string != '\0') {
216 		if (*string == lookfor)
217 			count++;
218 		string++;
219 	}
220 	DEBUG_LOG("count_char, returning %d\n",count);
221 	return count;
222 }
223 
explode_string(const char * string,char delimiter)224 char **explode_string(const char *string, char delimiter) {
225 	char **arr;
226 	const char *tmp = string;
227 	int cur= 0;
228 	int size = ((count_char(string, delimiter) + 2)*sizeof(char*));
229 	arr = malloc(size);
230 
231 	DEBUG_LOG("exploding string '%s', arr=%p with size %d, sizeof(char*)=%d\n",string,arr,size,sizeof(char*));
232 
233 	while (tmp) {
234 		char *tmp2 = strchr(tmp, delimiter);
235 		if (tmp2) {
236 			arr[cur] = strip_string(strndup(tmp, (tmp2-tmp)));
237 		} else {
238 			arr[cur] = strip_string(strdup(tmp));
239 		}
240 		if (strlen(arr[cur])==0) {
241 			free(arr[cur]);
242 		} else {
243 			DEBUG_LOG("found string '%s' at %p\n",arr[cur], arr[cur]);
244 			cur++;
245 		}
246 		tmp = (tmp2) ? tmp2+1 : NULL;
247 	}
248 	arr[cur] = NULL;
249 	DEBUG_MSG("exploding string, returning %p\n",arr);
250 	return arr;
251 }
252 
count_array(char ** arr)253 int count_array(char **arr) {
254 	char **tmp = arr;
255 	DEBUG_MSG("count_array, started for %p\n",arr);
256 	while (*tmp)
257 		tmp++;
258 	return (tmp-arr);
259 }
260 
free_array(char ** arr)261 void free_array(char **arr) {
262 	char **tmp = arr;
263 	if (!arr) return;
264 	while (*tmp) {
265 		free(*tmp);
266 		tmp++;
267 	}
268 	free(arr);
269 }
270