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

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

Parent Directory Parent Directory | Revision Log Revision Log


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

1 msalle 1484 /**
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 msalle 1470 #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 msalle 1515 #include <errno.h>
30 msalle 1470
31     #include "fileutil.h"
32 msalle 1476 #include "../safefile-1.0/safe_id_range_list.h"
33     #include "../safefile-1.0/safe_is_path_trusted.h"
34 msalle 1470
35 msalle 1484 static int priv_drop(int unpriv_uid,int unpriv_gid);
36 msalle 1470 static int raise_priv(uid_t euid, gid_t egid);
37    
38 msalle 1484 /**
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 msalle 1857 /* Anything to be done? Note: target_gid MAY be 0 (root group) */
54     rc=( target_gid==egid ? 0 : setegid(target_gid) );
55 msalle 1484 /* 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 msalle 1857 /* Anything to be done? target_uid SHOULD NOT be 0 */
61 msalle 1484 rc=( target_uid==0 || target_uid==euid ? 0 : seteuid(target_uid) );
62     /* Error: try to restore */
63 msalle 1510 if (rc!=0) setegid(egid); /* ignore rc of THIS process: damage control */
64 msalle 1484
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 msalle 1510 * 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 msalle 1484 * Returns -1 on error, 0 on success.
102     */
103 msalle 1470 int cgul_filelock(int fd, int lock_type, int action) {
104     struct flock lck_struct;
105 msalle 1905 int rc1,rc2;
106     #ifndef sun
107     int lck;
108     #endif
109 msalle 1470
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 msalle 1809 #ifdef sun /* Should NOT use flock on Solaris */
118     return -1;
119     #else
120 msalle 1470 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 msalle 1809 #endif
128 msalle 1484 } else
129     rc1=0;
130 msalle 1470 /* 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 msalle 1484 rc2=fcntl(fd,F_SETLKW,&lck_struct); /* -1 error */
142     } else
143     rc2=0;
144 msalle 1470 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 msalle 1498 * drop privilege to real-uid/real-gid when euid==0 and uid!=0.
151     * Space needed will be malloc-ed.
152 msalle 1476 * Upon successful completion config contains the contents of path.
153 msalle 1470 * 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 msalle 1510 * -6: locking failed
161 msalle 1470 */
162 msalle 1498 int cgul_read_proxy(const char *path, int lock_type, char **proxy) {
163 msalle 1470 const int tries=10; /* max number of retries for reading a changing file */
164 msalle 1473 int i,fd,rc=0;
165 msalle 1470 struct stat st1,st2,*sptr1,*sptr2,*sptr3;
166     uid_t uid=getuid(),euid=geteuid();
167 msalle 1498 gid_t gid=getgid(),egid=getegid();
168 msalle 1510 char *buf,*buf_new; /* *proxy will be updated when everything is ok */
169 msalle 1473 ssize_t size;
170 msalle 1498
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 msalle 1470 return -2;
175     /* Open file */
176 msalle 1476 if ((fd=open(path,O_RDONLY))==-1) {
177     raise_priv(euid,egid); return -1;
178     }
179 msalle 1470 /* Lock file */
180 msalle 1473 if (cgul_filelock(fd,lock_type,LCK_READ)) {
181 msalle 1510 close(fd); raise_priv(euid,egid); return -6;
182 msalle 1470 }
183     /* Stat the file before reading:
184     * Need ownership and mode for allowed values, size for malloc */
185     if (fstat(fd,&st1)) {
186 msalle 1476 close(fd); raise_priv(euid,egid); return -1;
187 msalle 1470 }
188 msalle 1510 /* Check we own it (only uid) and it is unreadable/unwriteable for anyone
189     * else */
190 msalle 1470 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 msalle 1476 close(fd); raise_priv(euid,egid); return -3;
194 msalle 1470 }
195 msalle 1484 /* Get expected space: need 1 extra for trailing '\0' */
196 msalle 1493 if ( (buf=(char *)malloc((size_t)(st1.st_size+sizeof(char))))==NULL) {
197 msalle 1476 close(fd); raise_priv(euid,egid); return -4;
198 msalle 1470 }
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 msalle 1493 size=read(fd,buf,(size_t)sptr1->st_size);
205 msalle 1484 buf[size]='\0'; /* Important: read doesn't add the '\0' */
206 msalle 1470 /* 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 msalle 1493 rc= (size==(ssize_t)sptr1->st_size ? 0 : -1);
217 msalle 1470 break;
218     }
219    
220     /* File has changed during reading: retry */
221     if (i<tries-1) { /* will be doing a retry */
222 msalle 1493 buf_new=(char *)realloc(buf,(size_t)(sptr2->st_size+sizeof(char)));
223 msalle 1484 if ( buf_new==NULL ) {
224 msalle 1470 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 msalle 1473 cgul_filelock(fd,lock_type,LCK_UNLOCK);
241 msalle 1470 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 msalle 1857 * 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 msalle 1503 * Upon successful completion config contains the contents of the file at path.
258 msalle 1857 *
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 msalle 1476 * Return values:
273     * 0: succes
274 msalle 1510 * -1: I/O error, including when file changed during reading in any way other
275     * than access time.
276 msalle 1476 * -2: privilege-drop error
277     * -3: permission error (untrusted path)
278     * -4: memory error
279     * -5: unknown or safefile error
280 msalle 1857 * -10: confidentiality error
281 msalle 1476 */
282 msalle 1484 int cgul_read_config(const char *path, char **config,
283 msalle 1857 uid_t trust_uid, int trust_gid) {
284 msalle 1484 int fd,rc,trust,switching;
285 msalle 1498 uid_t euid=geteuid(),uid=getuid(),target_uid;
286 msalle 1857 gid_t egid=getegid(),target_gid;
287 msalle 1476 struct safe_id_range_list ulist,glist;
288 msalle 1510 struct stat st_before,st_after;
289 msalle 1476 char *buf;
290 msalle 1857 #ifdef DEMAND_CONFIG_IS_CONFIDENTIAL
291     gid_t gid=getgid();
292     int dotest;
293     #endif
294 msalle 1476
295 msalle 1857
296 msalle 1484 /* Expected level of trust depends on mode: user switching or not */
297 msalle 1498 if (euid==0 || uid==0) {
298 msalle 1484 switching=1;
299 msalle 1857 /* 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 msalle 1498 if (target_uid!=0 && priv_drop(target_uid,target_gid))
306 msalle 1829 return -2; /* privdrop error */
307 msalle 1498 } else {
308 msalle 1484 /* Nothing to switch, trust_uid/trust_gid will be used to check the file
309     * permissions only. */
310     switching=0;
311 msalle 1498 }
312 msalle 1484
313 msalle 1476 /* initialize the lists of trusted uid/gid, can basically only fail when
314 msalle 1857 * out of memory. These are the UIDs GIDs trusted for WRITING!: only
315     * trust_uid, when specified, and root (automatic) */
316 msalle 1476 if ( safe_init_id_range_list(&ulist) ||
317     safe_init_id_range_list(&glist) ||
318 msalle 1857 safe_add_id_to_list(&ulist,trust_uid) ) {
319 msalle 1829 raise_priv(euid,egid); return -4; /* out-of-memory */
320 msalle 1476 }
321 msalle 1514 /* 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 msalle 1829 raise_priv(euid,egid); return -1; /* I/O error */
326 msalle 1510 }
327    
328 msalle 1857 /* Check whether file is trusted */
329 msalle 1476 trust=safe_is_path_trusted_r(path,&ulist,&glist);
330     /* Check the level of trust */
331     switch (trust) {
332 msalle 1857 case SAFE_PATH_TRUSTED_CONFIDENTIAL:
333     rc=0; break; /* GOOD */
334 msalle 1476 case SAFE_PATH_UNTRUSTED:
335 msalle 1484 /* Perms are wrong */
336 msalle 1857 rc=-3; break; /* perm error */
337 msalle 1484 case SAFE_PATH_TRUSTED:
338 msalle 1476 case SAFE_PATH_TRUSTED_STICKY_DIR:
339 msalle 1857 #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 msalle 1493 break;
373 msalle 1857 #else
374     /* TRUSTED-only is fine */
375     rc=0;
376 msalle 1484 break;
377 msalle 1857 #endif
378     case SAFE_PATH_ERROR: /* checking failed */
379     default: /* Unknown state, should not be reached */
380     rc=-5; break;
381 msalle 1476 }
382 msalle 1857
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 msalle 1476 /* Open file and stat the file (latter for size) */
393 msalle 1510 if ((fd=open(path,O_RDONLY))==-1) {
394 msalle 1829 raise_priv(euid,egid); return -1; /* I/O error */
395 msalle 1476 }
396 msalle 1484 /* Get expected space, don't forget trailing '\0' */
397 msalle 1510 if ( (buf=(char *)malloc((size_t)(st_before.st_size+sizeof(char))))==NULL)
398     {
399 msalle 1829 close(fd); raise_priv(euid,egid); return -4; /* out-of-memory */
400 msalle 1476 }
401     /* Read the file, check we get right size */
402 msalle 1510 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 msalle 1829 rc=-1; /* I/O error */
416 msalle 1484 else {
417     /* add trailing '\0' */
418 msalle 1510 buf[st_after.st_size]='\0';
419 msalle 1476 rc=0;
420 msalle 1484 }
421 msalle 1476 /* 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 msalle 1470 * Writes proxy from *proxy to *path using given lock_type (see cgul_filelock).
435 msalle 1498 * 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 msalle 1470 * Return values:
439     * 0: success
440     * -1: I/O error
441     * -2: privilege-drop error
442 msalle 1486 * -3: permissions error, including file directly in / or not absolute
443 msalle 1491 * -4: memory error
444 msalle 1510 * -6: locking failed
445 msalle 1470 */
446     int cgul_write_proxy(const char *path, int lock_type, const char *proxy,
447 msalle 1484 int write_uid, int write_gid) {
448 msalle 1486 const mode_t filemode=S_IRUSR | S_IWUSR;
449     const mode_t dirmode=S_IRUSR | S_IWUSR | S_IXUSR;
450 msalle 1473 int fd,rc;
451 msalle 1498 uid_t euid=geteuid(), uid=getuid(), target_uid;
452     gid_t egid=getegid(), gid=getgid(), target_gid;
453 msalle 1493 size_t expsize=strlen(proxy)/sizeof(char);
454 msalle 1491 char *pos,*pathcopy;
455 msalle 1470
456 msalle 1498 /* Set write uid */
457     if (write_uid>=0)
458     target_uid=write_uid;
459     else
460 msalle 1510 target_uid=(uid==0 ? euid : uid); /* when real==root: stay effective */
461 msalle 1498 /* Set write gid */
462     if (write_gid>=0)
463     target_gid=write_gid;
464     else
465 msalle 1510 target_gid=(gid==0 ? egid : gid); /* when real==root: stay effective */
466 msalle 1498
467     /* Drop privilege when (e)uid == 0 */
468     if ( (euid==0 || uid==0) && priv_drop(target_uid,target_gid))
469 msalle 1470 return -2;
470 msalle 1491 /* 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 msalle 1486 /* Create parent directories where needed */
480     pos[0]='\0';
481 msalle 1491 if ((rc=cgul_mkdir_with_parents(pathcopy,dirmode))!=0) {
482     free(pathcopy); raise_priv(euid,egid); return rc;
483 msalle 1486 }
484 msalle 1491 free(pathcopy);
485 msalle 1473 /* Open the file */
486 msalle 1486 if ( (fd=open(path,O_WRONLY | O_CREAT,filemode))==-1 ) {
487 msalle 1470 raise_priv(euid,egid); return -1;
488     }
489 msalle 1473 /* Lock the file */
490     if ( cgul_filelock(fd,lock_type,LCK_WRITE) ) {
491 msalle 1510 close(fd); raise_priv(euid,egid); return -6;
492 msalle 1470 }
493 msalle 1484 /* 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 msalle 1486 if ( fchmod(fd,filemode) || fchown(fd,target_uid,target_gid) ) {
497 msalle 1473 close(fd); raise_priv(euid,egid); return -3;
498     }
499 msalle 1470 /* Truncate and write file */
500 msalle 1473 if ( ftruncate(fd,0)!=0 ||
501 msalle 1493 write(fd,proxy,expsize)!=(ssize_t)expsize ) {
502 msalle 1470 close(fd); raise_priv(euid,egid); return -1; /* write error */
503     }
504 msalle 1510 /* unlock: ignore the exit code */
505 msalle 1473 cgul_filelock(fd,lock_type,LCK_UNLOCK);
506 msalle 1470 /* 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 msalle 1498 * Writes proxy to unique filename created from path_template using mkstemp().
519 msalle 1503 * path_template will be overridden with the actual filename.
520 msalle 1498 * 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 msalle 1470 * 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 msalle 1484 * -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 msalle 1470 * -2: privilege-drop error
530 msalle 1486 * -3: illegal path_template: in / or not absolute.
531 msalle 1470 * -4: memory error
532 msalle 1484 * -5: invalid template: it MUST end with 6 X's
533 msalle 1470 */
534 msalle 1503 int cgul_write_uniq_proxy(char *path_template, const char *proxy,
535 msalle 1510 int write_uid, int write_gid) {
536 msalle 1484 const mode_t filemode=S_IRUSR | S_IWUSR;
537     const mode_t dirmode=S_IRUSR | S_IWUSR | S_IXUSR;
538 msalle 1473 int fd,rc;
539 msalle 1503 char *pos;
540 msalle 1498 uid_t uid=getuid(), euid=geteuid(), target_uid;
541     gid_t gid=getgid(), egid=getegid(), target_gid;
542 msalle 1493 size_t expsize=strlen(proxy)/sizeof(char);
543 msalle 1470
544 msalle 1484 /* 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 msalle 1498 /* Set write uid */
550     if (write_uid>=0)
551     target_uid=write_uid;
552     else
553 msalle 1510 target_uid=(uid==0 ? euid : uid); /* when real==root: stay effective */
554 msalle 1498 /* Set write gid */
555     if (write_gid>=0)
556     target_gid=write_gid;
557     else
558 msalle 1510 target_gid=(gid==0 ? egid : gid); /* when real==root: stay effective */
559 msalle 1498
560     /* Drop privilege when (e)uid == 0 */
561     if ( (euid==0 || uid==0) && priv_drop(target_uid,target_gid))
562     return -2;
563    
564 msalle 1491 /* Check filename */
565 msalle 1503 if ( (pos=strrchr(path_template,'/'))==NULL) { /* illegal pathname */
566     raise_priv(euid,egid); return -3;
567 msalle 1470 }
568 msalle 1491 /* Create parent directories where needed */
569 msalle 1470 pos[0]='\0';
570 msalle 1503 if ((rc=cgul_mkdir_with_parents(path_template,dirmode))!=0) {
571     raise_priv(euid,egid); return rc;
572 msalle 1470 }
573     pos[0]='/';
574     /* Open unique filename */
575 msalle 1503 if ( (fd=mkstemp(path_template))==-1) {
576     raise_priv(euid,egid); return -1;
577 msalle 1470 }
578     /* chmod and write the file */
579 msalle 1484 if (fchmod(fd,filemode) ||
580 msalle 1493 write(fd,proxy,expsize)!=(ssize_t)expsize ) {
581 msalle 1503 close(fd); raise_priv(euid,egid); return -1;
582 msalle 1470 }
583     /* close the file, don't ignore exit values: might have write error */
584     if (close(fd)) {
585 msalle 1503 raise_priv(euid,egid); return -1;
586 msalle 1470 }
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 msalle 1516 * -1: I/O error, e.g. a component is not a dir, not accessible, etc.
598 msalle 1470 * -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 msalle 1515 int rc;
603 msalle 1892 mode_t oldumask;
604 msalle 1470 char *dir,*pos;
605     struct stat dir_stat;
606    
607     if (absolutedir[0]!='/') /* need absolute path */
608     return -3;
609 msalle 1484 /* make copy for local usage */
610 msalle 1470 if ( (dir=strdup(absolutedir))==NULL )
611     return -4; /* out of memory */
612    
613 msalle 1515 /* pos will 'loop' over all the '/' except the leading one */
614     pos=dir;
615 msalle 1892 /* Enforce mode as the creation mode, even when umask is more permissive */
616     oldumask=umask(~mode);
617 msalle 1484 do {
618 msalle 1515 /* 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 msalle 1892 /* reset umask */
641     umask(oldumask);
642 msalle 1515 /* Free memory and return */
643 msalle 1470 free(dir);
644     return rc;
645     }
646 msalle 1879
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 msalle 1892 mode_t mode,oldumask;
671 msalle 1879 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 msalle 1892
709     /* Set umask to enforce not overprotecting the filemask */
710     oldumask=umask(~filemask);
711 msalle 1879 /* 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 msalle 1892 umask(oldumask); /* still reset umask */
717 msalle 1879 return -2;
718     }
719 msalle 1892 /* reset umask */
720     umask(oldumask);
721 msalle 1879
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