Index: etc/newsyslog.ucl =================================================================== --- /dev/null +++ etc/newsyslog.ucl @@ -0,0 +1,243 @@ +#fucl0.1 +all { + # /var/log/all.log 600 7 * @T00 J + file = /var/log/all.log + mode = 600 + count = 7 + size = * + when = @T00 + compress = bzip2 +} +amd { + # /var/log/amd.log 644 7 100 * J + file = /var/log/amd.log + mode = 644 + count = 7 + size = 100kb + when = * + compress = bzip2 +} +auth { + # /var/log/auth.log 600 7 100 @0101T JC + file = /var/log/auth.log + mode = 600 + count = 7 + size = 100kb + when = @0101T + compress = bzip2 + flags = [ + create, + ] +} +console { + # /var/log/console.log 600 5 100 * J + file = /var/log/console.log + mode = 600 + count = 5 + size = 100kb + when = * + compress = bzip2 +} +cron { + # /var/log/cron 600 3 100 * JC + file = /var/log/cron + mode = 600 + count = 3 + size = 100kb + when = * + compress = bzip2 + flags = [ + create, + ] +} +daily { + # /var/log/daily.log 640 7 * @T00 JN + file = /var/log/daily.log + mode = 640 + count = 7 + size = * + when = @T00 + compress = bzip2 + flags = [ + nosignal, + ] +} +debug { + # /var/log/debug.log 600 7 100 * JC + file = /var/log/debug.log + mode = 600 + count = 7 + size = 100kb + when = * + compress = bzip2 + flags = [ + create, + ] +} +init { + # /var/log/init.log 644 3 100 * J + file = /var/log/init.log + mode = 644 + count = 3 + size = 100kb + when = * + compress = bzip2 +} +kerberos { + # /var/log/kerberos.log 600 7 100 * J + file = /var/log/kerberos.log + mode = 600 + count = 7 + size = 100kb + when = * + compress = bzip2 +} +lpd { + # /var/log/lpd-errs 644 7 100 * JC + file = /var/log/lpd-errs + mode = 644 + count = 7 + size = 100kb + when = * + compress = bzip2 + flags = [ + create, + ] +} +maillog { + # /var/log/maillog 640 7 * @T00 JC + file = /var/log/maillog + mode = 640 + count = 7 + size = * + when = @T00 + compress = bzip2 + flags = [ + create, + ] +} + +messages { + # /var/log/messages 644 5 100 @0101T JC + file = "/var/log/messages" + mode = 644 + count = 5 + size = 100kb + when = @0101T + compress = bzip2 + flags = [ + create, + ] +} +monthly { + # /var/log/monthly.log 640 12 * $M1D0 JN + file = /var/log/monthly.log + mode = 640 + count = 12 + size = * + when = $M1D0 + compress = bzip2 + flags = [ + nosignal, + ] +} +pflog { + # /var/log/pflog 600 3 100 * JB /var/run/pflogd.pid + file = /var/log/pflog + mode = 600 + count = 3 + size = 100kb + when = * + compress = bzip2 + flags = [ + binary, + ] + pidfile = /var/run/pflogd.pid +} +ppp { + # /var/log/ppp.log root:network 640 3 100 * JC + file = /var/log/ppp.log + user = root + group = network + mode = 640 + count = 3 + size = 100kb + when = * + compress = bzip2 + flags = [ + create, + ] +} +devd { + # /var/log/devd.log 644 3 100 * JC + file = /var/log/devd.log + mode = 644 + count = 3 + size = 100kb + when = * + compress = bzip2 + flags = [ + create, + ] +} +security { + # /var/log/security 600 10 100 * JC + file = /var/log/security + mode = 600 + count = 10 + size = 100kb + when = * + compress = bzip2 + flags = [ + create, + ] +} +sendmail { + # /var/log/sendmail.st 640 10 * 168 BN + file = /var/log/sendmail.st + mode = 640 + count = 10 + size = * + when = 168 + compress = none + flags = [ + binary, + nosignal, + ] +} +utx { + # /var/log/utx.log 644 3 * @01T05 B + file = /var/log/utx.log + mode = 644 + count = 3 + size = * + when = @01T05 + compress = none + flags = [ + binary, + ] +} +weekly { + # /var/log/weekly.log 640 5 * $W6D0 JN + file = /var/log/weekly.log + mode = 640 + count = 5 + size = * + when = $W6D0 + compress = bzip2 + flags = [ + nosignal, + ] +} +xfer { + # /var/log/xferlog 600 7 100 * JC + file = /var/log/xferlog + mode = 600 + count = 7 + size = 100kb + when = * + compress = bzip2 + flags = [ + create, + ] +} Index: usr.sbin/newsyslog/Makefile =================================================================== --- usr.sbin/newsyslog/Makefile +++ usr.sbin/newsyslog/Makefile @@ -5,6 +5,10 @@ PROG= newsyslog MAN= newsyslog.8 newsyslog.conf.5 SRCS= newsyslog.c ptimes.c +#CFLAGS+=-I${.CURDIR}/../../contrib/libucl/include +#.PATH: ${.CURDIR}/../../contrib/libucl/include +CFLAGS+=-I/usr/local/include -L/usr/local/lib +LIBADD= ucl .if ${MK_TESTS} != "no" SUBDIR+= tests Index: usr.sbin/newsyslog/newsyslog.c =================================================================== --- usr.sbin/newsyslog/newsyslog.c +++ usr.sbin/newsyslog/newsyslog.c @@ -2,6 +2,7 @@ * ------+---------+---------+-------- + --------+---------+---------+---------* * This file includes significant modifications done by: * Copyright (c) 2003, 2004 - Garance Alistair Drosehn . + * Copyright (c) 2014, 2015 Allan Jude . * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -80,6 +81,7 @@ #include #include #include +#include #include #include "pathnames.h" @@ -205,6 +207,21 @@ FREE_ENT, KEEP_ENT } fk_entry; +/** + * Config parser helper callback function pointer alias + */ +typedef bool (*conf_helper_func)(const ucl_object_t *, const ucl_object_t *, \ + void *); + +/** + * Config parser helper callback map. Maps UCL keys to callback functions that + * parse the key and store the value in the correct struct member + */ +typedef struct conf_helper_callback_map { + const char *conf_key; + conf_helper_func callback; +} conf_helper_map; + STAILQ_HEAD(cflist, conf_entry); static SLIST_HEAD(swlisthead, sigwork_entry) swhead = SLIST_HEAD_INITIALIZER(swhead); @@ -248,6 +265,20 @@ static struct cflist *get_worklist(char **files); static void parse_file(FILE *cf, struct cflist *work_p, struct cflist *glob_p, struct conf_entry *defconf_p, struct ilist *inclist); +static void parse_ucl(FILE *cf, struct cflist *work_p, struct cflist *glob_p, + struct conf_entry *defconf_p); +static bool conf_set_uid(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); +static bool conf_set_gid(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); +static bool conf_set_mode(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); +static bool conf_set_count(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); +static bool conf_set_size(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); +static bool conf_set_compress(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); +static bool conf_set_flags(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); +static bool conf_set_when(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); +static bool conf_set_command(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); +static bool conf_set_signal(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); +static bool conf_set_pidfile(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); +static bool conf_set_unknown(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target); static void add_to_queue(const char *fname, struct ilist *inclist); static char *sob(char *p); static char *son(char *p); @@ -280,6 +311,7 @@ static void savelog(char *from, char *to); static void createdir(const struct conf_entry *ent, char *dirpart); static void createlog(const struct conf_entry *ent); +static bool conf_helper_mapper (const ucl_object_t *obj, conf_helper_map *map, void *target); /* * All the following take a parameter of 'int', but expect values in the @@ -1041,16 +1073,33 @@ parse_file(FILE *cf, struct cflist *work_p, struct cflist *glob_p, struct conf_entry *defconf_p, struct ilist *inclist) { - char line[BUFSIZ], *parse, *q; + char line[BUFSIZ], *lineptr, *parse, *q; char *cp, *errline, *group; struct conf_entry *working; struct passwd *pwd; struct group *grp; glob_t pglob; int eol, ptm_opts, res, special; + double uclver = 0; size_t i; errline = NULL; + fgets(line, BUFSIZ, cf); + if (strncmp(line, "#fucl", 5) == 0) { + /* The config file is a FreeBSD UCL */ + /* Determine its schema version */ + lineptr = line; + uclver = strtod(lineptr + 5, NULL); + if (verbose) printf("\tUCL Version is %g\n", uclver); + if (uclver > 0.1) { + errx(1, "UCL Version too new\n"); + } else if (uclver == 0.1) { + parse_ucl(cf, work_p, glob_p, defconf_p); + } + return; + } else { + rewind(cf); + } while (fgets(line, BUFSIZ, cf)) { if ((line[0] == '\n') || (line[0] == '#') || (strlen(line) == 0)) @@ -1417,6 +1466,438 @@ free(errline); } +/* + * Parse a UCL configuration file and update a linked list of all the logs to + * process. + */ +static void +parse_ucl(FILE *cf, struct cflist *work_p, struct cflist *glob_p, + struct conf_entry *defconf_p) +{ + struct ucl_parser *parser = NULL; + ucl_object_t *conf_root = NULL; + ucl_object_iter_t it_conf = NULL; + const ucl_object_t *obj_file = NULL, *obj_key = NULL; + const char *cf_key = NULL, *cf_filename; + struct conf_entry *working; + bool success = false; + conf_helper_map map[] = + { + { "file", NULL }, /* The file is set when the conf_entry is created */ + { "user", (conf_helper_func) conf_set_uid }, + { "group", (conf_helper_func) conf_set_gid }, + { "mode", (conf_helper_func) conf_set_mode }, + { "count", (conf_helper_func) conf_set_count }, + { "size", (conf_helper_func) conf_set_size }, + { "compress", (conf_helper_func) conf_set_compress }, + { "flags", (conf_helper_func) conf_set_flags }, + { "when", (conf_helper_func) conf_set_when }, + { "command", (conf_helper_func) conf_set_command }, + { "signal", (conf_helper_func) conf_set_signal }, + { "pidfile", (conf_helper_func) conf_set_pidfile }, + { NULL, (conf_helper_func) conf_set_unknown } + }; + + parser = ucl_parser_new(UCL_PARSER_KEY_LOWERCASE); + ucl_parser_add_fd(parser, fileno(cf)); + if (ucl_parser_get_error(parser)) { + errx(1, "UCL Error occured: %s\n", + ucl_parser_get_error(parser)); + } + conf_root = ucl_parser_get_object(parser); + if (ucl_parser_get_error (parser)) { + errx(1, "Error: Parse Error occured: %s\n", + ucl_parser_get_error(parser)); + } + + it_conf = ucl_object_iterate_new(conf_root); + while ((obj_file = ucl_object_iterate_safe (it_conf, true)) != NULL) { + cf_key = ucl_object_key(obj_file); + if (strcasecmp(cf_key, "default") == 0) { + /* XXX: TODO: Need special handling of the 'default' entry */ + continue; + } + if (verbose > 2) { + printf("File: %s\n", cf_key); + } + + /* Find the filename key inside this object */ + obj_key = ucl_object_find_key(obj_file, "file"); + if (obj_key == NULL) { + if (verbose > 2) { + printf("Error: Unable to find filename in key: " + "%s\n", cf_key); + } + continue; + } + cf_filename = ucl_object_tostring(obj_key); + + working = init_entry(cf_filename, defconf_p); + working->flags = 0; + working->compress = COMPRESS_NONE; + working->pid_cmd_file = NULL; + working->sig = SIGHUP; + + success = conf_helper_mapper(obj_file, map, working); + if (success == false) { + warnx("Config parsing issue in section %s (%s)\n", + cf_key, cf_filename); + free_entry(working); + continue; + } + + /* + * Finish figuring out what pid-file to use (if any) in + * later processing if this logfile needs to be rotated. + */ + if ((working->flags & CE_NOSIGNAL) == CE_NOSIGNAL) { + /* + * This config-entry specified 'n' for nosignal, + * see if it also specified an explicit pid_cmd_file. + * This would be a pretty pointless combination. + */ + if (working->pid_cmd_file != NULL) { + warnx("Ignoring '%s' because flag 'n' was specified in section '%s'\n", + working->pid_cmd_file, cf_key); + free(working->pid_cmd_file); + working->pid_cmd_file = NULL; + } + } else if (working->pid_cmd_file == NULL) { + /* + * This entry did not specify the 'n' flag, which + * means it should signal syslogd unless it had + * specified some other pid-file (and obviously the + * syslog pid-file will not be for a process-group). + * Also, we should only try to notify syslog if we + * are root. + */ + if (working->flags & CE_SIGNALGROUP) { + warnx("Ignoring flag 'U' in section '%s'\n", + cf_key); + working->flags &= ~CE_SIGNALGROUP; + } + if (needroot) + working->pid_cmd_file = strdup(path_syslogpid); + } + /* + * Add this entry to the appropriate list of entries, unless + * it was some kind of special entry (eg: ). + */ + if (working->flags & CE_GLOB) { + STAILQ_INSERT_TAIL(glob_p, working, cf_nextp); + } else { + STAILQ_INSERT_TAIL(work_p, working, cf_nextp); + } + } + ucl_object_iterate_free (it_conf); + + if (parser != NULL) { + ucl_parser_free(parser); + } + if (conf_root != NULL) { + ucl_object_unref(conf_root); + } +} + +bool +conf_set_uid(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + struct passwd *pwd; + uid_t result; + const char *val = NULL; + + if (ucl_object_type(obj) == UCL_INT) { + result = ucl_object_toint(obj); + target->uid = result; + return true; + } else if (ucl_object_type(obj) == UCL_STRING) { + val = ucl_object_tostring(obj); + if (isnumberstr(val)) { + result = atoi(val); + target->uid = result; + return true; + } + if ((pwd = getpwnam(val)) == NULL) { + /* XXX: TODO: need to get the parent object in order to + * make a useful error message + */ + errx(1, + "error in config file; unknown user: '%s' in section '%s'\n", + val, ucl_object_key(top)); + } + result = pwd->pw_uid; + target->uid = result; + return true; + } + warnx("error in config file; invalid value for user in section '%s'\n", + ucl_object_key(obj)); + return false; +} + +bool +conf_set_gid(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + struct group *grp; + gid_t result; + const char *val = NULL; + + val = ucl_object_tostring_forced(obj); + if (!(isnumberstr(val))) { + if ((grp = getgrnam(val)) == NULL) { + errx(1, + "error in config file; unknown group '%s' in section '%s'\n", + val, ucl_object_key(top)); + } + result = grp->gr_gid; + } else { + result = atoi(val); + } + target->gid = result; + return true; +} + +bool +conf_set_mode(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + const char *val = NULL; + + val = ucl_object_tostring_forced(obj); + if (!sscanf(val, "%o", &target->permissions)) { + errx(1, "error in config file; bad permissions '%s' in section '%s'\n", + val, ucl_object_key(top)); + return false; + } + return true; +} + +bool +conf_set_count(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + const char *val = NULL; + + val = ucl_object_tostring_forced(obj); + if (!sscanf(val, "%d", &target->numlogs) || target->numlogs < 0) { + errx(1, "error in config file; bad value '%s' for count of logs to save in section '%s'\n", + val, ucl_object_key(top)); + } + return true; +} + +bool +conf_set_size(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + const char *val = NULL; + int log_size; + + val = ucl_object_tostring_forced(obj); + if (isdigitch(*val)) { + log_size = ucl_object_toint(obj); + target->trsize = kbytes(log_size); + } + else if (strcmp(val, "*") == 0) { + target->trsize = -1; + } + else { + warnx("Invalid value of '%s' for 'size' in section '%s'\n", + val, ucl_object_key(top)); + target->trsize = -1; + return false; + } + return true; +} + +bool +conf_set_compress(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + const char *val = NULL; + + val = ucl_object_tostring_forced(obj); + if (strcasecmp(val, "j") == 0 || strncasecmp(val, "bzip", 4) == 0) { + target->compress = COMPRESS_BZIP2; + } else if (strcasecmp(val, "x") == 0 || strncasecmp(val, "xz", 2) == 0) { + target->compress = COMPRESS_XZ; + } else if (strcasecmp(val, "z") == 0 || strcasecmp(val, "gzip") == 0) { + target->compress = COMPRESS_GZIP; + } else if (strlen(val) == 0 || strcasecmp(val, "none") == 0) { + target->compress = COMPRESS_NONE; + } else { + warnx("Invalid flag '%s' in section '%s'\n", + val, ucl_object_key(top)); + return false; + } + return true; +} + +bool +conf_set_flags(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + const ucl_object_t *obj_flag = NULL; + ucl_object_iter_t it_flag = NULL; + const char *val = NULL; + + it_flag = ucl_object_iterate_new(obj); + while ((obj_flag = ucl_object_iterate_safe (it_flag, true)) != NULL) { + val = ucl_object_tostring(obj_flag); + if (verbose > 2) { + printf("\t\tFound Flag: %s\n", val); + } + if (val == NULL) { + warnx("Invalid flag in section '%s'\n", + ucl_object_key(top)); + continue; + } + if (strcasecmp(val, "b") == 0 || strcasecmp(val, "binary") == 0) { + target->flags |= CE_BINARY; + } else if (strcasecmp(val, "c") == 0 || strcasecmp(val, "create") == 0) { + target->flags |= CE_CREATE; + } else if (strcasecmp(val, "d") == 0 || strcasecmp(val, "nodump") == 0) { + target->flags |= CE_NODUMP; + } else if (strcasecmp(val, "g") == 0 || strcasecmp(val, "glob") == 0) { + target->flags |= CE_GLOB; + } else if (strcasecmp(val, "j") == 0 || strncasecmp(val, "bzip", 4) == 0) { + target->compress = COMPRESS_BZIP2; + } else if (strcasecmp(val, "n") == 0 || strcasecmp(val, "nosignal") == 0) { + target->flags |= CE_NOSIGNAL; + } else if (strcasecmp(val, "r") == 0 || strcasecmp(val, "command") == 0) { + target->flags |= CE_PID2CMD; + } else if (strcasecmp(val, "u") == 0 || strcasecmp(val, "signalgroup") == 0) { + target->flags |= CE_SIGNALGROUP; + } else if (strcasecmp(val, "w") == 0) { + /* Depreciated flag - keep for compatibility purposes */ + } else if (strcasecmp(val, "x") == 0 || strncasecmp(val, "xz", 2) == 0) { + target->compress = COMPRESS_XZ; + } else if (strcasecmp(val, "z") == 0 || strcasecmp(val, "gzip") == 0) { + target->compress = COMPRESS_GZIP; + } else { + /** + * f: Used by OpenBSD for "CE_FOLLOW" + * m: Used by OpenBSD for "CE_MONITOR" + * p: Used by NetBSD for "CE_PLAIN0" + */ + errx(1, "illegal flag '%s' in section '%s'\n", + val, ucl_object_key(top)); + } + } + ucl_object_iterate_free(it_flag); + if (verbose > 3) { + printf("\t\tFinal Flags: %i\n", target->flags); + } + return true; +} + +bool +conf_set_when(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + int ptm_opts, res; + char *ep; + u_long ul; + const char *val = NULL; + + val = ucl_object_tostring_forced(obj); + ul = strtoul(val, &ep, 10); + if (ep == val) { + target->hours = 0; + } + else if (*ep == '*') { + target->hours = -1; + } + else if (ul > INT_MAX) { + errx(1, "interval '%ld' is too large in section '%s'\n", + ul, ucl_object_key(top)); + } + else { + target->hours = ul; + } + + if (*ep == '\0' || strcmp(ep, "*") == 0) { + return true; + } + if (*ep != '@' && *ep != '$') { + errx(1, "malformed interval '%s' in section '%s'\n", + val, ucl_object_key(top)); + } + + target->flags |= CE_TRIMAT; + target->trim_at = ptime_init(NULL); + ptm_opts = PTM_PARSE_ISO8601; + if (*ep == '$') { + ptm_opts = PTM_PARSE_DWM; + } + ptm_opts |= PTM_PARSE_MATCHDOM; + res = ptime_relparse(target->trim_at, ptm_opts, + ptimeget_secs(timenow), ep + 1); + if (res == -2) { + errx(1, "nonexistent time for 'at' value '%s' in section '%s'\n", + val, ucl_object_key(top)); + } + else if (res < 0) { + errx(1, "malformed 'at' value '%s' in section '%s'\n", + val, ucl_object_key(top)); + } + return true; +} + +bool +conf_set_command(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + const char *val = NULL; + + val = ucl_object_tostring(obj); + target->pid_cmd_file = strdup(val); + if (verbose > 3) { + printf("\t\tSetting command: '%s' in section %s\n", val, + ucl_object_key(top)); + } + return true; +} + +bool +conf_set_signal(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + const char *val = NULL; + + val = ucl_object_tostring(obj); + if (isdigit(*val)) { + target->sig = atoi(val); + } else { + /* XXX: TODO: Add signal name mapping */ + errx(1, + "illegal signal number '%s' in section '%s'\n", + val, ucl_object_key(top)); + } + if (target->sig < 1 || target->sig >= NSIG) { + errx(1, + "illegal signal number '%s' in section '%s'\n", + val, ucl_object_key(top)); + } + return true; +} + +bool +conf_set_pidfile(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + const char *val = NULL; + + val = ucl_object_tostring_forced(obj); + if (*val == '/') { + target->pid_cmd_file = strdup(val); + } else { + errx(1, "illegal pid file '%s' in section '%s'\n", + val, ucl_object_key(top)); + } + return true; +} + +bool +conf_set_unknown(const ucl_object_t *top, const ucl_object_t *obj, struct conf_entry *target) +{ + (void)target; /* silence clang warning */ + + warnx("Unknown key '%s' in section '%s'\n", + ucl_object_key(obj), ucl_object_key(top)); + return false; +} + static char * missing_field(char *p, char *errline) { @@ -2662,3 +3143,34 @@ warn("can't chflags %s NODUMP", fname); } } + +bool +conf_helper_mapper (const ucl_object_t *obj, conf_helper_map *map, void *target) +{ + ucl_object_iter_t it; + const ucl_object_t *cur; + const char *key = NULL; + int i; + bool ret = true, found = false; + + it = ucl_object_iterate_new(obj); + while ((cur = ucl_object_iterate_safe(it, true)) != NULL && ret) { + key = ucl_object_key(cur); + found = false; + for (i = 0; map[i].conf_key; i++) { + if (strcasecmp(map[i].conf_key, key) == 0) { + found = true; + if (map[i].callback != NULL) { + ret = map[i].callback(obj, cur, target); + } + break; + } + } + if (found == false && map[i].callback != NULL) { + /* Call default handler if there is one */ + ret = map[i].callback(obj, cur, target); + } + } + ucl_object_iterate_free(it); + return ret; +}