TCPIPアプリケーションCプログラミング

16Sep2004 coskx
16Mar2010 coskx

【1】はじめに

このWebページはWindowsPCにおいて,Winsock2を用いて,TCP/IPクライアントアプリケーションを紹介する。
ここで紹介する操作は,Windowsの「コマンドプロンプト」で実行する。
ここで紹介するCプログラムソースは,VC++を使う場合は「ws2_32.lib」をリンクリストに加え,BCCを使う場合はそのままコンパイルすればよい。

TCP/IPクライアントアプリケーションでポピュラなのはWebブラウザや,メーラであろう。これらはサーバマシン上で動作しているhttpサーバやsmtpサーバ・pop3サーバに接続して動作するクライアントアプリケーションである。
(サーバはサービスを提供する人,クライアントはお客さんの意味。お客さんの指図に従ってサービスを提供するのがサーバの役目)

クライアントマシン1 (パソコン)
(smtp/pop3クライアント)

 

クライアントマシン2 (パソコン)
(smtp/pop3クライアント)

 

クライアントマシン3 (パソコン)
(httpクライアント)

 

クライアントマシン4 (パソコン)

(httpクライアント)

 

 

 

 

 

 

サーバマシン (UNIXマシン)
(smtpサーバ)
(httpサーバ)
etc

サーバマシンもクライアントマシンもをそれぞれ自分を識別するIPアドレスを持っている。マシンが相互に通信するとき は,IPアドレスで通信先を確定している。(IPアドレスを覚えるのは人間にとってつらいので,各マシンはドメイン名を持っており,通常はこちらを人間が 使い,マシンがIPアドレスに変換して通信をしている。)
DNSサービステストプログラム(参考)
同じサーバマシンでsmtpやhttpなど複数のサービスを行なっている場合が多い。クライアントアプリケーションは,通信相手のサーバマシンのIPアドレスとサービスの種類を特定するポート番号を用いて,自分の通信する相手のサーバを特定している。
例えば,クライアントマシン1はサーバマシンのsmtpサーバと通信し,クライアントマシン3はサーバマシンのhttpサーバと通信している。

各サービスは1つのポート番号に対応しているため,クライアントマシン(パソコン)は「サーバマシンのドメイン名」と「ポート番号」を指定して,接続を行なう。よく使われているサービスに対応するポート番号は次の通りである。

サービス名 ポート番号 備考
echo 7 送信データをそのまま返送してくるプロトコル
daytime 13 日付と時刻のサービスのプロトコル
ftp 21 ファイル転送プロトコル
telnet 23 telnetプロトコル
smtp 25
(587)

メール送信プロトコル
outlookなどのメーラがメール送信サーバ(smtpサーバ)にメールを送信する
587はセキュアポート(プロバイダ境界を越えて通信できる)

finger 79 ユーザ情報取得のプロトコル
http 80 Webブラウザのプロトコル
pop3 110 メーラでの受信プロトコル
outlookなどのメーラがメール受信サーバ(pop3サーバ)からメールを受信する

 

【2】daytimeプロトコル

2.1 telnetによるdaytimeプロトコルの検証

Windowsのコマンドラインコマンドであるtelnetを使って,daytimeサーバに接続してみる。telnetコマンドの使い方は
「telnet  サーバドメイン名  ポート番号」なのでサーバドメイン名にはxxx.yyy.zzz,ポート番号には13を指定している。なおすべてのUNIXマシンがechoサービスをしているとは限らないので,このサービスを行なっているサーバマシンに接続する必要がある。
daytimeサーバは13番ポートを見張っていて,このポートに接続してきたら,日時を返送し、接続を遮断する。

「RFC 867」参照

telnetによるdaytimeプロトコルの検証

>telnet xxx.yyy.zzz 13

Thu Sep 16 16:05:37 2004


ホストとの接続が切断されました。

課題 UNIXマシンにdaytime接続を試みなさい。
2010年3月現在,このサービスを行っているサーバマシンは学内には存在しない。

 

2.2 winsock2を用いたdaytimeクライアントのプログラミング

VC++でANSIのCプログラムを作り,リンク時に「ws2_32.lib」をリンクするようにする。(「プロジェクト」メニュー→「設定」コマンド→「リンク」タブ→「ライブラリモジュール」に「ws2_32.lib」を追加する)

「winsockでのソケット通信の初期化,サーバのドメイン名からIPアドレスの取得,サービス名からポート番号の取得,ソケットオープン, コネクト」は一連の動作なので,1関数(connectSocket(),この関数はすべてに共通)にまとめ,接続後,サーバからの応答(日時情報)を受 け取り,closesocket(sock),WSACleanup()の2つの関数で後処理を行なっている。

dayime.c (薄い色の文字の部分はすべてのプログラムで共通)

/**********************************************************************
Winsock2を用いたDayTimeサンプルプログラム
このサンプルプログラムはサーバに日付時刻をリプライさせます。
(1)サーバ名はDayTimeプロトコルを受け付けるサーバ名を指定してください。
(2)コンパイル時に「ws2_32.lib」をリンクしてください。
**********************************************************************/

#define REQUEST_INFO
const char *servername="xxx.yyy.zzz";

/* Import Library: Link with ws2_32.lib */
#include <stdio.h>
#include <string.h>
#include <winsock2.h>

/*ソケットsに文字列データdataを送信する関数*/
void sendData(SOCKET s, char *data)
{
    send(s,data,strlen(data),0);
}

/*ソケットsからバッファサイズbuffsizeのデータバッファdatabuffに
データを受け取る関数 返す値は取得バイト数*/
int receiveData(SOCKET s, char *databuff, int buffsize)
{
    char *p=databuff;
    int i;
    for (i=0;i<buffsize;i++) *p++=0;
    return recv(s,databuff,buffsize,0);
}

/**************** for printing information  HEAD ************************/
#ifdef REQUEST_INFO
void printWinsockInfo(WSADATA *wsaData)
{
    union {
        short int s;
        unsigned char c[2];
    } v,hv;
    if (wsaData==NULL) return;
    v.s=wsaData->wVersion;
    hv.s=wsaData->wHighVersion;
    printf("== winsock information ==\n");
    printf("winsock version   = %d.%d\n",v.c[0],v.c[1]);
    printf("winsock HighVer   = %d.%d\n",hv.c[0],hv.c[1]);
    printf("Description       = %s\n",wsaData->szDescription);
    printf("SystemStatus      = %s\n" ,wsaData->szSystemStatus);
    printf("Max Sockets       = %d\n" ,wsaData->iMaxSockets);
    printf("Max UDP byte size = %d\n" ,wsaData->iMaxUdpDg);
    printf("\n");
}
void printServerInfo(LPHOSTENT lpHost)
{
    int i;
    if (lpHost==NULL) return;
    printf("== server information ==\n");
    printf("host name      = %s\n" , lpHost->h_name);
    printf("address type   = %d\n" , lpHost->h_addrtype); /*AF_INETは2*/
    printf("address length = %d\n" , lpHost->h_length);
    for(i = 0 ; lpHost->h_aliases[i] ; i++) {
        printf("aliases        = %s\n" , lpHost->h_aliases[i]);
    }

    for(i = 0 ; lpHost->h_addr_list[i] ; i++) {
        IN_ADDR *ip;
        ip = (IN_ADDR *)lpHost->h_addr_list[i];
        printf("IP address     = %s\n" , inet_ntoa(*ip));
    }
    printf("\n");
}
void printServiceInfo(LPSERVENT lpServ)
{
    int i;
    if (lpServ==NULL) return;
    printf("== service information ==\n");
    printf("service name = %s\n" , lpServ->s_name);
    for(i = 0 ; lpServ->s_aliases[i] ; i++) {
        printf("aliases      = %s\n" , lpServ->s_aliases[i]);
    }
    printf("port number  = %d\n" , lpServ->s_port);
    printf("protocol     = %s\n" , lpServ->s_proto);
    printf("\n");
}
void printPortInfo(int port)
{
    printf("== port number information ==\n");
    printf("port number = %d\n" , ntohs((short)port));
    /*ntohsはホストバイトオーダーに戻す関数*/
    printf("\n");
}
#endif
/**************** for printing information  TAIL ************************/

/************************************************************
ソケットをオープンし,目的のサーバのポートに接続する
server:     サーバの名前(ドメイン名,ドメインを省略してはいけない)
            例えばxxx.yyy.zzz
servicename:"smtp"とか"http"などのサービスの名前,
            次のところに名前の一覧がある
            Windowsシステムのsystem32\drivers\etc\services
service:    サービスのポート番号,smtpなら25,httpなら80など
            この情報は,servicenameが不明の場合に必要なだけで,
            通常は使われない
不成功ならINVALID_SOCKETを返す
*************************************************************/
SOCKET connectSocket(const char *server, char *servicename, short int service)
{
    WSADATA wsaData; /*winsock*/
    LPHOSTENT lpHost;
    LPSERVENT lpServ;
    SOCKET sock;
    SOCKADDR_IN sockAddr;
    int port; /*ポート番号*/
    int status;

    /*winsockをver1.1で初期化を要求*/
    status=WSAStartup(MAKEWORD(1, 1), &wsaData);
    if (status != 0) {
        fprintf(stderr,"WSAStartup() failed\n");
        return INVALID_SOCKET;
    }

    /*サーバ名からホストの情報を得る*/
    lpHost = gethostbyname(server);
    if(lpHost == NULL) {
        fprintf(stderr,"server name error\n");
        WSACleanup();
        return INVALID_SOCKET;
    }

    /*サービス情報からポート番号取得*/
    lpServ = getservbyname(servicename, NULL);
    if (lpServ == NULL) {
        port=htons(service);
        /*ホストバイトオーダーからネットワークバイトオーダー(ビッグエンディアン)に変換*/
    } else {
        port=lpServ->s_port;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printWinsockInfo(&wsaData); /*Winsocの情報表示*/
        printServerInfo(lpHost);    /*サーバの情報表示*/
        printServiceInfo(lpServ);   /*サービスの情報表示*/
        printPortInfo(port);        /*ポート番号の情報表示*/
    #endif

    /*ソケットオープン*/
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == INVALID_SOCKET) {
        fprintf(stderr,"socket() error\n");
        WSACleanup();
        return INVALID_SOCKET;
    }

    /*ソケット結合*/
    sockAddr.sin_family = AF_INET;
    sockAddr.sin_port = port;
    sockAddr.sin_addr = *((LPIN_ADDR)*lpHost->h_addr_list);
    status=connect(sock, (PSOCKADDR)&sockAddr, sizeof(SOCKADDR_IN));
    if (status) {
        fprintf(stderr,"connect() error\n");
        closesocket(sock);
        WSACleanup();
        return INVALID_SOCKET;
    }
    return sock;
}

#define rcvBufSize 1024

/*サーバにDayTimeプロトコルでリプライさせる*/
int getDayTime(const char *server)
{
    SOCKET sock;
    static char rcvBuf[rcvBufSize];
    /*DAYTIMEサーバへの接続*/
    sock=connectSocket(server,"daytime",IPPORT_DAYTIME);
    if (sock == INVALID_SOCKET) return 1;
    printf("connecting Socket\n");

    /*サーバからのリプライ受信*/
    receiveData(sock,rcvBuf,rcvBufSize);
    printf("receiving : %s\n",rcvBuf);

    closesocket(sock);
    WSACleanup();
    return 0;
}

int main()
{
    getDayTime(servername);
    return 0;
}

実行結果 (薄い色の文字の部分は初期化関数内で得られた情報)

== winsock information ==
winsock version   = 1.1
winsock HighVer   = 2.2
Description       = WinSock 2.0
SystemStatus      = Running
Max Sockets       = 32767
Max UDP byte size = 65467

== server information ==
host name      = xxx.yyy.zzz
address type   = 2
address length = 4
IP address     = 172.16.169.1

== service information ==
service name = daytime
port number  = 3328
protocol     = tcp

== port number information ==
port number = 13

connecting Socket
receiving : Thu Sep 16 10:22:22 2004

課題 UNIXマシンにdaytime接続を試みるプログラムを作りなさい。
ただし,接続するマシンのドメイン名はコマンドラインから取得しなさい。
2010年3月現在,このサービスを行っているサーバマシンは学内には存在しない。

 

【3】echoプロトコル

3.1 telnetによるechoプロトコルの検証

Windowsのコマンドラインコマンドであるtelnetを使って,echoサーバに接続してみる。telnetコマンドの使い方は
「telnet  サーバドメイン名  ポート番号」なのでサーバドメイン名にはxxx.yyy.zzz,ポート番号には7を指定している。なおすべてのUNIXマシンがechoサービスをしているとは限らないので,このサービスを行なっているサーバマシンに接続する必要がある。
echoサーバは7番ポートを見張っていて,このポートに接続してきたら,受け取った文字をそのまま返信するようになっている。この作業はクライアントが接続を切断するまで続く。

「RFC 862」参照

telnetによるechoプロトコルの検証

>telnet xxx.yyy.zzz 7

 Heelllloo

1文字送信するごとにエコーバックされているのがわかる。(キーボードからは「Hello」と入力)

課題 UNIXマシンにecho接続を試みなさい。
2010年3月現在,このサービスを行っているサーバマシンは学内には存在しない。

 

3.2 winsock2を用いたechoクライアントのプログラミング

VC++でANSIのCプログラムを作り,リンク時に「ws2_32.lib」をリンクするようにする。(「プロジェクト」メニュー→「設定」コマンド→「リンク」タブ→「ライブラリモジュール」に「ws2_32.lib」を追加する)

「winsockでのソケット通信の初期化,サーバのドメイン名からIPアドレスの取得,サービス名からポート番号の取得,ソケットオープン, コネクト」は一連の動作なので,1関数(connectSocket(),この関数はすべてに共通)にまとめ,接続後,サーバからの応答(日時情報)を受 け取り,closesocket(sock),WSACleanup()の2つの関数で後処理を行なっている。

telnetによる検証では1文字送信するごとにエコーバックされているが,このプログラムでは,テスト文字列を送信し終わってから受信するため,バッファに入っているエコー文字列を受け取ることになる。

echo.c (プログラムで共通部分は省略)

/**********************************************************************
Winsock2を用いたEchoサンプルプログラム
このサンプルプログラムはサーバに文字列をリプライさせます。
(1)サーバ名はEchoプロトコルを受け付けるサーバ名を指定してください。
(2)コンパイル時に「ws2_32.lib」をリンクしてください。
**********************************************************************/

#define REQUEST_INFO
const char *servername="xxx.yyy.zzz";

=この部分共通なので省略=
=この部分共通なので省略=

#define sndBufSize 1024
#define rcvBufSize 1024

/*サーバにDayTimeプロトコルでリプライさせる*/
int getEcho(const char *server)
{
    SOCKET sock;
    static char sndBuf[sndBufSize],rcvBuf[rcvBufSize];
    /*ECHOサーバへの接続*/
    sock=connectSocket(server,"echo",IPPORT_ECHO);
    if (sock == INVALID_SOCKET) return 1;
    printf("connecting Socket\n");

    /*サーバへの文字列送信とリプライ受信*/
    sprintf(sndBuf,"Hello from Tokyo.\r\n");
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    printf("sending   : %s",sndBuf);
    printf("receiving : %s\n",rcvBuf);

    /*サーバへの文字列送信とリプライ受信*/
    sprintf(sndBuf,"Seeing is believing.\r\n");
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    printf("sending   : %s",sndBuf);
    printf("receiving : %s\n",rcvBuf);

    closesocket(sock);
    WSACleanup();
    return 0;
}

int main()
{
    getEcho(servername);
    return 0;
}

実行結果 (薄い色の文字の部分は初期化関数内で得られた情報)

== winsock information ==
winsock version   = 1.1
winsock HighVer   = 2.2
Description       = WinSock 2.0
SystemStatus      = Running
Max Sockets       = 32767
Max UDP byte size = 65467

== server information ==
host name      = xxx.yyy.zzz
address type   = 2
address length = 4
IP address     = 172.16.169.1

== service information ==
service name = echo
port number  = 1792
protocol     = tcp

== port number information ==
port number = 7

connecting Socket
sending   : Hello from Tokyo.
receiving : Hello from Tokyo.

sending   : Seeing is believing.
receiving : Seeing is believing.

課題 UNIXマシンにecho接続を試みるプログラムを作りなさい。
2010年3月現在,このサービスを行っているサーバマシンは学内には存在しない。

 

【4】http

4.1 telnetによるhttpの検証

Windowsのコマンドラインコマンドであるtelnetを使って,httpサーバに接続してみる。telnetコマンドの使い方は
「telnet  サーバドメイン名  ポート番号」なのでサーバドメイン名にはxxx.yyy.zzz,ポート番号には80を指定している。なおすべてのUNIXマシンがhttpサービスをしているとは限らないので,このサービスを行なっているサーバマシンに接続する必要がある。
httpサーバは80番ポートを見張っていて,このポートに接続してきたら,次のコマンドを受け付ける状態になる。
そこで「GET / HTTP/1.1」とコマンドを入力する。大文字の「GET」でなければならない。なお,この入力文字はコンソールでは見えないようだ。(HTTP1.1を想定)
「GET / HTTP/1.1」ではWebサーバのドキュメントルートにあるindex.htmlが取得される。(Webサーバの設定による)
Webサーバのドキュメントルート/test/test.htmlを取得するなら
「GET /test/test.html HTTP/1.1」と入力する。
続いて「User-Agent: Mozilla/4.0」「Host: myhost」の後に空白行を入力する。

「RFC1945 (HTTP 0.9/HTTP 1.0)」「 RFC2068 (HTTP 1.1)」参照

telnetによるhttpプロトコルの検証(HTTP/1.1)
(行が乱れるのは,もとのファイルの改行コードのため)

>telnet xxx.yyy.zzz 80

GET / HTTP/1.1<RET>
User-Agent: Mozilla/4.0<RET>
Host: myhost<RET>
<RET>

HTTP/1.1 200 OK
Date: Mon, 02 Feb 2015 00:42:00 GMT
Server: Apache
Last-Modified: Sat, 20 Nov 2004 06:16:24 GMT
ETag: "3000000016dc6-2c-3e94a9010be00"
Accept-Ranges: bytes
Content-Length: 44
Content-Type: text/html

<html><body><h1>It works!</h1></body></html>


課題 UNIXマシンにhttp接続を試みなさい。

2015年1月現在,本校内ではtnct20.tokyo-ct.ac.jpでテストできる。
学校内のLANに接続されているPCから学校外のWebサイトに対してこの接続はできない。

 

4.2 winsock2を用いたhttpクライアントのプログラミング

VC++でANSIのCプログラムを作り,リンク時に「ws2_32.lib」をリンクするようにする。(「プロジェクト」メニュー→「設定」コマンド→「リンク」タブ→「ライブラリモジュール」に「ws2_32.lib」を追加する)

「winsockでのソケット通信の初期化,サーバのドメイン名からIPアドレスの取得,サービス名からポート番号の取得,ソケットオープン, コネクト」は一連の動作なので,1関数(connectSocket(),この関数はすべてに共通)にまとめ,接続後,サーバからの応答(日時情報)を受 け取り,closesocket(sock),WSACleanup()の2つの関数で後処理を行なっている。

デフォルトファイルをHTTP0.9で取得します。

http.c (プログラムで共通部分は省略) プログラムダウンロード

/**********************************************************************
Winsock2を用いたHttpサンプルプログラム
このサンプルプログラムはサーバにHttpプロトコルでgetコマンドを送信し,
デフォルトページをリプライさせます。
(1)サーバ名はHTTPプロトコルを受け付けるサーバ名を指定してください。
(2)コンパイル時に「ws2_32.lib」をリンクしてください。
**********************************************************************/

#define REQUEST_INFO
const char *servername="xxx.yyy.zzz";


=この部分共通なので省略=
=この部分共通なので省略=

#define sndBufSize 1024
#define rcvBufSize 65536

/*サーバにhttpプロトコルのgetコマンドにリプライさせる*/
int getHTML(const char *server)
{
    SOCKET sock;
    static char sndBuf[sndBufSize],rcvBuf[rcvBufSize];
    /*HTTPサーバへの接続*/
    sock=connectSocket(server,"http",80);
    if (sock == INVALID_SOCKET) return 1;
    printf("connecting Socket\n");

    /*サーバへの文字列送信とリプライ受信*/
    sprintf(sndBuf,"GET /\r\n");
    sendData(sock, sndBuf);
    printf("sending   : %s",sndBuf);
    printf("receiving : \n");
    while (receiveData(sock,rcvBuf,rcvBufSize)) {
        printf("%s",rcvBuf);
    }
    printf("\n");

    closesocket(sock);
    WSACleanup();
    return 0;
}

int main()
{
    getHTML(servername);
    return 0;
}

実行結果 (薄い色の文字の部分は初期化関数内で得られた情報)

== winsock information ==
winsock version   = 1.1
winsock HighVer   = 2.2
Description       = WinSock 2.0
SystemStatus      = Running
Max Sockets       = 32767
Max UDP byte size = 65467

== server information ==
host name      = xxx.yyy.zzz
address type   = 2
address length = 4
IP address     = 172.16.169.1

== service information ==
service name = http
aliases      = www
aliases      = www-http
port number  = 20480
protocol     = tcp

== port number information ==
port number = 80

connecting Socket
sending   : GET /
receiving :
<!---------------------------------------------------------------------------->
<!--                                                                        -->
<!--    HomePage of Department of Computer Science                          -->
<!--                                                                        -->
<!---------------------------------------------------------------------------->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<META http-equiv="Content-Type" content="text/html; charset=Shift_JIS">
<META name="GENERATOR" content="IBM WebSphere Studio Homepage Builder Version 7.
0.0.0 for Windows">
<META http-equiv="Content-Style-Type" content="text/css">

<title>ホームページ</title>
</head>
<BODY background="images/cs_logo.jpg" BGPROPERTIES="FIXED" bgcolor="#ffffff" tex
t="black" link="#4040ff" alink="#ff0000" vlink="#800080"><OBJECT classid="clsid:
369303c2-d7ac-11d0-89d5-00a0c90833e6" id="sample" style="position:absolute;width
:200;height:220;
  top : 2px;
  left : 19px;
">
:以下省略

課題 次のUNIXマシンにhttp接続を試みるプログラムを作りなさい。
ただし,接続するマシンのドメイン名,URLはコマンドラインから取得しなさい。
tnct20.ens.tokyo-ct.ac.jp,xythos.tokyo-ct.ac.jp

 

【5】fingerプロトコル

5.1 telnetによるfingerプロトコルの検証

Windowsのコマンドラインコマンドであるtelnetを使って,fingerサーバに接続してみる。telnetコマンドの使い方は
「telnet  サーバドメイン名  ポート番号」なのでサーバドメイン名にはxxx.yyy.zzz,ポート番号には79を指定している。
なおfinger接続では自分がユーザ登録しているサーバマシンに対して,ユーザ名を送信しなければならない。この条件を満たすサーバマシンに接続する必要がある。
fingerサーバは79番ポートを見張っていて,このポートに接続してきたらユーザ名を受け取り,正規ユーザ名であったら,そのユーザに関する情報を送信し,接続を切断するようになっている。

RFC 1288 参照

telnetによるfingerプロトコルの検証
正規ユーザ名taroについての問い合わせに対して最後にログインした時刻とターミナル名を応答している

>telnet xxx.yyy.zzz 79

taro<RET>


Login       Name               TTY         Idle    When    Where
taro            ???            804          <Sep 16 21:59> happyday.tokyo-ct.ac


ホストとの接続が切断されました。

telnetによるfingerプロトコルの検証
不正ユーザ名huseiuserについての問い合わせに対して答えられずに困っている。

>telnet xxx.yyy.zzz 79

huseiuser<RET>



Login       Name               TTY         Idle    When    Where
huseiuser         ???


ホストとの接続が切断されました。

課題 UNIXマシンにfinger接続を試みなさい。
2010年3月現在,このサービスを行っているサーバマシンは学内には存在しない。

 

5.2 winsock2を用いたfingerクライアントのプログラミング

VC++でANSIのCプログラムを作り,リンク時に「ws2_32.lib」をリンクするようにする。(「プロジェクト」メニュー→「設定」コマンド→「リンク」タブ→「ライブラリモジュール」に「ws2_32.lib」を追加する)

「winsockでのソケット通信の初期化,サーバのドメイン名からIPアドレスの取得,サービス名からポート番号の取得,ソケットオープン, コネクト」は一連の動作なので,1関数(connectSocket(),この関数はすべてに共通)にまとめ,接続後,サーバからの応答(日時情報)を受 け取り,closesocket(sock),WSACleanup()の2つの関数で後処理を行なっている。

finger.c (プログラムで共通部分は省略) プログラムダウンロード

/**********************************************************************
Winsock2を用いたFingerサンプルプログラム
このサンプルプログラムはサーバに日付時刻をリプライさせます。
(1)サーバ名はFingerプロトコルを受け付けるサーバ名を指定してください。
(2)コンパイル時に「ws2_32.lib」をリンクしてください。
**********************************************************************/

#define REQUEST_INFO
const char *servername="xxx.yyy.zzz";
const char *user="taro";


=この部分共通なので省略=
=この部分共通なので省略=


#define sndBufSize 1024
#define rcvBufSize 1024

/*サーバにFingerプロトコルでリプライさせる*/
int finger(const char *server,const char *user)
{
    SOCKET sock;
    static char sndBuf[sndBufSize],rcvBuf[rcvBufSize];
    /*Fingerサーバへの接続*/
    sock=connectSocket(server,"finger",IPPORT_FINGER);
    if (sock == INVALID_SOCKET) return 1;
    printf("connecting Socket\n");

    /*サーバへの文字列送信とリプライ受信*/
    sprintf(sndBuf,"%s\r\n",user);
    sendData(sock, sndBuf);
    printf("sending   : %s",sndBuf);

    /*サーバからのリプライ受信*/
    receiveData(sock,rcvBuf,rcvBufSize);
    printf("receiving : %s\n",rcvBuf);

    closesocket(sock);
    WSACleanup();
    return 0;
}

int main()
{
    finger(servername,user);
    return 0;
}

実行結果 (薄い色の文字の部分は初期化関数内で得られた情報)

=== winsock information ==
winsock version   = 1.1
winsock HighVer   = 2.2
Description       = WinSock 2.0
SystemStatus      = Running
Max Sockets       = 32767
Max UDP byte size = 65467

== server information ==
host name      = xxx.yyy.zzz
address type   = 2
address length = 4
IP address     = 172.16.169.1

== service information ==
service name = finger
port number  = 20224
protocol     = tcp

== port number information ==
port number = 79

connecting Socket
sending   : taro
receiving : Login       Name               TTY         Idle    When    Where
taro            ???            96           <Sep 14 12:54> palm.tokyo-ct.ac

課題 UNIXマシンにfinger接続を試みるプログラムを作りなさい。
2010年3月現在,このサービスを行っているサーバマシンは学内には存在しない。

 

【6】pop3

6.1 telnetによるpop3の検証

Windowsのコマンドラインコマンドであるtelnetを使って,pop3サーバに接続してみる。telnetコマンドの使い方は
「telnet  サーバドメイン名  ポート番号」なのでサーバドメイン名にはxxx.yyy.zzz(本校内部のみアクセス可能),ポート番号には110を指定している。

pop3はパソコンのメーラが,pop3サーバに一時保存されたメールを受け取るのに使われているプロトコルである。
パソコンのメーラは、メール受信するのにpop3,送信するときはsmtpを用いている。

pop3接続では自分がユーザ登録しているサーバマシンに対して,ユーザ名とパスワードを送信しなければならない。この条件を満たすサーバマシンに接続する必要がある。
pop3サーバは110番ポートを見張っていて,このポートに接続してきたらユーザ名とパスワードを受け取り,正規ユーザ名であったら,そのユーザに関する情報を送信し,接続を切断するようになっている。

RFC 1939 参照

telnetによるpop3プロトコルの検証(その1)
ユーザ名,パスワードを送り,リストを要求したがメールは来ていないので,終了(QUIT)している

>telnet xxx.yyy.zzz 110

+OK QPOP (version 3.0b18) at xxx starting.
USER taro
+OK Password required for taro.
PASS XXXXXXXX
+OK taro has 0 messages (0 octets).
LIST
+OK 0 messages (0 octets)
.
QUIT
+OK Pop server at xxx signing off.


ホストとの接続が切断されました。

telnetによるpop3プロトコルの検証(その2)
ユーザ名,パスワードを送り,リストを要求し4通のメールが来ているので,
1番目のメールを表示し(RETR 1),終了(QUIT)している

>telnet xxx.yyy.zzz 110

+OK aaa.tokyo-ct.ac.jp AAA POP3 3.4.8-GR server ready
USER taro
+OK Name is a valid mailbox
PASS XXXXXXXX
+OK Maildrop locked and ready
LIST
+OK scan listing follows.
1 715
2 1025
3 4123
4 521
.
RETR 1
+OK 715 octets
Return-Path: <taro@aaa.tokyo-ct.ac.jp>
Received: from happyday.tokyo-ct.ac.jp (virusgw.tokyo-ct.ac.jp [172.16.0.33])
        by bbbbb.tokyo-ct.ac.jp (MOS 3.4.8-GR)
        with ESMTP id ADA98601;
        Fri, 17 Sep 2004 10:33:39 +0900 (JST)
Message-Id: <5.1.1.11.2.20040917103320.05df56e0@aaa.tokyo-ct.ac.jp>
X-Sender: taro@aaa.tokyo-ct.ac.jp
X-Mailer: QUALCOMM Windows Eudora Version 5.1-Jr5
Date: Fri, 17 Sep 2004 10:33:38 +0900
To: jiro <taro@tokyo_ct.ac.jp>
From: taro <taro@tokyo_ct.ac.jp>
Subject: test
Mime-Version: 1.0
Content-Type: text/plain; charset="us-ascii"
Content-Transfer-Encoding: 7bit

Hello,
How are you?
Fine day, is't it?
Bye for now.
from taro
.
QUIT
+OK


ホストとの接続が切断されました。

POP3プロトコルのコマンドには以下のものがある。

コマンド 意味
USER [ユーザ名] ユーザ名の指定 USER taro
PASS [パスワード] パスワードの指定 PASS XXXXXXXX
(パスワードが見えてしまうので注意)
LIST POP3サーバが保持しているメール一覧要求
RETR [メッセージ番号] LISTコマンドで得たメール一覧表示の番号で指定するメールの送信要求
POP3サーバからパソコンにメールを受け取る
RETR 1
RETR 3
DELE [メッセージ番号] メール一覧表示の番号で指定するメールの削除
POP3サーバからメールの削除
DELE 1
DELE 3
QUIT 切断要求

課題1 自分が使っているpopサーバにpop3接続を試みなさい。自分のユーザIDとパスワードを使いなさい。
     メールの削除に関しては,テストメールを送っておいて,そのテストメールに対して行いなさい。(大事なメールを消さないように)
     (yahooメールなどでもpop3サービスがある。pop3サービスの設定をして試みなさい。ただし,学内LAN接続のPCからはこの接続はできません。)

 

6.2 winsock2を用いたpop3クライアントのプログラミング

VC++でANSIのCプログラムを作り,リンク時に「ws2_32.lib」をリンクするようにする。(「プロジェクト」メニュー→「設定」コマンド→「リンク」タブ→「ライブラリモジュール」に「ws2_32.lib」を追加する)

「winsockでのソケット通信の初期化,サーバのドメイン名からIPアドレスの取得,サービス名からポート番号の取得,ソケットオープン, コネクト」は一連の動作なので,1関数(connectSocket(),この関数はすべてに共通)にまとめ,接続後,サーバからの応答(日時情報)を受 け取り,closesocket(sock),WSACleanup()の2つの関数で後処理を行なっている。
本来は対話型で,メール一覧を見て,メールを受け取ったり,削除したりが出来なければならないが,このテストプログラムでは,リスト表示しただけで終了している。
またこのプログラムでは,パスワードをプログラム中に記述しているが,動作を確認したら,パスワードの部分は消しておくこと。実行結果がファイルになっている場合はパスワードの部分を消しておくこと。

pop3.c (プログラムで共通部分は省略) プログラムダウンロード

/**********************************************************************
Winsock2を用いたPop3サンプルプログラム
このサンプルプログラムはpop3サーバにloginし,
メールリストを表示して終了します。
(1)サーバ名はpopプロトコルを受け付けるサーバ名を指定してください。
(2)username,passwordは自分のものを使ってください。
(3)コンパイル時に「ws2_32.lib」をリンクしてください。
**********************************************************************/

#define REQUEST_INFO
const char *servername="xxx.yyy.zzz";
const char *username="watashi";
const char *password="xxxxxxxx";


=この部分共通なので省略=
=この部分共通なので省略=



#define sndBufSize 1024
#define rcvBufSize 1024

/*loginし,メールリストを表示して終了する*/
int pop3(const char *server,const char *user, const char *pass)
{
    SOCKET sock;
    static char sndBuf[sndBufSize],rcvBuf[rcvBufSize];
    /*POPサーバへの接続*/
    sock=connectSocket(server,"pop3",110);
    if (sock == INVALID_SOCKET) return 1;
    printf("connecting Socket\n");

    /*サーバから受信*/
    receiveData(sock,rcvBuf,rcvBufSize);
    printf("receiving : %s\n",rcvBuf);

    /*ユーザlogin*/
    sprintf(sndBuf,"USER %s\r\n",user);
    sendData(sock, sndBuf);
    printf("sending   : %s",sndBuf);

    receiveData(sock,rcvBuf,rcvBufSize);
    printf("receiving : %s\n",rcvBuf);

    /*パスワード*/
    sprintf(sndBuf,"PASS %s\r\n",pass);
    sendData(sock, sndBuf);
    printf("sending   : %s",sndBuf);

    receiveData(sock,rcvBuf,rcvBufSize);
    printf("receiving : %s\n",rcvBuf);

    /*メールリストの取得*/
    sprintf(sndBuf,"LIST\r\n");
    sendData(sock, sndBuf);
    printf("sending   : %s",sndBuf);
    Sleep(500);

    receiveData(sock,rcvBuf,rcvBufSize);
    printf("receiving : %s\n",rcvBuf);

    /*終了*/
    sprintf(sndBuf,"QUIT\r\n");
    sendData(sock, sndBuf);
    printf("sending   : %s",sndBuf);
    Sleep(500);

    receiveData(sock,rcvBuf,rcvBufSize);
    printf("receiving : %s\n",rcvBuf);

    closesocket(sock);
    WSACleanup();
    return 0;
}

int main()
{
    pop3(servername,username,password);
    return 0;
}

実行結果 (薄い色の文字の部分は初期化関数内で得られた情報)
ここでは3通のメールが来ているのがわかる。

host name      = xxx.yyy.zzz
address type   = 2
address length = 4
IP address     = 172.16.0.32

== service information ==
service name = pop3
port number  = 28160
protocol     = tcp

== port number information ==
port number = 110

connecting Socket
receiving : +OK aaa.tokyo-ct.ac.jp bbbbb POP3 3.4.8-GR server ready

sending   : USER watashi
receiving : +OK Name is a valid mailbox

sending   : PASS xxxxxxxx
receiving : +OK Maildrop locked and ready

sending   : LIST
receiving : +OK scan listing follows
1 844
2 1025
3 2652
.

sending   : QUIT
receiving : +OK

課題1 pop3接続するクライアントプログラムを作りなさい。
サーバ名,ユーザ名,パスワードは プログラム起動後に,キーボードから入力し,POP3サーバに接続したら,サーバの保持する着信メール一覧を表示し,対話型でメールを受信し,また対話型 でメールの削除を行なえるようにしなさい。対話コマンドは次の4つとする。(メールの削除は実装しなくてもよい)
ただし,メールは半角英数字のみでできているものとする。
コマンド 意味
LIST サーバが保持するメール一覧の表示
GET n サーバが保持するn番目のメールのパソコンへの取り込みと表示
DELE n サーバが保持するn番目のメールの削除
QUIT 接続終了(プログラム終了)

nはリスト表示されたメールの番号。

課題2 日本語対応のpop3接続するクライアントプログラムを作りなさい。
サーバ名,ユーザ名, パスワードはプログラム起動後に,キーボードから入力し,POP3サーバに接続したら,サーバの保持する着信メール一覧を表示し,対話型でメールを受信 し,また対話型でメールの削除を行なえるようにしなさい。対話コマンドは次の4つとする。(メールの削除は実装しなくてもよい)
日本語はJISコード(ISO-2022-JP)が用いられており,さらに本文以外ではbase64のmime変換が使われている。

参考コード base64のmime変換,逆変換,SJIS-JIS変換
参考プログラム メニュー形式pop3クライアント

 

【7】smtp [Simple Mail Transfer Protocol]
 smtpはメール送信のプロトコルであるが,このままのプロトコルでは,簡単になり済ましメールが送信できてしまうため,
 ユーザIDとパスワードによる認証が使われるようになってきている。また,スパムメール対策のため,プロバイダ間の
 メール転送を制限するため,セキュアポートが使われるようになっている。
 これらの解説を【8】で行う。

7.1 telnetによるsmtpの検証

Windowsのコマンドラインコマンドであるtelnetを使って,smtpサーバに接続してみる。telnetコマンドの使い方は
「telnet  サーバドメイン名  ポート番号」なのでサーバドメイン名にはxxx.yyy.zzz,ポート番号には25を指定している。

smtpはパソコンのメーラが,メールを送信のに使われているプロトコルである。
パソコンのメーラは、メール受信するのにpop3,送信するときはsmtpを用いている。

smtpサーバは25番ポートを見張っていて,このポートに接続してきたらメールを受け取り,あて先に向け送信する。

RFC 2821 参照

telnetによるsmtpプロトコルの検証
taroからjiro宛にテストメールを送信しているところ
なお作業に使っているパソコンのドメイン名はhappyday.tokyo-ct.ac.jp

>telnet xxx.yyy.zzz 25

220 cccc.tokyo-ct.ac.jp ESMTP Sendmail 8.11.4+3.4W/3.7W; Fri, 17 Sep 2004 18:22:01
 +0900 (JST)
HELO happyday.tokyo-ct.ac.jp
250 cccc.tokyo-ct.ac.jp Hello happyday.tokyo-ct.ac.jp [172.16.2.7], pleased to
meet you
MAIL FROM: taro@tokyo_ct.ac.jp
250 2.1.0 taro@tokyo_ct.ac.jp... Sender ok
RCPT TO: jiro@tokyo_ct.ac.jp
250 2.1.5 jiro@tokyo_ct.ac.jp... Recipient ok
DATA
354 Please start mail input.
From: taro
To: jiro
Subject: test

Hello.
This is mail test.
.
250 Mail queued for delivery.
QUIT
221 Closing connection. Good bye.


ホストとの接続が切断されました。

SMTPでは,次のコマンドを順に送信する。

コマンド 意味
HELO [送信パソコンのドメイン名] 接続してきたパソコンの認証に使われる。
実際にはパソコンがドメイン名を持たないこともあり,
smtpサーバのドメイン名を書くこともある
MAIL FROM: [送信者のメールアドレス] 送信者のメールアドレス
RCPT TO: [あて先メールアドレス] 送信先のメールアドレス
このコマンドは複数並べることが出来,同報送信が出来る
DATA

ここから先,送信文
送信文では空白行までがヘッダ,その後は本文
ヘッダにはFrom: ,To:, Subject:,CC: などの行が入る
「.」(ピリオド)のみからなる行を送ると,メール本体の終わりを意味する

QUIT 切断

受信されたメール
Return-Path: <taro@tokyo_ct.ac.jp>
Received: from xxx.yyy.zzz (ppp.qqq.ac.jp [172.16.0.33])by aaa.bbb.ac.jp (MOS 3.4.8-GR)with ESMTP id ADB01958;Fri, 17 Sep 2004 18:22:34 +0900 (JST)
Received: from happyday.tokyo-ct.ac.jp (happyday.tokyo-ct.ac.jp [172.16.2.7])by aaa.bbb.ac.jp (8.11.4+3.4W/3.7W) with SMTP id i8HAKX813891for <taro@tokyo_ct.ac.jp>; Fri, 17 Sep 2004 18:22:33 +0900 (JST)
Date: Fri, 17 Sep 2004 19:22:01 +0900 (JST)
Message-Id: <200409171020.i8HAKX813891@cccc.tokyo-ct.ac.jp>
Subject: test
To: jiro
From: taro

Hello.
THis is mail test.

課題 テスト用smtpサーバが使える場合にはsmtp接続を試みなさい。自分あてのメールを上記の例のように送信しなさい。
サブジェクト,メール本文には日本語を含めてはいけない。
6.2で作ったpop3クライアントで受信し動作を確認しなさい。
自分や友人の携帯電話メールアドレスや,インターネットプロバイダの自分のメールアドレスなどに送信し動作を確かめてもよい。
(注意 実用になっているsmtpサーバは認証を求めることが多いため,この課題は演習できないかもしれない)

7.2 winsock2を用いたsmtpクライアントのプログラミング

VC++でANSIのCプログラムを作り,リンク時に「ws2_32.lib」をリンクするようにする。(「プロジェクト」メニュー→「設定」コマンド→「リンク」タブ→「ライブラリモジュール」に「ws2_32.lib」を追加する)

「winsockでのソケット通信の初期化,サーバのドメイン名からIPアドレスの取得,サービス名からポート番号の取得,ソケットオープン, コネクト」は一連の動作なので,1関数(connectSocket(),この関数はすべてに共通)にまとめ,接続後,サーバからの応答(日時情報)を受 け取り,closesocket(sock),WSACleanup()の2つの関数で後処理を行なっている。

smtp.c (プログラムで共通部分は省略) プログラムダウンロード

/**********************************************************************
Winsock2を用いたSMTPサンプルプログラム
このサンプルプログラムはSMTPサーバを介してテストメールを自分のメールア
ドレスに送付します。
(1)smtpサーバ名,自分のパソコン名,自分のメールアドレスは正しいもの
を指定してください。
(2)インターネット上のSMTPサーバでは, POPサーバに一度ログインして認
証を受けないとメール受付を拒否するようになっています。通常のメーラで送
受信すると認証が有効になり,一定時間メールを受け付けるようになるので,
このソフトを試すことができるようになります。
(3)コンパイル時に「ws2_32.lib」をリンクしてください。
(4)本文で2バイトコード(漢字コードなど)を送信することは出来ません。
送信する場合はJISコードを使用し,本文ヘッダにそのことを記述してください。
(5)サブジェクトに2バイトコード(漢字コードなど)を送信することは出来
ません。送信する場合はmime変換をしてください。
**********************************************************************/

#define REQUEST_INFO
const char *smtpservername="aaa.tokyo-ct.ac.jp";
const char *smtpclientname="happyday.tokyo-ct.ac.jp";
const char *mymailaddress="taro@tokyo_ct.ac.jp";


=この部分共通なので省略=
=この部分共通なので省略=



#define sndBufSize 4096
#define rcvBufSize 1024

typedef struct {
    char sender[128];      /*送信者すなわち自分のメールアドレス*/
    char receiver[128];    /*送信先のメールアドレス*/
    char subject[1024];    /*サブジェクト(件名)*/
    char body[sndBufSize]; /*メール本体*/
} mail_t;

/*SMTPサーバsmtpserverにメールmailを送る関数 失敗したら0以外を戻す*/
int sendMail(const char *smtpserver, const char *smtpclient, mail_t *mail)
{
    SOCKET sock;
    static char sndBuf[sndBufSize],rcvBuf[rcvBufSize];

    /*SMTPサーバへの接続とリプライ受信*/
    sock=connectSocket(smtpserver,"mail",IPPORT_SMTP);
    if (sock == INVALID_SOCKET) return 1;
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("connecting Socket\n");
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*"HELO"コマンドの送信とリプライ受信*/
    /*クライアントの認証に失敗する場合はここの部分にsmtpサーバ名を書く*/

    sprintf(sndBuf,"HELO %s \r\n",smtpclient);
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*"MAIL FROM"コマンドの送信とリプライ受信*/
    sprintf(sndBuf,"MAIL FROM:<%s>\r\n",mail->sender);
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*"RCPT TO"コマンドの送信とリプライ受信*/
    /*複数のあて先に同報送信する場合は,RCPT TO"コマンドの送信とリプライ受信をここに並べればよい*/
    sprintf(sndBuf,"RCPT TO:<%s>\r\n",mail->receiver);
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*"DATA"コマンドの送信とリプライ受信*/
    sprintf(sndBuf,"DATA\r\n");
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*本文ヘッダの送信(サブジェクト)*/
    sprintf(sndBuf,"Subject: %s\r\n",mail->subject);
    sendData(sock, sndBuf);
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
    #endif

    /*本文ヘッダの送信(「To」行)*/
    sprintf(sndBuf,"To: %s\r\n",mail->receiver);
    sendData(sock, sndBuf);
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
    #endif

    /*本文ヘッダの送信(「From」行)*/
    sprintf(sndBuf,"From: %s\r\n",mail->sender);
    sendData(sock, sndBuf);
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
    #endif

    /*本文ヘッダの送信(ヘッダ終了の意味の空行)*/
    sprintf(sndBuf,"\r\n");
    sendData(sock, sndBuf);
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
    #endif
    /*そのほかの本文ヘッダがある場合は本文本体送信前に記述すればよい*/

    /*本文本体の送信*/
    sprintf(sndBuf,"%s\r\n",mail->body);
    sendData(sock, sndBuf);
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
    #endif

    /*本文終了マークの送信*/
    sprintf(sndBuf,".\r\n");
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*"QUIT"コマンドの送信とリプライ受信*/
    sprintf(sndBuf,"QUIT\r\n");
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    closesocket(sock);
    WSACleanup();
    return 0;
}

int main()
{
    mail_t mymail;
    strcpy(mymail.subject,"test mail"); /*メールのサブジェクトの設定*/
    strcpy(mymail.body,"test mail body  Hello from Tokyo."); /*メールの本文の設定*/
    strcpy(mymail.sender,mymailaddress); /*送信者アドレスの設定*/
    strcpy(mymail.receiver,mymailaddress); /*受信者アドレスの設定*/
    sendMail(smtpservername, smtpclientname, &mymail);
    return 0;
}

実行結果 (薄い色の文字の部分は初期化関数内で得られた情報)

== winsock information ==
winsock version   = 1.1
winsock HighVer   = 2.2
Description       = WinSock 2.0
SystemStatus      = Running
Max Sockets       = 32767
Max UDP byte size = 65467

== server information ==
host name      = aaa.tokyo-ct.ac.jp
address type   = 2
address length = 4
IP address     = 172.16.169.1

== service information ==
service name = smtp
aliases      = mail
port number  = 6400
protocol     = tcp

== port number information ==
port number = 25

connecting Socket
receiving : 220 bbb.tokyo-ct.ac.jp ESMTP Sendmail 8.11.4+3.4W/3.7W; Fri, 17 Sep 2
004 19:20:33 +0900 (JST)

sending   : HELO happyday.tokyo-ct.ac.jp
receiving : 250 bbb.tokyo-ct.ac.jp Hello happyday.tokyo-ct.ac.jp [172.16.2.7],
pleased to meet you

sending   : MAIL FROM:<taro@tokyo_ct.ac.jp>
receiving : 250 2.1.0 <taro@tokyo_ct.ac.jp>... Sender ok

sending   : RCPT TO:<jiro@tokyo_ct.ac.jp>
receiving : 250 2.1.5 <jiro@tokyo_ct.ac.jp>... Recipient ok

sending   : DATA
receiving : 354 Please start mail input.

sending   : Subject: test mail
sending   : To: jiro@tokyo_ct.ac.jp
sending   : From: taro@tokyo_ct.ac.jp
sending   :
sending   : test mail body  Hello from Tokyo.
sending   : .
receiving : 250 Mail queued for delivery.

sending   : QUIT
receiving : 221 Closing connection. Good bye.

受信されたメール
Return-Path: <taro@tokyo_ct.ac.jp>
Received: from aaa.tokyo-ct.ac.jp (bbb.tokyo-ct.ac.jp [172.16.0.33])by ccc.tokyo-ct.ac.jp (MOS 3.4.8-GR)with ESMTP id ADB01958;Fri, 17 Sep 2004 19:20:34 +0900 (JST)
Received: from happyday.tokyo-ct.ac.jp (happyday.tokyo-ct.ac.jp [172.16.2.7])by aaa.tokyo-ct.ac.jp (8.11.4+3.4W/3.7W) with SMTP id i8HAKX815741for <taro@tokyo_ct.ac.jp>; Fri, 17 Sep 2004 19:20:33 +0900 (JST)
Date: Fri, 17 Sep 2004 19:20:33 +0900 (JST)
Message-Id: <200409171020.i8HAKX815741@cccc.tokyo-ct.ac.jp>
Subject: test mail
To: taro@tokyo_ct.ac.jp
From: jiro@tokyo_ct.ac.jp

test mail body  Hello from Tokyo.

課題1 smtpクライアントを作りなさい。
(注意 実用になっているsmtpサーバは認証を求めることが多いため,この課題は演習できないかもしれない)
次の項目はテキストファイルから読み込みなさい。
 サーバドメイン名
 クライアントドメイン名
 あて先メールアドレス
 送信者メールアドレス
 メール本文(ヘッダ(サブジェクトを含む),本文)

サブジェクト,メール本文には日本語を含んではいけません。

テキストファイルの例

#server
aaa.tokyo-ct.ac.jp
#client
happyday.tokyo-ct.ac.jp
#receiver
xxxxxx@tokyo-ct.ac.jp
#sender
yyyyyy@tokyo-at.ac.jp
#body
To: xxxxxx@tokyo-ct.ac.jp
From: yyyyyy@tokyo-at.ac.jp
Subject: test

Hello, How are you?
Do you send an E-mail by your handmade mail application?
From xxxxxxxxxxx
#end

課題2 本文が日本語に対応できるsmtpクライアントを作りなさい。
(注意 実用になっているsmtpサーバは認証を求めることが多いため,この課題は演習できないかもしれない)
(1)メールに使用している文字がJISであることをメール本体のヘッダ部に書くこと

Content-Type: text/plain; charset="ISO-2022-JP"
Subject: This is a test mail from polaris
Content-Transfer-Encoding: 7bit
To: polaris@tokyo-ct.ac.jp
From: polaris@tokyo-ct.ac.jp

(2)メールの本文で漢字コードがある部分ではJISコードに変換すること
(3)アスキーコード部分からJISコード部分へ入る区切りとJISコード部分からアスキーコード部分に戻るところでは区切りのコードを入れること。
(これでもまだサブジェクトの部分は日本語に出来ない)
JISコードへの変換はインターネットで調べなさい。

課題3 いつも使っているメーラでSubjectのところに次の文字列を書き,自分宛に送信し,メーラで受け取って,サブジェクトの部分に何を受け取ったか調べなさい。作業はコピー&ペーストで行ない,タイプミスを避けなさい。
(注意 実用になっているsmtpサーバは認証を求めることが多いため,この課題は演習できないかもしれない)

テストメール


課題4 本文もサブジェクトも日本語に対応できるsmtpクライアントを作りなさい。
(注意 実用になっているsmtpサーバは認証を求めることが多いため,この課題は演習できないかもしれない)
サブジェクトの部分はMIME変換が必要である。
Content-Type: text/plain; charset="ISO-2022-JP"
Subject: テストメール
Content-Transfer-Encoding: 7bit
To: polaris@tokyo-ct.ac.jp
From: polaris@tokyo-ct.ac.jp

JISコードへの変換とmime変換はインターネットで調べなさい。

参考コード1  JISコード変換
参考コード2  base64のmime変換,逆変換,SJIS-JIS変換

 

【8】smtp [Simple Mail Transfer Protocol]+Auth
 smtpはメール送信のプロトコルであるが,裸のプロトコルでは,簡単になり済ましメールが送信できてしまうため,
 ユーザIDとパスワードによる認証が使われるようになってきている。また,スパムメール対策のため,プロバイダ間の
 メール転送を制限するため,セキュアポートが使われるようになっている。
 これらの解説をここで行う。

8.1 telnetによるsmtpの検証

Windowsのコマンドラインコマンドであるtelnetを使って,smtpサーバに接続してみる。telnetコマンドの使い方は
「telnet  サーバドメイン名  ポート番号」なのでサーバドメイン名にはsmtp.yahhoho.ac.jp ,
ポート番号にはプロバイダ間を通過できるセキュアポート587を指定している。
認証なしの場合の最初のコマンドは「HELO」であったが,認証ありの場合は「EHLO」になっている。

smtpはパソコンのメーラが,メールを送信のに使われているプロトコルである。
パソコンのメーラは、メール受信するのにpop3,送信するときはsmtpを用いている。

smtpサーバは587番ポートを見張っていて,このポートに接続してきたらメールを受け取り,あて先に向け送信する。

RFC 2821 参照

telnetによるsmtpプロトコルの検証
taroからjiro宛にテストメールを送信しているところ
なお作業に使っているパソコンのドメイン名はhappyday.tokyo-ct.ac.jp

>telnet smtp.yahhoho.ac.jp 587

220 smtp.yahhoho.ac.jp ESMTP
EHLO happyday.tokyo-ct.ac.jp
250-smtp.yahhoho.ac.jp
250-AUTH LOGIN PLAIN XYMCOOKIE
250-PIPELINING
250 8BITMIME
AUTH PLAIN xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
235 ok, go ahead (#2.0.0)
MAIL FROM: taro@tokyo_ct.ac.jp
250 ok
RCPT TO: jiro@tokyo_ct.ac.jp
250 ok
DATA
354 go ahead
From: taro
To: jiro
Subject: test

Hello.
This is mail test.
.
250 ok aaaaaaaaaaaa qp bbbbb
QUIT
221 smtp.yahhoho.ac.jp


ホストとの接続が切断されました。

SMTPでは,次のコマンドを順に送信する。

コマンド 意味
EHLO [送信パソコンのドメイン名]

接続してきたパソコンの認証に使われる。
EHLOに対してsmtpサーバは認証方式を知らせてくれる。

250-smtp.yahhoho.ac.jp
250-AUTH LOGIN PLAIN XYMCOOKIE
250-PIPELINING
250 8BITMIME

「AUTH LOGIN」と「AUTH PLAIN」が使えることを示している

AUTH PLAIN xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx」のところには,
「認証用ユーザID+'\0'+認証用ユーザID+'\0'+パスワード」の文字列をBASE64で
MIME変換した文字列を入力する。
認証用ユーザIDとパスワードからこの文字列を作るツールを使うとよい。
   生成ツール
MAIL FROM: [送信者のメールアドレス] 送信者のメールアドレス
RCPT TO: [あて先メールアドレス] 送信先のメールアドレス
このコマンドは複数並べることが出来,同報送信が出来る
DATA

ここから先,送信文
送信文では空白行までがヘッダ,その後は本文
ヘッダにはFrom: ,To:, Subject:,CC: などの行が入る
「.」(ピリオド)のみからなる行を送ると,メール本体の終わりを意味する

QUIT 切断

 

課題 テスト用smtpサーバが使える場合にはsmtp接続を試みなさい。自分あてのメールを上記の例のように送信しなさい。
サブジェクト,メール本文には日本語を含めてはいけない。
6.2で作ったpop3クライアントで受信し動作を確認しなさい。
自分や友人の携帯電話メールアドレスや,インターネットプロバイダの自分のメールアドレスなどに送信し動作を確かめてもよい。

8.2 winsock2を用いたsmtpクライアントのプログラミング

VC++でANSIのCプログラムを作り,リンク時に「ws2_32.lib」をリンクするようにする。(「プロジェクト」メニュー→「設定」コマンド→「リンク」タブ→「ライブラリモジュール」に「ws2_32.lib」を追加する)

「winsockでのソケット通信の初期化,サーバのドメイン名からIPアドレスの取得,サービス名からポート番号の取得,ソケットオープン, コネクト」は一連の動作なので,1関数(connectSocket(),この関数はすべてに共通)にまとめ,接続後,サーバからの応答(日時情報)を受 け取り,closesocket(sock),WSACleanup()の2つの関数で後処理を行なっている。

smtpauth.c (プログラムで共通部分は省略) プログラムダウンロード

/**********************************************************************
Winsock2を用いたSMTPサンプルプログラム
このサンプルプログラムはSMTPサーバを介してテストメールを自分のメールア
ドレスに送付します。
(1)smtpサーバ名,自分のパソコン名,自分のメールアドレス,ユーザID,パ
スワードは正しいものを指定してください。
(2)コンパイル時に「ws2_32.lib」をリンクしてください。
(3)本文で2バイトコード(漢字コードなど)を送信することは出来ません。
送信する場合はJISコードを使用し,本文ヘッダにそのことを記述してください。
(4)サブジェクトに2バイトコード(漢字コードなど)を送信することは出来
ません。送信する場合はmime変換をしてください。
**********************************************************************/

#define REQUEST_INFO
const char *smtpservername="smtp.yahhoho.ac.jp ";
const char *smtpclientname="happyday.tokyo-ct.ac.jp";
const char *mymailaddress="
watashi@yahhoho.ac.jp ";
const char *userID="watashi";
const char *passwd="uso800";
const int port=587;


=この部分共通なので省略=
=この部分共通なので省略=



#define sndBufSize 100*1024
#define rcvBufSize 1024

typedef struct {
    char sender[128];      /*送信者すなわち自分のメールアドレス*/
    char receiver[128];    /*送信先のメールアドレス*/
    char subject[1024];    /*サブジェクト(件名)*/
    char body[sndBufSize]; /*メール本体*/
} mail_t;

//mime変換のためのテーブル      0123456789012345678901234567890123456789012345678901234567890123
const char MimeEncodeTable[70]="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

//文字列intext(normal)を文字列otext(mime encoded)に変換
//BASE64を利用したMIME変換
void PureMimeEncode(char *intext, char *otext, int len)
{
    unsigned char *p;
    unsigned long int x,x1;
    unsigned char tmp[8]={0,0,0,0,0,0,0,0};
    char one[4]="A";
    int add,len1,i,j;
    add=0;
    len1=len;
    if (len%3!=0) {
        add= 3-(len%3);
        len1=len/3*3;
    }
    p=(unsigned char *)intext;
    *otext=0;
    for (i=0; i<len1; i+=3) {
        x=(p[0]<<16)+(p[1]<<8)+p[2];
        x1=(x>>18)&0x3f; one[0]=MimeEncodeTable[x1]; strcat(otext,one);
        x1=(x>>12)&0x3f; one[0]=MimeEncodeTable[x1]; strcat(otext,one);
        x1=(x>>06)&0x3f; one[0]=MimeEncodeTable[x1]; strcat(otext,one);
        x1=(   x )&0x3f; one[0]=MimeEncodeTable[x1]; strcat(otext,one);
        p+=3;
    }
    if (add) {
        for (j=0 ; i<len; i++,j++) tmp[j]=*p++;
        x=(tmp[0]<<16)+(tmp[1]<<8)+tmp[2];
        x1=(x>>18)&0x3f; one[0]=MimeEncodeTable[x1]; strcat(otext,one);
        x1=(x>>12)&0x3f; one[0]=MimeEncodeTable[x1]; strcat(otext,one);
        x1=(x>>06)&0x3f; one[0]=MimeEncodeTable[x1]; strcat(otext,one);
        x1=(   x )&0x3f; one[0]=MimeEncodeTable[x1]; strcat(otext,one);
    }
    //add=0 -> del=0, add=1 -> del=1, add=2 -> del=2
    p=otext+strlen(otext)-1;
    for (i=0; i<add; i++) *p--='=';
}

//ユーザIDuserとパスワードpassからauth文字列(mime(user\0user\0pass))を作る
void makeAuthPlain(char *user, char *pass, char *auth)
{
    int num,i;
    char plain[256];
    strcpy(plain,user);
    strcat(plain,"\t");
    strcat(plain,user);
    strcat(plain,"\t");
    strcat(plain,pass);
    //puts(plain);
    num=strlen(plain);
    for (i=0;i<num;i++) if (plain[i]=='\t') plain[i]=0;
    PureMimeEncode(plain,auth,num);
}

typedef struct {
    char smtpserver[128];
    int port;
    char userID[128];
    char passwd[128];
    char authplainstr[128];
} smtp_t;

void setAuthPlainString(smtp_t *smtp)
{
    makeAuthPlain(smtp->userID, smtp->passwd, smtp->authplainstr);
}

/*SMTPサーバsmtpにメールmailを送る関数 失敗したら0以外を戻す*/
int sendMail(smtp_t *smtp, const char *smtpclient, mail_t *mail)
{
    SOCKET sock;
    static char sndBuf[sndBufSize],rcvBuf[rcvBufSize];
    setAuthPlainString(smtp);

    /*SMTPサーバへの接続とリプライ受信*/
    sock=connectSocket(smtp->smtpserver,"secure_smtp",(short int)smtp->port);
    if (sock == INVALID_SOCKET) return 1;
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("connecting Socket\n");
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*"EHLO"コマンドの送信とリプライ受信*/
    /*クライアントの認証に失敗する場合はここの部分にsmtpサーバ名を書く*/
    sprintf(sndBuf,"EHLO %s \r\n",smtpclient);
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*"AUTH PLAIN"コマンドの送信とリプライ受信*/
    sprintf(sndBuf,"AUTH PLAIN %s\r\n",smtp->authplainstr);
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*"MAIL FROM"コマンドの送信とリプライ受信*/
    sprintf(sndBuf,"MAIL FROM:<%s>\r\n",mail->sender);
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*"RCPT TO"コマンドの送信とリプライ受信*/
    /*複数のあて先に同報送信する場合は,RCPT TO"コマンドの送信とリプライ受信をここに並べればよい*/
    sprintf(sndBuf,"RCPT TO:<%s>\r\n",mail->receiver);
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*"DATA"コマンドの送信とリプライ受信*/
    sprintf(sndBuf,"DATA\r\n");
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*本文ヘッダの送信(サブジェクト)*/
    sprintf(sndBuf,"Subject: %s\r\n",mail->subject);
    sendData(sock, sndBuf);
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
    #endif

    /*本文ヘッダの送信(「To」行)*/
    sprintf(sndBuf,"To: %s\r\n",mail->receiver);
    sendData(sock, sndBuf);
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
    #endif

    /*本文ヘッダの送信(「From」行)*/
    sprintf(sndBuf,"From: %s\r\n",mail->sender);
    sendData(sock, sndBuf);
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
    #endif

    /*本文ヘッダの送信(ヘッダ終了の意味の空行)*/
    sprintf(sndBuf,"\r\n");
    sendData(sock, sndBuf);
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
    #endif
    /*そのほかの本文ヘッダがある場合は本文本体送信前に記述すればよい*/

    /*本文本体の送信*/
    sprintf(sndBuf,"%s\r\n",mail->body);
    sendData(sock, sndBuf);
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
    #endif

    /*本文終了マークの送信*/
    sprintf(sndBuf,".\r\n");
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    /*"QUIT"コマンドの送信とリプライ受信*/
    sprintf(sndBuf,"QUIT\r\n");
    sendData(sock, sndBuf);
    receiveData(sock,rcvBuf,rcvBufSize);
    if ('3'<rcvBuf[0]) {
        printf("** ERROR ** %s\n",rcvBuf);
        closesocket(sock);
        WSACleanup();
        return 1;
    }
    #ifdef REQUEST_INFO /*情報要求がある場合(学習のため)*/
        printf("sending   : %s",sndBuf);
        printf("receiving : %s\n",rcvBuf);
    #endif

    closesocket(sock);
    WSACleanup();
    return 0;
}

int main()
{
    mail_t mymail;
    smtp_t smtp;
    strcpy(mymail.subject,"test mail"); /*メールのサブジェクトの設定*/
    strcpy(mymail.body,"test mail body  Hello from Tokyo."); /*メールの本文の設定*/
    strcpy(mymail.sender,mymailaddress); /*送信者アドレスの設定*/
    strcpy(mymail.receiver,mymailaddress); /*受信者アドレスの設定*/
    strcpy(smtp.smtpserver,smtpservername); /*smtpサーバ名の設定*/
    smtp.port=port; /*ポート番号のセット*/
    strcpy(smtp.userID,userID); /*認証用ユーザIDの設定*/
    strcpy(smtp.passwd,passwd); /*パスワードの設定*/
    sendMail(&smtp, smtpclientname, &mymail);
    return 0;
}

実行結果 (薄い色の文字の部分は初期化関数内で得られた情報)

== winsock information ==
winsock version   = 1.1
winsock HighVer   = 2.2
Description       = WinSock 2.0
SystemStatus      = Running
Max Sockets       = 32767
Max UDP byte size = 65467

== server information ==
host name      = smtp.yahhoho.ac.jp
address type   = 2
address length = 4
IP address     = 203.216.227.209
IP address     = 203.216.247.162

== port number information ==
port number = 587

connecting Socket
receiving : 220 smtp.yahhoho.ac.jp ESMTP

sending   : EHLO happyday.tokyo-ct.ac.jp
receiving : 250-smtp.yahhoho.ac.jp
250-AUTH LOGIN PLAIN XYMCOOKIE
250-PIPELINING
250 8BITMIME

sending   : AUTH PLAIN d2F0YXNoaQB3YXRhc2hpAHVzbzgwMA==
receiving : 235 ok, go ahead (#2.0.0)

sending   : MAIL FROM:<watashi@yahhoho.ac.jp >
receiving : 250 ok

sending   : RCPT TO:<watashi@yahhoho.ac.jp >
receiving : 250 ok

sending   : DATA
receiving : 354 go ahead

sending   : Subject: test mail
sending   : To: watashi@yahhoho.ac.jp
sending   : From: watashi@yahhoho.ac.jp
sending   :
sending   : test mail body  Hello from Tokyo.
sending   : .
receiving : 250 ok 1269338103 qp 88705

sending   : QUIT
receiving : 221 smtp.yahhoho.ac.jp

 

課題1 smtpクライアントを作りなさい。
(注意 実用になっているsmtpサーバは認証を求めることが多いため,この課題は演習できないかもしれない)
次の項目はテキストファイルから読み込みなさい。
 サーバドメイン名
 クライアントドメイン名
 あて先メールアドレス
 送信者メールアドレス
 メール本文(ヘッダ(サブジェクトを含む),本文)

サブジェクト,メール本文には日本語を含んではいけません。

テキストファイルの例

#server
aaa.tokyo-ct.ac.jp
#userID
watashi
#passwd
uso800

#client
happyday.tokyo-ct.ac.jp
#receiver
xxxxxx@tokyo-ct.ac.jp
#sender
watashi@tokyo-at.ac.jp
#body
To: xxxxxx@tokyo-ct.ac.jp
From: watashi@tokyo-at.ac.jp
Subject: test

Hello, How are you?
Do you send an E-mail by your handmade mail application?
From xxxxxxxxxxx
#end

課題2 本文が日本語に対応できるsmtpクライアントを作りなさい。
(注意 実用になっているsmtpサーバは認証を求めることが多いため,この課題は演習できないかもしれない)
(1)メールに使用している文字がJISであることをメール本体のヘッダ部に書くこと

Content-Type: text/plain; charset="ISO-2022-JP"
Subject: This is a test mail from polaris
Content-Transfer-Encoding: 7bit
To: polaris@tokyo-ct.ac.jp
From: polaris@tokyo-ct.ac.jp

(2)メールの本文で漢字コードがある部分ではJISコードに変換すること
(3)アスキーコード部分からJISコード部分へ入る区切りとJISコード部分からアスキーコード部分に戻るところでは区切りのコードを入れること。
(これでもまだサブジェクトの部分は日本語に出来ない)
JISコードへの変換はインターネットで調べなさい。

課題3 いつも使っているメーラでSubjectのところに次の文字列を書き,自分宛に送信し,メーラで受け取って,サブジェクトの部分に何を受け取ったか調べなさい。作業はコピー&ペーストで行ない,タイプミスを避けなさい。
(注意 実用になっているsmtpサーバは認証を求めることが多いため,この課題は演習できないかもしれない)

テストメール


課題4 本文もサブジェクトも日本語に対応できるsmtpクライアントを作りなさい。
(注意 実用になっているsmtpサーバは認証を求めることが多いため,この課題は演習できないかもしれない)
サブジェクトの部分はMIME変換が必要である。
Content-Type: text/plain; charset="ISO-2022-JP"
Subject: テストメール
Content-Transfer-Encoding: 7bit
To: polaris@tokyo-ct.ac.jp
From: polaris@tokyo-ct.ac.jp

JISコードへの変換とmime変換はインターネットで調べなさい。

参考コード1  JISコード変換
参考コード2  base64のmime変換,逆変換,SJIS-JIS変換
生成ツール  AUTH PLAIN 文字列生成ツール


ここまでたどり着いた君は,メールの世界で悪いことが出来ます。しかし,ここまでの知識で送信した悪ふざけメールは発信元の追跡調査が可能なようになっています。君の知識を悪用しないでください。 
(メールのヘッダ部に送信者のパソコンの名前が自動的に付きます。どこのsmtpサーバが受信したものかもわかります。)
(迷惑メールは刑事事件になります。民事事件になると損害賠償もあります。)