/* 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 */ #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 #include #include #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 */ }