From f3bc40136878ab91cb98f1b206ff9517000112f7 Mon Sep 17 00:00:00 2001 From: Jakub Hrozek Date: Mon, 5 Oct 2009 19:45:03 +0200 Subject: User home directories management Create and populate user directories on useradd, delete them on userdel Fixes: #212 --- server/tools/sss_sync_ops.c | 126 +++++++++++++++++++++---- server/tools/sss_sync_ops.h | 23 ++++- server/tools/sss_useradd.c | 81 +++++++++++++--- server/tools/sss_userdel.c | 70 +++++++++++++- server/tools/tools_util.c | 223 ++++++++++++++++++++++++++++++++++++++++++++ server/tools/tools_util.h | 19 ++++ 6 files changed, 505 insertions(+), 37 deletions(-) (limited to 'server/tools') diff --git a/server/tools/sss_sync_ops.c b/server/tools/sss_sync_ops.c index 932a7122..2bea4f07 100644 --- a/server/tools/sss_sync_ops.c +++ b/server/tools/sss_sync_ops.c @@ -20,6 +20,7 @@ #include #include +#include #include "util/util.h" #include "db/sysdb.h" @@ -28,6 +29,12 @@ /* Default settings for user attributes */ #define DFL_SHELL_VAL "/bin/bash" #define DFL_BASEDIR_VAL "/home" +#define DFL_CREATE_HOMEDIR "TRUE" +#define DFL_REMOVE_HOMEDIR "TRUE" +#define DFL_UMASK 077 +#define DFL_SKEL_DIR "/etc/skel" +#define DFL_MAIL_DIR "/var/spool/mail" + #define VAR_CHECK(var, val, attr, msg) do { \ if (var != (val)) { \ @@ -1111,6 +1118,47 @@ static int group_mod_recv(struct tevent_req *req) return sync_ops_recv(req); } +int userdel_defaults(TALLOC_CTX *mem_ctx, + struct confdb_ctx *confdb, + struct ops_ctx *data, + int remove_home) +{ + int ret; + char *conf_path; + bool dfl_remove_home; + + conf_path = talloc_asprintf(mem_ctx, CONFDB_DOMAIN_PATH_TMPL, data->domain->name); + if (!conf_path) { + return ENOMEM; + } + + /* remove homedir on user creation? */ + if (!remove_home) { + ret = confdb_get_bool(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_REMOVE_HOMEDIR, + DFL_REMOVE_HOMEDIR, &dfl_remove_home); + if (ret != EOK) { + goto done; + } + data->remove_homedir = dfl_remove_home; + } else { + data->remove_homedir = (remove_home == DO_REMOVE_HOME); + } + + /* a directory to remove mail spools from */ + ret = confdb_get_string(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_MAIL_DIR, + DFL_MAIL_DIR, &data->maildir); + if (ret != EOK) { + goto done; + } + + ret = EOK; +done: + talloc_free(conf_path); + return ret; +} + /* * Default values for add operations */ @@ -1119,11 +1167,12 @@ int useradd_defaults(TALLOC_CTX *mem_ctx, struct ops_ctx *data, const char *gecos, const char *homedir, - const char *shell) + const char *shell, + int create_home, + const char *skeldir) { int ret; char *basedir = NULL; - char *dfl_shell = NULL; char *conf_path = NULL; conf_path = talloc_asprintf(mem_ctx, CONFDB_DOMAIN_PATH_TMPL, data->domain->name); @@ -1131,18 +1180,17 @@ int useradd_defaults(TALLOC_CTX *mem_ctx, return ENOMEM; } + /* gecos */ data->gecos = talloc_strdup(mem_ctx, gecos ? gecos : data->name); if (!data->gecos) { ret = ENOMEM; goto done; } + DEBUG(7, ("Gecos: %s\n", data->gecos)); + /* homedir */ if (homedir) { data->home = talloc_strdup(data, homedir); - if (data->home == NULL) { - ret = ENOMEM; - goto done; - } } else { ret = confdb_get_string(confdb, mem_ctx, conf_path, CONFDB_LOCAL_DEFAULT_BASEDIR, @@ -1151,34 +1199,80 @@ int useradd_defaults(TALLOC_CTX *mem_ctx, goto done; } data->home = talloc_asprintf(mem_ctx, "%s/%s", basedir, data->name); - if (!data->home) { - ret = ENOMEM; - goto done; - } } if (!data->home) { ret = ENOMEM; goto done; } + DEBUG(7, ("Homedir: %s\n", data->home)); + /* default shell */ if (!shell) { ret = confdb_get_string(confdb, mem_ctx, conf_path, CONFDB_LOCAL_DEFAULT_SHELL, - DFL_SHELL_VAL, &dfl_shell); + DFL_SHELL_VAL, &data->shell); if (ret != EOK) { goto done; } - shell = dfl_shell; + } else { + data->shell = talloc_strdup(mem_ctx, shell); + if (!data->shell) { + ret = ENOMEM; + goto done; + } } - data->shell = talloc_strdup(mem_ctx, shell); - if (!data->shell) { - ret = ENOMEM; + DEBUG(7, ("Shell: %s\n", data->shell)); + + /* create homedir on user creation? */ + if (!create_home) { + ret = confdb_get_bool(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_CREATE_HOMEDIR, + DFL_CREATE_HOMEDIR, &data->create_homedir); + if (ret != EOK) { + goto done; + } + } else { + data->create_homedir = (create_home == DO_CREATE_HOME); + } + DEBUG(7, ("Auto create homedir: %s\n", data->create_homedir?"True":"False")); + + /* umask to create homedirs */ + ret = confdb_get_int(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_UMASK, + DFL_UMASK, (int *) &data->umask); + if (ret != EOK) { + goto done; + } + DEBUG(7, ("Umask: %o\n", data->umask)); + + /* a directory to create mail spools in */ + ret = confdb_get_string(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_MAIL_DIR, + DFL_MAIL_DIR, &data->maildir); + if (ret != EOK) { goto done; } + DEBUG(7, ("Mail dir: %s\n", data->maildir)); + + /* skeleton dir */ + if (!skeldir) { + ret = confdb_get_string(confdb, mem_ctx, + conf_path, CONFDB_LOCAL_SKEL_DIR, + DFL_SKEL_DIR, &data->skeldir); + if (ret != EOK) { + goto done; + } + } else { + data->skeldir = talloc_strdup(mem_ctx, skeldir); + if (!data->skeldir) { + ret = ENOMEM; + goto done; + } + } + DEBUG(7, ("Skeleton dir: %s\n", data->skeldir)); ret = EOK; done: - talloc_free(dfl_shell); talloc_free(basedir); talloc_free(conf_path); return ret; diff --git a/server/tools/sss_sync_ops.h b/server/tools/sss_sync_ops.h index 3988992e..383319a8 100644 --- a/server/tools/sss_sync_ops.h +++ b/server/tools/sss_sync_ops.h @@ -27,6 +27,13 @@ #define DO_LOCK 1 #define DO_UNLOCK 2 +/* 0 = not set, pick default */ +#define DO_CREATE_HOME 1 +#define DO_NOT_CREATE_HOME 2 +#define DO_REMOVE_HOME 1 +#define DO_NOT_REMOVE_HOME 2 +#define DO_FORCE_REMOVAL 1 + struct ops_ctx { struct sss_domain_info *domain; @@ -38,6 +45,12 @@ struct ops_ctx { char *shell; int lock; + bool create_homedir; + bool remove_homedir; + mode_t umask; + char *skeldir; + char *maildir; + char **addgroups; char **rmgroups; }; @@ -48,7 +61,15 @@ int useradd_defaults(TALLOC_CTX *mem_ctx, struct ops_ctx *data, const char *gecos, const char *homedir, - const char *shell); + const char *shell, + int create_home, + const char *skeldir); + +/* default values for remove operations */ +int userdel_defaults(TALLOC_CTX *mem_ctx, + struct confdb_ctx *confdb, + struct ops_ctx *data, + int remove_home); /* synchronous operations */ int useradd(TALLOC_CTX *mem_ctx, diff --git a/server/tools/sss_useradd.c b/server/tools/sss_useradd.c index d05f5ae9..94de68fd 100644 --- a/server/tools/sss_useradd.c +++ b/server/tools/sss_useradd.c @@ -32,15 +32,6 @@ #include "tools/tools_util.h" #include "tools/sss_sync_ops.h" -/* Default settings for user attributes */ -#define CONFDB_DFL_SECTION "config/user_defaults" - -#define DFL_SHELL_ATTR "defaultShell" -#define DFL_BASEDIR_ATTR "baseDirectory" - -#define DFL_SHELL_VAL "/bin/bash" -#define DFL_BASEDIR_VAL "/home" - static void get_gid_callback(void *ptr, int error, struct ldb_result *res) { struct tools_ctx *tctx = talloc_get_type(ptr, struct tools_ctx); @@ -115,7 +106,9 @@ int main(int argc, const char **argv) const char *pc_home = NULL; char *pc_shell = NULL; int pc_debug = 0; + int pc_create_home = 0; const char *pc_username = NULL; + const char *pc_skeldir = NULL; struct poptOption long_options[] = { POPT_AUTOHELP { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, _("The debug level to run with"), NULL }, @@ -125,6 +118,9 @@ int main(int argc, const char **argv) { "home", 'h', POPT_ARG_STRING, &pc_home, 0, _("Home directory"), NULL }, { "shell", 's', POPT_ARG_STRING, &pc_shell, 0, _("Login shell"), NULL }, { "groups", 'G', POPT_ARG_STRING, NULL, 'G', _("Groups"), NULL }, + { "create-home", 'm', POPT_ARG_NONE, NULL, 'm', _("Create user's directory if it does not exist"), NULL }, + { "no-create-home", 'M', POPT_ARG_NONE, NULL, 'M', _("Never create user's directory, overrides config"), NULL }, + { "skel", 'k', POPT_ARG_STRING, &pc_skeldir, 0, _("Specify an alternative skeleton directory") }, POPT_TABLEEND }; poptContext pc = NULL; @@ -147,12 +143,18 @@ int main(int argc, const char **argv) pc = poptGetContext(NULL, argc, argv, long_options, 0); poptSetOtherOptionHelp(pc, "USERNAME"); while ((ret = poptGetNextOpt(pc)) > 0) { - if (ret == 'G') { - groups = poptGetOptArg(pc); - if (!groups) { - ret = -1; + switch (ret) { + case 'G': + groups = poptGetOptArg(pc); + if (!groups) goto fini; + + case 'm': + pc_create_home = DO_CREATE_HOME; + break; + + case 'M': + pc_create_home = DO_NOT_CREATE_HOME; break; - } } } @@ -232,7 +234,8 @@ int main(int argc, const char **argv) * Fills in defaults for ops_ctx user did not specify. */ ret = useradd_defaults(tctx, tctx->confdb, tctx->octx, - pc_gecos, pc_home, pc_shell); + pc_gecos, pc_home, pc_shell, + pc_create_home, pc_skeldir); if (ret != EOK) { ERROR("Cannot set default values\n"); ret = EXIT_FAILURE; @@ -263,6 +266,54 @@ int main(int argc, const char **argv) end_transaction(tctx); + /* Create user's home directory and/or mail spool */ + if (tctx->octx->create_homedir) { + /* We need to know the UID and GID of the user, if + * sysdb did assign it automatically, do a lookup */ + if (tctx->octx->uid == 0 || tctx->octx->gid == 0) { + ret = sysdb_getpwnam_sync(tctx, + tctx->ev, + tctx->sysdb, + tctx->octx->name, + tctx->local, + &tctx->octx); + if (ret != EOK) { + ERROR("Cannot get info about the user\n"); + ret = EXIT_FAILURE; + goto fini; + } + } + + ret = create_homedir(tctx, + tctx->octx->skeldir, + tctx->octx->home, + tctx->octx->name, + tctx->octx->uid, + tctx->octx->gid, + tctx->octx->umask); + if (ret == EEXIST) { + ERROR("User's home directory already exists, not copying " + "data from skeldir\n"); + } else if (ret != EOK) { + ERROR("Cannot create user's home directory: %s\n", strerror(ret)); + ret = EXIT_FAILURE; + goto fini; + } + + ret = create_mail_spool(tctx, + tctx->octx->name, + tctx->octx->maildir, + tctx->octx->uid, + tctx->octx->gid); + if (ret != EOK) { + ERROR("Cannot create user's mail spool: %s\n", strerror(ret)); + DEBUG(1, ("Cannot create user's mail spool: [%d][%s].\n", + ret, strerror(ret))); + ret = EXIT_FAILURE; + goto fini; + } + } + done: if (tctx->error) { switch (tctx->error) { diff --git a/server/tools/sss_userdel.c b/server/tools/sss_userdel.c index d14ef3da..d4088cb5 100644 --- a/server/tools/sss_userdel.c +++ b/server/tools/sss_userdel.c @@ -36,11 +36,16 @@ int main(int argc, const char **argv) const char *pc_username = NULL; int pc_debug = 0; + int pc_remove = 0; + int pc_force = 0; poptContext pc = NULL; struct poptOption long_options[] = { POPT_AUTOHELP { "debug", '\0', POPT_ARG_INT | POPT_ARGFLAG_DOC_HIDDEN, &pc_debug, 0, _("The debug level to run with"), NULL }, + { "remove", 'r', POPT_ARG_NONE, NULL, 'r', _("Remove home directory and mail spool"), NULL }, + { "no-remove", 'R', POPT_ARG_NONE, NULL, 'R', _("Do not remove home directory and mail spool"), NULL }, + { "force", 'f', POPT_ARG_NONE, NULL, 'f', _("Force removal of files not owned by the user"), NULL }, POPT_TABLEEND }; @@ -57,14 +62,30 @@ int main(int argc, const char **argv) /* parse parameters */ pc = poptGetContext(NULL, argc, argv, long_options, 0); poptSetOtherOptionHelp(pc, "USERNAME"); - if ((ret = poptGetNextOpt(pc)) < -1) { + while ((ret = poptGetNextOpt(pc)) > 0) { + switch (ret) { + case 'r': + pc_remove = DO_REMOVE_HOME; + break; + + case 'R': + pc_remove = DO_NOT_REMOVE_HOME; + break; + + case 'f': + pc_force = DO_FORCE_REMOVAL; + break; + } + } + + debug_level = pc_debug; + + if (ret != -1) { usage(pc, poptStrerror(ret)); ret = EXIT_FAILURE; goto fini; } - debug_level = pc_debug; - pc_username = poptGetArg(pc); if (pc_username == NULL) { usage(pc, _("Specify user to delete\n")); @@ -90,6 +111,29 @@ int main(int argc, const char **argv) goto fini; } + /* + * Fills in defaults for ops_ctx user did not specify. + */ + ret = userdel_defaults(tctx, tctx->confdb, tctx->octx, pc_remove); + if (ret != EOK) { + ERROR("Cannot set default values\n"); + ret = EXIT_FAILURE; + goto fini; + } + + if (tctx->octx->remove_homedir) { + ret = sysdb_getpwnam_sync(tctx, + tctx->ev, + tctx->sysdb, + tctx->octx->name, + tctx->local, + &tctx->octx); + if (ret != EOK) { + /* Error message will be printed in the switch */ + goto done; + } + } + start_transaction(tctx); if (tctx->error != EOK) { goto done; @@ -107,9 +151,25 @@ int main(int argc, const char **argv) end_transaction(tctx); + if (tctx->octx->remove_homedir) { + ret = remove_homedir(tctx, + tctx->octx->home, + tctx->octx->maildir, + tctx->octx->name, + tctx->octx->uid, + pc_force); + if (ret == EPERM) { + ERROR("Not removing home dir - not owned by user\n"); + } else if (ret != EOK) { + ERROR("Cannot remove homedir: %s\n", strerror(ret)); + ret = EXIT_FAILURE; + goto fini; + } + } + + ret = tctx->error; done: - if (tctx->error) { - ret = tctx->error; + if (ret) { DEBUG(1, ("sysdb operation failed (%d)[%s]\n", ret, strerror(ret))); switch (ret) { case ENOENT: diff --git a/server/tools/tools_util.c b/server/tools/tools_util.c index 17cc3aa7..bcb8d5c1 100644 --- a/server/tools/tools_util.c +++ b/server/tools/tools_util.c @@ -23,6 +23,14 @@ #include #include #include +#include +#include +#include + +#include "config.h" +#ifdef HAVE_SELINUX +#include +#endif #include "util/util.h" #include "confdb/confdb.h" @@ -294,3 +302,218 @@ fini: return ret; } +/* + * Check is path is owned by uid + * returns 0 - owns + * -1 - does not own + * >0 - an error occured, error code + */ +static int is_owner(uid_t uid, const char *path) +{ + struct stat statres; + int ret; + + ret = stat(path, &statres); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot stat %s: [%d][%s]\n", path, ret, strerror(ret))); + return ret; + } + + if (statres.st_uid == uid) { + return EOK; + } + return -1; +} + +static int remove_mail_spool(TALLOC_CTX *mem_ctx, + const char *maildir, + const char *username, + uid_t uid, + bool force) +{ + int ret; + char *spool_file; + + spool_file = talloc_asprintf(mem_ctx, "%s/%s", maildir, username); + if (spool_file == NULL) { + ret = ENOMEM; + goto fail; + } + + if (force == false) { + /* Check the owner of the mail spool */ + ret = is_owner(uid, spool_file); + switch (ret) { + case 0: + break; + case -1: + DEBUG(3, ("%s not owned by %d, not removing\n", + spool_file, uid)); + ret = EACCES; + /* FALLTHROUGH */ + default: + goto fail; + } + } + + ret = unlink(spool_file); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot remove() the spool file %s: [%d][%s]\n", + spool_file, ret, strerror(ret))); + goto fail; + } + +fail: + talloc_free(spool_file); + return ret; +} + +int remove_homedir(TALLOC_CTX *mem_ctx, + const char *homedir, + const char *maildir, + const char *username, + uid_t uid, bool force) +{ + int ret; + + ret = remove_mail_spool(mem_ctx, maildir, username, uid, force); + if (ret != EOK) { + DEBUG(1, ("Cannot remove user's mail spool\n")); + /* Should this be fatal? I don't think so. Maybe convert to ERROR? */ + } + + if (force == false && is_owner(uid, homedir) == -1) { + DEBUG(1, ("Not removing home dir - not owned by user\n")); + return EPERM; + } + + /* Remove the tree */ + ret = remove_tree(homedir); + if (ret != EOK) { + DEBUG(1, ("Cannot remove homedir %s: %d\n", + homedir, ret)); + return ret; + } + + return EOK; +} + +/* The reason for not putting this into create_homedir + * is better granularity when it comes to reporting error + * messages and tracebacks in pysss + */ +int create_mail_spool(TALLOC_CTX *mem_ctx, + const char *username, + const char *maildir, + uid_t uid, gid_t gid) +{ + char *spool_file = NULL; + int fd; + int ret; + + spool_file = talloc_asprintf(mem_ctx, "%s/%s", maildir, username); + if (spool_file == NULL) { + ret = ENOMEM; + goto fail; + } + + selinux_file_context(spool_file); + + fd = open(spool_file, O_CREAT | O_WRONLY | O_EXCL, 0); + if (fd < 0) { + ret = errno; + DEBUG(1, ("Cannot open() the spool file: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } + + ret = fchmod(fd, 0600); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot fchmod() the spool file: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } + + ret = fchown(fd, uid, gid); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot fchown() the spool file: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } + + ret = fsync(fd); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot fsync() the spool file: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } + + ret = close(fd); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot close() the spool file: [%d][%s]\n", + ret, strerror(ret))); + goto fail; + } + +fail: + reset_selinux_file_context(); + talloc_free(spool_file); + return ret; +} + +int create_homedir(TALLOC_CTX *mem_ctx, + const char *skeldir, + const char *homedir, + const char *username, + uid_t uid, + gid_t gid, + mode_t default_umask) +{ + int ret; + + selinux_file_context(homedir); + + ret = mkdir(homedir, 0); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot create user's home directory: [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + ret = chown(homedir, uid, gid); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot chown user's home directory: [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + ret = chmod(homedir, 0777 & ~default_umask); + if (ret != 0) { + ret = errno; + DEBUG(1, ("Cannot chmod user's home directory: [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + + reset_selinux_file_context(); + + ret = copy_tree(skeldir, homedir, uid, gid); + if (ret != EOK) { + DEBUG(1, ("Cannot populate user's home directory: [%d][%s].\n", + ret, strerror(ret))); + goto done; + } + +done: + reset_selinux_file_context(); + return ret; +} + diff --git a/server/tools/tools_util.h b/server/tools/tools_util.h index 92fba20d..2a1ee25e 100644 --- a/server/tools/tools_util.h +++ b/server/tools/tools_util.h @@ -76,4 +76,23 @@ int check_group_names(struct tools_ctx *tctx, char **grouplist, char **badgroup); +int create_homedir(TALLOC_CTX *mem_ctx, + const char *skeldir, + const char *homedir, + const char *username, + uid_t uid, + gid_t gid, + mode_t default_umask); + +int create_mail_spool(TALLOC_CTX *mem_ctx, + const char *username, + const char *maildir, + uid_t uid, gid_t gid); + +int remove_homedir(TALLOC_CTX *mem_ctx, + const char *homedir, + const char *maildir, + const char *username, + uid_t uid, bool force); + #endif /* __TOOLS_UTIL_H__ */ -- cgit