e3aaff8e |
/* |
8ca8a18e |
* Copyright (C) 2002 - 2007 Tomasz Kojm <tkojm@clamav.net> |
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> |
63abd169 |
#include <unistd.h>
#include <string.h> |
e3aaff8e |
#include <sys/types.h> |
afb48b28 |
#include <sys/stat.h> |
e3aaff8e |
#include <sys/socket.h>
#include <sys/un.h>
#include <netinet/in.h>
#include <arpa/inet.h> |
f8f80da9 |
#include <netdb.h> |
63abd169 |
#include <utime.h>
#include <errno.h> |
e3aaff8e |
|
7708ddfc |
#ifdef HAVE_SYS_UIO_H
#include <sys/uio.h>
#endif
|
e3aaff8e |
#include "others.h"
#include "defaults.h" |
afb48b28 |
#include "options.h"
#include "cfgparser.h"
#include "output.h" |
63abd169 |
#include "misc.h" |
fe6c6a02 |
#include "str.h" |
fc83da82 |
#include "client.h" |
e3aaff8e |
|
b8cdcd2e |
#ifdef PF_INET
# define SOCKET_INET PF_INET
#else
# define SOCKET_INET AF_INET
#endif
|
63abd169 |
void move_infected(const char *filename, const struct optstruct *opt);
int notremoved = 0, notmoved = 0; |
fe6c6a02 |
|
079229d6 |
static int dsresult(int sockd, const struct optstruct *opt) |
fe6c6a02 |
{
int infected = 0, waserror = 0; |
7708ddfc |
char buff[4096], *pt; |
fe6c6a02 |
FILE *fd;
|
e871e527 |
#ifndef C_OS2 |
fe6c6a02 |
if((fd = fdopen(dup(sockd), "r")) == NULL) { |
e871e527 |
#else /* FIXME: accoriding to YD OS/2 does not support dup() for sockets */
if((fd = fdopen(sockd, "r")) == NULL) {
#endif |
0ae41a2d |
logg("^Can't open descriptor for reading.\n"); |
fe6c6a02 |
return -1;
}
while(fgets(buff, sizeof(buff), fd)) {
if(strstr(buff, "FOUND\n")) {
infected++;
logg("%s", buff); |
c6d2bbbc |
if(opt_check(opt, "move") || opt_check(opt, "copy")) { |
54c55863 |
/* filename: Virus FOUND */
if((pt = strrchr(buff, ':'))) {
*pt = 0;
move_infected(buff, opt);
} else { |
a3ae4847 |
logg("!Incorrect output from clamd. File not %s.\n", opt_check(opt, "move") ? "moved" : "copied"); |
54c55863 |
} |
63abd169 |
|
7b8edc5c |
} else if(opt_check(opt, "remove")) { |
54c55863 |
if(!(pt = strrchr(buff, ':'))) { |
a3ae4847 |
logg("!Incorrect output from clamd. File not removed.\n"); |
63abd169 |
} else { |
54c55863 |
*pt = 0;
if(unlink(buff)) { |
a3ae4847 |
logg("!%s: Can't remove.\n", buff); |
54c55863 |
notremoved++;
} else { |
5a3aeff4 |
logg("~%s: Removed.\n", buff); |
54c55863 |
} |
63abd169 |
}
} |
fe6c6a02 |
} |
7708ddfc |
|
63abd169 |
if(strstr(buff, "ERROR\n")) { |
a3ae4847 |
logg("~%s", buff); |
fe6c6a02 |
waserror = 1;
}
}
|
e871e527 |
#ifndef C_OS2 /* Small memory leak under OS/2 (see above) */ |
fe6c6a02 |
fclose(fd); |
e871e527 |
#endif |
fe6c6a02 |
|
7708ddfc |
return infected ? infected : (waserror ? -1 : 0);
}
|
8765287e |
static int dsfile(int sockd, const char *scantype, const char *filename, const struct optstruct *opt) |
7708ddfc |
{
int ret; |
6a31c2b4 |
char *scancmd; |
7708ddfc |
|
8ca8a18e |
scancmd = malloc(strlen(filename) + 20); |
8765287e |
sprintf(scancmd, "%s %s", scantype, filename); |
7708ddfc |
if(write(sockd, scancmd, strlen(scancmd)) <= 0) { |
0ae41a2d |
logg("^Can't write to the socket.\n"); |
7708ddfc |
free(scancmd);
return -1;
}
free(scancmd);
ret = dsresult(sockd, opt);
if(!ret) |
5a3aeff4 |
logg("~%s: OK\n", filename); |
fe6c6a02 |
|
7708ddfc |
return ret; |
fe6c6a02 |
}
|
079229d6 |
static int dsstream(int sockd, const struct optstruct *opt) |
fe6c6a02 |
{
int wsockd, loopw = 60, bread, port, infected = 0;
struct sockaddr_in server; |
040a5084 |
struct sockaddr_in peer; |
73b243dc |
socklen_t peer_size; |
fe6c6a02 |
char buff[4096], *pt;
|
7708ddfc |
|
fe6c6a02 |
if(write(sockd, "STREAM", 6) <= 0) { |
0ae41a2d |
logg("^Can't write to the socket.\n"); |
fe6c6a02 |
return 2;
}
while(loopw) { |
38fe8af4 |
memset(buff, 0, sizeof(buff)); |
73b243dc |
if(read(sockd, buff, sizeof(buff)) > 0) {
if((pt = strstr(buff, "PORT"))) {
pt += 5;
sscanf(pt, "%d", &port);
break;
} |
fe6c6a02 |
}
loopw--;
}
if(!loopw) { |
0ae41a2d |
logg("^Daemon not ready for stream scanning.\n"); |
fe6c6a02 |
return -1;
}
/* connect to clamd */
if((wsockd = socket(SOCKET_INET, SOCK_STREAM, 0)) < 0) {
perror("socket()"); |
0ae41a2d |
logg("^Can't create the socket.\n"); |
fe6c6a02 |
return -1;
}
server.sin_family = AF_INET;
server.sin_port = htons(port);
|
040a5084 |
peer_size = sizeof(peer);
if(getpeername(sockd, (struct sockaddr *) &peer, &peer_size) < 0) {
perror("getpeername()"); |
0ae41a2d |
logg("^Can't get socket peer name.\n"); |
040a5084 |
return -1;
}
|
7062124c |
switch (peer.sin_family) {
case AF_UNIX:
server.sin_addr.s_addr = inet_addr("127.0.0.1");
break;
case AF_INET:
server.sin_addr.s_addr = peer.sin_addr.s_addr;
break;
default: |
a3ae4847 |
logg("^Unexpected socket type: %d.\n", peer.sin_family); |
7062124c |
return -1;
} |
040a5084 |
|
fe6c6a02 |
if(connect(wsockd, (struct sockaddr *) &server, sizeof(struct sockaddr_in)) < 0) {
close(wsockd);
perror("connect()"); |
0ae41a2d |
logg("^Can't connect to clamd [port: %d].\n", port); |
fe6c6a02 |
return -1;
}
while((bread = read(0, buff, sizeof(buff))) > 0) {
if(write(wsockd, buff, bread) <= 0) { |
0ae41a2d |
logg("^Can't write to the socket.\n"); |
fe6c6a02 |
close(wsockd);
return -1;
}
}
close(wsockd);
memset(buff, 0, sizeof(buff));
while((bread = read(sockd, buff, sizeof(buff))) > 0) { |
0ae41a2d |
logg("%s", buff); |
fe6c6a02 |
if(strstr(buff, "FOUND\n")) {
infected++; |
63abd169 |
|
7708ddfc |
} else if(strstr(buff, "ERROR\n")) { |
fe6c6a02 |
logg("%s", buff);
return -1;
}
memset(buff, 0, sizeof(buff));
}
return infected;
}
|
079229d6 |
static char *abpath(const char *filename) |
fe6c6a02 |
{
struct stat foo;
char *fullpath, cwd[200];
if(stat(filename, &foo) == -1) { |
0ae41a2d |
logg("^Can't access file %s\n", filename); |
fe6c6a02 |
perror(filename);
return NULL;
} else { |
8ca8a18e |
fullpath = malloc(200 + strlen(filename) + 10); |
fe6c6a02 |
#ifdef C_CYGWIN
sprintf(fullpath, "%s", filename);
#else
if(!getcwd(cwd, 200)) { |
0ae41a2d |
logg("^Can't get absolute pathname of current working directory.\n"); |
fe6c6a02 |
return NULL;
}
sprintf(fullpath, "%s/%s", cwd, filename);
#endif
}
return fullpath;
}
|
079229d6 |
static int dconnect(const struct optstruct *opt) |
e3aaff8e |
{
struct sockaddr_un server; |
fe6c6a02 |
struct sockaddr_in server2; |
f8f80da9 |
struct hostent *he; |
73b243dc |
struct cfgstruct *copt;
const struct cfgstruct *cpt; |
7b8edc5c |
const char *clamav_conf = opt_arg(opt, "config-file"); |
fe6c6a02 |
int sockd;
|
e3aaff8e |
if(!clamav_conf)
clamav_conf = DEFAULT_CFG;
|
81837459 |
if((copt = getcfg(clamav_conf, 1)) == NULL) { |
0ae41a2d |
logg("^Can't parse the configuration file.\n"); |
fe6c6a02 |
return -1; |
e3aaff8e |
}
|
75ccac9f |
memset((char *) &server, 0, sizeof(server));
memset((char *) &server2, 0, sizeof(server2));
|
fe6c6a02 |
/* Set default address to connect to */ |
f6f5d56f |
server2.sin_addr.s_addr = inet_addr("127.0.0.1"); |
e3aaff8e |
|
57358cc8 |
if((cpt = cfgopt(copt, "LocalSocket"))->enabled) { |
e3aaff8e |
server.sun_family = AF_UNIX; |
658f19f8 |
strncpy(server.sun_path, cpt->strarg, sizeof(server.sun_path)); |
e3aaff8e |
if((sockd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) {
perror("socket()"); |
0ae41a2d |
logg("^Can't create the socket.\n"); |
8765287e |
freecfg(copt); |
fe6c6a02 |
return -1; |
e3aaff8e |
}
if(connect(sockd, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
close(sockd);
perror("connect()"); |
0ae41a2d |
logg("^Can't connect to clamd.\n"); |
8765287e |
freecfg(copt); |
fe6c6a02 |
return -1; |
e3aaff8e |
}
|
81837459 |
} else if((cpt = cfgopt(copt, "TCPSocket"))->enabled) { |
e3aaff8e |
|
b8cdcd2e |
if((sockd = socket(SOCKET_INET, SOCK_STREAM, 0)) < 0) { |
e3aaff8e |
perror("socket()"); |
0ae41a2d |
logg("^Can't create the socket.\n"); |
8765287e |
freecfg(copt); |
fe6c6a02 |
return -1; |
e3aaff8e |
}
server2.sin_family = AF_INET;
server2.sin_port = htons(cpt->numarg);
|
81837459 |
if((cpt = cfgopt(copt, "TCPAddr"))->enabled) { |
f8f80da9 |
if ((he = gethostbyname(cpt->strarg)) == 0) {
close(sockd);
perror("gethostbyname()"); |
0ae41a2d |
logg("^Can't lookup clamd hostname.\n"); |
8765287e |
freecfg(copt); |
fe6c6a02 |
return -1; |
f8f80da9 |
}
server2.sin_addr = *(struct in_addr *) he->h_addr_list[0]; |
f6f5d56f |
} |
f8f80da9 |
|
e3aaff8e |
if(connect(sockd, (struct sockaddr *) &server2, sizeof(struct sockaddr_in)) < 0) {
close(sockd);
perror("connect()"); |
0ae41a2d |
logg("^Can't connect to clamd.\n"); |
8765287e |
freecfg(copt); |
fe6c6a02 |
return -1; |
e3aaff8e |
}
} else { |
0ae41a2d |
logg("^Clamd is not configured properly.\n"); |
8765287e |
freecfg(copt); |
fe6c6a02 |
return -1; |
e3aaff8e |
}
|
8765287e |
freecfg(copt);
|
fe6c6a02 |
return sockd;
} |
e3aaff8e |
|
2a363377 |
int get_clamd_version(const struct optstruct *opt)
{
char buff[64];
int bread, sockd;
if((sockd = dconnect(opt)) < 0)
return 2;
if(write(sockd, "VERSION", 7) <= 0) {
logg("^Can't write to the socket.\n");
return 2;
}
while((bread = read(sockd, buff, sizeof(buff)-1)) > 0) {
buff[bread] = '\0';
printf("%s\n", buff);
}
close(sockd);
return 0;
}
|
fe6c6a02 |
int client(const struct optstruct *opt, int *infected)
{
char cwd[200], *fullpath;
int sockd, ret, errors = 0;
struct stat sb; |
402abedd |
const char *scantype = "CONTSCAN"; |
e3aaff8e |
|
fe6c6a02 |
*infected = 0; |
e3aaff8e |
|
09eab32a |
if(opt_check(opt, "multiscan")) |
3bb3357a |
scantype = "MULTISCAN";
|
8765287e |
/* parse argument list */ |
fe6c6a02 |
if(opt->filename == NULL || strlen(opt->filename) == 0) {
/* scan current directory */
if(!getcwd(cwd, 200)) { |
0ae41a2d |
logg("^Can't get absolute pathname of current working directory.\n"); |
e3aaff8e |
return 2;
}
|
fe6c6a02 |
if((sockd = dconnect(opt)) < 0) |
e3aaff8e |
return 2;
|
8765287e |
if((ret = dsfile(sockd, scantype, cwd, opt)) >= 0) |
fe6c6a02 |
*infected += ret;
else
errors++; |
e3aaff8e |
|
fe6c6a02 |
close(sockd); |
afb48b28 |
|
fe6c6a02 |
} else if(!strcmp(opt->filename, "-")) { /* scan data from stdin */
if((sockd = dconnect(opt)) < 0) |
e3aaff8e |
return 2;
|
63abd169 |
if((ret = dsstream(sockd, opt)) >= 0) |
fe6c6a02 |
*infected += ret;
else
errors++; |
e3aaff8e |
close(sockd);
|
fe6c6a02 |
} else {
int x;
char *thefilename;
for (x = 0; (thefilename = cli_strtok(opt->filename, x, "\t")) != NULL; x++) {
fullpath = thefilename;
if(stat(fullpath, &sb) == -1) { |
0ae41a2d |
logg("^Can't access file %s\n", fullpath); |
fe6c6a02 |
perror(fullpath);
errors++;
} else { |
9470856d |
if(strcmp(fullpath, "/") && (strlen(fullpath) < 2 || (fullpath[0] != '/' && fullpath[0] != '\\' && fullpath[1] != ':'))) { |
fe6c6a02 |
fullpath = abpath(thefilename);
free(thefilename);
if(!fullpath) { |
0ae41a2d |
logg("^Can't determine absolute path.\n"); |
fe6c6a02 |
return 2;
}
}
switch(sb.st_mode & S_IFMT) {
case S_IFREG:
case S_IFDIR:
if((sockd = dconnect(opt)) < 0)
return 2;
|
8765287e |
if((ret = dsfile(sockd, scantype, fullpath, opt)) >= 0) |
fe6c6a02 |
*infected += ret;
else
errors++;
close(sockd);
break;
default: |
0ae41a2d |
logg("^Not supported file type (%s)\n", fullpath); |
fe6c6a02 |
errors++;
}
} |
e3aaff8e |
|
fe6c6a02 |
free(fullpath); |
e3aaff8e |
}
}
|
fe6c6a02 |
return *infected ? 1 : (errors ? 2 : 0); |
e3aaff8e |
} |
63abd169 |
void move_infected(const char *filename, const struct optstruct *opt)
{ |
73b243dc |
char *movedir, *movefilename, numext[4 + 1];
const char *tmp;
struct stat ofstat, mfstat; |
63abd169 |
int n, len, movefilename_size; |
c6d2bbbc |
int moveflag = opt_check(opt, "move"); |
63abd169 |
struct utimbuf ubuf;
|
c6d2bbbc |
if((moveflag && !(movedir = opt_arg(opt, "move"))) ||
(!moveflag && !(movedir = opt_arg(opt, "copy")))) { |
63abd169 |
/* Should never reach here */ |
7b8edc5c |
logg("^opt_arg() returned NULL\n"); |
63abd169 |
notmoved++;
return;
}
if(access(movedir, W_OK|X_OK) == -1) { |
c6d2bbbc |
logg("^error %s file '%s': cannot write to '%s': %s\n", (moveflag) ? "moving" : "copying", filename, movedir, strerror(errno)); |
63abd169 |
notmoved++;
return;
}
|
73b243dc |
if(stat(filename, &ofstat) == -1) { |
0ae41a2d |
logg("^Can't stat file %s\n", filename);
logg("Try to run clamdscan with clamd privileges\n"); |
63abd169 |
notmoved++;
return;
}
if(!(tmp = strrchr(filename, '/'))) |
73b243dc |
tmp = filename; |
63abd169 |
movefilename_size = sizeof(char) * (strlen(movedir) + strlen(tmp) + sizeof(numext) + 2);
|
8ca8a18e |
if(!(movefilename = malloc(movefilename_size))) { |
0ae41a2d |
logg("^Memory allocation error\n"); |
63abd169 |
exit(2);
}
|
06a48630 |
if(!(cli_strrcpy(movefilename, movedir))) {
logg("^cli_strrcpy() returned NULL\n"); |
63abd169 |
notmoved++;
free(movefilename);
return;
}
strcat(movefilename, "/");
if(!(strcat(movefilename, tmp))) { |
0ae41a2d |
logg("^strcat() returned NULL\n"); |
63abd169 |
notmoved++;
free(movefilename);
return;
}
if(!stat(movefilename, &mfstat)) { |
6eef8704 |
if((ofstat.st_dev == mfstat.st_dev) && (ofstat.st_ino == mfstat.st_ino)) { /* It's the same file*/ |
63abd169 |
logg("File excluded '%s'\n", filename);
notmoved++;
free(movefilename);
return;
} else {
/* file exists - try to append an ordinal number to the
* quranatined file in an attempt not to overwrite existing
* files in quarantine
*/
len = strlen(movefilename);
n = 0;
do {
/* reset the movefilename to it's initial value by
* truncating to the original filename length
*/
movefilename[len] = 0;
/* append .XXX */
sprintf(numext, ".%03d", n++);
strcat(movefilename, numext);
} while(!stat(movefilename, &mfstat) && (n < 1000));
}
}
|
c6d2bbbc |
if(!moveflag || rename(filename, movefilename) == -1) { |
63abd169 |
if(filecopy(filename, movefilename) == -1) { |
c6d2bbbc |
logg("^cannot %s '%s' to '%s': %s\n", (moveflag) ? "move" : "copy", filename, movefilename, strerror(errno)); |
63abd169 |
notmoved++;
free(movefilename);
return;
}
|
73b243dc |
chmod(movefilename, ofstat.st_mode);
if(chown(movefilename, ofstat.st_uid, ofstat.st_gid) == -1)
logg("^chown() failed for %s: %s\n", movefilename, strerror(errno)); |
63abd169 |
|
73b243dc |
ubuf.actime = ofstat.st_atime;
ubuf.modtime = ofstat.st_mtime; |
63abd169 |
utime(movefilename, &ubuf);
|
c6d2bbbc |
if(moveflag && unlink(filename)) { |
0ae41a2d |
logg("^cannot unlink '%s': %s\n", filename, strerror(errno)); |
63abd169 |
notremoved++;
free(movefilename);
return;
}
}
|
c6d2bbbc |
logg("%s: %s to '%s'\n", (moveflag)?"moved":"copied", filename, movefilename); |
63abd169 |
free(movefilename);
} |