From f6975773c5ebf939f9e3f22b2b600d2c821604bc Mon Sep 17 00:00:00 2001 From: Baptiste Daroussin Date: Sun, 12 Jul 2015 20:29:51 +0000 Subject: Rework the home directory creation and copy or the skel content to use *at functions This allows to simplify the code a bit for -R by not having to keep modifying path and also prepare the code to improve support -R in userdel While here, add regression tests for the functionality --- pw/cpdir.c | 152 +++++++++++++++++++++++++++++------------------------------ pw/pw.c | 4 ++ pw/pw_user.c | 17 ++++--- pw/pwupd.h | 4 +- 4 files changed, 90 insertions(+), 87 deletions(-) (limited to 'pw') diff --git a/pw/cpdir.c b/pw/cpdir.c index 0fd671b..e84d0f3 100644 --- a/pw/cpdir.c +++ b/pw/cpdir.c @@ -45,87 +45,85 @@ static const char rcsid[] = #include "pwupd.h" void -copymkdir(char const * dir, char const * skel, mode_t mode, uid_t uid, gid_t gid) +copymkdir(int rootfd, char const * dir, int skelfd, mode_t mode, uid_t uid, + gid_t gid, int flags) { - char src[MAXPATHLEN]; - char dst[MAXPATHLEN]; - char lnk[MAXPATHLEN]; - int len; + char *p, lnk[MAXPATHLEN], copybuf[4096]; + int len, homefd, srcfd, destfd; + ssize_t sz; + struct stat st; + struct dirent *e; + DIR *d; - if (mkdir(dir, mode) != 0 && errno != EEXIST) { + if (*dir == '/') + dir++; + + if (mkdirat(rootfd, dir, mode) != 0 && errno != EEXIST) { warn("mkdir(%s)", dir); - } else { - int infd, outfd; - struct stat st; - - static char counter = 0; - static char *copybuf = NULL; - - ++counter; - chown(dir, uid, gid); - if (skel != NULL && *skel != '\0') { - DIR *d = opendir(skel); - - if (d != NULL) { - struct dirent *e; - - while ((e = readdir(d)) != NULL) { - char *p = e->d_name; - - if (snprintf(src, sizeof(src), "%s/%s", skel, p) >= (int)sizeof(src)) - warn("warning: pathname too long '%s/%s' (skel not copied)", skel, p); - else if (lstat(src, &st) == 0) { - if (strncmp(p, "dot.", 4) == 0) /* Conversion */ - p += 3; - if (snprintf(dst, sizeof(dst), "%s/%s", dir, p) >= (int)sizeof(dst)) - warn("warning: path too long '%s/%s' (skel file skipped)", dir, p); - else { - if (S_ISDIR(st.st_mode)) { /* Recurse for this */ - if (strcmp(e->d_name, ".") != 0 && strcmp(e->d_name, "..") != 0) - copymkdir(dst, src, st.st_mode & _DEF_DIRMODE, uid, gid); - chflags(dst, st.st_flags); /* propagate flags */ - } else if (S_ISLNK(st.st_mode) && (len = readlink(src, lnk, sizeof(lnk) - 1)) != -1) { - lnk[len] = '\0'; - symlink(lnk, dst); - lchown(dst, uid, gid); - /* - * Note: don't propagate special attributes - * but do propagate file flags - */ - } else if (S_ISREG(st.st_mode) && (outfd = open(dst, O_RDWR | O_CREAT | O_EXCL, st.st_mode)) != -1) { - if ((infd = open(src, O_RDONLY)) == -1) { - close(outfd); - remove(dst); - } else { - int b; - - /* - * Allocate our copy buffer if we need to - */ - if (copybuf == NULL) - copybuf = malloc(4096); - while ((b = read(infd, copybuf, 4096)) > 0) - write(outfd, copybuf, b); - close(infd); - /* - * Propagate special filesystem flags - */ - fchown(outfd, uid, gid); - fchflags(outfd, st.st_flags); - close(outfd); - chown(dst, uid, gid); - } - } - } - } - } - closedir(d); - } + return; + } + fchownat(rootfd, dir, uid, gid, AT_SYMLINK_NOFOLLOW); + if (flags > 0) + chflagsat(rootfd, dir, flags, AT_SYMLINK_NOFOLLOW); + + if (skelfd == -1) + return; + + homefd = openat(rootfd, dir, O_DIRECTORY); + if ((d = fdopendir(skelfd)) == NULL) { + close(skelfd); + close(homefd); + return; + } + + while ((e = readdir(d)) != NULL) { + if (strcmp(e->d_name, ".") == 0 || strcmp(e->d_name, "..") == 0) + continue; + + p = e->d_name; + if (fstatat(skelfd, p, &st, AT_SYMLINK_NOFOLLOW) == -1) + continue; + + if (strncmp(p, "dot.", 4) == 0) /* Conversion */ + p += 3; + + if (S_ISDIR(st.st_mode)) { + copymkdir(homefd, p, openat(skelfd, e->d_name, O_DIRECTORY), + st.st_mode & _DEF_DIRMODE, uid, gid, st.st_flags); + continue; + } + + if (S_ISLNK(st.st_mode) && + (len = readlinkat(skelfd, e->d_name, lnk, sizeof(lnk) -1)) + != -1) { + lnk[len] = '\0'; + symlinkat(lnk, homefd, p); + fchownat(homefd, p, uid, gid, AT_SYMLINK_NOFOLLOW); + continue; } - if (--counter == 0 && copybuf != NULL) { - free(copybuf); - copybuf = NULL; + + if (!S_ISREG(st.st_mode)) + continue; + + if ((srcfd = openat(skelfd, e->d_name, O_RDONLY)) == -1) + continue; + destfd = openat(homefd, p, O_RDWR | O_CREAT | O_EXCL, + st.st_mode); + if (destfd == -1) { + close(srcfd); + continue; } + + while ((sz = read(srcfd, copybuf, sizeof(copybuf))) > 0) + write(destfd, copybuf, sz); + + close(srcfd); + /* + * Propagate special filesystem flags + */ + fchown(destfd, uid, gid); + fchflags(destfd, st.st_flags); + close(destfd); } + closedir(d); } - diff --git a/pw/pw.c b/pw/pw.c index 532d77b..d81cfb0 100644 --- a/pw/pw.c +++ b/pw/pw.c @@ -136,6 +136,7 @@ main(int argc, char *argv[]) name = NULL; relocated = nis = false; memset(&conf, 0, sizeof(conf)); + strlcpy(conf.rootdir, "/", sizeof(conf.rootdir)); strlcpy(conf.etcpath, _PATH_PWD, sizeof(conf.etcpath)); conf.fd = -1; @@ -215,6 +216,9 @@ main(int argc, char *argv[]) if (mode == -1 || which == -1) cmdhelp(mode, which); + conf.rootfd = open(conf.rootdir, O_DIRECTORY|O_CLOEXEC); + if (conf.rootfd == -1) + errx(EXIT_FAILURE, "Unable to open '%s'", conf.rootdir); conf.which = which; /* * We know which mode we're in and what we're about to do, so now diff --git a/pw/pw_user.c b/pw/pw_user.c index 16b2ac8..afc163f 100644 --- a/pw/pw_user.c +++ b/pw/pw_user.c @@ -67,20 +67,19 @@ static void rmopie(char const * name); static void create_and_populate_homedir(struct passwd *pwd) { - char *homedir, *dotdir; struct userconf *cnf = conf.userconf; + const char *skeldir; + int skelfd = -1; - homedir = dotdir = NULL; + skeldir = cnf->dotdir; - if (conf.rootdir[0] != '\0') { - asprintf(&homedir, "%s/%s", conf.rootdir, pwd->pw_dir); - if (homedir == NULL) - errx(EX_OSERR, "out of memory"); - asprintf(&dotdir, "%s/%s", conf.rootdir, cnf->dotdir); + if (skeldir != NULL && *skeldir != '\0') { + skelfd = openat(conf.rootfd, cnf->dotdir, + O_DIRECTORY|O_CLOEXEC); } - copymkdir(homedir ? homedir : pwd->pw_dir, dotdir ? dotdir: cnf->dotdir, - cnf->homemode, pwd->pw_uid, pwd->pw_gid); + copymkdir(conf.rootfd, pwd->pw_dir, skelfd, cnf->homemode, pwd->pw_uid, + pwd->pw_gid, 0); pw_log(cnf, M_ADD, W_USER, "%s(%u) home %s made", pwd->pw_name, pwd->pw_uid, pwd->pw_dir); } diff --git a/pw/pwupd.h b/pw/pwupd.h index 92e5548..f08d2c7 100644 --- a/pw/pwupd.h +++ b/pw/pwupd.h @@ -87,6 +87,7 @@ struct pwconf { char *config; char *gecos; int fd; + int rootfd; int which; bool quiet; bool force; @@ -156,7 +157,8 @@ struct group * vgetgrnam(const char * nam); RET_SETGRENT vsetgrent(void); void vendgrent(void); -void copymkdir(char const * dir, char const * skel, mode_t mode, uid_t uid, gid_t gid); +void copymkdir(int rootfd, char const * dir, int skelfd, mode_t mode, uid_t uid, + gid_t gid, int flags); void rm_r(char const * dir, uid_t uid); __END_DECLS -- cgit v1.2.3-56-ge451