diff --git a/src/Makefile.am b/src/Makefile.am index dd0392e..e41b572 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -207,6 +207,7 @@ alert-unified-log.c alert-unified-log.h \ alert-unified-alert.c alert-unified-alert.h \ alert-unified2-alert.c alert-unified2-alert.h \ alert-syslog.c alert-syslog.h \ +alert-unix-socket.c alert-unix-socket.h \ log-droplog.c log-droplog.h \ log-httplog.c log-httplog.h \ stream.c stream.h \ diff --git a/src/alert-unix-socket.c b/src/alert-unix-socket.c new file mode 100644 index 0000000..7b7b517 --- /dev/null +++ b/src/alert-unix-socket.c @@ -0,0 +1,433 @@ +/* Copyright (C) 2007-2010 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 Vivek Rajagopalan + * + * Log alerts in Snort 2.9 compatible format to a Unix Domain Socket + */ + +#include "suricata-common.h" +#include "debug.h" +#include "detect.h" +#include "conf.h" + +#include "threads.h" +#include "threadvars.h" +#include "tm-modules.h" + +#include "util-unittest.h" +#include "util-error.h" +#include "output.h" +#include "alert-unix-socket.h" + +#include +#include +#include + +#define DEFAULT_LOG_FILENAME "suricata-unix-socket" + +#define ERRBUF_LENGTH 256 + +/* prototypes */ + +TmEcode UnixSocketAlert (ThreadVars *, Packet *, void *, PacketQueue *, PacketQueue *); +TmEcode UnixSocketAlertThreadInit(ThreadVars *, void *, void **); +TmEcode UnixSocketAlertThreadDeinit(ThreadVars *, void *); +int UnixSocketPacketTypeAlert(ThreadVars *, Packet *, void *); +void UnixSocketRegisterTests(); +static void UnixSocketAlertDeInitCtx(OutputCtx *); + +/** + * UnixSocket thread vars + * + * Used for storing file options. + */ +typedef struct UnixSocketAlertThread_ { + LogFileCtx *file_ctx; /** LogFileCtx pointer */ + struct sockaddr_un remote; /** contains unsock file name ready to sendto */ +} UnixSocketAlertThread; + + +/** + * Desired output format from Snort 2.9 codebase + */ +#define SNORT_2_9_ALERTMSG_LENGTH 256 + +typedef struct _Event +{ + uint32_t sig_generator; /* which part of snort generated the alert? */ + uint32_t sig_id; /* sig id for this generator */ + uint32_t sig_rev; /* sig revision for this id */ + uint32_t classification; /* event classification */ + uint32_t priority; /* event priority */ + uint32_t event_id; /* event ID */ + uint32_t event_reference; /* reference to other events that have gone off, + * such as in the case of tagged packets... + * */ + struct { + uint32_t tv_sec; + uint32_t tv_usec; + } ref_time; /* reference time for the event reference */ + + /* Don't add to this structure because this is the serialized data + * struct for unified logging. + */ + +} Event; + +/** + * SNORT_2_9_ALERTMSG_LENGTH is actually a fixed length, so is pkt in 2.9 snort + */ + +/* this struct is for the alert socket code.... */ +typedef struct _Alertpkt +{ + uint8_t alertmsg[SNORT_2_9_ALERTMSG_LENGTH]; /* variable.. */ + struct { + uint32_t tv_sec; + uint32_t tv_usec; + uint32_t caplen; + uint32_t len; + } pkth; + uint32_t dlthdr; /* datalink header offset. (ethernet, etc.. ) */ + uint32_t nethdr; /* network header offset. (ip etc...) */ + uint32_t transhdr; /* transport header offset (tcp/udp/icmp ..) */ + uint32_t data; + uint32_t val; /* which fields are valid. (NULL could be + * valids also) */ + /* Packet struct --> was null */ + #define NOPACKET_STRUCT 0x1 + /* no transport headers in packet */ + #define NO_TRANSHDR 0x2 + uint8_t pkt[65535]; + Event event; +} Alertpkt; + +#define MODULE_NAME "UnixSocketAlert" + +void TmModuleUnixSocketAlertRegister (void) { + tmm_modules[TMM_ALERTUNIXSOCKET].name = MODULE_NAME; + tmm_modules[TMM_ALERTUNIXSOCKET].ThreadInit = UnixSocketAlertThreadInit; + tmm_modules[TMM_ALERTUNIXSOCKET].Func = UnixSocketAlert; + tmm_modules[TMM_ALERTUNIXSOCKET].ThreadDeinit = UnixSocketAlertThreadDeinit; + tmm_modules[TMM_ALERTUNIXSOCKET].RegisterTests = UnixSocketRegisterTests; + tmm_modules[TMM_ALERTUNIXSOCKET].cap_flags = 0; + + OutputRegisterModule(MODULE_NAME, "unix-socket", UnixSocketAlertInitCtx); +} + +/** + * \brief Log alert into unix socket in a Snort 2.9.x compatible format + * Key points - 1) use DGRAM 2) no need for host/network order + * + * \param t Thread Variable containing input/output queue, cpu affinity etc. + * \param p Packet struct used to decide for ipv4 or ipv6 + * \param data UnixSocket thread data. + * \param pq Packet queue + * \retval 0 on succces + * \retval -1 on failure + */ + +TmEcode UnixSocketAlert (ThreadVars *tv, Packet *p, void *data, PacketQueue *pq, PacketQueue * postpq) +{ + UnixSocketAlertThread *aun = (UnixSocketAlertThread *)data; + PacketAlert *pa; + PacketAlert pa_tag; + + Alertpkt ap; + memset(&ap,0,sizeof(Alertpkt)); + + if (p->alerts.cnt == 0 ) + return TM_ECODE_OK; + + /* extract as much as possible into the snort structure */ + ap.pkth.tv_sec = p->ts.tv_sec; + ap.pkth.tv_usec = p->ts.tv_usec; + ap.pkth.len = p->pktlen; + ap.pkth.caplen = p->pktlen; + + /* ethernet offset nfq has no ethernet header so pad */ + if (p->ethh == NULL) { + ap.dlthdr = sizeof(EthernetHdr); + } else { + ap.dlthdr = (uint8_t*) p->ethh - GET_PKT_DATA(p); + } + + if (PKT_IS_IPV4(p)) { + ap.nethdr = (uint8_t*) p->ip4h - GET_PKT_DATA(p); + } else if (PKT_IS_IPV6(p)) { + ap.nethdr = (uint8_t*) p->ip6h - GET_PKT_DATA(p); + } + + if (p->icmpv4h != NULL ) { + ap.transhdr = (uint8_t*) p->icmpv4h - GET_PKT_DATA(p); + } else if (p->tcph != NULL ) { + ap.transhdr = (uint8_t*) p->tcph - GET_PKT_DATA(p); + } else if (p->udph != NULL ) { + ap.transhdr = (uint8_t*) p->udph - GET_PKT_DATA(p); + } else if (p->sctph != NULL ) { + ap.transhdr = (uint8_t*) p->sctph - GET_PKT_DATA(p); + } else { + ap.transhdr = 0; + ap.val|=NO_TRANSHDR; + } + + + if (p->payload != NULL ) { + ap.data = (uint8_t*) p->payload - GET_PKT_DATA(p); + } + + if (GET_PKT_LEN(p)==0 || GET_PKT_DATA(p)==NULL) { + ap.val|=NOPACKET_STRUCT; + } else { + memcpy(ap.pkt,GET_PKT_DATA(p),GET_PKT_LEN(p)); + } + + if (p->flags & PKT_HAS_TAG) + PacketAlertAppendTag(p, &pa_tag); + + + uint16_t i = 0; + for (; i < p->alerts.cnt + 1; i++) { + + + if (i < p->alerts.cnt) + pa = &p->alerts.alerts[i]; + else + if (p->flags & PKT_HAS_TAG) + pa = &pa_tag; + else + break; + + pa = &p->alerts.alerts[i]; + + if (pa->msg != NULL ) { + strlcpy((char*) ap.alertmsg,pa->msg,SNORT_2_9_ALERTMSG_LENGTH); + } + + ap.event.sig_generator = pa->gid; + ap.event.sig_id = pa->sid; + ap.event.sig_rev= pa->rev; + ap.event.classification = pa->class; + ap.event.priority = pa->prio; + ap.event.event_id = 0; + ap.event.ref_time.tv_sec = p->ts.tv_sec; + ap.event.ref_time.tv_usec =p->ts.tv_usec; + + if (-1 == sendto(aun->file_ctx->fd,&ap,sizeof(Alertpkt),0,(struct sockaddr*) &aun->remote, SUN_LEN(&aun->remote) )) { + char errbuf[ERRBUF_LENGTH]; + SCLogError(SC_ERR_COMM_WRITE, "Error: unix socket write failed: %s", strerror_r(errno,errbuf,ERRBUF_LENGTH)); + + /* returning fail here will respawn this thread, so error messages only indication it failed */ + return TM_ECODE_OK; + } + } + aun->file_ctx->alerts += p->alerts.cnt; + + return TM_ECODE_OK; +} + +/** + * \brief Thread init function. + * + * \param t Thread Variable containing input/output queue, cpu affinity etc. + * \param initdata UnixSocket thread initial data. + * \param data UnixSocket thread data. + * \retval TM_ECODE_OK on succces + * \retval TM_ECODE_FAILED on failure + */ + +TmEcode UnixSocketAlertThreadInit(ThreadVars *t, void *initdata, void **data) +{ + UnixSocketAlertThread *aun = SCMalloc(sizeof(UnixSocketAlertThread)); + if (aun == NULL) + return TM_ECODE_FAILED; + memset(aun, 0, sizeof(UnixSocketAlertThread)); + if(initdata == NULL) + { + SCLogDebug("Error getting context for UnixSocketAlert. \"initdata\" argument NULL"); + SCFree(aun); + return TM_ECODE_FAILED; + } + aun->file_ctx = ((OutputCtx *)initdata)->data; + + + memset(&aun->remote,0,sizeof(struct sockaddr_un)); + aun->remote.sun_family = AF_UNIX; + strlcpy(aun->remote.sun_path, aun->file_ctx->filename, sizeof(aun->remote.sun_path)); + + *data = (void *)aun; + return TM_ECODE_OK; +} + +/** + * \brief Thread deinit function. + * + * \param t Thread Variable containing input/output queue, cpu affinity etc. + * \param data UnixSocket thread data. + * \retval TM_ECODE_OK on succces + * \retval TM_ECODE_FAILED on failure + */ + +TmEcode UnixSocketAlertThreadDeinit(ThreadVars *t, void *data) +{ + UnixSocketAlertThread *aun = (UnixSocketAlertThread *)data; + if (aun == NULL) { + goto error; + } + + if (!(aun->file_ctx->flags & LOGFILE_ALERTS_PRINTED)) { + SCLogInfo("Alert unix socket module wrote %"PRIu64" alerts", + aun->file_ctx->alerts); + + /* Do not print it for each thread */ + aun->file_ctx->flags |= LOGFILE_ALERTS_PRINTED; + } + + /* clear memory */ + memset(aun, 0, sizeof(UnixSocketAlertThread)); + SCFree(aun); + return TM_ECODE_OK; + +error: + return TM_ECODE_FAILED; +} + +/** \brief Create a new LogFileCtx from the provided ConfNode. + * \param conf The configuration node for this output. + * \return NULL if failure, LogFileCtx* with valid socket fd & filename if successful + * */ +OutputCtx *UnixSocketAlertInitCtx(ConfNode *conf) +{ + int s; + + LogFileCtx* file_ctx = NULL; + + file_ctx = LogFileNewCtx(); + if (file_ctx == NULL) { + SCLogError(SC_ERR_INITIALIZATION, "Couldn't create new file_ctx"); + goto error; + } + + /* use the socket filename in the yaml file, if absolute, make it relative to log-dir */ + const char *filename = NULL; + if (conf != NULL) { /* To faciliate unit tests. */ + filename = ConfNodeLookupChildValue(conf, "filename"); + } + if (filename == NULL) + filename = DEFAULT_LOG_FILENAME; + + file_ctx->filename = SCMalloc(PATH_MAX); + memset(file_ctx->filename,0,PATH_MAX); + + /* relativize it if not absolute path */ + if (filename[0] != '/') + { + char *log_dir; + if (ConfGet("default-log-dir", &log_dir) != 1) + log_dir = DEFAULT_LOG_DIR; + + /* need sanity test due to use of fixed length PATH_MAX buffer */ + if (strlen(filename) + strlen(log_dir) + 2 >= PATH_MAX) + { + SCLogError(SC_ERR_INVALID_ARGUMENT, + "Failed to initialize unix socket output, filename + log_dir too big: Max allowed = %d", + PATH_MAX); + + goto error; + } + + strlcpy(file_ctx->filename,log_dir, PATH_MAX); + if (log_dir[strlen(log_dir)-1]!='/') + strlcat(file_ctx->filename,"/",PATH_MAX); + strlcat(file_ctx->filename,filename,PATH_MAX); + } + else + { + /* need sanity test due to use of fixed length PATH_MAX buffer */ + if (strlen(filename) >= PATH_MAX) + { + SCLogError(SC_ERR_INVALID_ARGUMENT, + "Failed to initialize unix socket output, filename too big: Max allowed = %d", + PATH_MAX); + goto error; + } + + strlcpy(file_ctx->filename,filename,PATH_MAX); + } + + + /* create a unix domain socket - connectionless is okay because all comms are local anyway */ + if ((s = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) + { + char errbuf[ERRBUF_LENGTH]; + + SCLogError(SC_ERR_COMM_OPEN, + "Cant create unix socket %d : %s", errno, strerror_r(errno,errbuf,ERRBUF_LENGTH)); + goto error; + } + file_ctx->fd = s; + + + /* pack everything into an output context */ + OutputCtx *output_ctx = SCCalloc(1, sizeof(OutputCtx)); + if (output_ctx == NULL) + goto error; + output_ctx->data = file_ctx; + output_ctx->DeInit = UnixSocketAlertDeInitCtx; + + SCLogInfo("unix-socket successfully initialized: filename %s", + file_ctx->filename ); + return output_ctx; + +error: + if (file_ctx != NULL) { + LogFileFreeCtx(file_ctx); + } + + return NULL; +} + +static void UnixSocketAlertDeInitCtx(OutputCtx *output_ctx) +{ + if (output_ctx != NULL) { + LogFileCtx *logfile_ctx = (LogFileCtx *)output_ctx->data; + if (logfile_ctx != NULL) { + LogFileFreeCtx(logfile_ctx); + } + free(output_ctx); + } +} + +#ifdef UNITTESTS + +/* + * No unit tests as of now + */ + +#endif + +/** + * \brief this function registers unit tests for UnixSocket + */ +void UnixSocketRegisterTests (void) { +#ifdef UNITTESTS +#endif /* UNITTESTS */ +} diff --git a/src/alert-unix-socket.h b/src/alert-unix-socket.h new file mode 100644 index 0000000..0428792 --- /dev/null +++ b/src/alert-unix-socket.h @@ -0,0 +1,30 @@ +/* Copyright (C) 2007-2010 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 alert-unix-socket snort 2.9.x compatible alerts to Unix Socket + * \author Vivek Rajagopalan + */ + +#ifndef __ALERT_UNIX_SOCKET_H +#define __ALERT_UNIX_SOCKET_H + +void TmModuleUnixSocketAlertRegister (void); +OutputCtx *UnixSocketAlertInitCtx(ConfNode *conf); + +#endif /* __ALERT_UNIX_SOCKET_H */ + diff --git a/src/suricata.c b/src/suricata.c index 91c4c60..5c6e2c5 100644 --- a/src/suricata.c +++ b/src/suricata.c @@ -77,6 +77,7 @@ #include "alert-unified-log.h" #include "alert-unified-alert.h" #include "alert-unified2-alert.h" +#include "alert-unix-socket.h" #include "alert-debuglog.h" #include "alert-prelude.h" #include "alert-syslog.h" @@ -924,6 +925,7 @@ int main(int argc, char **argv) TmModuleAlertSyslogIPv6Register(); TmModuleAlertUnifiedLogRegister(); TmModuleAlertUnifiedAlertRegister(); + TmModuleUnixSocketAlertRegister(); TmModuleUnified2AlertRegister(); TmModuleAlertSyslogRegister(); TmModuleLogDropLogRegister(); diff --git a/src/tm-modules.c b/src/tm-modules.c index a79bf00..0ea18d2 100644 --- a/src/tm-modules.c +++ b/src/tm-modules.c @@ -75,6 +75,8 @@ LogFileCtx *LogFileNewCtx() return NULL; memset(lf_ctx, 0, sizeof(LogFileCtx)); + lf_ctx->fd = -1; /* 0 is a valid fd */ + SCMutexInit(&lf_ctx->fp_mutex,NULL); return lf_ctx; @@ -98,6 +100,12 @@ int LogFileFreeCtx(LogFileCtx *lf_ctx) SCMutexUnlock(&lf_ctx->fp_mutex); } + if (lf_ctx->fd != -1) + { + close(lf_ctx->fd); + lf_ctx->fd = -1; + } + SCMutexDestroy(&lf_ctx->fp_mutex); if (lf_ctx->prefix != NULL) diff --git a/src/tm-modules.h b/src/tm-modules.h index 01e5e42..1b94932 100644 --- a/src/tm-modules.h +++ b/src/tm-modules.h @@ -88,6 +88,7 @@ enum { TMM_DECODEERFFILE, TMM_RECEIVEERFDAG, TMM_DECODEERFDAG, + TMM_ALERTUNIXSOCKET, TMM_SIZE, }; @@ -96,6 +97,11 @@ TmModule tmm_modules[TMM_SIZE]; /** Global structure for Output Context */ typedef struct LogFileCtx_ { FILE *fp; + + /** Any output file descriptor + * unix socket, (future) TCP socket, etc */ + int fd; + /** It will be locked if the log/alert * record cannot be written to the file in one call */ SCMutex fp_mutex; diff --git a/src/util-error.c b/src/util-error.c index 460ec06..b34ba55 100644 --- a/src/util-error.c +++ b/src/util-error.c @@ -201,6 +201,9 @@ const char * SCErrorToString(SCError err) CASE_CODE (SC_ERR_HTTP_COOKIE_NEEDS_PRECEEDING_CONTENT); CASE_CODE (SC_ERR_HTTP_COOKIE_INCOMPATIBLE_WITH_RAWBYTES); CASE_CODE (SC_ERR_HTTP_COOKIE_RELATIVE_MISSING); + CASE_CODE (SC_ERR_COMM_OPEN); + CASE_CODE (SC_ERR_COMM_WRITE); + CASE_CODE (SC_ERR_COMM_READ); default: return "UNKNOWN_ERROR"; diff --git a/src/util-error.h b/src/util-error.h index 31bb51c..10c9ebb 100644 --- a/src/util-error.h +++ b/src/util-error.h @@ -189,7 +189,7 @@ typedef enum { SC_ERR_LIBNET11_INCOMPATIBLE_WITH_LIBCAP_NG, SC_WARN_FLOW_EMERGENCY, SC_WARN_COMPATIBILITY, - SC_ERR_SVC, + SC_ERR_SVC, SC_ERR_ERF_DAG_OPEN_FAILED, SC_ERR_ERF_DAG_STREAM_OPEN_FAILED, SC_ERR_ERF_DAG_STREAM_START_FAILED, @@ -212,6 +212,9 @@ typedef enum { SC_ERR_HTTP_COOKIE_NEEDS_PRECEEDING_CONTENT, SC_ERR_HTTP_COOKIE_INCOMPATIBLE_WITH_RAWBYTES, SC_ERR_HTTP_COOKIE_RELATIVE_MISSING, + SC_ERR_COMM_OPEN, /* error opening communication channel, unix sockets, ipcs, tcp sockets */ + SC_ERR_COMM_WRITE, /* write errors on comm channel*/ + SC_ERR_COMM_READ, /* read error on comm channel */ } SCError; const char *SCErrorToString(SCError); diff --git a/suricata.yaml b/suricata.yaml index dd637ac..1cf8fbc 100644 --- a/suricata.yaml +++ b/suricata.yaml @@ -63,7 +63,7 @@ outputs: - unified2-alert: enabled: yes filename: unified2.alert - + # Limit in MB. #limit: 32 @@ -73,6 +73,12 @@ outputs: filename: http.log append: yes + # unix sockets + - unix-socket: + enabled: yes + filename : /tmp/suricata.sock + + # a full alerts log containing much information for signature writers # or for investigating suspected false positives. - alert-debug: