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

来源:互联网 发布:mac装win10重新分区 编辑:程序博客网 时间:2024/04/25 14:12
Clamav杀毒软件源码分析笔记[十]


刺猬@http://blog.csdn.net/littlehedgehog





[客户端处理]


服务端已经把主要的工作都已经处理的差不多了,剩下来也就是服务端等待客户端提出请求,然后根据客户端的请求做相应的工作. 所以客户端所做的事情就只是提交数据,然后坐享其成.服务端是在垂帘听政呢,还是做幕后英雄?话不多说了,下面进入正题.


客户端(clamdscan)的主函数main和clamscan其实用的是同一个main,当然里面的内容同样还是处理命令行,配置文件等相关信息,乏善可陈,经过这系列处理之后,我们来到了client这个客户端的逻辑处理主函数.但是我还是不准备详细来说这个函数,因为它除了因为客户端只是创建socket和使用connect对服务端进行连接而不需要啥绑定,监听,实在和服务端的创建没多少代码上的区别. 只是在连接的时候,我们要分配置文件里是设定的本地连接还是网络连接. 如下所示:


  1. /* 创建套间字 并建立链接*/
  2. int dconnect(const struct optstruct *opt)
  3. {
  4.     struct sockaddr_un server;                              //这里定义了两个地址,注意一个为AF_UNIX,而另一个为AF_INET
  5.     struct sockaddr_in server2;
  6.     struct hostent *he;
  7.     struct cfgstruct *copt, *cpt;
  8.     const char *clamav_conf = getargl(opt, "config-file");  //从config-file中获取配置文件名
  9.     int sockd;


  10.     if (!clamav_conf)
  11.         clamav_conf = DEFAULT_CFG;

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

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

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

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

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

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

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

  43.     }
  44.     else if ((cpt = cfgopt(copt, "TCPSocket")))
  45.     {

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

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

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

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

  72.     }
  73.     else
  74.     {
  75.         mprintf("@Clamd is not configured properly./n");
  76.         return -1;
  77.     }

  78.     return sockd;
  79. }



建立完连接之后,我们要做得就是提交各种任务给服务端让它做了,不能让它手下一大批线程给闲着.这里我们还是依照书上内容,看看stream扫描:


  1.   else if (!strcmp(opt->filename, "-"))  /* scan data from stdin  这个是作者的规定,哈哈我也没办法 比如cat testfile | clamscan - 后面加一个"-"表明从stdin获得数据流 所以这里用了cat的输出作为扫描的输入 */
  2.     {
  3.         if ((sockd = dconnect(opt)) < 0)
  4.             return 2;
  5.         if ((ret = dsstream(sockd, opt)) >= 0)
  6.             *infected += ret;
  7.         else
  8.             errors++;
  9.         close(sockd);
  10.     }

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


  1. /* 把文件标准输入流通过临时套间字传递给服务端扫描 
  2.  * 整个过程如下:
  3.  * 1 通知服务端我们要传数据流过来
  4.  * 2 服务端分配临时端口,这个端口是为了接收我们的数据流设定的
  5.  * 3 传数据流过去呗
  6.  */
  7. int dsstream(int sockd, const struct optstruct *opt)
  8. {
  9.     int wsockd, loopw = 60, bread, port, infected = 0;
  10.     struct sockaddr_in server;
  11.     struct sockaddr_in peer;
  12.     int peer_size;
  13.     char buff[4096], *pt;
  14.     //这里告诉服务端我们传的是数据流
  15.     if (write(sockd, "STREAM", 6) <= 0)
  16.     {
  17.         mprintf("@Can't write to the socket./n");
  18.         return 2;
  19.     }
  20.     memset(buff, 0, sizeof(buff));
  21.     /* 读取服务端传过来的端口号 */
  22.     while (loopw)
  23.     {
  24.         read(sockd, buff, sizeof(buff));
  25.         if ((pt = strstr(buff, "PORT")))
  26.         {
  27.             pt += 5;
  28.             sscanf(pt, "%d", &port);    //注意sscanf用法
  29.             break;
  30.         }
  31.         loopw--;
  32.     }
  33.     if (!loopw)
  34.     {
  35.         mprintf("@Daemon not ready for stream scanning./n");
  36.         return -1;
  37.     }
  38.     /* connect to clamd */
  39.     if ((wsockd = socket(SOCKET_INET, SOCK_STREAM, 0)) < 0)
  40.     {
  41.         perror("socket()");
  42.         mprintf("@Can't create the socket./n");
  43.         return -1;
  44.     }
  45.     server.sin_family = AF_INET;
  46.     server.sin_port = htons(port);
  47.     
  48.     /* 通过socket获取对方信息*/
  49.     peer_size = sizeof(peer);
  50.     if (getpeername(sockd, (struct sockaddr *) &peer, &peer_size) < 0)
  51.     {
  52.         perror("getpeername()");
  53.         mprintf("@Can't get socket peer name./n");
  54.         return -1;
  55.     }
  56.     
  57.     /*看看对方在哪里,是本地还是外面的...*/
  58.     switch (peer.sin_family)
  59.     {
  60.     case AF_UNIX:
  61.         server.sin_addr.s_addr = inet_addr("127.0.0.1");
  62.         break;
  63.     case AF_INET:
  64.         server.sin_addr.s_addr = peer.sin_addr.s_addr;
  65.         break;
  66.     default:
  67.         mprintf("@Unexpected socket type: %d./n", peer.sin_family);
  68.         return -1;
  69.     }
  70.     if (connect(wsockd, (struct sockaddr *) &server, sizeof(struct sockaddr_in)) < 0)
  71.     {
  72.         close(wsockd);
  73.         perror("connect()");
  74.         mprintf("@Can't connect to clamd [port: %d]./n", port);
  75.         return -1;
  76.     }
  77.     /* 从标准输入读取数据流然后写入到socket 服务端自会接应*/
  78.     while ((bread = read(0, buff, sizeof(buff))) > 0)
  79.     {
  80.         if (write(wsockd, buff, bread) <= 0)
  81.         {
  82.             mprintf("@Can't write to the socket./n");
  83.             close(wsockd);
  84.             return -1;
  85.         }
  86.     }
  87.     close(wsockd);
  88.     memset(buff, 0, sizeof(buff));
  89.     /* 下面是服务端的反馈消息 */
  90.     while ((bread = read(sockd, buff, sizeof(buff))) > 0)
  91.     {
  92.         mprintf("%s", buff);
  93.         if (strstr(buff, "FOUND/n"))
  94.         {
  95.             infected++;
  96.             logg("%s", buff);
  97.         }
  98.         else if (strstr(buff, "ERROR/n"))
  99.         {
  100.             logg("%s", buff);
  101.             return -1;
  102.         }
  103.         memset(buff, 0, sizeof(buff));
  104.     }
  105.     return infected;
  106. }
 

这里有个小问题 在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的地址了.


如果我们扫描到了病毒,这里我们就需要转移病毒文件了,因为这只是个扫毒软件,不能杀毒:


  1. /* 隔离病毒文件   说的比较通俗点儿就是转移目录,转移了...*/
  2. void move_infected(const char *filename, const struct optstruct *opt)
  3. {
  4.     char *movedir, *movefilename, *tmp, numext[4 + 1];
  5.     struct stat fstat, mfstat;
  6.     int n, len, movefilename_size;
  7.     struct utimbuf ubuf;


  8.     if (!(movedir = getargl(opt, "move")))      //文件隔离的目录
  9.     {
  10.         /* Should never reach here */
  11.         mprintf("@getargc() returned NULL/n", filename);
  12.         notmoved++;
  13.         return;
  14.     }
  15.     
  16.     /* 检查调用进程是否可以对指定的文件执行某种操作 */
  17.     if (access(movedir, W_OK|X_OK) == -1)
  18.     {
  19.         mprintf("@error moving file '%s': cannot write to '%s': %s/n", filename, movedir, strerror(errno));
  20.         notmoved++;
  21.         return;
  22.     }
  23.     
  24.     /* 查看病毒文件的stat */
  25.     if (stat(filename, &fstat) == -1)
  26.     {
  27.         mprintf("@Can't stat file %s/n", filename);
  28.         mprintf("Try to run clamdscan with clamd privileges/n");
  29.         notmoved++;
  30.         return;
  31.     }

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

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

  36.     if (!(movefilename = mmalloc(movefilename_size)))
  37.     {
  38.         mprintf("@Memory allocation error/n");
  39.         exit(2);
  40.     }

  41.     if (!(strrcpy(movefilename, movedir)))
  42.     {
  43.         mprintf("@strrcpy() returned NULL/n");
  44.         notmoved++;
  45.         free(movefilename);
  46.         return;
  47.     }
  48.     
  49.     /* "路径名/" */
  50.     strcat(movefilename, "/");
  51.     /* "路径名/文件名" */
  52.     if (!(strcat(movefilename, tmp)))
  53.     {
  54.         mprintf("@strcat() returned NULL/n");
  55.         notmoved++;
  56.         free(movefilename);
  57.         return;
  58.     }
  59.     
  60.     /* 这里我们用完整的路径名试探,看看这个文件存在不,呃,就这样吧,不知道怎么表达了 */
  61.     if (!stat(movefilename, &mfstat))
  62.     {
  63.         if (fstat.st_ino == mfstat.st_ino)  /* It's the same file 通过inode号检验确实是同一个文件,注意文件名相同不能判定同一文件 */
  64.         {
  65.             mprintf("File excluded '%s'/n", filename);
  66.             logg("File excluded '%s'/n", filename);
  67.             notmoved++;
  68.             free(movefilename);
  69.             return;
  70.         }
  71.         else            //到这里了,那就是隔离目录下面有同名文件
  72.         {
  73.             /* file exists - try to append an ordinal number to the
  74.             * quranatined file in an attempt not to overwrite existing
  75.             * files in quarantine
  76.             */
  77.             len = strlen(movefilename);
  78.             n = 0;
  79.             do
  80.             {
  81.                 /* reset the movefilename to it's initial value by
  82.                 * truncating to the original filename length
  83.                 */
  84.                 movefilename[len] = 0;
  85.                 /* append .XXX */
  86.                 sprintf(numext, ".%03d", n++);  //右边对齐 最后格式是: 病毒名.032
  87.                 strcat(movefilename, numext);   //加后缀
  88.             }
  89.             while (!stat(movefilename, &mfstat) && (n < 1000));     //这里只试探1000次,我想应该够了吧,除非你PC成了毒窝了...
  90.         }
  91.     }
  92.     /* 两种方式来处理  第一个就是改名字,其实文件都在磁盘,具体路径还不是我们自己设定而已,所以改名就能转移目录*/
  93.     if (rename(filename, movefilename) == -1)
  94.     {
  95.         if (filecopy(filename, movefilename) == -1)     //第二种方法就是直接拷贝了... 
  96.         {
  97.             mprintf("@cannot move '%s' to '%s': %s/n", filename, movefilename, strerror(errno));
  98.             notmoved++;
  99.             free(movefilename);
  100.             return;
  101.         }

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

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

  108.         if (unlink(filename))       //删除原文件
  109.         {
  110.             mprintf("@cannot unlink '%s': %s/n", filename, strerror(errno));
  111.             notremoved++;
  112.             free(movefilename);
  113.             return;
  114.         }
  115.     }

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

  118.     free(movefilename);
  119. }

转移主要就是一个copy,再加上同名文件的处理问题,有时候当你电脑成病毒窝的时候,可能病毒很多,统统转移到一个目录下面的时候造成同名冲突的可能性比较打,为了避免覆盖,这里就加上后缀. 这个好像很普遍的处理办法吧....






原创粉丝点击