/* -*- tab-width: 4; c-basic-offset: 4 -*- */ /* 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 . */ #define _GNU_SOURCE #include #include #include #include /* These three are just for name2gid, which is surprisingly * complicated. */ #include #include #include /* for sysconf(3) */ #include "hackers_parse.h" #include "hackers.h" #define DEFAULT_PASSWORD "!" /* Get a string value from a YAML scalar node */ #define STR_VALUE(node) \ (__extension__ ({ \ 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 ) */ #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) /* Returns GID_INVALID on error */ static gid_t name2gid(const char *name) { gid_t gid = GID_INVALID; char *buf = NULL; ssize_t buflen = sysconf(_SC_GETGR_R_SIZE_MAX); if (buflen < 1) buflen = 256; 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 { goto error; } } if (ret != NULL) gid = ret->gr_gid; error: 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); } /* Returns UID_INVALID on error */ uid_t filename2uid(const char *filename) { if (filename == NULL) return UID_INVALID; 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) ? (uid_t)atoi(bname) : UID_INVALID; 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; errno = 0; if (asprintf(&filename, "%s/.password", user->pw_dir) < 0) goto error; if ((file = fopen(filename, "r")) == NULL) goto nopassword; // TODO: check permissions on 'file' if ((line_len = getline(&line, &line_cap, file)) < 1) goto error; if (line[line_len-1] == '\n') line[--line_len] = '\0'; free(filename); fclose(file); user->pw_passwd = line; return 0; error: log_log(LOG_ERR, "unexpected error in %s", __func__); 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) { errno = 0; int ret = -1; unsigned char flags = 0; PASSWD_FREE(*user); FILE *yaml_file = NULL; yaml_parser_t yaml_parser; ZERO(yaml_parser); yaml_document_t yaml_document; ZERO(yaml_document); if ((user->pw_uid = filename2uid(filename)) == UID_INVALID) { log_log(LOG_NOTICE, "invalid user filename: %s", filename); goto error; } 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)); if (asprintf(&(user->pw_dir), "/home/%s", user->pw_name) < 0) goto error; flags |= PW_NAME | PW_DIR; } if (strcmp("fullname", STR_VALUE(key))==0) { ASSERT((user->pw_gecos = strdup(STR_VALUE(val))) != NULL); flags |= PW_GECOS; } if (strcmp("shell", STR_VALUE(key))==0) { ASSERT((user->pw_shell = strdup(STR_VALUE(val))) != NULL); 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 */ /* TODO */ char *grp_name = "users"/*STR_VALUE(item)*/; if ((user->pw_gid = name2gid(grp_name)) == GID_INVALID) { log_log(LOG_NOTICE, "unrecognized group name: %s", grp_name); continue; } 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; } if (flags != PW_ALL) { log_log(LOG_NOTICE, "user file was incomplete, ignoring: %s", filename); goto error; } ret = 0; goto end; error: log_log(LOG_INFO, "ignoring file: %s", filename); PASSWD_FREE(*user); user->pw_uid = UID_INVALID; end: yaml_document_delete(&yaml_document); yaml_parser_delete(&yaml_parser); if (yaml_file != NULL) fclose(yaml_file); return ret; }