]> arthur.barton.de Git - netatalk.git/blobdiff - libatalk/util/unix.c
Add advanced option "chmod request" controlling ACLs
[netatalk.git] / libatalk / util / unix.c
index 0ac210ec2c8d4e3e1d99ab16a33f7067cfabd24f..4572caddd454d36c1b0cd3e45b6b258b7aedadf3 100644 (file)
@@ -32,6 +32,8 @@
 #include <dirent.h>
 #include <sys/time.h>
 #include <time.h>
+#include <sys/wait.h>
+#include <libgen.h>
 
 #include <atalk/adouble.h>
 #include <atalk/ea.h>
@@ -41,6 +43,8 @@
 #include <atalk/util.h>
 #include <atalk/unix.h>
 #include <atalk/compat.h>
+#include <atalk/errchk.h>
+#include <atalk/acl.h>
 
 /* close all FDs >= a specified value */
 static void closeall(int fd)
@@ -51,6 +55,55 @@ static void closeall(int fd)
         close(fd++);
 }
 
+/*!
+ * Run command in a child and wait for it to finish
+ */
+int run_cmd(const char *cmd, char **cmd_argv)
+{
+    EC_INIT;
+    pid_t pid, wpid;
+    sigset_t sigs, oldsigs;
+       int status = 0;
+
+    sigfillset(&sigs);
+    pthread_sigmask(SIG_SETMASK, &sigs, &oldsigs);
+
+    if ((pid = fork()) < 0) {
+        LOG(log_error, logtype_default, "run_cmd: fork: %s", strerror(errno));
+        return -1;
+    }
+
+    if (pid == 0) {
+        /* child */
+        closeall(3);
+        execvp("mv", cmd_argv);
+    }
+
+    /* parent */
+       while ((wpid = waitpid(pid, &status, 0)) < 0) {
+           if (errno == EINTR)
+            continue;
+           break;
+       }
+       if (wpid != pid) {
+           LOG(log_error, logtype_default, "waitpid(%d): %s", (int)pid, strerror(errno));
+        EC_FAIL;
+       }
+
+    if (WIFEXITED(status))
+        status = WEXITSTATUS(status);
+    else if (WIFSIGNALED(status))
+        status = WTERMSIG(status);
+
+    LOG(log_note, logtype_default, "run_cmd(\"%s\"): status: %d", cmd, status);
+
+EC_CLEANUP:
+    if (status != 0)
+        ret = status;
+    pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
+    EC_EXIT;
+}
+
 /*!
  * Daemonize
  *
@@ -95,6 +148,29 @@ int daemonize(int nochdir, int noclose)
     return 0;
 }
 
+static uid_t saved_uid = -1;
+
+/*
+ * seteuid(0) and back, if either fails and panic != 0 we PANIC
+ */
+void become_root(void)
+{
+    if (getuid() == 0) {
+        saved_uid = geteuid();
+        if (seteuid(0) != 0)
+            AFP_PANIC("Can't seteuid(0)");
+    }
+}
+
+void unbecome_root(void)
+{
+    if (getuid() == 0) {
+        if (saved_uid == -1 || seteuid(saved_uid) < 0)
+            AFP_PANIC("Can't seteuid back");
+        saved_uid = -1;
+    }
+}
+
 /*!
  * @brief get cwd in static buffer
  *
@@ -153,20 +229,107 @@ char *stripped_slashes_basename(char *p)
     return (strrchr(p, '/') ? strrchr(p, '/') + 1 : p);
 }
 
+/*********************************************************************************
+ * chdir(), chmod(), chown(), stat() wrappers taking an additional option.
+ * Currently the only used options are O_NOFOLLOW, used to switch between symlink
+ * behaviour, and O_NETATALK_ACL for ochmod() indicating chmod_acl() shall be
+ * called which does special ACL handling depending on the filesytem
+ *********************************************************************************/
+
+int ostat(const char *path, struct stat *buf, int options)
+{
+    if (options & O_NOFOLLOW)
+        return lstat(path, buf);
+    else
+        return stat(path, buf);
+}
+
+int ochown(const char *path, uid_t owner, gid_t group, int options)
+{
+    if (options & O_NOFOLLOW)
+        return lchown(path, owner, group);
+    else
+        return chown(path, owner, group);
+}
+
+/*!
+ * chmod() wrapper for symlink and ACL handling
+ *
+ * @param path       (r) path
+ * @param mode       (r) requested mode
+ * @param sb         (r) stat() of path or NULL
+ * @param option     (r) O_NOFOLLOW | O_NETATALK_ACL
+ *
+ * Options description:
+ * O_NOFOLLOW: don't chmod() symlinks, do nothing, return 0
+ * O_NETATALK_ACL: call chmod_acl() instead of chmod()
+ * O_IGNORE: ignore chmod() request, directly return 0
+ */
+int ochmod(char *path, mode_t mode, const struct stat *st, int options)
+{
+    struct stat sb;
+
+    if (options & O_IGNORE)
+        return 0;
+
+    if (!st) {
+        if (lstat(path, &sb) != 0)
+            return -1;
+        st = &sb;
+    }
+
+    if (options & O_NOFOLLOW)
+        if (S_ISLNK(st->st_mode))
+            return 0;
+
+    if (options & O_NETATALK_ACL) {
+        return chmod_acl(path, mode);
+    } else {
+        return chmod(path, mode);
+    }
+}
+
+/* 
+ * @brief ostat/fsstatat multiplexer
+ *
+ * ostatat mulitplexes ostat and fstatat. If we dont HAVE_ATFUNCS, dirfd is ignored.
+ *
+ * @param dirfd   (r) Only used if HAVE_ATFUNCS, ignored else, -1 gives AT_FDCWD
+ * @param path    (r) pathname
+ * @param st      (rw) pointer to struct stat
+ */
+int ostatat(int dirfd, const char *path, struct stat *st, int options)
+{
+#ifdef HAVE_ATFUNCS
+    if (dirfd == -1)
+        dirfd = AT_FDCWD;
+    return fstatat(dirfd, path, st, (options & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0);
+#else
+    return ostat(path, st, options);
+#endif            
+
+    /* DEADC0DE */
+    return -1;
+}
+
 /*!
  * @brief symlink safe chdir replacement
  *
- * Only chdirs to dir if it doesn't contain symlinks.
+ * Only chdirs to dir if it doesn't contain symlinks or if symlink checking
+ * is disabled
  *
  * @returns 1 if a path element is a symlink, 0 otherwise, -1 on syserror
  */
-int lchdir(const char *dir)
+int ochdir(const char *dir, int options)
 {
     char buf[MAXPATHLEN+1];
     char cwd[MAXPATHLEN+1];
     char *test;
     int  i;
 
+    if (!(options & O_NOFOLLOW))
+        return chdir(dir);
+
     /*
      dir is a canonical path (without "../" "./" "//" )
      but may end with a / 
@@ -256,3 +419,138 @@ void randombytes(void *buf, int n)
 
     return;
 }
+
+int gmem(gid_t gid, int ngroups, gid_t *groups)
+{
+    int                i;
+
+    for ( i = 0; i < ngroups; i++ ) {
+        if ( groups[ i ] == gid ) {
+            return( 1 );
+        }
+    }
+    return( 0 );
+}
+
+/*
+ * realpath() replacement that always allocates storage for returned path
+ */
+char *realpath_safe(const char *path)
+{
+    char *resolved_path;
+
+#ifdef REALPATH_TAKES_NULL
+    if ((resolved_path = realpath(path, NULL)) == NULL) {
+        LOG(log_debug, logtype_afpd, "realpath() cannot resolve path \"%s\"", path);
+        return NULL;
+    }
+    return resolved_path;
+#else
+    if ((resolved_path = malloc(MAXPATHLEN+1)) == NULL)
+        return NULL;
+    if (realpath(path, resolved_path) == NULL) {
+        free(resolved_path);
+        LOG(log_debug, logtype_afpd, "realpath() cannot resolve path \"%s\"", path);
+        return NULL;
+    }
+    /* Safe some memory */
+    char *tmp;
+    if ((tmp = strdup(resolved_path)) == NULL) {
+        free(resolved_path);
+        return NULL;
+    }
+    free(resolved_path);
+    resolved_path = tmp;
+    return resolved_path;
+#endif
+}
+
+/**
+ * Returns pointer to static buffer with basename of path
+ **/
+const char *basename_safe(const char *path)
+{
+    static char buf[MAXPATHLEN+1];
+    strlcpy(buf, path, MAXPATHLEN);
+    return basename(buf);
+}
+
+/**
+ * extended strtok allows the quoted strings
+ * modified strtok.c in glibc 2.0.6
+ **/
+char *strtok_quote(char *s, const char *delim)
+{
+    static char *olds = NULL;
+    char *token;
+
+    if (s == NULL)
+        s = olds;
+
+    /* Scan leading delimiters.  */
+    s += strspn (s, delim);
+    if (*s == '\0')
+        return NULL;
+
+    /* Find the end of the token.  */
+    token = s;
+
+    if (token[0] == '\"') {
+        token++;
+        s = strpbrk (token, "\"");
+    } else {
+        s = strpbrk (token, delim);
+    }
+
+    if (s == NULL) {
+        /* This token finishes the string.  */
+        olds = strchr (token, '\0');
+    } else {
+        /* Terminate the token and make OLDS point past it.  */
+        *s = '\0';
+        olds = s + 1;
+    }
+    return token;
+}
+
+int set_groups(AFPObj *obj, struct passwd *pwd)
+{
+    if (initgroups(pwd->pw_name, pwd->pw_gid) < 0)
+        LOG(log_error, logtype_afpd, "initgroups(%s, %d): %s", pwd->pw_name, pwd->pw_gid, strerror(errno));
+
+    if ((obj->ngroups = getgroups(0, NULL)) < 0) {
+        LOG(log_error, logtype_afpd, "login: %s getgroups: %s", pwd->pw_name, strerror(errno));
+        return -1;
+    }
+
+    if (obj->groups)
+        free(obj->groups);
+    if (NULL == (obj->groups = calloc(obj->ngroups, sizeof(gid_t))) ) {
+        LOG(log_error, logtype_afpd, "login: %s calloc: %d", obj->ngroups);
+        return -1;
+    }
+
+    if ((obj->ngroups = getgroups(obj->ngroups, obj->groups)) < 0 ) {
+        LOG(log_error, logtype_afpd, "login: %s getgroups: %s", pwd->pw_name, strerror(errno));
+        return -1;
+    }
+
+    return 0;
+}
+
+#define GROUPSTR_BUFSIZE 1024
+const char *print_groups(int ngroups, gid_t *groups)
+{
+    static char groupsstr[GROUPSTR_BUFSIZE];
+    int i;
+    char *s = groupsstr;
+
+    if (ngroups == 0)
+        return "-";
+
+    for (i = 0; (i < ngroups) && (s < &groupsstr[GROUPSTR_BUFSIZE]); i++) {
+        s += snprintf(s, &groupsstr[GROUPSTR_BUFSIZE] - s, " %u", groups[i]);
+    }
+
+    return groupsstr;
+}