/[pdpsoft]/trunk/grid-mw-security/cgul/fileutil/fileutil.c
ViewVC logotype

Contents of /trunk/grid-mw-security/cgul/fileutil/fileutil.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 1905 - (show annotations) (download) (as text)
Tue Aug 31 12:44:47 2010 UTC (11 years, 10 months ago) by msalle
File MIME type: text/x-chdr
File size: 26560 byte(s)
- prevent "warning: unused variable `lck'"

1 /**
2 * Copyright (c) Members of the EGEE Collaboration. 2010.
3 * See http://www.eu-egee.org/partners/ for details on the copyright
4 * holders.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 *
18 * Authors: Oscar Koeroo, Mischa Sall\'e, Aram Verstegen
19 * NIKHEF Amsterdam, the Netherlands
20 * <grid-mw-security@nikhef.nl>
21 */
22
23 #include <sys/file.h>
24 #include <sys/stat.h>
25 #include <fcntl.h>
26 #include <unistd.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <errno.h>
30
31 #include "fileutil.h"
32 #include "../safefile-1.0/safe_id_range_list.h"
33 #include "../safefile-1.0/safe_is_path_trusted.h"
34
35 static int priv_drop(int unpriv_uid,int unpriv_gid);
36 static int raise_priv(uid_t euid, gid_t egid);
37
38 /**
39 * Private method.
40 * Drops privilege to an unprivileged account. When unpriv_uid and/or unpriv_gid
41 * are negative, they will be ignored and the information is taken from the real
42 * uid/gid (primary) combination.
43 * Returns 0 when successful, or the return code of set[ug]id() on error.
44 * */
45 int priv_drop(int unpriv_uid,int unpriv_gid) {
46 /* drop priv when needed: euid==0, uid!=0 */
47 uid_t euid=geteuid(),target_uid;
48 gid_t egid=getegid(),target_gid;
49 int rc;
50
51 /* Get correct target_gid */
52 target_gid=( unpriv_gid<0 ? getgid() : (gid_t)unpriv_gid );
53 /* Anything to be done? Note: target_gid MAY be 0 (root group) */
54 rc=( target_gid==egid ? 0 : setegid(target_gid) );
55 /* If error: don't continue */
56 if (rc!=0) return rc;
57
58 /* Get correct target_uid */
59 target_uid=( unpriv_uid<0 ? getuid() : (uid_t)unpriv_uid );
60 /* Anything to be done? target_uid SHOULD NOT be 0 */
61 rc=( target_uid==0 || target_uid==euid ? 0 : seteuid(target_uid) );
62 /* Error: try to restore */
63 if (rc!=0) setegid(egid); /* ignore rc of THIS process: damage control */
64
65 return 0;
66 }
67
68 /**
69 * Private method.
70 * Tries to raise privilege level back to euid/egid.
71 * Return -1 when fails or impossible (neither euid or real uid is root), 0
72 * upon success.
73 * */
74 int raise_priv(uid_t euid, gid_t egid) {
75 uid_t uid=getuid();
76
77 /* reset euid/egid if: it was (effective) root or real user is root */
78 if (euid==0 || uid==0) {
79 if (setegid(egid) || seteuid(euid))
80 return -1;
81 else
82 return 0;
83 }
84 return -1;
85 }
86 /**
87 * Does given lock action on file given by filedescriptor fd using mechanism
88 * defined by lock_type. lock_type can be a multiple types in which case they
89 * will be all used. LCK_NOLOCK is a special lock type which just does nothing
90 * and will not be combined with others. Valid lock types:
91 * LCK_NOLOCK - no locking
92 * LCK_FCNTL - fcntl() locking
93 * LCK_FLOCK - flock() locking
94 * Valid actions are:
95 * LCK_READ - set shared read lock
96 * LCK_WRITE - set exclusive write lock
97 * LCK_UNLOCK - unset lock
98 * Locks are exclusive for writing and shared for reading: multiple processes
99 * can read simultaneously, but writing is exclusive, both for reading and
100 * writing.
101 * Returns -1 on error, 0 on success.
102 */
103 int cgul_filelock(int fd, int lock_type, int action) {
104 struct flock lck_struct;
105 int rc1,rc2;
106 #ifndef sun
107 int lck;
108 #endif
109
110 /* Can have multiple lock_types */
111
112 if (lock_type & LCK_NOLOCK)
113 return 0;
114
115 /* FLOCK */
116 if (lock_type & LCK_FLOCK) {
117 #ifdef sun /* Should NOT use flock on Solaris */
118 return -1;
119 #else
120 switch (action) { /* Only one action at the time */
121 case LCK_READ: lck=LOCK_SH; break;
122 case LCK_WRITE: lck=LOCK_EX; break;
123 case LCK_UNLOCK: lck=LOCK_UN; break;
124 default: return -1;
125 }
126 rc1=flock(fd, lck);
127 #endif
128 } else
129 rc1=0;
130 /* FCNTL */
131 if (lock_type & LCK_FCNTL) {
132 switch (action) { /* Only one action at the time */
133 case LCK_READ: lck_struct.l_type=F_RDLCK; break;
134 case LCK_WRITE: lck_struct.l_type=F_WRLCK; break;
135 case LCK_UNLOCK: lck_struct.l_type=F_UNLCK; break;
136 default: return -1;
137 }
138 lck_struct.l_whence=SEEK_SET;
139 lck_struct.l_start=0;
140 lck_struct.l_len=0;
141 rc2=fcntl(fd,F_SETLKW,&lck_struct); /* -1 error */
142 } else
143 rc2=0;
144 return (rc1 || rc2 ? -1 : 0);
145 }
146
147
148 /**
149 * Reads proxy from *path using given lock_type (see cgul_filelock). It tries to
150 * drop privilege to real-uid/real-gid when euid==0 and uid!=0.
151 * Space needed will be malloc-ed.
152 * Upon successful completion config contains the contents of path.
153 * Return values:
154 * 0: success
155 * -1: I/O error
156 * -2: privilege-drop error
157 * -3: permissions error
158 * -4: memory error
159 * -5: too many retries needed during reading
160 * -6: locking failed
161 */
162 int cgul_read_proxy(const char *path, int lock_type, char **proxy) {
163 const int tries=10; /* max number of retries for reading a changing file */
164 int i,fd,rc=0;
165 struct stat st1,st2,*sptr1,*sptr2,*sptr3;
166 uid_t uid=getuid(),euid=geteuid();
167 gid_t gid=getgid(),egid=getegid();
168 char *buf,*buf_new; /* *proxy will be updated when everything is ok */
169 ssize_t size;
170
171 /* Drop privilege to real uid and real gid, only when we can and are
172 * not-root */
173 if ( euid==0 && uid!=0 && priv_drop(uid,gid) )
174 return -2;
175 /* Open file */
176 if ((fd=open(path,O_RDONLY))==-1) {
177 raise_priv(euid,egid); return -1;
178 }
179 /* Lock file */
180 if (cgul_filelock(fd,lock_type,LCK_READ)) {
181 close(fd); raise_priv(euid,egid); return -6;
182 }
183 /* Stat the file before reading:
184 * Need ownership and mode for allowed values, size for malloc */
185 if (fstat(fd,&st1)) {
186 close(fd); raise_priv(euid,egid); return -1;
187 }
188 /* Check we own it (only uid) and it is unreadable/unwriteable for anyone
189 * else */
190 if ( st1.st_uid!=uid ||
191 st1.st_mode & S_IRGRP || st1.st_mode & S_IWGRP ||
192 st1.st_mode & S_IROTH || st1.st_mode & S_IWOTH ) {
193 close(fd); raise_priv(euid,egid); return -3;
194 }
195 /* Get expected space: need 1 extra for trailing '\0' */
196 if ( (buf=(char *)malloc((size_t)(st1.st_size+sizeof(char))))==NULL) {
197 close(fd); raise_priv(euid,egid); return -4;
198 }
199 /* use pointers to the two so that we can swap them easily */
200 sptr1=&st1; sptr2=&st2;
201 /* reading retry loop */
202 for (i=0; i<tries; i++) {
203 /* Read file: if statted size changes, we will try again */
204 size=read(fd,buf,(size_t)sptr1->st_size);
205 buf[size]='\0'; /* Important: read doesn't add the '\0' */
206 /* Stat the file */
207 if (fstat(fd,sptr2)==-1) { /* cannot even stat: I/O error */
208 rc=-1; break;
209 }
210 /* Size, mtime and ctime should have stayed the same, especially ctime
211 * is good as we can't change it with touch ! */
212 if ( sptr2->st_size == sptr1->st_size && /* size equal */
213 sptr2->st_mtime== sptr1->st_mtime && /* mtime equal */
214 sptr2->st_ctime== sptr1->st_ctime) { /* ctime equal */
215 /* Just check the return of the read, we might have an I/O error */
216 rc= (size==(ssize_t)sptr1->st_size ? 0 : -1);
217 break;
218 }
219
220 /* File has changed during reading: retry */
221 if (i<tries-1) { /* will be doing a retry */
222 buf_new=(char *)realloc(buf,(size_t)(sptr2->st_size+sizeof(char)));
223 if ( buf_new==NULL ) {
224 rc=-4; break;
225 }
226 buf=buf_new;
227 /* swap struct pointers */
228 sptr3=sptr2; sptr2=sptr1; sptr1=sptr3;
229 /* wait */
230 usleep(500);
231 /* About to read again, make sure we're (again) at the start */
232 if (lseek(fd,0,SEEK_SET)!=0) { /* I/O error */
233 rc=-1; break;
234 }
235 } else /* failed too many times */
236 rc=-5;
237 }
238
239 /* unlock and close the file, ignore exitval: we have read already */
240 cgul_filelock(fd,lock_type,LCK_UNLOCK);
241 close(fd);
242 /* reset euid/egid if it was (effective) root. Ignore exit value. */
243 raise_priv(euid,egid);
244 /* finalize */
245 if (rc!=0) {
246 free(buf); return rc;
247 }
248 /* Only now put buf in *proxy */
249 *proxy=buf;
250 return 0;
251 }
252
253 /**
254 * Used to read in a config file, the path is checked to be trusted (and
255 * possible confidential) using safe_is_path_trusted_r() from the safefile
256 * library of J. Kupsch.
257 * Upon successful completion config contains the contents of the file at path.
258 *
259 * The config file (is trusted) when each of its pathcomponents is writeable
260 * only by user root or user trusted_uid (which may also be 0).
261 * In case userswitching is possible and the macro DEMAND_CONFIG_IS_CONFIDENTIAL
262 * is defined, then an additional check for 'confidentiality' is done. The file
263 * is confidential when it is only readable by the trusted users (root and
264 * trust_uid) and/or the groupids trust_gid (only when !=-1, hence the type int
265 * instead gid_t) or effective gid (when different from real gid, i.e. in setgid
266 * mode).
267 *
268 * In case userswitching is possible privilege is dropped to account
269 * trust_uid/trust_gid. If trust_uid==0 then real uid is used, if trust_gid==-1
270 * then effective gid is used.
271 *
272 * Return values:
273 * 0: succes
274 * -1: I/O error, including when file changed during reading in any way other
275 * than access time.
276 * -2: privilege-drop error
277 * -3: permission error (untrusted path)
278 * -4: memory error
279 * -5: unknown or safefile error
280 * -10: confidentiality error
281 */
282 int cgul_read_config(const char *path, char **config,
283 uid_t trust_uid, int trust_gid) {
284 int fd,rc,trust,switching;
285 uid_t euid=geteuid(),uid=getuid(),target_uid;
286 gid_t egid=getegid(),target_gid;
287 struct safe_id_range_list ulist,glist;
288 struct stat st_before,st_after;
289 char *buf;
290 #ifdef DEMAND_CONFIG_IS_CONFIDENTIAL
291 gid_t gid=getgid();
292 int dotest;
293 #endif
294
295
296 /* Expected level of trust depends on mode: user switching or not */
297 if (euid==0 || uid==0) {
298 switching=1;
299 /* Switch uid to trust_uid unless it's root, in that case switch to real uid */
300 target_uid=trust_uid!=0 ? trust_uid : uid;
301 /* Switch gid to trust_gid unless -1, in that case switch to effective
302 * gid (which might be real gid). */
303 target_gid=trust_gid!=-1 ? (gid_t)trust_gid : egid;
304 /* when target_uid!=0 (i.e. when real uid is 0) then set privileges */
305 if (target_uid!=0 && priv_drop(target_uid,target_gid))
306 return -2; /* privdrop error */
307 } else {
308 /* Nothing to switch, trust_uid/trust_gid will be used to check the file
309 * permissions only. */
310 switching=0;
311 }
312
313 /* initialize the lists of trusted uid/gid, can basically only fail when
314 * out of memory. These are the UIDs GIDs trusted for WRITING!: only
315 * trust_uid, when specified, and root (automatic) */
316 if ( safe_init_id_range_list(&ulist) ||
317 safe_init_id_range_list(&glist) ||
318 safe_add_id_to_list(&ulist,trust_uid) ) {
319 raise_priv(euid,egid); return -4; /* out-of-memory */
320 }
321 /* Do an stat so that we can compare modes etc. before/after, note we use
322 * stat and not lstat, because we want to know information about the target,
323 * not the symlink. In particular we need the size of the target! */
324 if (stat(path,&st_before)) {
325 raise_priv(euid,egid); return -1; /* I/O error */
326 }
327
328 /* Check whether file is trusted */
329 trust=safe_is_path_trusted_r(path,&ulist,&glist);
330 /* Check the level of trust */
331 switch (trust) {
332 case SAFE_PATH_TRUSTED_CONFIDENTIAL:
333 rc=0; break; /* GOOD */
334 case SAFE_PATH_UNTRUSTED:
335 /* Perms are wrong */
336 rc=-3; break; /* perm error */
337 case SAFE_PATH_TRUSTED:
338 case SAFE_PATH_TRUSTED_STICKY_DIR:
339 #ifdef DEMAND_CONFIG_IS_CONFIDENTIAL
340 /* Need to further test in switching mode */
341 if (switching) {
342 dotest=0;
343 if (egid!=gid) { /* Add egid (when !=gid) */
344 if (safe_add_id_to_list(&glist,egid)) {
345 rc=-4; break; /* out-of-memory */
346 }
347 dotest=1;
348 }
349 if (trust_gid!=-1) { /* Add trust_gid (when !=-1) */
350 if (safe_add_id_to_list(&glist,(gid_t)trust_gid)) {
351 rc=-4; break; /* out-of-memory */
352 }
353 dotest=1;
354 }
355 if (dotest) { /* Has something changed? */
356 /* Test whether it's confidential for this new list */
357 trust=safe_is_path_trusted_r(path,&ulist,&glist);
358 switch (trust) {
359 case SAFE_PATH_TRUSTED_CONFIDENTIAL: /* GOOD */
360 rc=0; break;
361 case SAFE_PATH_TRUSTED: /* confid error */
362 case SAFE_PATH_TRUSTED_STICKY_DIR: /* confid error */
363 rc=-10; break;
364 case SAFE_PATH_ERROR: /* checking failed */
365 default:
366 rc=-5; break; /* unknown error */
367 }
368 } else /* Nothing changed, so it's not confidential */
369 rc=-10;
370 } else /* not-switching, perms are ok */
371 rc=0;
372 break;
373 #else
374 /* TRUSTED-only is fine */
375 rc=0;
376 break;
377 #endif
378 case SAFE_PATH_ERROR: /* checking failed */
379 default: /* Unknown state, should not be reached */
380 rc=-5; break;
381 }
382
383 /* free the range lists */
384 safe_destroy_id_range_list(&ulist);
385 safe_destroy_id_range_list(&glist);
386
387 /* Check what we returned */
388 if (rc!=0) {
389 raise_priv(euid,egid); return rc;
390 }
391
392 /* Open file and stat the file (latter for size) */
393 if ((fd=open(path,O_RDONLY))==-1) {
394 raise_priv(euid,egid); return -1; /* I/O error */
395 }
396 /* Get expected space, don't forget trailing '\0' */
397 if ( (buf=(char *)malloc((size_t)(st_before.st_size+sizeof(char))))==NULL)
398 {
399 close(fd); raise_priv(euid,egid); return -4; /* out-of-memory */
400 }
401 /* Read the file, check we get right size */
402 if ( read(fd,buf,st_before.st_size)!=(ssize_t)st_before.st_size ||
403 fstat(fd,&st_after) || /* Do stat fd */
404 st_before.st_dev !=st_after.st_dev || /* device */
405 st_before.st_ino !=st_after.st_ino || /* inode */
406 st_before.st_size !=st_after.st_size || /* size */
407 st_before.st_mode !=st_after.st_mode || /* mode */
408 st_before.st_uid !=st_after.st_uid || /* uid */
409 st_before.st_gid !=st_after.st_gid || /* gid */
410 st_before.st_mtime !=st_after.st_mtime || /* modification time */
411 st_before.st_ctime !=st_after.st_ctime ) /* creation time */
412 /* something changed or went wrong: classify all as I/O error, because
413 * we were reading a trusted or confidential file.
414 * Don't return yet, we want to free the memory centrally */
415 rc=-1; /* I/O error */
416 else {
417 /* add trailing '\0' */
418 buf[st_after.st_size]='\0';
419 rc=0;
420 }
421 /* Close file */
422 close(fd);
423 /* reset euid/egid if it was (effective) root. Ignore exit value. */
424 raise_priv(euid,egid);
425 /* finalize */
426 if (rc!=0)
427 free(buf);
428 else /* Only now put buf in *proxy */
429 *config=buf;
430 return rc;
431 }
432
433 /**
434 * Writes proxy from *proxy to *path using given lock_type (see cgul_filelock).
435 * When (e)uid==0 it tries to drop privilege to given write_uid, write_gid. When
436 * either of these is -1, the real uid/gid is used instead, if one of those is
437 * root, the corresponding effective uid/gid is used instead.
438 * Return values:
439 * 0: success
440 * -1: I/O error
441 * -2: privilege-drop error
442 * -3: permissions error, including file directly in / or not absolute
443 * -4: memory error
444 * -6: locking failed
445 */
446 int cgul_write_proxy(const char *path, int lock_type, const char *proxy,
447 int write_uid, int write_gid) {
448 const mode_t filemode=S_IRUSR | S_IWUSR;
449 const mode_t dirmode=S_IRUSR | S_IWUSR | S_IXUSR;
450 int fd,rc;
451 uid_t euid=geteuid(), uid=getuid(), target_uid;
452 gid_t egid=getegid(), gid=getgid(), target_gid;
453 size_t expsize=strlen(proxy)/sizeof(char);
454 char *pos,*pathcopy;
455
456 /* Set write uid */
457 if (write_uid>=0)
458 target_uid=write_uid;
459 else
460 target_uid=(uid==0 ? euid : uid); /* when real==root: stay effective */
461 /* Set write gid */
462 if (write_gid>=0)
463 target_gid=write_gid;
464 else
465 target_gid=(gid==0 ? egid : gid); /* when real==root: stay effective */
466
467 /* Drop privilege when (e)uid == 0 */
468 if ( (euid==0 || uid==0) && priv_drop(target_uid,target_gid))
469 return -2;
470 /* make copy of the path */
471 if ( (pathcopy=strdup(path))==NULL ) {
472 /* out of mem */
473 raise_priv(euid,egid); return -4;
474 }
475 /* Check filename */
476 if ( (pos=strrchr(pathcopy,'/'))==NULL) {
477 free(pathcopy); raise_priv(euid,egid); return -3;
478 }
479 /* Create parent directories where needed */
480 pos[0]='\0';
481 if ((rc=cgul_mkdir_with_parents(pathcopy,dirmode))!=0) {
482 free(pathcopy); raise_priv(euid,egid); return rc;
483 }
484 free(pathcopy);
485 /* Open the file */
486 if ( (fd=open(path,O_WRONLY | O_CREAT,filemode))==-1 ) {
487 raise_priv(euid,egid); return -1;
488 }
489 /* Lock the file */
490 if ( cgul_filelock(fd,lock_type,LCK_WRITE) ) {
491 close(fd); raise_priv(euid,egid); return -6;
492 }
493 /* Do a chmod and chown, in case it already existed. If this fails, the file
494 * has the wrong permissions.
495 * Chowning is in principal only for the group. */
496 if ( fchmod(fd,filemode) || fchown(fd,target_uid,target_gid) ) {
497 close(fd); raise_priv(euid,egid); return -3;
498 }
499 /* Truncate and write file */
500 if ( ftruncate(fd,0)!=0 ||
501 write(fd,proxy,expsize)!=(ssize_t)expsize ) {
502 close(fd); raise_priv(euid,egid); return -1; /* write error */
503 }
504 /* unlock: ignore the exit code */
505 cgul_filelock(fd,lock_type,LCK_UNLOCK);
506 /* close the file, don't ignore exit values: might have write error */
507 if (close(fd))
508 rc=-1;
509 else
510 rc=0;
511
512 /* reset euid/egid if it was (effective) root. Ignore exit value. */
513 raise_priv(euid,egid);
514 return 0;
515 }
516
517 /**
518 * Writes proxy to unique filename created from path_template using mkstemp().
519 * path_template will be overridden with the actual filename.
520 * When (e)uid==0 it tries to drop privilege to given write_uid, write_gid. When
521 * either of these is -1, the real uid/gid is used instead, if one of those is
522 * root, the corresponding effective uid/gid is used instead.
523 * Any directory in path_template will be attempted to be created if it doesn't
524 * exist, with mode 0600.
525 * Return values:
526 * 0: success
527 * -1: I/O error, this includes a failure of mkstemp which can be due to a
528 * wrong template. It MUST contain 6 consecutive X's.
529 * -2: privilege-drop error
530 * -3: illegal path_template: in / or not absolute.
531 * -4: memory error
532 * -5: invalid template: it MUST end with 6 X's
533 */
534 int cgul_write_uniq_proxy(char *path_template, const char *proxy,
535 int write_uid, int write_gid) {
536 const mode_t filemode=S_IRUSR | S_IWUSR;
537 const mode_t dirmode=S_IRUSR | S_IWUSR | S_IXUSR;
538 int fd,rc;
539 char *pos;
540 uid_t uid=getuid(), euid=geteuid(), target_uid;
541 gid_t gid=getgid(), egid=getegid(), target_gid;
542 size_t expsize=strlen(proxy)/sizeof(char);
543
544 /* Check template format, see mkstemp() */
545 pos=(char *)&(path_template[strlen(path_template)-6]);
546 if (strncmp(pos,"XXXXXX",6)!=0)
547 return -5;
548
549 /* Set write uid */
550 if (write_uid>=0)
551 target_uid=write_uid;
552 else
553 target_uid=(uid==0 ? euid : uid); /* when real==root: stay effective */
554 /* Set write gid */
555 if (write_gid>=0)
556 target_gid=write_gid;
557 else
558 target_gid=(gid==0 ? egid : gid); /* when real==root: stay effective */
559
560 /* Drop privilege when (e)uid == 0 */
561 if ( (euid==0 || uid==0) && priv_drop(target_uid,target_gid))
562 return -2;
563
564 /* Check filename */
565 if ( (pos=strrchr(path_template,'/'))==NULL) { /* illegal pathname */
566 raise_priv(euid,egid); return -3;
567 }
568 /* Create parent directories where needed */
569 pos[0]='\0';
570 if ((rc=cgul_mkdir_with_parents(path_template,dirmode))!=0) {
571 raise_priv(euid,egid); return rc;
572 }
573 pos[0]='/';
574 /* Open unique filename */
575 if ( (fd=mkstemp(path_template))==-1) {
576 raise_priv(euid,egid); return -1;
577 }
578 /* chmod and write the file */
579 if (fchmod(fd,filemode) ||
580 write(fd,proxy,expsize)!=(ssize_t)expsize ) {
581 close(fd); raise_priv(euid,egid); return -1;
582 }
583 /* close the file, don't ignore exit values: might have write error */
584 if (close(fd)) {
585 raise_priv(euid,egid); return -1;
586 }
587
588 /* reset euid/egid if it was (effective) root. Ignore exit value. */
589 raise_priv(euid,egid);
590 return 0;
591 }
592
593 /**
594 * Behaviour as mkdir -p: create parents where needed.
595 * Return values:
596 * 0: success
597 * -1: I/O error, e.g. a component is not a dir, not accessible, etc.
598 * -3: absolutedir is not absolute (does not start with '/')
599 * -4: out of memory
600 */
601 int cgul_mkdir_with_parents(const char *absolutedir, mode_t mode) {
602 int rc;
603 mode_t oldumask;
604 char *dir,*pos;
605 struct stat dir_stat;
606
607 if (absolutedir[0]!='/') /* need absolute path */
608 return -3;
609 /* make copy for local usage */
610 if ( (dir=strdup(absolutedir))==NULL )
611 return -4; /* out of memory */
612
613 /* pos will 'loop' over all the '/' except the leading one */
614 pos=dir;
615 /* Enforce mode as the creation mode, even when umask is more permissive */
616 oldumask=umask(~mode);
617 do {
618 /* Setup the next path component */
619 pos=strchr(&(pos[1]),'/');
620 if (pos!=NULL) pos[0]='\0';
621 /* First check if dir exists: needed for automount */
622 if ((rc=stat(dir,&dir_stat))) { /* stat failed: rc now -1 */
623 /* Check if it is due to non-existing component */
624 if (errno==ENOENT) { /* means doesn't exist (since dir!="") */
625 if ((rc=mkdir(dir,mode)))
626 break; /* rc==-1 from mkdir */
627 } else /* stat failed for other reason: error */
628 break;
629 } else { /* Check if existing component is a directory */
630 if (!S_ISDIR(dir_stat.st_mode)) {
631 rc=-1;
632 break;
633 }
634 }
635 if (pos==NULL) /* This was the last path component */
636 break;
637 /* Put the / back */
638 pos[0]='/';
639 } while ( 1 );
640 /* reset umask */
641 umask(oldumask);
642 /* Free memory and return */
643 free(dir);
644 return rc;
645 }
646
647 /**
648 * Safely opens a root-owned logfile with given set of file- and directory
649 * permissions, using J. Kupsch safe_path_is_trusted_r() routine. The file will
650 * be opened with O_WRONLY | O_APPEND | O_CREAT and will have set in addition
651 * the flag FD_CLOEXEC (automatically close upon exec() calls, see fcntl() ).
652 * log_file absolute filename of the logfile
653 * filemask mode_t of the file, if file doesn't exists, it gets this set,
654 * otherwise, it may not be 'more open' than this.
655 * dirmask mode_t of directories that will be created.
656 * file FILE handle of opened file, or NULL on error
657 * Return values:
658 * 0 - success
659 * -1 - mkdir error
660 * -2 - I/O error
661 * -3 - permission failed (path or file)
662 * -4 - out-of-memory
663 * -5 - filename not absolute
664 * -6 - stat before/after differs
665 * -8 - unknown error
666 */
667 int cgul_open_logfile(const char *log_file,
668 const mode_t filemask, const mode_t dirmask,
669 FILE **file) {
670 mode_t mode,oldumask;
671 struct stat st_before, st_after;
672 char *pos, *dir;
673 int rc, fd, flags, trust;
674 struct safe_id_range_list ulist,glist;
675
676 /* Make sure file has a well-defined value */
677 *file=NULL;
678
679 /* First create the directory if it doesn't yet exist, rate a absolute path
680 * as a permission failure */
681 if ((dir=strdup(log_file))==NULL)
682 return -4; /* out-of-memory */
683 if ((pos=strrchr(dir,'/'))==NULL) {
684 free(dir);
685 return -5;
686 }
687 *pos='\0';
688 rc=cgul_mkdir_with_parents(dir, dirmask);
689 free(dir);
690 if (rc!=0) {
691 if (rc==-1) return -1; /* mkdir I/O error */
692 if (rc==-4) return -4; /* out-of-memory */
693 return -8;
694 }
695
696 /* Now open the file, in append mode, we will check everything afterwards:
697 * 1) stat to see if the thing when it exists is a file, needed to prevent
698 * blocking on a fifo.
699 * 2) open the file
700 * 3) check if the path is trusted
701 * 4) do a stat on the by now trusted path
702 * 5) check that the trusted path is the same as the opened one.*/
703
704 /* If not regular file: permission error. Note: output of this stat us
705 * further ignored. */
706 if (stat(log_file,&st_before)==0 && !S_ISREG(st_before.st_mode))
707 return -3;
708
709 /* Set umask to enforce not overprotecting the filemask */
710 oldumask=umask(~filemask);
711 /* Open log file, and add the FD_CLOEXEC flag, which means
712 * automatically close the file upon execve */
713 if ( (fd=open(log_file, O_WRONLY | O_APPEND | O_CREAT, filemask))==-1 ||
714 (flags=fcntl(fd,F_GETFD))==-1 ||
715 fcntl(fd,F_SETFD,flags | FD_CLOEXEC)==-1) {
716 umask(oldumask); /* still reset umask */
717 return -2;
718 }
719 /* reset umask */
720 umask(oldumask);
721
722 /* initialize the lists of trusted uid/gid, can basically only fail when
723 * out of memory. These are the UIDs GIDs trusted for WRITING!: only
724 * trust_uid, when specified, and root (automatic) */
725 if ( safe_init_id_range_list(&ulist) || safe_init_id_range_list(&glist) ) {
726 close(fd);
727 return -4;
728 }
729 /* Check whether file is trusted */
730 trust=safe_is_path_trusted_r(log_file,&ulist,&glist);
731
732 /* free the range lists */
733 safe_destroy_id_range_list(&ulist);
734 safe_destroy_id_range_list(&glist);
735
736 /* Do stats so that we can compare modes etc. before/after. */
737 if (stat(log_file,&st_after) || fstat(fd,&st_before)) { /* I/O error */
738 close(fd);
739 return -2;
740 }
741
742 /* Check if we actually opened the checked file and that it didn't change
743 * mode/ownership */
744 if ( st_before.st_dev !=st_after.st_dev || /* device */
745 st_before.st_ino !=st_after.st_ino || /* inode */
746 st_before.st_mode !=st_after.st_mode || /* mode */
747 st_before.st_uid !=st_after.st_uid || /* uid */
748 st_before.st_gid !=st_after.st_gid ) /* gid */ {
749 close(fd);
750 return -6;
751 }
752
753 /* Check the level of trust */
754 switch (trust) {
755 case SAFE_PATH_TRUSTED_CONFIDENTIAL:
756 case SAFE_PATH_TRUSTED:
757 case SAFE_PATH_TRUSTED_STICKY_DIR:
758 break; /* GOOD */
759 case SAFE_PATH_UNTRUSTED: /* perm error */
760 close(fd);
761 return -3; break;
762 case SAFE_PATH_ERROR: /* checking failed */
763 default: /* Unknown state, should not be reached */
764 close(fd);
765 return -8; break;
766 }
767
768 /* Check the mode of the file doesn't exceed the permissible: even though
769 * it's trusted, it is NOT allowed to be readable beyond filemask. */
770 mode = st_after.st_mode & ~S_IFMT;
771 if ((mode & filemask) != mode) {
772 close(fd);
773 return -3;
774 }
775
776
777 /* Now attach the filedescriptor to a stream */
778 if ( (*file = fdopen(fd,"a")) == NULL ) {
779 close(fd);
780 return -1;
781 }
782
783 /* All is fine now */
784 return 0;
785 }

grid.support@nikhef.nl
ViewVC Help
Powered by ViewVC 1.1.28