/*
* 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>
+#include <stdlib.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 <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>
struct authmech *mech;
};
+struct synclogindata
+{
+ int aborted;
+ int err;
+ wchar_t *reason;
+};
+
struct gencbdata
{
void (*callback)(int resp, void *data);
void *data;
};
+struct fnetcbdata
+{
+ void (*callback)(struct dc_fnetnode *fn, int resp, void *data);
+ int fnid;
+ void *data;
+};
+
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))
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)
{
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;
{
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;
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;
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;
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));
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! */
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);
},
#endif
{
+ .name = L"unix",
+ .process = process_authless,
+ .init = NULL,
+ .release = NULL
+ },
+ {
.name = L"authless",
.process = process_authless,
.init = NULL,
}
};
-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;
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;
{
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)
{
}
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")) {
data = smalloc(sizeof(*data));
if(conv == NULL)
- conv = builtinconv;
+ conv = dc_convtty;
data->conv = conv;
data->mech = NULL;
data->data = udata;
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;
+
+ for(datum = fn->peerdata; datum != NULL; datum = datum->next)
+ {
+ if(!wcscmp(datum->id, id))
+ break;
+ }
+ return(datum);
+}
+
+static struct dc_fnetpeerdatum *adddatum(struct dc_fnetnode *fn, wchar_t *id, int dt)
+{
+ struct dc_fnetpeerdatum *datum;
+
+ datum = smalloc(sizeof(*datum));
+ memset(datum, 0, sizeof(*datum));
+ datum->refcount = 0;
+ datum->dt = dt;
+ datum->id = swcsdup(id);
+ datum->prev = NULL;
+ datum->next = fn->peerdata;
+ if(fn->peerdata != NULL)
+ fn->peerdata->prev = datum;
+ fn->peerdata = datum;
+ return(datum);
+}
+
+static struct dc_fnetpeerdi *difindoradd(struct dc_fnetpeer *peer, struct dc_fnetpeerdatum *datum)
+{
+ int i;
+
+ for(i = 0; i < peer->dinum; i++)
+ {
+ if(peer->di[i].datum == datum)
+ return(&peer->di[i]);
+ }
+ peer->di = srealloc(peer->di, sizeof(struct dc_fnetpeerdi) * ++(peer->dinum));
+ memset(&peer->di[i], 0, sizeof(struct dc_fnetpeerdi));
+ peer->di[i].datum = datum;
+ datum->refcount++;
+ return(&peer->di[i]);
+}
+
+static void putdatum(struct dc_fnetnode *fn, struct dc_fnetpeerdatum *datum)
+{
+ if(--datum->refcount > 0)
+ return;
+ if(datum->next != NULL)
+ datum->next->prev = datum->prev;
+ if(datum->prev != NULL)
+ datum->prev->next = datum->next;
+ if(fn->peerdata == datum)
+ fn->peerdata = datum->next;
+ free(datum->id);
+ free(datum);
+}
+
+static void peersetnum(struct dc_fnetpeer *peer, wchar_t *id, int value)
+{
+ struct dc_fnetpeerdatum *datum;
+ struct dc_fnetpeerdi *di;
+
+ if((datum = finddatum(peer->fn, id)) == NULL)
+ datum = adddatum(peer->fn, id, DC_FNPD_INT);
+ di = difindoradd(peer, datum);
+ di->d.num = value;
+}
+
+static void peersetlnum(struct dc_fnetpeer *peer, wchar_t *id, long long value)
+{
+ struct dc_fnetpeerdatum *datum;
+ struct dc_fnetpeerdi *di;
+
+ if((datum = finddatum(peer->fn, id)) == NULL)
+ datum = adddatum(peer->fn, id, DC_FNPD_INT);
+ di = difindoradd(peer, datum);
+ di->d.lnum = value;
+}
+
+static void peersetstr(struct dc_fnetpeer *peer, wchar_t *id, wchar_t *value)
+{
+ struct dc_fnetpeerdatum *datum;
+ struct dc_fnetpeerdi *di;
+
+ if((datum = finddatum(peer->fn, id)) == NULL)
+ datum = adddatum(peer->fn, id, DC_FNPD_INT);
+ di = difindoradd(peer, datum);
+ if(di->d.str != NULL)
+ free(di->d.str);
+ di->d.str = swcsdup(value);
+}
+
+struct dc_fnetpeer *dc_fnetfindpeer(struct dc_fnetnode *fn, wchar_t *id)
+{
+ struct dc_fnetpeer *peer;
+
+ for(peer = fn->peers; peer != NULL; peer = peer->next)
+ {
+ if(!wcscmp(peer->id, id))
+ break;
+ }
+ return(peer);
+}
+
+static struct dc_fnetpeer *addpeer(struct dc_fnetnode *fn, wchar_t *id, wchar_t *nick)
+{
+ struct dc_fnetpeer *peer;
+
+ peer = smalloc(sizeof(*peer));
+ memset(peer, 0, sizeof(*peer));
+ peer->fn = fn;
+ peer->id = swcsdup(id);
+ peer->nick = swcsdup(nick);
+ peer->next = fn->peers;
+ peer->prev = NULL;
+ if(fn->peers != NULL)
+ fn->peers->prev = peer;
+ fn->peers = peer;
+ return(peer);
+}
+
+static void delpeer(struct dc_fnetpeer *peer)
+{
+ int i;
+
+ if(peer->next != NULL)
+ peer->next->prev = peer->prev;
+ if(peer->prev != NULL)
+ peer->prev->next = peer->next;
+ if(peer->fn->peers == peer)
+ peer->fn->peers = peer->next;
+ free(peer->id);
+ free(peer->nick);
+ for(i = 0; i < peer->dinum; i++)
+ {
+ if((peer->di[i].datum->dt == DC_FNPD_STR) && (peer->di[i].d.str != NULL))
+ free(peer->di[i].d.str);
+ putdatum(peer->fn, peer->di[i].datum);
+ }
+ free(peer->di);
+ free(peer);
+}
+
static struct dc_fnetnode *newfn(void)
{
struct dc_fnetnode *fn;
dc_fnetnodes = fn->next;
if(fn->destroycb != NULL)
fn->destroycb(fn);
+ while(fn->peers != NULL)
+ delpeer(fn->peers);
+ while(fn->peerdata != NULL)
+ {
+ fn->peerdata->refcount = 0;
+ putdatum(fn, fn->peerdata);
+ }
if(fn->name != NULL)
free(fn->name);
if(fn->fnet != NULL)
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;
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);
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, c;
+ struct dc_fnetnode *fn;
+ struct fnetcbdata *data;
+ struct dc_fnetpeer *peer;
+ struct dc_fnetpeer **plist;
+ size_t plistsize, plistdata;
+
+ data = resp->data;
+ if((fn = dc_findfnetnode(data->fnid)) == NULL)
+ {
+ data->callback(NULL, -1, data->data);
+ free(data);
+ return(1);
+ }
+ if(resp->code == 200)
+ {
+ 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((i < resp->numlines) && (o < plistdata))
+ {
+ c = wcscmp(resp->rlines[i].argv[1], plist[o]->id);
+ if(c < 0)
+ {
+ 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;
+ }
+ }
+ free(plist);
+ } else if(resp->code == 201) {
+ while(fn->peers != NULL)
+ delpeer(fn->peers);
+ }
+ data->callback(fn, resp->code, data->data);
+ free(data);
+ return(1);
+}
+
+static int getpalistcallback(struct dc_response *resp)
+{
+ struct dc_fnetnode *fn;
+ struct dc_intresp *ires;
+ struct fnetcbdata *data;
+
+ data = resp->data;
+ if((fn = dc_findfnetnode(data->fnid)) == NULL)
+ {
+ data->callback(NULL, -1, data->data);
+ free(data);
+ return(1);
+ }
+ if(resp->code == 200)
+ {
+ while((ires = dc_interpret(resp)) != NULL)
+ {
+ 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);
+ } else if(resp->code == 201) {
+ dc_queuecmd(getpeerlistcallback, data, L"lspeers", L"%i", fn->id, NULL);
+ } else {
+ data->callback(fn, resp->code, data->data);
+ free(data);
+ }
+ return(1);
+}
+
void dc_getfnlistasync(void (*callback)(int, void *), void *udata)
{
struct gencbdata *data;
dc_queuecmd(gettrlistcallback, data, L"lstrans", NULL);
}
+void dc_getpeerlistasync(struct dc_fnetnode *fn, void (*callback)(struct dc_fnetnode *, int, void *), void *udata)
+{
+ struct fnetcbdata *data;
+
+ data = smalloc(sizeof(*data));
+ data->callback = callback;
+ data->fnid = fn->id;
+ data->data = udata;
+ dc_queuecmd(getpalistcallback, data, L"lspa", L"%i", fn->id, NULL);
+}
+
void dc_uimisc_disconnected(void)
{
while(dc_fnetnodes != NULL)
void dc_uimisc_handlenotify(struct dc_response *resp)
{
+ int i;
struct dc_fnetnode *fn;
struct dc_transfer *transfer;
+ struct dc_fnetpeer *peer;
struct dc_intresp *ires;
if((ires = dc_interpret(resp)) == NULL)
transfer->hash = swcsdup(ires->argv[1].val.str);
}
break;
+ case 630:
+ if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
+ {
+ if((peer = dc_fnetfindpeer(fn, ires->argv[1].val.str)) == NULL)
+ {
+ 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:
+ if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
+ {
+ if((peer = dc_fnetfindpeer(fn, ires->argv[1].val.str)) != NULL)
+ {
+ if(wcscmp(ires->argv[2].val.str, peer->nick))
+ {
+ free(peer->nick);
+ peer->nick = swcsdup(ires->argv[2].val.str);
+ }
+ for(i = 4; i < resp->rlines[0].argc; i += 3)
+ {
+ switch(wcstol(resp->rlines[0].argv[i + 1], NULL, 10))
+ {
+ case DC_FNPD_INT:
+ peersetnum(peer, resp->rlines[0].argv[i], wcstol(resp->rlines[0].argv[i + 2], NULL, 10));
+ break;
+ case DC_FNPD_LL:
+ peersetlnum(peer, resp->rlines[0].argv[i], wcstoll(resp->rlines[0].argv[i + 2], NULL, 10));
+ break;
+ case DC_FNPD_STR:
+ peersetstr(peer, resp->rlines[0].argv[i], resp->rlines[0].argv[i + 2]);
+ break;
+ }
+ }
+ if(fn->chpeercb != NULL)
+ fn->chpeercb(peer);
+ }
+ }
+ break;
default:
break;
}