clamdscan/client.c
e3aaff8e
 /*
f6870133
  *  Copyright (C) 2009 Sourcefire, Inc.
  *
  *  Authors: Tomasz Kojm, aCaB
e3aaff8e
  *
  *  This program is free software; you can redistribute it and/or modify
bb34cb31
  *  it under the terms of the GNU General Public License version 2 as
  *  published by the Free Software Foundation.
e3aaff8e
  *
  *  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
48b7b4a7
  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
  *  MA 02110-1301, USA.
e3aaff8e
  */
 
6d6e8271
 #if HAVE_CONFIG_H
 #include "clamav-config.h"
 #endif
 
e3aaff8e
 #include <stdio.h>
87066fbc
 #include <stdlib.h>
ab1ed22f
 #ifdef	HAVE_UNISTD_H
63abd169
 #include <unistd.h>
ab1ed22f
 #endif
63abd169
 #include <string.h>
e3aaff8e
 #include <sys/types.h>
afb48b28
 #include <sys/stat.h>
e3aaff8e
 #include <sys/socket.h>
cfef445d
 #ifdef HAVE_SYS_LIMITS_H
 #include <sys/limits.h>
 #endif
befa580a
 #include <sys/select.h>
e3aaff8e
 #include <sys/un.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
f8f80da9
 #include <netdb.h>
63abd169
 #include <utime.h>
 #include <errno.h>
87066fbc
 #include <dirent.h>
07e8d00f
 #include <fcntl.h>
e3aaff8e
 
7708ddfc
 #ifdef HAVE_SYS_UIO_H
 #include <sys/uio.h>
 #endif
 
a68d5e2f
 #include "shared/optparser.h"
 #include "shared/output.h"
 #include "shared/misc.h"
ee6702ab
 #include "shared/actions.h"
a68d5e2f
 #include "libclamav/str.h"
6630b2a9
 #include "libclamav/others.h"
a68d5e2f
 
fc83da82
 #include "client.h"
f6870133
 #include "proto.h"
e3aaff8e
 
07e8bf35
 #ifndef INADDR_LOOPBACK
 #define INADDR_LOOPBACK 0x7f000001
e871e527
 #endif
fe6c6a02
 
f6870133
 struct sockaddr *mainsa = NULL;
 int mainsasz;
9877f2d8
 unsigned long int maxstream;
a659e393
 static struct sockaddr_un nixsock;
 static struct sockaddr_in tcpsock;
7708ddfc
 
 
fadd3046
 /* Inits the communication layer
  * Returns 0 if clamd is local, non zero if clamd is remote */
a659e393
 static int isremote(const struct optstruct *opts) {
     int s, ret;
     const struct optstruct *opt;
     struct optstruct *clamdopts;
     const char *clamd_conf = optget(opts, "config-file")->strarg;
befa580a
     static struct sockaddr_in testsock;
fe6c6a02
 
a659e393
     if((clamdopts = optparse(clamd_conf, 0, NULL, 1, OPT_CLAMD, 0, NULL)) == NULL) {
 	logg("!Can't parse clamd configuration file %s\n", clamd_conf);
 	return 0;
040a5084
     }
e9896728
     if((opt = optget(clamdopts, "LocalSocket"))->enabled) {
a659e393
 	memset((void *)&nixsock, 0, sizeof(nixsock));
 	nixsock.sun_family = AF_UNIX;
 	strncpy(nixsock.sun_path, opt->strarg, sizeof(nixsock.sun_path));
 	nixsock.sun_path[sizeof(nixsock.sun_path)-1]='\0';
 	mainsa = (struct sockaddr *)&nixsock;
 	mainsasz = sizeof(nixsock);
 	optfree(clamdopts);
 	return 0;
7062124c
     }
a659e393
     if(!(opt = optget(clamdopts, "TCPSocket"))->enabled) {
 	optfree(clamdopts);
 	return 0;
fe6c6a02
     }
a659e393
     mainsa = (struct sockaddr *)&tcpsock;
     mainsasz = sizeof(tcpsock);
7a997ac9
 
     if (cfg_tcpsock(clamdopts, &tcpsock, INADDR_LOOPBACK) == -1) {
 	logg("!Can't lookup clamd hostname: %s.\n", strerror(errno));
a659e393
 	optfree(clamdopts);
 	mainsa = NULL;
 	return 0;
fe6c6a02
     }
7a997ac9
     optfree(clamdopts);
befa580a
     memcpy((void *)&testsock, (void *)&tcpsock, sizeof(testsock));
     testsock.sin_port = htons(INADDR_ANY);
     if(!(s = socket(testsock.sin_family, SOCK_STREAM, 0))) return 0;
     ret = (bind(s, (struct sockaddr *)&testsock, sizeof(testsock)) != 0);
a659e393
     close(s);
     return ret;
fe6c6a02
 }
 
cfef445d
 
fadd3046
 /* Turns a relative path into an absolute one
  * Returns a pointer to the path (which must be 
  * freed by the caller) or NULL on error */
befa580a
 static char *makeabs(const char *basepath) {
b1442ed1
     int namelen;
befa580a
     char *ret;
fe6c6a02
 
befa580a
     if(!(ret = malloc(PATH_MAX + 1))) {
 	logg("^Can't make room for fullpath.\n");
fe6c6a02
 	return NULL;
befa580a
     }
     if(*basepath != '/') { /* FIXME: to be unified */
 	if(!getcwd(ret, PATH_MAX)) {
0ae41a2d
 	    logg("^Can't get absolute pathname of current working directory.\n");
befa580a
 	    free(ret);
fe6c6a02
 	    return NULL;
 	}
befa580a
 	namelen = strlen(ret);
 	snprintf(&ret[namelen], PATH_MAX - namelen, "/%s", basepath);
e3aaff8e
     } else {
befa580a
 	strncpy(ret, basepath, PATH_MAX);
e3aaff8e
     }
befa580a
     ret[PATH_MAX] = '\0';
     return ret;
 }
e3aaff8e
 
fadd3046
 /* Recursively scans a path with the given scantype
  * Returns non zero for serious errors, zero otherwise */
5b1eee09
 static int client_scan(const char *file, int scantype, int *infected, int maxlevel, int session, int flags) {
befa580a
     int ret;
     char *fullpath = makeabs(file);
 
fadd3046
     if(!fullpath)
 	return 0;
befa580a
     if (!session)
5b1eee09
 	ret = serial_client_scan(fullpath, scantype, infected, maxlevel, flags);
befa580a
     else
5b1eee09
 	ret = parallel_client_scan(fullpath, scantype, infected, maxlevel, flags);
befa580a
     free(fullpath);
fadd3046
     return ret;
fe6c6a02
 }
e3aaff8e
 
a68d5e2f
 int get_clamd_version(const struct optstruct *opts)
2a363377
 {
a659e393
 	char *buff;
 	int len, sockd;
 	struct RCVLN rcv;
2a363377
 
a659e393
     isremote(opts);
     if(!mainsa) return 2;
     if((sockd = dconnect()) < 0) return 2;
00f05166
     recvlninit(&rcv, sockd);
2a363377
 
a659e393
     if(sendln(sockd, "zVERSION", 9)) {
c1c9d9f9
 	close(sockd);
2a363377
 	return 2;
     }
 
a659e393
     while((len = recvln(&rcv, &buff, NULL))) {
 	if(len == -1) {
 	    logg("!Error occoured while receiving version information.\n");
 	    break;
 	}
2a363377
 	printf("%s\n", buff);
     }
 
     close(sockd);
     return 0;
 }
 
a68d5e2f
 int reload_clamd_database(const struct optstruct *opts)
c1c9d9f9
 {
a659e393
 	char *buff;
 	int len, sockd;
 	struct RCVLN rcv;
c1c9d9f9
 
a659e393
     isremote(opts);
     if(!mainsa) return 2;
     if((sockd = dconnect()) < 0) return 2;
00f05166
     recvlninit(&rcv, sockd);
c1c9d9f9
 
a659e393
     if(sendln(sockd, "zRELOAD", 8)) {
c1c9d9f9
 	close(sockd);
 	return 2;
     }
 
a659e393
     if(!(len = recvln(&rcv, &buff, NULL)) || len < 10 || memcmp(buff, "RELOADING", 9)) {
3bf21904
 	logg("!Clamd did not reload the database\n");
c1c9d9f9
 	close(sockd);
 	return 2;
     }
     close(sockd);
     return 0;
 }
 
a68d5e2f
 int client(const struct optstruct *opts, int *infected)
fe6c6a02
 {
07e8d00f
 	const char *clamd_conf = optget(opts, "config-file")->strarg;
 	struct optstruct *clamdopts;
ee6702ab
 	int remote, scantype, session = 0, errors = 0, scandash = 0, maxrec, flags = 0;
c2b6681b
 	const char *fname;
3bb3357a
 
07e8d00f
     if((clamdopts = optparse(clamd_conf, 0, NULL, 1, OPT_CLAMD, 0, NULL)) == NULL) {
 	logg("!Can't parse clamd configuration file %s\n", clamd_conf);
 	return 2;
     }
e3aaff8e
 
c2b6681b
     scandash = (opts->filename && opts->filename[0] && !strcmp(opts->filename[0], "-") && !optget(opts, "file-list")->enabled && !opts->filename[1]);
408be01f
     remote = isremote(opts) | optget(opts, "stream")->enabled;
07e8d00f
 #ifdef HAVE_FD_PASSING
07e8bf35
     if(!remote && optget(clamdopts, "LocalSocket")->enabled && (optget(opts, "fdpass")->enabled || scandash)) {
07e8d00f
 	scantype = FILDES;
 	session = optget(opts, "multiscan")->enabled;
07e8bf35
     } else 
07e8d00f
 #endif
07e8bf35
     if(remote || scandash) {
 	scantype = STREAM;
 	session = optget(opts, "multiscan")->enabled;
07e8d00f
     } else if(optget(opts, "multiscan")->enabled) scantype = MULTI;
     else scantype = CONT;
e3aaff8e
 
b1442ed1
     maxrec = optget(clamdopts, "MaxDirectoryRecursion")->numarg;
83c52f7e
     maxstream = optget(clamdopts, "StreamMaxLength")->numarg;
ee6702ab
     if (optget(clamdopts, "FollowDirectorySymlinks")->enabled)
 	flags |= CLI_FTW_FOLLOW_DIR_SYMLINK;
     if (optget(clamdopts, "FollowFileSymlinks")->enabled)
 	flags |= CLI_FTW_FOLLOW_FILE_SYMLINK;
a1598d7c
     flags |= CLI_FTW_TRIM_SLASHES;
07e8d00f
     optfree(clamdopts);
e3aaff8e
 
07e8d00f
     if(!mainsa) {
 	logg("!Clamd is not configured properly.\n");
 	return 2;
     }
e3aaff8e
 
07e8d00f
     *infected = 0;
afb48b28
 
07e8bf35
     if(scandash) {
a659e393
 	int sockd, ret;
b120dc19
 	struct stat sb;
 	fstat(0, &sb);
 	if((sb.st_mode & S_IFMT) != S_IFREG) scantype = STREAM;
22446430
 	if((sockd = dconnect()) >= 0 && (ret = dsresult(sockd, scantype, NULL, &ret)) >= 0)
2b68c490
 	    *infected = ret;
fe6c6a02
 	else
5b1eee09
 	    errors = 1;
1d9da95b
 	if(sockd >= 0) close(sockd);
c2b6681b
     } else if(opts->filename || optget(opts, "file-list")->enabled) {
 	if(opts->filename && optget(opts, "file-list")->enabled)
 	    logg("^Only scanning files from --file-list (files passed at cmdline are ignored)\n");
 
 	while(!errors && (fname = filelist(opts, NULL))) {
 	    if(!strcmp(fname, "-")) {
07e8bf35
 		logg("!Scanning from standard input requires \"-\" to be the only file argument\n");
a659e393
 		continue;
fe6c6a02
 	    }
c2b6681b
 	    errors = client_scan(fname, scantype, infected, maxrec, session, flags);
e3aaff8e
 	}
07e8d00f
     } else {
5b1eee09
 	errors = client_scan("", scantype, infected, maxrec, session, flags);
e3aaff8e
     }
fe6c6a02
     return *infected ? 1 : (errors ? 2 : 0);
e3aaff8e
 }