分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow
也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!
刺猬@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,再加上同名文件的处理问题,有时候当你电脑成病毒窝的时候,可能病毒很多,统统转移到一个目录下面的时候造成同名冲突的可能性比较打,为了避免覆盖,这里就加上后缀. 这个好像很普遍的处理办法吧....