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