clamdscan/client.c
e3aaff8e
 /*
c442ca9c
  *  Copyright (C) 2013-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
  *  Copyright (C) 2009-2013 Sourcefire, Inc.
f6870133
  *
  *  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>
cfef445d
 #ifdef HAVE_SYS_LIMITS_H
 #include <sys/limits.h>
 #endif
be4bf7f4
 #ifdef HAVE_SYS_SELECT_H
befa580a
 #include <sys/select.h>
be4bf7f4
 #endif
4cd80898
 #ifndef _WIN32
 #include <sys/socket.h>
e3aaff8e
 #include <sys/un.h>
 #include <netinet/in.h>
 #include <arpa/inet.h>
f8f80da9
 #include <netdb.h>
63abd169
 #include <utime.h>
4cd80898
 #endif
63abd169
 #include <errno.h>
87066fbc
 #include <dirent.h>
07e8d00f
 #include <fcntl.h>
e3aaff8e
 
7708ddfc
 #ifdef HAVE_SYS_UIO_H
 #include <sys/uio.h>
 #endif
 
60d8d2c3
 #include "libclamav/clamav.h"
a68d5e2f
 #include "shared/optparser.h"
 #include "shared/output.h"
 #include "shared/misc.h"
ee6702ab
 #include "shared/actions.h"
3507891f
 #include "shared/clamdcom.h"
 
a68d5e2f
 #include "libclamav/str.h"
6630b2a9
 #include "libclamav/others.h"
a68d5e2f
 
fc83da82
 #include "client.h"
f6870133
 #include "proto.h"
e3aaff8e
 
9877f2d8
 unsigned long int maxstream;
4cd80898
 #ifndef _WIN32
f0e1c2e6
 struct sockaddr_un nixsock;
4cd80898
 #endif
247fe3ae
 extern struct optstruct *clamdopts;
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;
1c1d726d
     char *ipaddr, port[10];
     struct addrinfo hints, *info, *p;
     int res;
fe6c6a02
 
6df13d04
     UNUSEDPARAM(opts);
 
be4bf7f4
 #ifndef _WIN32
e9896728
     if((opt = optget(clamdopts, "LocalSocket"))->enabled) {
8c241c57
         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';
         return 0;
7062124c
     }
be4bf7f4
 #endif
247fe3ae
     if(!(opt = optget(clamdopts, "TCPSocket"))->enabled)
8c241c57
         return 0;
247fe3ae
 
1c1d726d
     snprintf(port, sizeof(port), "%lld", optget(clamdopts, "TCPSocket")->numarg);
7a997ac9
 
8c241c57
     opt = optget(clamdopts, "TCPAddr");
     while (opt) {
6432bbfb
         ipaddr = NULL;
         if (opt->strarg)
1c1d726d
             ipaddr = (!strcmp(opt->strarg, "any") ? NULL : opt->strarg);
 
         memset(&hints, 0x00, sizeof(struct addrinfo));
         hints.ai_family = AF_UNSPEC;
         hints.ai_socktype = SOCK_STREAM;
         hints.ai_flags = AI_PASSIVE;
 
         if ((res = getaddrinfo(ipaddr, port, &hints, &info))) {
             logg("!Can't lookup clamd hostname: %s\n", gai_strerror(res));
6432bbfb
             opt = opt->nextarg;
1c1d726d
             continue;
8c241c57
         }
 
1c1d726d
         for (p = info; p != NULL; p = p->ai_next) {
             if((s = socket(p->ai_family, p->ai_socktype, p->ai_protocol)) < 0) {
                 logg("isremote: socket() returning: %s.\n", strerror(errno));
                 continue;
             }
 
f0e1c2e6
             switch (p->ai_family) {
             case AF_INET:
                 ((struct sockaddr_in *)(p->ai_addr))->sin_port = htons(INADDR_ANY);
                 break;
             case AF_INET6:
                 ((struct sockaddr_in6 *)(p->ai_addr))->sin6_port = htons(INADDR_ANY);
                 break;
             default:
                 break;
             }
 
             ret = bind(s, p->ai_addr, p->ai_addrlen);
1c1d726d
             if (ret) {
f0e1c2e6
                 if (errno == EADDRINUSE) {
                     /* 
                      * If we can't bind, then either we're attempting to listen on an IP that isn't
                      * ours or that clamd is already listening on.
                      */
                     closesocket(s);
                     freeaddrinfo(info);
                     return 0;
                 }
 
                 closesocket(s);
1c1d726d
                 freeaddrinfo(info);
                 return 1;
             }
f0e1c2e6
 
             closesocket(s);
8c241c57
         }
 
1c1d726d
         freeaddrinfo(info);
8c241c57
 
6432bbfb
         opt = opt->nextarg;
ae8f72fc
     }
1c1d726d
 
f0e1c2e6
     return 0;
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
     }
58481352
     if(!cli_is_abspath(basepath)) {
befa580a
 	if(!getcwd(ret, PATH_MAX)) {
0ae41a2d
 	    logg("^Can't get absolute pathname of current working directory.\n");
befa580a
 	    free(ret);
fe6c6a02
 	    return NULL;
 	}
6df3bfc3
 #ifdef _WIN32
 	if(*basepath == '\\') {
 	    namelen = 2;
 	    basepath++;
 	} else
 #endif
befa580a
 	namelen = strlen(ret);
58481352
 	snprintf(&ret[namelen], PATH_MAX - namelen, PATHSEP"%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 */
dd5092ff
 static int client_scan(const char *file, int scantype, int *infected, int *err, int maxlevel, int session, int flags) {
befa580a
     int ret;
     char *fullpath = makeabs(file);
 
fadd3046
     if(!fullpath)
 	return 0;
befa580a
     if (!session)
dd5092ff
 	ret = serial_client_scan(fullpath, scantype, infected, err, maxlevel, flags);
befa580a
     else
dd5092ff
 	ret = parallel_client_scan(fullpath, scantype, infected, err, 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((sockd = dconnect()) < 0) return 2;
00f05166
     recvlninit(&rcv, sockd);
2a363377
 
a659e393
     if(sendln(sockd, "zVERSION", 9)) {
081f6473
 	closesocket(sockd);
2a363377
 	return 2;
     }
 
a659e393
     while((len = recvln(&rcv, &buff, NULL))) {
 	if(len == -1) {
7cd9337a
 	    logg("!Error occurred while receiving version information.\n");
a659e393
 	    break;
 	}
2a363377
 	printf("%s\n", buff);
     }
 
081f6473
     closesocket(sockd);
2a363377
     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((sockd = dconnect()) < 0) return 2;
00f05166
     recvlninit(&rcv, sockd);
c1c9d9f9
 
a659e393
     if(sendln(sockd, "zRELOAD", 8)) {
081f6473
 	closesocket(sockd);
c1c9d9f9
 	return 2;
     }
 
a659e393
     if(!(len = recvln(&rcv, &buff, NULL)) || len < 10 || memcmp(buff, "RELOADING", 9)) {
3bf21904
 	logg("!Clamd did not reload the database\n");
081f6473
 	closesocket(sockd);
c1c9d9f9
 	return 2;
     }
081f6473
     closesocket(sockd);
c1c9d9f9
     return 0;
 }
 
dd5092ff
 int client(const struct optstruct *opts, int *infected, int *err)
fe6c6a02
 {
ee6702ab
 	int remote, scantype, session = 0, errors = 0, scandash = 0, maxrec, flags = 0;
c2b6681b
 	const char *fname;
3bb3357a
 
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;
6ad45a29
     } 
     else if(optget(opts, "multiscan")->enabled) scantype = MULTI;
     else if(optget(opts, "allmatch")->enabled) scantype = ALLMATCH;
07e8d00f
     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;
e3aaff8e
 
07e8d00f
     *infected = 0;
afb48b28
 
07e8bf35
     if(scandash) {
a659e393
 	int sockd, ret;
a2a004df
 	STATBUF sb;
eae1d361
 	if(FSTAT(0, &sb) < 0) {
 	    logg("client.c: fstat failed for file name \"%s\", with %s\n.", 
 		 opts->filename[0], strerror(errno));
 	    return 2;
 	}
b120dc19
 	if((sb.st_mode & S_IFMT) != S_IFREG) scantype = STREAM;
dd5092ff
 	if((sockd = dconnect()) >= 0 && (ret = dsresult(sockd, scantype, NULL, &ret, NULL)) >= 0)
2b68c490
 	    *infected = ret;
fe6c6a02
 	else
5b1eee09
 	    errors = 1;
081f6473
 	if(sockd >= 0) closesocket(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");
 
dd5092ff
 	while((fname = filelist(opts, NULL))) {
c2b6681b
 	    if(!strcmp(fname, "-")) {
07e8bf35
 		logg("!Scanning from standard input requires \"-\" to be the only file argument\n");
a659e393
 		continue;
fe6c6a02
 	    }
dd5092ff
 	    errors += client_scan(fname, scantype, infected, err, maxrec, session, flags);
 	    /* this may be too strict
 	    if(errors >= 10) {
 		logg("!Too many errors\n");
 		break;
 	    }
 	    */
e3aaff8e
 	}
07e8d00f
     } else {
dd5092ff
 	errors = client_scan("", scantype, infected, err, maxrec, session, flags);
e3aaff8e
     }
fe6c6a02
     return *infected ? 1 : (errors ? 2 : 0);
e3aaff8e
 }