]> granicus.if.org Git - fcron/blob - subs.c
don't fully disable @-line on apparent nextexe integer overflows: instead set nextexe...
[fcron] / subs.c
1 /*
2  * FCRON - periodic command scheduler 
3  *
4  *  Copyright 2000-2014 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
28 uid_t
29 get_user_uid_safe(char *username)
30     /* get the uid of user username, and die on error */
31 {
32     struct passwd *pass;
33
34     errno = 0;
35     pass = getpwnam(username);
36     if (pass == NULL) {
37         die_e("Unable to get the uid of user %s (is user in passwd file?)",
38               username);
39     }
40
41     return pass->pw_uid;
42
43 }
44
45 gid_t
46 get_group_gid_safe(char *groupname)
47     /* get the gid of group groupname, and die on error */
48 {
49     struct group *grp = NULL;
50
51     errno = 0;
52     grp = getgrnam(groupname);
53     if (grp == NULL) {
54         die_e("Unable to get the gid of group %s", groupname);
55     }
56
57     return grp->gr_gid;
58
59 }
60
61 #ifdef USE_SETE_ID
62 void
63 seteuid_safe(uid_t euid)
64 /* set the euid if different from the current one, and die on error */
65 {
66     /* on BSD, one can only seteuid() to the real UID or saved UID,
67      * and NOT the euid, so we don't call seteuid(geteuid()),
68      * which is why we need to check if a change is needed */
69
70     if (geteuid() != euid && seteuid(euid) != 0)
71         die_e("could not change euid to %d", euid);
72
73 }
74
75 void
76 setegid_safe(gid_t egid)
77 /* set the egid if different from the current one, and die on error */
78 {
79     /* on BSD, one can only setegid() to the real GID or saved GID,
80      * and NOT the egid, so we don't call setegid(getegid()),
81      * which is why we need to check if a change is needed */
82
83     if (getegid() != egid && setegid(egid) != 0)
84         die_e("could not change egid to %d", egid);
85
86 }
87 #endif                          /* def USE_SETE_ID */
88
89 #ifdef USE_SETE_ID
90 int
91 open_as_user(const char *pathname, uid_t openuid, gid_t opengid, int flags, ...)
92 /* Become user and call open(), then revert back to who we were.
93  * NOTE: when flags & O_CREAT, the 5th argument is mode_t and must be set
94  *       -- it is ignored otherwise */
95 {
96     uid_t orig_euid = geteuid();
97     gid_t orig_egid = getegid();
98     struct stat s;
99     int fd = -1;
100     va_list ap;
101     mode_t mode = (mode_t) 0;
102     int saved_errno = 0;
103
104     if (flags & O_CREAT) {
105         va_start(ap, flags);
106         mode =
107             (sizeof(mode_t) < sizeof(int)) ? va_arg(ap, int) : va_arg(ap,
108                                                                       mode_t);
109         va_end(ap);
110     }
111
112     seteuid_safe(openuid);
113     setegid_safe(opengid);
114
115     if (flags & O_CREAT) {
116         fd = open(pathname, flags, mode);
117     }
118     else
119         fd = open(pathname, flags);
120
121     saved_errno = errno;
122
123     /* change the effective uid/gid back to original values */
124     seteuid_safe(orig_euid);
125     setegid_safe(orig_egid);
126
127     /* if open() didn't fail make sure we opened a 'normal' file */
128     if (fd >= 0) {
129
130         if (fstat(fd, &s) < 0) {
131             saved_errno = errno;
132             error_e("open_as_user(): could not fstat %s", pathname);
133             if (xclose(&fd) < 0)
134                 error_e("open_as_user: could not xclose() %s", pathname);
135             fd = -1;
136         }
137
138         if (!S_ISREG(s.st_mode) || s.st_nlink != 1) {
139             error_e("open_as_user(): file %s is not a regular file", pathname);
140             if (xclose(&fd) < 0)
141                 error_e("open_as_user: could not xclose() %s", pathname);
142             saved_errno = 0;
143             fd = -1;
144         }
145
146     }
147
148     errno = saved_errno;
149
150     return fd;
151
152 }
153
154 #else                           /* def USE_SETE_ID */
155
156 int
157 open_as_user(const char *pathname, uid_t openuid, gid_t opengid, int flags, ...)
158 /* Become user and call open(), then revert back to who we were.
159  * As seteuid() is not available on this system attempt to similate that behavior
160  * as closely as possible.
161  * NOTE: when flags & O_CREAT, the 5th argument is mode_t and must be set
162  *       -- it is ignored otherwise */
163 {
164     int fd = -1;
165     struct stat s;
166     va_list ap;
167     mode_t mode = (mode_t) 0;
168     int saved_errno = 0;
169
170     if (flags & O_CREAT) {
171         va_start(ap, flags);
172         mode =
173             (sizeof(mode_t) < sizeof(int)) ? va_arg(ap, int) : va_arg(ap,
174                                                                       mode_t);
175         va_end(ap);
176     }
177
178     /* In case a flag as O_TRUNC is set, we should test if the user
179      * is allowed to open the file before we open it.
180      * There will always be a risk of race-condition between the test
181      * and the open but that's the best we can realistically do
182      * without seteuid()... */
183     if (stat(pathname, &s) == 0) {
184         if (!
185             (s.st_mode & S_IROTH || (s.st_uid == openuid && s.st_mode & S_IRUSR)
186              || (s.st_gid == opengid && s.st_mode & S_IRGRP))) {
187             error("open_as_user(): file %s does not pass the security test: "
188                   "uid=%d gid=%d mode=%lo openuid=%d opengid=%d",
189                   pathname, s.st_uid, s.st_gid, s.st_mode, openuid, opengid);
190             errno = EACCES;
191             return -1;
192         }
193     }
194     else if (errno == ENOENT) {
195         /* the file doesn't exist so no risk to truncate the wrong file! */
196         ;
197     }
198     else {
199         saved_errno = errno;
200         error_e("open_as_user(): could not stat %s", pathname);
201         errno = saved_errno;
202         return -1;
203     }
204
205     if (flags & O_CREAT) {
206         fd = open(pathname, flags, mode);
207     }
208     else
209         fd = open(pathname, flags);
210
211     if (fd < 0)
212         /* we couldn't open the file */
213         return fd;
214
215     /* if open() didn't fail make sure we opened a 'normal' file */
216     if (fstat(fd, &s) < 0) {
217         saved_errno = errno;
218         error_e("open_as_user(): could not fstat %s", pathname);
219         goto err;
220     }
221     if (!S_ISREG(s.st_mode) || s.st_nlink != 1) {
222         saved_errno = errno;
223         error_e("open_as_user(): file %s is not a regular file", pathname);
224         goto err;
225     }
226
227     /* we couldn't become openuid/opengid, so check manually if the user
228      * is allowed to read that file
229      * We do that again as a malicious user could have replaced the file
230      * by another one (e.g. a link) between the stat() and the open() earlier */
231     if (!(s.st_mode & S_IROTH || (s.st_uid == openuid && s.st_mode & S_IRUSR)
232           || (s.st_gid == opengid && s.st_mode & S_IRGRP))) {
233         error("open_as_user(): file %s does not pass the security test: "
234               "uid=%d gid=%d mode=%lo openuid=%d opengid=%d",
235               pathname, s.st_uid, s.st_gid, s.st_mode, openuid, opengid);
236         saved_errno = EACCES;
237         goto err;
238     }
239
240     /* if we created a new file, change the file ownership:
241      * make it as it would be if we had seteuid()
242      * NOTE: if O_CREAT was set without O_EXCL and the file existed before
243      *       then we will end up changing the ownership even if the seteuid()
244      *       version of that function wouldn't have. That shouldn't break
245      *       anything though. */
246     if ((flags & O_CREAT) && fchown(fd, openuid, opengid) != 0) {
247         saved_errno = errno;
248         error_e("Could not fchown %s to uid:%d gid:%d", pathname, openuid,
249                 opengid);
250         goto err;
251     }
252
253     /* everything went ok: return the file descriptor */
254     return fd;
255
256  err:
257     if (fd >= 0 && xclose(&fd) < 0)
258         error_e("open_as_user: could not xclose() %s", pathname);
259     errno = saved_errno;
260     return -1;
261 }
262
263 #endif                          /* def USE_SETE_ID */
264
265 int
266 remove_as_user(const char *pathname, uid_t removeuid, gid_t removegid)
267 /* Become user and call remove(), then revert back to who we were */
268 {
269     int rval = -1;
270 #ifdef USE_SETE_ID
271     uid_t orig_euid = geteuid();
272     gid_t orig_egid = getegid();
273
274     seteuid_safe(removeuid);
275     setegid_safe(removegid);
276 #endif                          /* def USE_SETE_ID */
277
278     rval = remove(pathname);
279
280 #ifdef USE_SETE_ID
281     seteuid_safe(orig_euid);
282     setegid_safe(orig_egid);
283 #endif                          /* def USE_SETE_ID */
284
285     return rval;
286 }
287
288 int
289 rename_as_user(const char *oldpath, const char *newpath, uid_t renameuid,
290                gid_t renamegid)
291 /* Become user and call rename(), then revert back to who we were */
292 {
293     int rval = -1;
294 #ifdef USE_SETE_ID
295     uid_t orig_euid = geteuid();
296     gid_t orig_egid = getegid();
297
298     seteuid_safe(renameuid);
299     setegid_safe(renamegid);
300 #endif                          /* def USE_SETE_ID */
301
302     rval = rename(oldpath, newpath);
303
304 #ifdef USE_SETE_ID
305     seteuid_safe(orig_euid);
306     setegid_safe(orig_egid);
307 #endif                          /* def USE_SETE_ID */
308
309     return rval;
310
311 }
312
313 int
314 remove_blanks(char *str)
315     /* remove blanks at the the end of str */
316     /* return the length of the new string */
317 {
318     char *c = str;
319
320     /* scan forward to the null */
321     while (*c)
322         c++;
323
324     /* scan backward to the first character that is not a space */
325     do {
326         c--;
327     }
328     while (c >= str && isspace((int)*c));
329
330     /* if last char is a '\n', we remove it */
331     if (*c == '\n')
332         *c = '\0';
333     else
334         /* one character beyond where we stopped above is where the null
335          * goes. */
336         *++c = '\0';
337
338     /* return the new length */
339     return (c - str);
340
341 }
342
343 int
344 strcmp_until(const char *left, const char *right, char until)
345 /* compare two strings up to a given char (copied from Vixie cron) */
346 /* Copyright 1988,1990,1993,1994 by Paul Vixie */
347 /* Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
348  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.  */
349 {
350     while (*left != '\0' && *left != until && *left == *right) {
351         left++;
352         right++;
353     }
354
355     if ((*left == '\0' || *left == until)
356         && (*right == '\0' || *right == until)) {
357         return (0);
358     }
359     return (*left - *right);
360 }
361
362 int
363 get_word(char **str)
364     /* make str point the next word and return word length */
365 {
366     char *ptr;
367
368     Skip_blanks(*str);
369     ptr = *str;
370
371     while ((isalnum((int)*ptr) || *ptr == '_' || *ptr == '-')
372            && *ptr != '=' && !isspace((int)*ptr))
373         ptr++;
374
375     return (ptr - *str);
376 }
377
378 void
379 my_unsetenv(const char *name)
380 /* call unsetenv() if available, otherwise call putenv("var=").
381  * Check for errors and log them. */
382 {
383
384 #ifdef HAVE_UNSETENV
385     if (unsetenv(name) < 0)
386         error_e("could not flush env var %s with unsetenv()", name);
387 #else
388     char buf[PATH_LEN];
389     snprintf(buf, sizeof(buf) - 1, "%s=", name);
390     buf[sizeof(buf) - 1] = '\0';
391     if (putenv(buf) < 0)
392         error_e("could not flush env var %s with putenv()", name);
393 #endif
394
395 }
396
397 void
398 my_setenv_overwrite(const char *name, const char *value)
399 /* call setenv(x, x, 1) if available, otherwise call putenv() with the appropriate
400  * constructed string.
401  * Check for errors and log them. */
402 {
403
404 #ifdef HAVE_SETENV
405
406     /* // */
407     debug("Calling setenv(%s, %s, 1)", name, value);
408     /* // */
409     if (setenv(name, value, 1) != 0)
410         error_e("setenv(%s, %s, 1) failed", name, value);
411
412 #else
413     char buf[PATH_LEN];
414
415     snprintf(buf, sizeof(buf) - 1, "%s=%s", name, value)
416
417         /* The final \0 may not have been copied because of lack of space:
418          * add it to make sure */
419         buf[sizeof(buf) - 1] = '\0';
420
421     /* // */
422     debug("Calling putenv(%s)", buf);
423     /* // */
424     if (putenv(buf) != 0)
425         error_e("putenv(%s) failed", buf);
426
427 #endif
428
429 }