Look up correct start of tag.
[doldaconnect.git] / daemon / fnet-dc.c
index 056c3d1..ed34272 100644 (file)
@@ -44,6 +44,7 @@
 #include "transfer.h"
 #include "sysevents.h"
 #include "net.h"
+#include "tiger.h"
 
 /*
  * The Direct Connect protocol is extremely ugly. Thus, this code must
 #endif
 
 #define PEER_CMD 0
-#define PEER_TRNS 1
-#define PEER_SYNC 2
+#define PEER_STOP 1
+#define PEER_TRNS 2
+#define PEER_SYNC 3
+#define PEER_TTHL 4
 
 #define CPRS_NONE 0
 #define CPRS_ZLIB 1
@@ -89,6 +92,7 @@ struct command
 {
     char *name;
     void (*handler)(struct socket *sk, void *data, char *cmd, char *args);
+    int stop;
 };
 
 struct qcommand
@@ -102,7 +106,7 @@ struct dchub
     char *inbuf;
     size_t inbufdata, inbufsize;
     struct qcommand *queue;
-    int extended;
+    int extended, dcppemu;
     char *nativename;
     char *nativenick;
 };
@@ -122,17 +126,20 @@ struct dcpeer
     struct fnetnode *fn;
     char *inbuf;
     size_t inbufdata, inbufsize;
+    size_t curread, totalsize;
     int freeing;
+    struct timer *timeout;
     struct qcommand *queue;
     struct transfer *transfer;
     int state;
     int ptclose;      /* Close after transfer is complete */
     int accepted;     /* If false, we connected, otherwise, we accepted */
-    int extended;
+    int extended, dcppemu;
     int direction;    /* Using the constants from transfer.h */
     int compress;
+    int hascurpos, fetchingtthl, notthl;
+    struct tigertreehash tth;
     void *cprsdata;
-    char *mbspath;
     char *key;
     char *nativename;
     char **supports;
@@ -158,6 +165,7 @@ static void transwrite(struct socket *sk, struct dcpeer *peer);
 static void updatehmlist(void);
 static void updatexmllist(void);
 static void updatexmlbz2list(void);
+static void requestfile(struct dcpeer *peer);
 
 static int reservedchar(unsigned char c)
 {
@@ -203,6 +211,50 @@ static char *dcmakekey(char *lock)
     return(key);
 }
 
+static char *pathnmdc2adc(char *path)
+{
+    char *ret;
+    size_t retsize, retdata;
+    
+    if(!strcmp(path, "files.xml") || !strcmp(path, "files.xml.bz2") || !strcmp(path, "MyList.DcLst"))
+       return(sstrdup(path));
+    ret = NULL;
+    retsize = retdata = 0;
+    addtobuf(ret, '/');
+    for(; *path; path++)
+    {
+       if(*path == '\\')
+           addtobuf(ret, '/');
+       else
+           addtobuf(ret, *path);
+    }
+    addtobuf(ret, 0);
+    return(ret);
+}
+
+static int isdchash(struct hash *hash)
+{
+    if(wcscmp(hash->algo, L"TTH"))
+       return(0);
+    if(hash->len != 24)
+       return(0);
+    return(1);
+}
+
+static int supports(struct dcpeer *peer, char *cap)
+{
+    char **p;
+    
+    if(peer->supports == NULL)
+       return(0);
+    for(p = peer->supports; *p != NULL; p++)
+    {
+       if(!strcasecmp(*p, cap))
+           return(1);
+    }
+    return(0);
+}
+
 static void endcompress(struct dcpeer *peer)
 {
     if(peer->compress == CPRS_ZLIB)
@@ -393,6 +445,14 @@ static void hubrecvchat(struct socket *sk, struct fnetnode *fn, char *from, char
     free(chat);
 }
 
+static void peertimeout(int cancelled, struct dcpeer *peer)
+{
+    peer->timeout = NULL;
+    if(cancelled)
+       return;
+    freedcpeer(peer);
+}
+
 static void sendadc(struct socket *sk, char *arg)
 {
     char *buf;
@@ -478,14 +538,16 @@ static char **parseadc(char *args)
                addtobuf(retbuf, NULL);
                freeparr(retbuf);
                return(NULL);
-           } else if(*args == 's') {
+           } else if((*args == 's') || (*args == ' ')) {
                addtobuf(buf, ' ');
            } else if(*args == 'n') {
                addtobuf(buf, '\n');
            } else if(*args == '\\') {
                addtobuf(buf, '\\');
            }
+           args++;
            state = 1;
+           break;
        }
     }
     if(buf != NULL)
@@ -518,6 +580,29 @@ static char *tr(char *str, char *trans)
     return(str);
 }
 
+static char *getadcid(struct dcpeer *peer)
+{
+    char *buf;
+    char *ret;
+    int isfilelist;
+    
+    if(!wcscmp(peer->transfer->path, L"files.xml") || !wcscmp(peer->transfer->path, L"files.xml.bz2") || !wcscmp(peer->transfer->path, L"MyList.DcLst"))
+       isfilelist = 1;
+    if(!isfilelist && (peer->transfer->hash != NULL) && isdchash(peer->transfer->hash) && supports(peer, "tthf"))
+    {
+       buf = base32encode(peer->transfer->hash->buf, 24);
+       ret = sprintf2("TTH/%.39s", buf);
+       free(buf);
+    } else {
+       if((buf = icwcstombs(peer->transfer->path, "UTF-8")) == NULL)
+           return(NULL);
+       ret = pathnmdc2adc(buf);
+       free(buf);
+    }
+    return(ret);
+}
+
+
 static int trresumecb(struct transfer *transfer, wchar_t *cmd, wchar_t *arg, struct dcpeer *peer)
 {
     if(!wcscmp(cmd, L"resume"))
@@ -528,245 +613,122 @@ static int trresumecb(struct transfer *transfer, wchar_t *cmd, wchar_t *arg, str
            freedcpeer(peer);
        } else {
            transfer->curpos = wcstol(arg, NULL, 10);
-           qstrf(peer->sk, "$Get %s$%i|", peer->mbspath, transfer->curpos + 1);
+           peer->hascurpos = 1;
+           requestfile(peer);
        }
-       free(peer->mbspath);
-       peer->mbspath = NULL;
        return(1);
     }
     return(0);
 }
 
-static void peerhandleaction(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+static void sendmynick(struct dcpeer *peer)
 {
     struct dchub *hub;
-    struct dcexppeer *expect;
-    struct transfer *transfer;
-    wchar_t tbuf[128];
-    char *mbsbuf;
     
-    hub = NULL;
-    if(peer->fn != NULL)
+    hub = (peer->fn == NULL)?NULL:(peer->fn->data);
+    if(hub == NULL)
+       qstrf(peer->sk, "$MyNick %s|", icswcstombs(confgetstr("cli", "defnick"), DCCHARSET, "DoldaConnectUser-IN"));
+    else
+       qstrf(peer->sk, "$MyNick %s|", hub->nativenick);
+}
+
+static void sendpeerlock(struct dcpeer *peer)
+{
+    if(peer->dcppemu)
+       qstrf(peer->sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DCPLUSPLUS0.674ABCABC|");
+    else
+       qstrf(peer->sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DOLDA%sABCABCABC|", VERSION);
+}
+
+static void sendsupports(struct dcpeer *peer)
+{
+    if(peer->dcppemu)
+       qstr(peer->sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG |");
+    else
+       qstr(peer->sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG|");
+}
+
+static void requestfile(struct dcpeer *peer)
+{
+    char *buf;
+    
+    if(peer->transfer->size == -1)
     {
-       if(peer->fn->fnet != &dcnet)
+       if((buf = icswcstombs(peer->transfer->path, DCCHARSET, NULL)) == NULL)
        {
-           peer->sk->close = 1;
+           transferseterror(peer->transfer, TRNSE_NOTFOUND);
+           freedcpeer(peer);
            return;
        }
-       hub = peer->fn->data;
-    }
-    if(peer->transfer != NULL)
-    {
-       swprintf(tbuf, 128, L"hs: dc-%s", cmd);
-       transfersetactivity(peer->transfer, tbuf);
+       /* The transfer will be restarted later from
+        * cmd_filelength when it detects that the sizes
+        * don't match. */
+       qstrf(peer->sk, "$Get %s$1|", buf);
+       return;
     }
-    if(peer->accepted)
+    if((peer->transfer->hash == NULL) && !peer->notthl)
     {
-       if(cmd == NULL) /* Connect event */
+       if(supports(peer, "adcget") && supports(peer, "tthl"))
        {
-       } else if(!strcmp(cmd, "$MyNick")) {
-           for(expect = expected; expect != NULL; expect = expect->next)
-           {
-               if(!strcmp(expect->nick, args))
-                   break;
-           }
-           if(expect == NULL)
-           {
-               peer->fn = NULL;
-           } else {
-               peer->fn = expect->fn;
-               getfnetnode(peer->fn);
-               freeexppeer(expect);
-           }
-       } else if(!strcmp(cmd, "$Lock")) {
-           if(peer->wcsname == NULL)
-           {
-               freedcpeer(peer);
-               return;
-           }
-           if(hub == NULL)
-               qstrf(sk, "$MyNick %s|", icswcstombs(confgetstr("cli", "defnick"), DCCHARSET, "DoldaConnectUser-IN"));
-           else
-               qstrf(sk, "$MyNick %s|", hub->nativenick);
-#ifdef DCPP_MASQUERADE
-           qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DCPLUSPLUS0.674ABCABC|");
-#else
-           qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DOLDA%sABCABCABC|", VERSION);
-#endif
-           if(peer->extended)
-           {
-#ifdef DCPP_MASQUERADE
-               qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG |");
-#else
-               qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG|");
-#endif
-           }
-           for(transfer = transfers; transfer != NULL; transfer = transfer->next)
-           {
-               if((transfer->dir == TRNSD_DOWN) && (transfer->iface == NULL) && !wcscmp(peer->wcsname, transfer->peerid))
-                   break;
-           }
-           if(transfer == NULL)
-           {
-               peer->direction = TRNSD_UP;
-               transfer = newupload(peer->fn, &dcnet, peer->wcsname, &dctransfer, peer);
-               transfersetnick(transfer, peer->wcsname);
-               peer->transfer = transfer;
-           } else {
-               peer->direction = TRNSD_DOWN;
-               peer->transfer = transfer;
-               transferattach(transfer, &dctransfer, peer);
-               transfersetnick(transfer, peer->wcsname);
-               transfersetstate(transfer, TRNS_HS);
-           }
-           qstrf(sk, "$Direction %s %i|", (peer->direction == TRNSD_UP)?"Upload":"Download", rand() % 10000);
-           if(peer->key != NULL)
-           {
-               /* I hate the DC protocol so much... */
-               qstrf(sk, "$Key %s|", peer->key);
-               free(peer->key);
-               peer->key = NULL;
-           }
-           if(peer->direction == TRNSD_DOWN)
-           {
-               if((mbsbuf = icwcstombs(peer->transfer->path, DCCHARSET)) == NULL)
-               {
-                   /* I believe that NOTFOUND should be used
-                    * since giving a path that cannot be
-                    * represented in the protocol's charset is
-                    * literally the same as giving a path that
-                    * the client doesn't have. */
-                   transferseterror(peer->transfer, TRNSE_NOTFOUND);
-                   freedcpeer(peer);
-                   return;
-               }
-               if(peer->transfer->size == -1)
-               {
-                   /* The transfer will be restarted later from
-                    * cmd_filelength when it detects that the sizes
-                    * don't match. */
-                   qstrf(sk, "$Get %s$1|", mbsbuf);
-               } else {
-                   if(forkfilter(transfer))
-                   {
-                       flog(LOG_WARNING, "could not fork filter for transfer %i: %s", transfer->id, strerror(errno));
-                       freedcpeer(peer);
-                       free(mbsbuf);
-                       return;
-                   }
-                   peer->mbspath = sstrdup(mbsbuf);
-                   CBREG(transfer, trans_filterout, (int (*)(struct transfer *, wchar_t *, wchar_t *, void *))trresumecb, NULL, peer);
-               }
-               free(mbsbuf);
-           }
-       } else if(!strcmp(cmd, "$FileLength")) {
-           if(peer->transfer == NULL)
+           qstr(peer->sk, "$ADCGET");
+           sendadc(peer->sk, "tthl");
+           if((buf = getadcid(peer)) == NULL)
            {
+               transferseterror(peer->transfer, TRNSE_NOTFOUND);
                freedcpeer(peer);
                return;
            }
-           transfersetstate(peer->transfer, TRNS_MAIN);
-           socksettos(peer->sk, confgetint("transfer", "dltos"));
-           peer->state = PEER_TRNS;
-           peer->sk->readcb = (void (*)(struct socket *, void *))transread;
-           peer->sk->errcb = (void (*)(struct socket *, int, void *))transerr;
-           qstr(peer->sk, "$Send|");
+           sendadc(peer->sk, buf);
+           free(buf);
+           sendadc(peer->sk, "0");
+           sendadc(peer->sk, "-1");
+           qstr(peer->sk, "|");
+           peer->fetchingtthl = 1;
+           return;
+       }
+    }
+    if(!peer->hascurpos)
+    {
+       if(forkfilter(peer->transfer))
+       {
+           flog(LOG_WARNING, "could not fork filter for transfer %i: %s", peer->transfer->id, strerror(errno));
+           freedcpeer(peer);
+           return;
+       }
+       CBREG(peer->transfer, trans_filterout, (int (*)(struct transfer *, wchar_t *, wchar_t *, void *))trresumecb, NULL, peer);
+       return;
+    }
+    if(supports(peer, "adcget"))
+    {
+       qstr(peer->sk, "$ADCGET");
+       sendadc(peer->sk, "file");
+       if((buf = getadcid(peer)) == NULL)
+       {
+           transferseterror(peer->transfer, TRNSE_NOTFOUND);
+           freedcpeer(peer);
+           return;
        }
+       sendadc(peer->sk, buf);
+       free(buf);
+       sendadcf(peer->sk, "%i", peer->transfer->curpos);
+       sendadcf(peer->sk, "%i", peer->transfer->size - peer->transfer->curpos);
+       qstr(peer->sk, "|");
+    } else if(supports(peer, "xmlbzlist")) {
+       if((buf = icswcstombs(peer->transfer->path, "UTF-8", NULL)) == NULL)
+       {
+           transferseterror(peer->transfer, TRNSE_NOTFOUND);
+           freedcpeer(peer);
+           return;
+       }
+       qstrf(peer->sk, "$UGetBlock %i %i %s|", peer->transfer->curpos, peer->transfer->size - peer->transfer->curpos, buf);
     } else {
-       if(cmd == NULL) /* Connect event */
+       if((buf = icswcstombs(peer->transfer->path, DCCHARSET, NULL)) == NULL)
        {
-           if(hub == NULL)
-               qstrf(sk, "$MyNick %s|", icswcstombs(confgetstr("cli", "defnick"), DCCHARSET, "DoldaConnectUser-IN"));
-           else
-               qstrf(sk, "$MyNick %s|", hub->nativenick);
-#ifdef DCPP_MASQUERADE
-           qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DCPLUSPLUS0.674ABCABC|");
-#else
-           qstrf(sk, "$Lock EXTENDEDPROTOCOLABCABCABCABCABCABC Pk=DOLDA%sABCABCABC|", VERSION);
-#endif
-       } else if(!strcmp(cmd, "$Direction")) {
-           if(peer->wcsname == NULL)
-           {
-               freedcpeer(peer);
-               return;
-           }
-           if(peer->direction == TRNSD_UP)
-           {
-               transfer = newupload(peer->fn, &dcnet, peer->wcsname, &dctransfer, peer);
-               transfersetnick(transfer, peer->wcsname);
-               peer->transfer = transfer;
-           } else {
-               for(transfer = transfers; transfer != NULL; transfer = transfer->next)
-               {
-                   if((transfer->dir == TRNSD_DOWN) && (transfer->state == TRNS_WAITING) && !wcscmp(peer->wcsname, transfer->peerid))
-                       break;
-               }
-               if(transfer == NULL)
-               {
-                   freedcpeer(peer);
-                   return;
-               }
-               peer->transfer = transfer;
-               transferattach(transfer, &dctransfer, peer);
-               transfersetnick(transfer, peer->wcsname);
-               transfersetstate(transfer, TRNS_HS);
-           }
-           if(peer->extended)
-           {
-#ifdef DCPP_MASQUERADE
-               qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG |");
-#else
-               qstr(sk, "$Supports MiniSlots XmlBZList ADCGet TTHL TTHF GetZBlock ZLIG|");
-#endif
-           }
-           qstrf(sk, "$Direction %s %i|", (peer->direction == TRNSD_UP)?"Upload":"Download", rand() % 10000);
-           if(peer->key != NULL)
-               qstrf(sk, "$Key %s|", peer->key);
-           if(peer->direction == TRNSD_DOWN)
-           {
-               if((mbsbuf = icwcstombs(peer->transfer->path, DCCHARSET)) == NULL)
-               {
-                   /* I believe that NOTFOUND should be used
-                    * since giving a path that cannot be
-                    * represented in the protocol's charset is
-                    * literally the same as giving a path that
-                    * the client doesn't have. */
-                   transferseterror(peer->transfer, TRNSE_NOTFOUND);
-                   freedcpeer(peer);
-                   return;
-               }
-               if(peer->transfer->size == -1)
-               {
-                   /* The transfer will be restarted later from
-                    * cmd_filelength when it detects that the sizes
-                    * don't match. */
-                   qstrf(sk, "$Get %s$1|", mbsbuf);
-               } else {
-                   if(forkfilter(transfer))
-                   {
-                       flog(LOG_WARNING, "could not fork filter for transfer %i: %s", transfer->id, strerror(errno));
-                       freedcpeer(peer);
-                       free(mbsbuf);
-                       return;
-                   }
-                   peer->mbspath = sstrdup(mbsbuf);
-                   CBREG(transfer, trans_filterout, (int (*)(struct transfer *, wchar_t *, wchar_t *, void *))trresumecb, NULL, peer);
-               }
-               free(mbsbuf);
-           }
-       } else if(!strcmp(cmd, "$FileLength")) {
-           if(peer->transfer == NULL)
-           {
-               freedcpeer(peer);
-               return;
-           }
-           transfersetstate(peer->transfer, TRNS_MAIN);
-           socksettos(peer->sk, confgetint("transfer", "dltos"));
-           peer->state = PEER_TRNS;
-           peer->sk->readcb = (void (*)(struct socket *, void *))transread;
-           peer->sk->errcb = (void (*)(struct socket *, int, void *))transerr;
-           qstr(peer->sk, "$Send|");
+           transferseterror(peer->transfer, TRNSE_NOTFOUND);
+           freedcpeer(peer);
+           return;
        }
+       qstrf(peer->sk, "$Get %s$%i|", buf, peer->transfer->curpos + 1);
     }
 }
 
@@ -775,23 +737,30 @@ static void sendmyinfo(struct socket *sk, struct fnetnode *fn)
     struct dchub *hub;
     char *buf;
     struct fnetnode *cfn;
-    int numhubs;
+    int hn1, hn2, hn3;
     
     hub = fn->data;
     qstrf(sk, "$MyINFO $ALL %s ", hub->nativenick);
     buf = tr(icswcstombs(confgetstr("dc", "desc"), DCCHARSET, "Charset_conv_failure"), "$_|_");
     qstrf(sk, "%s", buf);
-    numhubs = 0;
+    hn1 = hn2 = hn3 = 0;
     for(cfn = fnetnodes; cfn != NULL; cfn = cfn->next)
     {
        if((cfn->state == FNN_EST) || (cfn->state == FNN_HS))
-           numhubs++;
+       {
+           if(cfn->regstatus == FNNS_OP)
+               hn3++;
+           else if(cfn->regstatus == FNNS_REG)
+               hn2++;
+           else
+               hn1++;
+       }
     }
-    qstrf(sk, "<%s V:%s,M:%c,H:%i/0/0,S:%i>",
-         DCIDTAG,
-         DCIDTAGV,
+    qstrf(sk, "<%s V:%s,M:%c,H:%i/%i/%i,S:%i>",
+         (hub->dcppemu)?"++":"Dolda",
+         (hub->dcppemu)?"0.674":VERSION,
          (tcpsock == NULL)?'P':'A',
-         numhubs,
+         hn1, hn2, hn3,
          confgetint("transfer", "slots")
          );
     qstrf(sk, "$ $");
@@ -836,11 +805,10 @@ static void cmd_lock(struct socket *sk, struct fnetnode *fn, char *cmd, char *ar
        *(p++) = 0;
     if(hub->extended)
     {
-#ifdef DCPP_MASQUERADE
-       qstrf(sk, "$Supports UserCommand NoGetINFO NoHello UserIP2 TTHSearch GetZBlock |");
-#else
-       qstrf(sk, "$Supports UserCommand NoGetINFO NoHello UserIP2 TTHSearch GetZBlock|");
-#endif
+       if(hub->dcppemu)
+           qstrf(sk, "$Supports UserCommand NoGetINFO NoHello UserIP2 TTHSearch GetZBlock |");
+       else
+           qstrf(sk, "$Supports UserCommand NoGetINFO NoHello UserIP2 TTHSearch GetZBlock|");
     }
     key = dcmakekey(args);
     qstrf(sk, "$Key %s|", key);
@@ -977,7 +945,7 @@ static void cmd_myinfo(struct socket *sk, struct fnetnode *fn, char *cmd, char *
     *p2 = 0;
     if((buf = icmbstowcs(p, DCCHARSET)) == NULL)
        return;
-    if((wcslen(buf) > 0) && (buf[wcslen(buf) - 1] == L'>') && ((wp = wcschr(buf, L'<')) != NULL))
+    if((wcslen(buf) > 0) && (buf[wcslen(buf) - 1] == L'>') && ((wp = wcsrchr(buf, L'<')) != NULL))
     {
        buf[wcslen(buf) - 1] = L'\0';
        *(wp++) = L'\0';
@@ -1042,7 +1010,7 @@ static void cmd_forcemove(struct socket *sk, struct fnetnode *fn, char *cmd, cha
     } else {
        freeargs = 0;
     }
-    if((newfn = fnetinitconnect(L"dc", args)) != NULL)
+    if((newfn = fnetinitconnect(L"dc", args, NULL)) != NULL)
     {
        linkfnetnode(newfn);
        putfnetnode(newfn);
@@ -1158,7 +1126,7 @@ static void cmd_search(struct socket *sk, struct fnetnode *fn, char *cmd, char *
        goto out;
     if(*p == 0)
        goto out;
-    if(*(p++) == 'T')
+    if((*(p++) == 'T') && (minsize == 0))
     {
        maxsize = 0;
        minsize = -1;
@@ -1472,8 +1440,39 @@ static void cmd_usercommand(struct socket *sk, struct fnetnode *fn, char *cmd, c
     /* Do nothing for now. */
 }
 
+static void cmd_getpass(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+    struct dchub *hub;
+    wchar_t *pw;
+    char *mbspw;
+    
+    hub = fn->data;
+    pw = wpfind(fn->args, L"password");
+    if((pw == NULL) || ((mbspw = icwcstombs(pw, DCCHARSET)) == NULL))
+    {
+       killfnetnode(fn);
+       return;
+    }
+    qstrf(sk, "$MyPass %s|", mbspw);
+    free(mbspw);
+    fn->regstatus = FNNS_REG;
+    hubhandleaction(sk, fn, cmd, args);
+}
+
+static void cmd_logedin(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
+{
+    struct dchub *hub;
+    
+    hub = fn->data;
+    fn->regstatus = FNNS_OP;
+    hubhandleaction(sk, fn, cmd, args);
+}
+
 static void cmd_mynick(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
 {
+    struct dcexppeer *expect;
+    struct dchub *hub;
+
     if(peer->nativename != NULL)
        free(peer->nativename);
     peer->nativename = sstrdup(args);
@@ -1484,46 +1483,149 @@ static void cmd_mynick(struct socket *sk, struct dcpeer *peer, char *cmd, char *
        freedcpeer(peer);
        return;
     }
-    peerhandleaction(sk, peer, cmd, args);
+    if(peer->accepted)
+    {
+       for(expect = expected; expect != NULL; expect = expect->next)
+       {
+           if(!strcmp(expect->nick, args))
+               break;
+       }
+       if(expect == NULL)
+       {
+           peer->fn = NULL;
+       } else {
+           hub = expect->fn->data;
+           peer->fn = expect->fn;
+           getfnetnode(peer->fn);
+           peer->dcppemu = hub->dcppemu;
+           freeexppeer(expect);
+       }
+    }
 }
 
 static void cmd_direction(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
 {
     char *p;
+    int mydir;
+    struct transfer *transfer;
     
     if((p = strchr(args, ' ')) == NULL)
        return;
     *p = 0;
     if(!strcmp(args, "Upload"))
-       peer->direction = TRNSD_DOWN;
+       mydir = TRNSD_DOWN;
     if(!strcmp(args, "Download"))
-       peer->direction = TRNSD_UP;
-    peerhandleaction(sk, peer, cmd, args);
+       mydir = TRNSD_UP;
+    if(peer->accepted)
+    {
+       if((peer->transfer == NULL) || (mydir != peer->direction))
+       {
+           freedcpeer(peer);
+           return;
+       }
+       if(peer->direction == TRNSD_DOWN)
+           requestfile(peer);
+    } else {
+       if(peer->wcsname == NULL)
+       {
+           freedcpeer(peer);
+           return;
+       }
+       peer->direction = mydir;
+       if(peer->direction == TRNSD_UP)
+       {
+           transfer = newupload(peer->fn, &dcnet, peer->wcsname, &dctransfer, peer);
+       } else {
+           if((transfer = finddownload(peer->wcsname)) == NULL)
+           {
+               freedcpeer(peer);
+               return;
+           }
+           transferattach(transfer, &dctransfer, peer);
+           transfersetstate(transfer, TRNS_HS);
+       }
+       transfersetnick(transfer, peer->wcsname);
+       peer->transfer = transfer;
+       if(peer->extended)
+           sendsupports(peer);
+       qstrf(sk, "$Direction %s %i|", (peer->direction == TRNSD_UP)?"Upload":"Download", rand() % 10000);
+       if(peer->key != NULL)
+           qstrf(sk, "$Key %s|", peer->key);
+       if(peer->direction == TRNSD_DOWN)
+           requestfile(peer);
+    }
 }
 
 static void cmd_peerlock(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
 {
-    char *p;
+    char *p, *key;
+    struct transfer *transfer;
     
     if((p = strchr(args, ' ')) == NULL)
        return;
     *p = 0;
     if(!strncmp(args, "EXTENDEDPROTOCOL", 16))
        peer->extended = 1;
-    if(peer->key != NULL)
-       free(peer->key);
-    peer->key = dcmakekey(args);
-    peerhandleaction(sk, peer, cmd, args);
+    key = dcmakekey(args);
+    if(peer->accepted)
+    {
+       if(peer->wcsname == NULL)
+       {
+           freedcpeer(peer);
+           return;
+       }
+       sendmynick(peer);
+       sendpeerlock(peer);
+       if(peer->extended)
+           sendsupports(peer);
+       if((transfer = finddownload(peer->wcsname)) == NULL)
+       {
+           peer->direction = TRNSD_UP;
+           transfer = newupload(peer->fn, &dcnet, peer->wcsname, &dctransfer, peer);
+       } else {
+           peer->direction = TRNSD_DOWN;
+           transferattach(transfer, &dctransfer, peer);
+           transfersetstate(transfer, TRNS_HS);
+       }
+       transfersetnick(transfer, peer->wcsname);
+       peer->transfer = transfer;
+       qstrf(sk, "$Direction %s %i|", (peer->direction == TRNSD_UP)?"Upload":"Download", rand() % 10000);
+       qstrf(sk, "$Key %s|", key);
+    } else {
+       if(peer->key != NULL)
+           free(peer->key);
+       peer->key = key;
+    }
 }
 
 static void cmd_key(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
 {
-    peerhandleaction(sk, peer, cmd, args);
+    /* NOP */
+}
+
+static void startdl(struct dcpeer *peer)
+{
+    if(peer->timeout != NULL)
+       canceltimer(peer->timeout);
+    peer->state = PEER_TRNS;
+    transferstartdl(peer->transfer, peer->sk);
+    peer->sk->readcb = (void (*)(struct socket *, void *))transread;
+    peer->sk->errcb = (void (*)(struct socket *, int, void *))transerr;
+}
+
+static void startul(struct dcpeer *peer)
+{
+    if(peer->timeout != NULL)
+       canceltimer(peer->timeout);
+    peer->state = PEER_TRNS;
+    transferstartul(peer->transfer, peer->sk);
+    peer->sk->writecb = (void (*)(struct socket *, void *))transwrite;
 }
 
 static void cmd_filelength(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
 {
     int size;
+    struct transfer *transfer;
     
     if(peer->transfer == NULL)
     {
@@ -1531,22 +1633,27 @@ static void cmd_filelength(struct socket *sk, struct dcpeer *peer, char *cmd, ch
        return;
     }
     size = atoi(args);
-    if(peer->transfer->size == size)
+    if(peer->transfer->size != size)
     {
-       
-    } else {
-       /* Will have to restart, then... I really hate this, but what
-        * am I to do then, considering the DC protocol requires the
-        * resume offset before it sends the filesize? */
        transfersetsize(peer->transfer, size);
+       transfer = peer->transfer;
        freedcpeer(peer);
+       trytransferbypeer(transfer->fnet, transfer->peerid);
        return;
     }
-    peerhandleaction(sk, peer, cmd, args);
+    startdl(peer);
+    qstr(peer->sk, "$Send|");
 }
 
 static void cmd_error(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
 {
+    if(peer->fetchingtthl)
+    {
+       peer->fetchingtthl = 0;
+       peer->notthl = 1;
+       requestfile(peer);
+       return;
+    }
     if((peer->transfer != NULL) && (peer->transfer->dir == TRNSD_DOWN))
     {
        transferseterror(peer->transfer, TRNSE_NOTFOUND);
@@ -1680,15 +1787,10 @@ static void cmd_get(struct socket *sk, struct dcpeer *peer, char *cmd, char *arg
     } else if(fd >= 0) {
        if((buf2 = icsmbstowcs(args, DCCHARSET, NULL)) != NULL)
            transfersetpath(peer->transfer, buf2);
+       peer->transfer->flags.b.minislot = 1;
     }
     if(fd < 0)
     {
-       if(slotsleft() < 1)
-       {
-           qstr(sk, "$MaxedOut|");
-           freedcpeer(peer);
-           return;
-       }
        if((node = resdcpath(args, DCCHARSET, '\\')) == NULL)
        {
            qstrf(sk, "$Error File not in share|");
@@ -1716,6 +1818,14 @@ static void cmd_get(struct socket *sk, struct dcpeer *peer, char *cmd, char *arg
        freedcpeer(peer);
        return;
     }
+    if(sb.st_size < 65536)
+       peer->transfer->flags.b.minislot = 1;
+    if(!peer->transfer->flags.b.minislot && (slotsleft() < 1)) {
+       close(fd);
+       qstr(sk, "$MaxedOut|");
+       freedcpeer(peer);
+       return;
+    }
     if((offset != 0) && (lseek(fd, offset, SEEK_SET) < 0))
     {
        close(fd);
@@ -1727,14 +1837,6 @@ static void cmd_get(struct socket *sk, struct dcpeer *peer, char *cmd, char *arg
     transferprepul(peer->transfer, sb.st_size, offset, -1, lesk);
     putsock(lesk);
     qstrf(sk, "$FileLength %i|", peer->transfer->size);
-    peerhandleaction(sk, peer, cmd, args);
-}
-
-static void starttrans(struct dcpeer *peer)
-{
-    peer->state = PEER_TRNS;
-    transferstartul(peer->transfer, peer->sk);
-    peer->sk->writecb = (void (*)(struct socket *, void *))transwrite;
 }
 
 static void cmd_send(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
@@ -1750,8 +1852,7 @@ static void cmd_send(struct socket *sk, struct dcpeer *peer, char *cmd, char *ar
        return;
     }
     peer->ptclose = 1;
-    starttrans(peer);
-    peerhandleaction(sk, peer, cmd, args);
+    startul(peer);
 }
 
 static void cmd_supports(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
@@ -1780,7 +1881,6 @@ static void cmd_supports(struct socket *sk, struct dcpeer *peer, char *cmd, char
     } while((p = p2) != NULL);
     addtobuf(arr, NULL);
     peer->supports = arr;
-    peerhandleaction(sk, peer, cmd, args);
 }
 
 static void cmd_getblock(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
@@ -1829,14 +1929,10 @@ static void cmd_getblock(struct socket *sk, struct dcpeer *peer, char *cmd, char
     } else if(fd >= 0) {
        if((buf2 = icsmbstowcs(args, charset, NULL)) != NULL)
            transfersetpath(peer->transfer, buf2);
+       peer->transfer->flags.b.minislot = 1;
     }
     if(fd < 0)
     {
-       if(slotsleft() < 1)
-       {
-           qstr(sk, "$MaxedOut|");
-           return;
-       }
        if((node = resdcpath(p, charset, '\\')) == NULL)
        {
            qstr(sk, "$Error File not in cache|");
@@ -1861,6 +1957,13 @@ static void cmd_getblock(struct socket *sk, struct dcpeer *peer, char *cmd, char
        qstr(sk, "$Error|");
        return;
     }
+    if(sb.st_size < 65536)
+       peer->transfer->flags.b.minislot = 1;
+    if(!peer->transfer->flags.b.minislot && (slotsleft() < 1)) {
+       close(fd);
+       qstr(sk, "$MaxedOut|");
+       return;
+    }
     if((start != 0) && ((start >= sb.st_size) || (lseek(fd, start, SEEK_SET) < 0)))
     {
        close(fd);
@@ -1873,8 +1976,7 @@ static void cmd_getblock(struct socket *sk, struct dcpeer *peer, char *cmd, char
     transferprepul(peer->transfer, sb.st_size, start, start + numbytes, lesk);
     putsock(lesk);
     qstrf(sk, "$Sending %i|", numbytes);
-    starttrans(peer);
-    peerhandleaction(sk, peer, cmd, args);
+    startul(peer);
 }
 
 static void cmd_adcget(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
@@ -1914,14 +2016,10 @@ static void cmd_adcget(struct socket *sk, struct dcpeer *peer, char *cmd, char *
     } else if(fd >= 0) {
        if((wbuf = icsmbstowcs(argv[1], "UTF-8", NULL)) != NULL)
            transfersetpath(peer->transfer, wbuf);
+       peer->transfer->flags.b.minislot = 1;
     }
     if(fd < 0)
     {
-       if(slotsleft() < 1)
-       {
-           qstr(sk, "$MaxedOut|");
-           goto out;
-       }
        if(!strncmp(argv[1], "TTH/", 4))
        {
            if((node = findbytth(argv[1] + 4)) == NULL)
@@ -1930,7 +2028,7 @@ static void cmd_adcget(struct socket *sk, struct dcpeer *peer, char *cmd, char *
                goto out;
            }
        } else {
-           if((node = resdcpath(argv[1], "UTF-8", '/')) == NULL)
+           if((node = resdcpath((argv[1][0] == '/')?(argv[1] + 1):(argv[1]), "UTF-8", '/')) == NULL)
            {
                qstr(sk, "$Error File not in cache|");
                goto out;
@@ -1961,6 +2059,12 @@ static void cmd_adcget(struct socket *sk, struct dcpeer *peer, char *cmd, char *
            qstr(sk, "$Error|");
            goto out;
        }
+       if(sb.st_size < 65536)
+           peer->transfer->flags.b.minislot = 1;
+       if(!peer->transfer->flags.b.minislot && (slotsleft() < 1)) {
+           qstr(sk, "$MaxedOut|");
+           goto out;
+       }
        if((start != 0) && ((start >= sb.st_size) || (lseek(fd, start, SEEK_SET) < 0)))
        {
            qstr(sk, "$Error Offset out of range|");
@@ -1980,7 +2084,7 @@ static void cmd_adcget(struct socket *sk, struct dcpeer *peer, char *cmd, char *
        if(peer->compress == CPRS_ZLIB)
            sendadc(sk, "ZL1");
        qstr(sk, "|");
-       starttrans(peer);
+       startul(peer);
     } else if(!strcmp(argv[0], "tthl")) {
        /*
         * XXX: Implement full TTHL support.
@@ -2005,7 +2109,6 @@ static void cmd_adcget(struct socket *sk, struct dcpeer *peer, char *cmd, char *
        qstr(sk, "$Error Namespace not implemented|");
        goto out;
     }
-    peerhandleaction(sk, peer, cmd, args);
     
  out:
     if(fd >= 0)
@@ -2013,6 +2116,120 @@ static void cmd_adcget(struct socket *sk, struct dcpeer *peer, char *cmd, char *
     freeparr(argv);
 }
 
+static void handletthl(struct dcpeer *peer)
+{
+    char buf[24];
+    
+    while(peer->inbufdata >= 24)
+    {
+       pushtigertree(&peer->tth, peer->inbuf);
+       memmove(peer->inbuf, peer->inbuf + 24, peer->inbufdata -= 24);
+       peer->curread += 24;
+    }
+    if(peer->curread >= peer->totalsize)
+    {
+       if(peer->timeout == NULL)
+           peer->timeout = timercallback(ntime() + 180, (void (*)(int, void *))peertimeout, peer);
+       peer->state = PEER_CMD;
+       synctigertree(&peer->tth);
+       restigertree(&peer->tth, buf);
+       transfersethash(peer->transfer, newhash(L"TTH", 24, buf));
+       requestfile(peer);
+    }
+}
+
+static void cmd_adcsnd(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+    char **argv;
+    int start, numbytes;
+    
+    if(peer->transfer == NULL)
+    {
+       freedcpeer(peer);
+       return;
+    }
+    if((argv = parseadc(args)) == NULL)
+    {
+       freedcpeer(peer);
+       return;
+    }
+    if(parrlen(argv) < 4)
+    {
+       freedcpeer(peer);
+       goto out;
+    }
+    start = atoi(argv[2]);
+    numbytes = atoi(argv[3]);
+    if(!strcmp(argv[0], "tthl"))
+    {
+       if((start != 0) || (numbytes % 24 != 0))
+       {
+           /* Weird. Bail out. */
+           freedcpeer(peer);
+           goto out;
+       }
+       if(peer->timeout != NULL)
+           canceltimer(peer->timeout);
+       peer->state = PEER_TTHL;
+       peer->totalsize = numbytes;
+       peer->curread = 0;
+       peer->fetchingtthl = 0;
+       inittigertree(&peer->tth);
+       handletthl(peer);
+    } else if(!strcmp(argv[0], "file")) {
+       if(start != peer->transfer->curpos)
+       {
+           freedcpeer(peer);
+           goto out;
+       }
+       if(start + numbytes != peer->transfer->size)
+       {
+           transfersetsize(peer->transfer, start + numbytes);
+           freedcpeer(peer);
+           goto out;
+       }
+       startdl(peer);
+       if(peer->inbufdata > 0)
+       {
+           sockpushdata(sk, peer->inbuf, peer->inbufdata);
+           peer->inbufdata = 0;
+           transread(sk, peer);
+       }
+    } else {
+       /* We certainly didn't request this...*/
+       freedcpeer(peer);
+       goto out;
+    }
+    
+ out:
+    freeparr(argv);
+}
+
+static void cmd_sending(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
+{
+    int numbytes;
+    
+    if(peer->transfer == NULL)
+    {
+       freedcpeer(peer);
+       return;
+    }
+    numbytes = atoi(args);
+    if(peer->transfer->size - peer->transfer->curpos != numbytes)
+    {
+       transfersetsize(peer->transfer, peer->transfer->curpos + numbytes);
+       freedcpeer(peer);
+       return;
+    }
+    startdl(peer);
+    if(peer->inbufdata > 0)
+    {
+       sockpushdata(sk, peer->inbuf, peer->inbufdata);
+       peer->inbufdata = 0;
+       transread(sk, peer);
+    }
+}
+
 /*
 Hub skeleton:
 static void cmd_(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
@@ -2026,7 +2243,6 @@ static void cmd_(struct socket *sk, struct fnetnode *fn, char *cmd, char *args)
 Peer skeleton:
 static void cmd_(struct socket *sk, struct dcpeer *peer, char *cmd, char *args)
 {
-    peerhandleaction(sk, peer, cmd, args);
 }
 
 */
@@ -2172,6 +2388,33 @@ static void findsizelimit(struct sexpr *sexpr, int *min, int *max)
        *max = retmax;
 }
 
+static struct hash *findsehash(struct sexpr *sexpr)
+{
+    struct hash *h1, *h2;
+    
+    switch(sexpr->op)
+    {
+    case SOP_AND:
+       if((h1 = findsehash(sexpr->l)) != NULL)
+           return(h1);
+       if((h1 = findsehash(sexpr->r)) != NULL)
+           return(h1);
+       break;
+    case SOP_OR:
+       h1 = findsehash(sexpr->l);
+       h2 = findsehash(sexpr->r);
+       if(hashcmp(h1, h2))
+           return(h1);
+       break;
+    case SOP_HASHIS:
+       if(!wcscmp(sexpr->d.hash->algo, L"TTH"))
+           return(sexpr->d.hash);
+    default:
+       break;
+    }
+    return(NULL);
+}
+
 static int hubsearch(struct fnetnode *fn, struct search *srch, struct srchfnnlist *ln)
 {
     struct dchub *hub;
@@ -2181,13 +2424,14 @@ static int hubsearch(struct fnetnode *fn, struct search *srch, struct srchfnnlis
     struct sockaddr *name;
     socklen_t namelen;
     int minsize, maxsize;
-    char sizedesc[32];
+    struct hash *hash;
     
     hub = fn->data;
     if((fn->state != FNN_EST) || (fn->sk == NULL) || (fn->sk->state != SOCK_EST))
        return(1);
     list = findsexprstrs(srch->sexpr);
     findsizelimit(srch->sexpr, &minsize, &maxsize);
+    hash = findsehash(srch->sexpr);
     if((minsize != 0) && (maxsize != -1))
     {
        /* Choose either minsize or maxsize by trying to determine
@@ -2198,52 +2442,64 @@ static int hubsearch(struct fnetnode *fn, struct search *srch, struct srchfnnlis
        else
            maxsize = -1;
     }
-    if(minsize != 0)
-    {
-       snprintf(sizedesc, sizeof(sizedesc), "T?F?%i", minsize);
-    } else if(maxsize != -1) {
-       snprintf(sizedesc, sizeof(sizedesc), "T?T?%i", maxsize);
-    } else {
-       strcpy(sizedesc, "F?F?0");
-    }
     sstr = NULL;
     sstrsize = sstrdata = 0;
-    if(list != NULL)
+    if((hash != NULL) && (hash->len == 24))
     {
-       for(cur = list; cur != NULL; cur = cur->next)
+       /* Prioritize hash searches above all else */
+       bufcat(sstr, "F?T?0?9?TTH:", 12);
+       buf = base32encode(hash->buf, hash->len);
+       
+       bufcat(sstr, buf, 39);
+       free(buf);
+    } else {
+       if(minsize != 0)
        {
-           if((buf = icwcstombs(cur->str, DCCHARSET)) == NULL)
-           {
-               /* Can't find anything anyway if the search expression
-                * requires characters outside DC's charset. There's
-                * nothing technically wrong with the search itself,
-                * however, so return success. This should be
-                * considered as an optimization. */
-               freesl(&list);
-               if(sstr != NULL)
-                   free(sstr);
-               return(0);
-           }
-           if(cur != list)
-               addtobuf(sstr, '$');
-           /*
-            * It probably doesn't hurt if buf contains any extra
-            * dollar signs - it will just result in extra search
-            * terms, and the extraneous results will be filtered by
-            * the search layer anyway. It hurts if it contains any
-            * pipes, though, so let's sell them for money.
-            */
-           for(p = buf; *p; p++)
+           sizebuf2(sstr, sstrdata + 32, 1);
+           sstrdata += snprintf(sstr + sstrdata, sstrsize - sstrdata, "T?F?%i?1?", minsize);
+       } else if(maxsize != -1) {
+           sizebuf2(sstr, sstrdata + 32, 1);
+           sstrdata += snprintf(sstr + sstrdata, sstrsize - sstrdata, "T?T?%i?1?", maxsize);
+       } else {
+           bufcat(sstr, "F?F?0?1?", 8);
+       }
+       if(list != NULL)
+       {
+           for(cur = list; cur != NULL; cur = cur->next)
            {
-               if(*p == '|')
-                   *p = '$';
+               if((buf = icwcstombs(cur->str, DCCHARSET)) == NULL)
+               {
+                   /* Can't find anything anyway if the search expression
+                    * requires characters outside DC's charset. There's
+                    * nothing technically wrong with the search itself,
+                    * however, so return success. This should be
+                    * considered as an optimization. */
+                   freesl(&list);
+                   if(sstr != NULL)
+                       free(sstr);
+                   return(0);
+               }
+               if(cur != list)
+                   addtobuf(sstr, '$');
+               /*
+                * It probably doesn't hurt if buf contains any extra
+                * dollar signs - it will just result in extra search
+                * terms, and the extraneous results will be filtered by
+                * the search layer anyway. It hurts if it contains any
+                * pipes, though, so let's sell them for money.
+                */
+               for(p = buf; *p; p++)
+               {
+                   if(*p == '|')
+                       *p = '$';
+               }
+               bufcat(sstr, buf, strlen(buf));
+               free(buf);
            }
-           bufcat(sstr, buf, strlen(buf));
-           free(buf);
+       } else {
+           /* Will match all files... :-/ */
+           addtobuf(sstr, '.');
        }
-    } else {
-       /* Will match all files... :-/ */
-       addtobuf(sstr, '.');
     }
     addtobuf(sstr, 0);
     if(tcpsock != NULL)
@@ -2252,11 +2508,11 @@ static int hubsearch(struct fnetnode *fn, struct search *srch, struct srchfnnlis
        {
            flog(LOG_WARNING, "cannot get address of UDP socket");
        } else {
-           qstrf(fn->sk, "$Search %s %s?1?%s|", formataddress(name, namelen), sizedesc, sstr);
+           qstrf(fn->sk, "$Search %s %s|", formataddress(name, namelen), sstr);
            free(name);
        }
     } else {
-       qstrf(fn->sk, "$Search Hub:%s %s?1?%s|", hub->nativenick, sizedesc, sstr);
+       qstrf(fn->sk, "$Search Hub:%s %s|", hub->nativenick, sstr);
     }
     free(sstr);
     freesl(&list);
@@ -2287,6 +2543,8 @@ struct command hubcmds[] =
     {"$To:", cc(cmd_to)},
     {"$SR", cc(cmd_sr)},
     {"$UserCommand", cc(cmd_usercommand)},
+    {"$GetPass", cc(cmd_getpass)},
+    {"$LogedIn", cc(cmd_logedin)}, /* sic */
     {NULL, NULL}
 };
 
@@ -2307,6 +2565,8 @@ struct command peercmds[] =
     {"$GetZBlock", cc(cmd_getblock)},
     {"$UGetZBlock", cc(cmd_getblock)},
     {"$ADCGET", cc(cmd_adcget)},
+    {"$ADCSND", cc(cmd_adcsnd), 1},
+    {"$Sending", cc(cmd_sending), 1},
     {NULL, NULL}
 };
 #undef cc
@@ -2381,10 +2641,13 @@ static void dctransgotdata(struct transfer *transfer, struct dcpeer *peer)
                {
                    freedcpeer(peer);
                } else {
+                   if(peer->timeout == NULL)
+                       peer->timeout = timercallback(ntime() + 180, (void (*)(int, void *))peertimeout, peer);
                    peer->state = PEER_CMD;
                    endcompress(peer);
                    transfersetstate(transfer, TRNS_HS);
                    socksettos(peer->sk, confgetint("fnet", "fnptos"));
+                   transfer->flags.b.minislot = 0;
                    peer->sk->writecb = NULL;
                }
            }
@@ -2636,10 +2899,20 @@ static int hubsetnick(struct fnetnode *fn, wchar_t *newnick)
 static struct dchub *newdchub(struct fnetnode *fn)
 {
     struct dchub *new;
+    wchar_t *emu;
     
     new = smalloc(sizeof(*new));
     memset(new, 0, sizeof(*new));
     fn->data = new;
+    if(confgetint("dc", "dcppemu"))
+       new->dcppemu = 1;
+    if((emu = wpfind(fn->args, L"dcppemu")) != NULL)
+    {
+       if(*emu == L'y')
+           new->dcppemu = 1;
+       if(*emu == L'n')
+           new->dcppemu = 0;
+    }
     if(hubsetnick(fn, fn->mynick))
        fnetsetnick(fn, L"DoldaConnectUser-IN");
     /* IN as in Invalid Nick */
@@ -2655,6 +2928,8 @@ static struct dcpeer *newdcpeer(struct socket *sk)
     new->transfer = NULL;
     getsock(sk);
     new->sk = sk;
+    if(confgetint("dc", "dcppemu"))
+       new->dcppemu = 1;
     new->next = peers;
     new->prev = NULL;
     if(peers != NULL)
@@ -2684,6 +2959,8 @@ static void freedcpeer(struct dcpeer *peer)
            resettransfer(peer->transfer);
        transferdetach(peer->transfer);
     }
+    if(peer->timeout != NULL)
+       canceltimer(peer->timeout);
     if(peer->sk->data == peer)
        peer->sk->data = NULL;
     peer->sk->readcb = NULL;
@@ -2697,8 +2974,6 @@ static void freedcpeer(struct dcpeer *peer)
            free(peer->supports[i]);
        free(peer->supports);
     }
-    if(peer->mbspath != NULL)
-       free(peer->mbspath);
     if(peer->inbuf != NULL)
        free(peer->inbuf);
     if(peer->key != NULL)
@@ -2785,21 +3060,35 @@ static void peerread(struct socket *sk, struct dcpeer *peer)
 {
     char *newbuf, *p;
     size_t datalen;
+    struct command *cmd;
 
     if((newbuf = sockgetinbuf(sk, &datalen)) == NULL)
        return;
     sizebuf2(peer->inbuf, peer->inbufdata + datalen, 1);
     memcpy(peer->inbuf + peer->inbufdata, newbuf, datalen);
     free(newbuf);
-    p = peer->inbuf + peer->inbufdata;
     peer->inbufdata += datalen;
-    while((datalen > 0) && (p = memchr(p, '|', datalen)) != NULL)
+    if(peer->state == PEER_CMD)
     {
-       *(p++) = 0;
-       newqcmd(&peer->queue, peer->inbuf);
-       memmove(peer->inbuf, p, peer->inbufdata -= p - peer->inbuf);
-       datalen = peer->inbufdata;
        p = peer->inbuf;
+       while((peer->inbufdata > 0) && (p = memchr(peer->inbuf, '|', peer->inbufdata)) != NULL)
+       {
+           *(p++) = 0;
+           newqcmd(&peer->queue, peer->inbuf);
+           for(cmd = peercmds; cmd->handler != NULL; cmd++)
+           {
+               if(!memcmp(peer->inbuf, cmd->name, strlen(cmd->name)) && ((peer->inbuf[strlen(cmd->name)] == ' ') || (peer->inbuf[strlen(cmd->name)] == '|')))
+                   break;
+           }
+           memmove(peer->inbuf, p, peer->inbufdata -= p - peer->inbuf);
+           if(cmd->stop)
+           {
+               peer->state = PEER_STOP;
+               break;
+           }
+       }
+    } else if(peer->state == PEER_TTHL) {
+       handletthl(peer);
     }
 }
 
@@ -2811,21 +3100,26 @@ static void peererror(struct socket *sk, int err, struct dcpeer *peer)
 static void peerconnect(struct socket *sk, int err, struct fnetnode *fn)
 {
     struct dcpeer *peer;
+    struct dchub *hub;
     
     if(err != 0)
     {
        putfnetnode(fn);
        return;
     }
+    hub = fn->data;
     peer = newdcpeer(sk);
     peer->fn = fn;
     peer->accepted = 0;
+    peer->dcppemu = hub->dcppemu;
     sk->readcb = (void (*)(struct socket *, void *))peerread;
     sk->errcb = (void (*)(struct socket *, int, void *))peererror;
     sk->data = peer;
     socksettos(sk, confgetint("fnet", "fnptos"));
-    peerhandleaction(sk, peer, NULL, NULL);
     putsock(sk);
+    peer->timeout = timercallback(ntime() + 180, (void (*)(int, void *))peertimeout, peer);
+    sendmynick(peer);
+    sendpeerlock(peer);
 }
 
 static void peeraccept(struct socket *sk, struct socket *newsk, void *data)
@@ -2838,7 +3132,7 @@ static void peeraccept(struct socket *sk, struct socket *newsk, void *data)
     newsk->errcb = (void (*)(struct socket *, int, void *))peererror;
     newsk->data = peer;
     socksettos(newsk, confgetint("fnet", "fnptos"));
-    peerhandleaction(sk, peer, NULL, NULL);
+    peer->timeout = timercallback(ntime() + 180, (void (*)(int, void *))peertimeout, peer);
 }
 
 static void updatehmlist(void)
@@ -3020,7 +3314,10 @@ static void updatexmllist(void)
     for(i = 0; i < sizeof(cidbuf) - 1; i++)
        cidbuf[i] = (rand() % ('Z' - 'A' + 1)) + 'A';
     cidbuf[i] = 0;
-    fprintf(fs, "<FileListing Version=\"1\" CID=\"%s\" Base=\"/\" Generator=\"%s\">\r\n", cidbuf, DCIDFULL);
+    if(confgetint("dc", "dcppemu"))
+       fprintf(fs, "<FileListing Version=\"1\" CID=\"%s\" Base=\"/\" Generator=\"DC++ 0.674\">\r\n", cidbuf);
+    else
+       fprintf(fs, "<FileListing Version=\"1\" CID=\"%s\" Base=\"/\" Generator=\"%s\">\r\n", cidbuf, "DoldaConnect" VERSION);
     
     node = shareroot->child;
     lev = 0;
@@ -3065,11 +3362,10 @@ static void updatexmllist(void)
        }
     }
     
-#ifdef DCPP_MASQUERADE
-    fprintf(fs, "</FileListing>");
-#else
-    fprintf(fs, "</FileListing>\r\n");
-#endif
+    if(confgetint("dc", "dcppemu"))
+       fprintf(fs, "</FileListing>");
+    else
+       fprintf(fs, "</FileListing>\r\n");
     fclose(fs);
 }
 
@@ -3198,6 +3494,9 @@ static int run(void)
        nextpeer = peer->next;
        if((qcmd = ulqcmd(&peer->queue)) != NULL)
        {
+           if(peer->timeout != NULL)
+               canceltimer(peer->timeout);
+           peer->timeout = timercallback(ntime() + 180, (void (*)(int, void *))peertimeout, peer);
            if(*qcmd->string == '$')
                dispatchcommand(qcmd, peercmds, peer->sk, peer);
            freeqcmd(qcmd);
@@ -3307,6 +3606,7 @@ static struct configvar myvars[] =
     {CONF_VAR_STRING, "email", {.str = L"spam@spam.org"}},
     {CONF_VAR_INT, "udpport", {.num = 0}},
     {CONF_VAR_INT, "tcpport", {.num = 0}},
+    {CONF_VAR_BOOL, "dcppemu", {.num = 0}},
     {CONF_VAR_END}
 };