/* -*- 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;
}