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 までお願いします。