]> granicus.if.org Git - fcron/blob - subs.c
fixed bug preventing audit from being disabled -- thanks Thomas Deutschmann
[fcron] / subs.c
1 /*
2  * FCRON - periodic command scheduler 
3  *
4  *  Copyright 2000-2016 Thibault Godouet <fcron@free.fr>
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  * 
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  * 
20  *  The GNU General Public License can also be found in the file
21  *  `LICENSE' that comes with the fcron source distribution.
22  */
23
24
25 #include "global.h"
26 #include "subs.h"
27 #include <sys/time.h>
28
29 uid_t
30 get_user_uid_safe(char *username)
31     /* get the uid of user username, and die on error */
32 {
33     struct passwd *pass;
34
35     errno = 0;
36     pass = getpwnam(username);
37     if (pass == NULL) {
38         die_e("Unable to get the uid of user %s (is user in passwd file?)",
39               username);
40     }
41
42     return pass->pw_uid;
43
44 }
45
46 gid_t
47 get_group_gid_safe(char *groupname)
48     /* get the gid of group groupname, and die on error */
49 {
50     struct group *grp = NULL;
51
52     errno = 0;
53     grp = getgrnam(groupname);
54     if (grp == NULL) {
55         die_e("Unable to get the gid of group %s", groupname);
56     }
57
58     return grp->gr_gid;
59
60 }
61
62 #ifdef USE_SETE_ID
63 void
64 seteuid_safe(uid_t euid)
65 /* set the euid if different from the current one, and die on error */
66 {
67     /* on BSD, one can only seteuid() to the real UID or saved UID,
68      * and NOT the euid, so we don't call seteuid(geteuid()),
69      * which is why we need to check if a change is needed */
70
71     if (geteuid() != euid && seteuid(euid) != 0)
72         die_e("could not change euid to %d", euid);
73
74 }
75
76 void
77 setegid_safe(gid_t egid)
78 /* set the egid if different from the current one, and die on error */
79 {
80     /* on BSD, one can only setegid() to the real GID or saved GID,
81      * and NOT the egid, so we don't call setegid(getegid()),
82      * which is why we need to check if a change is needed */
83
84     if (getegid() != egid && setegid(egid) != 0)
85         die_e("could not change egid to %d", egid);
86
87 }
88 #endif                          /* def USE_SETE_ID */
89
90 #ifdef USE_SETE_ID
91 int
92 open_as_user(const char *pathname, uid_t openuid, gid_t opengid, int flags, ...)
93 /* Become user and call open(), then revert back to who we were.
94  * NOTE: when flags & O_CREAT, the 5th argument is mode_t and must be set
95  *       -- it is ignored otherwise */
96 {
97     uid_t orig_euid = geteuid();
98     gid_t orig_egid = getegid();
99     struct stat s;
100     int fd = -1;
101     va_list ap;
102     mode_t mode = (mode_t) 0;
103     int saved_errno = 0;
104
105     if (flags & O_CREAT) {
106         va_start(ap, flags);
107         mode =
108             (sizeof(mode_t) < sizeof(int)) ? va_arg(ap, int) : va_arg(ap,
109                                                                       mode_t);
110         va_end(ap);
111     }
112
113     seteuid_safe(openuid);
114     setegid_safe(opengid);
115
116     if (flags & O_CREAT) {
117         fd = open(pathname, flags, mode);
118     }
119     else
120         fd = open(pathname, flags);
121
122     saved_errno = errno;
123
124     /* change the effective uid/gid back to original values */
125     seteuid_safe(orig_euid);
126     setegid_safe(orig_egid);
127
128     /* if open() didn't fail make sure we opened a 'normal' file */
129     if (fd >= 0) {
130
131         if (fstat(fd, &s) < 0) {
132             saved_errno = errno;
133             error_e("open_as_user(): could not fstat %s", pathname);
134             if (xclose(&fd) < 0)
135                 error_e("open_as_user: could not xclose() %s", pathname);
136             fd = -1;
137         }
138
139         if (!S_ISREG(s.st_mode) || s.st_nlink != 1) {
140             error_e("open_as_user(): file %s is not a regular file", pathname);
141             if (xclose(&fd) < 0)
142                 error_e("open_as_user: could not xclose() %s", pathname);
143             saved_errno = 0;
144             fd = -1;
145         }
146
147     }
148
149     errno = saved_errno;
150
151     return fd;
152
153 }
154
155 #else                           /* def USE_SETE_ID */
156
157 int
158 open_as_user(const char *pathname, uid_t openuid, gid_t opengid, int flags, ...)
159 /* Become user and call open(), then revert back to who we were.
160  * As seteuid() is not available on this system attempt to similate that behavior
161  * as closely as possible.
162  * NOTE: when flags & O_CREAT, the 5th argument is mode_t and must be set
163  *       -- it is ignored otherwise */
164 {
165     int fd = -1;
166     struct stat s;
167     va_list ap;
168     mode_t mode = (mode_t) 0;
169     int saved_errno = 0;
170
171     if (flags & O_CREAT) {
172         va_start(ap, flags);
173         mode =
174             (sizeof(mode_t) < sizeof(int)) ? va_arg(ap, int) : va_arg(ap,
175                                                                       mode_t);
176         va_end(ap);
177     }
178
179     /* In case a flag as O_TRUNC is set, we should test if the user
180      * is allowed to open the file before we open it.
181      * There will always be a risk of race-condition between the test
182      * and the open but that's the best we can realistically do
183      * without seteuid()... */
184     if (stat(pathname, &s) == 0) {
185         if (!
186             (s.st_mode & S_IROTH || (s.st_uid == openuid && s.st_mode & S_IRUSR)
187              || (s.st_gid == opengid && s.st_mode & S_IRGRP))) {
188             error("open_as_user(): file %s does not pass the security test: "
189                   "uid=%d gid=%d mode=%lo openuid=%d opengid=%d",
190                   pathname, s.st_uid, s.st_gid, s.st_mode, openuid, opengid);
191             errno = EACCES;
192             return -1;
193         }
194     }
195     else if (errno == ENOENT) {
196         /* the file doesn't exist so no risk to truncate the wrong file! */
197         ;
198     }
199     else {
200         saved_errno = errno;
201         error_e("open_as_user(): could not stat %s", pathname);
202         errno = saved_errno;
203         return -1;
204     }
205
206     if (flags & O_CREAT) {
207         fd = open(pathname, flags, mode);
208     }
209     else
210         fd = open(pathname, flags);
211
212     if (fd < 0)
213         /* we couldn't open the file */
214         return fd;
215
216     /* if open() didn't fail make sure we opened a 'normal' file */
217     if (fstat(fd, &s) < 0) {
218         saved_errno = errno;
219         error_e("open_as_user(): could not fstat %s", pathname);
220         goto err;
221     }
222     if (!S_ISREG(s.st_mode) || s.st_nlink != 1) {
223         saved_errno = errno;
224         error_e("open_as_user(): file %s is not a regular file", pathname);
225         goto err;
226     }
227
228     /* we couldn't become openuid/opengid, so check manually if the user
229      * is allowed to read that file
230      * We do that again as a malicious user could have replaced the file
231      * by another one (e.g. a link) between the stat() and the open() earlier */
232     if (!(s.st_mode & S_IROTH || (s.st_uid == openuid && s.st_mode & S_IRUSR)
233           || (s.st_gid == opengid && s.st_mode & S_IRGRP))) {
234         error("open_as_user(): file %s does not pass the security test: "
235               "uid=%d gid=%d mode=%lo openuid=%d opengid=%d",
236               pathname, s.st_uid, s.st_gid, s.st_mode, openuid, opengid);
237         saved_errno = EACCES;
238         goto err;
239     }
240
241     /* if we created a new file, change the file ownership:
242      * make it as it would be if we had seteuid()
243      * NOTE: if O_CREAT was set without O_EXCL and the file existed before
244      *       then we will end up changing the ownership even if the seteuid()
245      *       version of that function wouldn't have. That shouldn't break
246      *       anything though. */
247     if ((flags & O_CREAT) && fchown(fd, openuid, opengid) != 0) {
248         saved_errno = errno;
249         error_e("Could not fchown %s to uid:%d gid:%d", pathname, openuid,
250                 opengid);
251         goto err;
252     }
253
254     /* everything went ok: return the file descriptor */
255     return fd;
256
257  err:
258     if (fd >= 0 && xclose(&fd) < 0)
259         error_e("open_as_user: could not xclose() %s", pathname);
260     errno = saved_errno;
261     return -1;
262 }
263
264 #endif                          /* def USE_SETE_ID */
265
266 int
267 remove_as_user(const char *pathname, uid_t removeuid, gid_t removegid)
268 /* Become user and call remove(), then revert back to who we were */
269 {
270     int rval = -1;
271 #ifdef USE_SETE_ID
272     uid_t orig_euid = geteuid();
273     gid_t orig_egid = getegid();
274
275     seteuid_safe(removeuid);
276     setegid_safe(removegid);
277 #endif                          /* def USE_SETE_ID */
278
279     rval = remove(pathname);
280
281 #ifdef USE_SETE_ID
282     seteuid_safe(orig_euid);
283     setegid_safe(orig_egid);
284 #endif                          /* def USE_SETE_ID */
285
286     return rval;
287 }
288
289 int
290 rename_as_user(const char *oldpath, const char *newpath, uid_t renameuid,
291                gid_t renamegid)
292 /* Become user and call rename(), then revert back to who we were */
293 {
294     int rval = -1;
295 #ifdef USE_SETE_ID
296     uid_t orig_euid = geteuid();
297     gid_t orig_egid = getegid();
298
299     seteuid_safe(renameuid);
300     setegid_safe(renamegid);
301 #endif                          /* def USE_SETE_ID */
302
303     rval = rename(oldpath, newpath);
304
305 #ifdef USE_SETE_ID
306     seteuid_safe(orig_euid);
307     setegid_safe(orig_egid);
308 #endif                          /* def USE_SETE_ID */
309
310     return rval;
311
312 }
313
314 int
315 remove_blanks(char *str)
316     /* remove blanks at the the end of str */
317     /* return the length of the new string */
318 {
319     char *c = str;
320
321     /* scan forward to the null */
322     while (*c)
323         c++;
324
325     /* scan backward to the first character that is not a space */
326     do {
327         c--;
328     }
329     while (c >= str && isspace((int)*c));
330
331     /* if last char is a '\n', we remove it */
332     if (*c == '\n')
333         *c = '\0';
334     else
335         /* one character beyond where we stopped above is where the null
336          * goes. */
337         *++c = '\0';
338
339     /* return the new length */
340     return (c - str);
341
342 }
343
344 int
345 strcmp_until(const char *left, const char *right, char until)
346 /* compare two strings up to a given char (copied from Vixie cron) */
347 /* Copyright 1988,1990,1993,1994 by Paul Vixie */
348 /* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
349  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.  */
350 {
351     while (*left != '\0' && *left != until && *left == *right) {
352         left++;
353         right++;
354     }
355
356     if ((*left == '\0' || *left == until)
357         && (*right == '\0' || *right == until)) {
358         return (0);
359     }
360     return (*left - *right);
361 }
362
363 int
364 get_word(char **str)
365     /* make str point the next word and return word length */
366 {
367     char *ptr;
368
369     Skip_blanks(*str);
370     ptr = *str;
371
372     while ((isalnum((int)*ptr) || *ptr == '_' || *ptr == '-')
373            && *ptr != '=' && !isspace((int)*ptr))
374         ptr++;
375
376     return (ptr - *str);
377 }
378
379 void
380 my_unsetenv(const char *name)
381 /* call unsetenv() if available, otherwise call putenv("var=").
382  * Check for errors and log them. */
383 {
384
385 #ifdef HAVE_UNSETENV
386     if (unsetenv(name) < 0)
387         error_e("could not flush env var %s with unsetenv()", name);
388 #else
389     char buf[PATH_LEN];
390     snprintf(buf, sizeof(buf) - 1, "%s=", name);
391     buf[sizeof(buf) - 1] = '\0';
392     if (putenv(buf) < 0)
393         error_e("could not flush env var %s with putenv()", name);
394 #endif
395
396 }
397
398 void
399 my_setenv_overwrite(const char *name, const char *value)
400 /* call setenv(x, x, 1) if available, otherwise call putenv() with the appropriate
401  * constructed string.
402  * Check for errors and log them. */
403 {
404
405 #ifdef HAVE_SETENV
406
407     /* // */
408     debug("Calling setenv(%s, %s, 1)", name, value);
409     /* // */
410     if (setenv(name, value, 1) != 0)
411         error_e("setenv(%s, %s, 1) failed", name, value);
412
413 #else
414     char buf[PATH_LEN];
415
416     snprintf(buf, sizeof(buf) - 1, "%s=%s", name, value)
417
418         /* The final \0 may not have been copied because of lack of space:
419          * add it to make sure */
420         buf[sizeof(buf) - 1] = '\0';
421
422     /* // */
423     debug("Calling putenv(%s)", buf);
424     /* // */
425     if (putenv(buf) != 0)
426         error_e("putenv(%s) failed", buf);
427
428 #endif
429
430 }
431
432 time_t
433 tmax(time_t x, time_t y)
434 /* return the larger value (maximum) of x and y */
435 {
436     if (x > y) {
437         return x;
438     }
439     else {
440         return y;
441     }
442 }
443
444 time_t
445 tmin(time_t x, time_t y)
446 /* return the smaller value (minimum) of x and y */
447 {
448     if (x < y) {
449         return x;
450     }
451     else {
452         return y;
453     }
454 }
455
456 int
457 imax(int x, int y)
458 /* return the larger value (maximum) of x and y */
459 {
460     if (x > y) {
461         return x;
462     }
463     else {
464         return y;
465     }
466 }
467
468 int
469 imin(int x, int y)
470 /* return the smaller value (minimum) of x and y */
471 {
472     if (x < y) {
473         return x;
474     }
475     else {
476         return y;
477     }
478 }
479
480 time_t
481 my_time(void)
482 /* return the current time as number of seconds since the Epoch. */
483 {
484     /* Use gettimeofday() if available as this is more accurate
485      * than time() on recent Linux systems.
486      * (I suspect time() sacrifies accuracy for speed in the same way as
487      * clock_gettime()'s CLOCK_REALTIME_COARSE, and only updates on systems ticks) */
488 #ifdef HAVE_GETTIMEOFDAY
489     struct timeval tv;
490     gettimeofday(&tv, NULL);
491     return tv.tv_sec;
492 #else                           /* HAVE_GETTIMEOFDAY */
493     return time(NULL);
494 #endif                          /* HAVE_GETTIMEOFDAY */
495 }