added QServer for external cache access
[NeonServV5.git] / src / QServer.c
diff --git a/src/QServer.c b/src/QServer.c
new file mode 100644 (file)
index 0000000..fd10beb
--- /dev/null
@@ -0,0 +1,421 @@
+/* QServer.c - NeonServ v5.3
+ * Copyright (C) 2011-2012  Philipp Kreil (pk910)
+ * 
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public License 
+ * along with this program. If not, see <http://www.gnu.org/licenses/>. 
+ */
+
+#include "QServer.h"
+#include "UserNode.h"
+#include "ChanNode.h"
+#include "ModeNode.h"
+#include "ChanUser.h"
+#include "ClientSocket.h"
+#include "WHOHandler.h"
+#include "ConfigParser.h"
+#include "bots.h"
+
+#define QSERVER_TIMEOUT 30
+#define QSERVER_MAXCLIENTS 100
+
+#define QSERVER_FLAG_DISCONNECT 0x01
+#define QSERVER_FLAG_AUTHED     0x02
+#define QSERVER_FLAG_IN_USE     0x04
+
+struct QServerClient {
+    int sock;
+    unsigned int flags;
+    time_t lastmsg;
+    char buffer[MAXLEN];
+    int bufferpos;
+    int references;
+    struct QServerClient *next;
+};
+
+static int server_sockfd = 0;
+struct QServerClient *qserver_clients = NULL;
+static int qserver_clientcount = 0;
+
+void qserver_init() {
+    if(get_int_field("QServer.enabled")) {
+        server_sockfd = socket(AF_INET, SOCK_STREAM, 0);
+        if (server_sockfd < 0) 
+            return;
+        struct sockaddr_in serv_addr;
+        bzero((char *) &serv_addr, sizeof(serv_addr));
+        int portno = get_int_field("QServer.port");
+        if(!portno)
+            portno = 7499;
+        serv_addr.sin_family = AF_INET;
+        serv_addr.sin_addr.s_addr = INADDR_ANY;
+        serv_addr.sin_port = htons(portno);
+        if (bind(server_sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) 
+            return;
+        listen(server_sockfd,5);
+    }
+}
+
+static int qserver_write(struct QServerClient *client, char* msg, int len) {
+    if (!(client && !(client->flags & QSERVER_FLAG_DISCONNECT))) return 0;
+    if(!len)
+        len = strlen(msg);
+    #ifdef WIN32
+    send(client->sock, msg, len, 0);
+    #else
+    write(client->sock, msg, len);
+    #endif
+    return 1;
+}
+
+static void qserver_put(struct QServerClient *client, const char *text, ...) {
+    va_list arg_list;
+    char sendBuf[MAXLEN];
+    int pos;
+    if (!(client && !(client->flags & QSERVER_FLAG_DISCONNECT))) return;
+    sendBuf[0] = '\0';
+    va_start(arg_list, text);
+    pos = vsnprintf(sendBuf, MAXLEN - 2, text, arg_list);
+    va_end(arg_list);
+    if (pos < 0 || pos > (MAXLEN - 2)) pos = MAXLEN - 2;
+    sendBuf[pos] = '\n';
+    sendBuf[pos+1] = '\0';
+    qserver_write(client, sendBuf, pos+1);
+}
+
+static void qserver_parse_A(struct QServerClient *client, char **argv, int argc) {
+    if(client->flags & QSERVER_FLAG_AUTHED) {
+        qserver_put(client, "E :Already Authed");
+        return;
+    }
+    if(!argv) {
+        qserver_put(client, "E :Missing Parameter");
+        return;
+    }
+    if(strcmp(argv[0], get_string_field("QServer.pass"))) {
+        qserver_put(client, "E :Wrong Password");
+        return;
+    }
+    client->flags |= QSERVER_FLAG_AUTHED;
+    client->lastmsg = time(0);
+    qserver_put(client, "A :Logged in");
+}
+
+#define QSERVER_COMMAND_HEADER {\
+    if(!(client->flags & QSERVER_FLAG_AUTHED)) {\
+        qserver_put(client, "E :Not Authed");\
+        return;\
+    }\
+    client->lastmsg = time(0);\
+}
+
+static void qserver_parse_U(struct QServerClient *client, char **argv, int argc);
+static void qserver_parse_C(struct QServerClient *client, char **argv, int argc);
+static void qserver_parse_AC(struct QServerClient *client, char **argv, int argc);
+static void qserver_parse_ACU(struct QServerClient *client, char **argv, int argc);
+static void qserver_parse_R(struct QServerClient *client, char **argv, int argc);
+
+static void qserver_parse(struct QServerClient *client, char *line, int len) {
+    int argc = 0;
+    char *argv[MAXNUMPARAMS];
+    while(*line) {
+        //skip leading spaces
+        while (*line == ' ')
+            *line++ = 0;
+        if (*line == ':') {
+           //the rest is a single parameter
+           argv[argc++] = line + 1;
+           break;
+        }
+        argv[argc++] = line;
+        if (argc >= MAXNUMPARAMS)
+            break;
+        while (*line != ' ' && *line)
+            line++;
+    }
+    if(!stricmp(argv[0], "A")) //AUTH
+        qserver_parse_A(client, argv+1, argc-1);
+    else if(!stricmp(argv[0], "U")) //get User
+        qserver_parse_U(client, argv+1, argc-1);
+    else if(!stricmp(argv[0], "C")) //get Channel
+        qserver_parse_C(client, argv+1, argc-1);
+    else if(!stricmp(argv[0], "AC")) //get All Channels
+        qserver_parse_AC(client, argv+1, argc-1);
+    else if(!stricmp(argv[0], "ACU")) //get All ChannelUsers
+        qserver_parse_ACU(client, argv+1, argc-1);
+    else if(!stricmp(argv[0], "R")) //RAW
+        qserver_parse_R(client, argv+1, argc-1);
+    else
+        qserver_put(client, "E :Unknown Command");
+}
+
+void qserver_loop() {
+    struct timeval tv;
+    struct QServerClient *client, *next, *prev = NULL;
+    int ret;
+    time_t now = time(0);
+    fd_set fds;
+    tv.tv_sec = 0;
+    tv.tv_usec = 0;
+    FD_ZERO(&fds);
+    ret = server_sockfd;
+    FD_SET(server_sockfd, &fds);
+    for (client = qserver_clients; client; client = next) {
+        next = client->next;
+        if((client->flags & (QSERVER_FLAG_DISCONNECT | QSERVER_FLAG_IN_USE)) == QSERVER_FLAG_DISCONNECT) {
+            close(client->sock);
+            if(prev) 
+                prev->next = client->next;
+            else
+                qserver_clients = client->next;
+            qserver_clientcount--;
+            free(client);
+            continue;
+        }
+        prev = client;
+        if(client->flags & QSERVER_FLAG_DISCONNECT) continue;
+        if(now - client->lastmsg > QSERVER_TIMEOUT) {
+            qserver_put(client, "E :Timeout");
+            client->flags |= QSERVER_FLAG_DISCONNECT;
+            continue;
+        }
+        FD_SET(client->sock, &fds);
+        if(client->sock > ret)
+            ret = client->sock;
+    }
+    ret = select(ret + 1, &fds, NULL, NULL, &tv);
+    if(ret == 0) {
+        return;
+    }
+    if(FD_ISSET(server_sockfd, &fds)) {
+        //new connection
+        struct sockaddr_in cli_addr;
+        socklen_t clilen;
+        if(qserver_clientcount >= QSERVER_MAXCLIENTS) {
+            qserver_put(client, "E :Maximum QServer Connections reached");
+            close(client->sock);
+        } else {
+            client = malloc(sizeof(*client));
+            clilen = sizeof(cli_addr);
+            client->sock = accept(server_sockfd, (struct sockaddr *) &cli_addr, &clilen);
+            client->flags = 0;
+            client->lastmsg = now;
+            client->bufferpos = 0;
+            client->references = 0;
+            client->next = qserver_clients;
+            qserver_clients = client;
+            qserver_clientcount++;
+        }
+    }
+    int bytes, i;
+    char buffer[MAXLEN];
+    for (client = qserver_clients; client; client = next) {
+        next = client->next;
+        if(FD_ISSET(client->sock, &fds)) {
+            #ifdef WIN32
+            bytes = recv(client->sock, buffer, sizeof(buffer), 0);
+            #else
+            bytes = read(client->sock, buffer, sizeof(buffer));
+            #endif
+            if(bytes <= 0) {
+                client->flags |= QSERVER_FLAG_DISCONNECT;
+                continue;
+            }
+            for(i = 0; i < bytes; i++) {
+                if(client->bufferpos == MAXLEN-1) {
+                    //buffer overflow
+                    qserver_put(client, "E :Buffer Overflow");
+                    client->flags |= QSERVER_FLAG_DISCONNECT;
+                    break;
+                }
+                if(buffer[i] == '\r') continue;
+                else if(buffer[i] == '\n') {
+                    client->buffer[client->bufferpos] = '\0';
+                    qserver_parse(client, client->buffer, client->bufferpos);
+                    client->bufferpos = 0;
+                } else {
+                    client->buffer[client->bufferpos++] = buffer[i];
+                }
+            }
+        }
+    }
+}
+
+void qserver_free() {
+    struct QServerClient *client, *next;
+    for (client = qserver_clients; client; client = next) {
+        next = client->next;
+        close(client->sock);
+        free(client);
+    }
+    qserver_clients = NULL;
+    qserver_clientcount = 0;
+    close(server_sockfd);
+}
+
+/* 
+* Command functions
+*/
+
+static USERAUTH_CALLBACK(qserver_parse_U_async);
+static void qserver_parse_U(struct QServerClient *client, char **argv, int argc) {
+    QSERVER_COMMAND_HEADER;
+    if(!argv) {
+        qserver_put(client, "E :Missing Parameter");
+        return;
+    }
+    struct UserNode *cuser = getUserByNick(argv[0]);
+    if(!cuser) {
+        cuser = createTempUser(argv[0]);
+        if(!cuser) {
+            qserver_put(client, "U 0 :Unknown User");
+            return;
+        }
+        cuser->flags |= USERFLAG_ISTMPUSER;
+    }
+    client->references++;
+    client->flags |= QSERVER_FLAG_IN_USE;
+    get_userauth(cuser, qserver_parse_U_async, client);
+}
+
+static USERAUTH_CALLBACK(qserver_parse_U_async) {
+    struct QServerClient *qclient = data;
+    qclient->references--;
+    if(!qclient->references)
+        qclient->flags &= ~QSERVER_FLAG_IN_USE;
+    if(!user) {
+        qserver_put(qclient, "U 0 :Unknown User");
+        return;
+    }
+    qserver_put(qclient, "U 1 %s %s %s %s :%s", user->nick, user->ident, user->host, ((user->flags & USERFLAG_ISAUTHED) ? user->auth : "0"), user->realname);
+}
+
+static void qserver_parse_C(struct QServerClient *client, char **argv, int argc) {
+    QSERVER_COMMAND_HEADER;
+    if(!argv) {
+        qserver_put(client, "E :Missing Parameter");
+        return;
+    }
+    struct ChanNode *chan = getChanByName(argv[0]);
+    if(!chan) {
+        qserver_put(client, "C 0 :Unknown Channel");
+        return;
+    }
+    char tmpStr[MAXLEN];
+    getModeString(chan->modes, tmpStr);
+    qserver_put(client, "C 1 %s %d %s :%s", chan->name, chan->usercount, tmpStr, chan->topic);
+}
+
+static void qserver_parse_AC(struct QServerClient *client, char **argv, int argc) {
+    QSERVER_COMMAND_HEADER;
+    struct ChanNode *chan;
+    char tmpStr[MAXLEN];
+    for(chan = getAllChans(NULL); chan; chan = getAllChans(chan)) {
+        getModeString(chan->modes, tmpStr);
+        qserver_put(client, "AC %s %d %s :%s", chan->name, chan->usercount, tmpStr, chan->topic);
+    }
+    qserver_put(client, "ACE"); //AllChannelsEnd
+}
+
+static USERLIST_CALLBACK(qserver_parse_ACU_async);
+static void qserver_parse_ACU(struct QServerClient *client, char **argv, int argc) {
+    QSERVER_COMMAND_HEADER;
+    if(!argv) {
+        qserver_put(client, "E :Missing Parameter");
+        return;
+    }
+    struct ChanNode *chan = getChanByName(argv[0]);
+    if(!chan) {
+        qserver_put(client, "ACUE 0 :Unknown Channel");
+        return;
+    }
+    if(argc > 1 && !stricmp(argv[1], "1")) {
+        client->references++;
+        client->flags |= QSERVER_FLAG_IN_USE;
+        get_userlist_if_invisible(chan, qserver_parse_ACU_async, client);
+        return;
+    }
+    char tmpStr[6];
+    int tmpStrPos;
+    struct ChanUser *chanuser;
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        tmpStrPos = 0;
+        if(chanuser->flags & CHANUSERFLAG_OPPED)
+            tmpStr[tmpStrPos++] = '@';
+        if(chanuser->flags & CHANUSERFLAG_HALFOPPED)
+            tmpStr[tmpStrPos++] = '%';
+        if(chanuser->flags & CHANUSERFLAG_VOICED)
+            tmpStr[tmpStrPos++] = '+';
+        if(chanuser->flags & CHANUSERFLAG_INVISIBLE)
+            tmpStr[tmpStrPos++] = '<';
+        tmpStr[tmpStrPos] = '\0';
+        qserver_put(client, "ACU %s %s %s", chanuser->user->nick, ((chanuser->user->flags & USERFLAG_ISAUTHED) ? chanuser->user->auth : "0"), tmpStr);
+    }
+    qserver_put(client, "ACUE 1");
+}
+
+static USERLIST_CALLBACK(qserver_parse_ACU_async) {
+    struct QServerClient *qclient = data;
+    qclient->references--;
+    if(!qclient->references)
+        qclient->flags &= ~QSERVER_FLAG_IN_USE;
+    char tmpStr[6];
+    int tmpStrPos;
+    struct ChanUser *chanuser;
+    for(chanuser = getChannelUsers(chan, NULL); chanuser; chanuser = getChannelUsers(chan, chanuser)) {
+        tmpStrPos = 0;
+        if(chanuser->flags & CHANUSERFLAG_OPPED)
+            tmpStr[tmpStrPos++] = '@';
+        if(chanuser->flags & CHANUSERFLAG_HALFOPPED)
+            tmpStr[tmpStrPos++] = '%';
+        if(chanuser->flags & CHANUSERFLAG_VOICED)
+            tmpStr[tmpStrPos++] = '+';
+        if(chanuser->flags & CHANUSERFLAG_INVISIBLE)
+            tmpStr[tmpStrPos++] = '<';
+        tmpStr[tmpStrPos] = '\0';
+        qserver_put(qclient, "ACU %s %s %s", chanuser->user->nick, ((chanuser->user->flags & USERFLAG_ISAUTHED) ? chanuser->user->auth : "0"), tmpStr);
+    }
+    qserver_put(qclient, "ACUE 1");
+}
+
+static void qserver_parse_R(struct QServerClient *client, char **argv, int argc) {
+    QSERVER_COMMAND_HEADER;
+    if(argc < 3) {
+        qserver_put(client, "E :Missing Parameter");
+        return;
+    }
+    struct ClientSocket *bot;
+    if(!strcmp(argv[0], "1")) {
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(!stricmp(bot->user->nick, argv[1])) break;
+        }
+    } else {
+        struct ClientSocket *low_bot;
+        int botid = resolve_botalias(argv[1]);
+        if(botid == -1)
+            botid = atoi(argv[1]);
+        for(bot = getBots(SOCKET_FLAG_READY, NULL); bot; bot = getBots(SOCKET_FLAG_READY, bot)) {
+            if(bot->botid == botid) {
+                if(bot->flags & SOCKET_FLAG_PREFERRED) break;
+                low_bot = bot;
+            }
+        }
+        if(!bot)
+            bot = low_bot;
+    }
+    if(!bot) {
+        qserver_put(client, "R 0 :Bot not found");
+        return;
+    }
+    putsock(bot, "%s", argv[2]);
+    qserver_put(client, "R 1");
+}