X-Git-Url: https://arthur.barton.de/cgi-bin/gitweb.cgi?p=netatalk.git;a=blobdiff_plain;f=libatalk%2Futil%2Funix.c;h=4572caddd454d36c1b0cd3e45b6b258b7aedadf3;hp=72545e5b632fd6eba24918003f1a0b171681871e;hb=c72d10d6f92fe81d040ab983768d7fdccea7fb2e;hpb=14874ef66d68c5200a9a42a7408d022d58211898 diff --git a/libatalk/util/unix.c b/libatalk/util/unix.c index 72545e5b..4572cadd 100644 --- a/libatalk/util/unix.c +++ b/libatalk/util/unix.c @@ -32,6 +32,8 @@ #include #include #include +#include +#include #include #include @@ -41,6 +43,133 @@ #include #include #include +#include +#include + +/* close all FDs >= a specified value */ +static void closeall(int fd) +{ + int fdlimit = sysconf(_SC_OPEN_MAX); + + while (fd < fdlimit) + 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 + * + * Fork, exit parent, setsid(), optionally chdir("/"), optionally close all fds + * + * returns -1 on failure, but you can't do much except exit in that case + * since we may already have forked + */ +int daemonize(int nochdir, int noclose) +{ + switch (fork()) { + case 0: + break; + case -1: + return -1; + default: + _exit(0); + } + + if (setsid() < 0) + return -1; + + switch (fork()) { + case 0: + break; + case -1: + return -1; + default: + _exit(0); + } + + if (!nochdir) + chdir("/"); + + if (!noclose) { + closeall(0); + open("/dev/null",O_RDWR); + dup(0); + dup(0); + } + + 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 @@ -59,28 +188,25 @@ const char *getcwdpath(void) } /*! - * Make argument path absoulte + * @brief Request absolute path * - * @returns pointer to path or pointer to error messages on error + * @returns Absolute filesystem path to object */ -const char *abspath(const char *name) +const char *fullpathname(const char *name) { - static char buf[MAXPATHLEN + 1]; - char *p; - int n; + static char wd[MAXPATHLEN + 1]; if (name[0] == '/') return name; - if ((p = getcwd(buf, MAXPATHLEN)) == NULL) - return strerror(errno); + if (getcwd(wd , MAXPATHLEN)) { + strlcat(wd, "/", MAXPATHLEN); + strlcat(wd, name, MAXPATHLEN); + } else { + strlcpy(wd, name, MAXPATHLEN); + } - n = strlen(buf); - if (buf[n-1] != '/') - buf[n++] = '/'; - - strlcpy(buf + n, name, MAXPATHLEN - n); - return buf; + return wd; } /*! @@ -103,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 / @@ -206,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; +}