2 * FCRON - periodic command scheduler
4 * Copyright 2000-2016 Thibault Godouet <fcron@free.fr>
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.
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.
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
20 * The GNU General Public License can also be found in the file
21 * `LICENSE' that comes with the fcron source distribution.
30 get_user_uid_safe(char *username)
31 /* get the uid of user username, and die on error */
36 pass = getpwnam(username);
38 die_e("Unable to get the uid of user %s (is user in passwd file?)",
47 get_group_gid_safe(char *groupname)
48 /* get the gid of group groupname, and die on error */
50 struct group *grp = NULL;
53 grp = getgrnam(groupname);
55 die_e("Unable to get the gid of group %s", groupname);
64 seteuid_safe(uid_t euid)
65 /* set the euid if different from the current one, and die on error */
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 */
71 if (geteuid() != euid && seteuid(euid) != 0)
72 die_e("could not change euid to %d", euid);
77 setegid_safe(gid_t egid)
78 /* set the egid if different from the current one, and die on error */
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 */
84 if (getegid() != egid && setegid(egid) != 0)
85 die_e("could not change egid to %d", egid);
88 #endif /* def USE_SETE_ID */
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 */
97 uid_t orig_euid = geteuid();
98 gid_t orig_egid = getegid();
102 mode_t mode = (mode_t) 0;
105 if (flags & O_CREAT) {
108 (sizeof(mode_t) < sizeof(int)) ? va_arg(ap, int) : va_arg(ap,
113 seteuid_safe(openuid);
114 setegid_safe(opengid);
116 if (flags & O_CREAT) {
117 fd = open(pathname, flags, mode);
120 fd = open(pathname, flags);
124 /* change the effective uid/gid back to original values */
125 seteuid_safe(orig_euid);
126 setegid_safe(orig_egid);
128 /* if open() didn't fail make sure we opened a 'normal' file */
131 if (fstat(fd, &s) < 0) {
133 error_e("open_as_user(): could not fstat %s", pathname);
135 error_e("open_as_user: could not xclose() %s", pathname);
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);
142 error_e("open_as_user: could not xclose() %s", pathname);
155 #else /* def USE_SETE_ID */
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 */
168 mode_t mode = (mode_t) 0;
171 if (flags & O_CREAT) {
174 (sizeof(mode_t) < sizeof(int)) ? va_arg(ap, int) : va_arg(ap,
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) {
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);
195 else if (errno == ENOENT) {
196 /* the file doesn't exist so no risk to truncate the wrong file! */
201 error_e("open_as_user(): could not stat %s", pathname);
206 if (flags & O_CREAT) {
207 fd = open(pathname, flags, mode);
210 fd = open(pathname, flags);
213 /* we couldn't open the file */
216 /* if open() didn't fail make sure we opened a 'normal' file */
217 if (fstat(fd, &s) < 0) {
219 error_e("open_as_user(): could not fstat %s", pathname);
222 if (!S_ISREG(s.st_mode) || s.st_nlink != 1) {
224 error_e("open_as_user(): file %s is not a regular file", pathname);
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;
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) {
249 error_e("Could not fchown %s to uid:%d gid:%d", pathname, openuid,
254 /* everything went ok: return the file descriptor */
258 if (fd >= 0 && xclose(&fd) < 0)
259 error_e("open_as_user: could not xclose() %s", pathname);
264 #endif /* def USE_SETE_ID */
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 */
272 uid_t orig_euid = geteuid();
273 gid_t orig_egid = getegid();
275 seteuid_safe(removeuid);
276 setegid_safe(removegid);
277 #endif /* def USE_SETE_ID */
279 rval = remove(pathname);
282 seteuid_safe(orig_euid);
283 setegid_safe(orig_egid);
284 #endif /* def USE_SETE_ID */
290 rename_as_user(const char *oldpath, const char *newpath, uid_t renameuid,
292 /* Become user and call rename(), then revert back to who we were */
296 uid_t orig_euid = geteuid();
297 gid_t orig_egid = getegid();
299 seteuid_safe(renameuid);
300 setegid_safe(renamegid);
301 #endif /* def USE_SETE_ID */
303 rval = rename(oldpath, newpath);
306 seteuid_safe(orig_euid);
307 setegid_safe(orig_egid);
308 #endif /* def USE_SETE_ID */
315 remove_blanks(char *str)
316 /* remove blanks at the the end of str */
317 /* return the length of the new string */
321 /* scan forward to the null */
325 /* scan backward to the first character that is not a space */
329 while (c >= str && isspace((int)*c));
331 /* if last char is a '\n', we remove it */
335 /* one character beyond where we stopped above is where the null
339 /* return the new length */
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. */
351 while (*left != '\0' && *left != until && *left == *right) {
356 if ((*left == '\0' || *left == until)
357 && (*right == '\0' || *right == until)) {
360 return (*left - *right);
365 /* make str point the next word and return word length */
372 while ((isalnum((int)*ptr) || *ptr == '_' || *ptr == '-')
373 && *ptr != '=' && !isspace((int)*ptr))
380 my_unsetenv(const char *name)
381 /* call unsetenv() if available, otherwise call putenv("var=").
382 * Check for errors and log them. */
386 if (unsetenv(name) < 0)
387 error_e("could not flush env var %s with unsetenv()", name);
390 snprintf(buf, sizeof(buf) - 1, "%s=", name);
391 buf[sizeof(buf) - 1] = '\0';
393 error_e("could not flush env var %s with putenv()", name);
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. */
408 debug("Calling setenv(%s, %s, 1)", name, value);
410 if (setenv(name, value, 1) != 0)
411 error_e("setenv(%s, %s, 1) failed", name, value);
416 snprintf(buf, sizeof(buf) - 1, "%s=%s", name, value)
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';
423 debug("Calling putenv(%s)", buf);
425 if (putenv(buf) != 0)
426 error_e("putenv(%s) failed", buf);
433 tmax(time_t x, time_t y)
434 /* return the larger value (maximum) of x and y */
445 tmin(time_t x, time_t y)
446 /* return the smaller value (minimum) of x and y */
458 /* return the larger value (maximum) of x and y */
470 /* return the smaller value (minimum) of x and y */
482 /* return the current time as number of seconds since the Epoch. */
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
490 gettimeofday(&tv, NULL);
492 #else /* HAVE_GETTIMEOFDAY */
494 #endif /* HAVE_GETTIMEOFDAY */