C言語で ftp クライアントを作ってみよう (1)

前へ << C 言語で echo サーバを作ってみよう (2) *BSD で kqueue・kevent を使ってみよう >> 次へ

ftp クライアント C言語版

ftp プロトコル自体の解説は、FTP クライアントを作ってみよう (1) を読んで下さい。HTTP クライアント同様に
% cc -o ftp-client ftp-client.c
でコンパイルします。SunOS ではネットワーク関係のライブラリが libc に含まれていないので、
% cc -o ftp-client ftp-client.c -lresolv -lsocket -lnsl
とします。

「ftp://ホスト名/パス」のファイルを取得したい場合は、

% ./ftp-client ユーザ名 パスワード ホスト名 パス
とします。ftp サーバへのログイン時には、引数で指定したユーザ名とパスワードが使われます。 anonymous ftp サーバからファイルを取得したい場合は
% ./ftp-client anonymous 68user@X68000.startshop.co.jp ftp.jp.FreeBSD.org /pub/FreeBSD/README.TXT
のような感じですね。
% ./ftp-client -d anonymous 68user@X68000.startshop.co.jp ftp.jp.FreeBSD.org /pub/FreeBSD/README.TXT
と -d オプションを付けるとデバッグモードになり、送受信した FTP プロトコルを表示します。

解説はナシです。

  • バッファオーバーラン機能つき。
  • ほとんどエラーチェックをしていない。
  • 指定のファイルが存在しなかったらデッドロックで永遠に待ち続ける。
という低機能っぷりを満喫してください。

なお、このプログラムは Active mode にしか対応していません。 そのため NAT 環境で使用すると

  --> PORT 192,168,0,7,9,58
  <-- 500 Illegal PORT range rejected.
などとプライベート IP アドレスを送信してしまいます。 しかもエラーチェックを行っていないため、 デッドロックしてしまいますのでご注意を。

ftp-client.c

    1: /* $Id: ftp-client.c,v 1.4 2004/05/29 05:36:31 68user Exp $ */
    2: 
    3: #include <stdio.h>
    4: #include <string.h>
    5: #include <stdlib.h>
    6: #include <sys/types.h>
    7: #include <sys/socket.h>
    8: #include <netdb.h>
    9: #include <netinet/in.h>
   10: #include <sys/param.h>
   11: #include <sys/uio.h>
   12: #include <unistd.h>
   13: #include <ctype.h>
   14: 
   15: #define BUF_LEN 256             /* バッファのサイズ */
   16: 
   17: int debug_flg = 0;      /* -d オプションを付けると turn on する */
   18: 
   19: 
   20: /*--------------------------------------------------
   21:  * ソケットから1行読み込む
   22:  */
   23: char *read_line(int socket, char *p){
   24:     char *org_p = p;
   25: 
   26:     while (1){
   27:         if ( read(socket, p, 1) == 0 ) break;
   28:         if ( *p == '\n' ) break;
   29:         p++;
   30:     }
   31:     *(++p) = '\0';
   32:     return org_p;
   33: }
   34: 
   35: 
   36: /*--------------------------------------------------
   37:  * レスポンスを取得する。^\d\d\d- ならもう1行取得
   38:  */
   39: void read_response(int socket, char *p){
   40:     do { 
   41:         read_line(socket, p);
   42:         if ( debug_flg ){
   43:             fprintf(stderr, "<-- %s", p);
   44:         }
   45:     } while ( isdigit(p[0]) &&
   46:               isdigit(p[1]) && 
   47:               isdigit(p[2]) &&
   48:               p[3]=='-' );
   49: }
   50: 
   51: 
   52: /*--------------------------------------------------
   53:  * 指定されたソケット socket に文字列 p を送信。
   54:  * 文字列 p の終端は \0 で terminate されている
   55:  * 必要がある
   56:  */
   57: 
   58: void write_to_server(int socket, char *p){
   59:     if ( debug_flg ){
   60:         fprintf(stderr, "--> %s", p);
   61:     }
   62:     write(socket, p, strlen(p));
   63: }
   64: 
   65: void error( char *message ){
   66:     fprintf(stderr, message);
   67:     exit(1);
   68: }
   69: 
   70: 
   71: int main(int argc, char *argv[]){
   72:     int command_socket;           /* コマンド用ソケット */
   73:     int data_socket;              /* データ用ソケット */
   74:     int data_waiting_socket;          /* データコネクションの待ち受け用ソケット */
   75:     struct hostent *servhost;         /* ホスト名とIPアドレスを扱うための構造体 */
   76:     struct sockaddr_in server;        /* ソケットを扱うための構造体 */
   77:     struct sockaddr_in sin;
   78:     int len;
   79: 
   80:     char send_mesg[BUF_LEN];          /* サーバに送るメッセージ */
   81:     char user[BUF_LEN];           /* ftp サーバに送信するユーザ名 */
   82:     char passwd[BUF_LEN];             /* ftp サーバに送信するパスワード */
   83:     char host[BUF_LEN];               /* 接続するホスト名 */
   84:     char path[BUF_LEN];               /* 要求するパス */
   85:     char buf[BUF_LEN];
   86: 
   87:     while (1){
   88:         int c;
   89:         c = getopt(argc, argv, "d");
   90:         if ( c == -1 ) break;
   91:         switch (c){
   92:           case 'd':
   93:             debug_flg = 1;
   94:             argc--;
   95:             argv++;
   96:             break;
   97:           default:
   98:             break;
   99:         }
  100:     }
  101:                 /* 引数解析 */
  102:     if ( argc == 5 ){
  103:         strncpy(user,   argv[1], sizeof(user));
  104:         strncpy(passwd, argv[2], sizeof(passwd));
  105:         strncpy(host,   argv[3], sizeof(host));
  106:         strncpy(path,   argv[4], sizeof(path));
  107:     } else {
  108:         fprintf(stderr, "ftp-client ユーザ名 パスワード ホスト名 パス\n");
  109:         exit(1);
  110:     }
  111:                                 /* ホストの情報 (IP アドレスなど) を取得 */
  112:     servhost = gethostbyname(host);
  113:     if ( servhost == NULL ){
  114:         fprintf(stderr, "Bad hostname [%s]\n", host);
  115:         exit(1);
  116:     }
  117: 
  118:                                 /* IP アドレスを示す構造体をコピー */
  119:     bzero((char*)&server, sizeof(server));
  120:     server.sin_family = AF_INET;
  121:                 /* 構造体をゼロクリア */
  122:     bcopy(servhost->h_addr, (char *)&server.sin_addr, servhost->h_length);
  123: 
  124:                 /* ポート番号取得 */
  125:     server.sin_port = (getservbyname("ftp", "tcp"))->s_port;
  126: 
  127:                 /* ソケット生成 */
  128:     command_socket = socket(AF_INET, SOCK_STREAM, 0);
  129: 
  130:                 /* サーバに接続 */
  131:     connect(command_socket, (struct sockaddr *)&server, sizeof(server));
  132: 
  133:                 /* welcome response を取得 */
  134:     read_response(command_socket, buf);
  135: 
  136:                 /* USER・PASS を送信 */
  137:     sprintf(send_mesg, "USER %s\n", user);
  138:     write_to_server(command_socket, send_mesg);
  139:     read_response(command_socket, buf);
  140: 
  141:     sprintf(send_mesg, "PASS %s\n", passwd);
  142:     write_to_server(command_socket, send_mesg);
  143:     read_response(command_socket, buf);
  144: 
  145:                 /* データコネクション用ソケットを作成し、
  146:                  * bind・listen する
  147:                  */
  148:     data_waiting_socket = socket(AF_INET, SOCK_STREAM, 0);
  149: 
  150:     sin.sin_family = AF_INET;
  151:     sin.sin_port = 0;
  152:     sin.sin_addr.s_addr = htonl(INADDR_ANY);
  153: 
  154:     if ( bind(data_waiting_socket, (struct sockaddr *)&sin, sizeof sin) < 0 ){
  155:         error("bind failed.\n");
  156:     }
  157:     if ( listen(data_waiting_socket, SOMAXCONN) == -1 ){
  158:         error("listen failed.\n");
  159:     }
  160:     /* まだ accept はしない。PORT・LIST を送ってから */
  161: 
  162: 
  163:     /* ----------------------------------------- */
  164:     {
  165:         u_long local_ip;
  166: 
  167:         /* localhost の IP アドレスを取得。既に ESTABLISHED である
  168:          * command_socket から取得していることに注意。
  169:          */
  170: 
  171:         len = sizeof(sin);
  172:         if ( getsockname(command_socket,
  173:                          (struct sockaddr *)&sin, &len) < 0 ){
  174:             error("getsockname failed.\n");
  175:         }
  176:         local_ip = ntohl(sin.sin_addr.s_addr);
  177: 
  178:                 /* ポート番号を取得 */
  179:         if ( getsockname(data_waiting_socket,
  180:                          (struct sockaddr *)&sin, &len) < 0 ){
  181:             error("getsockname failed.\n");
  182:         }
  183: 
  184:         sprintf(send_mesg, "PORT %d,%d,%d,%d,%d,%d\n",
  185:                 (int)(local_ip >> 24) & 0xff,
  186:                 (int)(local_ip >> 16) & 0xff,
  187:                 (int)(local_ip >>  8) & 0xff,
  188:                 (int)(local_ip)       & 0xff,
  189:                 /*
  190:                  * ↑は inet_ntoa(local_ip) でもいいんだけど、
  191:                  * その場合はピリオドをカンマに変換しないといけない。
  192:                  */
  193:                 (ntohs(sin.sin_port) >>  8) & 0xff,
  194:                  ntohs(sin.sin_port)        & 0xff);
  195: 
  196:                 /* PORT・RETR を送信 */
  197:         write_to_server(command_socket, send_mesg);
  198:         read_response(command_socket, buf);
  199: 
  200:         sprintf(send_mesg, "RETR %s\n", path);
  201:         write_to_server(command_socket, send_mesg);
  202:     }
  203: 
  204:                 /* データコネクションの確立 */
  205:     len = sizeof(sin);
  206:     data_socket = accept(data_waiting_socket, (struct sockaddr *)&sin, &len);
  207:     if ( data_socket == -1 ){
  208:         error("accept failed.\n");
  209:     }
  210: 
  211:                 /* ファイルの内容取得 */
  212:     while (1){
  213:         int read_size;
  214:         read_size = read(data_socket, buf, BUF_LEN);
  215:         if ( read_size > 0 ){
  216:             write(1, buf, read_size);
  217:         } else {
  218:             break;
  219:         }
  220:     }
  221:                 /* 150 Opening ASCII mode data connection ... 
  222:                  * のようなレスポンスを受け取る
  223:                  */
  224:     read_response(command_socket, buf);
  225:                 /* 226 Transfer complete. のようなレスポンスを受け取る */
  226:     read_response(command_socket, buf);
  227: 
  228:                 /* QUIT 送って終了 */
  229:     write_to_server(command_socket, "QUIT\n");
  230:     read_response(command_socket, buf);
  231: 
  232:     close(data_waiting_socket);
  233:     close(command_socket);
  234: 
  235:     return 0;
  236: }
前へ << C 言語で echo サーバを作ってみよう (2) *BSD で kqueue・kevent を使ってみよう >> 次へ

ご意見・ご指摘は Twitter: @68user までお願いします。