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