summaryrefslogtreecommitdiff
path: root/nslcd
diff options
context:
space:
mode:
authorLuke Shumaker <lukeshu@sbcglobal.net>2014-11-28 15:17:16 -0500
committerLuke Shumaker <lukeshu@sbcglobal.net>2014-11-28 15:17:16 -0500
commitd8c48a5aae8f3561cec14f7f08c35fc3619ef00a (patch)
treec0c11cefff91cb8d34deb1091d4d02a1bb71f3d0 /nslcd
parentc9618dfe442305531ee6cab9660333f4a697e094 (diff)
foo
Diffstat (limited to 'nslcd')
-rw-r--r--nslcd/common.h19
-rw-r--r--nslcd/db_pam.c258
-rw-r--r--nslcd/db_passwd.c36
-rw-r--r--nslcd/hackers_parse.c214
-rw-r--r--nslcd/hackers_parse.h32
-rw-r--r--nslcd/hackers_watch.c188
-rw-r--r--nslcd/hackers_watch.h21
-rw-r--r--nslcd/inotify_helper.c51
-rw-r--r--nslcd/inotify_iterator.h78
9 files changed, 740 insertions, 157 deletions
diff --git a/nslcd/common.h b/nslcd/common.h
index ce87b09..1893698 100644
--- a/nslcd/common.h
+++ b/nslcd/common.h
@@ -256,20 +256,20 @@ int nslcd_pam_pwmod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid);
int nslcd_usermod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid);
/* macros for generating service handling code */
-#define NSLCD_HANDLE(db, fn, action, fndecls, fnread, fncheck, fnsearch, fnwrite) \
+#define NSLCD_HANDLE(db, fn, action, fndecls, fnread, fncheck, tentry, fnsearch, fnwrite, fnclean) \
int nslcd_##db##_##fn(TFILE *fp, MYLDAP_SESSION *session) \
- NSLCD_HANDLE_BODY(db, fn, action, fndecls, fnread, fncheck, fnsearch, fnwrite)
-#define NSLCD_HANDLE_UID(db, fn, action, fndecls, fnread, fncheck, fnsearch, fnwrite) \
+ NSLCD_HANDLE_BODY(db, fn, action, fndecls, fnread, fncheck, tentry, fnsearch, fnwrite, fnclean)
+#define NSLCD_HANDLE_UID(db, fn, action, fndecls, fnread, fncheck, tentry, fnsearch, fnwrite, fnclean) \
int nslcd_##db##_##fn(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid) \
- NSLCD_HANDLE_BODY(db, fn, action, fndecls, fnread, fncheck, fnsearch, fnwrite)
-#define NSLCD_HANDLE_BODY(db, fn, action, fndecls, fnread, fncheck, fnsearch, fnwrite) \
+ NSLCD_HANDLE_BODY(db, fn, action, fndecls, fnread, fncheck, tentry, fnsearch, fnwrite, fnclean)
+#define NSLCD_HANDLE_BODY(db, fn, action, fndecls, fnread, fncheck, tentry, fnsearch, fnwrite, fnclean) \
{ \
/* define common variables */ \
int32_t tmpint32; \
- void *entry = NULL; \
+ tentry *entry = NULL; \
int rc = 1; \
fndecls \
- MYLDAP_ENTRY *search(int *rcp) { fnsearch } \
+ tentry *search(int *rcp) { fnsearch } \
/* read request parameters */ \
fnread \
/* validate request parameters */ \
@@ -280,14 +280,17 @@ int nslcd_usermod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid);
/* go over results */ \
while ((entry = search(&rc)) != NULL) \
{ \
- if ( ({ fnwrite }) ) \
+ if ( ({ fnwrite }) ) { \
+ fnclean \
return -1; \
+ } \
} \
/* write the final result code */ \
if (rc == 0) \
{ \
WRITE_INT32(fp, NSLCD_RESULT_END); \
} \
+ fnclean \
return 0; \
}
diff --git a/nslcd/db_pam.c b/nslcd/db_pam.c
index 0eff71b..f47b331 100644
--- a/nslcd/db_pam.c
+++ b/nslcd/db_pam.c
@@ -39,6 +39,17 @@
#include "common/dict.h"
#include "common/expr.h"
+struct authc {
+ int authc_rc;
+ int authz_rc;
+ char authz_msg[BUFLEN_MESSAGE];
+};
+
+struct authz {
+ int authz_rc;
+ char authz_msg[BUFLEN_MESSAGE];
+};
+
/* set up a connection and try to bind with the specified DN and password,
returns an LDAP result code */
static int try_bind(const char *userdn, const char *password,
@@ -94,32 +105,6 @@ static int try_bind(const char *userdn, const char *password,
return rc;
}
-/* ensure that both userdn and username are filled in from the entry,
- returns an LDAP result code */
-static MYLDAP_ENTRY *validate_user(MYLDAP_SESSION *session,
- char *username, int *rcp)
-{
- int rc;
- MYLDAP_ENTRY *entry = NULL;
- /* check username for validity */
- if (!isvalidname(username))
- {
- log_log(LOG_WARNING, "request denied by validnames option");
- *rcp = LDAP_NO_SUCH_OBJECT;
- return NULL;
- }
- /* get the user entry based on the username */
- entry = uid2entry(session, username, &rc);
- if (entry == NULL)
- {
- if (rc == LDAP_SUCCESS)
- rc = LDAP_NO_SUCH_OBJECT;
- log_log(LOG_DEBUG, "\"%s\": user not found: %s", username, ldap_err2string(rc));
- *rcp = rc;
- }
- return entry;
-}
-
/* update the username value from the entry if needed */
static void update_username(MYLDAP_ENTRY *entry, char *username,
size_t username_len)
@@ -156,6 +141,16 @@ static void update_username(MYLDAP_ENTRY *entry, char *username,
}
}
+static int check_password(const char *password, const char *hash)
+{
+ int ret;
+ struct crypt_data data;
+ data.initialized = 0;
+ ret = (strcmp(crypt_r(password, hash, &data), hash) == 0);
+ memset(&data, 0, sizeof(data));
+ return ret;
+}
+
static int check_shadow(MYLDAP_SESSION *session, const char *username,
char *authzmsg, size_t authzmsgsz,
int check_maxdays, int check_mindays)
@@ -260,95 +255,73 @@ static int check_shadow(MYLDAP_SESSION *session, const char *username,
}
/* check authentication credentials of the user */
-int nslcd_pam_authc(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid)
-{
- int32_t tmpint32;
- int rc;
- char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
- char password[BUFLEN_PASSWORD];
- const char *userdn;
- MYLDAP_ENTRY *entry;
- int authzrc = NSLCD_PAM_SUCCESS;
- char authzmsg[BUFLEN_MESSAGE];
- authzmsg[0] = '\0';
- /* read request parameters */
+NSLCD_HANDLE_UID(
+ pam, authc, NSLCD_ACTION_PAM_AUTHC
+ ,/* decls */
+ char username[BUFLEN_NAME],
+ service[BUFLEN_NAME],
+ ruser[BUFLEN_NAME],
+ rhost[BUFLEN_HOSTNAME],
+ tty[64],
+ password[BUFLEN_PASSWORD];
+ struct authc _entry;
+ ,/* read */
READ_STRING(fp, username);
READ_STRING(fp, service);
READ_STRING(fp, ruser);
READ_STRING(fp, rhost);
READ_STRING(fp, tty);
READ_STRING(fp, password);
- /* log call */
log_setrequest("authc=\"%s\"", username);
log_log(LOG_DEBUG, "nslcd_pam_authc(\"%s\",\"%s\",\"%s\")",
username, service, *password ? "***" : "");
- /* write the response header */
- WRITE_INT32(fp, NSLCD_VERSION);
- WRITE_INT32(fp, NSLCD_ACTION_PAM_AUTHC);
- /* if the username is blank and rootpwmoddn is configured, try to
- authenticate as administrator, otherwise validate request as usual */
- if (*username == '\0')
+ ,/* check */
+ if (!isvalidname(username))
{
- if (nslcd_cfg->rootpwmoddn == NULL)
- {
- log_log(LOG_NOTICE, "rootpwmoddn not configured");
- /* we break the protocol */
- memset(password, 0, sizeof(password));
- return -1;
- }
- userdn = nslcd_cfg->rootpwmoddn;
- /* if the caller is root we will allow the use of the rootpwmodpw option */
- if ((*password == '\0') && (calleruid == 0) && (nslcd_cfg->rootpwmodpw != NULL))
- {
- if (strlen(nslcd_cfg->rootpwmodpw) >= sizeof(password))
- {
- log_log(LOG_ERR, "nslcd_pam_authc(): rootpwmodpw will not fit in password");
- memset(password, 0, sizeof(password));
- return -1;
- }
- strcpy(password, nslcd_cfg->rootpwmodpw);
- }
+ log_log(LOG_WARNING, "request denied by validnames option");
+ return -1;
}
- else
+ ,/* search(int *rcp) */
+ struct authc,
+ static size_t i = 0;
+ struct passwd *user = NULL;
+ struct authc *entry = &_entry;
+
+ *rcp = 0;
+
+ for (; i < session->cnt; i++)
{
- /* try normal authentication, lookup the user entry */
- entry = validate_user(session, username, &rc);
- if (entry == NULL)
- {
- /* for user not found we just say no result */
- if (rc == LDAP_NO_SUCH_OBJECT)
- {
- WRITE_INT32(fp, NSLCD_RESULT_END);
- }
- memset(password, 0, sizeof(password));
- return -1;
+ if (strcmp(username, session->users[i].pw_name)==0) {
+ *rcp = 0;
+ i = session->cnt;
+ user = &(session->users[i]);
}
- userdn = myldap_get_dn(entry);
- update_username(entry, username, sizeof(username));
}
+ if (user == NULL)
+ return NULL;
+
+ entry->authz_msg[0] = '\0';
+
/* try authentication */
- rc = try_bind(userdn, password, &authzrc, authzmsg, sizeof(authzmsg));
- if (rc == LDAP_SUCCESS)
- log_log(LOG_DEBUG, "bind successful");
- /* map result code */
- switch (rc)
- {
- case LDAP_SUCCESS: rc = NSLCD_PAM_SUCCESS; break;
- case LDAP_INVALID_CREDENTIALS: rc = NSLCD_PAM_AUTH_ERR; break;
- default: rc = NSLCD_PAM_AUTH_ERR;
- }
+ entry->authc_rc = check_password(password, hash)
+ ? NSLCD_PAM_SUCCESS
+ : NSLCD_PAM_AUTH_ERR;
+ entry->authz_rc = entry->authc_rc;
+ myldap_get_policy_response(session, &(entry->authz_rc), &(entry->authz_msg))
+
/* perform shadow attribute checks */
- if ((*username != '\0') && (authzrc == NSLCD_PAM_SUCCESS))
- authzrc = check_shadow(session, username, authzmsg, sizeof(authzmsg), 1, 0);
- /* write response */
+ if (entry->authz_rc == NSLCD_PAM_SUCCESS)
+ entry->authz_rc = check_shadow(session, username, entry->authz_msg, sizeof(entry->authz_msg), 1, 0);
+
+ return entry;
+ ,/* write */
WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
- WRITE_INT32(fp, rc);
+ WRITE_INT32( fp, entry->authc_rc);
WRITE_STRING(fp, username);
- WRITE_INT32(fp, authzrc);
- WRITE_STRING(fp, authzmsg);
- WRITE_INT32(fp, NSLCD_RESULT_END);
+ WRITE_INT32( fp, entry->authz_rc);
+ WRITE_STRING(fp, entry->authz_msg);
+ ,/* cleanup */
memset(password, 0, sizeof(password));
- return 0;
}
static void autzsearch_var_add(DICT *dict, const char *name,
@@ -501,15 +474,16 @@ static int try_autzsearch(MYLDAP_SESSION *session, const char *dn,
}
/* check authorisation of the user */
-int nslcd_pam_authz(TFILE *fp, MYLDAP_SESSION *session)
-{
- int32_t tmpint32;
- int rc;
- char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
- MYLDAP_ENTRY *entry;
- char authzmsg[BUFLEN_MESSAGE];
- authzmsg[0] = '\0';
- /* read request parameters */
+NSLCD_HANDLE(
+ pam, authz, NSLCD_ACTION_PAM_AUTHZ
+ ,/* decls */
+ char username[BUFLEN_NAME],
+ service[BUFLEN_NAME],
+ ruser[BUFLEN_NAME],
+ rhost[BUFLEN_HOSTNAME],
+ tty[64];
+ struct authz _entry;
+ ,/* read */
READ_STRING(fp, username);
READ_STRING(fp, service);
READ_STRING(fp, ruser);
@@ -519,46 +493,54 @@ int nslcd_pam_authz(TFILE *fp, MYLDAP_SESSION *session)
log_setrequest("authz=\"%s\"", username);
log_log(LOG_DEBUG, "nslcd_pam_authz(\"%s\",\"%s\",\"%s\",\"%s\",\"%s\")",
username, service, ruser, rhost, tty);
- /* write the response header */
- WRITE_INT32(fp, NSLCD_VERSION);
- WRITE_INT32(fp, NSLCD_ACTION_PAM_AUTHZ);
- /* validate request */
- entry = validate_user(session, username, &rc);
- if (entry == NULL)
+ ,/* search(int *rcp) */
+ struct authz,
+ static size_t i = 0;
+ struct passwd *user = NULL;
+ struct authz *entry = &_entry;
+
+ *rcp = 0;
+
+ for (; i < session->cnt; i++)
{
- /* for user not found we just say no result */
- if (rc == LDAP_NO_SUCH_OBJECT)
- {
- WRITE_INT32(fp, NSLCD_RESULT_END);
+ if (strcmp(username, session->users[i].pw_name)==0) {
+ *rcp = 0;
+ i = session->cnt;
+ user = &(session->users[i]);
}
- return -1;
}
+ if (user == NULL)
+ return NULL;
+
/* check authorisation search */
- rc = try_autzsearch(session, myldap_get_dn(entry), username, service, ruser,
- rhost, tty);
+ int rc = try_autzsearch(session, myldap_get_dn(entry),
+ username, service, ruser, rhost, tty);
if (rc != LDAP_SUCCESS)
{
- WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
- WRITE_INT32(fp, NSLCD_PAM_PERM_DENIED);
- WRITE_STRING(fp, "LDAP authorisation check failed");
- WRITE_INT32(fp, NSLCD_RESULT_END);
- return 0;
+ entry->authz_rc = NSLCD_PAM_PERM_DENIED);
+ strcpy(entry->authz_msg, "LDAP authorisation check failed");
+ return entry;
}
+
/* perform shadow attribute checks */
- rc = check_shadow(session, username, authzmsg, sizeof(authzmsg), 0, 0);
- /* write response */
+ entry->authz_rc = check_shadow(session, username, entry->authz_msg, sizeof(entry->authz_msg), 0, 0);
+
+ ,/* write response */
WRITE_INT32(fp, NSLCD_RESULT_BEGIN);
- WRITE_INT32(fp, rc);
- WRITE_STRING(fp, authzmsg);
- WRITE_INT32(fp, NSLCD_RESULT_END);
- return 0;
+ WRITE_INT32( fp, entry->authz_rc);
+ WRITE_STRING(fp, entry->authz_msg);
+ ,/* cleanup */
}
int nslcd_pam_sess_o(TFILE *fp, MYLDAP_SESSION UNUSED(*session))
{
int32_t tmpint32;
- char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
- char sessionid[25];
+ char username[BUFLEN_NAME],
+ service[BUFLEN_NAME],
+ ruser[BUFLEN_NAME],
+ rhost[BUFLEN_HOSTNAME],
+ tty[64],
+ sessionid[25];
static const char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"01234567890";
@@ -590,8 +572,12 @@ int nslcd_pam_sess_o(TFILE *fp, MYLDAP_SESSION UNUSED(*session))
int nslcd_pam_sess_c(TFILE *fp, MYLDAP_SESSION UNUSED(*session))
{
int32_t tmpint32;
- char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
- char sessionid[64];
+ char username[BUFLEN_NAME],
+ service[BUFLEN_NAME],
+ ruser[BUFLEN_NAME],
+ rhost[BUFLEN_HOSTNAME],
+ tty[64],
+ sessionid[64];
/* read request parameters */
READ_STRING(fp, username);
READ_STRING(fp, service);
@@ -735,7 +721,11 @@ int nslcd_pam_pwmod(TFILE *fp, MYLDAP_SESSION *session, uid_t calleruid)
{
int32_t tmpint32;
int rc;
- char username[BUFLEN_NAME], service[BUFLEN_NAME], ruser[BUFLEN_NAME], rhost[BUFLEN_HOSTNAME], tty[64];
+ char username[BUFLEN_NAME],
+ service[BUFLEN_NAME],
+ ruser[BUFLEN_NAME],
+ rhost[BUFLEN_HOSTNAME],
+ tty[64];
int asroot;
char oldpassword[BUFLEN_PASSWORD];
char newpassword[BUFLEN_PASSWORD];
diff --git a/nslcd/db_passwd.c b/nslcd/db_passwd.c
index 12cdea8..17b7781 100644
--- a/nslcd/db_passwd.c
+++ b/nslcd/db_passwd.c
@@ -49,10 +49,10 @@
/* Note that the resulting password value should be one of:
<empty> - no password set, allow login without password
- * - often used to prevent logins
+ ! - used to prevent logins
x - "valid" encrypted password that does not match any valid password
often used to indicate that the password is defined elsewhere
- other - encrypted password, usually in crypt(3) format */
+ other - encrypted password, in crypt(3) format */
static int write_passwd(TFILE *fp, struct passwd *entry, uid_t calleruid)
{
@@ -96,20 +96,22 @@ NSLCD_HANDLE_UID(
log_log(LOG_WARNING, "request denied by validnames option");
return -1;
}
- nsswitch_check_reload();,
- /* search */
+ nsswitch_check_reload();
+ ,/* search */
+ struct passwd,
static size_t i = 0;
for (; i < session->cnt; i++)
{
- if (strcmp(name, session->users[i].pw_name)==0) {
- *rcp = 0;
- i = session->cnt;
- return &(session->users[i]);
- }
+ if (strcmp(name, session->users[i].pw_name)==0) {
+ *rcp = 0;
+ i = session->cnt;
+ return &(session->users[i]);
+ }
}
return NULL;
- /* write */
+ ,/* write */
write_passwd(fp, entry, calleruid)
+ ,/* cleanup */
)
NSLCD_HANDLE_UID(
@@ -130,18 +132,20 @@ NSLCD_HANDLE_UID(
}
nsswitch_check_reload();
,/* search */
+ struct passwd,
static size_t i = 0;
for (; i < session->cnt; i++)
{
- if (uid == session->users[i].pw_uid) {
- *rcp = 0;
- i = session->cnt;
- return &(session->users[i]);
- }
+ if (uid == session->users[i].pw_uid) {
+ *rcp = 0;
+ i = session->cnt;
+ return &(session->users[i]);
+ }
}
return NULL;
,/* write */
write_passwd(fp, entry, calleruid)
+ ,/* cleanup */
)
NSLCD_HANDLE_UID(
@@ -152,6 +156,7 @@ NSLCD_HANDLE_UID(
,/* check */
nsswitch_check_reload();
,/* search */
+ struct passwd,
static size_t i = 0;
for (; i < session->cnt; i++)
{
@@ -160,4 +165,5 @@ NSLCD_HANDLE_UID(
return NULL;
,/* write */
write_passwd(fp, entry, calleruid);
+ ,/* cleanup */
)
diff --git a/nslcd/hackers_parse.c b/nslcd/hackers_parse.c
new file mode 100644
index 0000000..0146197
--- /dev/null
+++ b/nslcd/hackers_parse.c
@@ -0,0 +1,214 @@
+/* hackers_parse.c - load user data from hackers.git
+ *
+ * Copyright (C) 2014 Luke Shumaker
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#define _GNU_SOURCE
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <yaml.h>
+
+/* These three are just for name2gid, which is surprisingly
+ * complicated. */
+#include <errno.h>
+#include <grp.h>
+#include <unistd.h> /* for sysconf(3) */
+
+#include "hackers_parse.h"
+
+/* None of the malloc-ish calls are error checked. Bite me. */
+
+#define DEFAULT_PASSWORD "!"
+
+#define ASSERT(expr) if (!(expr)) goto error
+
+/* Get a string value from a YAML scalar node */
+#define STR_VALUE(node) \
+ ({ \
+ ASSERT((node)->type == YAML_SCALAR_NODE); \
+ ((char*)(node)->data.scalar.value); \
+ })
+
+/* Bitmask flags for the completion of the fields in
+ * 'struct passwd' (which is defined in <pwd.h>) */
+#define PW_NAME (1 << 0)
+#define PW_PASSWD (1 << 1)
+#define PW_UID (1 << 2)
+#define PW_GID (1 << 3)
+#define PW_GECOS (1 << 4)
+#define PW_DIR (1 << 5)
+#define PW_SHELL (1 << 6)
+#define PW_ALL ((1 << 7) - 1)
+
+static
+gid_t
+name2gid(const char *name) {
+ gid_t gid;
+ ssize_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX);
+ if (buflen < 1)
+ buflen = 256;
+ char *buf = malloc(buflen);
+ struct group grp;
+ struct group *ret;
+ while (getgrnam_r(name, &grp, buf, (size_t)buflen, &ret) < 0) {
+ if (errno == ERANGE) {
+ buflen += 256;
+ buf = realloc(buf, buflen);
+ } else {
+ gid = 0;
+ goto end;
+ }
+ }
+ if (ret == NULL) {
+ gid = 0;
+ goto end;
+ }
+ gid = ret->gr_gid;
+ end:
+ free(buf);
+ return gid;
+}
+
+static
+bool
+is_numeric(const char *str) {
+ size_t i;
+ for (i = 0; str[i] != '\0'; i++) {
+ if ('0' > str[i] || str[i] > '9')
+ return false;
+ }
+ return (i > 0);
+}
+
+uid_t
+filename2uid(const char *filename) {
+ if (filename == NULL)
+ return 0;
+
+ char *tmp = strdupa(filename);
+ char *bname = basename(tmp);
+ char *ext = strchr(bname, '.');
+ if ((strcmp(ext, ".yml") != 0) || (ext != &bname[4]))
+ return 0;
+ ext[0] = '\0';
+ uid_t uid = is_numeric(bname) ? atoi(bname) : 0;
+ return uid;
+}
+
+int
+load_user_password(struct passwd *user) {
+ char *filename = NULL;
+ FILE *file;
+ char *line = NULL;
+ ssize_t line_len;
+ size_t line_cap = 0;
+
+ asprintf(&filename, "%s/.password", user->pw_dir);
+ if ((file = fopen(filename, "r")) == NULL)
+ goto nopassword;
+ // TODO: check permissions on 'file'
+ if ((line_len = getline(&line, &line_cap, file)) < 1)
+ goto nopassword;
+ if (line[line_len-1] == '\n')
+ line[--line_len] = '\0';
+ free(filename);
+ fclose(file);
+ user->pw_passwd = line;
+ return 0;
+ nopassword:
+ free(filename);
+ free(line);
+ user->pw_passwd = strdup("!");
+ return 0;
+}
+
+#define NODE(id) yaml_document_get_node(&yaml_document, id)
+int
+load_user_yaml(const char *filename, struct passwd *user) {
+ int ret = -1;
+ unsigned char flags = 0;
+
+ PASSWD_FREE(*user);
+
+ FILE *yaml_file;
+ yaml_parser_t yaml_parser; ZERO(yaml_parser);
+ yaml_document_t yaml_document; ZERO(yaml_document);
+
+ uid_t uid;
+ ASSERT((uid = user->pw_uid = filename2uid(filename)) > 0);
+ flags |= PW_UID;
+
+ ASSERT((yaml_file = fopen(filename, "r")) != NULL);
+ ASSERT(yaml_parser_initialize(&yaml_parser));
+ yaml_parser_set_input_file(&yaml_parser, yaml_file);
+ ASSERT(yaml_parser_load(&yaml_parser, &yaml_document));
+ yaml_node_t *root = yaml_document_get_root_node(&yaml_document);
+
+ ASSERT(root->type == YAML_MAPPING_NODE);
+ for (yaml_node_pair_t *pair = root->data.mapping.pairs.start;
+ pair < root->data.mapping.pairs.top;
+ pair++) {
+ yaml_node_t *key = NODE(pair->key);
+ yaml_node_t *val = NODE(pair->value);
+ if (strcmp("username", STR_VALUE(key))==0) {
+ user->pw_name = strdup(STR_VALUE(val));
+ asprintf(&(user->pw_dir), "/home/%s", user->pw_name);
+ flags |= PW_NAME | PW_DIR;
+ }
+ if (strcmp("fullname", STR_VALUE(key))==0) {
+ user->pw_gecos = strdup(STR_VALUE(val));
+ flags |= PW_GECOS;
+ }
+ if (strcmp("shell", STR_VALUE(key))==0) {
+ user->pw_shell = strdup(STR_VALUE(val));
+ flags |= PW_SHELL;
+ }
+ if (strcmp("groups", STR_VALUE(key))==0) {
+ if (val->type != YAML_SEQUENCE_NODE)
+ goto error;
+ for (yaml_node_item_t *itemp = val->data.sequence.items.start;
+ itemp < val->data.sequence.items.top;
+ itemp++) {
+ yaml_node_t *item = NODE(*itemp);
+ if (itemp == val->data.sequence.items.start) {
+ /* primary group */
+ char *grp_name = STR_VALUE(item);
+ ASSERT((user->pw_gid = name2gid(grp_name)) > 0);
+ flags |= PW_GID;
+ } else {
+ /* secondary group */
+ // TODO: do something here
+ }
+ }
+ }
+ }
+ if (flags & PW_DIR) {
+ if (load_user_password(user) < 0)
+ goto error;
+ flags |= PW_PASSWD;
+ }
+ ASSERT(flags == PW_ALL);
+ ret = 0;
+ goto end;
+ error:
+ PASSWD_FREE(*user);
+ user->pw_uid = uid;
+ end:
+ yaml_document_delete(&yaml_document);
+ yaml_parser_delete(&yaml_parser);
+ fclose(yaml_file);
+ return ret;
+}
diff --git a/nslcd/hackers_parse.h b/nslcd/hackers_parse.h
new file mode 100644
index 0000000..e1df21d
--- /dev/null
+++ b/nslcd/hackers_parse.h
@@ -0,0 +1,32 @@
+#ifndef _HACKERS_PARSE_H
+#define _HACKERS_PARSE_H
+
+#include <string.h> /* for memset(3) */
+#include <stdlib.h> /* for free(3) */
+#include <pwd.h> /* for 'struct passwd' */
+
+#define ZERO(var) memset(&(var), 0, sizeof(var))
+
+/* Free+zero a 'struct passwd' */
+#define PASSWD_FREE(user) \
+ ({ \
+ free((user).pw_name); \
+ free((user).pw_passwd); \
+ free((user).pw_gecos); \
+ free((user).pw_dir); \
+ free((user).pw_shell); \
+ ZERO(user); \
+ })
+
+/** Returns 0 on error, or the UID on success. Only handles "normal
+ * user" UIDs; that is in the range [1000,9999]. */
+uid_t filename2uid(const char *filename);
+
+/** Returns 0 on success, < 0 on failure. Sets
+ userp->pw_passwd. */
+int load_user_password(struct passwd *userp);
+/** Returns 0 on success, non-zero on failere.
+ May set userp->pw_uid even on failure. */
+int load_user_yaml(const char *filename, struct passwd *userp);
+
+#endif
diff --git a/nslcd/hackers_watch.c b/nslcd/hackers_watch.c
new file mode 100644
index 0000000..5217163
--- /dev/null
+++ b/nslcd/hackers_watch.c
@@ -0,0 +1,188 @@
+/* hackers_watch.c - watch for changes in hackers.git
+ *
+ * Copyright (C) 2014 Luke Shumaker
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+#define _GNU_SOURCE
+
+#include <glob.h>
+#include <stdio.h> /* for asprintf(3) */
+#include <unistd.h> /* for chdir(3) */
+
+#include "inotify_iterator.h"
+#include "hackers_parse.h"
+#include "hackers_watch.h"
+
+#define EVENT_FILE_IS(event, str) \
+ (((event)->len == sizeof(str)) && (strcmp((event)->name, str) == 0))
+#define EVENT_CHILD_ADD (IN_CREATE | IN_MOVED_TO)
+#define EVENT_CHILD_DEL (IN_DELETE | IN_MOVED_FROM)
+#define EVENT_CHILD_MOD (IN_CLOSE_WRITE | IN_MOVED_TO)
+#define EVENT_CHILD_ANY (EVENT_CHILD_ADD | EVENT_CHILD_DEL | EVENT_CHILD_MOD)
+
+#define WATCH_HOMEDIR(session, i) \
+ session->in_user_wds[i] = \
+ inotify_add_watch(session->in_fd, \
+ session->users[i].pw_dir, \
+ EVENT_CHILD_ANY | IN_MOVE_SELF)
+
+int
+hackers_init(const char *yamldir, struct session *sess) {
+ char *glob_pattern;
+ glob_t glob_results;
+ char *filename;
+
+ sess->yamldir = strdup(yamldir);
+ pthread_rwlock_init(&(sess->lock), NULL);
+ sess->in_fd = inotify_init();
+ sess->in_wd_yaml = inotify_add_watch(sess->in_fd, yamldir, EVENT_CHILD_ANY);
+ sess->in_wd_home = inotify_add_watch(sess->in_fd, "/home" , EVENT_CHILD_ADD);
+
+ asprintf(&glob_pattern, "%s/*.yml", yamldir);
+ glob(glob_pattern, 0, NULL, &glob_results);
+ free(glob_pattern);
+
+ sess->cnt = glob_results.gl_pathc - glob_results.gl_offs;
+ sess->users = calloc(sess->cnt, sizeof(struct passwd));
+ sess->in_user_wds = calloc(sess->cnt, sizeof(int));
+
+ for (size_t i = 0; i < sess->cnt; i++) {
+ filename = glob_results.gl_pathv[glob_results.gl_offs+i];
+ if (load_user_yaml(filename, &(sess->users[i]))==0) {
+ WATCH_HOMEDIR(sess, i);
+ } else {
+ sess->users[i].pw_uid = 0;
+ sess->in_user_wds[i] = -1;
+ }
+ }
+
+ globfree(&glob_results);
+
+ return 0;
+}
+
+static
+void
+worker_watch_homedirs(struct session *sess) {
+ pthread_rwlock_wrlock(&(sess->lock));
+ for (size_t i = 0; i < sess->cnt; i++) {
+ if (sess->users[i].pw_uid > 0) {
+ int oldwd = sess->in_user_wds[i];
+ WATCH_HOMEDIR(sess, i);
+ if (oldwd != sess->in_user_wds[i]) {
+ if (oldwd != -1)
+ inotify_rm_watch(sess->in_fd, oldwd);
+ load_user_password(&(sess->users[i]));
+ }
+ }
+ }
+ pthread_rwlock_unlock(&(sess->lock));
+}
+
+static
+void
+worker_handle_add_yaml(struct session *sess, struct passwd *newdata) {
+ /* We have to first see if this is for an existing UID, then
+ * if it's not, we need to find an empty slot, potentially
+ * realloc()ing the array.
+ */
+ pthread_rwlock_wrlock(&(sess->lock));
+ ssize_t spot = -1;
+ for (size_t i = 0; i < sess->cnt; i++) {
+ if (spot < 0 && sess->users[i].pw_uid == 0) {
+ spot = i;
+ }
+ if (sess->users[i].pw_uid == newdata->pw_uid) {
+ spot = i;
+ break;
+ }
+ }
+ if (spot < 0) {
+ /* must grow the array */
+ spot = sess->cnt++;
+ sess->users = realloc(sess->users , sess->cnt * sizeof(struct passwd));
+ sess->in_user_wds = realloc(sess->in_user_wds, sess->cnt * sizeof(int));
+ ZERO(sess->users[spot]);
+ } else if (sess->users[spot].pw_uid != 0) {
+ PASSWD_FREE(sess->users[spot]);
+ }
+ sess->users[spot] = *newdata;
+ pthread_rwlock_unlock(&(sess->lock));
+}
+
+static
+void
+worker_handle_del_yaml(struct session *sess, uid_t uid) {
+ pthread_rwlock_wrlock(&(sess->lock));
+ for (size_t i = 0; i < sess->cnt; i++) {
+ if (sess->users[i].pw_uid == uid) {
+ PASSWD_FREE(sess->users[i]);
+ inotify_rm_watch(sess->in_fd, sess->in_user_wds[i]);
+ break;
+ }
+ }
+ pthread_rwlock_unlock(&(sess->lock));
+}
+
+int
+hackers_worker(struct session *sess) {
+ chdir(sess->yamldir);
+ struct inotify_event *event;
+ for (INOTIFY_ITERATOR(sess->in_fd, event)) {
+ if (event->wd == sess->in_wd_yaml) {
+ /* handle updates to yaml files */
+ if (event->mask & (EVENT_CHILD_ADD | EVENT_CHILD_MOD)) {
+ struct passwd user; ZERO(user);
+ if (load_user_yaml(event->name, &user)==0) {
+ /* User added/updated */
+ worker_handle_add_yaml(sess, &user);
+ } else if (user.pw_uid > 0) {
+ /* User became invalid */
+ worker_handle_del_yaml(sess,
+ user.pw_uid);
+ }
+ } else if (event->mask & EVENT_CHILD_DEL) {
+ uid_t uid = filename2uid(event->name);
+ if (uid > 0) {
+ worker_handle_del_yaml(sess, uid);
+ }
+ }
+ } else if ((event->wd == sess->in_wd_home)
+ && (event->mask & IN_ISDIR)) {
+ /* handle added home directory */
+ worker_watch_homedirs(sess);
+ } else {
+ /* handle a change to someone's password */
+ if (event->mask & IN_MOVE_SELF) {
+ inotify_rm_watch(sess->in_fd, event->wd);
+ worker_watch_homedirs(sess);
+ }
+ if ((event->mask & IN_IGNORED)
+ || EVENT_FILE_IS(event, ".password")) {
+ pthread_rwlock_wrlock(&(sess->lock));
+ for (size_t i = 0; i < sess->cnt; i++) {
+ if (sess->in_user_wds[i] == event->wd) {
+ if (event->mask & IN_IGNORED)
+ sess->in_user_wds[i] = -1;
+ load_user_password(&(sess->users[i]));
+ break;
+ }
+ }
+ pthread_rwlock_unlock(&(sess->lock));
+ }
+ }
+ }
+ return -1;
+}
diff --git a/nslcd/hackers_watch.h b/nslcd/hackers_watch.h
new file mode 100644
index 0000000..156813e
--- /dev/null
+++ b/nslcd/hackers_watch.h
@@ -0,0 +1,21 @@
+#ifndef _HACKERS_WATCH_H
+#define _HACKERS_WATCH_H
+
+#include <pthread.h>
+
+struct session {
+ pthread_rwlock_t lock;
+ size_t cnt;
+ struct passwd *users;
+ /* The following are only for writers */
+ char *yamldir;
+ int *in_user_wds;
+ int in_fd;
+ int in_wd_home;
+ int in_wd_yaml;
+};
+
+int hackers_init(const char *yamldir, struct session *session);
+int hackers_worker(struct session *session);
+
+#endif
diff --git a/nslcd/inotify_helper.c b/nslcd/inotify_helper.c
new file mode 100644
index 0000000..dc1ae63
--- /dev/null
+++ b/nslcd/inotify_helper.c
@@ -0,0 +1,51 @@
+/* helper.h */
+#include <sys/inotify.h> /* for 'struct inotify_event' */
+#include <limits.h> /* for 'NAME_MAX' */
+
+struct inotify_buffer {
+ ssize_t len;
+ ssize_t pos;
+ char dat[sizeof(struct inotify_event)+NAME_MAX+1];
+};
+
+struct inotify_event *inotify_next_event_r(int fd, struct inotify_buffer *buf);
+struct inotify_event *inotify_next_event(int fd);
+
+#define INOTIFY_ITERATOR(FD, EVENT) \
+ struct inotify_event *EVENT = inotify_next_event(FD); \
+ EVENT != NULL; \
+ EVENT = inotify_next_event(FD)
+
+#define INOTIFY_ITERATOR_R(FD, EVENT, BUF) \
+ struct inotify_event *EVENT = inotify_next_event_r(FD, BUF); \
+ EVENT != NULL; \
+ EVENT = inotify_next_event_r(FD, BUF)
+
+/* helper.c */
+#include <unistd.h> /* for read(3) */
+
+#define EVENT_PTR(BUF) ((struct inotify_event *)&((BUF)->dat[(BUF)->pos]))
+#define EVENT_SIZE(BUF) (sizeof(struct inotify_event) + EVENT_PTR(BUF)->len)
+
+struct inotify_event *
+inotify_next_event_r(int fd, struct inotify_buffer *buf) {
+ if ((buf->len - buf->pos > sizeof(struct inotify_event))
+ && (buf->len - buf->pos > EVENT_SIZE(buf))) {
+ buf->pos += EVENT_SIZE(buf);
+ } else {
+ do {
+ buf->len = read(FD, buf->dat, sizeof(buf->dat));
+ } while (buf->len == 0);
+ BUF.pos = 0;
+ if (buf->len < 0) {
+ return NULL;
+ }
+ }
+ return EVENT_PTR(buf);
+}
+
+struct inotify_event *
+inotify_next_event(int fd) {
+ static struct inotify_buffer buf = { 0 };
+ return inotify_next_event_r(fd, &buf);
+}
diff --git a/nslcd/inotify_iterator.h b/nslcd/inotify_iterator.h
new file mode 100644
index 0000000..0fa5829
--- /dev/null
+++ b/nslcd/inotify_iterator.h
@@ -0,0 +1,78 @@
+/* inotify_helper.h - Simple iteration for inotify events
+ *
+ * Copyright (C) 2014 Luke Shumaker
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/* The usage is pretty simple:
+ *
+ * int my_filedesc = inotify_init();
+ * ...
+ * struct inotify_event *my_event;
+ * for (INOTIFY_ITERATOR(my_filedesc, my_event)) {
+ * ...
+ * }
+ *
+ * Easy, right?
+ */
+
+#ifndef _INOTIFY_ITERATOR_H
+#define _INOTIFY_ITERATOR_H
+
+#include <sys/inotify.h>
+#include <limits.h> /* for NAME_MAX */
+#include <string.h> /* for memset(3) */
+#include <unistd.h> /* for read(3) */
+
+struct _inotify_buffer {
+ ssize_t len;
+ ssize_t pos;
+ char dat[sizeof(struct inotify_event)+NAME_MAX+1];
+};
+
+#define _INOTIFY_ITERATOR_EVENT(BUF) \
+ ((struct inotify_event *)&((BUF).dat[(BUF).pos]))
+#define _INOTIFY_ITERATOR_EVENT_SIZE(BUF) \
+ (sizeof(struct inotify_event) + _INOTIFY_ITERATOR_EVENT(BUF)->len)
+#define _INOTIFY_ITERATOR_CHECK(FD, BUF, EVENT) \
+ BUF.len > -1
+#define _INOTIFY_ITERATOR_UPDATE(FD, BUF, EVENT) \
+ EVENT = ({ \
+ if (BUF.len-(BUF.pos+_INOTIFY_ITERATOR_EVENT_SIZE(BUF)) < 1) { \
+ do { \
+ BUF.len = read(FD, BUF.dat, sizeof(BUF.dat)); \
+ } while (BUF.len == 0); \
+ BUF.pos = 0; \
+ } else { \
+ BUF.pos += _INOTIFY_ITERATOR_EVENT_SIZE(BUF); \
+ } \
+ _INOTIFY_ITERATOR_EVENT(BUF); \
+ })
+#define _INOTIFY_ITERATOR_INIT(FD, BUF, EVENT) \
+ struct _inotify_buffer BUF = ({ \
+ struct _inotify_buffer tmp; \
+ memset(&tmp, 0, sizeof(tmp)); \
+ do { \
+ tmp.len = read(FD, tmp.dat, sizeof(tmp.dat)); \
+ } while (tmp.len == 0); \
+ EVENT = (struct inotify_event *)&BUF; \
+ tmp; \
+ })
+#define INOTIFY_ITERATOR(FD, EVENT) \
+ _INOTIFY_ITERATOR_INIT(FD, _buf, EVENT); \
+ _INOTIFY_ITERATOR_CHECK(FD, _buf, EVENT); \
+ _INOTIFY_ITERATOR_UPDATE(FD, _buf, EVENT)
+
+#endif