X-Git-Url: http://www.chiark.greenend.org.uk/ucgi/~ianmdlvl/git?p=elogind.git;a=blobdiff_plain;f=src%2Fshared%2Futil.c;h=43ec62eac653d67a10190187c82eb0c76a551934;hp=53185403d62d8e762682d1f7b499e04904fd15c1;hb=d05c5031ad4c528fe6bbfed289519edb9f13180a;hpb=f67cc036ba92a3c71acb664ed2d548de5827cf1f diff --git a/src/shared/util.c b/src/shared/util.c index 53185403d..43ec62eac 100644 --- a/src/shared/util.c +++ b/src/shared/util.c @@ -50,11 +50,12 @@ #include #include #include -#include #include #include #include #include +#include +#include #include "macro.h" #include "util.h" @@ -1082,7 +1083,7 @@ int get_process_cmdline(pid_t pid, size_t max_length, bool comm_fallback, char * if (h < 0) return h; - r = join("[", t, "]", NULL); + r = strjoin("[", t, "]", NULL); free(t); if (!r) @@ -2306,12 +2307,14 @@ int open_terminal(const char *name, int mode) { */ for (;;) { - if ((fd = open(name, mode)) >= 0) + fd = open(name, mode); + if (fd >= 0) break; if (errno != EIO) return -errno; + /* Max 1s in total */ if (c >= 20) return -errno; @@ -2322,7 +2325,8 @@ int open_terminal(const char *name, int mode) { if (fd < 0) return -errno; - if ((r = isatty(fd)) < 0) { + r = isatty(fd); + if (r < 0) { close_nointr_nofail(fd); return -errno; } @@ -2374,8 +2378,16 @@ int flush_fd(int fd) { } } -int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocstty_eperm) { - int fd = -1, notify = -1, r, wd = -1; +int acquire_terminal( + const char *name, + bool fail, + bool force, + bool ignore_tiocstty_eperm, + usec_t timeout) { + + int fd = -1, notify = -1, r = 0, wd = -1; + usec_t ts = 0; + struct sigaction sa_old, sa_new; assert(name); @@ -2392,40 +2404,57 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst * on the same tty as an untrusted user this should not be a * problem. (Which he probably should not do anyway.) */ + if (timeout != (usec_t) -1) + ts = now(CLOCK_MONOTONIC); + if (!fail && !force) { - if ((notify = inotify_init1(IN_CLOEXEC)) < 0) { + notify = inotify_init1(IN_CLOEXEC | (timeout != (usec_t) -1 ? IN_NONBLOCK : 0)); + if (notify < 0) { r = -errno; goto fail; } - if ((wd = inotify_add_watch(notify, name, IN_CLOSE)) < 0) { + wd = inotify_add_watch(notify, name, IN_CLOSE); + if (wd < 0) { r = -errno; goto fail; } } for (;;) { - if (notify >= 0) - if ((r = flush_fd(notify)) < 0) + if (notify >= 0) { + r = flush_fd(notify); + if (r < 0) goto fail; + } /* We pass here O_NOCTTY only so that we can check the return * value TIOCSCTTY and have a reliable way to figure out if we * successfully became the controlling process of the tty */ - if ((fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC)) < 0) + fd = open_terminal(name, O_RDWR|O_NOCTTY|O_CLOEXEC); + if (fd < 0) return fd; + /* Temporarily ignore SIGHUP, so that we don't get SIGHUP'ed + * if we already own the tty. */ + zero(sa_new); + sa_new.sa_handler = SIG_IGN; + sa_new.sa_flags = SA_RESTART; + assert_se(sigaction(SIGHUP, &sa_new, &sa_old) == 0); + /* First, try to get the tty */ - r = ioctl(fd, TIOCSCTTY, force); + if (ioctl(fd, TIOCSCTTY, force) < 0) + r = -errno; + + assert_se(sigaction(SIGHUP, &sa_old, NULL) == 0); /* Sometimes it makes sense to ignore TIOCSCTTY * returning EPERM, i.e. when very likely we already * are have this controlling terminal. */ - if (r < 0 && errno == EPERM && ignore_tiocstty_eperm) + if (r < 0 && r == -EPERM && ignore_tiocstty_eperm) r = 0; - if (r < 0 && (force || fail || errno != EPERM)) { - r = -errno; + if (r < 0 && (force || fail || r != -EPERM)) { goto fail; } @@ -2441,9 +2470,29 @@ int acquire_terminal(const char *name, bool fail, bool force, bool ignore_tiocst ssize_t l; struct inotify_event *e; - if ((l = read(notify, inotify_buffer, sizeof(inotify_buffer))) < 0) { + if (timeout != (usec_t) -1) { + usec_t n; - if (errno == EINTR) + n = now(CLOCK_MONOTONIC); + if (ts + timeout < n) { + r = -ETIMEDOUT; + goto fail; + } + + r = fd_wait_for_event(fd, POLLIN, ts + timeout - n); + if (r < 0) + goto fail; + + if (r == 0) { + r = -ETIMEDOUT; + goto fail; + } + } + + l = read(notify, inotify_buffer, sizeof(inotify_buffer)); + if (l < 0) { + + if (errno == EINTR || errno == EAGAIN) continue; r = -errno; @@ -2707,7 +2756,7 @@ int parse_usec(const char *t, usec_t *usec) { { "m", USEC_PER_MINUTE }, { "usec", 1ULL }, { "us", 1ULL }, - { "", USEC_PER_SEC }, + { "", USEC_PER_SEC }, /* default is sec */ }; const char *p; @@ -2753,6 +2802,71 @@ int parse_usec(const char *t, usec_t *usec) { return 0; } +int parse_nsec(const char *t, nsec_t *nsec) { + static const struct { + const char *suffix; + nsec_t nsec; + } table[] = { + { "sec", NSEC_PER_SEC }, + { "s", NSEC_PER_SEC }, + { "min", NSEC_PER_MINUTE }, + { "hr", NSEC_PER_HOUR }, + { "h", NSEC_PER_HOUR }, + { "d", NSEC_PER_DAY }, + { "w", NSEC_PER_WEEK }, + { "msec", NSEC_PER_MSEC }, + { "ms", NSEC_PER_MSEC }, + { "m", NSEC_PER_MINUTE }, + { "usec", NSEC_PER_USEC }, + { "us", NSEC_PER_USEC }, + { "nsec", 1ULL }, + { "ns", 1ULL }, + { "", 1ULL }, /* default is nsec */ + }; + + const char *p; + nsec_t r = 0; + + assert(t); + assert(nsec); + + p = t; + do { + long long l; + char *e; + unsigned i; + + errno = 0; + l = strtoll(p, &e, 10); + + if (errno != 0) + return -errno; + + if (l < 0) + return -ERANGE; + + if (e == p) + return -EINVAL; + + e += strspn(e, WHITESPACE); + + for (i = 0; i < ELEMENTSOF(table); i++) + if (startswith(e, table[i].suffix)) { + r += (nsec_t) l * table[i].nsec; + p = e + strlen(table[i].suffix); + break; + } + + if (i >= ELEMENTSOF(table)) + return -EINVAL; + + } while (*p != 0); + + *nsec = r; + + return 0; +} + int parse_bytes(const char *t, off_t *bytes) { static const struct { const char *suffix; @@ -2836,7 +2950,8 @@ int make_stdio(int fd) { int make_null_stdio(void) { int null_fd; - if ((null_fd = open("/dev/null", O_RDWR|O_NOCTTY)) < 0) + null_fd = open("/dev/null", O_RDWR|O_NOCTTY); + if (null_fd < 0) return -errno; return make_stdio(null_fd); @@ -2963,26 +3078,22 @@ bool hostname_is_set(void) { return !isempty(u.nodename) && !streq(u.nodename, "(none)"); } -char* getlogname_malloc(void) { - uid_t uid; + +static char *lookup_uid(uid_t uid) { long bufsize; char *buf, *name; struct passwd pwbuf, *pw = NULL; - struct stat st; - - if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) - uid = st.st_uid; - else - uid = getuid(); /* Shortcut things to avoid NSS lookups */ if (uid == 0) return strdup("root"); - if ((bufsize = sysconf(_SC_GETPW_R_SIZE_MAX)) <= 0) + bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); + if (bufsize <= 0) bufsize = 4096; - if (!(buf = malloc(bufsize))) + buf = malloc(bufsize); + if (!buf) return NULL; if (getpwuid_r(uid, &pwbuf, buf, bufsize, &pw) == 0 && pw) { @@ -2999,6 +3110,28 @@ char* getlogname_malloc(void) { return name; } +char* getlogname_malloc(void) { + uid_t uid; + struct stat st; + + if (isatty(STDIN_FILENO) && fstat(STDIN_FILENO, &st) >= 0) + uid = st.st_uid; + else + uid = getuid(); + + return lookup_uid(uid); +} + +char *getusername_malloc(void) { + const char *e; + + e = getenv("USER"); + if (e) + return strdup(e); + + return lookup_uid(getuid()); +} + int getttyname_malloc(int fd, char **r) { char path[PATH_MAX], *c; int k; @@ -3139,14 +3272,14 @@ int get_ctty(pid_t pid, dev_t *_devnr, char **r) { return 0; } -int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { +int rm_rf_children_dangerous(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { DIR *d; int ret = 0; assert(fd >= 0); /* This returns the first error we run into, but nevertheless - * tries to go on */ + * tries to go on. This closes the passed fd. */ d = fdopendir(fd); if (!d) { @@ -3157,7 +3290,8 @@ int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { for (;;) { struct dirent buf, *de; - bool is_dir, keep_around = false; + bool is_dir, keep_around; + struct stat st; int r; r = readdir_r(d, &buf, &de); @@ -3172,51 +3306,41 @@ int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { if (streq(de->d_name, ".") || streq(de->d_name, "..")) continue; - if (de->d_type == DT_UNKNOWN) { - struct stat st; - + if (de->d_type == DT_UNKNOWN || + honour_sticky || + (de->d_type == DT_DIR && root_dev)) { if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { if (ret == 0 && errno != ENOENT) ret = -errno; continue; } - if (honour_sticky) - keep_around = - (st.st_uid == 0 || st.st_uid == getuid()) && - (st.st_mode & S_ISVTX); - is_dir = S_ISDIR(st.st_mode); - + keep_around = + honour_sticky && + (st.st_uid == 0 || st.st_uid == getuid()) && + (st.st_mode & S_ISVTX); } else { - if (honour_sticky) { - struct stat st; - - if (fstatat(fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) < 0) { - if (ret == 0 && errno != ENOENT) - ret = -errno; - continue; - } - - keep_around = - (st.st_uid == 0 || st.st_uid == getuid()) && - (st.st_mode & S_ISVTX); - } - is_dir = de->d_type == DT_DIR; + keep_around = false; } if (is_dir) { int subdir_fd; - subdir_fd = openat(fd, de->d_name, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); + /* if root_dev is set, remove subdirectories only, if device is same as dir */ + if (root_dev && st.st_dev != root_dev->st_dev) + continue; + + subdir_fd = openat(fd, de->d_name, + O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); if (subdir_fd < 0) { if (ret == 0 && errno != ENOENT) ret = -errno; continue; } - r = rm_rf_children(subdir_fd, only_dirs, honour_sticky); + r = rm_rf_children(subdir_fd, only_dirs, honour_sticky, root_dev); if (r < 0 && ret == 0) ret = r; @@ -3240,18 +3364,61 @@ int rm_rf_children(int fd, bool only_dirs, bool honour_sticky) { return ret; } -int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { - int fd; - int r; +int rm_rf_children(int fd, bool only_dirs, bool honour_sticky, struct stat *root_dev) { + struct statfs s; + + assert(fd >= 0); + + if (fstatfs(fd, &s) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + /* We refuse to clean disk file systems with this call. This + * is extra paranoia just to be sure we never ever remove + * non-state data */ + + if (s.f_type != TMPFS_MAGIC && + s.f_type != RAMFS_MAGIC) { + log_error("Attempted to remove disk file system, and we can't allow that."); + close_nointr_nofail(fd); + return -EPERM; + } + + return rm_rf_children_dangerous(fd, only_dirs, honour_sticky, root_dev); +} + +static int rm_rf_internal(const char *path, bool only_dirs, bool delete_root, bool honour_sticky, bool dangerous) { + int fd, r; + struct statfs s; assert(path); + /* We refuse to clean the root file system with this + * call. This is extra paranoia to never cause a really + * seriously broken system. */ + if (path_equal(path, "/")) { + log_error("Attempted to remove entire root file system, and we can't allow that."); + return -EPERM; + } + fd = open(path, O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW|O_NOATIME); if (fd < 0) { if (errno != ENOTDIR) return -errno; + if (!dangerous) { + if (statfs(path, &s) < 0) + return -errno; + + if (s.f_type != TMPFS_MAGIC && + s.f_type != RAMFS_MAGIC) { + log_error("Attempted to remove disk file system, and we can't allow that."); + return -EPERM; + } + } + if (delete_root && !only_dirs) if (unlink(path) < 0 && errno != ENOENT) return -errno; @@ -3259,8 +3426,21 @@ int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky return 0; } - r = rm_rf_children(fd, only_dirs, honour_sticky); + if (!dangerous) { + if (fstatfs(fd, &s) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + if (s.f_type != TMPFS_MAGIC && + s.f_type != RAMFS_MAGIC) { + log_error("Attempted to remove disk file system, and we can't allow that."); + close_nointr_nofail(fd); + return -EPERM; + } + } + r = rm_rf_children_dangerous(fd, only_dirs, honour_sticky, NULL); if (delete_root) { if (honour_sticky && file_is_priv_sticky(path) > 0) @@ -3275,6 +3455,14 @@ int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky return r; } +int rm_rf(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { + return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, false); +} + +int rm_rf_dangerous(const char *path, bool only_dirs, bool delete_root, bool honour_sticky) { + return rm_rf_internal(path, only_dirs, delete_root, honour_sticky, true); +} + int chmod_and_chown(const char *path, mode_t mode, uid_t uid, gid_t gid) { assert(path); @@ -4717,7 +4905,12 @@ int socket_from_display(const char *display, char **path) { return 0; } -int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **home) { +int get_user_creds( + const char **username, + uid_t *uid, gid_t *gid, + const char **home, + const char **shell) { + struct passwd *p; uid_t u; @@ -4738,6 +4931,10 @@ int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **h if (home) *home = "/root"; + + if (shell) + *shell = "/bin/sh"; + return 0; } @@ -4769,6 +4966,9 @@ int get_user_creds(const char **username, uid_t *uid, gid_t *gid, const char **h if (home) *home = p->pw_dir; + if (shell) + *shell = p->pw_shell; + return 0; } @@ -4985,7 +5185,7 @@ finish: return r; } -char *join(const char *x, ...) { +char *strjoin(const char *x, ...) { va_list ap; size_t l; char *r, *p; @@ -5639,3 +5839,163 @@ bool is_valid_documentation_url(const char *url) { return false; } + +bool in_initrd(void) { + static int saved = -1; + struct statfs s; + + if (saved >= 0) + return saved; + + /* We make two checks here: + * + * 1. the flag file /etc/initrd-release must exist + * 2. the root file system must be a memory file system + * + * The second check is extra paranoia, since misdetecting an + * initrd can have bad bad consequences due the initrd + * emptying when transititioning to the main systemd. + */ + + saved = access("/etc/initrd-release", F_OK) >= 0 && + statfs("/", &s) >= 0 && + (s.f_type == TMPFS_MAGIC || s.f_type == RAMFS_MAGIC); + + return saved; +} + +void warn_melody(void) { + int fd; + + fd = open("/dev/console", O_WRONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return; + + /* Yeah, this is synchronous. Kinda sucks. Bute well... */ + + ioctl(fd, KIOCSOUND, (int)(1193180/440)); + usleep(125*USEC_PER_MSEC); + + ioctl(fd, KIOCSOUND, (int)(1193180/220)); + usleep(125*USEC_PER_MSEC); + + ioctl(fd, KIOCSOUND, (int)(1193180/220)); + usleep(125*USEC_PER_MSEC); + + ioctl(fd, KIOCSOUND, 0); + close_nointr_nofail(fd); +} + +int make_console_stdio(void) { + int fd, r; + + /* Make /dev/console the controlling terminal and stdin/stdout/stderr */ + + fd = acquire_terminal("/dev/console", false, true, true, (usec_t) -1); + if (fd < 0) { + log_error("Failed to acquire terminal: %s", strerror(-fd)); + return fd; + } + + r = make_stdio(fd); + if (r < 0) { + log_error("Failed to duplicate terminal fd: %s", strerror(-r)); + return r; + } + + return 0; +} + +int get_home_dir(char **_h) { + char *h; + const char *e; + uid_t u; + struct passwd *p; + + assert(_h); + + /* Take the user specified one */ + e = getenv("HOME"); + if (e) { + h = strdup(e); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; + } + + /* Hardcode home directory for root to avoid NSS */ + u = getuid(); + if (u == 0) { + h = strdup("/root"); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; + } + + /* Check the database... */ + errno = 0; + p = getpwuid(u); + if (!p) + return errno ? -errno : -ENOENT; + + if (!path_is_absolute(p->pw_dir)) + return -EINVAL; + + h = strdup(p->pw_dir); + if (!h) + return -ENOMEM; + + *_h = h; + return 0; +} + +int get_shell(char **_sh) { + char *sh; + const char *e; + uid_t u; + struct passwd *p; + + assert(_sh); + + /* Take the user specified one */ + e = getenv("SHELL"); + if (e) { + sh = strdup(e); + if (!sh) + return -ENOMEM; + + *_sh = sh; + return 0; + } + + /* Hardcode home directory for root to avoid NSS */ + u = getuid(); + if (u == 0) { + sh = strdup("/bin/sh"); + if (!sh) + return -ENOMEM; + + *_sh = sh; + return 0; + } + + /* Check the database... */ + errno = 0; + p = getpwuid(u); + if (!p) + return errno ? -errno : -ESRCH; + + if (!path_is_absolute(p->pw_shell)) + return -EINVAL; + + sh = strdup(p->pw_shell); + if (!sh) + return -ENOMEM; + + *_sh = sh; + return 0; +}