/* Copyright 2016 Alex 'AdUser' Z (ad_user@runbox.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as * published by the Free Software Foundation. */ #include #include #include #include #include #include #include #include #include "../strlcpy.h" #include "backend.h" #define MODNAME "exec" typedef struct cmd_t { struct cmd_t *next; char *orig; /**< stores original command, used in log messages */ char *args; /**< stores path of cmd & args, delimited by '\0' */ char **argv; /**< stores array of pointers to args + NULL */ size_t argc; /**< args count */ size_t pos_ip; /**< index+1 in argv[] where to insert IP address (zero means "no placeholder") */ size_t pos_id; /**< index+1 in argv[] where to insert IP address (zero means "no placeholder") */ } cmd_t; struct _config { char name[ID_MAX + 1]; void (*logcb)(enum loglevel lvl, const char *msg); time_t timeout; bool shared; cmd_t *start; cmd_t *stop; cmd_t *ban; cmd_t *unban; cmd_t *check; }; #include "backend.c" static cmd_t * cmd_from_str(const char *str) { cmd_t *cmd = NULL; const char *delim = " \t"; char *token, *saveptr, **argv; assert(str != NULL); if ((cmd = calloc(1, sizeof(cmd_t))) == NULL) return NULL; if ((cmd->orig = strdup(str)) == NULL) goto cleanup; if ((cmd->args = strdup(str)) == NULL) goto cleanup; cmd->argc = 1; if ((cmd->argv = calloc(2, sizeof(cmd->argv))) == NULL) goto cleanup; cmd->argv[cmd->argc] = NULL; strtok_r(cmd->args, delim, &saveptr); cmd->argv[0] = cmd->args; while ((token = strtok_r(NULL, delim, &saveptr)) != NULL) { if ((argv = realloc(cmd->argv, sizeof(cmd->argv) * (cmd->argc + 2))) == NULL) goto cleanup; cmd->argv = argv; if (strcmp(token, TOKEN_ID) == 0) cmd->pos_id = cmd->argc; if (strcmp(token, TOKEN_IP) == 0) cmd->pos_ip = cmd->argc; cmd->argv[cmd->argc] = token; cmd->argc++; } cmd->argv[cmd->argc] = NULL; return cmd; cleanup: free(cmd->orig); free(cmd->args); free(cmd->argv); free(cmd); return NULL; } static cmd_t * cmd_list_append(cmd_t *list, cmd_t *cmd) { cmd_t *c = NULL; assert(cmd != NULL); if (!list) return cmd; for (c = list; c->next != NULL; c = c->next); c->next = cmd; return list; } static void cmd_list_destroy(cmd_t *list) { cmd_t *next = NULL; for (; list != NULL; list = next) { next = list->next; free(list->orig); free(list->args); free(list->argv); free(list); } } static bool cmd_list_exec(cfg_t *cfg, cmd_t *list, const char *ip) { int status = 0; pid_t pid; for (cmd_t *cmd = list; cmd != NULL; cmd = cmd->next) { pid = fork(); if (pid == 0) { /* child */ if (cmd->pos_ip && ip) cmd->argv[cmd->pos_ip] = strdup(ip); if (cmd->pos_id) cmd->argv[cmd->pos_id] = strdup(cfg->name); if (cfg->timeout) alarm(cfg->timeout); execv(cmd->args, cmd->argv); } else if (pid > 0) { /* parent */ waitpid(pid, &status, 0); if (WIFEXITED(status) && WEXITSTATUS(status) == 0) continue; if (WIFEXITED(status)) { log_msg(cfg, error, "cmd '%s' terminated with code %d", cmd->orig, WEXITSTATUS(status)); } else if (WIFSIGNALED(status)) { log_msg(cfg, error, "cmd '%s' terminated by signal %d", cmd->orig, WTERMSIG(status)); } return false; } else { /* parent, fork error */ log_msg(cfg, error, "can't fork(): %s", strerror(errno)); return false; } } return true; } /* handlers */ cfg_t * create(const char *id) { cfg_t *cfg = NULL; assert(id != NULL); if ((cfg = calloc(1, sizeof(cfg_t))) == NULL) return NULL; strlcpy(cfg->name, id, sizeof(cfg->name)); cfg->logcb = &logcb_stub; return cfg; } #define CREATE_CMD(TYPE) \ if (strcmp(key, #TYPE) == 0) { \ cmd = cmd_from_str(value); \ if (cmd) { \ cfg->TYPE = cmd_list_append(cfg->TYPE, cmd); \ return true; \ } \ return false; \ } bool config(cfg_t *cfg, const char *key, const char *value) { cmd_t *cmd = NULL; assert(cfg != NULL); assert(key != NULL); assert(value != NULL); if (strcmp(key, "timeout") == 0) { cfg->timeout = atoi(value); return true; } if (strcmp(key, "shared") == 0) { cfg->shared = (strcmp(value, "yes") == 0) ? true : false; return true; } CREATE_CMD(start) CREATE_CMD(stop) CREATE_CMD(ban) CREATE_CMD(unban) CREATE_CMD(check) return false; } bool ready(cfg_t *cfg) { assert(cfg != NULL); if (cfg->ban && cfg->unban) return true; return false; } bool start(cfg_t *cfg) { assert(cfg != NULL); if (!cfg->start) return true; if (cfg->shared && usage_inc(cfg->name) > 1) return true; return cmd_list_exec(cfg, cfg->start, NULL); } bool stop(cfg_t *cfg) { assert(cfg != NULL); if (!cfg->stop) return true; if (cfg->shared && usage_dec(cfg->name) > 0) return true; return cmd_list_exec(cfg, cfg->stop, NULL); } bool ban(cfg_t *cfg, const char *ip) { assert(cfg != NULL && ip != NULL); return cmd_list_exec(cfg, cfg->ban, ip); } bool unban(cfg_t *cfg, const char *ip) { assert(cfg != NULL && ip != NULL); return cmd_list_exec(cfg, cfg->unban, ip); } bool check(cfg_t *cfg, const char *ip) { assert(cfg != NULL && ip != NULL); if (!cfg->check) return false; return cmd_list_exec(cfg, cfg->check, ip); } bool ping(cfg_t *cfg) { assert(cfg != NULL); (void)(cfg); /* suppress warning about unused variable */ return true; } void destroy(cfg_t *cfg) { assert(cfg != NULL); /* free commands */ cmd_list_destroy(cfg->start); cmd_list_destroy(cfg->stop); cmd_list_destroy(cfg->ban); cmd_list_destroy(cfg->unban); cmd_list_destroy(cfg->check); free(cfg); }