HLRCON v3

主にAPIを意識した切り分け・・・のつもりだったけど、収集つかなくなって投げた形になってる。
小学生でももっとマシなコード書ける感。
ネットワーク周りの処理が長くなりがちなので、イベント毎にコールバック関数作ってあげて、もう少しすっきりさせてみようと思います。


/*
遠隔rconツール hlrcon v3
v3
・バインディングで使いやすいようにした(失敗作)
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 >> 2)

// チャレンジコード発行時のレスポンスプレフィクス
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;

enum HLRCON_ERROR_CODE{
        HLRCON_OK,
        HLRCON_COMMAND_OK,
        HLRCON_CHALLENGE_OK,
        // ERROR
        HLRCON_ADDRESS_IS_NULL_ERROR,
        HLRCON_PASSWORD_IS_NULL_ERROR,
        HLRCON_COMMAND_IS_NULL_ERROR,
        HLRCON_RESPONSE_IS_NULL_ERROR,
        HLRCON_SOCKET_ERROR,
        HLRCON_HOST_LOOKUP_ERROR,
        HLRCON_FAILURE_SEND_CHALLENGE_REQUEST,
        HLRCON_FAILURE_SEND_CHALLENGE_CODE,
        HLRCON_FAILURE_RECV_CHALLENGE_CODE,
        HLRCON_CHALLENGE_NG,
        HLRCON_RECV_ERROR,
        HLRCON_TIMEOUT_ERROR,
        HLRCON_UNKNOWN_ERROR,
};

// エラーメッセージ変換
const char * hlrconErrorCodeMessage(int code){
        switch(code){
                case HLRCON_OK: return "OK.";
                case HLRCON_SOCKET_ERROR: return "Socket error.";
                case HLRCON_HOST_LOOKUP_ERROR: return "Host lookup error.";
                case HLRCON_ADDRESS_IS_NULL_ERROR: return "Address is null.";
                case HLRCON_PASSWORD_IS_NULL_ERROR: return "PASSWORD is null.";
                case HLRCON_COMMAND_IS_NULL_ERROR: return "COMMAND is null.";
                case HLRCON_RESPONSE_IS_NULL_ERROR: return "RESSPONSE is null.";
                case HLRCON_FAILURE_SEND_CHALLENGE_REQUEST: return "Failure to send the challenge request.";
                case HLRCON_FAILURE_SEND_CHALLENGE_CODE: return "Failure to send the challenge code.";
                case HLRCON_FAILURE_RECV_CHALLENGE_CODE: return "Failure to recv the challenge code.";
                case HLRCON_RECV_ERROR: return "Recv error.";
                case HLRCON_COMMAND_OK: return "Commend OK";
                case HLRCON_CHALLENGE_OK: return "Can sent challenge code.";
                case HLRCON_CHALLENGE_NG: return "Bad challenge code.";
                case HLRCON_TIMEOUT_ERROR: return "Timeout error.";
                case HLRCON_UNKNOWN_ERROR: return "Unknown error.";
        }
        return "Unknown error.";
}


// socket close
// argv: SOCKET
void hlrconSocketClose(SOCKET sock){
#ifdef WIN32
        closesocket(sock);
        WSACleanup();
#else
        close (sock);
#endif
}

// socket open
// ret: SOCKET
SOCKET hlrconSocketOpen(){
        SOCKET sock;
        // 通信初期化
#ifdef WIN32
        struct WSAData wsaData;
        WSAStartup(MAKEWORD(2,0), &wsaData);
#endif
        sock = socket(AF_INET, SOCK_DGRAM, 0);
        return sock;
}

// convert Host to IP
// ret: success:0, error:1
enum HLRCON_ERROR_CODE hlrconHostToIp(const char *srcHost, char *dstIp){
        struct hostent *host = gethostbyname(srcHost);
        if(host == NULL){
#ifdef WIN32
                WSACleanup();
#endif
                return HLRCON_HOST_LOOKUP_ERROR;
        }
        strncpy(dstIp, inet_ntoa(*(struct in_addr *)(host->h_addr_list[0])), 16);
        dstIp[15] = '\0';
        return HLRCON_OK;
}


// RCONレシーバ
// レスポンス内容をbufへ記録した後、記録長をrecvsizeへセットする
// レスポンス内容により、戻り値は異なる
enum HLRCON_ERROR_CODE hlrconRecv(const SOCKET sock, char *buf, const 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 HLRCON_RECV_ERROR;
        }

        if(memcmp(buf, CHALLENGE_RCON_PREFIX, CHALLENGE_RCON_PREFIX_LENGTH) == 0){
                return HLRCON_CHALLENGE_OK;
        }

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

        return HLRCON_COMMAND_OK;
}

// send HLRCON command
// argv: etc...
// ret: HLRCON_ERROR_CODE
enum HLRCON_ERROR_CODE hlrconExecute(const char * address, const unsigned short port, const char * password, const char * command, char *response, const int responseBufferSize){
        SOCKET sock;

        // 非同期ループ
        int loop = 1;
        int ret, i;
        char ip_addr[16];
        char challenge_code[128];
        struct sockaddr_in fromaddr;
        struct sockaddr_in toaddr;
        fd_set fds, readfds;
        struct timeval timeout;

        timeout.tv_sec = 5;
        timeout.tv_usec= 0;

        if(address == NULL){
                return HLRCON_ADDRESS_IS_NULL_ERROR;
        }
        if(password == NULL){
                return HLRCON_PASSWORD_IS_NULL_ERROR;
        }
        if(command == NULL){
                return HLRCON_COMMAND_IS_NULL_ERROR;
        }
        if(response == NULL){
                return HLRCON_RESPONSE_IS_NULL_ERROR;
        }

        // ホスト名解決
        ret = hlrconHostToIp(address, ip_addr);

        if(ret != HLRCON_OK){
                return ret;
        }

        sock = hlrconSocketOpen();

        if(!sock){
                return HLRCON_SOCKET_ERROR;
        }

        // あて先指定
        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){
                hlrconSocketClose(sock);
                return HLRCON_SOCKET_ERROR;
        }

        ret = sendto(sock, "\xFF\xFF\xFF\xFF challenge rcon\n", 20, 0, (struct sockaddr *)&toaddr, sizeof(toaddr));

        if(ret == -1){
                hlrconSocketClose(sock);
                return HLRCON_FAILURE_SEND_CHALLENGE_REQUEST;
        }

        // 非同期のためのファイルディスクリプタの設定
        FD_ZERO(&readfds); // 初期化
        FD_SET(sock, &readfds); // 監視ソケット追加

        // ループ処理
        while(loop){
                memcpy(&fds, &readfds, sizeof(fd_set));
                ret = select(sock + 1, &fds, NULL, NULL, &timeout);
                if(ret == 0){
                        // timeout
                        return HLRCON_TIMEOUT_ERROR;
                }

                if(FD_ISSET(sock, &fds)){
                        int recvsize;
                        ret = hlrconRecv(sock, response, responseBufferSize, &fromaddr, &recvsize);
                                        printf("recv: %s[%d byte]\n", response, recvsize);
                        switch(ret){
                                case HLRCON_CHALLENGE_OK:
                                        // チャレンジコード取得
                                        memset(challenge_code, 0, sizeof(challenge_code));
                                        strncpy(challenge_code, response+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':
                                                        case  '\0':
                                                                challenge_code[i] = '\0';
                                                                i=sizeof(challenge_code); // 終わり
                                                                break;
                                                }
                                        }

                                        if(challenge_code[0] == '\0'){
                                                hlrconSocketClose(sock);
                                                return HLRCON_FAILURE_RECV_CHALLENGE_CODE;
                                        }

                                        // チャレンジコードを含めてリクエスト
                                        ret = snprintf(response, responseBufferSize, "\xFF\xFF\xFF\xFF rcon %s \"%s\" %s", challenge_code, password, command);
                                        response[responseBufferSize - 1] = '\0';
                                        ret = sendto(sock, response, ret + 1, 0, (struct sockaddr *)&toaddr, sizeof(toaddr));
                                        if(ret == -1){
                                                hlrconSocketClose(sock);
                                                return HLRCON_FAILURE_SEND_CHALLENGE_CODE;
                                        }
                                        printf("send: %s[%d byte]\n", response, ret);
                                        break;
                                case HLRCON_COMMAND_OK:
                                        // コマンド処理結果
                                        loop = 0; // ループ脱出
                                        response[responseBufferSize - 1] = '\0';

                                        break;
                                default:
                                        // 何かしらのエラー
                                        hlrconSocketClose(sock);
                                        return ret;
                        }
                }
        }

        hlrconSocketClose(sock);
        return HLRCON_OK;
}


// メイン処理
int main(int argc, char **argv){
        char buf[BUFFSIZE];
        char command_buf[2048]={0};
        int i, port;
        int check;
        enum HLRCON_ERROR_CODE code;

        // 引数チェック
        if(argc<4){
                printf("Usage: this.exe ADDRESS PORT PASS COMMAND\n");
                return 1;
        }

        // 引数チェック
        check = 0;
        for(i=4;i<argc;i++){
                check += strlen(argv[i]) + 1;
                if(check >= sizeof(command_buf)){
                        fprintf(stderr, "error:too long a command\n");
                        return 1;
                }
                strcat(command_buf, argv[i]);
                strcat(command_buf, " ");
        }
        command_buf[check] = '\0'; // ' ' -> '\0'
        port = atoi(argv[2]);

        if(port < 1 || port > 65535){
                fprintf(stderr, "error:port.\n");
                return 1;
        }

        code = hlrconExecute(argv[1], port, argv[3], command_buf, buf, BUFFSIZE);
        if(code == HLRCON_OK){
                printf("result: %s\n", buf + RCON_COMMAND_OK_PREFIX_LENGTH);
                return 0;
        }else{
                fprintf(stderr, "%s[code:%d]\n", hlrconErrorCodeMessage(code), code);
                return 1;
        }

}