概要

SMTPサーバーを介してメールを送信するコマンドプロンプトで動作するプログラムである。認証方式はCRAM-MD認証である。
PLAIN認証は簡単に説明するとユーザー名とパスワードをBase64でエンコードして送信する。盗聴された場合、平文ではないので少しは秘匿性があるが、Base64でデコードすれば完全にユーザー名とパスワード名が暴露してしまう危険がある。
CRAM-MD5認証は、サーバーから送られてきた文字列をパスワードをキーとするHMAC-MD5ハッシュ値をBase64でエンコードして送信するので、盗聴されていた場合ユーザー名はPLAIN認証同様、暴露する危険があるが、パスワードは送信されていないので、PLAIN認証より安全である。
送信内容は、プログラムソースのグローバール変数で定義する必要がある。(ソースコードをダウンロードし本文中に示すソースコードの赤字の行の""の中の部分を追加しコンパイルすること。)
ソースコードでコメントアウトしている#define INFO_DISP 1を有効にすると下記で説明する「CRAM-MD5認証でメールを送信する方法」にような送受信の様子が標準出力に出力される。
なお本プログラムソースの、メールの送信テスト(PLAIN認証)との違いは、#define CRAM_MD5 1をコメントアウトしていない点だけである。

テスト環境

コンパイラ

Visual C++ 2013 Express 32bit/64bit

実行環境

Windows 7 Enterprise Service Pack 1 64bit(Sandy Bridge-E)

CRAM-MD5認証でメールを送信する方法

サーバー名等の定義

説明に当たり、以下のような架空の名称を定義する。

SMTPサーバー名 testserver.com
送信者アドレス send@testserver.com
送信先アドレス recv@virtualserver.com
SMTPサーバーログインユーザー名 user
SMTPサーバーパスワード名 pass
応答サーバー名 good.last.com
送信文章 吾輩は猫である。名前はまだ無い。
上記と対応するWindows Liveメールの設定例は以下のとおりである。


以下の文章で赤字がサーバーへの送信文字列青字がサーバーからの受信文字列である。
\0はバイト値が0を示し、\r\nはバイト値が0x0d 0x0aで改行を示す。

winsockへの接続

SMTPサーバーとの接続および送受信は、winsockを用いている。
winsockでSMTPサーバーへ接続する。接続されると例えば以下のような文字列がサーバーから送信されるのでクライアントで受信する。
220 good.last.com ESMTP Sendmail 8.14.5/8.14.5; Sun, 30 Nov 2014 07:52:13 +0900 (JST)
先頭文字が3以下なので正常に接続されたと判断できる。

EHLOコマンドの送信

EHLO サーバー名 を送信する。
EHLO testserver.com\r\n
サーバーは、サポート指定拡張情報を送信してくるのでクライアントで受信する。
recv:250-good.last.com Hello , pleased to meet you
250-ENHANCEDSTATUSCODES
250-PIPELINING
250-8BITMIME
250-SIZE 209715200
250-DSN
250-AUTH CRAM-MD5 DIGEST-MD5 LOGIN PLAIN
250-STARTTLS
250-DELIVERBY
250 HELP
認証方法等の情報が得られる。

CRAM-MD5認証

ここではCRAM-MD5認証を行うので、サーバーへ AUTH CRAM-MD5\r\nを送信する。
AUTH CRAM-MD5\r\n
サーバーから結果が送信されてくるので、クライアントは受信する。結果が以下のように先頭文字が3以下で始まっていれば正常値である。 続く文字列は、Base64でエンコードされた、タイムスタンプ及びサーバー名である。

334 PDEyMzQ1Njc4OS4wMTJAdGVzdHNlcnZlci5jb20+
受信したBase64でエンコードされた文字列「PDEyMzQ1Njc4OS4wMTJAdGVzdHNlcnZlci5jb20+」をデコードして元の文字列に戻した結果は次のとおりである。
<123456789.012@testserver.com>
パスワード値をキーとして上記の文字列のHMAC-MD5ハッシュ値を求めこれを16進数で表すと次のようになる。
6809c881bb5115413420c6b7b6aa7ec5
文字列「ユーザー名 ハッシュ値の16進数」をBase64でエンコードしてサーバーへ送信する。
ユーザー名 ハッシュ値の16進数
user 6809c881bb5115413420c6b7b6aa7ec5
Base64でエンコード
dXNlciA2ODA5Yzg4MWJiNTExNTQxMzQyMGM2YjdiNmFhN2VjNQ==
サーバーへ送信
dXNlciA2ODA5Yzg4MWJiNTExNTQxMzQyMGM2YjdiNmFhN2VjNQ==\r\n
サーバーからの応答を受信する。結果が以下のように3以下で始まっていれば正常値である。

235 2.0.0 OK Authenticated

エラーの通知先(送信者)の設定

エラーの通知先(送信者)のメールアドレスを以下のような文字列でサーバーへ送信する。
MAIL FORM:<エラーの通知先>\r\n

MAIL FORM<send@testserver.com>\r\n
サーバーから応答が送信されるのでクライアントで受信する。以下のように先頭文字が3以下で始まっていれば正常に動作している。

250 2.1.0 <send@testserver.com>... Sender ok

メールの送信先の設定

メールの送信先のメールアドレスは以下のような文字列をサーバーへ送信する。
RCPT TO:<メール送信先>\r\n

RCPT TO:<recv@virtualserver.com>\r\n
サーバーから応答が送信されるのでクライアントで受信する。以下のように先頭文字が3以下で始まっていれば正常に動作している。

250 2.1.5 <recv@virtualserver.com>... Recipient ok

メール本文の送信

DATA\r\nをサーバーへ送信する。
DATA\r\n
サーバーから応答が送信されるのでクライアントで受信する。以下のように先頭文字が3以下で始まっていれば正常に動作している。
354 Enter mail, end with "." on a line by itself

メールタイトルの送信

メールのタイトルは以下のような文字列でサーバーへ送信する。
Subject: タイトル\r\n
subject: 激励文\r\n
サーバーからの応答はない。

メールヘッダーでの送信先

メールヘッダーで表示される送信先は以下のような文字列でサーバーへ送信する。
To: 送信先\r\n
To: recv@virtualserver.com\r\n
サーバーからの応答はない。

メールヘッダーでの送信者

メールヘッダーで表示される送信者は以下のような文字列でサーバーへ送信する。
From: 送信者\r\n
To: send@testserver.com\r\n
サーバーからの応答はない。

メールヘッダーの終了を送信

メールヘッダーの終了は、空行を送信する。
.\r\n

メール文章の送信

ヘッダー終了後に文章を送信する。改行は\r\nである。文字コードはJISで送信する。

吾輩は猫である。名前はまだ無い。\r\n
文章の送信が終了したら、 .\r\nを送信して本文終了マークを送信する。
.\r\n
サーバーから応答が送信されるのでクライアントで受信する。以下のように先頭文字が3以下で始まっていれば正常に動作している。
250 2.0.0 Message accepted for delivery

メール送信の終了

文字列QUIT\r\nをサーバーへ送信する。
QUIT\r\n
サーバーから応答が送信されるのでクライアントで受信する。以下のように先頭文字が3以下で始まっていれば認証に成功している。
221 2.0.0 good.last.com closing connection

プログラムソースの概要

main

sendMail関数でメールを送信します。

sendMail

UNICODEで動作する場合は、あらかじめユーザー名とパスワードをSJISに変換します。
connectSocket関数でwinsockへ接続します。
PLAIN認証でメールを送信する方法で説明した方法で、メールを送信します。
送信には、sendData(ANSI専用)又はsendStr(漢字対応)関数
受信には、receiveData関数を用います。

connectSocket

WSAStartup関数でwinsockを初期化します。
gethostbyname関数でホスト情報を取得します。
getservbyname関数でポート番号を取得します。取得できない場合は、connectSocket関数の第3引数の値を使用します。
socket関数でwinscokへ接続します。

md5Hashed

指定された文字数の文字列からMD5ハッシュを算出します。詳しくは、 MD5ハッシュを計算(Cryptography API)を参照してください。

hmac_md5

指定されたキーと文字からHMAC-MD5ハッシュを算出します。詳しくは、 HMAC-MD5ハッシュを計算(Cryptography API)を参照してください。

sjis2jis

メールではJISコードを用いますので、マルチバイトで動作する場合は、SJISからJISに変換する必要があります。
MultiByteToWideChar関数で一度UNICODEに変換し、WideCharToMultiByteでUNICODEからJISに変換します。

sendDataSJIS

マルチバイトの文字列をサーバーへ送信します。
マクロによりマルチバイトでコンパイルした場合sendDataの呼び出しが本関数に置き換わります。
sjis2jis関数によりマルチバイト文字をJISに変換し、send関数を呼び出しサーバーへ文字列を送信します。

sendDataUTF16

UNICODEの文字列をサーバーへ送信します。
マクロによりUNICODEでコンパイルした場合sendDataの呼び出しが本関数に置き換わります。
WideCharToMultiByte関数によりUNICODEをJISに変換し、send関数を呼び出しサーバーへ文字列を送信します。

sendData

ANSI文字列をsend関数を呼び出しサーバーへ送信します。

receiveData

recv関数を呼び出しサーバーからデータを受信します。
受信データはANSI文字列を対象としています。

base64_to_6bit

base64の1文字を6bitの値に変換します。
詳しくはBase64エンコード・デコードを参照してください。

base64Decode

Base64を16進数で表した文字列をデコードします。
詳しくはBase64エンコード・デコードを参照してください。

base64Encode

指定した長さのバイナリの配列をBase64でエンコードします。
詳しくはBase64エンコード・デコードを参照してください。

makeAuthPlain

PLAIN認証に必要な文字列をユーザー名・パスワードから作成します。
まずユーザー名とユーザー名とパスワードを\tを間に挟んで結合します。
次に\tを\0に置き換えます。最初から\0を挟んで結合しないのは、\0を挟むとそこで文字列が終了しているとstrlen等が解釈するのを防ぐためです。
次にbase64Encode関数でbase64でエンコードします。

ErrorMessageBox

エラー時に発生するエラーコードを日本語に変換してメッセージボックスに表示します。

ソースコード


//      CRAM-MD5認証でメールを送信するプログラム

#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#include <tchar.h>
#include <wincrypt.h>
#include <locale.h>

#pragma comment(lib, "ws2_32.lib" )
#pragma comment (lib, "crypt32.lib")


//      以下に送信するメールの定義をしてください。

//      SMTPサーバー名
const TCHAR *smtpservername = _TEXT("");
//      送信元のメールアドレス
const TCHAR *smtpclientname = _TEXT("");
//      送信元のメールアドレス
const TCHAR *mymailaddress = _TEXT("");
//      SMTPサーバー ログインユーザー名
const TCHAR *userID = _TEXT("");
//      SMTPサーバー ログインパスワード
const TCHAR *passwd = _TEXT("");
//      SMTPサーバー ポート番号
const int port = 25;
//      メールタイトル
const TCHAR *subject = _TEXT("");
//      送信先
const TCHAR *receivername = _TEXT("");
//const TCHAR *receivername = _TEXT("");
//      メール本文
const TCHAR* body = TEXT("\r\n");


#define CRAM_MD5        1               //CRAM_MD5認証する場合にこの定義を有効にしてください。
//#define INFO_DISP 1                   //SMTMPサーバーとの送受信を確認したい場合は有効にする。

//      ハッシュ値を保存する共用体

union HASH_UINT128{
        UINT64  u64[2];
        UINT32  u32[4];
        BYTE    u8[16];

        void hex(char* s){
                sprintf_s(s, 33, "%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x%0.2x", u8[0], u8[1], u8[2], u8[3], u8[4], u8[5], u8[6], u8[7], u8[8], u8[9], u8[10], u8[11], u8[12], u8[13], u8[14], u8[15]);
        }
};

int sendMail(void);     //      SMTPサーバにメールを送る関数
SOCKET connectSocket(const TCHAR *server, char *servicename, short int service);        //      winsockへ接続
bool md5Hashed(char* szPassword, DWORD dwLength, HASH_UINT128* u128); //        文字列からバイナリのハッシュ値を算出
void hmac_md5(char* key, char* data, HASH_UINT128* dtc);        //      HMAC-MD5を計算
void sjis2jis(char* sjis, char* jis, int jis_max, WCHAR* utf, int utf_max); //  シフトJISをJISに変換する
void sendDataSJIS(SOCKET s, char *data);        //      ソケットsに文字列データdata(sjis)を送信する関数
void sendDataUTF16(SOCKET s, WCHAR *utf);       //      ソケットsに文字列データdata(UNICODE)を送信する関数
void sendData(SOCKET s, char *data);    //      ソケットsに文字列データdataを送信する関数(ANSI専用)
int receiveData(SOCKET s, char *buf, int size); // ソケットsから文字列を取得
void makeAuthPlain(char *user, char *pass, char *auth, int auth_max);   // AUTH PLAIN認証のための文字列作成
void ErrorMessageBox(HWND hWnd, TCHAR* title);
int base64_to_6bit(int c);      //      base64の1文字を6bitの値に変換する
int base64Decode(char* src, int len, char* dtc);        //      base64の文字列srcをデコードしてバイナリ値に変換しdtcに格納
void base64Encode(char* src, char* dtc, int len, int* dtc_len); //      バイナリの配列srcをbase64でエンコードする


//漢字に対応した文字列送信関数
#ifdef UNICODE
        #define sendStr(soc,str) sendDataUTF16(soc,str)
#else
        #define sendStr(soc,str) sendDataSJIS(soc,str)
#endif

#ifdef UNICODE

hostent* gethostbynameW(const TCHAR* serverW){
        char ansi[256];
        WideCharToMultiByte(932, 0, serverW, -1, ansi, sizeof(ansi), NULL, NULL);
        return gethostbyname(ansi);
}
        #define gethostbyname(serverW) gethostbynameW(serverW)
#endif

int main(void){
        _tsetlocale(LC_ALL, _TEXT(""));       //      UNICODE文字を標準出力に正しく表示できるように
        sendMail();     //      メールを送信
        return 0;
}


//      SMTPサーバにメールを送る関数
//      失敗したら0以外を返す

#define sndBufSize 1024
#define rcvBufSize 1024

int sendMail(void){
        SOCKET sock;
        static TCHAR sndBuf[sndBufSize];        //      送信用バッファ
        static char rcvBuf[rcvBufSize];         //      受信用バッファ
#if UNICODE
        char userIDA[256];
        char passwdA[256];
        WideCharToMultiByte(932, 0, (LPWSTR)::userID, -1, userIDA, sizeof(userIDA), NULL, NULL);
        WideCharToMultiByte(932, 0, (LPWSTR)::passwd, -1, passwdA, sizeof(passwdA), NULL, NULL);

        char ansi[256];
        int ansi_max = sizeof(ansi);
#else
        char* ansi = sndBuf;
        int ansi_max = sizeof(sndBuf);
        char* userIDA;
        char* passwdA;
        userIDA = ::userID;
        passwdA = ::passwd;
#endif

        //      SMTPサーバへ接続
        sock = connectSocket(::smtpservername, "secure_smtp", (short int)::port);
        if (sock == INVALID_SOCKET){
                ErrorMessageBox(0, _TEXT("Error"));
                return 1;
        }
        receiveData(sock, rcvBuf, rcvBufSize);  //      SMTPサーバーから受信
        if ('3'<rcvBuf[0]) { //      先頭が220 の場合、準備完了を示す 4?? 5??の場合エラーを示す
                MessageBoxA(0, rcvBuf, "connectSocket error", MB_OK);
                closesocket(sock);
                WSACleanup();
                return 1;
        }

        // EHLO コマンドを送信し通信開始を宣言する
        _stprintf_s(sndBuf, sizeof(sndBuf) / sizeof(TCHAR), _TEXT("EHLO %s \r\n"), ::smtpservername);
        sendStr(sock, sndBuf);
        receiveData(sock, rcvBuf, rcvBufSize);  // 正常に動作した場合 使用できる認証方法が返ってくる
        if ('3'<rcvBuf[0]) { // 250が返ってくれば正常 4?? 5??の場合エラーを示す
                MessageBoxA(0, rcvBuf, "EHLO error", MB_OK);
                closesocket(sock);
                WSACleanup();
                return 1;
        }

#ifndef CRAM_MD5
        // AUTH PLAIN 認証
        char authplainstr[128];
        makeAuthPlain(userIDA, passwdA, authplainstr, sizeof(authplainstr));

        sprintf_s(ansi, ansi_max, "AUTH PLAIN %s\r\n", authplainstr);
        sendData(sock, ansi);
        receiveData(sock, rcvBuf, rcvBufSize);
        if ('3'<rcvBuf[0]) {
                MessageBoxA(0, rcvBuf, "AUTH PLAIN error", MB_OK);
                closesocket(sock);
                WSACleanup();
                return 1;
        }
#else
        //      AUTH CRAM-MD認証を要求する
        sendData(sock, "AUTH CRAM-MD5\r\n");
        receiveData(sock, rcvBuf, rcvBufSize);  //      SMTPサーバーから 応答コードと タイムスタンプとサーバー名等をbase64で返されていくる
        if ('3'<rcvBuf[0]) { //      応答コードが3??の場合、コマンドは正常に動作し次の送信を要求している
                MessageBoxA(0, rcvBuf, "AUTH CRAM-MD5 error", MB_OK);
                closesocket(sock);
                WSACleanup();
                return 1;
        }
        char buf[1024];
        // SMTPサーバーから返された タイムスタンプとサーバー名等をbase64でデコードする
        base64Decode(rcvBuf + 4, (int)strlen(rcvBuf + 4), buf);

        HASH_UINT128 hash;

        // 上記でbase64でデコードされたものをパスワードをキーとするHMAC-MD5を算出する
        hmac_md5((char*)passwdA, buf, &hash);
        char hexs[34];
        hash.hex(hexs); //      ハッシュ値を16進数文字列に変換する
        char cram_md5[256];
        //      ユーザーID ハッシュ値の16進数文字列を作成
        sprintf_s(cram_md5, sizeof(cram_md5), "%s %s", (char*)userIDA, hexs);
        char b64[256];
        int len = sizeof(b64);
        //      上記をbase64でエンコードする
        base64Encode(cram_md5, b64, (int)strlen(cram_md5), &len);
        int num = 256;
        //      改行等を付加
        sprintf_s(buf, sizeof(buf), "%s\r\n", b64);
        //      SMTPサーバーへ送信する
        sendData(sock, buf);
        //      SMTPサーバーの応答を確認する
        receiveData(sock, rcvBuf, rcvBufSize);
        if ('3'<rcvBuf[0]) { //      通常 2??が返ってくる
                MessageBoxA(0, rcvBuf, "認証 error", MB_OK);
                closesocket(sock);
                WSACleanup();
                return 1;
        }

#endif
        // MAIL FROMコマンドの送信 送信元(エラーの通知先)を指定
        _stprintf_s(sndBuf, sizeof(sndBuf) / sizeof(TCHAR), _TEXT("MAIL FROM:<%s>\r\n"), ::mymailaddress);      //      送信者
        sendStr(sock, sndBuf);
        receiveData(sock, rcvBuf, rcvBufSize);
        if ('3'<rcvBuf[0]) { //      4 ? ? 5 ? ? の場合エラーを示す
                MessageBoxA(0, rcvBuf, "MAIL FORM error", MB_OK);
                closesocket(sock);
                WSACleanup();
                return 1;
        }

        // RCPT TOコマンドの送信 メールの送信先を指定する 複数の宛先に送信する場合はここからreceiveDataを繰り返す。
        _stprintf_s(sndBuf, sizeof(sndBuf) / sizeof(TCHAR), _TEXT("RCPT TO:<%s>\r\n"), ::receivername);
        sendStr(sock, sndBuf);
        receiveData(sock, rcvBuf, rcvBufSize);
        if ('3'<rcvBuf[0]) { //      4 ? ? 5 ? ? の場合エラーを示す
                MessageBoxA(0, rcvBuf, "RCPT TO error", MB_OK);
                closesocket(sock);
                WSACleanup();
                return 1;
        }

        //      DATAコマンドの送信 メール本文を開始
        sendData(sock, "DATA\r\n");
        receiveData(sock, rcvBuf, rcvBufSize);
        if ('3'<rcvBuf[0]) { //      4 ? ? 5 ? ? の場合エラーを示す
                MessageBoxA(0, rcvBuf, "DATA error", MB_OK);
                closesocket(sock);
                WSACleanup();
                return 1;
        }

        //      本文ヘッダの送信(サブジェクト:タイトル)*/
        _stprintf_s(sndBuf, sizeof(sndBuf) / sizeof(TCHAR), _TEXT("Subject: %s\r\n"), ::subject);
        sendStr(sock, sndBuf);
        //      本文ヘッダの送信  To
        _stprintf_s(sndBuf, sizeof(sndBuf) / sizeof(TCHAR), _TEXT("To: %s\r\n"), ::receivername);
        sendStr(sock, sndBuf);

        //      本文ヘッダの送信 From
        _stprintf_s(sndBuf, sizeof(sndBuf) / sizeof(TCHAR), _TEXT("From: %s\r\n"), ::mymailaddress);
        sendStr(sock, sndBuf);

        //      ヘッダ終了を知らせるため空行を送信
        sprintf_s(ansi, ansi_max, "\r\n");
        sendData(sock, ansi);
        /*そのほかの本文ヘッダがある場合は本文本体送信前に記述すればよい*/

        //      メール文章を送信
        _stprintf_s(sndBuf, sizeof(sndBuf) / sizeof(TCHAR), _TEXT("%s\r\n"), ::body);
        sendStr(sock, sndBuf);

        //      本文終了マークを送信
        sendData(sock, ".\r\n");
        receiveData(sock, rcvBuf, rcvBufSize);
        if ('3'<rcvBuf[0]) { //      4 ? ? 5 ? ? の場合エラーを示す
                MessageBoxA(0, rcvBuf, "header error", MB_OK);
                closesocket(sock);
                WSACleanup();
                return 1;
        }

        //      QUITコマンドの送信 メールの送信を終了
        sendData(sock, "QUIT\r\n");
        receiveData(sock, rcvBuf, rcvBufSize);
        if ('3'<rcvBuf[0]) { //      4 ? ? 5 ? ? の場合エラーを示す
                MessageBoxA(0, rcvBuf, "QUIT error", MB_OK);
                closesocket(sock);
                WSACleanup();
                return 1;
        }

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

//      winsockへ接続
//      server:ホスト名 ,servicename:サービス名前 ポート番号:service
//      接続できたらsocketを返す。エラーの場合INVALID_SOCKETを返す

SOCKET connectSocket(const TCHAR *server, char *servicename, short int service){
        WSADATA wsaData;
        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) {
                return INVALID_SOCKET;
        }

        //      サーバ名からホストの情報を得る
        lpHost = gethostbyname(server);
        if (lpHost == NULL) {
                WSACleanup();
                return INVALID_SOCKET;
        }

        //      サービス情報からポート番号取得
        lpServ = getservbyname(servicename, NULL);
        if (lpServ == NULL) {
                //      データーの並び順を変換
                port = htons(service);
        }
        else {
                port = lpServ->s_port;
        }

        //      ソケットオープン
        sock = socket(PF_INET, SOCK_STREAM, 0);
        if (sock == INVALID_SOCKET) {
                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) {
                closesocket(sock);
                WSACleanup();
                return INVALID_SOCKET;
        }
        return sock;
}


//      文字列からバイナリのハッシュ値を算出

bool md5Hashed(char* szPassword, DWORD dwLength, HASH_UINT128* u128){
        HCRYPTPROV hProv = 0;
        HCRYPTKEY hKey = 0;
        HCRYPTHASH hHash = 0;
        if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)){
                MessageBox(0, _TEXT("CryptAcquireContext"), _TEXT("Error"), MB_OK);
                return false;
        }
        if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)){
                MessageBox(0, _TEXT("CryptCreateHash"), _TEXT("Error"), MB_OK);
                CryptReleaseContext(hProv, 0);
                return false;
        }
        if (!CryptHashData(hHash, (BYTE*)szPassword, dwLength, 0)){
                MessageBox(0, _TEXT("CryptCreateHash"), _TEXT("Error"), MB_OK);
                CryptDestroyHash(hHash);
                CryptReleaseContext(hProv, 0);
                return false;
        }

        DWORD dwRead = 16;
        if (!CryptGetHashParam(hHash, HP_HASHVAL, (BYTE*)u128, &dwRead, 0)){
                MessageBox(0, _TEXT("CryptGetHashParam"), _TEXT("Error"), MB_OK);
                CryptDestroyHash(hHash);
                CryptReleaseContext(hProv, 0);
                return false;
        }

        CryptDestroyHash(hHash);
        CryptReleaseContext(hProv, 0);
        return true;
}

//      HMAC-MD5を計算

void hmac_md5(char* key, char* data, HASH_UINT128* dtc){
        int n;
        char temp[64 + 64];

        int len = (int)strlen(key);
        strcpy_s(temp, sizeof(temp), key);
        SecureZeroMemory(temp + len, 64 - len);

        for (n = 0; n < 64; n++)
                temp[n] = temp[n] ^ 0x36;
        strcpy_s(temp + 64, sizeof(temp)-64, data);

        md5Hashed(temp, 64 + (int)strlen(data), dtc);
        strcpy_s(temp, sizeof(temp), key);
        SecureZeroMemory(temp + len, 64 - len);

        for (n = 0; n < 64; n++)
                temp[n] = temp[n] ^ 0x5c;

        memcpy(temp + 64, dtc, 16);

        md5Hashed(temp, 64 + 16, dtc);
}

//      シフトJISをJISに変換する

void sjis2jis(char* sjis, char* jis, int jis_max, WCHAR* utf, int utf_max){
        MultiByteToWideChar(932, 0, sjis, -1, (LPWSTR)utf, utf_max);
        WideCharToMultiByte(50220, 0, (LPWSTR)utf, -1, jis, jis_max, NULL, NULL);
}

//      ソケットsに文字列データdata(sjis)を送信する関数
void sendDataSJIS(SOCKET s, char *data){
        WCHAR utf[1024];
        char jis[1024 * 2];
        sjis2jis(data, jis, sizeof(jis), utf, sizeof(utf) / sizeof(WCHAR));
#ifdef INFO_DISP
        printf("send:%s\n", data);
#endif
        send(s, data, (int)strlen(data), 0);
}

//      ソケットsに文字列データdata(UNICODE)を送信する関数
void sendDataUTF16(SOCKET s, WCHAR *utf){
        char jis[1024 * 2];
        WideCharToMultiByte(50220, 0, (LPWSTR)utf, -1, jis, sizeof(jis), NULL, NULL);
#ifdef INFO_DISP
        wprintf(L"send:%s\n", utf);
#endif
        send(s, jis, (int)strlen(jis), 0);
}

//      ソケットsに文字列データdataを送信する関数(ANSI専用)
void sendData(SOCKET s, char *data){
#ifdef INFO_DISP
        printf("send:%s\n", data);
#endif
        send(s, data, (int)strlen(data), 0);
}

// ソケットsから文字列を取得

int receiveData(SOCKET s, char *buf, int size){
        char *p = buf;
        int i;
        for (i = 0; i<size; i++)
                *p++ = 0;
        i= recv(s, buf, size, 0);
#ifdef INFO_DISP
        printf("recv:%s\n",buf);
#endif
        return i;
}


//      base64の1文字を6bitの値に変換する

int base64_to_6bit(int c){
        if (c == '=')
                return 0;
        if (c == '/')
                return 63;
        if (c == '+')
                return 62;
        if (c <= '9')
                return (c - '0') + 52;
        if ('a' <= c)
                return (c - 'a') + 26;
        return (c - 'A');
}

//      base64の文字列srcをデコードしてバイナリ値に変換しdtcに格納
//      lenはbase64の文字数
//      変換後のバイト数を返す

int base64Decode(char* src, int len, char* dtc){
        unsigned o0, o1, o2, o3;
        char* p = dtc;
        for (int n = 0; src[n];){
                o0 = base64_to_6bit(src[n]);
                o1 = base64_to_6bit(src[n + 1]);
                o2 = base64_to_6bit(src[n + 2]);
                o3 = base64_to_6bit(src[n + 3]);

                *p++ = (o0 << 2) | ((o1 & 0x30) >> 4);
                *p++ = ((o1 & 0xf) << 4) | ((o2 & 0x3c) >> 2);
                *p++ = ((o2 & 0x3) << 6) | o3 & 0x3f;
                n += 4;
        }
        *p = 0;
        return int(p - dtc);
}

//      バイナリの配列srcをbase64でエンコードする
//      src:バイナリ配列 len:バイナリ配列長さ
//      dtc:変換後の文字列を保存するアドレス dtc_len:変換後のバイト数を保存するアドレス

void base64Encode(char* src, char* dtc, int len, int* dtc_len){
        //      6bitからbase64の文字へ変換するテーブル
        //                              1         2         3         4         5         6
        //                    0123456789012345678901234567890123456789012345678901234567890123456789
        //                                    1               2               3
        //                    0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF
        const char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

        int n;
        int mod = len % 3;

        int adj_len = len - mod;        //      3の倍数に修正
        char* p = dtc;
        int o0, o1, o2, o3;
        for (n = 0; n < adj_len;){
                UINT32 x = ((unsigned)src[n] << 16) + ((unsigned)src[n + 1] << 8) + unsigned(src[n + 2]);
                o0 = (x >> 18) & 0x3f;
                o1 = (x >> 12) & 0x3f;
                o2 = (x >> 6) & 0x3f;
                o3 = x & 0x3f;
                *p++ = table[o0];
                *p++ = table[o1];
                *p++ = table[o2];
                *p++ = table[o3];
                n += 3;
        }
        if (mod){
                if (1 == mod){
                        UINT32 x = (unsigned)src[n] << 16;
                        o0 = (x >> 18) & 0x3f;
                        o1 = (x >> 12) & 0x3f;
                        *p++ = table[o0];
                        *p++ = table[o1];
                        *p++ = '=';
                        *p++ = '=';
                }
                else if (2 == mod){
                        UINT32 x = ((unsigned)src[n] << 16) + ((unsigned)src[n + 1] << 8);
                        o0 = (x >> 18) & 0x3f;
                        o1 = (x >> 12) & 0x3f;
                        o2 = (x >> 6) & 0x3f;
                        *p++ = table[o0];
                        *p++ = table[o1];
                        *p++ = table[o2];
                        *p++ = '=';
                }
        }
        *p = 0;
        *dtc_len = int(p - dtc);
}


// AUTH PLAIN認証のための文字列作成

void makeAuthPlain(char *user, char *pass, char *auth, int auth_max){
        int num, i;
        char plain[256];
        sprintf_s(plain, sizeof(plain), "%s\t%s\t%s", user, user, pass);
        num = (int)strlen(plain);
        char* p = plain;
        for (i = 0; i<num; i++)
                if (p[i] == _T('\t'))
                        p[i] = 0;
        base64Encode(p, auth, num, &auth_max);
}

//      エラー発生時のエラーコードを文字列に変換してメッセージボックスに表示する

void ErrorMessageBox(HWND hWnd, TCHAR* title){
        TCHAR* lpMsgBuf;
        FormatMessage(
                FORMAT_MESSAGE_ALLOCATE_BUFFER |
                FORMAT_MESSAGE_FROM_SYSTEM |
                FORMAT_MESSAGE_IGNORE_INSERTS,
                NULL,
                GetLastError(),
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // 既定の言語
                (LPTSTR)&lpMsgBuf,
                0,
                NULL
                );
        MessageBox(NULL, lpMsgBuf, title, MB_OK | MB_ICONINFORMATION);
        LocalFree(lpMsgBuf);
}

ソースファイルのダウンロード(送信先等をグローバル変数で定義しなければ使用できません)