Fixed HTTP-client query-string handling bug.
[doldaconnect.git] / daemon / fnet-adc.c
index 7f5722b..0112710 100644 (file)
@@ -1,6 +1,6 @@
 /*
  *  Dolda Connect - Modular multiuser Direct Connect-style client
- *  Copyright (C) 2004 Fredrik Tolf (fredrik@dolda2000.com)
+ *  Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com>
  *  
  *  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
@@ -19,6 +19,7 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <unistd.h>
+#include <fcntl.h>
 #include <stdarg.h>
 #include <string.h>
 #include <netinet/in.h>
@@ -49,7 +50,7 @@
 
 struct command {
     wchar_t *name;
-    int minargs, needsender;
+    int minargs, needsid, needsender;
     void (*func)(struct fnetnode *fn, wchar_t *command, wchar_t *sender, int argc, wchar_t **argv);
 };
 
@@ -58,7 +59,13 @@ struct qcmd {
     wchar_t **args;
 };
 
+struct qcmdqueue {
+    struct qcmd *f, *l;
+    int size;
+};
+
 struct adchub {
+    struct socket *sk;
     char *inbuf;
     size_t inbufdata, inbufsize;
     wchar_t *sid;
@@ -68,12 +75,14 @@ struct adchub {
     iconv_t ich;
     int state;
     struct wcspair *hubinf;
-    struct qcmd *queue;
+    struct qcmdqueue queue;
 };
 
-static wchar_t *eoc;
+static wchar_t *eoc, *ns, *fmt;
 /* I've never understood why both of these are necessary, but... */
 static wchar_t *privid, *cid;
+struct socket *udpsock;
+struct lport *tcpsock;
 
 static wchar_t **parseadc(wchar_t *cmdline)
 {
@@ -148,40 +157,69 @@ static void sendadc1(struct socket *sk, int sep, wchar_t *arg)
 
 static void sendadc(struct socket *sk, int sep, ...)
 {
+    int i;
     va_list args;
     wchar_t *arg;
-    int i;
+    wchar_t *type, *buf;
     
     va_start(args, sep);
     for(i = 0; (arg = va_arg(args, wchar_t *)) != NULL; i++) {
-       if(arg == eoc)
+       if(arg == eoc) {
            sendeoc(sk);
-       else
-           sendadc1(sk, (i == 0)?sep:1, arg);
+       } else if(arg == ns) {
+           sep = 0;
+       } else if(arg == fmt) {
+           type = va_arg(args, wchar_t *);
+           if(!wcscmp(type, L"i")) {
+               buf = swprintf2(L"%i", va_arg(args, int));
+           } else if(!wcscmp(type, L"mi")) {
+               buf = swprintf2(L"%ji", va_arg(args, intmax_t));
+           }
+           sendadc1(sk, sep, buf);
+           free(buf);
+           sep = 1;
+       } else {
+           sendadc1(sk, sep, arg);
+           sep = 1;
+       }
     }
     va_end(args);
 }
 
-static struct qcmd *newqcmd(struct qcmd **queue, wchar_t **args)
+static wchar_t *findkv(wchar_t **argv, wchar_t *name)
+{
+    while(*argv != NULL) {
+       if(!wcsncmp(*argv, name, 2))
+           return((*argv) + 2);
+    }
+    return(NULL);
+}
+
+static struct qcmd *newqcmd(struct qcmdqueue *queue, wchar_t **args)
 {
     struct qcmd *new;
     
-    while(*queue != NULL)
-       queue = &((*queue)->next);
     new = smalloc(sizeof(*new));
     new->next = NULL;
     new->args = args;
-    *queue = new;
+    if(queue->l == NULL)
+       queue->f = new;
+    else
+       queue->l->next = new;
+    queue->l = new;
+    queue->size++;
     return(new);
 }
 
-static struct qcmd *ulqcmd(struct qcmd **queue)
+static struct qcmd *ulqcmd(struct qcmdqueue *queue)
 {
     struct qcmd *ret;
     
-    if((ret = *queue) == NULL)
+    if((ret = queue->f) == NULL)
        return(NULL);
-    *queue = ret->next;
+    if((queue->f = ret->next) == NULL)
+       queue->l = NULL;
+    queue->size--;
     return(ret);
 }
 
@@ -191,8 +229,41 @@ static void freeqcmd(struct qcmd *qcmd)
     free(qcmd);
 }
 
+static void sendinf(struct fnetnode *fn)
+{
+    struct adchub *hub = fn->data;
+    struct socket *sk = hub->sk;
+    struct sockaddr_in *a4;
+    socklen_t alen;
+    
+    sendadc(sk, 0, L"BINF", hub->sid, NULL);
+    sendadc(sk, 1, L"PD", ns, privid, L"ID", ns, cid, NULL);
+    sendadc(sk, 1, L"VEDolda ", ns, icsmbstowcs(VERSION, "us-ascii", NULL), NULL);
+    sendadc(sk, 1, L"NI", ns, fn->mynick, NULL);
+    sendadc(sk, 1, L"SS", ns, fmt, L"mi", (intmax_t)sharesize, L"SF", ns, fmt, L"i", sharedfiles, NULL);
+    if(sockfamily(sk) == AF_INET)
+       sendadc(sk, 1, L"I40.0.0.0", NULL);
+    else if(sockfamily(sk) == AF_INET6)
+       sendadc(sk, 1, L"I6::", NULL);
+    sendadc(sk, 1, L"SL", ns, fmt, L"i", confgetint("transfer", "slot"), NULL);
+    if(tcpsock != NULL) {
+       if((sockfamily(sk) == AF_INET) && !sockgetremotename(udpsock, (struct sockaddr **)&a4, &alen)) {
+           sendadc(sk, 1, L"U4", ns, fmt, L"i", ntohs(a4->sin_port), NULL);
+           free(a4);
+       }
+    }
+    sendadc(sk, 1, eoc, NULL);
+}
+
 #define ADC_CMDFN(name) static void name(struct fnetnode *fn, wchar_t *command, wchar_t *sender, int argc, wchar_t **argv)
-#define ADC_CMDCOM struct socket *sk = fn->sk; struct adchub *hub = fn->data;
+#ifdef __GNUC__
+#define UNUSED __attribute__ ((unused))
+#else
+#define UNUSED
+#endif
+#define ADC_CMDCOM \
+       struct adchub *hub UNUSED = fn->data; \
+       struct socket *sk UNUSED = hub->sk;
 
 ADC_CMDFN(cmd_sup)
 {
@@ -231,23 +302,26 @@ ADC_CMDFN(cmd_sid)
     hub->sid = swcsdup(argv[1]);
     if(hub->state == ADC_PROTOCOL) {
        hub->state = ADC_IDENTIFY;
+       sendinf(fn);
     }
 }
 
 ADC_CMDFN(cmd_inf)
 {
     ADC_CMDCOM;
+    wchar_t *p;
     
     if(sender == NULL) {
-       
+       if((p = findkv(argv, L"NI")) != NULL)
+           fnetsetname(fn, p);
     }
 }
 
 static struct command hubcmds[] = {
-    {L"SUP", 1, 0, cmd_sup},
-    {L"SID", 2, 0, cmd_sid},
-    {L"INF", 0, 0, cmd_inf},
-    {NULL, 0, 0, NULL}
+    {L"SUP", 1, 0, 0, cmd_sup},
+    {L"SID", 2, 0, 0, cmd_sid},
+    {L"INF", 1, 0, 0, cmd_inf},
+    {NULL, 0, 0, 0, NULL}
 };
 
 static void dispatch(struct qcmd *qcmd, struct fnetnode *fn)
@@ -279,13 +353,21 @@ static void dispatch(struct qcmd *qcmd, struct fnetnode *fn)
        if(!wcscmp(cmd->name, qcmd->args[0] + 1)) {
            if(argc < cmd->minargs)
                return;
-           cmd->func(fn, cmdnm, sender, argc, qcmd->args);
+           cmd->func(fn, cmdnm, sender, argc, argv);
            return;
        }
     }
     flog(LOG_DEBUG, "unknown adc command: %ls", qcmd->args[0]);
 }
 
+static void peeraccept(struct lport *lp, struct socket *newsk, void *uudata)
+{
+}
+
+static void udpread(struct socket *sk, void *uudata)
+{
+}
+
 static void hubread(struct socket *sk, struct fnetnode *fn)
 {
     int ret;
@@ -344,32 +426,31 @@ static void huberr(struct socket *sk, int err, struct fnetnode *fn)
     killfnetnode(fn);
 }
 
-static void hubconnect(struct fnetnode *fn)
+static void hubconnect(struct fnetnode *fn, struct socket *sk)
 {
     struct adchub *hub;
     
-    fn->sk->readcb = (void (*)(struct socket *, void *))hubread;
-    fn->sk->errcb = (void (*)(struct socket *, int, void *))huberr;
-    fn->sk->data = fn;
-    getfnetnode(fn);
+    sk->readcb = (void (*)(struct socket *, void *))hubread;
+    sk->errcb = (void (*)(struct socket *, int, void *))huberr;
+    sk->data = fn;
     
     hub = smalloc(sizeof(*hub));
     memset(hub, 0, sizeof(*hub));
+    getsock(hub->sk = sk);
     if((hub->ich = iconv_open("wchar_t", "utf-8")) == (iconv_t)-1) {
        flog(LOG_CRIT, "iconv cannot handle UTF-8: %s", strerror(errno));
        killfnetnode(fn);
        return;
     }
     fn->data = hub;
-    sendadc(fn->sk, 0, L"HSUP", L"ADBASE", eoc, NULL);
+    sendadc(sk, 0, L"HSUP", L"ADBASE", L"ADTIGR", eoc, NULL);
 }
 
 static void hubdestroy(struct fnetnode *fn)
 {
     struct adchub *hub;
     
-    if((hub = fn->data) == NULL)
-       return;
+    hub = fn->data;
     iconv_close(hub->ich);
     if(hub->inbuf != NULL)
        free(hub->inbuf);
@@ -378,6 +459,14 @@ static void hubdestroy(struct fnetnode *fn)
     free(hub);
 }
 
+static void hubkill(struct fnetnode *fn)
+{
+    struct adchub *hub;
+    
+    hub = fn->data;
+    closesock(hub->sk);
+}
+
 static int hubsetnick(struct fnetnode *fn, wchar_t *newnick)
 {
     return(0);
@@ -391,6 +480,7 @@ static int hubreqconn(struct fnetpeer *peer)
 static struct fnet adcnet_store = {
     .connect = hubconnect,
     .destroy = hubdestroy,
+    .kill = hubkill,
     .setnick = hubsetnick,
     .reqconn = hubreqconn,
     .name = L"adc"
@@ -413,7 +503,7 @@ static int run(void)
        if((hub = fn->data) == NULL)
            continue;
        if((qcmd = ulqcmd(&hub->queue)) != NULL) {
-           if((fn->sk != NULL) && (fn->sk->state == SOCK_EST))
+           if((hub->sk != NULL) && (hub->sk->state == SOCK_EST))
                dispatch(qcmd, fn);
            freeqcmd(qcmd);
            ret = 1;
@@ -430,18 +520,78 @@ static void preinit(int hup)
     regfnet(adcnet);
 }
 
-static int init(int hup)
+static void makepid(char *idbuf)
 {
     int i;
+    int fd, ret;
+    
+    i = 0;
+    if((fd = open("/dev/urandom", O_RDONLY)) >= 0) {
+       for(i = 0; i < 24; i += ret) {
+           if((ret = read(fd, idbuf + i, 24 - i)) <= 0) {
+               flog(LOG_WARNING, "could not read random data from /dev/urandom for ADC PID: %s", (errno == 0)?"EOF":strerror(errno));
+               break;
+           }
+       }
+       close(fd);
+    } else {
+       flog(LOG_WARNING, "could not open /dev/urandom: %s", strerror(errno));
+    }
+    if(i != 24) {
+       for(i = 0; i < sizeof(idbuf); i++)
+           idbuf[i] = rand() % 256;
+    }
+}
+
+static int updateudpport(struct configvar *var, void *uudata)
+{
+    struct sockaddr_in addr;
+    struct socket *newsock;
+    
+    memset(&addr, 0, sizeof(addr));
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(var->val.num);
+    if((newsock = netcsdgram((struct sockaddr *)&addr, sizeof(addr))) == NULL)
+    {
+       flog(LOG_WARNING, "could not create new DC UDP socket, reverting to old: %s", strerror(errno));
+       return(0);
+    }
+    newsock->readcb = udpread;
+    if(udpsock != NULL)
+       putsock(udpsock);
+    udpsock = newsock;
+    return(0);
+}
+
+static int updatetcpport(struct configvar *var, void *uudata)
+{
+    struct sockaddr_in addr;
+    struct lport *newsock;
+    
+    memset(&addr, 0, sizeof(addr));
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(var->val.num);
+    if((newsock = netcslisten(SOCK_STREAM, (struct sockaddr *)&addr, sizeof(addr), peeraccept, NULL)) == NULL)
+       flog(LOG_INFO, "could not listen to a remote address, going into passive mode");
+    if(tcpsock != NULL)
+       closelport(tcpsock);
+    tcpsock = newsock;
+    return(0);
+}
+
+static int init(int hup)
+{
     char idbuf[24], *id32;
     struct tigerhash th;
+    struct sockaddr_in addr;
     
     if(!hup) {
        eoc = swcsdup(L"");
+       ns = swcsdup(L"");
+       fmt = swcsdup(L"");
        
        if((privid = fetchvar("adc.pid", NULL)) == NULL) {
-           for(i = 0; i < sizeof(idbuf); i++)
-               idbuf[i] = rand() % 256;
+           makepid(idbuf);
            id32 = base32encode(idbuf, sizeof(idbuf));
            id32[39] = 0;
            privid = icmbstowcs(id32, "us-ascii");
@@ -459,6 +609,23 @@ static int init(int hup)
        id32[39] = 0;
        cid = icmbstowcs(id32, "us-ascii");
        free(id32);
+       
+       memset(&addr, 0, sizeof(addr));
+       addr.sin_family = AF_INET;
+       addr.sin_port = htons(confgetint("adc", "udpport"));
+       if((udpsock = netcsdgram((struct sockaddr *)&addr, sizeof(addr))) == NULL) {
+           flog(LOG_CRIT, "could not create ADC UDP socket: %s", strerror(errno));
+           return(1);
+       }
+       udpsock->readcb = udpread;
+       addr.sin_port = htons(confgetint("adc", "tcpport"));
+       if((tcpsock = netcslisten(SOCK_STREAM, (struct sockaddr *)&addr, sizeof(addr), peeraccept, NULL)) == NULL) {
+           flog(LOG_CRIT, "could not create ADC TCP socket: %s", strerror(errno));
+           return(1);
+       }
+       CBREG(confgetvar("adc", "udpport"), conf_update, updateudpport, NULL, NULL);
+       CBREG(confgetvar("adc", "tcpport"), conf_update, updatetcpport, NULL, NULL);
+       CBREG(confgetvar("net", "mode"), conf_update, updatetcpport, NULL, NULL);
     }
     return(0);
 }