aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--USER/USER.xs437
1 files changed, 433 insertions, 4 deletions
diff --git a/USER/USER.xs b/USER/USER.xs
index 50e32fb..d1ef6c9 100644
--- a/USER/USER.xs
+++ b/USER/USER.xs
@@ -11,12 +11,33 @@
#include <grp.h>
#include <pwd.h>
+#include <crypt.h>
+#include <ctype.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <locale.h>
+#include <limits.h>
+#include <sys/signal.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <unistd.h>
#include <glib.h>
+#include <utime.h>
#include <libuser/user.h>
#include <libuser/user_private.h>
+#define INVALID (-0x80000000)
+#ifndef _
+#define _(String) gettext(String)
+#endif
+#ifndef N_
+#define N_(String) (String)
+#endif
+
typedef struct context USER__ADMIN;
typedef struct lu_ent USER__ENT;
typedef struct lu_error USER__ERR;
@@ -40,6 +61,395 @@ static SV ** convert_value_array_list(register SV **sp, GValueArray *array) {
return sp;
}
+/* Populate a user's home directory, copying data from a named skeleton
+ * directory, setting all ownerships as given, and setting the mode of
+ * the top-level directory as given. */
+int
+lu_homedir_populate(const char *skeleton, const char *directory,
+ uid_t owner, gid_t group, mode_t mode,
+ USER__ERR **error)
+{
+ struct dirent *ent;
+ DIR *dir;
+ struct stat st;
+ char skelpath[PATH_MAX], path[PATH_MAX], buf[PATH_MAX];
+ struct utimbuf timebuf;
+ int ifd = -1, ofd = -1, i;
+ off_t offset;
+ LU_ERROR_CHECK(error);
+ /* If the destination directory exists, return. */
+ dir = opendir(skeleton);
+ if (dir == NULL) {
+ lu_error_new(error, lu_error_generic,
+ _("Error reading `%s': %s"), skeleton,
+ strerror(errno));
+ return 0;
+ }
+ /* Create the top-level directory. */
+ if ((mkdir(directory, mode) == -1) && (errno != EEXIST)) {
+ lu_error_new(error, lu_error_generic,
+ _("Error creating `%s': %s"), directory,
+ strerror(errno));
+ closedir(dir);
+ return 0;
+ }
+ /* Set the ownership on the top-level directory. */
+ chown(directory, owner, group);
+ while ((ent = readdir(dir)) != NULL) {
+ /* Iterate through each item in the directory. */
+ /* Skip over self and parent hard links. */
+ if (strcmp(ent->d_name, ".") == 0) {
+ continue;
+ }
+ if (strcmp(ent->d_name, "..") == 0) {
+ continue;
+ }
+ /* Build the path of the skeleton file or directory and
+ * its corresponding member in the new tree. */
+ snprintf(skelpath, sizeof(skelpath), "%s/%s",
+ skeleton, ent->d_name);
+ snprintf(path, sizeof(path), "%s/%s", directory,
+ ent->d_name);
+ /* What we do next depends on the type of entry we're
+ * looking at. */
+ if (lstat(skelpath, &st) != -1) {
+ /* We always want to preserve atime/mtime. */
+ timebuf.actime = st.st_atime;
+ timebuf.modtime = st.st_mtime;
+ /* If it's a directory, descend into it. */
+ if (S_ISDIR(st.st_mode)) {
+ if (!lu_homedir_populate(skelpath,
+ path,
+ owner,
+ st.st_gid ?: group,
+ st.st_mode,
+ error)) {
+ /* Aargh! Fail up. */
+ closedir(dir);
+ return 0;
+ }
+ /* Set the date on the directory. */
+ utime(path, &timebuf);
+ continue;
+ }
+ /* If it's a symlink, duplicate it. */
+ if (S_ISLNK(st.st_mode)) {
+ if (readlink(skelpath, buf,
+ sizeof(buf) - 1) != -1) {
+ buf[sizeof(buf) - 1] = '\0';
+ symlink(buf, path);
+ lchown(path, owner, st.st_gid ?: group);
+ utime(path, &timebuf);
+ }
+ continue;
+ }
+ /* If it's a regular file, copy it. */
+ if (S_ISREG(st.st_mode)) {
+ /* Open both the input and output
+ * files. If we fail to do either,
+ * we have to give up. */
+ ifd = open(skelpath, O_RDONLY);
+ if (ifd != -1) {
+ ofd = open(path,
+ O_EXCL | O_CREAT | O_WRONLY,
+ st.st_mode);
+ }
+ if ((ifd == -1) || (ofd == -1)) {
+ /* Sorry, no can do. */
+ close (ifd);
+ close (ofd);
+ continue;
+ }
+ /* Now just copy the data. */
+ do {
+ i = read(ifd, &buf, sizeof(buf));
+ if (i > 0) {
+ write(ofd, buf, i);
+ }
+ } while (i > 0);
+ /* Close the files. */
+ offset = lseek(ofd, 0, SEEK_CUR);
+ if (offset != ((off_t) -1)) {
+ ftruncate(ofd, offset);
+ }
+ close (ifd);
+ close (ofd);
+ /* Set the ownership and timestamp on
+ * the new file. */
+ chown(path, owner, st.st_gid ?: group);
+ utime(path, &timebuf);
+ continue;
+ }
+ /* Note that we don't copy device specials. */
+ }
+ }
+ closedir(dir);
+ return 1;
+}
+
+/* Recursively remove a user's home (or really, any) directory. */
+int
+lu_homedir_remove(const char *directory, struct lu_error ** error)
+{
+ struct dirent *ent;
+ DIR *dir;
+ struct stat st;
+ char path[PATH_MAX];
+ LU_ERROR_CHECK(error);
+ /* Open the directory. This catches the case that it's already gone. */
+ dir = opendir(directory);
+ if (dir == NULL) {
+ lu_error_new(error, lu_error_generic,
+ _("Error removing `%s': %s"), directory,
+ strerror(errno));
+ return 0;
+ }
+ /* Iterate over all of its contents. */
+ while ((ent = readdir(dir)) != NULL) {
+ /* Skip over the self and parent hard links. */
+ if (strcmp(ent->d_name, ".") == 0) {
+ continue;
+ }
+ if (strcmp(ent->d_name, "..") == 0) {
+ continue;
+ }
+ /* Generate the full path of the next victim. */
+ snprintf(path, sizeof(path), "%s/%s", directory, ent->d_name);
+ /* What we do next depends on whether or not the next item to
+ * remove is a directory. */
+ if (lstat(path, &st) != -1) {
+ if (S_ISDIR(st.st_mode)) {
+ /* We decend into subdirectories... */
+ if (lu_homedir_remove(path, error) == FALSE) {
+ closedir(dir);
+ return 0;
+ }
+ } else {
+ /* ... and unlink everything else. */
+ if (unlink(path) == -1) {
+ lu_error_new(error,
+ lu_error_generic,
+ _("Error removing "
+ "`%s': %s"),
+ path,
+ strerror
+ (errno));
+ closedir(dir);
+ return 0;
+ }
+ }
+ }
+ }
+ closedir(dir);
+
+ /* As a final step, remove the directory itself. */
+ if (rmdir(directory) == -1) {
+ lu_error_new(error, lu_error_generic,
+ _("Error removing `%s': %s"), directory,
+ strerror(errno));
+ return 0;
+ }
+
+ return TRUE;
+}
+/* Move a directory from one place to another. */
+int
+lu_homedir_move(const char *oldhome, const char *newhome,
+ USER__ERR ** error)
+{
+ struct stat st;
+ LU_ERROR_CHECK(error);
+ /* If the directory exists... */
+ if (stat(oldhome, &st) != -1) {
+ /* ... and we can copy it ... */
+ if (lu_homedir_populate(oldhome, newhome,
+ st.st_uid, st.st_gid, st.st_mode,
+ error)) {
+ /* ... remove the old one. */
+ return lu_homedir_remove(oldhome, error);
+ }
+ }
+ return 0;
+}
+/* Concatenate a string onto another string on the heap. */
+char *
+lu_strconcat(char *existing, const char *appendee)
+{
+ char *tmp;
+ if (existing == NULL) {
+ existing = g_strdup(appendee);
+ } else {
+ tmp = g_strconcat(existing, appendee, NULL);
+ g_free(existing);
+ existing = tmp;
+ }
+ return existing;
+}
+/* Send nscd an arbitrary signal. */
+void
+lu_signal_nscd(int signum)
+{
+ FILE *fp;
+ char buf[LINE_MAX];
+ /* If it's running, then its PID is in this file. Open it. */
+ if ((fp = fopen("/var/run/nscd.pid", "r")) != NULL) {
+ /* Read the PID. */
+ memset(buf, 0, sizeof(buf));
+ fgets(buf, sizeof(buf), fp);
+ /* If the PID is sane, send it a signal. */
+ if (strlen(buf) > 0) {
+ pid_t pid = atol(buf);
+ if (pid != 0) {
+ kill(pid, signum);
+ }
+ }
+ fclose(fp);
+ }
+}
+
+/* Send nscd a SIGHUP. */
+void
+lu_hup_nscd()
+{
+ lu_signal_nscd(SIGHUP);
+}
+
+/* Create a mail spool for the user. */
+int
+lu_mailspool_create_remove(USER__ADMIN *ctx, USER__ENT *ent,
+ int action)
+{
+ GValueArray *array;
+ GValue *value;
+ const char *spooldir;
+ long uid, gid;
+ char *p, *username;
+ struct group grp, *err;
+ USER__ENT *groupEnt;
+ USER__ERR *error = NULL;
+ char buf[LINE_MAX * 4];
+ int fd;
+
+ /* Find the GID of the owner of the file. */
+ gid = INVALID;
+ groupEnt = lu_ent_new();
+ if (lu_group_lookup_name(ctx, "mail", groupEnt, &error)) {
+ array = lu_ent_get(groupEnt, LU_GIDNUMBER);
+ if (array != NULL) {
+ value = g_value_array_get_nth(array, 0);
+ if (G_VALUE_HOLDS_LONG(value)) {
+ gid = g_value_get_long(value);
+ } else
+ if (G_VALUE_HOLDS_STRING(value)) {
+ gid = strtol(g_value_get_string(value), &p, 0);
+ if (*p != '\0') {
+ gid = INVALID;
+ }
+ } else {
+ g_assert_not_reached();
+ }
+ }
+ }
+ lu_ent_free(groupEnt);
+
+ /* Er, okay. Check with libc. */
+ if (gid == INVALID) {
+ if ((getgrnam_r("mail", &grp, buf, sizeof(buf), &err) == 0) &&
+ (err == &grp)) {
+ gid = grp.gr_gid;
+ }
+ }
+
+ /* Aiieee. Use the user's group. */
+ if (gid == INVALID) {
+ array = lu_ent_get(ent, LU_GIDNUMBER);
+ if (array != NULL) {
+ value = g_value_array_get_nth(array, 0);
+ if (G_VALUE_HOLDS_LONG(value)) {
+ gid = g_value_get_long(value);
+ } else
+ if (G_VALUE_HOLDS_STRING(value)) {
+ gid = strtol(g_value_get_string(value), &p, 0);
+ if (*p == '\0') {
+ gid = INVALID;
+ }
+ } else {
+ g_warning("Unable to determine user's GID.");
+ g_assert_not_reached();
+ }
+ }
+ }
+ g_return_val_if_fail(gid != INVALID, FALSE);
+
+ /* Now get the user's UID. */
+ array = lu_ent_get(ent, LU_UIDNUMBER);
+ if (array != NULL) {
+ value = g_value_array_get_nth(array, 0);
+ uid = INVALID;
+ if (G_VALUE_HOLDS_LONG(value)) {
+ uid = g_value_get_long(value);
+ } else
+ if (G_VALUE_HOLDS_STRING(value)) {
+ uid = strtol(g_value_get_string(value), &p, 0);
+ if (*p != '\0') {
+ uid = INVALID;
+ }
+ } else {
+ g_warning("Unable to determine user's UID.");
+ g_assert_not_reached();
+ }
+ }
+ g_return_val_if_fail(uid != INVALID, FALSE);
+
+ /* Now get the user's login. */
+ username = NULL;
+ array = lu_ent_get(ent, LU_USERNAME);
+ if (array != NULL) {
+ value = g_value_array_get_nth(array, 0);
+ if (G_VALUE_HOLDS_LONG(value)) {
+ username = g_strdup_printf("%ld",
+ g_value_get_long(value));
+ } else
+ if (G_VALUE_HOLDS_STRING(value)) {
+ username = g_value_dup_string(value);
+ } else {
+ g_warning("Unable to determine user's name.");
+ g_assert_not_reached();
+ }
+ }
+ g_return_val_if_fail(username != NULL, FALSE);
+
+ /* Get the location of the spool directory. */
+ spooldir = lu_cfg_read_single(ctx, "defaults/mailspooldir",
+ "/var/mail");
+
+ /* That wasn't that hard. Now we just need to create the file. */
+ p = g_strdup_printf("%s/%s", spooldir, username);
+ g_free(username);
+ if (action) {
+ fd = open(p, O_WRONLY | O_CREAT, 0);
+ if (fd != -1) {
+ fchown(fd, uid, gid);
+ fchmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
+ close(fd);
+ g_free(p);
+ return 1;
+ }
+ } else {
+ if (unlink(p) == 0) {
+ g_free(p);
+ return 1;
+ }
+ if (errno == ENOENT) {
+ g_free(p);
+ return 1;
+ }
+ }
+ g_free(p);
+
+ return 0;
+}
+
MODULE = USER PACKAGE = USER::ADMIN PREFIX = Admin_
USER::ADMIN *
@@ -112,13 +522,13 @@ Admin_UserAdd(self, ent, is_system, dont_create_home)
if (lu_homedir_populate(skeleton, homeDirectory,
uidNumber, gidNumber, 0700,
- &error) == FALSE) {
+ &error) == 0) {
warn("Error creating %s: %s.\n", homeDirectory, error ? error->string : "unknown error");
RETVAL = 2;
}
/* Create a mail spool for the user. */
- if (lu_mailspool_create_remove(self, ent, TRUE) != TRUE) {
+ if (lu_mailspool_create_remove(self, ent, TRUE) != 1) {
warn(_("Error creating mail spool.\n"));
return 8;
}
@@ -148,6 +558,19 @@ Admin_InitUser(self, name, is_system)
XPUSHs(sv_2mortal(sv_bless(newRV_noinc(newSViv(ent)), gv_stashpv("USER::ENT", 1))));
void
+Admin_UserSetPass(self, ent, userPasswd)
+ USER::ADMIN *self
+ USER::ENT *ent
+ char *userPasswd
+ PPCODE:
+ USER__ERR *error = NULL;
+ gboolean crypted = FALSE;
+ if (lu_user_setpass(self, ent, userPasswd, crypted, &error) == FALSE) {
+ croak("Failed to set password %s.\n", error ? error->string : _("unknown error"));
+ if (error) { lu_error_free(&error); }
+ }
+
+void
Admin_LookupUserByName(self, name)
USER::ADMIN *self
char *name
@@ -204,9 +627,15 @@ Admin_LookupGroupById(self, id)
}
void
-Admin_GroupAdd(self, name)
+Admin_GroupAdd(self, ent)
USER::ADMIN *self
- char *name
+ USER::ENT *ent
+ PPCODE:
+ struct lu_error *error = NULL;
+ GValue value;
+ if (lu_group_add(self, ent, &error) == FALSE) {
+ warn("Group creation failed.\n");
+ }
void
Admin_GroupDel(self, ent)