您的位置:首页 > 其它

Clamav杀毒软件源码分析笔记[十]

2008-10-13 21:13 477 查看
Clamav杀毒软件源码分析笔记[十]
刺猬@http://blog.csdn.net/littlehedgehog

[客户端处理]
服务端已经把主要的工作都已经处理的差不多了,剩下来也就是服务端等待客户端提出请求,然后根据客户端的请求做相应的工作. 所以客户端所做的事情就只是提交数据,然后坐享其成.服务端是在垂帘听政呢,还是做幕后英雄?话不多说了,下面进入正题.
客户端(clamdscan)的主函数main和clamscan其实用的是同一个main,当然里面的内容同样还是处理命令行,配置文件等相关信息,乏善可陈,经过这系列处理之后,我们来到了client这个客户端的逻辑处理主函数.但是我还是不准备详细来说这个函数,因为它除了因为客户端只是创建socket和使用connect对服务端进行连接而不需要啥绑定,监听,实在和服务端的创建没多少代码上的区别. 只是在连接的时候,我们要分配置文件里是设定的本地连接还是网络连接. 如下所示:
/* 创建套间字 并建立链接*/
int dconnect(const struct optstruct *opt)
{
struct sockaddr_un server; //这里定义了两个地址,注意一个为AF_UNIX,而另一个为AF_INET
struct sockaddr_in server2;
struct hostent *he;
struct cfgstruct *copt, *cpt;
const char *clamav_conf = getargl(opt, "config-file"); //从config-file中获取配置文件名
int sockd;

if (!clamav_conf)
clamav_conf = DEFAULT_CFG;

if ((copt = parsecfg(clamav_conf, 1)) == NULL)
{
mprintf("@Can't parse the configuration file./n");
return -1;
}

memset((char *) &server, 0, sizeof(server));
memset((char *) &server2, 0, sizeof(server2));

/* Set default address to connect to 这里默认是设置为本地地址*/
server2.sin_addr.s_addr = inet_addr("127.0.0.1");

if (cfgopt(copt, "TCPSocket") && cfgopt(copt, "LocalSocket")) //TCPSocket 和 LocalSocket 只能配置一个
{
mprintf("@Clamd is not configured properly./n");
return -1;
}
else if ((cpt = cfgopt(copt, "LocalSocket"))) //本地socket,也就是用AF_UNIX地址域啦
{

server.sun_family = AF_UNIX;
strncpy(server.sun_path, cpt->strarg, sizeof(server.sun_path));

if ((sockd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0)
{
perror("socket()");
mprintf("@Can't create the socket./n");
return -1;
}

if (connect(sockd, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0)
{
close(sockd);
perror("connect()");
mprintf("@Can't connect to clamd./n");
return -1;
}

}
else if ((cpt = cfgopt(copt, "TCPSocket")))
{

if ((sockd = socket(SOCKET_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket()");
mprintf("@Can't create the socket./n");
return -1;
}

server2.sin_family = AF_INET;
server2.sin_port = htons(cpt->numarg);

if ((cpt = cfgopt(copt, "TCPAddr")))
{
if ((he = gethostbyname(cpt->strarg)) == 0) //这里默认的地址其实是127.0.0.1 还是本地的
{
close(sockd);
perror("gethostbyname()");
mprintf("@Can't lookup clamd hostname./n");
return -1;
}
server2.sin_addr = *(struct in_addr *) he->h_addr_list[0]; //确实可能存在一个主机对应多个IP地址,比如多个网卡,这里只取第一个了
}

if (connect(sockd, (struct sockaddr *) &server2, sizeof(struct sockaddr_in)) < 0)
{
close(sockd);
perror("connect()");
mprintf("@Can't connect to clamd./n");
return -1;
}

}
else
{
mprintf("@Clamd is not configured properly./n");
return -1;
}

return sockd;
}

建立完连接之后,我们要做得就是提交各种任务给服务端让它做了,不能让它手下一大批线程给闲着.这里我们还是依照书上内容,看看stream扫描:
else if (!strcmp(opt->filename, "-")) /* scan data from stdin 这个是作者的规定,哈哈我也没办法 比如cat testfile | clamscan - 后面加一个"-"表明从stdin获得数据流 所以这里用了cat的输出作为扫描的输入 */
{
if ((sockd = dconnect(opt)) < 0)
return 2;

if ((ret = dsstream(sockd, opt)) >= 0)
*infected += ret;
else
errors++;

close(sockd);
}

stream的扫描就是在命令行里面加上一个'-', 利用linux强大的重定向功能,我们把文件的内容作为数据流传给服务端.

/* 把文件标准输入流通过临时套间字传递给服务端扫描
* 整个过程如下:
* 1 通知服务端我们要传数据流过来
* 2 服务端分配临时端口,这个端口是为了接收我们的数据流设定的
* 3 传数据流过去呗
*/
int dsstream(int sockd, const struct optstruct *opt)
{
int wsockd, loopw = 60, bread, port, infected = 0;
struct sockaddr_in server;
struct sockaddr_in peer;
int peer_size;
char buff[4096], *pt;

//这里告诉服务端我们传的是数据流
if (write(sockd, "STREAM", 6) <= 0)
{
mprintf("@Can't write to the socket./n");
return 2;
}

memset(buff, 0, sizeof(buff));
/* 读取服务端传过来的端口号 */
while (loopw)
{
read(sockd, buff, sizeof(buff));
if ((pt = strstr(buff, "PORT")))
{
pt += 5;
sscanf(pt, "%d", &port); //注意sscanf用法
break;
}
loopw--;
}

if (!loopw)
{
mprintf("@Daemon not ready for stream scanning./n");
return -1;
}

/* connect to clamd */

if ((wsockd = socket(SOCKET_INET, SOCK_STREAM, 0)) < 0)
{
perror("socket()");
mprintf("@Can't create the socket./n");
return -1;
}

server.sin_family = AF_INET;
server.sin_port = htons(port);

/* 通过socket获取对方信息*/
peer_size = sizeof(peer);
if (getpeername(sockd, (struct sockaddr *) &peer, &peer_size) < 0)
{
perror("getpeername()");
mprintf("@Can't get socket peer name./n");
return -1;
}

/*看看对方在哪里,是本地还是外面的...*/
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:
mprintf("@Unexpected socket type: %d./n", peer.sin_family);
return -1;
}

if (connect(wsockd, (struct sockaddr *) &server, sizeof(struct sockaddr_in)) < 0)
{
close(wsockd);
perror("connect()");
mprintf("@Can't connect to clamd [port: %d]./n", port);
return -1;
}
/* 从标准输入读取数据流然后写入到socket 服务端自会接应*/
while ((bread = read(0, buff, sizeof(buff))) > 0)
{
if (write(wsockd, buff, bread) <= 0)
{
mprintf("@Can't write to the socket./n");
close(wsockd);
return -1;
}
}
close(wsockd);

memset(buff, 0, sizeof(buff));
/* 下面是服务端的反馈消息 */
while ((bread = read(sockd, buff, sizeof(buff))) > 0)
{
mprintf("%s", buff);
if (strstr(buff, "FOUND/n"))
{
infected++;
logg("%s", buff);

}
else if (strstr(buff, "ERROR/n"))
{
logg("%s", buff);
return -1;
}
memset(buff, 0, sizeof(buff));
}

return infected;
}


这里有个小问题 在42行有句 if ((wsockd = socket(SOCKET_INET, SOCK_STREAM, 0)) < 0)我们直接定死了是SOCKET_INET,那如果服务端在本地怎么办呢? 看看67行的处理: server.sin_addr.s_addr = inet_addr("127.0.0.1"); 也就是如果是UNIX域就是用本地IP地址作为sockaddr的地址了.
如果我们扫描到了病毒,这里我们就需要转移病毒文件了,因为这只是个扫毒软件,不能杀毒:
/* 隔离病毒文件 说的比较通俗点儿就是转移目录,转移了...*/
void move_infected(const char *filename, const struct optstruct *opt)
{
char *movedir, *movefilename, *tmp, numext[4 + 1];
struct stat fstat, mfstat;
int n, len, movefilename_size;
struct utimbuf ubuf;

if (!(movedir = getargl(opt, "move"))) //文件隔离的目录
{
/* Should never reach here */
mprintf("@getargc() returned NULL/n", filename);
notmoved++;
return;
}

/* 检查调用进程是否可以对指定的文件执行某种操作 */
if (access(movedir, W_OK|X_OK) == -1)
{
mprintf("@error moving file '%s': cannot write to '%s': %s/n", filename, movedir, strerror(errno));
notmoved++;
return;
}

/* 查看病毒文件的stat */
if (stat(filename, &fstat) == -1)
{
mprintf("@Can't stat file %s/n", filename);
mprintf("Try to run clamdscan with clamd privileges/n");
notmoved++;
return;
}

if (!(tmp = strrchr(filename, '/')))
tmp = (char *) filename;

/* numext 是指如果病毒文件在隔离目录下面有同名文件,那我们要加后缀,比如有个virus文件,那就在后面加上一些字符串,避免同一文件夹下同名文件冲突,这个就是后缀预留空间 */
movefilename_size = sizeof(char) * (strlen(movedir) + strlen(tmp) + sizeof(numext) + 2);

if (!(movefilename = mmalloc(movefilename_size)))
{
mprintf("@Memory allocation error/n");
exit(2);
}

if (!(strrcpy(movefilename, movedir)))
{
mprintf("@strrcpy() returned NULL/n");
notmoved++;
free(movefilename);
return;
}

/* "路径名/" */
strcat(movefilename, "/");
/* "路径名/文件名" */
if (!(strcat(movefilename, tmp)))
{
mprintf("@strcat() returned NULL/n");
notmoved++;
free(movefilename);
return;
}

/* 这里我们用完整的路径名试探,看看这个文件存在不,呃,就这样吧,不知道怎么表达了 */
if (!stat(movefilename, &mfstat))
{
if (fstat.st_ino == mfstat.st_ino) /* It's the same file 通过inode号检验确实是同一个文件,注意文件名相同不能判定同一文件 */
{
mprintf("File excluded '%s'/n", filename);
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++); //右边对齐 最后格式是: 病毒名.032
strcat(movefilename, numext); //加后缀
}
while (!stat(movefilename, &mfstat) && (n < 1000)); //这里只试探1000次,我想应该够了吧,除非你PC成了毒窝了...
}
}
/* 两种方式来处理 第一个就是改名字,其实文件都在磁盘,具体路径还不是我们自己设定而已,所以改名就能转移目录*/
if (rename(filename, movefilename) == -1)
{
if (filecopy(filename, movefilename) == -1) //第二种方法就是直接拷贝了...
{
mprintf("@cannot move '%s' to '%s': %s/n", filename, movefilename, strerror(errno));
notmoved++;
free(movefilename);
return;
}

/* 下面由于我们是新建一个文件,然后拷贝原文件内容,所以文件权限和时间属性都变了,这里要改回来*/
chmod(movefilename, fstat.st_mode);
chown(movefilename, fstat.st_uid, fstat.st_gid);

ubuf.actime = fstat.st_atime;
ubuf.modtime = fstat.st_mtime;
utime(movefilename, &ubuf);

if (unlink(filename)) //删除原文件
{
mprintf("@cannot unlink '%s': %s/n", filename, strerror(errno));
notremoved++;
free(movefilename);
return;
}
}

mprintf("%s: moved to '%s'/n", filename, movefilename);
logg("%s: moved to '%s'/n", filename, movefilename);

free(movefilename);
}

转移主要就是一个copy,再加上同名文件的处理问题,有时候当你电脑成病毒窝的时候,可能病毒很多,统统转移到一个目录下面的时候造成同名冲突的可能性比较打,为了避免覆盖,这里就加上后缀. 这个好像很普遍的处理办法吧....
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: