C++でソケット通信を用いたメールの送信

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
DAICHI0922
記事: 26
登録日時: 5年前

C++でソケット通信を用いたメールの送信

#1

投稿記事 by DAICHI0922 » 5年前

gmailでsmtpサーバーが使えるということだったのでポート25でホスト名をsmtp.gmal.comにしてソケット通信を試みたのですが、応答がありません。もしくは接続を遮断されてしまいます。

どのようにすればいいのでしょうか。

コード:

#include <stdio.h>
#include <winsock.h>

#pragma comment(lib, "wsock32.lib")

int main(void) {
	int sock, ret;
	struct sockaddr_in addr;
	WSADATA wsadata;
	WSAStartup(0x0101, &wsadata);

	// サーバのIPアドレス取得
	PHOSTENT phostent;                          // サーバの情報を指すポインタ
	unsigned long ulIPAddress;                  // サーバのIPアドレス格納変数
	ulIPAddress = inet_addr("smtp.gmail.com");        // IPアドレス取得
	// inet_addr()関数が失敗したら
	if (ulIPAddress == -1) {
		if ((phostent = gethostbyname("smtp.gmail.com")) != NULL) {
			ulIPAddress = *((unsigned long *)(phostent->h_addr));
		}
		else {
			printf("Can't get host address\n");
			printf("ErrorCode%d\n", WSAGetLastError());
			return -1;
		}
	}

	addr.sin_family = AF_INET;
	addr.sin_addr.s_addr = ulIPAddress;
	addr.sin_port = htons(25);

	sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	printf("connect smtp ...\n");
	ret = connect(sock, (struct sockaddr *)&addr, sizeof addr);
	if (ret < 0) {
		printf("can't open smtp port\n");
		printf("ErrorCode:%d\n", WSAGetLastError());
		return 0;
	}

	char buf[1000];
	int n;

	n = recv(sock, buf, sizeof(buf) - 1, 0);
	buf[n] = '\0';
	printf("S:%s", buf);

	sprintf_s(buf, "HELO %s\r\n", "ホスト名");
	printf("C:%s", buf);
	n = send(sock, buf, strlen(buf), 0);

	n = recv(sock, buf, sizeof(buf) - 1, 0);
	buf[n] = '\0';
	printf("S:%s", buf);

	sprintf_s(buf, "QUIT\r\n");
	printf("C:%s", buf);
	n = send(sock, buf, strlen(buf), 0);

	shutdown(sock, 2);
	closesocket(sock);

	return 1;
}

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: C++でソケット通信を用いたメールの送信

#2

投稿記事 by みけCAT » 5年前

まず、ホスト名が、質問文では「smtp.gmal.com」になっていますが、
提示されたコードでは「smtp.gmail.com」になっています。
実際に実行しているプログラムではどっちになっているでしょうか?

次に、
プリンタ、スキャナ、アプリからメールを送信する - G Suite 管理者 ヘルプ
によると、smtp.gmail.comではポート25は使えないようです。
smtp-relay.gmail.comおよびaspmx.l.google.comではポート25が使えるようなので、
代わりにこれらのホスト名を使うとよさそうであると考えられます。

それでも接続できない場合、プロバイダによるOB25Bの影響が考えられます。
この場合、
OP25B 実施状況 | 迷惑メール対策 | 迷惑メール相談センター
などを参考にOB25Bを実施していないプロバイダまたはインターネット接続サービスを利用する方法が考えられます。

または、smtp-relay.gmail.comの587番ポートに接続するということも考えられます。
ただし、このサーバーでは、手元での実験ではMAIL FROMコマンドがSyntax Errorになってしまいました。

もう一つの方法としては、Googleではなく、
自分のプロバイダやローカルのメールサーバにアクセスするようにすることで、
OB25Bの影響を回避することも考えられます。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: C++でソケット通信を用いたメールの送信

#3

投稿記事 by みけCAT » 5年前

また、sprintf_s()関数が使われていますが、使い方が不適切なようです。
sprintf_s()関数の第二引数には書き込み先のバッファサイズを指定するはずなのに、これがありません。
従って、修正するべきでしょう。
  • バッファサイズを引数に取らないsprintf()関数に変更する
  • 引数にバッファサイズを追加する
  • より多くの環境で使えると考えられるsnprintf()関数に変更した上で、引数にバッファサイズを追加する
などの修正方法が考えられます。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

DAICHI0922
記事: 26
登録日時: 5年前

Re: C++でソケット通信を用いたメールの送信

#4

投稿記事 by DAICHI0922 » 5年前

ありがとうございます。
sprintf_sに関しては...お恥ずかしい。
sprintf_sに第二引数があること自体初めて知りました...

ところで、smtp-relay.gmail.comに587番ポートで接続したところ、応答が帰ってきました。
OKこそ帰ってきていませんが、250はOKと同義?なようで。

しかし、以下のように出力されてからずっと止まったままです。
どうやら、エラーではないもののrecvでずっと固まっているみたいです。

どうなっているんでしょうか?

コード:

--------------出力結果-------------
WinSock初期化成功
ソケット作成成功
ホストアドレス取得成功
サーバとの接続を確立
Request > HELO smtp-relay.gmail.com
サーバーにリクエスト開始...
応答を受信中...
220 smtp-relay.gmail.com ESMTP a64sm675020ywa.7 - gsmtp
250 smtp-relay.gmail.com at your service

コード:

#include <iostream>
#include <string>
#include <winsock.h>

#pragma comment(lib, "wsock32.lib")

#define WIN32_LEAN_AND_MEAN

char ServerName[] = "smtp-relay.gmail.com"; // 接続先のホスト名
unsigned short Port = 587; //smtpポート
using namespace std;

#define BUFSIZE 2600 // バッファサイズ
#define RECVSIZE 65536 // 受信バッファサイズ

int main() {
	// WinSock初期化
	WORD wVersionRequested = MAKEWORD(1, 1);
	WSADATA wsaData;
	int nErrorStatus;

	nErrorStatus = WSAStartup(wVersionRequested, &wsaData);
	if (atexit((void(*)(void))(WSACleanup))) {
		printf("atexit(WSACleanup)失敗\n");
		return -1;
	}
	if (nErrorStatus != 0) {
		printf("初期化失敗です\n");
		return -1;
	}
	cout << "WinSock初期化成功" << endl;

	// ソケット作成
	int soc; // ソケット
	soc = socket(AF_INET, SOCK_STREAM, 0);
	if (soc == INVALID_SOCKET) {
		printf("ソケット作成失敗です\n");
		printf("エラー%dが発生しました\n", WSAGetLastError());
		return -1;
	}
	cout << "ソケット作成成功" << endl;

	// サーバのIPアドレス取得
	PHOSTENT phostent;
	unsigned long ulIPAddress; // サーバのIPアドレス格納変数
	ulIPAddress = inet_addr(ServerName); // IPアドレス取得
	// inet_addr()関数が失敗したら
	if (ulIPAddress == -1) {
		if ((phostent = gethostbyname(ServerName)) != NULL) {
			ulIPAddress = *((unsigned long *)(phostent->h_addr));
		}
		else {
			printf("ホストアドレス取得失敗です\n");
			printf("エラー%dが発生しました\n", WSAGetLastError());
			closesocket(soc);
			return -1;
		}
	}
	cout << "ホストアドレス取得成功" << endl;

	// サーバへ接続
	SOCKADDR_IN addrServer; // サーバのアドレス
	addrServer.sin_family = AF_INET; // アドレスファミリの指定
	addrServer.sin_addr.s_addr = ulIPAddress; // サーバのIPアドレスの指定
	addrServer.sin_port = htons((unsigned short)Port); // ポート番号の指定
	if (connect(soc, (LPSOCKADDR)&addrServer, sizeof(addrServer)) == SOCKET_ERROR) {
		printf("サーバへの接続失敗です\n");
		printf("エラー%dが発生しました\n", WSAGetLastError());
		closesocket(soc);
		return -1;
	}
	cout << "サーバとの接続を確立" << endl;

	// リクエストを送信

	for (;;) {

		string Rec;
		cout << "Request > "; getline(cin, Rec);
		char request[BUFSIZE];
		cout << "\nサーバーにリクエスト開始... (" << Rec << ")" << endl;
		sprintf_s(request, BUFSIZE, "%s\r\n", Rec.c_str());
		if (send(soc, request, sizeof(request), 0) == SOCKET_ERROR) {
			printf("サーバへの送信失敗です\n");
			printf("エラー%dが発生しました\n", WSAGetLastError());
			shutdown(soc, 2);
			closesocket(soc);
			return -1;
		}

		// データの受信
		char recv_buf[RECVSIZE];
		cout << "応答を受信中...\n" << endl;
		for (int t = 0; t < 100; t++) {
			int nBytesRecv = recv(soc, recv_buf, sizeof(recv_buf), 0); //ここで止まったまま
			if (nBytesRecv == SOCKET_ERROR) {
				printf("サーバからの受信失敗です\n");
				printf("エラー%dが発生しました\n", WSAGetLastError());
				break;
			}
			if (nBytesRecv == 0)break;
			recv_buf[nBytesRecv] = '\0'; // 受信バッファの後ろにNULLを付加する
			printf("%s", recv_buf); // 受信した内容を表示
		}
	}

	shutdown(soc, 2);
	closesocket(soc);

	return 0;
}

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: C++でソケット通信を用いたメールの送信

#5

投稿記事 by みけCAT » 5年前

recv関数は、デフォルトでは受信したデータが無い(これまで来た分は全て読みきっている)時は、
新しいデータを受信するまで待つ仕様になっています。
一方、サーバー側では、送るべき応答は返したので、クライアント側からの次のコマンドを待っています。
そのため、デッドロックになっているようです。
これを回避するには、受信したデータをちゃんと見てプロトコルに沿って解釈し、
サーバーの応答が終わってクライアントがデータ(コマンド)を送るタイミングになったら、
recv関数を呼ぶのをやめ、次のデータ(コマンド)を送るようにするべきでしょう。

また、とりあえず止まらないようにするには、ioctlsocket関数を用いて非ブロッキングモードにするといいでしょう。
ノンブロッキングソケット:Geekなぺーじ
データの送受信(標準編)
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

DAICHI0922
記事: 26
登録日時: 5年前

Re: C++でソケット通信を用いたメールの送信

#6

投稿記事 by DAICHI0922 » 5年前

ありがとうございます。

なんとか、SMTPとの会話に持ち込めました。しかし...
MAIL FROMで切断されてしまうようです...

先の返信でもMAIL FROMでエラーになるとおっしゃっていますが、どうにかなりませんか。

コード:

------------出力結果-----------
WinSock初期化成功
ソケット作成成功
ホストアドレス取得成功
サーバとの接続を確立
応答を受信中...

220 smtp-relay.gmail.com ESMTP v191sm1186935itb.7 - gsmtp


Request > EHLO test.gmail.com

サーバーにリクエスト開始... (EHLO test.gmail.com)
応答を受信中...

250-smtp-relay.gmail.com at your service, [59.147.248.66]


Request >

サーバーにリクエスト開始... ()
応答を受信中...

250-SIZE 157286400
250-8BITMIME
250-STARTTLS
250-ENHANCE

Request > STARTTLS

サーバーにリクエスト開始... (STARTTLS)
応答を受信中...

DSTATUSCODES
250-PIPELINING
250-CHUNKING
250 SMTPUTF8


Request > SMTPUTF8

サーバーにリクエスト開始... (SMTPUTF8)
応答を受信中...

220 2.0.0 Ready to start TLS


Request > MAIL FROM:<test1234@gmail.com>

サーバーにリクエスト開始... (MAIL FROM:<test1234@gmail.com>)
サーバへの送信失敗です
エラー10054が発生しました
続行するには何かキーを押してください . . .

コード:

#include <iostream>
#include <string>
#include <winsock.h>

#pragma comment(lib, "wsock32.lib")

#define WIN32_LEAN_AND_MEAN

char ServerName[] = "smtp-relay.gmail.com"; // 接続先のホスト名
unsigned short Port = 587; //smtpポート
using namespace std;

#define BUFSIZE 2600 // バッファサイズ
#define RECVSIZE 65536 // 受信バッファサイズ

int main() {
	// WinSock初期化
	WORD wVersionRequested = MAKEWORD(1, 1);
	WSADATA wsaData;
	int nErrorStatus;

	nErrorStatus = WSAStartup(wVersionRequested, &wsaData);
	if (atexit((void(*)(void))(WSACleanup))) {
		printf("atexit(WSACleanup)失敗\n");
		return -1;
	}
	if (nErrorStatus != 0) {
		printf("初期化失敗です\n");
		return -1;
	}
	cout << "WinSock初期化成功" << endl;

	// ソケット作成
	int soc; // ソケット
	soc = socket(AF_INET, SOCK_STREAM, 0);
	if (soc == INVALID_SOCKET) {
		printf("ソケット作成失敗です\n");
		printf("エラー%dが発生しました\n", WSAGetLastError());
		return -1;
	}
	cout << "ソケット作成成功" << endl;

	// サーバのIPアドレス取得
	PHOSTENT phostent;
	unsigned long ulIPAddress; // サーバのIPアドレス格納変数
	ulIPAddress = inet_addr(ServerName); // IPアドレス取得
	// inet_addr()関数が失敗したら
	if (ulIPAddress == -1) {
		if ((phostent = gethostbyname(ServerName)) != NULL) {
			ulIPAddress = *((unsigned long *)(phostent->h_addr));
		}
		else {
			printf("ホストアドレス取得失敗です\n");
			printf("エラー%dが発生しました\n", WSAGetLastError());
			closesocket(soc);
			return -1;
		}
	}
	cout << "ホストアドレス取得成功" << endl;

	// サーバへ接続
	SOCKADDR_IN addrServer; // サーバのアドレス
	addrServer.sin_family = AF_INET; // アドレスファミリの指定
	addrServer.sin_addr.s_addr = ulIPAddress; // サーバのIPアドレスの指定
	addrServer.sin_port = htons((unsigned short)Port); // ポート番号の指定
	if (connect(soc, (LPSOCKADDR)&addrServer, sizeof(addrServer)) == SOCKET_ERROR) {
		printf("サーバへの接続失敗です\n");
		printf("エラー%dが発生しました\n", WSAGetLastError());
		closesocket(soc);
		return -1;
	}
	cout << "サーバとの接続を確立" << endl;

	// リクエストを送信
	//DWORD dwNonBlocking = 1;
	//ioctlsocket(soc, FIONBIO, &dwNonBlocking);

	for (;;) {
		// データの受信
		char recv_buf[RECVSIZE];
		cout << "応答を受信中...\n" << endl;
		int nBytesRecv = recv(soc, recv_buf, strlen(recv_buf), 0); //ここで止まったまま
		if (nBytesRecv == SOCKET_ERROR) {
			printf("サーバからの受信失敗です\n");
			printf("エラー%dが発生しました\n", WSAGetLastError());
			break;
		}
		recv_buf[nBytesRecv] = '\0'; // 受信バッファの後ろにNULLを付加する
		printf("%s", recv_buf); // 受信した内容を表示

		string Rec;
		cout << "\n\nRequest > "; getline(cin, Rec);
		char request[BUFSIZE];
		cout << "\nサーバーにリクエスト開始... (" << Rec << ")" << endl;
		//sprintf_s(request, BUFSIZE, "%s",);
		Rec += "\r\n";
		if (send(soc, Rec.c_str(), strlen(Rec.c_str()), 0) == SOCKET_ERROR) {
			printf("サーバへの送信失敗です\n");
			printf("エラー%dが発生しました\n", WSAGetLastError());
			shutdown(soc, 2);
			closesocket(soc);
			return -1;
		}
	}

	shutdown(soc, 2);
	closesocket(soc);

	return 0;
}

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: C++でソケット通信を用いたメールの送信

#7

投稿記事 by みけCAT » 5年前

意味的には1まとまりのデータでも分割して送られる可能性があるTCPの仕様を無視し、
recvを1回だけ呼び出すことでサーバーからの応答を全て受信できると勘違いしたプログラムを用いたことにより、
実際はSTARTTLSコマンドを送ったにもかかわらず、
SSL/TLSのネゴシエーションを開始せずに別のデータを送ったために切断されているのを、
MAIL FROMで切断されてしまうと勘違いしたようですね。

前にも書いたとおり、きちんとサーバーから送られてきたデータを見てプロトコルに沿って解釈することで、
データの区切りを判定し、データの区切りまで必要十分に受信するべきです。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

DAICHI0922
記事: 26
登録日時: 5年前

Re: C++でソケット通信を用いたメールの送信

#8

投稿記事 by DAICHI0922 » 5年前

ありがとうございます。

recvを何回呼び出すかは人が判断するしか無いのでしょうか?
whileなどで無限ループし、受信バイト数が0になったら抜ける...という処理は先に示した通り無理でした。(データがない状態でrecvするとそこで固まる)

つまり、機械的に何回recvするかを判定することは不可能...?ということなんでしょうか。

アバター
みけCAT
記事: 6734
登録日時: 13年前
住所: 千葉県
連絡を取る:

Re: C++でソケット通信を用いたメールの送信

#9

投稿記事 by みけCAT » 5年前

このスレッドにこれを書くのはこれで3回目ですが、
recvを続けるかどうかは、
受信したデータをプロトコルに沿って解釈し、
レスポンスが終わったかどうかで判断するべきです。


SMTPの仕様によれば、コマンドのレスポンスは
1個または複数個の「行」からなります。
「行」は、3個の数字から始まり、CR('\r')LF('\n')で終わる文字列です。
「行」の最初の3個の数字の次の文字がハイフンであればこの後も「行」が続き、
そうでなければそれがこのレスポンスの最終「行」です。
従って、3個の数字の後にハイフンが無い「行」の最後のCRLFまで受信するまでrecvを続け、
それを受信したらrecvをやめるのがいいでしょう。
複雑な問題?マシンの性能を上げてOpenMPで殴ればいい!(死亡フラグ)

DAICHI0922
記事: 26
登録日時: 5年前

Re: C++でソケット通信を用いたメールの送信

#10

投稿記事 by DAICHI0922 » 5年前

了解しました!
ハイフンの有無で自動化することとしました。
ありがとうございました。

返信

“C言語何でも質問掲示板” へ戻る