diff options
author | Luke Shumaker <lukeshu@sbcglobal.net> | 2014-11-28 15:17:16 -0500 |
---|---|---|
committer | Luke Shumaker <lukeshu@sbcglobal.net> | 2014-11-28 15:17:16 -0500 |
commit | d8c48a5aae8f3561cec14f7f08c35fc3619ef00a (patch) | |
tree | c0c11cefff91cb8d34deb1091d4d02a1bb71f3d0 /nslcd | |
parent | c9618dfe442305531ee6cab9660333f4a697e094 (diff) |
foo
Diffstat (limited to 'nslcd')
-rw-r--r-- | nslcd/common.h | 19 | ||||
-rw-r--r-- | nslcd/db_pam.c | 258 | ||||
-rw-r--r-- | nslcd/db_passwd.c | 36 | ||||
-rw-r--r-- | nslcd/hackers_parse.c | 214 | ||||
-rw-r--r-- | nslcd/hackers_parse.h | 32 | ||||
-rw-r--r-- | nslcd/hackers_watch.c | 188 | ||||
-rw-r--r-- | nslcd/hackers_watch.h | 21 | ||||
-rw-r--r-- | nslcd/inotify_helper.c | 51 | ||||
-rw-r--r-- | nslcd/inotify_iterator.h | 78 |
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 |