echo サーバを作ってみよう (2) で作成した、最も基本的な echo サーバを C 言語に書き直したものが、 以下のソースです。
1: /* 2: * $Id: echo-server-1.c,v 1.6 2005/02/19 16:01:53 68user Exp $ 3: * 4: * echo サーバサンプル 5: * 6: * written by 68user http://X68000.q-e-d.net/~68user/ 7: */ 8: 9: #include <stdio.h> 10: #include <stdlib.h> 11: #include <string.h> 12: #include <netdb.h> 13: #include <sys/types.h> 14: #include <sys/socket.h> 15: #include <sys/uio.h> 16: #include <unistd.h> 17: #include <sys/param.h> 18: #include <netinet/in.h> 19: #include <arpa/inet.h> 20: 21: #define BUF_LEN 256 /* バッファのサイズ */ 22: 23: /* ソケット socket から1行読み込み、読み込んだ文字列を p に格納する。 24: 改行コードを読み込む前にソケットから read できなくなった場合は、 25: その時点で呼び出し元に戻る。 26: 27: 戻り値で読み込んだ文字数を返す。p は \0 でターミネートする。 28: */ 29: int read_line(int socket, char *p){ 30: int len = 0; 31: while (1){ 32: int ret; 33: ret = read(socket, p, 1); 34: if ( ret == -1 ){ 35: perror("read"); 36: exit(1); 37: } else if ( ret == 0 ){ 38: break; 39: } 40: if ( *p == '\n' ){ 41: p++; 42: len++; 43: break; 44: } 45: p++; 46: len++; 47: } 48: *p = '\0'; 49: return len; 50: } 51: 52: 53: int main(int argc, char *argv[]){ 54: int connected_socket, listening_socket; 55: struct sockaddr_in sin; 56: int len, ret; 57: int sock_optval = 1; 58: int port = 5000; 59: /* リスニングソケットを作成 */ 60: listening_socket = socket(AF_INET, SOCK_STREAM, 0); 61: if ( listening_socket == -1 ){ 62: perror("socket"); 63: exit(1); 64: } 65: /* ソケットオプション設定 */ 66: if ( setsockopt(listening_socket, SOL_SOCKET, SO_REUSEADDR, 67: &sock_optval, sizeof(sock_optval)) == -1 ){ 68: perror("setsockopt"); 69: exit(1); 70: } 71: /* アドレスファミリ・ポート番号・IPアドレス設定 */ 72: sin.sin_family = AF_INET; 73: sin.sin_port = htons(port); 74: sin.sin_addr.s_addr = htonl(INADDR_ANY); 75: 76: /* ソケットにアドレス(=名前)を割り付ける */ 77: if ( bind(listening_socket, (struct sockaddr *)&sin, sizeof(sin)) < 0 ){ 78: perror("bind"); 79: exit(1); 80: } 81: /* ポートを見張るよう、OS に命令する */ 82: ret = listen(listening_socket, SOMAXCONN); 83: if ( ret == -1 ){ 84: perror("listen"); 85: exit(1); 86: } 87: printf("ポート %d を見張ります。\n", port); 88: 89: while (1){ 90: struct hostent *peer_host; 91: struct sockaddr_in peer_sin; 92: 93: len = sizeof(peer_sin); 94: /* コネクション受け付け */ 95: connected_socket = accept(listening_socket, (struct sockaddr *)&peer_sin, &len); 96: if ( connected_socket == -1 ){ 97: perror("accept"); 98: exit(1); 99: } 100: /* 相手側のホスト・ポート情報を表示 */ 101: peer_host = gethostbyaddr((char *)&peer_sin.sin_addr.s_addr, 102: sizeof(peer_sin.sin_addr), AF_INET); 103: if ( peer_host == NULL ){ 104: printf("gethostbyname failed\n"); 105: exit(1); 106: } 107: 108: printf("接続: %s [%s] ポート %d\n", 109: peer_host->h_name, 110: inet_ntoa(peer_sin.sin_addr), 111: ntohs(peer_sin.sin_port) 112: ); 113: 114: while (1){ 115: int read_size; 116: char buf[BUF_LEN]; 117: /* 1行読み込む */ 118: read_size = read_line(connected_socket, buf); 119: if ( read_size == 0 ) break; 120: 121: printf("メッセージ: %s", buf); 122: /* クライアントに文字列をそのまま返す */ 123: write(connected_socket, buf, strlen(buf)); 124: } 125: 126: printf("接続が切れました。引き続きポート %d を見張ります。\n", port); 127: ret = close(connected_socket); 128: if ( ret == -1 ){ 129: perror("close"); 130: exit(1); 131: } 132: } 133: ret = close(listening_socket); 134: if ( ret == -1 ){ 135: perror("close"); 136: exit(1); 137: } 138: 139: return 0; 140: }perl 版は40行程度でしたが、C言語版は 100行以上になっています。 全体的な流れは perl 版と同じですが、注意してほしいのは 引数の型の違い・引数の数の違いですが、まぁ見ればわかるでしょう、 ということで説明はしません。
古来からいろんなプログラムがバッファオーバーランの餌食になってきました。 クラッカーがあるホストをクラックしようとするときの常套手段の一つに、 バッファオーバーランを起こし 任意のコード (パスワードを外部に流したりトロイの木馬を仕込んだり) を実行させる、 というものがあります。
ちなみに、(FreeBSD 2.2.7-RELEASE の) inetd 組み込みの echo サーバは
while ((i = read(s, buffer, sizeof(buffer))) > 0 && write(s, buffer, i) > 0)としています。 これだとバッファオーバーランは起こりませんが、 本当の意味での 一行単位での読み込みは実現できていません。 なぜなら、read は1行単位で読み込む関数ではないし、 そもそも1行分のデータが送られてきたという保証はないからです (現在1行の途中までのデータしか送られてきていないかもしれない)。
対策としては
ご意見・ご指摘は Twitter: @68user までお願いします。