1 |
#include <sys/file.h> |
2 |
#include <sys/stat.h> |
3 |
#include <fcntl.h> |
4 |
#include <unistd.h> |
5 |
#include <stdlib.h> |
6 |
#include <string.h> |
7 |
|
8 |
#include "fileutil.h" |
9 |
#include "../safefile-1.0/safe_id_range_list.h" |
10 |
#include "../safefile-1.0/safe_is_path_trusted.h" |
11 |
|
12 |
static int priv_drop(uid_t unpriv_uid,gid_t unpriv_gid); |
13 |
static int raise_priv(uid_t euid, gid_t egid); |
14 |
|
15 |
int cgul_filelock(int fd, int lock_type, int action) { |
16 |
struct flock lck_struct; |
17 |
int rc1,rc2,lck; |
18 |
|
19 |
/* Can have multiple lock_types */ |
20 |
|
21 |
if (lock_type & LCK_NOLOCK) |
22 |
return 0; |
23 |
|
24 |
/* FLOCK */ |
25 |
if (lock_type & LCK_FLOCK) { |
26 |
switch (action) { /* Only one action at the time */ |
27 |
case LCK_READ: lck=LOCK_SH; break; |
28 |
case LCK_WRITE: lck=LOCK_EX; break; |
29 |
case LCK_UNLOCK: lck=LOCK_UN; break; |
30 |
default: return -1; |
31 |
} |
32 |
rc1=flock(fd, lck); |
33 |
} |
34 |
/* FCNTL */ |
35 |
if (lock_type & LCK_FCNTL) { |
36 |
switch (action) { /* Only one action at the time */ |
37 |
case LCK_READ: lck_struct.l_type=F_RDLCK; break; |
38 |
case LCK_WRITE: lck_struct.l_type=F_WRLCK; break; |
39 |
case LCK_UNLOCK: lck_struct.l_type=F_UNLCK; break; |
40 |
default: return -1; |
41 |
} |
42 |
lck_struct.l_whence=SEEK_SET; |
43 |
lck_struct.l_start=0; |
44 |
lck_struct.l_len=0; |
45 |
rc2=fcntl(fd,F_SETLK,&lck_struct); /* -1 error */ |
46 |
} |
47 |
return (rc1 || rc2 ? -1 : 0); |
48 |
} |
49 |
|
50 |
/** |
51 |
* drops privilege to an unprivileged account. When unpriv_uid and/or unpriv_gid |
52 |
* are negative, they will be ignored and the information is taken from the real |
53 |
* uid/gid (primary) combination |
54 |
* Returns 0 when successful. |
55 |
* */ |
56 |
int priv_drop(uid_t unpriv_uid,gid_t unpriv_gid) { |
57 |
/* drop priv when needed: euid==0, uid!=0 */ |
58 |
uid_t euid=geteuid(),uid; |
59 |
gid_t egid=getegid(),gid; |
60 |
int rc; |
61 |
|
62 |
if (euid!=0) /* Cannot do anything */ |
63 |
return 0; |
64 |
|
65 |
/* Drop group */ |
66 |
if (unpriv_gid>=0) |
67 |
rc=setegid(unpriv_gid); |
68 |
else { |
69 |
gid=getgid(); |
70 |
rc=(gid==0 || gid==egid ? 0 : setegid(gid) ); |
71 |
} |
72 |
/* If error: don't continue */ |
73 |
if (rc!=0) return rc; |
74 |
|
75 |
/* Drop user */ |
76 |
if (unpriv_uid>=0) |
77 |
rc=seteuid(unpriv_uid); |
78 |
else { |
79 |
uid=getuid(); |
80 |
rc=(uid==0 || uid==euid ? 0 : seteuid(uid) ); |
81 |
} |
82 |
if (rc!=0) { |
83 |
/* try restoring the euid */ |
84 |
setegid(egid); /* ignore rc of THIS process, it's damage control */ |
85 |
return rc; |
86 |
} |
87 |
return 0; |
88 |
} |
89 |
|
90 |
/** |
91 |
* Tries to raise privilege level back to euid/egid. |
92 |
* Return -1 when fails or impossible (neither euid or real uid is root), 0 |
93 |
* upon success. |
94 |
* */ |
95 |
int raise_priv(uid_t euid, gid_t egid) { |
96 |
uid_t uid=getuid(); |
97 |
|
98 |
/* reset euid/egid if: it was (effective) root or real user is root */ |
99 |
if (euid==0 || uid==0) { |
100 |
if (setegid(egid) || seteuid(euid)) |
101 |
return -1; |
102 |
else |
103 |
return 0; |
104 |
} |
105 |
return -1; |
106 |
} |
107 |
|
108 |
/** |
109 |
* Reads proxy from *path using given lock_type (see cgul_filelock). It tries to |
110 |
* drop privilege to real-uid/read_gid. Space needed will be malloc-ed. |
111 |
* Upon successful completion config contains the contents of path. |
112 |
* Return values: |
113 |
* 0: success |
114 |
* -1: I/O error |
115 |
* -2: privilege-drop error |
116 |
* -3: permissions error |
117 |
* -4: memory error |
118 |
* -5: too many retries needed during reading |
119 |
*/ |
120 |
int cgul_read_proxy(const char *path, int lock_type, char **proxy, gid_t read_gid) { |
121 |
const int tries=10; /* max number of retries for reading a changing file */ |
122 |
int i,fd,rc=0; |
123 |
struct stat st1,st2,*sptr1,*sptr2,*sptr3; |
124 |
uid_t uid=getuid(),euid=geteuid(); |
125 |
gid_t egid=getegid(); |
126 |
char *buf,*buf_new; /* *proxy will be updated when we know everything is ok */ |
127 |
ssize_t size; |
128 |
|
129 |
/* Drop privilege to real uid and special read group */ |
130 |
if (priv_drop(uid,read_gid)) |
131 |
return -2; |
132 |
/* Open file */ |
133 |
if ((fd=open(path,O_RDONLY))==-1) { |
134 |
raise_priv(euid,egid); return -1; |
135 |
} |
136 |
/* Lock file */ |
137 |
if (cgul_filelock(fd,lock_type,LCK_READ)) { |
138 |
close(fd); raise_priv(euid,egid); return -1; |
139 |
} |
140 |
/* Stat the file before reading: |
141 |
* Need ownership and mode for allowed values, size for malloc */ |
142 |
if (fstat(fd,&st1)) { |
143 |
close(fd); raise_priv(euid,egid); return -1; |
144 |
} |
145 |
/* Check we own it (only uid) and is unreadable/unwriteable for anyone else |
146 |
* */ |
147 |
if ( st1.st_uid!=uid || |
148 |
st1.st_mode & S_IRGRP || st1.st_mode & S_IWGRP || |
149 |
st1.st_mode & S_IROTH || st1.st_mode & S_IWOTH ) { |
150 |
close(fd); raise_priv(euid,egid); return -3; |
151 |
} |
152 |
/* Get expected space */ |
153 |
if ( (buf=(char *)malloc(st1.st_size))==NULL) { |
154 |
close(fd); raise_priv(euid,egid); return -4; |
155 |
} |
156 |
/* use pointers to the two so that we can swap them easily */ |
157 |
sptr1=&st1; sptr2=&st2; |
158 |
/* reading retry loop */ |
159 |
for (i=0; i<tries; i++) { |
160 |
/* Read file: if statted size changes, we will try again */ |
161 |
size=read(fd,buf,sptr1->st_size); |
162 |
/* Stat the file */ |
163 |
if (fstat(fd,sptr2)==-1) { /* cannot even stat: I/O error */ |
164 |
rc=-1; break; |
165 |
} |
166 |
/* Size, mtime and ctime should have stayed the same, especially ctime |
167 |
* is good as we can't change it with touch ! */ |
168 |
if ( sptr2->st_size == sptr1->st_size && /* size equal */ |
169 |
sptr2->st_mtime== sptr1->st_mtime && /* mtime equal */ |
170 |
sptr2->st_ctime== sptr1->st_ctime) { /* ctime equal */ |
171 |
/* Just check the return of the read, we might have an I/O error */ |
172 |
rc= (size==sptr1->st_size ? 0 : -1); |
173 |
break; |
174 |
} |
175 |
|
176 |
/* File has changed during reading: retry */ |
177 |
if (i<tries-1) { /* will be doing a retry */ |
178 |
if ( (buf_new=(char *)realloc(buf,sptr2->st_size))==NULL ) { |
179 |
rc=-4; break; |
180 |
} |
181 |
buf=buf_new; |
182 |
/* swap struct pointers */ |
183 |
sptr3=sptr2; sptr2=sptr1; sptr1=sptr3; |
184 |
/* wait */ |
185 |
usleep(500); |
186 |
/* About to read again, make sure we're (again) at the start */ |
187 |
if (lseek(fd,0,SEEK_SET)!=0) { /* I/O error */ |
188 |
rc=-1; break; |
189 |
} |
190 |
} else /* failed too many times */ |
191 |
rc=-5; |
192 |
} |
193 |
|
194 |
/* unlock and close the file, ignore exitval: we have read already */ |
195 |
cgul_filelock(fd,lock_type,LCK_UNLOCK); |
196 |
close(fd); |
197 |
/* reset euid/egid if it was (effective) root. Ignore exit value. */ |
198 |
raise_priv(euid,egid); |
199 |
/* finalize */ |
200 |
if (rc!=0) { |
201 |
free(buf); return rc; |
202 |
} |
203 |
/* Only now put buf in *proxy */ |
204 |
*proxy=buf; |
205 |
return 0; |
206 |
} |
207 |
|
208 |
/** |
209 |
* Used to read in a config file, the path is checked to be trusted using |
210 |
* safe_is_path_trusted_r() from the safefile library of J. Kupsch. |
211 |
* Upon successful completion config contains the contents of path |
212 |
* Return values: |
213 |
* 0: succes |
214 |
* -1: I/O error |
215 |
* -2: privilege-drop error |
216 |
* -3: permission error (untrusted path) |
217 |
* -4: memory error |
218 |
* -5: unknown or safefile error |
219 |
*/ |
220 |
int cgul_read_config(const char *path, char **config, gid_t read_gid) { |
221 |
int fd,rc,trust; |
222 |
uid_t uid=getuid(),euid=geteuid(); |
223 |
gid_t gid=getgid(),egid=getegid(),target_gid=read_gid<0 ? gid : read_gid; |
224 |
struct safe_id_range_list ulist,glist; |
225 |
struct stat st; |
226 |
char *buf; |
227 |
|
228 |
/* Drop privilege to real uid and special read group */ |
229 |
if (priv_drop(uid,read_gid)) |
230 |
return -2; |
231 |
/* initialize the lists of trusted uid/gid, can basically only fail when |
232 |
* out of memory */ |
233 |
if ( safe_init_id_range_list(&ulist) || |
234 |
safe_init_id_range_list(&glist) || |
235 |
safe_add_id_to_list(&ulist,uid) || |
236 |
safe_add_id_to_list(&glist,target_gid) ) { |
237 |
raise_priv(euid,egid); return -4; |
238 |
} |
239 |
/* Check trust */ |
240 |
trust=safe_is_path_trusted_r(path,&ulist,&glist); |
241 |
/* free the range lists */ |
242 |
safe_destroy_id_range_list(&ulist); |
243 |
safe_destroy_id_range_list(&glist); |
244 |
/* Check the level of trust */ |
245 |
switch (trust) { |
246 |
case SAFE_PATH_ERROR: |
247 |
raise_priv(euid,egid); return -5; break; /* */ |
248 |
case SAFE_PATH_UNTRUSTED: |
249 |
case SAFE_PATH_TRUSTED_STICKY_DIR: |
250 |
raise_priv(euid,egid); return -3; break; /* Perms are somehow wrong */ |
251 |
case SAFE_PATH_TRUSTED: |
252 |
case SAFE_PATH_TRUSTED_CONFIDENTIAL: |
253 |
break; /* trusted */ |
254 |
default: |
255 |
raise_priv(euid,egid); return -5; break; /* unknown error */ |
256 |
} |
257 |
/* Open file and stat the file (latter for size) */ |
258 |
if ((fd=open(path,O_RDONLY))==-1 || fstat(fd,&st)) { |
259 |
raise_priv(euid,egid); return -1; |
260 |
} |
261 |
/* Get expected space */ |
262 |
if ( (buf=(char *)malloc(st.st_size))==NULL) { |
263 |
close(fd); raise_priv(euid,egid); return -4; |
264 |
} |
265 |
/* Read the file, check we get right size */ |
266 |
if (read(fd,buf,st.st_size)!=st.st_size) |
267 |
/* read error, but don't return yet, we want to free the memory centrally */ |
268 |
rc=-1; |
269 |
else |
270 |
rc=0; |
271 |
/* Close file */ |
272 |
close(fd); |
273 |
/* reset euid/egid if it was (effective) root. Ignore exit value. */ |
274 |
raise_priv(euid,egid); |
275 |
/* finalize */ |
276 |
if (rc!=0) |
277 |
free(buf); |
278 |
else /* Only now put buf in *proxy */ |
279 |
*config=buf; |
280 |
return rc; |
281 |
} |
282 |
|
283 |
/** |
284 |
* Writes proxy from *proxy to *path using given lock_type (see cgul_filelock). |
285 |
* It tries to drop privilege to given write_uid, gid_t write_gid. When either |
286 |
* of them is -1, that one is ignored. |
287 |
* Return values: |
288 |
* 0: success |
289 |
* -1: I/O error |
290 |
* -2: privilege-drop error |
291 |
* -3: permissions error |
292 |
*/ |
293 |
int cgul_write_proxy(const char *path, int lock_type, const char *proxy, |
294 |
uid_t write_uid, gid_t write_gid) { |
295 |
int fd,rc; |
296 |
uid_t uid=getuid(),euid=geteuid(),target_uid=(write_uid>=0 ? write_uid : uid); |
297 |
gid_t gid=getgid(),egid=getegid(),target_gid=(write_gid>=0 ? write_gid : gid); |
298 |
mode_t mode=S_IRUSR | S_IWUSR; |
299 |
size_t size,expsize=strlen(proxy)/sizeof(char); |
300 |
|
301 |
/* Drop privilege, will be ignored when euid != 0 */ |
302 |
if (priv_drop(write_uid,write_gid)) |
303 |
return -2; |
304 |
/* Open the file */ |
305 |
if ( (fd=open(path,O_WRONLY | O_CREAT,mode))==-1 ) { |
306 |
raise_priv(euid,egid); return -1; |
307 |
} |
308 |
/* Lock the file */ |
309 |
if ( cgul_filelock(fd,lock_type,LCK_WRITE) ) { |
310 |
close(fd); raise_priv(euid,egid); return -1; |
311 |
} |
312 |
/* Do a chmod and chown, in case it already existed. If this fails, the file has |
313 |
* the wrong permissions. Chowning is in principal only for the group. */ |
314 |
if ( fchmod(fd,mode) || fchown(fd,target_uid,target_gid) ) { |
315 |
close(fd); raise_priv(euid,egid); return -3; |
316 |
} |
317 |
/* Truncate and write file */ |
318 |
if ( ftruncate(fd,0)!=0 || |
319 |
(size=write(fd,proxy,expsize))!=expsize) { |
320 |
close(fd); raise_priv(euid,egid); return -1; /* write error */ |
321 |
} |
322 |
/* unlock */ |
323 |
cgul_filelock(fd,lock_type,LCK_UNLOCK); |
324 |
/* close the file, don't ignore exit values: might have write error */ |
325 |
if (close(fd)) |
326 |
rc=-1; |
327 |
else |
328 |
rc=0; |
329 |
|
330 |
/* reset euid/egid if it was (effective) root. Ignore exit value. */ |
331 |
raise_priv(euid,egid); |
332 |
return 0; |
333 |
} |
334 |
|
335 |
/** |
336 |
* Writes proxy to random unique filename created from path_template using |
337 |
* mkstemp(). It drops privilege (if possible) to write_uid/write_gid |
338 |
* Any directory in path_template will be attempted to be created if it doesn't |
339 |
* exist, with mode 0600. |
340 |
* Return values: |
341 |
* 0: success |
342 |
* -1: I/O error |
343 |
* -2: privilege-drop error |
344 |
* -3: path_template is not absolute (does not start with '/') |
345 |
* -4: memory error |
346 |
*/ |
347 |
int cgul_write_uniq_proxy(const char *path_template, const char *proxy, |
348 |
uid_t write_uid, gid_t write_gid) { |
349 |
int fd,rc; |
350 |
char *path,*pos; |
351 |
uid_t euid=geteuid(); |
352 |
gid_t egid=getegid(); |
353 |
mode_t mode=S_IRUSR | S_IWUSR; |
354 |
size_t size,expsize=strlen(proxy)/sizeof(char); |
355 |
|
356 |
/* Drop privilege, will be ignored when euid != 0 */ |
357 |
if (priv_drop(write_uid,write_gid)) |
358 |
return -2; /* priv problem */ |
359 |
/* make copy of the template, since it will be overwritten by mkstemp() */ |
360 |
if ( (path=strdup(path_template))==NULL ) { |
361 |
/* out of mem */ |
362 |
raise_priv(euid,egid); return -4; |
363 |
} |
364 |
/* Now create the path */ |
365 |
if ( (pos=strrchr(path,'/'))==NULL) { |
366 |
/* illegal pathname */ |
367 |
free(path); raise_priv(euid,egid); return -3; |
368 |
} |
369 |
pos[0]='\0'; |
370 |
if ((rc=cgul_mkdir_with_parents(path,mode))!=0) { |
371 |
free(path); raise_priv(euid,egid); return rc; |
372 |
} |
373 |
pos[0]='/'; |
374 |
|
375 |
/* Open unique filename */ |
376 |
if ( (fd=mkstemp(path))==-1) { |
377 |
free(path); raise_priv(euid,egid); return -1; |
378 |
} |
379 |
/* chmod and write the file */ |
380 |
if (fchmod(fd,mode) || |
381 |
(size=write(fd,proxy,expsize))!=expsize ) { |
382 |
close(fd); free(path); raise_priv(euid,egid); return -1; |
383 |
} |
384 |
/* close the file, don't ignore exit values: might have write error */ |
385 |
if (close(fd)) { |
386 |
free(path); raise_priv(euid,egid); return -1; |
387 |
} |
388 |
|
389 |
/* reset euid/egid if it was (effective) root. Ignore exit value. */ |
390 |
free(path); |
391 |
raise_priv(euid,egid); |
392 |
return 0; |
393 |
} |
394 |
|
395 |
/** |
396 |
* Behaviour as mkdir -p: create parents where needed. |
397 |
* Return values: |
398 |
* 0: success |
399 |
* -1: result is not a directory |
400 |
* -3: absolutedir is not absolute (does not start with '/') |
401 |
* -4: out of memory |
402 |
*/ |
403 |
int cgul_mkdir_with_parents(const char *absolutedir, mode_t mode) { |
404 |
int rc; |
405 |
char *dir,*pos; |
406 |
struct stat dir_stat; |
407 |
|
408 |
if (absolutedir[0]!='/') /* need absolute path */ |
409 |
return -3; |
410 |
/* make copy */ |
411 |
if ( (dir=strdup(absolutedir))==NULL ) |
412 |
return -4; /* out of memory */ |
413 |
|
414 |
/* pos will 'loop' over all the '/' */ |
415 |
pos=&(dir[1]); /* start with 2nd position, it could be '\0' but not NULL */ |
416 |
while ( (pos=strchr(pos,'/')) ) { |
417 |
pos[0]='\0'; |
418 |
mkdir(dir,mode); /* just create, we will check later if it worked */ |
419 |
pos[0]='/'; |
420 |
pos=&(pos[1]); |
421 |
} |
422 |
/* Now check if dir exists */ |
423 |
if (lstat(dir,&dir_stat) || !S_ISDIR(dir_stat.st_mode)) |
424 |
rc=-1; |
425 |
else |
426 |
rc=0; |
427 |
free(dir); |
428 |
return rc; |
429 |
} |