変なところと通信すると、永遠に待ち続けてしまう状態だったので、タイムアウト処理を追加しました。
あとはバッファをちょこっと食べるようにして、いくつかチェックを加えてみたり、
エラー時は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; }