/* wydawca - automatic release submission daemon
Copyright (C) 2007-2022 Sergey Poznyakoff
Wydawca 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.
Wydawca 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 wydawca. If not, see . */
#include "wydawca.h"
#include "sql.h"
static int create_directories;
static struct directory_metadata global_directory_metadata;
struct keyword {
char *name;
int tok;
};
enum {
CASE_SENSITIVE,
CASE_INSENSITIVE
};
static int
keyword_to_tok(const char *str, struct keyword *kw, int ci, int *pres)
{
for (; kw->name; kw++)
if ((ci ? strcasecmp : strcmp) (kw->name, str) == 0) {
*pres = kw->tok;
return 0;
}
return 1;
}
static int
tok_to_keyword(int tok, struct keyword *kw, const char **pres)
{
for (; kw->name; kw++)
if (kw->tok == tok) {
*pres = kw->name;
return 0;
}
return 1;
}
static struct keyword kwfac[] = {
{ "USER", LOG_USER },
{ "DAEMON", LOG_DAEMON },
{ "AUTH", LOG_AUTH },
{ "AUTHPRIV", LOG_AUTHPRIV },
{ "MAIL", LOG_MAIL },
{ "CRON", LOG_CRON },
{ "LOCAL0", LOG_LOCAL0 },
{ "LOCAL1", LOG_LOCAL1 },
{ "LOCAL2", LOG_LOCAL2 },
{ "LOCAL3", LOG_LOCAL3 },
{ "LOCAL4", LOG_LOCAL4 },
{ "LOCAL5", LOG_LOCAL5 },
{ "LOCAL6", LOG_LOCAL6 },
{ "LOCAL7", LOG_LOCAL7 },
{ NULL }
};
static struct keyword kwpri[] = {
{ "EMERG", LOG_EMERG },
{ "ALERT", LOG_ALERT },
{ "CRIT", LOG_CRIT },
{ "ERR", LOG_ERR },
{ "WARNING", LOG_WARNING },
{ "NOTICE", LOG_NOTICE },
{ "INFO", LOG_INFO },
{ "DEBUG", LOG_DEBUG },
{ NULL }
};
int
wy_strtofac(const char *str)
{
int res;
if (keyword_to_tok(str, kwfac, CASE_INSENSITIVE, &res))
return -1;
return res;
}
int
wy_strtopri(const char *str)
{
int res;
if (keyword_to_tok(str, kwpri, CASE_INSENSITIVE, &res))
return -1;
return res;
}
const char *
wy_pritostr(int pri)
{
const char *res;
if (tok_to_keyword(pri, kwpri, &res))
return NULL;
return res;
}
const char *
wy_factostr(int fac)
{
const char *res;
if (tok_to_keyword(fac, kwfac, &res))
return NULL;
return res;
}
static struct archive_descr default_archive_descr = {
archive_none,
NULL,
no_backups
};
static struct dictionary *default_dictionary[dictionary_count];
NOTIFYQ default_notification = NOTIFYQ_INITIALIZER(default_notification);
/* safe_file_name: convert a file name possibly containig relative
specs (../) into a safer form using only direct descendence.
Strip trailing delimiter if present, unless it is the only character
left. E.g.:
/home/user/../smith --> /home/smith
/home/user/../.. --> /
../file --> NULL
*/
char *
safe_file_name(char *file_name)
{
int len;
char *p;
if (!file_name)
return file_name;
len = strlen(file_name);
/* Empty string is returned as is */
if (len == 0)
return file_name;
/* delete trailing delimiter if any */
if (len && file_name[len - 1] == '/')
file_name[len - 1] = 0;
/* Eliminate any ./ and /../ */
for (p = strchr(file_name, '.'); p; p = strchr(p, '.')) {
if (p[1] == '/' && (p == file_name || p[-1] == '/')) {
char *q, *s;
s = p + 2;
q = p;
while ((*q++ = *s++));
continue;
} else if (p[1] == '.' && (p[2] == 0 || p[2] == '/')) {
if (p == file_name)
return NULL;
if (p[-1] == '/') {
/* found */
char *q, *s;
s = p + 2;
/* Find previous delimiter */
for (q = p - 2; *q != '/' && q >= file_name; q--);
if (q < file_name) {
q = file_name;
s++;
}
/* Copy stuff */
p = q;
while ((*q++ = *s++));
continue;
}
}
p++;
}
if (file_name[0] == 0) {
file_name[0] = '/';
file_name[1] = 0;
}
return file_name;
}
/* Same as safe_file_name, but returns an allocated copy. */
char *
safe_file_name_alloc(const char *file_name)
{
char *s = grecs_strdup(file_name);
char *ns = safe_file_name(s);
if (!ns)
free(s);
return ns;
}
static struct keyword event_tab[] = {
{ "success", wy_ev_success },
{ "bad-ownership", wy_ev_bad_ownership },
{ "bad-directive-signature", wy_ev_bad_directive_signature },
{ "bad-detached-signature", wy_ev_bad_detached_signature },
{ "check-failure", wy_ev_check_fail },
{ "statistics", wy_ev_stat },
{ "finish", wy_ev_stat },
{ NULL }
};
const char *
wy_event_str(enum wy_event evt)
{
const char *ret;
if (tok_to_keyword(evt, event_tab, &ret)) {
grecs_error(NULL, 0,
_("INTERNAL ERROR: "
"unknown notification event number: %d"), evt);
return NULL;
}
return ret;
}
int
string_to_wy_event(grecs_locus_t * locus, const char *val,
enum wy_event *pret)
{
int res;
if (keyword_to_tok(val, event_tab, CASE_SENSITIVE, &res)) {
grecs_error(locus, 0, _("unknown notification event: %s"), val);
return 1;
}
*pret = res;
return 0;
}
int
wy_assert_string_arg(grecs_locus_t *locus,
enum grecs_callback_command cmd,
const grecs_value_t * value)
{
if (cmd != grecs_callback_set_value) {
grecs_error(locus, 0, _("Unexpected block statement"));
return 1;
}
if (!value || value->type != GRECS_TYPE_STRING) {
grecs_error(&value->locus, 0, _("expected scalar value as a tag"));
return 1;
}
return 0;
}
grecs_value_t *
get_arg(grecs_value_t * value, unsigned n, int type)
{
if (!value || value->type != GRECS_TYPE_ARRAY || n >= value->v.arg.c) {
grecs_error(&value->locus, 0, _("not enough arguments"));
return NULL;
}
value = value->v.arg.v[n];
if (value->type != type) {
grecs_error(&value->locus, 0, _("argument %d has wrong type"), n);
return NULL;
}
return value;
}
static int
cb_interval(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
int rc;
time_t interval;
const char *endp;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
/* FIXME 1: Support arrays */
if (wy_assert_string_arg(locus, cmd, value))
return 1;
/* FIXME 2: Support ISO intervals? */
rc = parse_time_interval(value->v.string, &interval, &endp);
if (rc)
grecs_error(&value->locus, 0,
_("unrecognized interval format (near `%s')"), endp);
else
*(time_t *) varptr = interval;
return 0;
}
static int
cb_absolute_name(enum grecs_callback_command cmd,
grecs_node_t * node, void *varptr, void *cb_data)
{
char *word;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
/* FIXME 1: Support arrays */
if (wy_assert_string_arg(locus, cmd, value))
return 1;
word = safe_file_name((char *) value->v.string);
if (!word || word[0] != '/')
grecs_error(&value->locus, 0, _("must be an absolute file name"));
else
*(char **) varptr = word;
return 0;
}
static int
cb_set_umask(enum grecs_callback_command cmd,
grecs_node_t * node, void *varptr, void *cb_data)
{
char *p;
mode_t m;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
m = strtoul(value->v.string, &p, 8) & 0777;
if (*p)
grecs_error(&value->locus, 0, _("invalid umask (near %s)"), p);
else
umask(m);
return 0;
}
static struct keyword stat_tab[] = {
{ "errors", WY_STAT_ERRORS },
{ "warnings", WY_STAT_WARNINGS },
{ "bad-signatures", WY_STAT_BAD_SIGNATURE },
{ "access-violations", WY_STAT_ACCESS_VIOLATIONS },
{ "complete-triplets", WY_STAT_COMPLETE_TRIPLETS },
{ "incomplete-triplets", WY_STAT_INCOMPLETE_TRIPLETS },
{ "bad-triplets", WY_STAT_BAD_TRIPLETS },
{ "expired-triplets", WY_STAT_EXPIRED_TRIPLETS },
{ "triplet-success", WY_STAT_TRIPLET_SUCCESS },
{ "uploads", WY_STAT_UPLOADS },
{ "archives", WY_STAT_ARCHIVES },
{ "symlinks", WY_STAT_SYMLINKS },
{ "rmsymlinks", WY_STAT_RMSYMLINKS },
{ NULL },
};
static int
parse_single_statmask(grecs_locus_t *locus, const grecs_value_t *val,
unsigned long *pmask)
{
const char *arg;
int x;
if (val->type != GRECS_TYPE_STRING) {
grecs_error(&val->locus, 0,
_("expected scalar value but found list"));
return 1;
}
arg = val->v.string;
if (strcmp(arg, "all") == 0) {
*pmask |= WY_STAT_MASK_ALL;
return 0;
} else if (strcmp(arg, "none") == 0) {
*pmask &= ~WY_STAT_MASK_ALL;
return 0;
}
if (keyword_to_tok(arg, stat_tab, CASE_SENSITIVE, &x)) {
grecs_error(&val->locus, 0, _("unknown statistics type: %s"), arg);
return 1;
}
*pmask |= WY_STAT_MASK(x);
return 0;
}
static int
parse_statmask(grecs_locus_t *loc, grecs_value_t *val, unsigned long *pmask)
{
int err = 0;
unsigned long mask = *pmask;
int i;
struct grecs_list_entry *ep;
switch (val->type) {
case GRECS_TYPE_STRING:
err = parse_single_statmask(loc, val, &mask);
break;
case GRECS_TYPE_ARRAY:
for (i = 0; i < val->v.arg.c; i++) {
if (parse_single_statmask(loc, val->v.arg.v[i], &mask)) {
err = 1;
}
}
break;
case GRECS_TYPE_LIST:
for (ep = val->v.list->head; ep; ep = ep->next) {
const grecs_value_t *vp = ep->data;
if (parse_single_statmask(loc, vp, &mask))
err = 1;
}
break;
}
if (!err)
*pmask = mask;
return err;
}
int
wy_cb_statistics(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
return parse_statmask(&node->locus, node->v.value, varptr);
}
static int
cb_sql_host(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct sqlconn *pconn = varptr;
char *p;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
p = strchr(value->v.string, ':');
if (p) {
/* FIXME: Modifies constant string */
*p++ = 0;
if (p[0] == '/') {
pconn->socket = grecs_strdup(p);
pconn->host = grecs_strdup("localhost");
} else {
char *end;
unsigned long n = strtoul(p, &end, 10);
if (*end) {
grecs_error(&value->locus, 0,
_("invalid port number (near %s)"), end);
return 0;
}
if (n == 0 || n > USHRT_MAX) {
grecs_error(&value->locus, 0,
_("port number out of range 1..%d"),
USHRT_MAX);
return 0;
}
pconn->port = n;
/* Save host name */
pconn->host = grecs_strdup(value->v.string);
}
} else
pconn->host = grecs_strdup(value->v.string);
return 0;
}
static int
cb_sql(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct sqlconn *pconn;
void **pdata = cb_data;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
switch (cmd) {
case grecs_callback_section_begin:
if (!value || value->type != GRECS_TYPE_STRING) {
grecs_error(value ? &value->locus : locus, 0,
_("tag must be a string"));
return 0;
}
pconn = grecs_zalloc(sizeof(*pconn));
pconn->ident = strdup(value->v.string);
*pdata = pconn;
break;
case grecs_callback_section_end:
pconn = *pdata;
sql_register_conn(pconn);
free(pconn);
*pdata = NULL;
break;
case grecs_callback_set_value:
grecs_error(locus, 0, _("invalid use of block statement"));
}
return 0;
}
static struct grecs_keyword sql_kw[] = {
{ "config-file", N_("file"),
N_("Read MySQL configuration from "),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct sqlconn, config_file) },
{ "config-group", N_("name"),
N_("Read the named group from the SQL configuration file"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct sqlconn, config_group) },
{ "host", N_("host"),
N_("Set SQL server hostname or IP address"),
grecs_type_string, GRECS_CONST,
NULL, 0, cb_sql_host },
{ "database", N_("dbname"), N_("Set database name"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct sqlconn, database), },
{ "user", N_("name"), N_("Set SQL user name"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct sqlconn, user) },
{ "password", N_("arg"), N_("Set SQL user password"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct sqlconn, password) },
{ "ssl-ca", N_("file"),
N_("File name of the Certificate Authority (CA) certificate"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct sqlconn, cacert) },
{ NULL }
};
static int
cb_syslog_facility(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
int fac;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
fac = wy_strtofac(value->v.string);
if (fac == -1)
grecs_error(&value->locus, 0,
_("Unknown syslog facility `%s'"), value->v.string);
else
*(int *) varptr = fac;
return 0;
}
static struct grecs_keyword syslog_kw[] = {
{ "facility", N_("name"),
N_("Set syslog facility. Arg is one of the following: user, daemon, "
"auth, authpriv, mail, cron, local0 through local7 "
"(case-insensitive), or a facility number."),
grecs_type_string, GRECS_CONST,
&wy_log_facility, 0, cb_syslog_facility },
{ "tag", N_("string"),
N_("Tag syslog messages with this string"),
grecs_type_string, GRECS_CONST,
&wy_syslog_tag },
{ "print-priority", N_("arg"),
N_("Prefix each message with its priority"),
grecs_type_bool, GRECS_CONST,
&syslog_include_prio },
{ NULL },
};
static int
cb_metadata_mode(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
unsigned long m;
char *p;
struct directory_metadata *mp = varptr;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
m = strtoul(value->v.string, &p, 8);
if (*p) {
grecs_error(&value->locus, 0, _("invalid file mode (near %s)"), p);
return 1;
}
if (m & ~07777) {
grecs_error(&value->locus, 0, "%s", _("file mode out of range"));
return 1;
}
mp->flags |= METADATA_MODE;
mp->mode = m;
return 0;
}
static int
arg_to_uid(grecs_value_t *value, uid_t *uid)
{
char const *user = value->v.string;
unsigned long n;
char *p;
struct passwd *pw;
if (user[0] == '+') {
user++;
errno = 0;
n = strtoul(user, &p, 10);
if (errno || *p) {
grecs_error(&value->locus, 0, _("invalid user ID: %s"), user);
return 1;
}
*uid = n;
return 0;
} else if (isdigit(user[0])) {
errno = 0;
n = strtoul(user, &p, 10);
if (errno) {
grecs_error(&value->locus, 0, _("invalid user ID: %s"), user);
return 1;
}
if (*p == 0) {
*uid = 0;
return 0;
}
}
pw = getpwnam(user);
if (!pw) {
grecs_error(&value->locus, 0, _("no such user: %s"), user);
return 1;
}
*uid = pw->pw_uid;
return 0;
}
static int
arg_to_gid(grecs_value_t *value, gid_t *gid)
{
char const *group = value->v.string;
unsigned long n;
char *p;
struct group *grp;
if (group[0] == '+') {
group++;
errno = 0;
n = strtoul(group, &p, 10);
if (errno || *p) {
grecs_error(&value->locus, 0, _("invalid GID: %s"), group);
return 1;
}
*gid = n;
return 0;
} else if (isdigit(group[0])) {
errno = 0;
n = strtoul(group, &p, 10);
if (errno) {
grecs_error(&value->locus, 0, _("invalid GID: %s"), group);
return 1;
}
if (*p == 0) {
*gid = 0;
return 0;
}
}
grp = getgrnam(group);
if (!grp) {
grecs_error(&value->locus, 0, _("no such group: %s"), group);
return 1;
}
*gid = grp->gr_gid;
return 0;
}
static int
cb_metadata_owner(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
grecs_value_t *value = node->v.value, *uval, *gval;
struct directory_metadata *mp = varptr;
if (!(uval = get_arg(value, 0, GRECS_TYPE_STRING)))
return 1;
if (!(gval = get_arg(value, 1, GRECS_TYPE_STRING)))
return 1;
if (arg_to_uid(uval, &mp->uid))
return 1;
if (arg_to_gid(gval, &mp->gid))
return 1;
mp->flags |= METADATA_OWNER;
return 0;
}
static struct keyword backup_tab[] = {
{ "none", no_backups },
{ "off", no_backups },
{ "simple", simple_backups },
{ "never", simple_backups },
{ "existing", numbered_existing_backups },
{ "nil", numbered_existing_backups },
{ "numbered", numbered_backups },
{ "t", numbered_backups },
{ NULL }
};
static enum backup_type
get_backup_version(grecs_locus_t * locus, const char *ctx,
const char *version)
{
int d;
if (version == 0 || *version == 0)
return numbered_existing_backups;
else if (keyword_to_tok(version, backup_tab, CASE_SENSITIVE, &d)) {
if (ctx)
grecs_error(locus, 0,
_("%s: ambiguous backup type `%s'"), ctx, version);
else
grecs_error(locus, 0, _("ambiguous backup type `%s'"),
version);
return no_backups;
}
return d;
}
static int
cb_backup(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
enum backup_type *ptype = varptr;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
*ptype = get_backup_version(&value->locus, NULL, value->v.string);
return 0;
}
static struct grecs_keyword archive_kw[] = {
{ "name", N_("file-or-dir"),
N_("Name of archive file or directory"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct archive_descr, name)},
{ "backup", N_("type"),
N_("Define backup type"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct archive_descr, backup_type),
cb_backup },
{ "directory-mode", N_("mode: octal"),
N_("mode for the archive directory"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct archive_descr, metadata),
cb_metadata_mode },
{ "directory-owner", N_("uid: name-or-uid> locus;
grecs_value_t *value = node->v.value;
switch (cmd) {
case grecs_callback_section_begin:
*pdata = arch;
/* fallthrough */
case grecs_callback_set_value:
if (!value) {
grecs_error(locus, 0, _("expected tag"));
return 1;
}
if (value->type != GRECS_TYPE_STRING) {
grecs_error(&value->locus, 0,
_("expected scalar value but found list"));
return 1;
}
if (strcmp(value->v.string, "none") == 0)
arch->type = archive_none;
else if (strcmp(value->v.string, "tar") == 0)
arch->type = archive_tar;
else if (strcmp(value->v.string, "directory") == 0)
arch->type = archive_directory;
else {
grecs_error(&value->locus, 0, _("unknown archive type"));
return 1;
}
if (cmd == grecs_callback_section_begin)
return 0;
break;
case grecs_callback_section_end:
break;
}
if (arch->type == archive_none)
return 0;
if (arch->name == NULL) {
grecs_error(locus, 0, _("at least archive name must be set"));
return 1;
}
if (arch->type == archive_tar && arch->backup_type != no_backups) {
grecs_warning(locus, 0,
_("backup type ignored for this archive type"));
return 1;
}
return 0;
}
static int
cb_event(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
enum wy_event *pev = varptr;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
string_to_wy_event(&value->locus, value->v.string, pev);
return 0;
}
static struct grecs_keyword notify_event_kw[] = {
{ "event", N_("ev-id"),
N_("Event on which to notify"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct notification, ev), cb_event },
{ "module", N_("name"),
N_("Name of the module to invoke on event"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct notification, modname) },
{ "module-config", NULL,
N_("Module-specific configuration data"),
grecs_type_section, GRECS_INAC, NULL, 0, NULL, NULL, NULL },
{ NULL }
};
static int
cb_notify_event(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct notification *ntf;
void **pdata = cb_data;
grecs_locus_t *locus = &node->locus;
switch (cmd) {
case grecs_callback_section_begin:
ntf = grecs_zalloc(sizeof(*ntf));
ntf->modnode = grecs_find_node(node->down, "module-config");
*pdata = ntf;
break;
case grecs_callback_section_end:
ntf = *pdata;
if (!ntf->modname)
grecs_error(locus, 0, _("missing module name"));
else {
/* FIXME: Check if the module is defined. Better yet,
delay this check until config_finish */
NOTIFYQ_APPEND((NOTIFYQ*)varptr, ntf);
/* FIXME: check ev and tgt? */
}
break;
case grecs_callback_set_value:
grecs_error(locus, 0, _("invalid use of block statement"));
}
return 0;
}
static enum dictionary_type
string_to_dictionary_type(const char *str)
{
if (strcmp(str, "sql") == 0)
return dictionary_sql;
else if (strcmp(str, "builtin") == 0)
return dictionary_builtin;
else if (strcmp(str, "external") == 0)
return dictionary_external;
else
return dictionary_none;
}
static int
cb_dictionary_type(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
enum dictionary_type *ptype = varptr;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
*ptype = string_to_dictionary_type(value->v.string);
if (*ptype == dictionary_none)
grecs_error(&value->locus, 0,
_("unknown dictionary type: %s"), value->v.string);
return 0;
}
static int
cb_dictionary_params(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct dictionary *meth = varptr;
size_t size;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
if (cmd != grecs_callback_set_value) {
grecs_error(locus, 0, _("Unexpected block statement"));
return 1;
}
if (!value || value->type != GRECS_TYPE_LIST) {
grecs_error(value ? &value->locus : locus, 0,
_("expected list value"));
return 1;
}
size = grecs_list_size(value->v.list);
if (size == 0) {
meth->parmc = 0;
meth->parmv = NULL;
} else {
int i;
struct grecs_list_entry *ep;
meth->parmc = size;
meth->parmv = grecs_calloc(size + 1, sizeof(meth->parmv[0]));
for (i = 0, ep = value->v.list->head; ep; ep = ep->next, i++) {
const grecs_value_t *vp = ep->data;
if (wy_assert_string_arg(locus, cmd, vp))
break;
meth->parmv[i] = grecs_strdup(vp->v.string);
}
meth->parmv[i] = NULL;
}
return 0;
}
static struct grecs_keyword dictionary_kw[] = {
{ "type", N_("type"),
N_("Dictionary type"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct dictionary, type),
cb_dictionary_type },
{ "query", N_("string"),
N_("Query template"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct dictionary, query) },
{ "params", N_("arg"),
N_("Set dictionary parameters"),
grecs_type_string, GRECS_LIST,
NULL, 0, cb_dictionary_params },
{ NULL }
};
int
string_to_dictionary_id(grecs_locus_t * locus,
const char *str, enum dictionary_id *idp)
{
static struct keyword id_tab[] = {
{ "project-uploader", project_uploader_dict },
{ "project-owner", project_owner_dict },
{ NULL }
};
int res;
if (keyword_to_tok(str, id_tab, CASE_SENSITIVE, &res)) {
grecs_error(locus, 0, _("unknown dictionary ID: %s"), str);
return 1;
}
*idp = res;
return 0;
}
static int
cb_dictionary(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct dictionary **pmeth, *meth;
void **pdata = cb_data;
enum dictionary_id id;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
switch (cmd) {
case grecs_callback_section_begin:
if (!value || value->type != GRECS_TYPE_STRING) {
grecs_error(value ? &value->locus : locus, 0,
_("tag must be a string"));
return 0;
}
if (string_to_dictionary_id(&value->locus, value->v.string, &id))
return 1;
pmeth = (struct dictionary **) varptr + id;
*pmeth = dictionary_new(id, dictionary_builtin);
*pdata = *pmeth;
break;
case grecs_callback_section_end:
meth = *pdata;
switch (meth->type) {
case dictionary_sql:
if (meth->parmc == 0 || !meth->parmv[0]) {
grecs_error(locus, 0,
_("SQL connection is not " "declared"));
meth->type = dictionary_none;
} else if (!sql_connection_exists_p(meth->parmv[0])) {
grecs_error(locus, 0,
_("SQL connection `%s' "
"not declared"), meth->parmv[0]);
meth->type = dictionary_none;
}
break;
default:
/* FIXME: More checks ? */
break;
}
*pdata = NULL;
break;
case grecs_callback_set_value:
grecs_error(locus, 0, _("invalid use of block statement"));
}
return 0;
}
static int
cb_url(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
wy_url_t *purl = varptr, url;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
url = wy_url_create(value->v.string);
if (!url) {
grecs_error(&value->locus, 0, _("cannot create URL `%s': %s"),
value->v.string, strerror(errno));
return 1;
}
*purl = url;
return 0;
}
static struct grecs_keyword spool_kw[] = {
{ "url", N_("arg"),
N_("URL corresponding to this spool"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct spool, url) },
{ "alias", N_("arg"),
N_("Aliases"),
grecs_type_string, GRECS_LIST,
NULL, offsetof(struct spool, aliases) },
{ "source", N_("dir"),
N_("Source directory"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct spool, source_dir) },
{ "source-mode", N_("mode: octal"),
N_("mode for the source directory"),
grecs_type_string, GRECS_CONST,
NULL, offsetof(struct spool, source_metadata),
cb_metadata_mode },
{ "source-owner", N_("uid: name-or-uid> locus;
grecs_value_t *value = node->v.value;
switch (cmd) {
case grecs_callback_section_begin:
if (!value || value->type != GRECS_TYPE_STRING) {
grecs_error(value ? &value->locus : locus, 0,
_("tag must be a string"));
return 1;
}
spool = grecs_zalloc(sizeof(*spool));
spool->tag = grecs_strdup(value->v.string);
spool->file_sweep_time = file_sweep_time;
spool->inotify_enable = 1;
for (i = 0; i < NITEMS(spool->dictionary); i++)
spool->dictionary[i] = default_dictionary[i];
spool->archive = default_archive_descr;
spool->source_metadata.flags = METADATA_NONE;
spool->locus.beg.file = grecs_strdup(locus->beg.file);
spool->locus.beg.line = locus->beg.line;
spool->locus.beg.col = locus->beg.col;
*pdata = spool;
break;
case grecs_callback_section_end:
rc = 0;
spool = *pdata;
if (!spool->source_dir) {
grecs_error(locus, 0, _("source is not given"));
rc = 1;
}
if (!spool->dest_url) {
grecs_error(locus, 0, _("destination is not given"));
rc = 1;
} else if (url_to_vtab(spool->dest_url, &spool->vtab)) {
grecs_error(locus, 0, _("unsupported url: %s"),
wy_url_printable(spool->dest_url));
rc = 1;
} else if (spool->vtab.test_url
&& spool->vtab.test_url(spool->dest_url, locus))
rc = 1;
for (i = 0; i < dictionary_count; i++)
if (spool->dictionary[i]->type == dictionary_external) {
grecs_error(locus, 0,
_("Sorry, the dictionary type "
"`external' is not yet " "supported"));
rc = 1;
}
if (rc) {
//FIXME: free spool */
return rc;
}
spool->source_fd = -1;
spool->locus.end.file = grecs_strdup(locus->end.file);
spool->locus.end.line = locus->end.line;
spool->locus.end.col = locus->end.col;
//FIXME
if (NOTIFYQ_EMPTY(&spool->notification_queue))
spool->notification_queue = default_notification;
spool->dest_dir = wy_url_printable(spool->dest_url);
register_spool(spool);
*pdata = NULL;
break;
case grecs_callback_set_value:
grecs_error(locus, 0, _("invalid use of block statement"));
}
return 0;
}
static int
cb_user(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct passwd *pw;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
pw = getpwnam(value->v.string);
if (!pw) {
grecs_error(&value->locus, 0,
_("no such user: %s"), value->v.string);
return 1;
}
wydawca_uid = pw->pw_uid;
wydawca_gid = pw->pw_gid;
wydawca_runas = 1;
return 0;
}
static int
cb_supp_groups(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
if (cmd != grecs_callback_set_value) {
grecs_error(locus, 0, _("Unexpected block statement"));
return 1;
}
if (!value || value->type != GRECS_TYPE_LIST) {
grecs_error(value ? &value->locus : locus, 0,
_("expected list value"));
return 1;
}
wydawca_supp_groupc = grecs_list_size(value->v.list);
if (wydawca_supp_groupc == 0)
wydawca_supp_groups = NULL;
else {
int i;
struct grecs_list_entry *ep;
wydawca_supp_groups =
grecs_calloc(wydawca_supp_groupc,
sizeof(wydawca_supp_groups[0]));
for (i = 0, ep = value->v.list->head; ep; ep = ep->next, i++) {
const grecs_value_t *vp = ep->data;
struct group *grp;
if (wy_assert_string_arg(locus, cmd, vp))
break;
grp = getgrnam(vp->v.string);
if (!grp) {
grecs_error(&value->locus, 0,
_("no such group: %s"), vp->v.string);
break;
}
wydawca_supp_groups[i] = grp->gr_gid;
}
}
wydawca_runas = 1;
return 0;
}
static int
cb_load_path(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
struct grecs_list **lpp = varptr, *lp;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
if (*lpp)
lp = *lpp;
else {
lp = _grecs_simple_list_create(1);
*lpp = lp;
}
switch (value->type) {
case GRECS_TYPE_STRING:
grecs_list_append(lp, grecs_strdup(value->v.string));
break;
case GRECS_TYPE_LIST:{
struct grecs_list_entry *ep;
for (ep = value->v.list->head; ep; ep = ep->next) {
const grecs_value_t *vp = ep->data;
if (vp->type != GRECS_TYPE_STRING) {
grecs_error(&vp->locus, 0,
_("list element must be a string"));
return 1;
}
grecs_list_append(lp, grecs_strdup(vp->v.string));
}
break;
}
case GRECS_TYPE_ARRAY:
grecs_error(locus, 0, _("too many arguments"));
return 1;
}
return 0;
}
static int
cb_upload_version(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
unsigned *pversion = varptr, n;
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
if (directive_pack_version(value->v.string, &n)) {
grecs_error(&value->locus, 0, _("invalid version number"));
return 0;
}
if (n < MIN_DIRECTIVE_VERSION) {
grecs_error(&value->locus, 0, _("version number too low"));
return 0;
}
if (n > MAX_DIRECTIVE_VERSION) {
grecs_error(&value->locus, 0, _("version number too high"));
return 0;
}
*pversion = n;
return 0;
}
static int
cb_cron(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
struct micronent *entry = varptr;
int rc;
char *endp;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
rc = micron_parse(value->v.string, &endp, entry);
if (rc) {
grecs_error(&value->locus, 0, "%s near %s",
micron_strerror(rc), endp);
return 0;
}
if (endp[strcspn(endp, " \t")])
grecs_error(&value->locus, 0, "garbage after cron specification");
return 0;
}
static int
cb_daemon_mode(enum grecs_callback_command cmd, grecs_node_t *node,
void *varptr, void *cb_data)
{
grecs_locus_t *locus = &node->locus;
grecs_value_t *value = node->v.value;
int t;
if (wy_assert_string_arg(locus, cmd, value))
return 1;
if (grecs_string_convert(&t, grecs_type_bool, value->v.string, locus))
return 1;
if (t)
wy_mode = WY_MODE_DAEMON;
return 0;
}
static struct grecs_keyword wydawca_kw[] = {
{ "daemon", NULL,
N_("Enable daemon mode"),
grecs_type_bool, GRECS_DFLT, NULL, 0, cb_daemon_mode },
{ "foreground", NULL,
N_("Start in foreground even in daemon mode"),
grecs_type_bool, GRECS_DFLT, &foreground },
{ "pidfile", N_("file"),
N_("Set pid file name"),
grecs_type_string, GRECS_CONST, &pidfile},
{ "module-prepend-load-path", N_("path"),
N_("List of directories searched for modules prior to "
"the default module directory"),
grecs_type_string, GRECS_LIST | GRECS_AGGR,
&module_prepend_load_path, 0, cb_load_path },
{ "module-load-path", N_("path"),
N_("List of directories searched for database modules."),
grecs_type_string, GRECS_LIST | GRECS_AGGR,
&module_load_path, 0, cb_load_path },
{ "module", N_("name: string> s_port;
for (i = 0; i < dictionary_count; i++)
default_dictionary[i] = dictionary_new(i, dictionary_builtin);
}
static int
create_spool_dir(struct spool *spool, char const *dir,
struct directory_metadata *meta, char const *descr)
{
struct stat st;
int rc;
if ((rc = stat(dir, &st)) != 0) {
if (errno != ENOENT) {
grecs_error(NULL, errno, _("%s: cannot stat %s %s"),
spool->tag, descr, dir);
return 1;
} else if (!create_directories) {
grecs_error(NULL, 0,
_("%s: %s %s does not exist"),
spool->tag, descr, dir);
grecs_error(NULL, 0,
"%s",
_("use \"create-directories yes\" to create it"));
return 1;
} else {
wy_debug(1, (_("%s: creating %s"), spool->tag, descr));
if (wy_dry_run)
return 0;
if (create_hierarchy(AT_FDCWD, dir)) {
return 1;
}
}
} else if (!S_ISDIR(st.st_mode)) {
grecs_error(NULL, errno, _("%s: %s is not a directory"),
spool->tag, dir);
return 1;
}
if (wy_dry_run)
return 0;
if ((meta->flags & METADATA_OWNER)
&& (rc || st.st_uid != meta->uid || st.st_gid != meta->gid)
&& chown(dir, meta->uid, meta->gid)) {
if (errno == EPERM && getuid() != 0) {
wy_log(LOG_WARNING,
_("%s: can't chown %s %s; not running as root"),
spool->tag, descr, dir);
} else {
grecs_error(NULL, errno, _("%s: can't chown %s %s"),
spool->tag, descr, dir);
return 1;
}
}
if ((meta->flags & METADATA_MODE)
&& (rc || (st.st_mode & 07777) != meta->mode)
&& chmod(dir, meta->mode)) {
if (errno == EPERM && getuid() != 0) {
wy_log(LOG_WARNING,
_("%s: can't chmod %s %s; not running as root"),
spool->tag, descr, dir);
} else {
grecs_error(NULL, errno, _("%s: can't chmod %s %s"),
spool->tag, descr, dir);
return 1;
}
}
return 0;
}
static struct directory_metadata *
effective_metadata(struct directory_metadata *storage,
struct directory_metadata const *meta)
{
if (create_directories) {
if (global_directory_metadata.flags) {
*storage = global_directory_metadata;
} else {
storage->mode = 0755;
storage->uid = wydawca_uid;
storage->gid = wydawca_gid;
storage->flags = METADATA_MODE | METADATA_OWNER;
}
if (meta->flags & METADATA_MODE) {
storage->mode = meta->mode;
storage->flags |= METADATA_MODE;
}
if (meta->flags & METADATA_OWNER) {
storage->uid = meta->uid;
storage->gid = meta->gid;
storage->flags |= METADATA_OWNER;
}
} else {
storage->flags = 0;
}
return storage;
}
static int
create_spool_dirs(struct spool *spool, void *data)
{
struct directory_metadata dm;
if (create_spool_dir(spool, spool->source_dir,
effective_metadata(&dm, &spool->source_metadata),
_("source directory")))
*(int *) data = 1;
else {
struct stat st;
struct spool *prev;
spool->source_fd = open(spool->source_dir, O_SEARCH);
if (spool->source_fd == -1) {
grecs_error(NULL, errno,
_("can't open spool source directory %s"),
spool->source_dir);
*(int *) data = 1;
return 0;
}
if (fstat(spool->source_fd, &st)) {
grecs_error(NULL, errno,
_("can't stat spool source directory %s"),
spool->source_dir);
return 0;
}
if ((prev = wydawca_find_spool_source(st.st_ino, st.st_dev)) != NULL) {
grecs_error(&spool->locus, 0,
_("cannot define spool %s: source directory %s already in use by spool %s"),
spool->tag, spool->source_dir, prev->tag);
grecs_error(&prev->locus, 0,
_("spool %s defined here"),
prev->tag);
*(int *) data = 1;
return 0;
}
spool->inode = st.st_ino;
spool->dev = st.st_dev;
}
if (wy_url_is_local(spool->dest_url)
&& create_spool_dir(spool, spool->dest_dir,
effective_metadata(&dm, &spool->dest_metadata),
_("destination directory")))
*(int *) data = 1;
switch (spool->archive.type) {
case archive_none:
break;
case archive_tar:{
char *dir;
split_filename(spool->archive.name, &dir);
if (create_spool_dir(spool, dir,
effective_metadata(&dm,
&spool->archive.
metadata),
_("archive directory")))
*(int *) data = 1;
free(dir);
}
break;
case archive_directory:
if (create_spool_dir(spool, spool->archive.name,
effective_metadata(&dm,
&spool->archive.metadata),
_("archive directory")))
*(int *) data = 1;
break;
}
return 0;
}
void
config_finish(struct grecs_node *tree)
{
struct grecs_node *p;
int err;
micron_parse("@hourly", NULL, &stat_report_schedule);
if (grecs_tree_process(tree, wydawca_kw))
exit(EX_CONFIG);
for (p = tree->down; p; p = p->next) {
if (strcmp(p->ident, "module-init") == 0) {
if (wy_assert_string_arg(&p->v.value->locus,
grecs_callback_set_value, p->v.value))
continue;
if (module_set_init(p->v.value->v.string, p))
grecs_error(&p->v.value->locus, 0, _("unknown module"));
}
}
err = 0;
if (for_each_spool(create_spool_dirs, &err) || err)
exit(EX_CONFIG);
if (file_sweep_time <= 0) {
file_sweep_time = DEFAULT_FILE_SWEEP_TIME;
wy_log(LOG_NOTICE,
_("%s too low; reverting to the default %lus"),
"file-sweep-time", (unsigned long)file_sweep_time);
}
}