HLRcon v2

変なところと通信すると、永遠に待ち続けてしまう状態だったので、タイムアウト処理を追加しました。
あとはバッファをちょこっと食べるようにして、いくつかチェックを加えてみたり、
エラー時は1を返すようにして、エラーメッセージは全て標準エラー出力に出すようにしました。
VisualStudio 2010とCentOS5.6で動作確認してます。


それにしても、全くC言語がかけなくなっててワロタ。
(12/06/23: 引数があった場合に正しく動かなかったのを修正)

/*
遠隔rconツール hlrcon v2

usage:
	hlrcon.exe 123.45.67.89 27015 "pass" command [args...]

compile:
	WIN32
		gcc hlrcon.c -lwsock32 -o hlrcon.exe
	Linux
		gcc hlcon.c -o hlrcon
*/

#include <stdio.h>
#include <string.h>


#ifdef WIN32
	#include <winsock.h>
#else
	#include <sys/socket.h>
	#include <arpa/inet.h>
	#include <netdb.h>
	typedef int SOCKET;
#endif


#undef FD_SETSIZE
#define FD_SETSIZE (32)

#define BUFFSIZE (0xFFFF >> 4)

enum{
	RCON_COMMAND_OK,
	RCON_CHALLENGE_OK,
	RCON_CHALLENGE_NG,
	// ERROR
	RCON_RECV_ERROR,
	RCON_UNKNOWN_ERROR
};


// チャレンジコード発行時のレスポンスプレフィクス
const char CHALLENGE_RCON_PREFIX[] = "\xFF\xFF\xFF\xFF" "challenge rcon ";
const int CHALLENGE_RCON_PREFIX_LENGTH = sizeof(CHALLENGE_RCON_PREFIX) - 1;

// 不正なチャレンジコードだった場合のレスポンスプレフィクス
const char BAD_CHALLENGE_PREFIX[] = "\xFF\xFF\xFF\xFF" "9Bad challenge";
const int BAD_CHALLENGE_PREFIX_LENGTH = sizeof(BAD_CHALLENGE_PREFIX) - 1;

// RCONコマンド送信後のレスポンスプレフィクス
const char RCON_COMMAND_OK_PREFIX[] = "\xFF\xFF\xFF\xFF" "\x6C";
const int RCON_COMMAND_OK_PREFIX_LENGTH = sizeof(RCON_COMMAND_OK_PREFIX) - 1;

// ソケットクローズ
void sock_close(SOCKET sock){
#ifdef WIN32
	closesocket(sock);
	WSACleanup();
#else
	close (sock);
#endif
}

// エラーメッセージ変換
const char * rconErrorCodeString(int code){
	switch(code){
		case RCON_COMMAND_OK: return "sent command.";
		case RCON_CHALLENGE_OK: return "can send challenge code.";
		case RCON_CHALLENGE_NG: return "bad challenge code.";
		// ERROR
		case RCON_RECV_ERROR: return "recv error.";
		default: return "unknown error.";
	}
}

// RCONレシーバ
// レスポンス内容をbufへ記録した後、記録長をrecvsizeへセットする
// レスポンス内容により、戻り値は異なる
int rconRecv(SOCKET sock, char *buf, int bufsize, struct sockaddr_in *fromaddr, int *recvsize){
	int fromlen = sizeof(struct sockaddr_in);
	int ret;
	
	memset(buf, '\0', bufsize);
	ret = recvfrom(sock, buf, bufsize, 0, (struct sockaddr *)fromaddr, &fromlen);

	if(recvsize != NULL){
		*recvsize = ret;
	}

	if(ret < 0){
		return RCON_RECV_ERROR;
	}
	
	if(memcmp(buf, CHALLENGE_RCON_PREFIX, CHALLENGE_RCON_PREFIX_LENGTH) == 0){
		return RCON_CHALLENGE_OK;
	}

	if(memcmp(buf, BAD_CHALLENGE_PREFIX, BAD_CHALLENGE_PREFIX_LENGTH) == 0){
		return RCON_CHALLENGE_NG;
	}
	
	return RCON_COMMAND_OK;
}

// メイン処理
int main(int argc, char **argv){
#ifdef WIN32
	struct WSAData wsaData;
#endif
	SOCKET sock;
	struct sockaddr_in fromaddr;
	struct sockaddr_in toaddr;
	char buf[BUFFSIZE];
	char command_buf[2048]={0};
	char challenge_code[1024];
	char ip_addr[1024];
	int i, port;
	int ret;
	int check;
	
	// 非同期ループ
	int loop = 1;
	fd_set fds, readfds;
	struct timeval timeout;
	timeout.tv_sec = 5;
	timeout.tv_usec= 0;

	// 引数チェック
	if(argc<4){
		printf("Usage: this.exe ADDRESS PORT PASS COMMAND\n");
		printf("timeout = %d\n", timeout.tv_sec);
		return 1;
	}
	
	// 引数チェック
	check = 0;
	for(i=4;i<argc;i++){
		check += strlen(argv[i]) + 1;
		if(check > sizeof(command_buf) - 1){
			fprintf(stderr, "error:too long a command\n");
			return 1;
		}
		strcat(command_buf, argv[i]);
		strcat(command_buf, " ");
	}
	command_buf[check] = '\0'; // ' ' -> '\0'
	
	// アドレス設定
	if(strlen(argv[1]) > sizeof(ip_addr) - 1){
		fprintf(stderr, "error:too long a IP Address\n");
		return 1;
	}
	strcpy(ip_addr, argv[1]);
	port = atoi(argv[2]);
	
	if(port < 1 || port > 65535){
		fprintf(stderr, "error:port.\n");
		return 1;
	}
	

	// 通信初期化
#ifdef WIN32
	WSAStartup(MAKEWORD(2,0), &wsaData);
#endif
	
	// ホスト名解決
	// host -> ip , ip -> ip
	{
		struct hostent *host;
		host = gethostbyname(ip_addr);
		if(host == NULL){
			fprintf(stderr, "error:gethostbyname.\n");
#ifdef WIN32
			WSACleanup();
#endif
			return 1;
		}
		strcpy(ip_addr, inet_ntoa(*(struct in_addr *)(host->h_addr_list[0])));
	}

	// ソケット作成
	sock = socket(AF_INET, SOCK_DGRAM, 0);
	
	if( !sock ){
		fprintf(stderr, "error:SOCKET_ERROR(socket).\n");
		return 1;
	}
	
	// あて先指定
	toaddr.sin_family = AF_INET;
	toaddr.sin_port = htons(port);
	
#ifdef WIN32
	toaddr.sin_addr.S_un.S_addr = inet_addr(ip_addr);
#else
	toaddr.sin_addr.s_addr = inet_addr(ip_addr);
#endif
	
	// 接続
	ret = connect(sock,(struct sockaddr *)&toaddr, sizeof(toaddr));
	
	if( ret == -1){
		sock_close(sock);
		fprintf(stderr, "error:SOCKET_ERROR(connect).\n");
		return 1;
	}

	// 非同期のためのファイルディスクリプタの設定
	FD_ZERO(&readfds); // 初期化
	FD_SET(sock, &readfds); // 監視ソケット追加
	
	// チャレンジコード発行
	ret = sendto(sock, "\xFF\xFF\xFF\xFF challenge rcon\n", 20, 0, (struct sockaddr *)&toaddr, sizeof(toaddr));
	
	if(ret == -1){
		sock_close(sock);
		fprintf(stderr, "error:SOCKET_ERROR(sendto).\n");
		return 1;
	}

	// ループ処理
	while(loop){
		memcpy(&fds, &readfds, sizeof(fd_set));
		ret = select(sock+1, &fds, NULL, NULL, &timeout);
		if(ret == 0){
			// timeout
			fprintf(stderr, "error:timeout");
			return 1;
		}
		
		if(FD_ISSET(sock, &fds)){
			ret = rconRecv(sock, buf, BUFFSIZE, &fromaddr, NULL);
			switch(ret){
				case RCON_CHALLENGE_OK:
					// チャレンジコード取得
					memset(challenge_code, 0, sizeof(challenge_code));
					strncpy(challenge_code, buf+CHALLENGE_RCON_PREFIX_LENGTH, sizeof(challenge_code)-1);
					for(i=0; i<sizeof(challenge_code); ++i){
						switch(challenge_code[i]){
							case  ' ':
							case  '\n':
							case  '\r':
								challenge_code[i] = '\0';
								i=sizeof(challenge_code); // 終わり
								break;
						}
					}

					// チャレンジコードを含めてリクエスト
					sprintf(buf, "\xFF\xFF\xFF\xFF rcon %s \"%s\" %s", challenge_code, argv[3], command_buf);
					ret = sendto(sock, buf, strlen(buf), 0, (struct sockaddr *)&toaddr, sizeof(toaddr));
					
					if(ret == -1){
						sock_close(sock);
						fprintf(stderr, "error:SOCKET_ERROR(sendto).\n");
						return 1;
					}
					break;
				case RCON_COMMAND_OK:
					// コマンド処理結果
					printf("%s\n", buf + RCON_COMMAND_OK_PREFIX_LENGTH);
					loop = 0; // ループ脱出
					break;
				default:
					// 何かしらのエラー
					sock_close(sock);
					fprintf(stderr, "error:%s(code:%d)\n", rconErrorCodeString(ret), ret);
					return 1;
			}
		}
	}

	sock_close(sock);
	
	return 0;
}