TCP/IP Programing

課題1: telnetコマンドによるWWWサーバへのアクセス

telnetコマンドを使って任意のWWWサーバにアクセスし、任意のURLのページデータ(htmlソースプログラム)を画面に表示せよ。(報告書にはそのURLとページデータの先頭の20行程度を添付せよ)

実行結果(telnet www.yahoo.cp.jp 80 -> GET /index.html HTTP/1.0)

[j05030@~]% telnet www.yahoo.co.jp 80
Trying 203.216.247.225...
Connected to www.yahoo.co.jp.
Escape character is '^]'.
GET /index.html HTTP/1.0

HTTP/1.1 200 OK
Date: Tue, 19 Dec 2006 13:35:07 GMT
P3P: policyref="http://privacy.yahoo.co.jp/w3c/p3p.xml", CP="CAO DSP COR CUR ADM
  DEV TAI PSA PSD IVAi IVDi CONi TELo OTPi OUR DELi SAMi OTRi UNRi PUBi 
IND PHY ONL  UNI PUR FIN COM NAV INT DEM CNT STA POL HEA PRE GOV"
Expires: -1
Pragma: no-cache
Cache-Control: no-cache
Connection: close
Content-Type: text/html; charset=euc-jp

<html>
<head >
<meta http-equiv="Content-Type" content="text/html; charset=euc-jp">
<!--京-- >
<title >Yahoo! JAPAN</title>
<meta name="description" content="日本最大級のポータルサイト。検索、オークション
、ニュース、メール、コミュニティ、ショッピング、など80以上のサービスを展開。あな
たの生活をより豊かにする「ライフ・エンジン」を目指していきます。">
<style type="text/css" media="all">

・・・以下省略・・・

	

課題2: inetdを使用するサーバプログラムの作成

変更点

実行結果(client.c)

	[j05030@tcp:ip]% ./client
Connected.
Input Keyword = warning: this program uses gets(), which is unsafe.
yama
Keyword = [yama] / Data = [kawa]

Input Keyword = 123
Keyword = [123] / Data = [456]

Input Keyword = xyz
Keyword = [xyz] / Data = [XYZ]

Input Keyword = 
Disocnnected.
	
  • 関数の説明
  • 課題3:inetdを使用しないサーバプログラムの作成

    serverプログラムのソース

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <sys/time.h>
    #include <unistd.h>
    
    #define PORT 5524       /* Port Number */
    #define SOCK_MAX 5      /* Max Socket */
    #define UNUSED (-1)
    #define	DATAFILE	"/Users/j05030/jikken2/tcpip/data.txt"
    #define	DELIMITS	" \t\n\r"
    
    char	datavalue[256];
        char buff[256];
    char *Modification(char *str, int length);
    
    int main()
    {
        int s[SOCK_MAX + 1];
        int max = 0;
        int n = 0;
        int len;
        fd_set readfds;
        int clilen;
        struct sockaddr_in saddr;
        struct sockaddr_in caddr;
        char str[1024];
        int i, j;
        int msglen;
        char	*p;
    
        if ((s[0] = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
    	perror("socket");
    	exit(1);
    	}
    	
        bzero((char *)&saddr, sizeof(saddr));
    
        saddr.sin_family = AF_INET;
        saddr.sin_addr.s_addr = INADDR_ANY;
        saddr.sin_port = htons(PORT); 
    
        if ((bind(s[0], (struct sockaddr *)&saddr, sizeof(saddr))) == -1) {
    	perror("bind");
    	exit(1);
        }
    
        if ((listen(s[0], SOCK_MAX)) == -1) {
    	perror("listen");
    	exit(1);
        } 
        max = 1;
    
        while (1) {
    	FD_ZERO(&readfds);
    	printf("max: %d\n", max);
    	for (i = 0; i < max; i++) {
    	    if (s[i] != UNUSED) {
    		FD_SET(s[i], &readfds);
    	    }
    	}
    	if ((n = select(FD_SETSIZE, &readfds, NULL, NULL, NULL)) == -1) {
    	    perror("select");
    	    exit(1);
    	}
    	printf("select returns: %d\n", n);
    	for (i = 1; i < max; i++) {
    	    if (s[i] != UNUSED) {
    		if (FD_ISSET(s[i], &readfds)) {
    		    printf("s[%d] ready for reading\n", i);
    		    if ((msglen = read(s[i], str, sizeof(str))) == -1) {
    			/* Failed */
    			perror("read");
    		    } else if (msglen != 0) {
    			/* Successed */
    			printf("client[%d]: %s", i, str);
    			/* Modified */
    			Modification(str,msglen);  
    			for (j = 1; j < max; j++) {
    			    if (s[j] != UNUSED) {
    				write(s[j], str, strlen(str));
    			    }
    			}
    		    } else {
    			printf("client[%d]: connection closed.\n", i);
    			close(s[i]);
    			s[i] = UNUSED;
    		    }
    		}
    	    }
    	}
    		
    	if (FD_ISSET(s[0], &readfds) != 0) {
    	    printf("Accept New one.\n");
    	    len = sizeof(caddr);
    	    s[max] = accept(s[0], (struct sockaddr *)&caddr, &len);
    	    printf("%d = accept()\n", s[max]);
    	    if (s[max] == -1) {
    		perror(NULL);
    		exit(1);
    	    }
    	    if (max < SOCK_MAX) {
    		printf("client accepted(%d).\n", max);
    		max++;
    	    } else {
    		printf("refuse connection.\n");
    		strcpy(str, "Server is too busy.\n");
    		write(s[max], str, strlen(str));
    		close(s[max]);
    	    }
    	}
        }
    }
    
    /* Return */
    char *Modification(char *str, int length) {
      int i;
      FILE	*fp;
     int    n;
      if((FILE *)NULL == (fp = fopen(DATAFILE,"r")))
        return (char *)NULL;
         n=strlen(str);
         str[n-1]='\0';
    
      for(;;)
        {
          char	*p;
          char	*pp;
         
          fgets(buff,256,fp);
          
          if(feof(fp))
    	break;
          
          if(ferror(fp))
    	break;
          
          if((char *)NULL == (p = strtok(buff,DELIMITS)))
    	continue;
          
          if((char *)NULL == (pp = strtok((char *)NULL,DELIMITS)))
    	continue;
          
       
          
          
          if(0 == strcmp(p,str))
    	{
    	  fclose(fp);
    	  strcpy(str,pp);
    	  return str;
    	}
        }
      
      fclose(fp);
      sprintf(str,"\n"); 
      return str;
    }
    


    clientプログラムのソース

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <netdb.h>
    
    #define PORT 5524 
    #define HOST_NAME   "nw0530.st.ie.u-ryukyu.ac.jp"
    
    main(int argc, char *argv[])
    {
        struct sockaddr_in    addr;
        struct hostent *hp;
        int    fd;
        int    len;
        char   buf[1024];
        int    ret;
    
    	/* Make Socket */
        if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
            perror("socket");
            exit(1);
        }
    
        bzero((char *)&addr, sizeof(addr));
    
    
        if ((hp = gethostbyname(HOST_NAME)) == NULL) {
    	perror("No such host");
    	exit(1);
        }
        bcopy(hp->h_addr, &addr.sin_addr, hp->h_length);
        addr.sin_family = AF_INET;
        addr.sin_port = htons(PORT);
    
        /* Connect to Server */
        if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0){
            perror("Server connected");
            exit(1);
        }
        /* Data Read */
        while (fgets(buf, 1024, stdin)) {
            write(fd, buf, 1024);
            ret = read(fd, buf, 1024);
    	buf[ret] = '\0';
            printf("%s\n",buf);
        }
        close(fd);
        exit(0);
    }
    


    実行結果

    [j05030@~]% ./client_noinetd
    yama
    kawa
    123
    456
    xyz
    XYZ
    


    考察

    inetdを使用するサーバプログラムとそうでないものとの実装上の違いは、inetdを用いたときは、クライアントが要求をおくると同時にサーバーが立ち上がり、要求に応答するとすぐに落ちる。そのため、メモリなどの資源は節約できる、しかし、呼び出されてから起動するので通信の速度は遅くなる。
    逆に、デーモン型の場合はクライアントの要求のたびににサーバープログラムを常駐させなければならない。そのため資源の消費が激しいが、応答時間が短い。




    課題4:HTTPクライアントの作成

    Source Code

    	/*** TCP Client ***/
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define PORT 80
    #define SERVICE_NAME "http"
    #define	PROTOCOL	"tcp"
    
    void	Disconnect(int SocketNumber)
    {
    close(SocketNumber);
    }
    
    void	SendData(int SocketNumber,char *line)
    {
    if(strlen(line) != write(SocketNumber,line,strlen(line)))
    	{
    	printf("\7Send Failed.\n");
    	exit(1);
    	}
    }
    
    int	RecvData(int SocketNumber,char *line)
    {
    int	i;
    
    for(i = 0 ;;)
    	{
    	if(1 != read(SocketNumber,line + i,1)){
    		continue;
    	}
    	else
    		{
    		i++;
    		*(line + i) = (char)0;
    		if(((char)('\n')) == *(line + i - 1))
    			return i;
    		}
    	}
    }
    
    int	main()
    {
    int	Sock;
    char	Key[256];
    char	Data[256];
    char	buff[256];
    char    HOST_NAME[256];
    char	answ[3];
    	
    int	SocketNumber;		/* Socket descripter */
    struct	servent	*ServiceEntry;	/* service entry */
    struct	hostent	*HostEntry;	/* host entry */
    struct	sockaddr_in	sin;	/* Socket Entry */
    
    /* Get Service Entry by Service-Name */
    
    if((struct servent *)NULL == (ServiceEntry =
    	getservbyname(SERVICE_NAME,PROTOCOL)))
    	{
    	printf("\7No service [%s].\n",SERVICE_NAME);
    	exit(1);
    	}
    
    /* Get Host Entry by Host-Name */
    printf("Input server address = ");
    scanf("%s",HOST_NAME);
    printf("\n");
    
    if((struct hostent *)NULL == (HostEntry = gethostbyname(HOST_NAME)))
    	{
    	printf("\7No Hosts [%s].\n",HOST_NAME);
    	exit(1);
    	}
    
    /* Get Socket */
    
    if(0 > (SocketNumber = (socket(PF_INET,SOCK_STREAM,0))))
    	{
    	printf("\7Cannot get Socket.\n");
    	exit(1);
    	}
    
    
    bzero((char *)(&sin), sizeof(sin));
    
    bcopy(HostEntry->h_addr,&sin.sin_addr,HostEntry->h_length);
    sin.sin_family = PF_INET;
    sin.sin_port = htons(PORT);
    
    
    if(0 > connect(SocketNumber,(struct sockaddr *)(&sin),sizeof(sin)))
    	{
    	printf("\7Cannot Connect.\n");
    	exit(1);
    	}
    
    Sock = SocketNumber;	/* Connect */
    printf("Connected.\n");
    
    	char	*p;
    
    printf("download index.html (yes?/no?)\n");
    scanf("%s",answ);
    if ( strcmp(answ,"yes") ==  0)
    {
    	fflush(stdout);
    
        strcpy(Key,"/index.html");
        if(0 == strlen(Key)){
    	   Disconnect(Sock);
    	   printf("Disocnnected.\n");
    	   exit(1);
        }
    
    	sprintf(buff,"GET %s HTTP/1.0\r\n",Key);
    	SendData(Sock,buff);
    	sprintf(buff,"Host: %s:%d\r\n", HOST_NAME, PORT);
    	SendData(Sock,buff);
    	sprintf(buff,"\r\n");
    	SendData(Sock,buff);		/* Send It ! */
    
    	while (1){
    	   char buf[256];
           int size;
           size = read(Sock, buf, 256);
    
    	   if ( size > 0 ){
              write(1, buf, size);
            } else {
              break;
            }
    	}
    }
    Disconnect(Sock);
    printf("Disocnnected.\n");
    }
    	

    実行結果

    [j05030@~]%  ./gethtml
    Input server address = www.u-ryukyu.ac.jp 
    
    Connected.
    download index.html (yes?/no?)
    yes
    HTTP/1.1 200 OK
    Date: Tue, 12 Dec 2006 06:33:01 GMT
    Server: Apache/2.2.2 (Fedora)
    Last-Modified: Tue, 12 Dec 2006 06:11:30 GMT
    ETag: "122b27d-23a6-2734a880"
    Accept-Ranges: bytes
    Content-Length: 9126
    Connection: close
    Content-Type: text/html
    
    <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
    #######################################
    <style type="text/css">
    <!--
        td {
             font-size: smaller;
        }
    -->
    </style>
    <!-- <script language="javascript" src="preview/rollover.js"></script> -->
    <style type="text/css">
    </style>
    <link href="preview/ryudai2.css" rel="stylesheet" type="text/css">
    </head>
    	

    課題5:ポートスキャンの実験

    ソースコード

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <stdio.h>
    #include <string.h>
    
    #define BUFFSIZE BUFSIZ
    
    int
    main(int argc, char *argv[])
    {
      int port;
      int socket_fd;
      char str[BUFFSIZE];
      struct sockaddr_in addr;
    
      if(argc >= 3) {
        printf("Usage: %s [IP addr \n", argv[0]);
        return 1;
      }
      if(argc == 1) {
        strcpy(str, "127.0.0.1");
      } else {
        strcpy(str, argv[1]);
      }
    
      printf("\"%s\" PortScan Start!!\n", str);
    
      for(port=1; port<1024; port++) {
        if((socket_fd = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
          perror("socket");
          return 1;
        }
        
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(str); 
        addr.sin_port = htons(port); 
    
        if(connect(socket_fd, (struct sockaddr *)&addr, sizeof(addr)) == -1) {
        } else {
          printf("%4d\n", port);
        }
        close(socket_fd);
      }
      printf("There are using\n");
    }
    


    実行結果

    [j05030@~]% ./portscan
    "127.0.0.1" PortScan Start!!
      80
     427
     548
     631
    There are using
    


    課題6:バッファオーバーフローの実験

    問題の原因

    	操作していないバッファdmy[]に値が入ることがあるという問題の原因は,
    	gets()関数に起因する。このgets()関数は読み込む文字列のサイズを
    	指定できないので,バッファのサイズを超えて読み込みを続けてしまうことがあり,
    	このために、バッファのサイズを超える文字列を読み込み結果として,buf[]に
    	格納できなくなり,dmy[]に値が入ってしまうことになる。gccのコンパイラーでも
    	warning: this program uses gets(), which is unsafe.と警告が出る。
    	

    問題の解決方法

    	この問題の解決方法は、既に考えられており、fgets()関数を用いることで
    	解決することができる。fgets()関数では保存先のメモリだけではなく、そのメモリの
    	サイズも指定することができるので、gets()関数のようにバッファのサイズを超えて文字列
    	を読み込むことはなくなる。
    	以下にこのgets()をfgets()に書き換えたプログラムを示す。
    	

    実行結果(BUFLEN=20)

    	[j05030@~]% ./bufovf
    before
    buf(Len:0) = 
    dmy(Len:0) = 
    warning: this program uses gets(), which is unsafe.
    zxsdftyui90op;lkjhgfdesw3456789olkjhgfdsw34567890p@;lkjhgfdsdzxfcgvhbjnkjloi98765trewsdfghjkl;:/.;lkjhgtfrdsdfghjkl
    after
    buf(Len:115) = zxsdftyui90op;lkjhgfdesw3456789olkjhgfdsw34567890p@;lkjhgfdsdzxfcgvhbjnkjloi98765trewsdfghjkl;:/.;lkjhgtfrdsdfghjkl
    dmy(Len:0) = 
    Segmentation fault
    
    	

    改善した後のソースを以下に示す
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    
    #define BUFLEN 20
    
    int main(int argc, char **argv) {
    
      char *rtn;
      char dmy[BUFLEN];
      char buf[BUFLEN];
    
      memset(dmy, '\0', BUFLEN);
      memset(buf, '\0', BUFLEN);
    
      printf("before\n");
    
      printf("buf(Len:%d) = %S\n", strlen(buf), buf);
      printf("dmy(Len:%d) = %s\n", strlen(dmy), dmy);
    
      if ((rtn = fgets(buf,sizeof(buf),stdin) == NULL) {
        exit(-1);
      }
    
      printf("after\n");
      printf("buf(Len:%d) = %s\n", strlen(buf), buf);
      printf("dmy(Len:%d) = %s\n", strlen(dmy), dmy);
    
    }
    


    実行結果を以下に示す。
    
    [j05030@~]% ./bufovf 
    before
    buf(Len:0) = 
    dmy(Len:0) = 
    1^[23ertyhjkl;/:lmnbgfcdxzasdfgyhujiop@^-09754e3wqertyuiop@-p0o9i8uytrewsdfghjk:;lkiujyhtredrftgyhujikop@[
    after
    buf(Len:19) = 13ertyhjkl;/:lmnb
    dmy(Len:0) = 
    
    
    

    セキュリティ上の問題

      バッファオーバーフローを悪用した不正アクセスを行なうには高度な知識が必要である、
    しかし、これが実行できるとバッファオーバーフローを引き起こしたプログラムが持っているアクセス権
    の範囲で任意の動作を行なうことが可能となる。
     一般にWebサーバなど、インターネット経由でサービスを提供するプログラムは管理者権限で
    稼動しているため、こういったプログラムのバッファオーバーフローを使われてしまうと、
    不正侵入者に管理者権限を奪われることもある。