2 * FCRON - periodic command scheduler
4 * Copyright 2000-2014 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.
29 get_user_uid_safe(char *username)
30 /* get the uid of user username, and die on error */
35 pass = getpwnam(username);
37 die_e("Unable to get the uid of user %s (is user in passwd file?)",
46 get_group_gid_safe(char *groupname)
47 /* get the gid of group groupname, and die on error */
49 struct group *grp = NULL;
52 grp = getgrnam(groupname);
54 die_e("Unable to get the gid of group %s", groupname);
63 seteuid_safe(uid_t euid)
64 /* set the euid if different from the current one, and die on error */
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 */
70 if (geteuid() != euid && seteuid(euid) != 0)
71 die_e("could not change euid to %d", euid);
76 setegid_safe(gid_t egid)
77 /* set the egid if different from the current one, and die on error */
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 */
83 if (getegid() != egid && setegid(egid) != 0)
84 die_e("could not change egid to %d", egid);
87 #endif /* def USE_SETE_ID */
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 */
96 uid_t orig_euid = geteuid();
97 gid_t orig_egid = getegid();
101 mode_t mode = (mode_t) 0;
104 if (flags & O_CREAT) {
107 (sizeof(mode_t) < sizeof(int)) ? va_arg(ap, int) : va_arg(ap,
112 seteuid_safe(openuid);
113 setegid_safe(opengid);
115 if (flags & O_CREAT) {
116 fd = open(pathname, flags, mode);
119 fd = open(pathname, flags);
123 /* change the effective uid/gid back to original values */
124 seteuid_safe(orig_euid);
125 setegid_safe(orig_egid);
127 /* if open() didn't fail make sure we opened a 'normal' file */
130 if (fstat(fd, &s) < 0) {
132 error_e("open_as_user(): could not fstat %s", pathname);
134 error_e("open_as_user: could not xclose() %s", pathname);
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);
141 error_e("open_as_user: could not xclose() %s", pathname);
154 #else /* def USE_SETE_ID */
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 */
167 mode_t mode = (mode_t) 0;
170 if (flags & O_CREAT) {
173 (sizeof(mode_t) < sizeof(int)) ? va_arg(ap, int) : va_arg(ap,
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) {
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);
194 else if (errno == ENOENT) {
195 /* the file doesn't exist so no risk to truncate the wrong file! */
200 error_e("open_as_user(): could not stat %s", pathname);
205 if (flags & O_CREAT) {
206 fd = open(pathname, flags, mode);
209 fd = open(pathname, flags);
212 /* we couldn't open the file */
215 /* if open() didn't fail make sure we opened a 'normal' file */
216 if (fstat(fd, &s) < 0) {
218 error_e("open_as_user(): could not fstat %s", pathname);
221 if (!S_ISREG(s.st_mode) || s.st_nlink != 1) {
223 error_e("open_as_user(): file %s is not a regular file", pathname);
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;
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) {
248 error_e("Could not fchown %s to uid:%d gid:%d", pathname, openuid,
253 /* everything went ok: return the file descriptor */
257 if (fd >= 0 && xclose(&fd) < 0)
258 error_e("open_as_user: could not xclose() %s", pathname);
263 #endif /* def USE_SETE_ID */
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 */
271 uid_t orig_euid = geteuid();
272 gid_t orig_egid = getegid();
274 seteuid_safe(removeuid);
275 setegid_safe(removegid);
276 #endif /* def USE_SETE_ID */
278 rval = remove(pathname);
281 seteuid_safe(orig_euid);
282 setegid_safe(orig_egid);
283 #endif /* def USE_SETE_ID */
289 rename_as_user(const char *oldpath, const char *newpath, uid_t renameuid,
291 /* Become user and call rename(), then revert back to who we were */
295 uid_t orig_euid = geteuid();
296 gid_t orig_egid = getegid();
298 seteuid_safe(renameuid);
299 setegid_safe(renamegid);
300 #endif /* def USE_SETE_ID */
302 rval = rename(oldpath, newpath);
305 seteuid_safe(orig_euid);
306 setegid_safe(orig_egid);
307 #endif /* def USE_SETE_ID */
314 remove_blanks(char *str)
315 /* remove blanks at the the end of str */
316 /* return the length of the new string */
320 /* scan forward to the null */
324 /* scan backward to the first character that is not a space */
328 while (c >= str && isspace((int)*c));
330 /* if last char is a '\n', we remove it */
334 /* one character beyond where we stopped above is where the null
338 /* return the new length */
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. */
350 while (*left != '\0' && *left != until && *left == *right) {
355 if ((*left == '\0' || *left == until)
356 && (*right == '\0' || *right == until)) {
359 return (*left - *right);
364 /* make str point the next word and return word length */
371 while ((isalnum((int)*ptr) || *ptr == '_' || *ptr == '-')
372 && *ptr != '=' && !isspace((int)*ptr))
379 my_unsetenv(const char *name)
380 /* call unsetenv() if available, otherwise call putenv("var=").
381 * Check for errors and log them. */
385 if (unsetenv(name) < 0)
386 error_e("could not flush env var %s with unsetenv()", name);
389 snprintf(buf, sizeof(buf) - 1, "%s=", name);
390 buf[sizeof(buf) - 1] = '\0';
392 error_e("could not flush env var %s with putenv()", name);
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. */
407 debug("Calling setenv(%s, %s, 1)", name, value);
409 if (setenv(name, value, 1) != 0)
410 error_e("setenv(%s, %s, 1) failed", name, value);
415 snprintf(buf, sizeof(buf) - 1, "%s=%s", name, value)
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';
422 debug("Calling putenv(%s)", buf);
424 if (putenv(buf) != 0)
425 error_e("putenv(%s) failed", buf);