Fixed HTTP-client query-string handling bug.
[doldaconnect.git] / lib / uimisc.c
index d989e1e..2aa6fd3 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
 */
 
 #include <unistd.h>
-/* I'm very unsure about this, but for now it defines wcstoll (which
- * should be defined anyway) and doesn't break anything... let's keep
- * two eyes wide open, though. */
-#define __USE_ISOC99
+#include <stdlib.h>
 #include <wchar.h>
 #include <wctype.h>
 #include <pwd.h>
 #include <string.h>
-#include <malloc.h>
 #include <stdio.h>
+#include <sys/poll.h>
 
 #ifdef HAVE_CONFIG_H
 #include <config.h>
 #endif
 #include <doldaconnect/uilib.h>
 #include <doldaconnect/uimisc.h>
-#include <doldaconnect/utils.h>
+#include <utils.h>
 
 #ifdef HAVE_KRB5
 #include <krb5.h>
@@ -63,6 +60,13 @@ struct logindata
     struct authmech *mech;
 };
 
+struct synclogindata
+{
+    int aborted;
+    int err;
+    wchar_t *reason;
+};
+
 struct gencbdata
 {
     void (*callback)(int resp, void *data);
@@ -79,6 +83,26 @@ struct fnetcbdata
 struct dc_fnetnode *dc_fnetnodes = NULL;
 struct dc_transfer *dc_transfers = NULL;
 
+static void message(int bits, char *format, ...)
+{
+    static int hb = -1;
+    char *v;
+    va_list args;
+    
+    if(hb == -1)
+    {
+       hb = 0;
+       if((v = getenv("LIBDCUI_MSG")) != NULL)
+           hb = strtol(v, NULL, 0) & 65535;
+    }
+    if(hb & bits)
+    {
+       va_start(args, format);
+       vfprintf(stderr, format, args);
+       va_end(args);
+    }
+}
+
 static void freelogindata(struct logindata *data)
 {
     if((data->mech != NULL) && (data->mech->release != NULL))
@@ -167,7 +191,7 @@ static void process_pam(struct dc_response *resp, struct logindata *data)
                data->callback(DC_LOGIN_ERR_CONV, NULL, data->data);
                freelogindata(data);
            } else {
-               dc_queuecmd(logincallback, data, L"pass", L"%%s", buf, NULL);
+               dc_queuecmd(logincallback, data, L"pass", L"%s", buf, NULL);
            }
            if(buf != NULL)
            {
@@ -205,77 +229,6 @@ struct krb5data
     int valid, fwd, fwded;
 };
 
-static char *hexencode(char *data, size_t datalen)
-{
-    char *buf, this;
-    size_t bufsize, bufdata;
-    int dig;
-    
-    buf = NULL;
-    bufsize = bufdata = 0;
-    for(; datalen > 0; datalen--, data++)
-    {
-       dig = (*data & 0xF0) >> 4;
-       if(dig > 9)
-           this = 'A' + dig - 10;
-       else
-           this = dig + '0';
-       addtobuf(buf, this);
-       dig = *data & 0x0F;
-       if(dig > 9)
-           this = 'A' + dig - 10;
-       else
-           this = dig + '0';
-       addtobuf(buf, this);
-    }
-    addtobuf(buf, 0);
-    return(buf);
-}
-
-static char *hexdecode(char *data, size_t *len)
-{
-    char *buf, this;
-    size_t bufsize, bufdata;
-    
-    buf = NULL;
-    bufsize = bufdata = 0;
-    for(; *data; data++)
-    {
-       if((*data >= 'A') && (*data <= 'F'))
-       {
-           this = (this & 0x0F) | ((*data - 'A' + 10) << 4);
-       } else if((*data >= '0') && (*data <= '9')) {
-           this = (this & 0x0F) | ((*data - '0') << 4);
-       } else {
-           if(buf != NULL)
-               free(buf);
-           return(NULL);
-       }
-       data++;
-       if(!*data)
-       {
-           if(buf != NULL)
-               free(buf);
-           return(NULL);
-       }
-       if((*data >= 'A') && (*data <= 'F'))
-       {
-           this = (this & 0xF0) | (*data - 'A' + 10);
-       } else if((*data >= '0') && (*data <= '9')) {
-           this = (this & 0xF0) | (*data - '0');
-       } else {
-           if(buf != NULL)
-               free(buf);
-           return(NULL);
-       }
-       addtobuf(buf, this);
-    }
-    addtobuf(buf, 0);
-    if(len != NULL)
-       *len = bufdata - 1;
-    return(buf);
-}
-
 static void process_krb5(struct dc_response *resp, struct logindata *data)
 {
     int ret;
@@ -284,6 +237,7 @@ static void process_krb5(struct dc_response *resp, struct logindata *data)
     krb5_data k5d;
     krb5_ap_rep_enc_part *repl;
     char *buf;
+    size_t kdl;
     
     krb = data->mechdata;
     switch(resp->code)
@@ -297,14 +251,15 @@ static void process_krb5(struct dc_response *resp, struct logindata *data)
        {
        case 0:
            buf = hexencode(krb->reqbuf.data, krb->reqbuf.length);
-           dc_queuecmd(logincallback, data, L"pass", L"%%s", buf, NULL);
+           dc_queuecmd(logincallback, data, L"pass", L"%s", buf, NULL);
            free(buf);
            krb->state = 1;
            break;
        case 1:
            if((ires = dc_interpret(resp)) != NULL)
            {
-               k5d.data = hexdecode(icswcstombs(ires->argv[0].val.str, NULL, NULL), &k5d.length);
+               k5d.data = hexdecode(icswcstombs(ires->argv[0].val.str, NULL, NULL), &kdl);
+               k5d.length = kdl;
                if(!krb->valid)
                {
                    if((ret = krb5_rd_rep(krb->context, krb->authcon, &k5d, &repl)) != 0)
@@ -324,7 +279,7 @@ static void process_krb5(struct dc_response *resp, struct logindata *data)
                    krb->reqbuf.data = NULL;
                    if((ret = krb5_fwd_tgt_creds(krb->context, krb->authcon, NULL, krb->servcreds->client, krb->servcreds->server, 0, 1, &krb->reqbuf)) != 0)
                    {
-                       fprintf(stderr, "krb5_fwd_tgt_creds reported an error: %s\n", error_message(ret));
+                       message(1, "krb5_fwd_tgt_creds reported an error: %s\n", error_message(ret));
                        dc_queuecmd(logincallback, data, L"pass", L"31", NULL);
                        krb->fwd = 0;
                        krb->state = 2;
@@ -369,6 +324,11 @@ static int init_krb5(struct logindata *data)
     krb5_data cksum;
     krb5_creds creds;
     
+    if(dc_gethostname() == NULL)
+    {
+       message(1, "cannot use krb5 without a host name\n");
+       return(1);
+    }
     krb = smalloc(sizeof(*krb));
     memset(krb, 0, sizeof(*krb));
     krb->fwd = 1;
@@ -376,28 +336,28 @@ static int init_krb5(struct logindata *data)
     data->mechdata = krb;
     if((ret = krb5_init_context(&krb->context)) != 0)
     {
-       fprintf(stderr, "krb5_init_context reported an error: %s\n", error_message(ret));
+       message(1, "krb5_init_context reported an error: %s\n", error_message(ret));
        return(1);
     }
     if((ret = krb5_auth_con_init(krb->context, &krb->authcon)) != 0)
     {
-       fprintf(stderr, "krb5_auth_con_init reported an error: %s\n", error_message(ret));
+       message(1, "krb5_auth_con_init reported an error: %s\n", error_message(ret));
        return(1);
     }
     krb5_auth_con_setflags(krb->context, krb->authcon, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
     if((ret = krb5_sname_to_principal(krb->context, dc_gethostname(), "doldacond", KRB5_NT_SRV_HST, &krb->sprinc)) != 0)
     {
-       fprintf(stderr, "krb5_sname_to_principal reported an error: %s\n", error_message(ret));
+       message(1, "krb5_sname_to_principal reported an error: %s\n", error_message(ret));
        return(1);
     }
     if((ret = krb5_cc_default(krb->context, &krb->ccache)) != 0)
     {
-       fprintf(stderr, "krb5_cc_default reported an error: %s\n", error_message(ret));
+       message(1, "krb5_cc_default reported an error: %s\n", error_message(ret));
        return(1);
     }
     if((ret = krb5_cc_get_principal(krb->context, krb->ccache, &krb->myprinc)) != 0)
     {
-       fprintf(stderr, "krb5_cc_default reported an error: %s\n", error_message(ret));
+       message(1, "krb5_cc_default reported an error: %s\n", error_message(ret));
        return(1);
     }
     memset(&creds, 0, sizeof(creds));
@@ -405,7 +365,7 @@ static int init_krb5(struct logindata *data)
     creds.server = krb->sprinc;
     if((ret = krb5_get_credentials(krb->context, 0, krb->ccache, &creds, &krb->servcreds)) != 0)
     {
-       fprintf(stderr, "krb5_get_credentials reported an error: %s\n", error_message(ret));
+       message(1, "krb5_get_credentials reported an error: %s\n", error_message(ret));
        return(1);
     }
     /* WTF is this checksum stuff?! The Krb docs don't say a word about it! */
@@ -413,7 +373,7 @@ static int init_krb5(struct logindata *data)
     cksum.length = strlen(cksum.data);
     if((ret = krb5_mk_req_extended(krb->context, &krb->authcon, AP_OPTS_MUTUAL_REQUIRED, &cksum, krb->servcreds, &krb->reqbuf)) != 0)
     {
-       fprintf(stderr, "krb5_mk_req_extended reported an error: %s\n", error_message(ret));
+       message(1, "krb5_mk_req_extended reported an error: %s\n", error_message(ret));
        return(1);
     }
     free(cksum.data);
@@ -457,6 +417,12 @@ static struct authmech authmechs[] =
     },
 #endif
     {
+       .name = L"unix",
+       .process = process_authless,
+       .init = NULL,
+       .release = NULL
+    },
+    {
        .name = L"authless",
        .process = process_authless,
        .init = NULL,
@@ -473,7 +439,7 @@ static struct authmech authmechs[] =
     }
 };
 
-static int builtinconv(int type, wchar_t *text, char **resp, void *data)
+int dc_convtty(int type, wchar_t *text, char **resp, void *data)
 {
     char *buf, *pass;
     
@@ -490,6 +456,11 @@ static int builtinconv(int type, wchar_t *text, char **resp, void *data)
     return(1);
 }
 
+int dc_convnone(int type, wchar_t *text, char **resp, void *data)
+{
+    return(1);
+}
+
 static int logincallback(struct dc_response *resp)
 {
     int i;
@@ -522,12 +493,13 @@ static int logincallback(struct dc_response *resp)
                    {
                        odata = data->mechdata;
                        data->mechdata = NULL;
+                       message(4, "trying auth mech %ls\n", authmechs[i].name);
                        if((authmechs[i].init != NULL) && authmechs[i].init(data))
                        {
                            if(authmechs[i].release != NULL)
                                authmechs[i].release(data);
                            data->mechdata = odata;
-                           fprintf(stderr, "authentication mechanism %ls failed, trying further...\n", authmechs[i].name);
+                           message(2, "authentication mechanism %ls failed, trying further...\n", authmechs[i].name);
                        } else {
                            if((data->mech != NULL) && data->mech->release != NULL)
                            {
@@ -559,7 +531,7 @@ static int logincallback(struct dc_response *resp)
                    }
                    username = pwent->pw_name;
                }
-               dc_queuecmd(logincallback, data, L"login", data->mech->name, L"%%s", username, NULL);
+               dc_queuecmd(logincallback, data, L"login", data->mech->name, L"%s", username, NULL);
            }
        }
     } else if(!wcscmp(resp->cmdname, L"login") || !wcscmp(resp->cmdname, L"pass")) {
@@ -574,7 +546,7 @@ void dc_loginasync(char *username, int useauthless, int (*conv)(int, wchar_t *,
     
     data = smalloc(sizeof(*data));
     if(conv == NULL)
-       conv = builtinconv;
+       conv = dc_convtty;
     data->conv = conv;
     data->mech = NULL;
     data->data = udata;
@@ -592,6 +564,58 @@ void dc_loginasync(char *username, int useauthless, int (*conv)(int, wchar_t *,
     dc_queuecmd(logincallback, data, L"lsauth", NULL);
 }
 
+static void synclogincb(int err, wchar_t *reason, struct synclogindata *data)
+{
+    if(data->aborted)
+    {
+       free(data);
+       return;
+    }
+    data->err = err;
+    if(reason == NULL)
+       data->reason = NULL;
+    else
+       data->reason = swcsdup(reason);
+}
+
+int dc_login(char *username, int useauthless, int (*conv)(int, wchar_t *, char **, void *), wchar_t **reason)
+{
+    int ret, abort;
+    struct synclogindata *dbuf;
+    struct pollfd pfd;
+    
+    dbuf = smalloc(sizeof(*dbuf));
+    memset(dbuf, 0, sizeof(*dbuf));
+    dbuf->err = -1;
+    dc_loginasync(username, useauthless, conv, (void (*)(int, wchar_t *, void *))synclogincb, dbuf);
+    while(dbuf->err == -1)
+    {
+       pfd.fd = dc_getfd();
+       pfd.events = POLLIN;
+       if(dc_wantwrite())
+           pfd.events |= POLLOUT;
+       abort = 0;
+       if(poll(&pfd, 1, -1) < 0)
+           abort = 1;
+       if(!abort && (pfd.revents & POLLIN) && dc_handleread())
+           abort = 1;
+       if(!abort && (pfd.revents & POLLOUT) && dc_handlewrite())
+           abort = 1;
+       if(abort)
+       {
+           dbuf->aborted = 1;
+           return(-1);
+       }
+    }
+    if(reason != NULL)
+       *reason = dbuf->reason;
+    else if(dbuf->reason != NULL)
+       free(dbuf->reason);
+    ret = dbuf->err;
+    free(dbuf);
+    return(ret);
+}
+
 static struct dc_fnetpeerdatum *finddatum(struct dc_fnetnode *fn, wchar_t *id)
 {
     struct dc_fnetpeerdatum *datum;
@@ -733,6 +757,7 @@ static void delpeer(struct dc_fnetpeer *peer)
            free(peer->di[i].d.str);
        putdatum(peer->fn, peer->di[i].datum);
     }
+    free(peer->di);
     free(peer);
 }
 
@@ -869,6 +894,9 @@ static int getfnlistcallback(struct dc_response *resp)
                fn->name = swcsdup(ires->argv[2].val.str);
                fn->numusers = ires->argv[3].val.num;
                fn->state = ires->argv[4].val.num;
+               if(fn->pubid != NULL)
+                   free(fn->pubid);
+               fn->pubid = swcsdup(ires->argv[5].val.str);
            } else {
                fn = newfn();
                fn->id = ires->argv[0].val.num;
@@ -876,6 +904,7 @@ static int getfnlistcallback(struct dc_response *resp)
                fn->name = swcsdup(ires->argv[2].val.str);
                fn->numusers = ires->argv[3].val.num;
                fn->state = ires->argv[4].val.num;
+               fn->pubid = swcsdup(ires->argv[5].val.str);
                fn->found = 1;
            }
            dc_freeires(ires);
@@ -938,8 +967,8 @@ static int gettrlistcallback(struct dc_response *resp)
                }
                transfer->dir = ires->argv[1].val.num;
                transfer->state = ires->argv[2].val.num;
-               transfer->size = ires->argv[6].val.num;
-               transfer->curpos = ires->argv[7].val.num;
+               transfer->size = ires->argv[6].val.lnum;
+               transfer->curpos = ires->argv[7].val.lnum;
                if(transfer->hash != NULL)
                {
                    free(transfer->hash);
@@ -955,8 +984,8 @@ static int gettrlistcallback(struct dc_response *resp)
                transfer->peerid = swcsdup(ires->argv[3].val.str);
                transfer->peernick = swcsdup(ires->argv[4].val.str);
                transfer->path = swcsdup(ires->argv[5].val.str);
-               transfer->size = ires->argv[6].val.num;
-               transfer->curpos = ires->argv[7].val.num;
+               transfer->size = ires->argv[6].val.lnum;
+               transfer->curpos = ires->argv[7].val.lnum;
                if(wcslen(ires->argv[8].val.str) > 0)
                    transfer->hash = swcsdup(ires->argv[8].val.str);
                transfer->found = 1;
@@ -985,13 +1014,49 @@ static int gettrlistcallback(struct dc_response *resp)
     return(1);
 }
 
+static int sortlist1(const struct dc_respline *l1, const struct dc_respline *l2)
+{
+    return(wcscmp(l1->argv[1], l2->argv[1]));
+}
+
+static int sortlist2(const struct dc_fnetpeer **p1, const struct dc_fnetpeer **p2)
+{
+    return(wcscmp((*p1)->id, (*p2)->id));
+}
+
+static void fillpeer(struct dc_fnetpeer *peer, struct dc_respline *r)
+{
+    int i;
+    struct dc_fnetpeerdatum *datum;
+    
+    for(i = 3; i < r->argc; i += 2)
+    {
+       if((datum = finddatum(peer->fn, r->argv[i])) != NULL)
+       {
+           switch(datum->dt)
+           {
+           case DC_FNPD_INT:
+               peersetnum(peer, datum->id, wcstol(r->argv[i + 1], NULL, 10));
+               break;
+           case DC_FNPD_LL:
+               peersetlnum(peer, datum->id, wcstoll(r->argv[i + 1], NULL, 10));
+               break;
+           case DC_FNPD_STR:
+               peersetstr(peer, datum->id, r->argv[i + 1]);
+               break;
+           }
+       }
+    }
+}
+
 static int getpeerlistcallback(struct dc_response *resp)
 {
-    int i, o;
+    int i, o, c;
     struct dc_fnetnode *fn;
     struct fnetcbdata *data;
-    struct dc_fnetpeer *peer, *next;
-    struct dc_fnetpeerdatum *datum;
+    struct dc_fnetpeer *peer;
+    struct dc_fnetpeer **plist;
+    size_t plistsize, plistdata;
     
     data = resp->data;
     if((fn = dc_findfnetnode(data->fnid)) == NULL)
@@ -1002,38 +1067,43 @@ static int getpeerlistcallback(struct dc_response *resp)
     }
     if(resp->code == 200)
     {
-       for(peer = fn->peers; peer != NULL; peer = peer->next)
-           peer->found = 0;
-       for(i = 0; i < resp->numlines; i++)
+       qsort(resp->rlines, resp->numlines, sizeof(*resp->rlines), (int (*)(const void *, const void *))sortlist1);
+       plist = NULL;
+       plistsize = plistdata = 0;
+       for(i = 0, peer = fn->peers; peer != NULL; peer = peer->next)
+           addtobuf(plist, peer);
+       qsort(plist, plistdata, sizeof(*plist), (int (*)(const void *, const void *))sortlist2);
+       i = o = 0;
+       while(1)
        {
-           if((peer = dc_fnetfindpeer(fn, resp->rlines[i].argv[1])) == NULL)
-               peer = addpeer(fn, resp->rlines[i].argv[1], resp->rlines[i].argv[2]);
-           peer->found = 1;
-           for(o = 3; o < resp->rlines[i].argc; o += 2)
+           if((i < resp->numlines) && (o < plistdata))
            {
-               if((datum = finddatum(fn, resp->rlines[i].argv[o])) != NULL)
+               c = wcscmp(resp->rlines[i].argv[1], plist[o]->id);
+               if(c < 0)
                {
-                   switch(datum->dt)
-                   {
-                   case DC_FNPD_INT:
-                       peersetnum(peer, datum->id, wcstol(resp->rlines[i].argv[o + 1], NULL, 10));
-                       break;
-                   case DC_FNPD_LL:
-                       peersetlnum(peer, datum->id, wcstoll(resp->rlines[i].argv[o + 1], NULL, 10));
-                       break;
-                   case DC_FNPD_STR:
-                       peersetstr(peer, datum->id, resp->rlines[i].argv[o + 1]);
-                       break;
-                   }
+                   peer = addpeer(fn, resp->rlines[i].argv[1], resp->rlines[i].argv[2]);
+                   fillpeer(peer, resp->rlines + i);
+                   i++;
+               } else if(c > 0) {
+                   delpeer(plist[o]);
+                   o++;
+               } else {
+                   fillpeer(plist[o], resp->rlines + i);
+                   i++;
+                   o++;
                }
+           } else if(i < resp->numlines) {
+               peer = addpeer(fn, resp->rlines[i].argv[1], resp->rlines[i].argv[2]);
+               fillpeer(peer, resp->rlines + i);
+               i++;
+           } else if(o < plistdata) {
+               delpeer(plist[o]);
+               o++;
+           } else {
+               break;
            }
        }
-       for(peer = fn->peers; peer != NULL; peer = next)
-       {
-           next = peer->next;
-           if(!peer->found)
-               delpeer(peer);
-       }
+       free(plist);
     } else if(resp->code == 201) {
        while(fn->peers != NULL)
            delpeer(fn->peers);
@@ -1063,9 +1133,9 @@ static int getpalistcallback(struct dc_response *resp)
            adddatum(fn, ires->argv[0].val.str, ires->argv[1].val.num);
            dc_freeires(ires);
        }
-       dc_queuecmd(getpeerlistcallback, data, L"lspeers", L"%%i", fn->id, NULL);
+       dc_queuecmd(getpeerlistcallback, data, L"lspeers", L"%i", fn->id, NULL);
     } else if(resp->code == 201) {
-       dc_queuecmd(getpeerlistcallback, data, L"lspeers", L"%%i", fn->id, NULL);
+       dc_queuecmd(getpeerlistcallback, data, L"lspeers", L"%i", fn->id, NULL);
     } else {
        data->callback(fn, resp->code, data->data);
        free(data);
@@ -1101,7 +1171,7 @@ void dc_getpeerlistasync(struct dc_fnetnode *fn, void (*callback)(struct dc_fnet
     data->callback = callback;
     data->fnid = fn->id;
     data->data = udata;
-    dc_queuecmd(getpalistcallback, data, L"lspa", L"%%i", fn->id, NULL);
+    dc_queuecmd(getpalistcallback, data, L"lspa", L"%i", fn->id, NULL);
 }
 
 void dc_uimisc_disconnected(void)
@@ -1177,7 +1247,7 @@ void dc_uimisc_handlenotify(struct dc_response *resp)
        break;
     case 613:
        if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
-           transfer->size = ires->argv[1].val.num;
+           transfer->size = ires->argv[1].val.lnum;
        break;
     case 614:
        if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
@@ -1188,7 +1258,7 @@ void dc_uimisc_handlenotify(struct dc_response *resp)
        break;
     case 615:
        if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
-           transfer->curpos = ires->argv[1].val.num;
+           transfer->curpos = ires->argv[1].val.lnum;
        break;
     case 616:
        if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
@@ -1218,14 +1288,22 @@ void dc_uimisc_handlenotify(struct dc_response *resp)
        if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
        {
            if((peer = dc_fnetfindpeer(fn, ires->argv[1].val.str)) == NULL)
-               addpeer(fn, ires->argv[1].val.str, ires->argv[2].val.str);
+           {
+               peer = addpeer(fn, ires->argv[1].val.str, ires->argv[2].val.str);
+               if(fn->newpeercb != NULL)
+                   fn->newpeercb(peer);
+           }
        }
        break;
     case 631:
        if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
        {
            if((peer = dc_fnetfindpeer(fn, ires->argv[1].val.str)) != NULL)
+           {
+               if(fn->delpeercb != NULL)
+                   fn->delpeercb(peer);
                delpeer(peer);
+           }
        }
        break;
     case 632:
@@ -1238,7 +1316,7 @@ void dc_uimisc_handlenotify(struct dc_response *resp)
                    free(peer->nick);
                    peer->nick = swcsdup(ires->argv[2].val.str);
                }
-               for(i = 3; i < resp->rlines[0].argc; i += 3)
+               for(i = 4; i < resp->rlines[0].argc; i += 3)
                {
                    switch(wcstol(resp->rlines[0].argv[i + 1], NULL, 10))
                    {
@@ -1253,6 +1331,8 @@ void dc_uimisc_handlenotify(struct dc_response *resp)
                        break;
                    }
                }
+               if(fn->chpeercb != NULL)
+                   fn->chpeercb(peer);
            }
        }
        break;