/[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 1892 - (show annotations) (download) (as text)
Tue Aug 24 12:36:17 2010 UTC (11 years, 11 months ago) by msalle
File MIME type: text/x-chdr
File size: 26532 byte(s)
- Forcing umask for cgul_mkdir_with_parents() and cgul_open_logfile() to be
  identical to the specified modes, so forcing it to be not stricter than the
  specified mode.

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

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