From f687013351d7dcb48545dfd69d3da5244ed62787 Mon Sep 17 00:00:00 2001 From: aCaB Date: Sat, 7 Feb 2009 00:59:58 +0000 Subject: [PATCH] IDSESSION + INSTREAM sport tidy (smthng still fuxxxd up) git-svn-id: file:///var/lib/svn/clamav-devel/branches/clamd-proto@4693 77e5149b-7576-45b1-b177-96237e5ba77b --- clamdscan/Makefile.am | 3 +- clamdscan/Makefile.in | 10 +- clamdscan/client.c | 476 +------------------------------------ clamdscan/client.h | 11 +- clamdscan/proto.c | 536 ++++++++++++++++++++++++++++++++++++++++++ clamdscan/proto.h | 38 +++ 6 files changed, 604 insertions(+), 470 deletions(-) create mode 100644 clamdscan/proto.c create mode 100644 clamdscan/proto.h diff --git a/clamdscan/Makefile.am b/clamdscan/Makefile.am index dfba3882d..19995ad6d 100644 --- a/clamdscan/Makefile.am +++ b/clamdscan/Makefile.am @@ -31,9 +31,10 @@ clamdscan_SOURCES = \ $(top_srcdir)/shared/getopt.h \ $(top_srcdir)/libclamav/regex/strlcpy.c\ clamdscan.c \ + proto.c \ + proto.h \ client.c \ client.h - endif DEFS = @DEFS@ -DCL_NOTHREADS -DCL_NOLIBCLAMAV diff --git a/clamdscan/Makefile.in b/clamdscan/Makefile.in index a6e00fdf4..f119fd96c 100644 --- a/clamdscan/Makefile.in +++ b/clamdscan/Makefile.in @@ -76,12 +76,13 @@ am__clamdscan_SOURCES_DIST = $(top_srcdir)/shared/output.c \ $(top_srcdir)/shared/optparser.h $(top_srcdir)/shared/misc.c \ $(top_srcdir)/shared/misc.h $(top_srcdir)/shared/getopt.c \ $(top_srcdir)/shared/getopt.h \ - $(top_srcdir)/libclamav/regex/strlcpy.c clamdscan.c client.c \ - client.h + $(top_srcdir)/libclamav/regex/strlcpy.c clamdscan.c proto.c \ + proto.h client.c client.h @BUILD_CLAMD_TRUE@am_clamdscan_OBJECTS = output.$(OBJEXT) \ @BUILD_CLAMD_TRUE@ optparser.$(OBJEXT) misc.$(OBJEXT) \ @BUILD_CLAMD_TRUE@ getopt.$(OBJEXT) strlcpy.$(OBJEXT) \ -@BUILD_CLAMD_TRUE@ clamdscan.$(OBJEXT) client.$(OBJEXT) +@BUILD_CLAMD_TRUE@ clamdscan.$(OBJEXT) proto.$(OBJEXT) \ +@BUILD_CLAMD_TRUE@ client.$(OBJEXT) clamdscan_OBJECTS = $(am_clamdscan_OBJECTS) clamdscan_LDADD = $(LDADD) DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) @@ -268,6 +269,8 @@ top_srcdir = @top_srcdir@ @BUILD_CLAMD_TRUE@ $(top_srcdir)/shared/getopt.h \ @BUILD_CLAMD_TRUE@ $(top_srcdir)/libclamav/regex/strlcpy.c\ @BUILD_CLAMD_TRUE@ clamdscan.c \ +@BUILD_CLAMD_TRUE@ proto.c \ +@BUILD_CLAMD_TRUE@ proto.h \ @BUILD_CLAMD_TRUE@ client.c \ @BUILD_CLAMD_TRUE@ client.h @@ -367,6 +370,7 @@ distclean-compile: @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/misc.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/optparser.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/output.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/proto.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/strlcpy.Po@am__quote@ .c.o: diff --git a/clamdscan/client.c b/clamdscan/client.c index fa1b5df1a..5491c385d 100644 --- a/clamdscan/client.c +++ b/clamdscan/client.c @@ -1,5 +1,7 @@ /* - * Copyright (C) 2002 - 2007 Tomasz Kojm + * Copyright (C) 2009 Sourcefire, Inc. + * + * Authors: Tomasz Kojm, aCaB * * 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 @@ -51,28 +53,25 @@ #include "libclamav/others.h" #include "client.h" +#include "proto.h" #ifndef INADDR_LOOPBACK #define INADDR_LOOPBACK 0x7f000001 #endif +#ifndef PATH_MAX +#define PATH_MAX 1024 +#endif + int notremoved = 0, notmoved = 0; int printinfected = 0; -static struct sockaddr *mainsa = NULL; -static int mainsasz; +struct sockaddr *mainsa = NULL; +int mainsasz; static struct sockaddr_un nixsock; static struct sockaddr_in tcpsock; -enum { - CONT, - MULTI, - STREAM, - FILDES -}; - -static const char *scancmd[] = { "CONTSCAN", "MULTISCAN" }; -static void (*action)(const char *) = NULL; +void (*action)(const char *) = NULL; static char *actarget; static void move_infected(const char *filename, int move); static void action_move(const char *filename) { @@ -102,241 +101,6 @@ void actsetup(const struct optstruct *opts) { } } -static int sendln(int sockd, const char *line, unsigned int len) { - while(len) { - int sent = send(sockd, line, len, 0); - if(sent <= 0) { - if(sent && errno == EINTR) continue; - logg("!Can't send request to clamd\n"); - return 1; - } - line += sent; - len -= sent; - } - return 0; -} - -struct RCVLN { - char buf[PATH_MAX+1024]; /* FIXME must match that in clamd - bb1349 */ - int sockd; - int r; - char *cur; - char *bol; -}; - -static void recvlninit(struct RCVLN *s, int sockd) { - s->sockd = sockd; - s->bol = s->cur = s->buf; - s->r = 0; -} - -static int recvln(struct RCVLN *s, char **rbol, char **reol) { - char *eol; - - while(1) { - if(!s->r) { - s->r = recv(s->sockd, s->cur, sizeof(s->buf) - (s->cur - s->buf), 0); - if(s->r<=0) { - if(s->r && errno == EINTR) { - s->r = 0; - continue; - } - if(s->r || s->cur!=s->buf) { - logg("!Communication error\n"); - return -1; - } - return 0; - } - } - if((eol = memchr(s->cur, 0, s->r))) { - int ret = 0; - eol++; - s->r -= eol - s->cur; - *rbol = s->bol; - if(reol) *reol = eol; - ret = eol - s->bol; - if(s->r) - s->bol = s->cur = eol; - else - s->bol = s->cur = s->buf; - return ret; - } - s->r += s->cur - s->bol; - if(!eol && s->r==sizeof(s->buf)) { - logg("!Overlong reply from clamd\n"); - return -1; - } - if(!eol) { - if(s->buf != s->bol) { /* old memmove sux */ - memmove(s->buf, s->bol, s->r); - s->bol = s->buf; - } - s->cur = &s->bol[s->r]; - s->r = 0; - } - } -} - -static int send_stream(int sockd, const char *filename) { - uint32_t buf[BUFSIZ/sizeof(uint32_t)]; - int fd, len; - - if(filename) { - if(!(fd = open(filename, O_RDONLY))) { - logg("!Open failed on %s.\n", filename); - return 1; - } - } else fd = 0; - - if(sendln(sockd, "zINSTREAM", 10)) return 1; - - while((len = read(fd, &buf[1], sizeof(buf) - sizeof(uint32_t))) > 0) { - buf[0] = htonl(len); - if(sendln(sockd, (const char *)buf, len+sizeof(uint32_t))) { /* FIXME: need to handle limits */ - logg("!Can't write to the socket.\n"); - close(fd); - return 1; - } - } - close(fd); - if(len) { - logg("!Failed to read from %s.\n", filename); - return 1; - } - return 0; -} - -static int send_fdpass(int sockd, const char *filename) { - struct iovec iov[1]; - struct msghdr msg; - struct cmsghdr *cmsg; - unsigned char fdbuf[CMSG_SPACE(sizeof(int))]; - char dummy[]=""; - int fd; - - if(filename) { - if(!(fd = open(filename, O_RDONLY))) { - logg("!Open failed on %s.\n", filename); - return 1; - } - } else fd = 0; - if(sendln(sockd, "zFILDES", 8)) return 1; - - iov[0].iov_base = dummy; - iov[0].iov_len = 1; - memset(&msg, 0, sizeof(msg)); - msg.msg_control = fdbuf; - msg.msg_iov = iov; - msg.msg_iovlen = 1; - msg.msg_controllen = CMSG_LEN(sizeof(int)); - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_len = CMSG_LEN(sizeof(int)); - cmsg->cmsg_level = SOL_SOCKET; - cmsg->cmsg_type = SCM_RIGHTS; - *(int *)CMSG_DATA(cmsg) = fd; - if(sendmsg(sockd, &msg, 0) == -1) { - logg("!FD send failed\n"); - return 1; - } - return 0; -} - -static int dsresult(int sockd, int scantype, const char *filename) -{ - int infected = 0, waserror = 0, len; - char *bol, *eol; - struct RCVLN rcv; - - recvlninit(&rcv, sockd); - - switch(scantype) { - case MULTI: - case CONT: - len = strlen(filename) + strlen(scancmd[scantype]) + 3; - if (!(bol = malloc(len))) { - logg("!Cannot allocate a command buffer\n"); - return -1; - } - sprintf(bol, "z%s %s", scancmd[scantype], filename); - if(sendln(sockd, bol, len)) return -1; - free(bol); - break; - - case STREAM: - if(send_stream(sockd, filename)) - return -1; - -#ifdef HAVE_FD_PASSING - case FILDES: - if(send_fdpass(sockd, filename)) - return -1; - break; -#endif - } - - while((len = recvln(&rcv, &bol, &eol))) { - if(len == -1) { - waserror = 1; - break; - } - if(!filename) logg("~%s\n", bol); - if(len > 7) { - char *colon = colon = strrchr(bol, ':'); - if(!colon) { - logg("Failed to parse reply\n"); - waserror = 1; - } else if(!memcmp(eol - 7, " FOUND", 6)) { - infected++; - if(filename) { - if(scantype >= STREAM) { - logg("~%s%s\n", filename, colon); - if(action) action(filename); - } else { - logg("~%s\n", bol); - *colon = '\0'; - if(action) - action(bol); - } - } - } else if(!memcmp(eol-7, " ERROR", 6)) { - if(filename) { - if(scantype >= STREAM) - logg("~%s%s\n", filename, colon); - else - logg("~%s\n", bol); - } - waserror = 1; - } - } - } - return infected ? infected : (waserror ? -1 : 0); -} - - -#ifndef PATH_MAX -#define PATH_MAX 1024 -#endif - -static int dconnect() -{ - int sockd; - - if((sockd = socket(mainsa->sa_family, SOCK_STREAM, 0)) < 0) { - perror("socket()"); - logg("!Can't create the socket.\n"); - return -1; - } - - if(connect(sockd, (struct sockaddr *)mainsa, mainsasz) < 0) { - close(sockd); - perror("connect()"); - logg("!Can't connect to clamd.\n"); - return -1; - } - - return sockd; -} - static int isremote(const struct optstruct *opts) { int s, ret; const struct optstruct *opt; @@ -390,58 +154,6 @@ static int isremote(const struct optstruct *opts) { return ret; } -struct client_serial_data { - int infected; - int errors; - int scantype; - int spam; -}; - -static int serial_callback(struct stat *sb, char *filename, const char *path, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data) { - struct client_serial_data *c = (struct client_serial_data *)data->data; - int sockd, ret; - const char *f = filename; - - switch(reason) { - case error_stat: - logg("^Can't access file %s\n", filename); - return CL_SUCCESS; - case error_mem: - logg("^Memory allocation failed in ftw\n"); - return CL_EMEM; - case warning_skipped_dir: - logg("^Directory recursion limit reached\n"); - return CL_SUCCESS; - case warning_skipped_special: - logg("~%s: Not supported file type. ERROR\n", filename); - c->errors++; - return CL_SUCCESS; - default: - break; - } - - if(reason == visit_directory_toplev) { - c->spam = 1; - if(c->scantype >= STREAM) { - free(filename); - return CL_SUCCESS; - } - f = path; - } - if((sockd = dconnect()) < 0) { - free(filename); - return CL_BREAK; - } - if((ret = dsresult(sockd, c->scantype, f)) >= 0) - c->infected += ret; - else - c->errors++; - close(sockd); - free(filename); - if(reason == visit_directory_toplev) - return CL_BREAK; - return CL_SUCCESS; -} static char *makeabs(const char *basepath) { int namelen; @@ -466,173 +178,7 @@ static char *makeabs(const char *basepath) { return ret; } -static int serial_client_scan(const char *file, int scantype, int *infected, int *errors, int maxlevel) { - struct cli_ftw_cbdata data; - struct client_serial_data cdata; - - cdata.infected = 0; - cdata.errors = 0; - cdata.scantype = scantype; - cdata.spam = 0; - data.data = &cdata; - - cli_ftw(file, CLI_FTW_STD, maxlevel ? maxlevel : INT_MAX, serial_callback, &data); - if(!cdata.infected && (!cdata.errors || cdata.spam)) logg("~%s: OK\n", file); - - *infected += cdata.infected; - *errors += cdata.errors; - return 0; -} - -struct client_parallel_data { - int infected; - int errors; - int scantype; - int spam; - int sockd; - int lastid; - struct SCANID { - unsigned int id; - const char *file; - struct SCANID *next; - } *ids; -}; - -int dspresult(struct client_parallel_data *c) { - int rid, len; - char *bol, *eol, *filename; - fd_set fds; - struct SCANID **id = NULL; - struct timeval tv = { 0, 0 }; - struct RCVLN rcv; - - recvlninit(&rcv, c->sockd); - while(1) { - FD_ZERO(&fds); - FD_SET(c->sockd, &fds); - switch (select(1, &fds, NULL, NULL, &tv)) { - case -1: - c->errors++; - logg("!select failed during session\n"); - return CL_BREAK; /* this is an hard failure */ - case 0: - c->errors++; - return CL_SUCCESS; - } - - /* FIXME: repeat till i have stuff in the buff */ - len = recvln(&rcv, &bol, &eol); - if(len == -1) { - c->errors++; - break; - } - if((rid = atoi(bol))) { - id = &c->ids; - while(*id) { - if((*id)->id == rid) break; - id = &((*id)->next); - } - if(!*id) id = NULL; - } - if(!id) { - c->errors++; - logg("!Bogus session id from clamd\n"); - return CL_BREAK; /* this is an hard failure */ - } - filename = (*id)->file; - if(len > 7) { - char *colon = colon = strrchr(bol, ':'); - if(!colon) { - c->errors++; - logg("Failed to parse reply\n"); - } else if(!memcmp(eol - 7, " FOUND", 6)) { - c->infected++; - logg("~%s%s\n", filename, colon); - if(action) action(filename); - } else if(!memcmp(eol-7, " ERROR", 6)) { - c->errors++; - if(filename) - logg("~%s%s\n", filename, colon); - } - } - } - return CL_SUCCESS; -} - - -static int parallel_callback(struct stat *sb, char *filename, const char *path, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data) { - struct client_parallel_data *c = (struct client_parallel_data *)data->data; - struct SCANID **id = &c->ids, *cid; - - switch(reason) { - case error_stat: - logg("^Can't access file %s\n", filename); - return CL_SUCCESS; - case error_mem: - logg("^Memory allocation failed in ftw\n"); - return CL_EMEM; - case warning_skipped_dir: - logg("^Directory recursion limit reached\n"); - return CL_SUCCESS; - case warning_skipped_special: - logg("~%s: Not supported file type. ERROR\n", filename); - c->errors++; - return CL_SUCCESS; - case visit_directory_toplev: - c->spam = 1; - free(filename); - return CL_SUCCESS; - default: - break; - } - while (*id) - id = &((*id)->next); - cid = (struct SCANID *)malloc(sizeof(struct SCANID *)); - *id = cid; - cid->id = ++c->lastid; - cid->file = filename; - cid->next = NULL; - - switch(c->scantype) { - case FILDES: - send_fdpass(c->sockd, filename); /* FIXME: check return */ - break; - case STREAM: - send_stream(c->sockd, filename); /* FIXME: check return */ - break; - } - - return dspresult(c); -} - -static int parallel_client_scan(const char *file, int scantype, int *infected, int *errors, int maxlevel) { - struct cli_ftw_cbdata data; - struct client_parallel_data cdata; - - if((cdata.sockd = dconnect()) < 0) - return 1; - - if(sendln(cdata.sockd, "zIDSESSION", 11)) { - close(cdata.sockd); - return 1; - } - - cdata.infected = 0; - cdata.errors = 0; - cdata.scantype = scantype; - cdata.spam = 0; - cdata.lastid = 0; - data.data = &cdata; - - cli_ftw(file, CLI_FTW_STD, maxlevel ? maxlevel : INT_MAX, parallel_callback, &data); - /* FIXME: check return */ - if(!cdata.infected && (!cdata.errors || cdata.spam)) logg("~%s: OK\n", file); - - *infected += cdata.infected; - *errors += cdata.errors; - return 0; -} static int client_scan(const char *file, int scantype, int *infected, int *errors, int maxlevel, int session) { int ret; diff --git a/clamdscan/client.h b/clamdscan/client.h index b06d4a61d..d4429be8a 100644 --- a/clamdscan/client.h +++ b/clamdscan/client.h @@ -1,5 +1,7 @@ /* - * Copyright (C) 2002 Tomasz Kojm + * Copyright (C) 2009 Sourcefire, Inc. + * + * Authors: Tomasz Kojm, aCaB * * 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 @@ -21,6 +23,13 @@ #include "shared/optparser.h" +enum { + CONT, + MULTI, + STREAM, + FILDES +}; + void actsetup(const struct optstruct *opts); int client(const struct optstruct *opts, int *infected); int get_clamd_version(const struct optstruct *opts); diff --git a/clamdscan/proto.c b/clamdscan/proto.c new file mode 100644 index 000000000..27a11bb28 --- /dev/null +++ b/clamdscan/proto.c @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2009 Sourcefire, Inc. + * + * Authors: Tomasz Kojm, aCaB + * + * 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. + * + * 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 + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "libclamav/others.h" +#include "shared/output.h" + +#include "proto.h" +#include "client.h" + +extern struct sockaddr *mainsa; +extern int mainsasz; +extern void (*action)(const char *); + +static const char *scancmd[] = { "CONTSCAN", "MULTISCAN" }; + +/* Connects to clamd + * Returns a FD or -1 on error */ +int dconnect() { + int sockd; + + if((sockd = socket(mainsa->sa_family, SOCK_STREAM, 0)) < 0) { + perror("socket()"); + logg("!Can't create the socket.\n"); + return -1; + } + + if(connect(sockd, (struct sockaddr *)mainsa, mainsasz) < 0) { + close(sockd); + perror("connect()"); + logg("!Can't connect to clamd.\n"); + return -1; + } + return sockd; +} + +/* Sends bytes over a socket + * Returns 0 on success */ +int sendln(int sockd, const char *line, unsigned int len) { + while(len) { + int sent = send(sockd, line, len, 0); + if(sent <= 0) { + if(sent && errno == EINTR) continue; + logg("!Can't send request to clamd\n"); + return 1; + } + line += sent; + len -= sent; + } + return 0; +} + +/* Inits a RECVLN struct before it can be used in recvln() - see below */ +void recvlninit(struct RCVLN *s, int sockd) { + s->sockd = sockd; + s->bol = s->cur = s->buf; + s->r = 0; +} + +/* Receives a full (terminated with \0) line from a socket + * Sets rbol to the begin of the received line, and optionally + * reol to the ond of line. + * Should be called repeatedly untill all input is conumed + * Returns + * - the lenght of the line (a positive number) on success + * - 0 if the connection is closed + * - -1 on error + */ +int recvln(struct RCVLN *s, char **rbol, char **reol) { + char *eol; + + while(1) { + if(!s->r) { + s->r = recv(s->sockd, s->cur, sizeof(s->buf) - (s->cur - s->buf), 0); + if(s->r<=0) { + if(s->r && errno == EINTR) { + s->r = 0; + continue; + } + if(s->r || s->cur!=s->buf) { + logg("!Communication error\n"); + return -1; + } + return 0; + } + } + if((eol = memchr(s->cur, 0, s->r))) { + int ret = 0; + eol++; + s->r -= eol - s->cur; + *rbol = s->bol; + if(reol) *reol = eol; + ret = eol - s->bol; + if(s->r) + s->bol = s->cur = eol; + else + s->bol = s->cur = s->buf; + return ret; + } + s->r += s->cur - s->bol; + if(!eol && s->r==sizeof(s->buf)) { + logg("!Overlong reply from clamd\n"); + return -1; + } + if(!eol) { + if(s->buf != s->bol) { /* old memmove sux */ + memmove(s->buf, s->bol, s->r); + s->bol = s->buf; + } + s->cur = &s->bol[s->r]; + s->r = 0; + } + } +} + +/* Issues an INSTREAM command to clamd and streams the given file + * Returns 0 on success */ +static int send_stream(int sockd, const char *filename) { + uint32_t buf[BUFSIZ/sizeof(uint32_t)]; + int fd, len; + + if(filename) { + if(!(fd = open(filename, O_RDONLY))) { + logg("!Open failed on %s.\n", filename); + return 1; + } + } else fd = 0; + + if(sendln(sockd, "zINSTREAM", 10)) return 1; + + while((len = read(fd, &buf[1], sizeof(buf) - sizeof(uint32_t))) > 0) { + buf[0] = htonl(len); + if(sendln(sockd, (const char *)buf, len+sizeof(uint32_t))) { /* FIXME: need to handle limits */ + logg("!Can't write to the socket.\n"); + close(fd); + return 1; + } + } + close(fd); + if(len) { + logg("!Failed to read from %s.\n", filename); + return 1; + } + return 0; +} + +/* Issues a FILDES command and pass a FD to clamd + * Returns 0 on success */ +static int send_fdpass(int sockd, const char *filename) { + struct iovec iov[1]; + struct msghdr msg; + struct cmsghdr *cmsg; + unsigned char fdbuf[CMSG_SPACE(sizeof(int))]; + char dummy[]=""; + int fd; + + if(filename) { + if(!(fd = open(filename, O_RDONLY))) { + logg("!Open failed on %s.\n", filename); + return 1; + } + } else fd = 0; + if(sendln(sockd, "zFILDES", 8)) return 1; + + iov[0].iov_base = dummy; + iov[0].iov_len = 1; + memset(&msg, 0, sizeof(msg)); + msg.msg_control = fdbuf; + msg.msg_iov = iov; + msg.msg_iovlen = 1; + msg.msg_controllen = CMSG_LEN(sizeof(int)); + cmsg = CMSG_FIRSTHDR(&msg); + cmsg->cmsg_len = CMSG_LEN(sizeof(int)); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + *(int *)CMSG_DATA(cmsg) = fd; + if(sendmsg(sockd, &msg, 0) == -1) { + logg("!FD send failed\n"); + return 1; + } + return 0; +} + +/* Sends a proper scan request to clamd and parses its replies + * This is used only in non IDSESSION mode + * Returns the number of infected files or -1 on error */ +int dsresult(int sockd, int scantype, const char *filename) { + int infected = 0, waserror = 0, len; + char *bol, *eol; + struct RCVLN rcv; + + recvlninit(&rcv, sockd); + + switch(scantype) { + case MULTI: + case CONT: + len = strlen(filename) + strlen(scancmd[scantype]) + 3; + if (!(bol = malloc(len))) { + logg("!Cannot allocate a command buffer\n"); + return -1; + } + sprintf(bol, "z%s %s", scancmd[scantype], filename); + if(sendln(sockd, bol, len)) return -1; + free(bol); + break; + + case STREAM: + if(send_stream(sockd, filename)) + return -1; + +#ifdef HAVE_FD_PASSING + case FILDES: + if(send_fdpass(sockd, filename)) + return -1; + break; +#endif + } + + while((len = recvln(&rcv, &bol, &eol))) { + if(len == -1) { + waserror = 1; + break; + } + printf(">%s<\n", bol); + if(!filename) logg("~%s\n", bol); + if(len > 7) { + char *colon = colon = strrchr(bol, ':'); + if(!colon) { + logg("Failed to parse reply\n"); + waserror = 1; + } else if(!memcmp(eol - 7, " FOUND", 6)) { + infected++; + if(filename) { + if(scantype >= STREAM) { + logg("~%s%s\n", filename, colon); + if(action) action(filename); + } else { + logg("~%s\n", bol); + *colon = '\0'; + if(action) + action(bol); + } + } + } else if(!memcmp(eol-7, " ERROR", 6)) { + if(filename) { + if(scantype >= STREAM) + logg("~%s%s\n", filename, colon); + else + logg("~%s\n", bol); + } + waserror = 1; + } + } + } + return infected ? infected : (waserror ? -1 : 0); +} + + + +/* Used by serial_callback() */ +struct client_serial_data { + int infected; + int errors; + int scantype; + int spam; +}; + +/* FTW callback for scanning in non IDSESSION mode */ +static int serial_callback(struct stat *sb, char *filename, const char *path, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data) { + struct client_serial_data *c = (struct client_serial_data *)data->data; + int sockd, ret; + const char *f = filename; + + switch(reason) { + case error_stat: + logg("^Can't access file %s\n", filename); + return CL_SUCCESS; + case error_mem: + logg("^Memory allocation failed in ftw\n"); + return CL_EMEM; + case warning_skipped_dir: + logg("^Directory recursion limit reached\n"); + return CL_SUCCESS; + case warning_skipped_special: + logg("~%s: Not supported file type. ERROR\n", filename); + c->errors++; + return CL_SUCCESS; + default: + break; + } + + if(reason == visit_directory_toplev) { + c->spam = 1; + if(c->scantype >= STREAM) { + free(filename); + return CL_SUCCESS; + } + f = path; + } + if((sockd = dconnect()) < 0) { + free(filename); + return CL_BREAK; + } + if((ret = dsresult(sockd, c->scantype, f)) >= 0) + c->infected += ret; + else + c->errors++; + close(sockd); + free(filename); + if(reason == visit_directory_toplev) + return CL_BREAK; + return CL_SUCCESS; +} + +/* Non-IDSESSION handler + * FIXME: returns what ? */ +int serial_client_scan(const char *file, int scantype, int *infected, int *errors, int maxlevel) { + struct cli_ftw_cbdata data; + struct client_serial_data cdata; + + cdata.infected = 0; + cdata.errors = 0; + cdata.scantype = scantype; + cdata.spam = 0; + data.data = &cdata; + + cli_ftw(file, CLI_FTW_STD, maxlevel ? maxlevel : INT_MAX, serial_callback, &data); + /* FIXME: care about return ? */ + if(!cdata.infected && (!cdata.errors || cdata.spam)) logg("~%s: OK\n", file); + + *infected += cdata.infected; + *errors += cdata.errors; + return 0; +} + +/* Used in IDSESSION mode */ +struct client_parallel_data { + int infected; + int errors; + int scantype; + int spam; + int sockd; + int lastid; + struct SCANID { + unsigned int id; + const char *file; + struct SCANID *next; + } *ids; +}; + +/* Sends a proper scan request to clamd and parses its replies + * This is used only in IDSESSION mode + * Returns 0 on success, 1 on hard failures */ +int dspresult(struct client_parallel_data *c) { + const char *filename; + char *bol, *eol; + unsigned int rid; + int len; + struct SCANID **id = NULL; + struct RCVLN rcv; + + recvlninit(&rcv, c->sockd); + do { + len = recvln(&rcv, &bol, &eol); + if(len == -1) { + c->errors++; + break; + } + if((rid = atoi(bol))) { + id = &c->ids; + while(*id) { + if((*id)->id == rid) break; + id = &((*id)->next); + } + if(!*id) id = NULL; + } + if(!id) { + c->errors++; + logg("!Bogus session id from clamd\n"); + return 1; /* this is an hard failure */ + } + filename = (*id)->file; + if(len > 7) { + char *colon = colon = strrchr(bol, ':'); + if(!colon) { + c->errors++; + logg("Failed to parse reply\n"); + } else if(!memcmp(eol - 7, " FOUND", 6)) { + c->infected++; + logg("~%s%s\n", filename, colon); + if(action) action(filename); + } else if(!memcmp(eol-7, " ERROR", 6)) { + c->errors++; + if(filename) + logg("~%s%s\n", filename, colon); + } + } + free((void *)filename); + bol = (char *)*id; + *id = (*id)->next; + free(bol); + } while(rcv.cur != rcv.buf); /* clamd sends whole lines, so, on partial lines, we just assume + more data can be recv()'d with close to zero latency */ + return 0; +} + +/* FTW callback for scanning in IDSESSION mode */ +static int parallel_callback(struct stat *sb, char *filename, const char *path, enum cli_ftw_reason reason, struct cli_ftw_cbdata *data) { + struct client_parallel_data *c = (struct client_parallel_data *)data->data; + struct SCANID **id = &c->ids, *cid; + + switch(reason) { + case error_stat: + logg("^Can't access file %s\n", filename); + return CL_SUCCESS; + case error_mem: + logg("^Memory allocation failed in ftw\n"); + return CL_EMEM; + case warning_skipped_dir: + logg("^Directory recursion limit reached\n"); + return CL_SUCCESS; + case warning_skipped_special: + logg("~%s: Not supported file type. ERROR\n", filename); + c->errors++; + return CL_SUCCESS; + case visit_directory_toplev: + c->spam = 1; + free(filename); + return CL_SUCCESS; + default: + break; + } + + while(1) { + /* consume all the available input to let some of the clamd + * threads blocked on send() to be dead. + * by doing so we shouldn't deadlock on the next recv() */ + fd_set rfds, wfds; + FD_ZERO(&rfds); + FD_SET(c->sockd, &rfds); + FD_ZERO(&wfds); + FD_SET(c->sockd, &wfds); + if(select(c->sockd + 1, &rfds, &wfds, NULL, NULL) < 0) { + if(errno == EINTR) continue; + c->errors++; + free(filename); + logg("!select failed during session\n"); + return CL_BREAK; /* this is an hard failure */ + } + if(FD_ISSET(c->sockd, &rfds)) { + if(dspresult(c)) { + free(filename); + return CL_BREAK; + } else continue; + } + if(FD_ISSET(c->sockd, &wfds)) break; + } + + while (*id) + id = &((*id)->next); + cid = (struct SCANID *)malloc(sizeof(struct SCANID *)); + *id = cid; + cid->id = ++c->lastid; + cid->file = filename; + cid->next = NULL; + + switch(c->scantype) { + case FILDES: + send_fdpass(c->sockd, filename); /* FIXME: check return */ + break; + case STREAM: + send_stream(c->sockd, filename); /* FIXME: check return */ + break; + } + + return CL_SUCCESS; +} + +/* Non-IDSESSION handler + * FIXME: returns what ? */ +int parallel_client_scan(const char *file, int scantype, int *infected, int *errors, int maxlevel) { + struct cli_ftw_cbdata data; + struct client_parallel_data cdata; + + if((cdata.sockd = dconnect()) < 0) + return 1; + + if(sendln(cdata.sockd, "zIDSESSION", 11)) { + close(cdata.sockd); + return 1; + } + + cdata.infected = 0; + cdata.errors = 0; + cdata.scantype = scantype; + cdata.spam = 0; + cdata.lastid = 0; + data.data = &cdata; + + cli_ftw(file, CLI_FTW_STD, maxlevel ? maxlevel : INT_MAX, parallel_callback, &data); + /* FIXME: check return */ + if(!cdata.infected && (!cdata.errors || cdata.spam)) logg("~%s: OK\n", file); + + /* FIXME: call dspresult in a loop here */ + + *infected += cdata.infected; + *errors += cdata.errors; + return 0; +} diff --git a/clamdscan/proto.h b/clamdscan/proto.h new file mode 100644 index 000000000..d096e5d38 --- /dev/null +++ b/clamdscan/proto.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2009 Sourcefire, Inc. + * + * Authors: Tomasz Kojm, aCaB + * + * 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. + * + * 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 + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#ifndef PROTO_H +#define PROTO_H +struct RCVLN { + char buf[PATH_MAX+1024]; /* FIXME must match that in clamd - bb1349 */ + int sockd; + int r; + char *cur; + char *bol; +}; + +int dconnect(void); +int sendln(int sockd, const char *line, unsigned int len); +void recvlninit(struct RCVLN *s, int sockd); +int recvln(struct RCVLN *s, char **rbol, char **reol); +int serial_client_scan(const char *file, int scantype, int *infected, int *errors, int maxlevel); +int parallel_client_scan(const char *file, int scantype, int *infected, int *errors, int maxlevel); +int dsresult(int sockd, int scantype, const char *filename); +#endif