Linux socket プログラミング/POP3 クライアントプログラム目次†概要†こんどは POP3 のクライアントプログラムです。MTA(メール転送エージェント)へのメール送信機能は ありませんので、POP3 サーバからメールを読み込むだけのプログラムです。 見た目かなり汚くなってしまいましたが、要所要所を変えればいろいろなプログラムへも応用できるので はないかと思います。 プログラムのイメージ†このプログラムで使われる関数のイメージを以下に示します。コネクションを張ったあとには POP3 のコマンドをサーバに送信(send(2))して、サーバからしかるべきメッセージを受信(recv(2))すると言う流れです。
ソースコード†1 /* 2 * POP3 client program 3 * mailp.c 4 * cc mailp.c -lnsl -o mailp 5 */ 6 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 #include 14 #include 15 16 #define BUFMAX 256 17 #define PORT_NO 110 18 19 void read_msg(); 20 void print_err(char *); 21 22 extern int h_errno; 23 int net; 24 int nbyte; 25 char r_msg[BUFMAX]; 26 char s_msg[BUFMAX]; 27 char input[6]; 28 29 int 30 main(int argc, char *argv[]) 31 { 32 struct hostent *host; 33 struct servent *serv; 34 struct sockaddr_in sa; 35 char p_list[] = "LIST\n"; 36 char p_user[] = "USER pop_user\n"; 37 char p_pass[] = "PASS pop_password\n"; 38 char p_quit[] = "QUIT\n"; 39 40 if (argc != 2) { 41 printf("Usage: %s hostname\n", argv[0]); 42 exit(1); 43 } 44 45 /* 46 * Check Host name 47 */ 48 if ((host = gethostbyname(argv[1])) == NULL){ 49 herror("gethostbyname()"); 50 close(net); 51 exit(1); 52 } 53 54 memset(&sa,0, sizeof(struct sockaddr_in)); 55 memcpy(&sa.sin_addr, host->h_addr, sizeof(struct in_addr)); 56 57 sa.sin_family = AF_INET; 58 sa.sin_port = htons(PORT_NO); 59 60 /* 61 * Make socket 62 */ 63 if((net = socket(AF_INET, SOCK_STREAM, 0)) < 0) 64 print_err("socket()"); 65 66 /* 67 * Connect to the mail server 68 */ 69 if (connect(net, (struct sockaddr *)&sa, sizeof(struct sockaddr)) < 0) 70 print_err("connect()"); 71 72 /* 73 * Receive connection messages from server 74 */ 75 memset(r_msg,0,BUFMAX); 76 if( recv(net,r_msg,BUFMAX,0) < 0) 77 print_err("recv()"); 78 printf("%s",r_msg); 79 80 /* 81 * Send user name 82 */ 83 nbyte = strlen( p_user ); 84 if( send(net,p_user,nbyte,0) < 0) 85 print_err("send()"); 86 87 memset(r_msg,0,BUFMAX); 88 if( recv(net,r_msg,BUFMAX,0) < 0) 89 print_err("recv"); 90 printf("%s",r_msg); 91 92 /* 93 * Send password 94 */ 95 nbyte = strlen(p_pass); 96 if( send(net,p_pass,nbyte,0) < 0 ) 97 print_err("send()"); 98 99 memset(r_msg,0,BUFMAX); 100 if( recv(net,r_msg,BUFMAX,0) < 0) 101 print_err("recv()"); 102 printf("%s",r_msg); 103 104 /* 105 * Request mail list & Select letter number 106 */ 107 while(1){ /* loop start */ 108 nbyte = strlen( p_list ); 109 if( send(net,p_list,nbyte,0) < 0 ) 110 print_err("send()"); 111 while(1){ 112 memset(r_msg,0,BUFMAX); 113 if( recv(net,r_msg,BUFMAX,0) < 0) 114 print_err("recv()"); 115 printf("%s",r_msg); 116 if ( strstr( r_msg, "\n.") != NULL) { 117 printf("\n"); 118 break; 119 } 120 } 121 122 /* 123 * Enter letter number 124 */ 125 printf("Enter the number you want to read( q for quit)\n"); 126 if ( scanf("%5s", input) < 0) 127 print_err("scanf()"); 128 129 if ( input[0] == 'q' ) { 130 nbyte = strlen( p_quit ); 131 if( send(net,p_quit,nbyte,0) < 0) 132 print_err("send()"); 133 memset(r_msg,0,BUFMAX); 134 if( recv(net,r_msg,BUFMAX,0) < 0) 135 print_err("recv()"); 136 printf("%s\n",r_msg); 137 /* Normal closure process */ 138 if (shutdown(net, 2) < 0) 139 print_err("shutdown()"); 140 close(net); 141 exit(0); 142 } 143 if( (isdigit(input[0])) == 0){ 144 printf("Input error\n"); 145 continue; 146 } 147 read_msg(); 148 } /* loop end */ 149 } 150 151 /********************************************************* 152 * read_msg() 153 * 154 * Retrieve and print email message from the mail server 155 *********************************************************/ 156 void 157 read_msg() 158 { 159 sprintf(s_msg,"RETR %s\n",input); 160 nbyte = strlen(s_msg); 161 if( send(net,s_msg,nbyte,0) < 0 ) 162 print_err("send()"); 163 while(1){ 164 memset(r_msg,0,BUFMAX); 165 if( recv(net,r_msg,BUFMAX,0) < 0) 166 print_err("recv()"); 167 printf("%s",r_msg); 168 if( strstr(r_msg,"\n.") != NULL ){ 169 printf("\n"); 170 break; 171 } 172 } 173 return; 174 } 175 176 void 177 print_err(char *func) 178 { 179 perror(func); 180 close(net); 181 exit(1); 182 } ソースファイル mailp.c 実行例†このプログラムの実行例を示します。 プログラム名の後に引数として POP3 サーバ名、または IP アドレスを入力します。USER ID と PASSWORD はプログラム中に宣言されていますので入力の必要はありません。本当は「From:」ヘッダとか「To:」ヘッダを検出して抜き出そうと思ったんですが、ヘッダーからメッセージまでそのまま出力してしまってます。まぁ、その辺は後で付け足そうと思っています。(文字列の操作が良く分からない) % ./mailp mail.example.com +OK POP3 mail v2001.80 server ready +OK User name accepted, password please +OK Mailbox open, 2 messages +OK Mailbox scan listing follows 1 392 2 395 . Enter the number you want to read.( q for quit) 1 +OK 392 octets Return-Path: <root><root@mail.example.com> Received: (from root@localhost) by mail.example.com (8.8.7/8.8.7) id AAA21525 for pop_user; Thu, 1 Jul 1999 00:24:05 +0900 Date: Thu, 1 Jul 1999 00:24:05 +0900 From: root Message-Id: <199906301524.AAA21525@mail.example.com> To: pop_user@mail.example.com Subject: test1 X-UIDL: 0beb13dbeab34ae6e3549b3f5ea2872c Status: RO This is a test mail from root. Can you read? . +OK 2 messages (787 octets) 1 392 2 395 . Enter the number you want to read( q for quit) q +OK Sayonara 解説†以下にこのプログラムの説明をします。最初にサーバの名前を IP アドレスに変換して、前の「ポート検査」プログラムでやったように コネクションを張り、サーバに対してPOP3のコマンドを送信するという順序です。 40 if (argc != 2) { 41 printf("Usage: %s hostname\n", argv[0]); 42 exit(1); 43 } ここではプログラムの起動時に引数を指定しています。プログラム名 + サーバ名 というのが必要な構文です。 サーバ名が指定されないと、「Usage」が表示されます。 48 if ((host = gethostbyname(argv[1])) == NULL){ 49 herror("gethostbyname()"); 50 close(net); 51 exit(1); 52 } 48 行目ではコマンドの引数であるサーバ名から gethostbyname() 関数を使って hostent 構造体を得ています。 gethostbyname() では /etc/nsswitch.conf で設定しているネームサービスを利用して、ネームアドレスから IP アドレスの参照を行います。 hosts: files dns たとえは nsswitch.conf が上記のようになっていれば最初に /etc/hosts ファイルを見に行き、もしここに該当するホスト名が記述されていなければ、/etc/resolv.conf に記述されている DNS サーバに問い合わせを行い、IP アドレスを得ます。
54 memset(&sa,0, sizeof(struct sockaddr_in)); 54行目は sockaddr_in 構造体である「sa」に 0 つまり NULL をセットして初期化しています。 80 /* 81 * Send user name 82 */ 83 nbyte = strlen( p_user ); 84 if( send(net,p_user,nbyte,0) < 0) 85 print_err("send()"); 86 87 memset(r_msg,0,BUFMAX); 88 if( recv(net,r_msg,BUFMAX,0) < 0) 89 print_err("recv"); 90 printf("%s",r_msg); ここでは send()システムコールを使って p_user(= USER pop_user)という文字列を送ります。USER というのは POP3 のコマンドで、その後に続くアカウントでのログインの処理を要求します。
95〜102 行目も同様です。プログラムの最初で宣言している p_pass をサーバに送信します。 104 /* 105 * Request mail list & Select letter number 106 */ 107 while(1){ /* loop start */ 108 nbyte = strlen( p_list ); 109 if( send(net,p_list,nbyte,0) < 0 ) 110 print_err("send()"); 111 while(1){ 112 memset(r_msg,0,BUFMAX); 113 if( recv(net,r_msg,BUFMAX,0) < 0) 114 print_err("recv()"); 115 printf("%s",r_msg); 116 if ( strstr( r_msg, "\n.") != NULL) { 117 printf("\n"); 118 break; 119 } 120 } ここでは p_list という文字列を送信しているのはこれまでと同じです。この p_list に含まれる「LIST」というコマンドをサーバに送るとサーバが保存しているメールの番号とバイト数を返してきます。いままでのコマンドではサーバからのメッセージは BUFMAX(=256 バイト)で受け取れますが、メールが大量にとどいている場合はこのバッファーサイズ以上のリストが送信されてくる場合もありますので、 while() を使って順次メッセージを受信しています。 122 /* 123 * Enter letter number 124 */ 125 printf("Enter the number you want to read( q for quit)\n"); 126 if ( scanf("%5s", input) < 0) 127 print_err("scanf()"); 128 129 if ( input[0] == 'q' ) { 130 nbyte = strlen( p_quit ); 131 if( send(net,p_quit,nbyte,0) < 0) 132 print_err("send()"); 133 memset(r_msg,0,BUFMAX); 134 if( recv(net,r_msg,BUFMAX,0) < 0) 135 print_err("recv()"); 136 printf("%s\n",r_msg); 137 /* Normal closure process */ 138 if (shutdown(net, 2) < 0) 139 print_err("shutdown()"); 140 close(net); 141 exit(0); 142 } 143 if( (isdigit(input[0])) == 0){ 144 printf("Input error\n"); 145 continue; 146 } 126行目では scanf()でユーザからのメール番号の入力を待ちます。 143 if( (isdigit(input[0])) == 0){ 144 printf("Input error\n"); 145 continue; 146 } 147 read_msg(); 「q」以外の文字が入力された場合にはまず、isdigit(3) 関数を使って文字が数字であるかどうかを判定しています。数字以外であった場合にはエラーを表示し、メールのリストを再表示します。数字であった場合には 147 行目で read_msg() 関数に飛びます。 156 void 157 read_msg() 158 { 159 sprintf(s_msg,"RETR %s\n",input); 160 nbyte = strlen(s_msg); 161 if( send(net,s_msg,nbyte,0) < 0 ) 162 print_err("send()"); 163 while(1){ 164 memset(r_msg,0,BUFMAX); 165 if( recv(net,r_msg,BUFMAX,0) < 0) 166 print_err("recv()"); 167 printf("%s",r_msg); 168 if( strstr(r_msg,"\n.") != NULL ){ 169 printf("\n"); 170 break; 171 } 172 } 173 return; 174 } 159 行目では 126 行目でユーザから受け取ったメッセージ番号を例えば「RETR 2」という文字列にして 変数 s_msg に代入しています。 これ以降は上のメールリストの取得と同じです。256 バイトで受け取れきれないメッセージを while() で逐次 recv() し、「改行」+「ピリオド」がきたら終了します。 |