山本ワールド
Windowsプログラミング
アルゴリズム Vitual C++ 2008/2013によるWin32/Win64 APIレベルのプログラム 基礎 Vitual C++ 2008/2013によるAPIレベルのプログラム(32/64bit) Wix3でインストーラーを作る Visual C++ 2008 Standard Editonによるフォームアプリケーションのプログラム(32/64bit) Vitual C++ 2008 Standard EditonによるAPIレベルのプログラム(32/64bit) Windows 7対応 Visual C++ 2008 ExpressによるAPIレベルのプログラム Visual C++ 2005 ExpressによるAPIレベルのプログラム Visual C++ Versiosn 5 BORLAND C++ Windowsプログラム全般 Excel VBA その他メールの送信テスト(CRAM-MD5認証)
概要
SMTPサーバーを介してメールを送信するコマンドプロンプトで動作するプログラムである。認証方式はCRAM-MD認証である。
PLAIN認証は簡単に説明するとユーザー名とパスワードをBase64でエンコードして送信する。盗聴された場合、平文ではないので少しは秘匿性があるが、Base64でデコードすれば完全にユーザー名とパスワード名が暴露してしまう危険がある。
CRAM-MD5認証は、サーバーから送られてきた文字列をパスワードをキーとするHMAC-MD5ハッシュ値をBase64でエンコードして送信するので、盗聴されていた場合ユーザー名はPLAIN認証同様、暴露する危険があるが、パスワードは送信されていないので、PLAIN認証より安全である。
送信内容は、プログラムソースのグローバール変数で定義する必要がある。(ソースコードをダウンロードし本文中に示すソースコードの赤字の行の""の中の部分を追加しコンパイルすること。)
ソースコードでコメントアウトしている#define INFO_DISP 1を有効にすると下記で説明する「CRAM-MD5認証でメールを送信する方法」にような送受信の様子が標準出力に出力される。
なお本プログラムソースの、メールの送信テスト(PLAIN認証)との違いは、#define CRAM_MD5 1をコメントアウトしていない点だけである。
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);
}
Copyright (C) 2012 山本ワールド All Rights Reserved.