diff options
Diffstat (limited to '')
-rw-r--r-- | src/unix-manager.c | 1304 |
1 files changed, 1304 insertions, 0 deletions
diff --git a/src/unix-manager.c b/src/unix-manager.c new file mode 100644 index 0000000..9fb5bd7 --- /dev/null +++ b/src/unix-manager.c @@ -0,0 +1,1304 @@ +/* Copyright (C) 2013-2018 Open Information Security Foundation + * + * You can copy, redistribute or modify this Program under the terms of + * the GNU General Public License version 2 as published by the Free + * Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * version 2 along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + */ + +/** + * \file + * + * \author Eric Leblond <eric@regit.org> + */ + +#include "suricata-common.h" +#include "unix-manager.h" +#include "threads.h" +#include "detect-engine.h" +#include "tm-threads.h" +#include "runmodes.h" +#include "conf.h" +#include "runmode-unix-socket.h" + +#include "output-json-stats.h" + +#include "util-conf.h" +#include "util-privs.h" +#include "util-debug.h" +#include "util-device.h" +#include "util-ebpf.h" +#include "util-signal.h" +#include "util-buffer.h" +#include "util-path.h" +#include "util-profiling.h" + +#if (defined BUILD_UNIX_SOCKET) && (defined HAVE_SYS_UN_H) && (defined HAVE_SYS_STAT_H) && (defined HAVE_SYS_TYPES_H) +#include <sys/un.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "output.h" +#include "output-json.h" + +// MSG_NOSIGNAL does not exists on OS X +#ifdef OS_DARWIN +# ifndef MSG_NOSIGNAL +# define MSG_NOSIGNAL SO_NOSIGPIPE +# endif +#endif + +#define SOCKET_PATH LOCAL_STATE_DIR "/run/suricata/" +#define SOCKET_FILENAME "suricata-command.socket" +#define SOCKET_TARGET SOCKET_PATH SOCKET_FILENAME + +SCCtrlCondT unix_manager_ctrl_cond; +SCCtrlMutex unix_manager_ctrl_mutex; + +#define MAX_FAILED_RULES 20 + +typedef struct Command_ { + char *name; + TmEcode (*Func)(json_t *, json_t *, void *); + void *data; + int flags; + TAILQ_ENTRY(Command_) next; +} Command; + +typedef struct Task_ { + TmEcode (*Func)(void *); + void *data; + TAILQ_ENTRY(Task_) next; +} Task; + +#define CLIENT_BUFFER_SIZE 4096 +typedef struct UnixClient_ { + int fd; + MemBuffer *mbuf; /**< buffer for response construction */ + int version; + TAILQ_ENTRY(UnixClient_) next; +} UnixClient; + +typedef struct UnixCommand_ { + time_t start_timestamp; + int socket; + struct sockaddr_un client_addr; + int select_max; + TAILQ_HEAD(, Command_) commands; + TAILQ_HEAD(, Task_) tasks; + TAILQ_HEAD(, UnixClient_) clients; +} UnixCommand; + +/** + * \brief Create a command unix socket on system + * + * \retval 0 in case of error, 1 in case of success + */ +static int UnixNew(UnixCommand * this) +{ + struct sockaddr_un addr; + int len; + int ret; + int on = 1; + char sockettarget[PATH_MAX]; + const char *socketname; + + this->start_timestamp = time(NULL); + this->socket = -1; + this->select_max = 0; + + TAILQ_INIT(&this->commands); + TAILQ_INIT(&this->tasks); + TAILQ_INIT(&this->clients); + + int check_dir = 0; + if (ConfGet("unix-command.filename", &socketname) == 1) { + if (PathIsAbsolute(socketname)) { + strlcpy(sockettarget, socketname, sizeof(sockettarget)); + } else { + snprintf(sockettarget, sizeof(sockettarget), "%s/%s", + SOCKET_PATH, socketname); + check_dir = 1; + } + } else { + strlcpy(sockettarget, SOCKET_TARGET, sizeof(sockettarget)); + check_dir = 1; + } + SCLogInfo("unix socket '%s'", sockettarget); + + if (check_dir) { + struct stat stat_buf; + /* coverity[toctou] */ + if (stat(SOCKET_PATH, &stat_buf) != 0) { + /* coverity[toctou] */ + ret = SCMkDir(SOCKET_PATH, S_IRWXU|S_IXGRP|S_IRGRP); + if (ret != 0) { + int err = errno; + if (err != EEXIST) { + SCLogError( + "failed to create socket directory %s: %s", SOCKET_PATH, strerror(err)); + return 0; + } + } else { + SCLogInfo("created socket directory %s", SOCKET_PATH); + } + } + } + + /* Remove socket file */ + (void) unlink(sockettarget); + + /* set address */ + addr.sun_family = AF_UNIX; + strlcpy(addr.sun_path, sockettarget, sizeof(addr.sun_path)); + addr.sun_path[sizeof(addr.sun_path) - 1] = 0; + len = strlen(addr.sun_path) + sizeof(addr.sun_family) + 1; + + /* create socket */ + this->socket = socket(AF_UNIX, SOCK_STREAM, 0); + if (this->socket == -1) { + SCLogWarning( + "Unix Socket: unable to create UNIX socket %s: %s", addr.sun_path, strerror(errno)); + return 0; + } + this->select_max = this->socket + 1; + + /* set reuse option */ + ret = setsockopt(this->socket, SOL_SOCKET, SO_REUSEADDR, + (char *) &on, sizeof(on)); + if ( ret != 0 ) { + SCLogWarning("Cannot set sockets options: %s.", strerror(errno)); + } + + /* bind socket */ + ret = bind(this->socket, (struct sockaddr *) &addr, len); + if (ret == -1) { + SCLogWarning("Unix socket: UNIX socket bind(%s) error: %s", sockettarget, strerror(errno)); + return 0; + } + +#if !(defined OS_FREEBSD || defined __OpenBSD__) + /* Set file mode: will not fully work on most system, the group + * permission is not changed on some Linux. *BSD won't do the + * chmod: it returns EINVAL when calling chmod on sockets. */ + ret = chmod(sockettarget, S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP); + if (ret == -1) { + int err = errno; + SCLogWarning("Unable to change permission on socket: %s (%d)", strerror(err), err); + } +#endif + + /* listen */ + if (listen(this->socket, 1) == -1) { + SCLogWarning("Command server: UNIX socket listen() error: %s", strerror(errno)); + return 0; + } + return 1; +} + +static void UnixCommandSetMaxFD(UnixCommand *this) +{ + UnixClient *item; + + if (this == NULL) { + SCLogError("Unix command is NULL, warn devel"); + return; + } + + this->select_max = this->socket + 1; + TAILQ_FOREACH(item, &this->clients, next) { + if (item->fd >= this->select_max) { + this->select_max = item->fd + 1; + } + } +} + +static UnixClient *UnixClientAlloc(void) +{ + UnixClient *uclient = SCMalloc(sizeof(UnixClient)); + if (unlikely(uclient == NULL)) { + SCLogError("Can't allocate new client"); + return NULL; + } + uclient->mbuf = MemBufferCreateNew(CLIENT_BUFFER_SIZE); + if (uclient->mbuf == NULL) { + SCLogError("Can't allocate new client send buffer"); + SCFree(uclient); + return NULL; + } + return uclient; +} + +static void UnixClientFree(UnixClient *c) +{ + if (c != NULL) { + MemBufferFree(c->mbuf); + SCFree(c); + } +} + +/** + * \brief Close the unix socket + */ +static void UnixCommandClose(UnixCommand *this, int fd) +{ + UnixClient *item; + int found = 0; + + TAILQ_FOREACH(item, &this->clients, next) { + if (item->fd == fd) { + found = 1; + break; + } + } + + if (found == 0) { + SCLogError("No fd found in client list"); + return; + } + + TAILQ_REMOVE(&this->clients, item, next); + + close(item->fd); + UnixCommandSetMaxFD(this); + UnixClientFree(item); +} + +#define UNIX_PROTO_VERSION_LENGTH 200 +#define UNIX_PROTO_VERSION_V1 "0.1" +#define UNIX_PROTO_V1 1 +#define UNIX_PROTO_VERSION "0.2" +#define UNIX_PROTO_V2 2 + +static int UnixCommandSendJSONToClient(UnixClient *client, json_t *js) +{ + MemBufferReset(client->mbuf); + + OutputJSONMemBufferWrapper wrapper = { + .buffer = &client->mbuf, + .expand_by = CLIENT_BUFFER_SIZE + }; + + int r = json_dump_callback(js, OutputJSONMemBufferCallback, &wrapper, + JSON_PRESERVE_ORDER|JSON_COMPACT|JSON_ENSURE_ASCII| + JSON_ESCAPE_SLASH); + if (r != 0) { + SCLogWarning("unable to serialize JSON object"); + return -1; + } + + if (client->version > UNIX_PROTO_V1) { + if (MEMBUFFER_OFFSET(client->mbuf) + 1 >= MEMBUFFER_SIZE(client->mbuf)) { + MemBufferExpand(&client->mbuf, 1); + } + MemBufferWriteRaw(client->mbuf, "\n", 1); + } + + if (send(client->fd, (const char *)MEMBUFFER_BUFFER(client->mbuf), + MEMBUFFER_OFFSET(client->mbuf), MSG_NOSIGNAL) == -1) + { + SCLogWarning("unable to send block of size " + "%" PRIuMAX ": %s", + (uintmax_t)MEMBUFFER_OFFSET(client->mbuf), strerror(errno)); + return -1; + } + + SCLogDebug("sent message of size %"PRIuMAX" to client socket %d", + (uintmax_t)MEMBUFFER_OFFSET(client->mbuf), client->fd); + return 0; +} + +/** + * \brief Accept a new client on unix socket + * + * The function is called when a new user is detected + * in UnixMain(). It does the initial protocol negotiation + * with client. + * + * \retval 0 in case of error, 1 in case of success + */ +static int UnixCommandAccept(UnixCommand *this) +{ + char buffer[UNIX_PROTO_VERSION_LENGTH + 1]; + json_t *client_msg; + json_t *server_msg; + json_t *version; + json_error_t jerror; + int client; + int client_version; + int ret; + UnixClient *uclient = NULL; + + /* accept client socket */ + socklen_t len = sizeof(this->client_addr); + client = accept(this->socket, (struct sockaddr *) &this->client_addr, + &len); + if (client < 0) { + SCLogInfo("Unix socket: accept() error: %s", + strerror(errno)); + return 0; + } + SCLogDebug("Unix socket: client connection"); + + /* read client version */ + buffer[sizeof(buffer)-1] = 0; + ret = recv(client, buffer, sizeof(buffer)-1, 0); + if (ret < 0) { + SCLogInfo("Command server: client doesn't send version"); + close(client); + return 0; + } + if (ret >= (int)(sizeof(buffer)-1)) { + SCLogInfo("Command server: client message is too long, " + "disconnect him."); + close(client); + return 0; + } + buffer[ret] = 0; + + client_msg = json_loads(buffer, 0, &jerror); + if (client_msg == NULL) { + SCLogInfo("Invalid command, error on line %d: %s\n", jerror.line, jerror.text); + close(client); + return 0; + } + + version = json_object_get(client_msg, "version"); + if (!json_is_string(version)) { + SCLogInfo("error: version is not a string"); + close(client); + json_decref(client_msg); + return 0; + } + + /* check client version */ + if ((strcmp(json_string_value(version), UNIX_PROTO_VERSION) != 0) + && (strcmp(json_string_value(version), UNIX_PROTO_VERSION_V1) != 0)) { + SCLogInfo("Unix socket: invalid client version: \"%s\"", + json_string_value(version)); + json_decref(client_msg); + close(client); + return 0; + } else { + SCLogDebug("Unix socket: client version: \"%s\"", + json_string_value(version)); + if (strcmp(json_string_value(version), UNIX_PROTO_VERSION_V1) == 0) { + client_version = UNIX_PROTO_V1; + } else { + client_version = UNIX_PROTO_V2; + } + } + + json_decref(client_msg); + /* send answer */ + server_msg = json_object(); + if (server_msg == NULL) { + close(client); + return 0; + } + json_object_set_new(server_msg, "return", json_string("OK")); + + uclient = UnixClientAlloc(); + if (unlikely(uclient == NULL)) { + json_decref(server_msg); + close(client); + return 0; + } + uclient->fd = client; + uclient->version = client_version; + + if (UnixCommandSendJSONToClient(uclient, server_msg) != 0) { + SCLogWarning("Unable to send command"); + + UnixClientFree(uclient); + json_decref(server_msg); + close(client); + return 0; + } + + json_decref(server_msg); + + /* client connected */ + SCLogDebug("Unix socket: client connected"); + TAILQ_INSERT_TAIL(&this->clients, uclient, next); + UnixCommandSetMaxFD(this); + return 1; +} + +static int UnixCommandBackgroundTasks(UnixCommand* this) +{ + int ret = 1; + Task *ltask; + + TAILQ_FOREACH(ltask, &this->tasks, next) { + int fret = ltask->Func(ltask->data); + if (fret != TM_ECODE_OK) { + ret = 0; + } + } + return ret; +} + +/** + * \brief Command dispatcher + * + * \param this a UnixCommand:: structure + * \param command a string containing a json formatted + * command + * + * \retval 0 in case of error, 1 in case of success + */ +static int UnixCommandExecute(UnixCommand * this, char *command, UnixClient *client) +{ + int ret = 1; + json_error_t error; + json_t *jsoncmd = NULL; + json_t *cmd = NULL; + json_t *server_msg = json_object(); + const char * value; + int found = 0; + Command *lcmd; + + if (server_msg == NULL) { + return 0; + } + + jsoncmd = json_loads(command, 0, &error); + if (jsoncmd == NULL) { + SCLogInfo("Invalid command, error on line %d: %s\n", error.line, error.text); + goto error; + } + + cmd = json_object_get(jsoncmd, "command"); + if(!json_is_string(cmd)) { + SCLogInfo("error: command is not a string"); + goto error_cmd; + } + value = json_string_value(cmd); + + TAILQ_FOREACH(lcmd, &this->commands, next) { + if (!strcmp(value, lcmd->name)) { + int fret = TM_ECODE_OK; + found = 1; + if (lcmd->flags & UNIX_CMD_TAKE_ARGS) { + cmd = json_object_get(jsoncmd, "arguments"); + if(!json_is_object(cmd)) { + SCLogInfo("error: argument is not an object"); + goto error_cmd; + } + } + fret = lcmd->Func(cmd, server_msg, lcmd->data); + if (fret != TM_ECODE_OK) { + ret = 0; + } + break; + } + } + + if (found == 0) { + json_object_set_new(server_msg, "message", json_string("Unknown command")); + ret = 0; + } + + switch (ret) { + case 0: + json_object_set_new(server_msg, "return", json_string("NOK")); + break; + case 1: + json_object_set_new(server_msg, "return", json_string("OK")); + break; + } + + if (UnixCommandSendJSONToClient(client, server_msg) != 0) { + goto error; + } + + json_decref(jsoncmd); + json_decref(server_msg); + return ret; + +error_cmd: + json_decref(jsoncmd); +error: + json_decref(server_msg); + UnixCommandClose(this, client->fd); + return 0; +} + +static void UnixCommandRun(UnixCommand * this, UnixClient *client) +{ + char buffer[4096]; + int ret; + if (client->version <= UNIX_PROTO_V1) { + ret = recv(client->fd, buffer, sizeof(buffer) - 1, 0); + if (ret <= 0) { + if (ret == 0) { + SCLogDebug("Unix socket: lost connection with client"); + } else { + SCLogError("Unix socket: error on recv() from client: %s", strerror(errno)); + } + UnixCommandClose(this, client->fd); + return; + } + if (ret >= (int)(sizeof(buffer)-1)) { + SCLogError("Command server: client command is too long, " + "disconnect him."); + UnixCommandClose(this, client->fd); + } + buffer[ret] = 0; + } else { + int try = 0; + int offset = 0; + int cmd_over = 0; + ret = recv(client->fd, buffer + offset, sizeof(buffer) - offset - 1, 0); + do { + if (ret <= 0) { + if (ret == 0) { + SCLogDebug("Unix socket: lost connection with client"); + } else { + SCLogError("Unix socket: error on recv() from client: %s", strerror(errno)); + } + UnixCommandClose(this, client->fd); + return; + } + if (ret >= (int)(sizeof(buffer)- offset - 1)) { + SCLogInfo("Command server: client command is too long, " + "disconnect him."); + UnixCommandClose(this, client->fd); + } + if (buffer[ret - 1] == '\n') { + buffer[ret-1] = 0; + cmd_over = 1; + } else { + struct timeval tv; + fd_set select_set; + offset += ret; + do { + FD_ZERO(&select_set); + FD_SET(client->fd, &select_set); + tv.tv_sec = 0; + tv.tv_usec = 200 * 1000; + try++; + ret = select(client->fd, &select_set, NULL, NULL, &tv); + /* catch select() error */ + if (ret == -1) { + /* Signal was caught: just ignore it */ + if (errno != EINTR) { + SCLogInfo("Unix socket: lost connection with client"); + UnixCommandClose(this, client->fd); + return; + } + } + } while (ret == 0 && try < 3); + if (ret > 0) { + ret = recv(client->fd, buffer + offset, + sizeof(buffer) - offset - 1, 0); + } + } + } while (try < 3 && cmd_over == 0); + + if (try == 3 && cmd_over == 0) { + SCLogInfo("Unix socket: incomplete client message, closing connection"); + UnixCommandClose(this, client->fd); + return; + } + } + UnixCommandExecute(this, buffer, client); +} + +/** + * \brief Select function + * + * \retval 0 in case of error, 1 in case of success + */ +static int UnixMain(UnixCommand * this) +{ + struct timeval tv; + int ret; + fd_set select_set; + UnixClient *uclient; + UnixClient *tclient; + + if (suricata_ctl_flags & SURICATA_STOP) { + TAILQ_FOREACH_SAFE (uclient, &this->clients, next, tclient) { + UnixCommandClose(this, uclient->fd); + } + return 1; + } + + /* Wait activity on the socket */ + FD_ZERO(&select_set); + FD_SET(this->socket, &select_set); + TAILQ_FOREACH(uclient, &this->clients, next) { + FD_SET(uclient->fd, &select_set); + } + + tv.tv_sec = 0; + tv.tv_usec = 200 * 1000; + ret = select(this->select_max, &select_set, NULL, NULL, &tv); + + /* catch select() error */ + if (ret == -1) { + /* Signal was caught: just ignore it */ + if (errno == EINTR) { + return 1; + } + SCLogError("Command server: select() fatal error: %s", strerror(errno)); + return 0; + } + + /* timeout: continue */ + if (ret == 0) { + return 1; + } + + TAILQ_FOREACH_SAFE(uclient, &this->clients, next, tclient) { + if (FD_ISSET(uclient->fd, &select_set)) { + UnixCommandRun(this, uclient); + } + } + if (FD_ISSET(this->socket, &select_set)) { + if (!UnixCommandAccept(this)) + return 1; + } + + return 1; +} + +static TmEcode UnixManagerShutdownCommand(json_t *cmd, + json_t *server_msg, void *data) +{ + SCEnter(); + json_object_set_new(server_msg, "message", json_string("Closing Suricata")); + EngineStop(); + SCReturnInt(TM_ECODE_OK); +} + +static TmEcode UnixManagerVersionCommand(json_t *cmd, + json_t *server_msg, void *data) +{ + SCEnter(); + json_object_set_new(server_msg, "message", json_string(GetProgramVersion())); + SCReturnInt(TM_ECODE_OK); +} + +static TmEcode UnixManagerUptimeCommand(json_t *cmd, + json_t *server_msg, void *data) +{ + SCEnter(); + int uptime; + UnixCommand *ucmd = (UnixCommand *)data; + + uptime = time(NULL) - ucmd->start_timestamp; + json_object_set_new(server_msg, "message", json_integer(uptime)); + SCReturnInt(TM_ECODE_OK); +} + +static TmEcode UnixManagerRunningModeCommand(json_t *cmd, + json_t *server_msg, void *data) +{ + SCEnter(); + json_object_set_new(server_msg, "message", json_string(RunmodeGetActive())); + SCReturnInt(TM_ECODE_OK); +} + +static TmEcode UnixManagerCaptureModeCommand(json_t *cmd, + json_t *server_msg, void *data) +{ + SCEnter(); + json_object_set_new(server_msg, "message", json_string(RunModeGetMainMode())); + SCReturnInt(TM_ECODE_OK); +} + +static TmEcode UnixManagerReloadRulesWrapper(json_t *cmd, json_t *server_msg, void *data, int do_wait) +{ + SCEnter(); + + if (SuriHasSigFile()) { + json_object_set_new(server_msg, "message", + json_string("Live rule reload not possible if -s " + "or -S option used at runtime.")); + SCReturnInt(TM_ECODE_FAILED); + } + + int r = DetectEngineReloadStart(); + + if (r == 0 && do_wait) { + while (!DetectEngineReloadIsIdle()) + usleep(100); + } else { + if (r == -1) { + json_object_set_new(server_msg, "message", json_string("Reload already in progress")); + SCReturnInt(TM_ECODE_FAILED); + } + } + + json_object_set_new(server_msg, "message", json_string("done")); + SCReturnInt(TM_ECODE_OK); +} + +static TmEcode UnixManagerReloadRules(json_t *cmd, json_t *server_msg, void *data) +{ + return UnixManagerReloadRulesWrapper(cmd, server_msg, data, 1); +} + +static TmEcode UnixManagerNonBlockingReloadRules(json_t *cmd, json_t *server_msg, void *data) +{ + return UnixManagerReloadRulesWrapper(cmd, server_msg, data, 0); +} + +static TmEcode UnixManagerReloadTimeCommand(json_t *cmd, + json_t *server_msg, void *data) +{ + SCEnter(); + TmEcode retval; + json_t *jdata = NULL; + + retval = OutputEngineStatsReloadTime(&jdata); + json_object_set_new(server_msg, "message", jdata); + SCReturnInt(retval); +} + +static TmEcode UnixManagerRulesetStatsCommand(json_t *cmd, + json_t *server_msg, void *data) +{ + SCEnter(); + TmEcode retval; + json_t *jdata = NULL; + + retval = OutputEngineStatsRuleset(&jdata); + json_object_set_new(server_msg, "message", jdata); + SCReturnInt(retval); +} + +#ifdef PROFILE_RULES +static TmEcode UnixManagerRulesetProfileCommand(json_t *cmd, json_t *server_msg, void *data) +{ + SCEnter(); + DetectEngineCtx *de_ctx = DetectEngineGetCurrent(); + + json_t *js = SCProfileRuleTriggerDump(de_ctx); + if (js == NULL) { + json_object_set_new(server_msg, "message", json_string("NOK")); + SCReturnInt(TM_ECODE_FAILED); + } + json_object_set_new(server_msg, "message", js); + SCReturnInt(TM_ECODE_OK); +} + +static TmEcode UnixManagerRulesetProfileStartCommand(json_t *cmd, json_t *server_msg, void *data) +{ + SCEnter(); + SCProfileRuleStartCollection(); + json_object_set_new(server_msg, "message", json_string("OK")); + SCReturnInt(TM_ECODE_OK); +} + +static TmEcode UnixManagerRulesetProfileStopCommand(json_t *cmd, json_t *server_msg, void *data) +{ + SCEnter(); + SCProfileRuleStopCollection(); + json_object_set_new(server_msg, "message", json_string("OK")); + SCReturnInt(TM_ECODE_OK); +} +#endif + +static TmEcode UnixManagerShowFailedRules(json_t *cmd, + json_t *server_msg, void *data) +{ + SCEnter(); + int rules_cnt = 0; + DetectEngineCtx *de_ctx = DetectEngineGetCurrent(); + if (de_ctx == NULL) { + json_object_set_new(server_msg, "message", json_string("Unable to get info")); + SCReturnInt(TM_ECODE_OK); + } + + /* Since we need to deference de_ctx, we don't want to lost it. */ + DetectEngineCtx *list = de_ctx; + json_t *js_sigs_array = json_array(); + + if (js_sigs_array == NULL) { + json_object_set_new(server_msg, "message", json_string("Unable to get info")); + goto error; + } + while (list) { + SigString *sigs_str = NULL; + TAILQ_FOREACH(sigs_str, &list->sig_stat.failed_sigs, next) { + json_t *jdata = json_object(); + if (jdata == NULL) { + json_object_set_new(server_msg, "message", json_string("Unable to get the sig")); + goto error; + } + + json_object_set_new(jdata, "tenant_id", json_integer(list->tenant_id)); + json_object_set_new(jdata, "rule", json_string(sigs_str->sig_str)); + json_object_set_new(jdata, "filename", json_string(sigs_str->filename)); + json_object_set_new(jdata, "line", json_integer(sigs_str->line)); + if (sigs_str->sig_error) { + json_object_set_new(jdata, "error", json_string(sigs_str->sig_error)); + } + json_array_append_new(js_sigs_array, jdata); + if (++rules_cnt > MAX_FAILED_RULES) { + break; + } + } + if (rules_cnt > MAX_FAILED_RULES) { + break; + } + list = list->next; + } + + json_object_set_new(server_msg, "message", js_sigs_array); + DetectEngineDeReference(&de_ctx); + SCReturnInt(TM_ECODE_OK); + +error: + DetectEngineDeReference(&de_ctx); + json_object_clear(js_sigs_array); + json_decref(js_sigs_array); + SCReturnInt(TM_ECODE_FAILED); +} + +static TmEcode UnixManagerConfGetCommand(json_t *cmd, + json_t *server_msg, void *data) +{ + SCEnter(); + + const char *confval = NULL; + char *variable = NULL; + + json_t *jarg = json_object_get(cmd, "variable"); + if(!json_is_string(jarg)) { + SCLogInfo("error: variable is not a string"); + json_object_set_new(server_msg, "message", json_string("variable is not a string")); + SCReturnInt(TM_ECODE_FAILED); + } + + variable = (char *)json_string_value(jarg); + if (ConfGet(variable, &confval) != 1) { + json_object_set_new(server_msg, "message", json_string("Unable to get value")); + SCReturnInt(TM_ECODE_FAILED); + } + + if (confval) { + json_object_set_new(server_msg, "message", json_string(confval)); + SCReturnInt(TM_ECODE_OK); + } + + json_object_set_new(server_msg, "message", json_string("No string value")); + SCReturnInt(TM_ECODE_FAILED); +} + +static TmEcode UnixManagerListCommand(json_t *cmd, + json_t *answer, void *data) +{ + SCEnter(); + json_t *jdata; + json_t *jarray; + Command *lcmd = NULL; + UnixCommand *gcmd = (UnixCommand *) data; + int i = 0; + + jdata = json_object(); + if (jdata == NULL) { + json_object_set_new(answer, "message", + json_string("internal error at json object creation")); + return TM_ECODE_FAILED; + } + jarray = json_array(); + if (jarray == NULL) { + json_object_set_new(answer, "message", + json_string("internal error at json object creation")); + return TM_ECODE_FAILED; + } + + TAILQ_FOREACH(lcmd, &gcmd->commands, next) { + json_array_append_new(jarray, json_string(lcmd->name)); + i++; + } + + json_object_set_new(jdata, "count", json_integer(i)); + json_object_set_new(jdata, "commands", jarray); + json_object_set_new(answer, "message", jdata); + SCReturnInt(TM_ECODE_OK); +} + +static TmEcode UnixManagerReopenLogFiles(json_t *cmd, json_t *server_msg, void *data) +{ + OutputNotifyFileRotation(); + json_object_set_new(server_msg, "message", json_string("done")); + SCReturnInt(TM_ECODE_OK); +} + +#if 0 +TmEcode UnixManagerReloadRules(json_t *cmd, + json_t *server_msg, void *data) +{ + SCEnter(); + if (suricata_ctl_flags != 0) { + json_object_set_new(server_msg, "message", + json_string("Live rule swap no longer possible." + " Engine in shutdown mode.")); + SCReturn(TM_ECODE_FAILED); + } else { + /* FIXME : need to check option value */ + UtilSignalHandlerSetup(SIGUSR2, SignalHandlerSigusr2Idle); + DetectEngineSpawnLiveRuleSwapMgmtThread(); + json_object_set_new(server_msg, "message", json_string("Reloading rules")); + } + SCReturn(TM_ECODE_OK); +} +#endif + +static UnixCommand command; + +/** + * \brief Add a command to the list of commands + * + * This function adds a command to the list of commands available + * through the unix socket. + * + * When a command is received from user through the unix socket, the content + * of 'Command' field in the JSON message is match against keyword, then the + * Func is called. See UnixSocketAddPcapFile() for an example. + * + * \param keyword name of the command + * \param Func function to run when command is received + * \param data a pointer to data that are passed to Func when it is run + * \param flags a flag now used to tune the command type + * \retval TM_ECODE_OK in case of success, TM_ECODE_FAILED in case of failure + */ +TmEcode UnixManagerRegisterCommand(const char * keyword, + TmEcode (*Func)(json_t *, json_t *, void *), + void *data, int flags) +{ + SCEnter(); + Command *cmd = NULL; + Command *lcmd = NULL; + + if (Func == NULL) { + SCLogError("Null function"); + SCReturnInt(TM_ECODE_FAILED); + } + + if (keyword == NULL) { + SCLogError("Null keyword"); + SCReturnInt(TM_ECODE_FAILED); + } + + TAILQ_FOREACH(lcmd, &command.commands, next) { + if (!strcmp(keyword, lcmd->name)) { + SCLogError("%s already registered", keyword); + SCReturnInt(TM_ECODE_FAILED); + } + } + + cmd = SCMalloc(sizeof(Command)); + if (unlikely(cmd == NULL)) { + SCLogError("Can't alloc cmd"); + SCReturnInt(TM_ECODE_FAILED); + } + cmd->name = SCStrdup(keyword); + if (unlikely(cmd->name == NULL)) { + SCLogError("Can't alloc cmd name"); + SCFree(cmd); + SCReturnInt(TM_ECODE_FAILED); + } + cmd->Func = Func; + cmd->data = data; + cmd->flags = flags; + /* Add it to the list */ + TAILQ_INSERT_TAIL(&command.commands, cmd, next); + + SCReturnInt(TM_ECODE_OK); +} + +/** + * \brief Add a task to the list of tasks + * + * This function adds a task to run in the background. The task is run + * each time the UnixMain() function exits from select. + * + * \param Func function to run when a command is received + * \param data a pointer to data that are passed to Func when it is run + * \retval TM_ECODE_OK in case of success, TM_ECODE_FAILED in case of failure + */ +TmEcode UnixManagerRegisterBackgroundTask(TmEcode (*Func)(void *), + void *data) +{ + SCEnter(); + Task *task = NULL; + + if (Func == NULL) { + SCLogError("Null function"); + SCReturnInt(TM_ECODE_FAILED); + } + + task = SCMalloc(sizeof(Task)); + if (unlikely(task == NULL)) { + SCLogError("Can't alloc task"); + SCReturnInt(TM_ECODE_FAILED); + } + task->Func = Func; + task->data = data; + /* Add it to the list */ + TAILQ_INSERT_TAIL(&command.tasks, task, next); + + SCReturnInt(TM_ECODE_OK); +} + +int UnixManagerInit(void) +{ + if (UnixNew(&command) == 0) { + int failure_fatal = 0; + if (ConfGetBool("engine.init-failure-fatal", &failure_fatal) != 1) { + SCLogDebug("ConfGetBool could not load the value."); + } + if (failure_fatal) { + FatalError("Unable to create unix command socket"); + } else { + SCLogWarning("Unable to create unix command socket"); + return -1; + } + } + + /* Init Unix socket */ + UnixManagerRegisterCommand("shutdown", UnixManagerShutdownCommand, NULL, 0); + UnixManagerRegisterCommand("command-list", UnixManagerListCommand, &command, 0); + UnixManagerRegisterCommand("help", UnixManagerListCommand, &command, 0); + UnixManagerRegisterCommand("version", UnixManagerVersionCommand, &command, 0); + UnixManagerRegisterCommand("uptime", UnixManagerUptimeCommand, &command, 0); + UnixManagerRegisterCommand("running-mode", UnixManagerRunningModeCommand, &command, 0); + UnixManagerRegisterCommand("capture-mode", UnixManagerCaptureModeCommand, &command, 0); + UnixManagerRegisterCommand("conf-get", UnixManagerConfGetCommand, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("dump-counters", StatsOutputCounterSocket, NULL, 0); + UnixManagerRegisterCommand("reload-rules", UnixManagerReloadRules, NULL, 0); + UnixManagerRegisterCommand("ruleset-reload-rules", UnixManagerReloadRules, NULL, 0); + UnixManagerRegisterCommand("ruleset-reload-nonblocking", UnixManagerNonBlockingReloadRules, NULL, 0); + UnixManagerRegisterCommand("ruleset-reload-time", UnixManagerReloadTimeCommand, NULL, 0); + UnixManagerRegisterCommand("ruleset-stats", UnixManagerRulesetStatsCommand, NULL, 0); + UnixManagerRegisterCommand("ruleset-failed-rules", UnixManagerShowFailedRules, NULL, 0); +#ifdef PROFILE_RULES + UnixManagerRegisterCommand("ruleset-profile", UnixManagerRulesetProfileCommand, NULL, 0); + UnixManagerRegisterCommand( + "ruleset-profile-start", UnixManagerRulesetProfileStartCommand, NULL, 0); + UnixManagerRegisterCommand( + "ruleset-profile-stop", UnixManagerRulesetProfileStopCommand, NULL, 0); +#endif + UnixManagerRegisterCommand("register-tenant-handler", UnixSocketRegisterTenantHandler, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("unregister-tenant-handler", UnixSocketUnregisterTenantHandler, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("register-tenant", UnixSocketRegisterTenant, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("reload-tenant", UnixSocketReloadTenant, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("reload-tenants", UnixSocketReloadTenants, &command, 0); + UnixManagerRegisterCommand("unregister-tenant", UnixSocketUnregisterTenant, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("add-hostbit", UnixSocketHostbitAdd, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("remove-hostbit", UnixSocketHostbitRemove, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("list-hostbit", UnixSocketHostbitList, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("reopen-log-files", UnixManagerReopenLogFiles, NULL, 0); + UnixManagerRegisterCommand("memcap-set", UnixSocketSetMemcap, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("memcap-show", UnixSocketShowMemcap, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("memcap-list", UnixSocketShowAllMemcap, NULL, 0); + + UnixManagerRegisterCommand("dataset-add", UnixSocketDatasetAdd, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("dataset-remove", UnixSocketDatasetRemove, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand( + "get-flow-stats-by-id", UnixSocketGetFlowStatsById, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("dataset-dump", UnixSocketDatasetDump, NULL, 0); + UnixManagerRegisterCommand( + "dataset-clear", UnixSocketDatasetClear, &command, UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand( + "dataset-lookup", UnixSocketDatasetLookup, &command, UNIX_CMD_TAKE_ARGS); + + return 0; +} + +typedef struct UnixManagerThreadData_ { + int padding; +} UnixManagerThreadData; + +static TmEcode UnixManagerThreadInit(ThreadVars *t, const void *initdata, void **data) +{ + UnixManagerThreadData *utd = SCCalloc(1, sizeof(*utd)); + if (utd == NULL) + return TM_ECODE_FAILED; + + *data = utd; + return TM_ECODE_OK; +} + +static TmEcode UnixManagerThreadDeinit(ThreadVars *t, void *data) +{ + SCFree(data); + return TM_ECODE_OK; +} + +static TmEcode UnixManager(ThreadVars *th_v, void *thread_data) +{ + int ret; + + /* set the thread name */ + SCLogDebug("%s started...", th_v->name); + + StatsSetupPrivate(th_v); + + /* Set the threads capability */ + th_v->cap_flags = 0; + SCDropCaps(th_v); + + TmThreadsSetFlag(th_v, THV_INIT_DONE | THV_RUNNING); + + while (1) { + ret = UnixMain(&command); + if (ret == 0) { + SCLogError("Fatal error on unix socket"); + } + + if ((ret == 0) || (TmThreadsCheckFlag(th_v, THV_KILL))) { + UnixClient *item; + UnixClient *titem; + TAILQ_FOREACH_SAFE(item, &(&command)->clients, next, titem) { + close(item->fd); + SCFree(item); + } + StatsSyncCounters(th_v); + break; + } + + UnixCommandBackgroundTasks(&command); + } + return TM_ECODE_OK; +} + + +/** \brief Spawn the unix socket manager thread + * + * \param mode if set to 1, init failure cause suricata exit + * */ +void UnixManagerThreadSpawn(int mode) +{ + ThreadVars *tv_unixmgr = NULL; + + SCCtrlCondInit(&unix_manager_ctrl_cond, NULL); + SCCtrlMutexInit(&unix_manager_ctrl_mutex, NULL); + + tv_unixmgr = TmThreadCreateCmdThreadByName(thread_name_unix_socket, + "UnixManager", 0); + + if (tv_unixmgr == NULL) { + FatalError("TmThreadsCreate failed"); + } + if (TmThreadSpawn(tv_unixmgr) != TM_ECODE_OK) { + FatalError("TmThreadSpawn failed"); + } + if (mode == 1) { + if (TmThreadsCheckFlag(tv_unixmgr, THV_RUNNING_DONE)) { + FatalError("Unix socket init failed"); + } + } + return; +} + +// TODO can't think of a good name +void UnixManagerThreadSpawnNonRunmode(const bool unix_socket) +{ + /* Spawn the unix socket manager thread */ + if (unix_socket) { + if (UnixManagerInit() == 0) { + UnixManagerRegisterCommand("iface-stat", LiveDeviceIfaceStat, NULL, + UNIX_CMD_TAKE_ARGS); + UnixManagerRegisterCommand("iface-list", LiveDeviceIfaceList, NULL, 0); + UnixManagerRegisterCommand("iface-bypassed-stat", + LiveDeviceGetBypassedStats, NULL, 0); + /* For backward compatibility */ + UnixManagerRegisterCommand("ebpf-bypassed-stat", + LiveDeviceGetBypassedStats, NULL, 0); + UnixManagerThreadSpawn(0); + } + } +} + +/** + * \brief Used to kill unix manager thread(s). + * + * \todo Kinda hackish since it uses the tv name to identify unix manager + * thread. We need an all weather identification scheme. + */ +void UnixSocketKillSocketThread(void) +{ + ThreadVars *tv = NULL; + +again: + SCMutexLock(&tv_root_lock); + + /* unix manager thread(s) is/are a part of command threads */ + tv = tv_root[TVT_CMD]; + + while (tv != NULL) { + if (strcasecmp(tv->name, "UnixManagerThread") == 0) { + /* If the thread dies during init it will have + * THV_RUNNING_DONE set, so we can set the correct flag + * and exit. + */ + if (TmThreadsCheckFlag(tv, THV_RUNNING_DONE)) { + TmThreadsSetFlag(tv, THV_KILL); + TmThreadsSetFlag(tv, THV_DEINIT); + TmThreadsSetFlag(tv, THV_CLOSED); + break; + } + TmThreadsSetFlag(tv, THV_KILL); + TmThreadsSetFlag(tv, THV_DEINIT); + /* Be sure it has shut down */ + if (!TmThreadsCheckFlag(tv, THV_CLOSED)) { + SCMutexUnlock(&tv_root_lock); + usleep(100); + goto again; + } + } + tv = tv->next; + } + + SCMutexUnlock(&tv_root_lock); + return; +} + +#else /* BUILD_UNIX_SOCKET */ + +void UnixManagerThreadSpawn(int mode) +{ + SCLogError("Unix socket is not compiled"); + return; +} + +void UnixSocketKillSocketThread(void) +{ + return; +} + +void UnixManagerThreadSpawnNonRunmode(const bool unix_socket_enabled) +{ + return; +} + +#endif /* BUILD_UNIX_SOCKET */ + +void TmModuleUnixManagerRegister (void) +{ +#if defined(BUILD_UNIX_SOCKET) && defined(HAVE_SYS_UN_H) && defined(HAVE_SYS_STAT_H) && defined(HAVE_SYS_TYPES_H) + tmm_modules[TMM_UNIXMANAGER].name = "UnixManager"; + tmm_modules[TMM_UNIXMANAGER].ThreadInit = UnixManagerThreadInit; + tmm_modules[TMM_UNIXMANAGER].ThreadDeinit = UnixManagerThreadDeinit; + tmm_modules[TMM_UNIXMANAGER].Management = UnixManager; + tmm_modules[TMM_UNIXMANAGER].cap_flags = 0; + tmm_modules[TMM_UNIXMANAGER].flags = TM_FLAG_COMMAND_TM; +#endif /* BUILD_UNIX_SOCKET */ +} |