92d1074814ebe7e8636f92b24b4eba0fddb6ee1b
[doldaconnect.git] / lib / uimisc.c
1 /*
2  *  Dolda Connect - Modular multiuser Direct Connect-style client
3  *  Copyright (C) 2004 Fredrik Tolf <fredrik@dolda2000.com>
4  *  
5  *  This program is free software; you can redistribute it and/or modify
6  *  it under the terms of the GNU General Public License as published by
7  *  the Free Software Foundation; either version 2 of the License, or
8  *  (at your option) any later version.
9  *  
10  *  This program is distributed in the hope that it will be useful,
11  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  *  GNU General Public License for more details.
14  *  
15  *  You should have received a copy of the GNU General Public License
16  *  along with this program; if not, write to the Free Software
17  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18 */
19
20 #include <unistd.h>
21 #include <stdlib.h>
22 #include <wchar.h>
23 #include <wctype.h>
24 #include <pwd.h>
25 #include <string.h>
26 #include <stdio.h>
27 #include <sys/poll.h>
28
29 #ifdef HAVE_CONFIG_H
30 #include <config.h>
31 #endif
32 #include <doldaconnect/uilib.h>
33 #include <doldaconnect/uimisc.h>
34 #include <utils.h>
35
36 #ifdef HAVE_KRB5
37 #include <krb5.h>
38 #include <com_err.h>
39 #endif
40
41 struct logindata;
42
43 struct authmech
44 {
45     wchar_t *name;
46     void (*process)(struct dc_response *resp, struct logindata *data);
47     int (*init)(struct logindata *data);
48     void (*release)(struct logindata *data);
49 };
50
51 struct logindata
52 {
53     int (*conv)(int type, wchar_t *text, char **resp, void *data);
54     void (*callback)(int err, wchar_t *reason, void *data);
55     char *username;
56     int freeusername;
57     int useauthless;
58     void *data;
59     void *mechdata;
60     struct authmech *mech;
61 };
62
63 struct synclogindata
64 {
65     int aborted;
66     int err;
67     wchar_t *reason;
68 };
69
70 struct gencbdata
71 {
72     void (*callback)(int resp, void *data);
73     void *data;
74 };
75
76 struct fnetcbdata
77 {
78     void (*callback)(struct dc_fnetnode *fn, int resp, void *data);
79     int fnid;
80     void *data;
81 };
82
83 struct dc_fnetnode *dc_fnetnodes = NULL;
84 struct dc_transfer *dc_transfers = NULL;
85
86 static void message(int bits, char *format, ...)
87 {
88     static int hb = -1;
89     char *v;
90     va_list args;
91     
92     if(hb == -1)
93     {
94         hb = 0;
95         if((v = getenv("LIBDCUI_MSG")) != NULL)
96             hb = strtol(v, NULL, 0) & 65535;
97     }
98     if(hb & bits)
99     {
100         va_start(args, format);
101         vfprintf(stderr, format, args);
102         va_end(args);
103     }
104 }
105
106 static void freelogindata(struct logindata *data)
107 {
108     if((data->mech != NULL) && (data->mech->release != NULL))
109         data->mech->release(data);
110     if(data->freeusername)
111         free(data->username);
112     free(data);
113 }
114
115 static int logincallback(struct dc_response *resp);
116
117 static void process_authless(struct dc_response *resp, struct logindata *data)
118 {
119     switch(resp->code)
120     {
121     case 200:
122         data->callback(DC_LOGIN_ERR_SUCCESS, NULL, data->data);
123         freelogindata(data);
124         break;
125 /*
126     case 303:
127     case 304:
128         if((ires = dc_interpret(resp)) != NULL)
129         {
130             buf = NULL;
131             if(data->conv((resp->code == 303)?DC_LOGIN_CONV_INFO:DC_LOGIN_CONV_ERROR, ires->argv[0].val.str, &buf, data->data))
132             {
133                 data->callback(DC_LOGIN_ERR_CONV, NULL, data->data);
134                 freelogindata(data);
135             } else {
136                 dc_queuecmd(logincallback, data, L"pass", L"", NULL);
137             }
138             if(buf != NULL)
139             {
140                 memset(buf, 0, strlen(buf));
141                 free(buf);
142             }
143             dc_freeires(ires);
144         }
145         break;
146 */
147     case 505:
148         data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data);
149         freelogindata(data);
150         break;
151     case 506:
152         data->callback(DC_LOGIN_ERR_AUTHFAIL, NULL, data->data);
153         freelogindata(data);
154         break;
155     default:
156         data->callback(DC_LOGIN_ERR_USER, NULL, data->data);
157         freelogindata(data);
158         break;
159     }
160 }
161
162 static void process_pam(struct dc_response *resp, struct logindata *data)
163 {
164     struct dc_intresp *ires;
165     int convtype;
166     char *buf;
167     
168     switch(resp->code)
169     {
170     case 200:
171         data->callback(DC_LOGIN_ERR_SUCCESS, NULL, data->data);
172         freelogindata(data);
173         break;
174     case 301:
175     case 302:
176     case 303:
177     case 304:
178         if(resp->code == 301)
179             convtype = DC_LOGIN_CONV_NOECHO;
180         else if(resp->code == 302)
181             convtype = DC_LOGIN_CONV_ECHO;
182         else if(resp->code == 303)
183             convtype = DC_LOGIN_CONV_INFO;
184         else if(resp->code == 304)
185             convtype = DC_LOGIN_CONV_ERROR;
186         if((ires = dc_interpret(resp)) != NULL)
187         {
188             buf = NULL;
189             if(data->conv(convtype, ires->argv[0].val.str, &buf, data->data))
190             {
191                 data->callback(DC_LOGIN_ERR_CONV, NULL, data->data);
192                 freelogindata(data);
193             } else {
194                 dc_queuecmd(logincallback, data, L"pass", L"%s", buf, NULL);
195             }
196             if(buf != NULL)
197             {
198                 memset(buf, 0, strlen(buf));
199                 free(buf);
200             }
201             dc_freeires(ires);
202         }
203         break;
204     case 505:
205         data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data);
206         freelogindata(data);
207         break;
208     case 506:
209         data->callback(DC_LOGIN_ERR_AUTHFAIL, NULL, data->data);
210         freelogindata(data);
211         break;
212     default:
213         data->callback(DC_LOGIN_ERR_USER, NULL, data->data);
214         freelogindata(data);
215         break;
216     }
217 }
218
219 #ifdef HAVE_KRB5
220 struct krb5data
221 {
222     int state;
223     krb5_context context;
224     krb5_principal sprinc, myprinc;
225     krb5_ccache ccache;
226     krb5_auth_context authcon;
227     krb5_data reqbuf;
228     krb5_creds *servcreds;
229     int valid, fwd, fwded;
230 };
231
232 static void process_krb5(struct dc_response *resp, struct logindata *data)
233 {
234     int ret;
235     struct dc_intresp *ires;
236     struct krb5data *krb;
237     krb5_data k5d;
238     krb5_ap_rep_enc_part *repl;
239     char *buf;
240     
241     krb = data->mechdata;
242     switch(resp->code)
243     {
244     case 200:
245         data->callback(DC_LOGIN_ERR_SUCCESS, NULL, data->data);
246         freelogindata(data);
247         break;
248     case 300:
249         switch(krb->state)
250         {
251         case 0:
252             buf = hexencode(krb->reqbuf.data, krb->reqbuf.length);
253             dc_queuecmd(logincallback, data, L"pass", L"%s", buf, NULL);
254             free(buf);
255             krb->state = 1;
256             break;
257         case 1:
258             if((ires = dc_interpret(resp)) != NULL)
259             {
260                 k5d.data = hexdecode(icswcstombs(ires->argv[0].val.str, NULL, NULL), &k5d.length);
261                 if(!krb->valid)
262                 {
263                     if((ret = krb5_rd_rep(krb->context, krb->authcon, &k5d, &repl)) != 0)
264                     {
265                         data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data);
266                         freelogindata(data);
267                         break;
268                     }
269                     /* XXX: Do I need to do something with this? */
270                     krb->valid = 1;
271                     krb5_free_ap_rep_enc_part(krb->context, repl);
272                 }
273                 if(krb->fwd && !krb->fwded)
274                 {
275                     if(krb->reqbuf.data != NULL)
276                         free(krb->reqbuf.data);
277                     krb->reqbuf.data = NULL;
278                     if((ret = krb5_fwd_tgt_creds(krb->context, krb->authcon, NULL, krb->servcreds->client, krb->servcreds->server, 0, 1, &krb->reqbuf)) != 0)
279                     {
280                         message(1, "krb5_fwd_tgt_creds reported an error: %s\n", error_message(ret));
281                         dc_queuecmd(logincallback, data, L"pass", L"31", NULL);
282                         krb->fwd = 0;
283                         krb->state = 2;
284                     } else {
285                         dc_queuecmd(logincallback, data, L"pass", L"32", NULL);
286                         krb->state = 0;
287                         krb->fwded = 1;
288                     }
289                 } else {
290                     dc_queuecmd(logincallback, data, L"pass", L"31", NULL);
291                     krb->state = 2;
292                 }
293                 free(k5d.data);
294                 dc_freeires(ires);
295             }
296             break;
297         default:
298             data->callback(DC_LOGIN_ERR_USER, NULL, data->data);
299             freelogindata(data);
300             break;
301         }
302         break;
303     case 505:
304         data->callback(DC_LOGIN_ERR_SERVER, NULL, data->data);
305         freelogindata(data);
306         break;
307     case 506:
308         data->callback(DC_LOGIN_ERR_AUTHFAIL, NULL, data->data);
309         freelogindata(data);
310         break;
311     default:
312         data->callback(DC_LOGIN_ERR_USER, NULL, data->data);
313         freelogindata(data);
314         break;
315     }
316 }
317
318 static int init_krb5(struct logindata *data)
319 {
320     int ret;
321     struct krb5data *krb;
322     krb5_data cksum;
323     krb5_creds creds;
324     
325     if(dc_gethostname() == NULL)
326     {
327         message(1, "cannot use krb5 without a host name\n");
328         return(1);
329     }
330     krb = smalloc(sizeof(*krb));
331     memset(krb, 0, sizeof(*krb));
332     krb->fwd = 1;
333     krb->fwded = 0;
334     data->mechdata = krb;
335     if((ret = krb5_init_context(&krb->context)) != 0)
336     {
337         message(1, "krb5_init_context reported an error: %s\n", error_message(ret));
338         return(1);
339     }
340     if((ret = krb5_auth_con_init(krb->context, &krb->authcon)) != 0)
341     {
342         message(1, "krb5_auth_con_init reported an error: %s\n", error_message(ret));
343         return(1);
344     }
345     krb5_auth_con_setflags(krb->context, krb->authcon, KRB5_AUTH_CONTEXT_DO_SEQUENCE);
346     if((ret = krb5_sname_to_principal(krb->context, dc_gethostname(), "doldacond", KRB5_NT_SRV_HST, &krb->sprinc)) != 0)
347     {
348         message(1, "krb5_sname_to_principal reported an error: %s\n", error_message(ret));
349         return(1);
350     }
351     if((ret = krb5_cc_default(krb->context, &krb->ccache)) != 0)
352     {
353         message(1, "krb5_cc_default reported an error: %s\n", error_message(ret));
354         return(1);
355     }
356     if((ret = krb5_cc_get_principal(krb->context, krb->ccache, &krb->myprinc)) != 0)
357     {
358         message(1, "krb5_cc_default reported an error: %s\n", error_message(ret));
359         return(1);
360     }
361     memset(&creds, 0, sizeof(creds));
362     creds.client = krb->myprinc;
363     creds.server = krb->sprinc;
364     if((ret = krb5_get_credentials(krb->context, 0, krb->ccache, &creds, &krb->servcreds)) != 0)
365     {
366         message(1, "krb5_get_credentials reported an error: %s\n", error_message(ret));
367         return(1);
368     }
369     /* WTF is this checksum stuff?! The Krb docs don't say a word about it! */
370     cksum.data = sstrdup(dc_gethostname());
371     cksum.length = strlen(cksum.data);
372     if((ret = krb5_mk_req_extended(krb->context, &krb->authcon, AP_OPTS_MUTUAL_REQUIRED, &cksum, krb->servcreds, &krb->reqbuf)) != 0)
373     {
374         message(1, "krb5_mk_req_extended reported an error: %s\n", error_message(ret));
375         return(1);
376     }
377     free(cksum.data);
378     krb->state = 0;
379     return(0);
380 }
381
382 static void release_krb5(struct logindata *data)
383 {
384     struct krb5data *krb;
385     
386     if((krb = data->mechdata) == NULL)
387         return;
388     if(krb->servcreds != NULL)
389         krb5_free_creds(krb->context, krb->servcreds);
390     if(krb->reqbuf.data != NULL)
391         free(krb->reqbuf.data);
392     if(krb->sprinc != NULL)
393         krb5_free_principal(krb->context, krb->sprinc);
394     if(krb->myprinc != NULL)
395         krb5_free_principal(krb->context, krb->myprinc);
396     if(krb->ccache != NULL)
397         krb5_cc_close(krb->context, krb->ccache);
398     if(krb->authcon != NULL)
399         krb5_auth_con_free(krb->context, krb->authcon);
400     if(krb->context != NULL)
401         krb5_free_context(krb->context);
402     free(krb);
403 }
404 #endif
405
406 /* Arranged in order of priority */
407 static struct authmech authmechs[] =
408 {
409 #ifdef HAVE_KRB5
410     {
411         .name = L"krb5",
412         .process = process_krb5,
413         .init = init_krb5,
414         .release = release_krb5
415     },
416 #endif
417     {
418         .name = L"unix",
419         .process = process_authless,
420         .init = NULL,
421         .release = NULL
422     },
423     {
424         .name = L"authless",
425         .process = process_authless,
426         .init = NULL,
427         .release = NULL
428     },
429     {
430         .name = L"pam",
431         .process = process_pam,
432         .init = NULL,
433         .release = NULL
434     },
435     {
436         .name = NULL
437     }
438 };
439
440 int dc_convtty(int type, wchar_t *text, char **resp, void *data)
441 {
442     char *buf, *pass;
443     
444     if(isatty(0))
445     {
446         if((buf = icwcstombs(text, NULL)) == NULL)
447             return(1);
448         pass = getpass(buf);
449         free(buf);
450         *resp = sstrdup(pass);
451         memset(pass, 0, strlen(pass));
452         return(0);
453     }
454     return(1);
455 }
456
457 int dc_convnone(int type, wchar_t *text, char **resp, void *data)
458 {
459     return(1);
460 }
461
462 static int logincallback(struct dc_response *resp)
463 {
464     int i;
465     struct dc_intresp *ires;
466     struct logindata *data;
467     int mech;
468     char *username;
469     struct passwd *pwent;
470     void *odata, *ndata;
471     
472     data = resp->data;
473     if(!wcscmp(resp->cmdname, L"lsauth"))
474     {
475         if(resp->code == 201)
476         {
477             data->callback(DC_LOGIN_ERR_NOLOGIN, NULL, data->data);
478             freelogindata(data);
479         } else {
480             mech = -1;
481             while((ires = dc_interpret(resp)) != NULL)
482             {
483                 if(!data->useauthless && !wcscmp(ires->argv[0].val.str, L"authless"))
484                 {
485                     dc_freeires(ires);
486                     continue;
487                 }
488                 for(i = 0; authmechs[i].name != NULL; i++)
489                 {
490                     if(!wcscmp(authmechs[i].name, ires->argv[0].val.str) && ((i < mech) || (mech == -1)))
491                     {
492                         odata = data->mechdata;
493                         data->mechdata = NULL;
494                         message(4, "trying auth mech %ls\n", authmechs[i].name);
495                         if((authmechs[i].init != NULL) && authmechs[i].init(data))
496                         {
497                             if(authmechs[i].release != NULL)
498                                 authmechs[i].release(data);
499                             data->mechdata = odata;
500                             message(2, "authentication mechanism %ls failed, trying further...\n", authmechs[i].name);
501                         } else {
502                             if((data->mech != NULL) && data->mech->release != NULL)
503                             {
504                                 ndata = data->mechdata;
505                                 data->mechdata = odata;
506                                 data->mech->release(data);
507                                 data->mechdata = ndata;
508                             }
509                             mech = i;
510                             data->mech = authmechs + i;
511                         }
512                         break;
513                     }
514                 }
515                 dc_freeires(ires);
516             }
517             if(mech == -1)
518             {
519                 data->callback(DC_LOGIN_ERR_NOLOGIN, NULL, data->data);
520                 freelogindata(data);
521             } else {
522                 if((username = data->username) == NULL)
523                 {
524                     if((pwent = getpwuid(getuid())) == NULL)
525                     {
526                         data->callback(DC_LOGIN_ERR_USER, NULL, data->data);
527                         freelogindata(data);
528                         return(1);
529                     }
530                     username = pwent->pw_name;
531                 }
532                 dc_queuecmd(logincallback, data, L"login", data->mech->name, L"%s", username, NULL);
533             }
534         }
535     } else if(!wcscmp(resp->cmdname, L"login") || !wcscmp(resp->cmdname, L"pass")) {
536         data->mech->process(resp, data);
537     }
538     return(1);
539 }
540
541 void dc_loginasync(char *username, int useauthless, int (*conv)(int, wchar_t *, char **, void *), void (*callback)(int, wchar_t *, void *), void *udata)
542 {
543     struct logindata *data;
544     
545     data = smalloc(sizeof(*data));
546     if(conv == NULL)
547         conv = dc_convtty;
548     data->conv = conv;
549     data->mech = NULL;
550     data->data = udata;
551     data->mechdata = NULL;
552     data->callback = callback;
553     data->useauthless = useauthless;
554     data->freeusername = 0;
555     if(username == NULL)
556     {
557         data->username = NULL;
558     } else {
559         data->username = sstrdup(username);
560         data->freeusername = 1;
561     }
562     dc_queuecmd(logincallback, data, L"lsauth", NULL);
563 }
564
565 static void synclogincb(int err, wchar_t *reason, struct synclogindata *data)
566 {
567     if(data->aborted)
568     {
569         free(data);
570         return;
571     }
572     data->err = err;
573     if(reason == NULL)
574         data->reason = NULL;
575     else
576         data->reason = swcsdup(reason);
577 }
578
579 int dc_login(char *username, int useauthless, int (*conv)(int, wchar_t *, char **, void *), wchar_t **reason)
580 {
581     int ret, abort;
582     struct synclogindata *dbuf;
583     struct pollfd pfd;
584     
585     dbuf = smalloc(sizeof(*dbuf));
586     memset(dbuf, 0, sizeof(*dbuf));
587     dbuf->err = -1;
588     dc_loginasync(username, useauthless, conv, (void (*)(int, wchar_t *, void *))synclogincb, dbuf);
589     while(dbuf->err == -1)
590     {
591         pfd.fd = dc_getfd();
592         pfd.events = POLLIN;
593         if(dc_wantwrite())
594             pfd.events |= POLLOUT;
595         abort = 0;
596         if(poll(&pfd, 1, -1) < 0)
597             abort = 1;
598         if(!abort && (pfd.revents & POLLIN) && dc_handleread())
599             abort = 1;
600         if(!abort && (pfd.revents & POLLOUT) && dc_handlewrite())
601             abort = 1;
602         if(abort)
603         {
604             dbuf->aborted = 1;
605             return(-1);
606         }
607     }
608     if(reason != NULL)
609         *reason = dbuf->reason;
610     else if(dbuf->reason != NULL)
611         free(dbuf->reason);
612     ret = dbuf->err;
613     free(dbuf);
614     return(ret);
615 }
616
617 static struct dc_fnetpeerdatum *finddatum(struct dc_fnetnode *fn, wchar_t *id)
618 {
619     struct dc_fnetpeerdatum *datum;
620     
621     for(datum = fn->peerdata; datum != NULL; datum = datum->next)
622     {
623         if(!wcscmp(datum->id, id))
624             break;
625     }
626     return(datum);
627 }
628
629 static struct dc_fnetpeerdatum *adddatum(struct dc_fnetnode *fn, wchar_t *id, int dt)
630 {
631     struct dc_fnetpeerdatum *datum;
632     
633     datum = smalloc(sizeof(*datum));
634     memset(datum, 0, sizeof(*datum));
635     datum->refcount = 0;
636     datum->dt = dt;
637     datum->id = swcsdup(id);
638     datum->prev = NULL;
639     datum->next = fn->peerdata;
640     if(fn->peerdata != NULL)
641         fn->peerdata->prev = datum;
642     fn->peerdata = datum;
643     return(datum);
644 }
645
646 static struct dc_fnetpeerdi *difindoradd(struct dc_fnetpeer *peer, struct dc_fnetpeerdatum *datum)
647 {
648     int i;
649     
650     for(i = 0; i < peer->dinum; i++)
651     {
652         if(peer->di[i].datum == datum)
653             return(&peer->di[i]);
654     }
655     peer->di = srealloc(peer->di, sizeof(struct dc_fnetpeerdi) * ++(peer->dinum));
656     memset(&peer->di[i], 0, sizeof(struct dc_fnetpeerdi));
657     peer->di[i].datum = datum;
658     datum->refcount++;
659     return(&peer->di[i]);
660 }
661
662 static void putdatum(struct dc_fnetnode *fn, struct dc_fnetpeerdatum *datum)
663 {
664     if(--datum->refcount > 0)
665         return;
666     if(datum->next != NULL)
667         datum->next->prev = datum->prev;
668     if(datum->prev != NULL)
669         datum->prev->next = datum->next;
670     if(fn->peerdata == datum)
671         fn->peerdata = datum->next;
672     free(datum->id);
673     free(datum);
674 }
675
676 static void peersetnum(struct dc_fnetpeer *peer, wchar_t *id, int value)
677 {
678     struct dc_fnetpeerdatum *datum;
679     struct dc_fnetpeerdi *di;
680     
681     if((datum = finddatum(peer->fn, id)) == NULL)
682         datum = adddatum(peer->fn, id, DC_FNPD_INT);
683     di = difindoradd(peer, datum);
684     di->d.num = value;
685 }
686
687 static void peersetlnum(struct dc_fnetpeer *peer, wchar_t *id, long long value)
688 {
689     struct dc_fnetpeerdatum *datum;
690     struct dc_fnetpeerdi *di;
691     
692     if((datum = finddatum(peer->fn, id)) == NULL)
693         datum = adddatum(peer->fn, id, DC_FNPD_INT);
694     di = difindoradd(peer, datum);
695     di->d.lnum = value;
696 }
697
698 static void peersetstr(struct dc_fnetpeer *peer, wchar_t *id, wchar_t *value)
699 {
700     struct dc_fnetpeerdatum *datum;
701     struct dc_fnetpeerdi *di;
702     
703     if((datum = finddatum(peer->fn, id)) == NULL)
704         datum = adddatum(peer->fn, id, DC_FNPD_INT);
705     di = difindoradd(peer, datum);
706     if(di->d.str != NULL)
707         free(di->d.str);
708     di->d.str = swcsdup(value);
709 }
710
711 struct dc_fnetpeer *dc_fnetfindpeer(struct dc_fnetnode *fn, wchar_t *id)
712 {
713     struct dc_fnetpeer *peer;
714     
715     for(peer = fn->peers; peer != NULL; peer = peer->next)
716     {
717         if(!wcscmp(peer->id, id))
718             break;
719     }
720     return(peer);
721 }
722
723 static struct dc_fnetpeer *addpeer(struct dc_fnetnode *fn, wchar_t *id, wchar_t *nick)
724 {
725     struct dc_fnetpeer *peer;
726     
727     peer = smalloc(sizeof(*peer));
728     memset(peer, 0, sizeof(*peer));
729     peer->fn = fn;
730     peer->id = swcsdup(id);
731     peer->nick = swcsdup(nick);
732     peer->next = fn->peers;
733     peer->prev = NULL;
734     if(fn->peers != NULL)
735         fn->peers->prev = peer;
736     fn->peers = peer;
737     return(peer);
738 }
739
740 static void delpeer(struct dc_fnetpeer *peer)
741 {
742     int i;
743     
744     if(peer->next != NULL)
745         peer->next->prev = peer->prev;
746     if(peer->prev != NULL)
747         peer->prev->next = peer->next;
748     if(peer->fn->peers == peer)
749         peer->fn->peers = peer->next;
750     free(peer->id);
751     free(peer->nick);
752     for(i = 0; i < peer->dinum; i++)
753     {
754         if((peer->di[i].datum->dt == DC_FNPD_STR) && (peer->di[i].d.str != NULL))
755             free(peer->di[i].d.str);
756         putdatum(peer->fn, peer->di[i].datum);
757     }
758     free(peer->di);
759     free(peer);
760 }
761
762 static struct dc_fnetnode *newfn(void)
763 {
764     struct dc_fnetnode *fn;
765     
766     fn = smalloc(sizeof(*fn));
767     memset(fn, 0, sizeof(*fn));
768     fn->id = -1;
769     fn->name = NULL;
770     fn->fnet = NULL;
771     fn->state = fn->numusers = fn->found = 0;
772     fn->destroycb = NULL;
773     fn->udata = NULL;
774     fn->next = dc_fnetnodes;
775     fn->prev = NULL;
776     if(dc_fnetnodes != NULL)
777         dc_fnetnodes->prev = fn;
778     dc_fnetnodes = fn;
779     return(fn);
780 }
781
782 static void freefn(struct dc_fnetnode *fn)
783 {
784     if(fn->next != NULL)
785         fn->next->prev = fn->prev;
786     if(fn->prev != NULL)
787         fn->prev->next = fn->next;
788     if(fn == dc_fnetnodes)
789         dc_fnetnodes = fn->next;
790     if(fn->destroycb != NULL)
791         fn->destroycb(fn);
792     while(fn->peers != NULL)
793         delpeer(fn->peers);
794     while(fn->peerdata != NULL)
795     {
796         fn->peerdata->refcount = 0;
797         putdatum(fn, fn->peerdata);
798     }
799     if(fn->name != NULL)
800         free(fn->name);
801     if(fn->fnet != NULL)
802         free(fn->fnet);
803     free(fn);
804 }
805
806 struct dc_fnetnode *dc_findfnetnode(int id)
807 {
808     struct dc_fnetnode *fn;
809     
810     for(fn = dc_fnetnodes; fn != NULL; fn = fn->next)
811     {
812         if(fn->id == id)
813             break;
814     }
815     return(fn);
816 }
817
818 static struct dc_transfer *newtransfer(void)
819 {
820     struct dc_transfer *transfer;
821     
822     transfer = smalloc(sizeof(*transfer));
823     memset(transfer, 0, sizeof(*transfer));
824     transfer->id = -1;
825     transfer->peerid = transfer->peernick = transfer->path = NULL;
826     transfer->state = DC_TRNS_WAITING;
827     transfer->dir = DC_TRNSD_UNKNOWN;
828     transfer->size = -1;
829     transfer->curpos = -1;
830     transfer->destroycb = NULL;
831     transfer->udata = NULL;
832     transfer->next = dc_transfers;
833     transfer->prev = NULL;
834     if(dc_transfers != NULL)
835         dc_transfers->prev = transfer;
836     dc_transfers = transfer;
837     return(transfer);
838 }
839
840 static void freetransfer(struct dc_transfer *transfer)
841 {
842     if(transfer->next != NULL)
843         transfer->next->prev = transfer->prev;
844     if(transfer->prev != NULL)
845         transfer->prev->next = transfer->next;
846     if(transfer == dc_transfers)
847         dc_transfers = transfer->next;
848     if(transfer->destroycb != NULL)
849         transfer->destroycb(transfer);
850     if(transfer->peerid != NULL)
851         free(transfer->peerid);
852     if(transfer->peernick != NULL)
853         free(transfer->peernick);
854     if(transfer->path != NULL)
855         free(transfer->path);
856     free(transfer);
857 }
858
859 struct dc_transfer *dc_findtransfer(int id)
860 {
861     struct dc_transfer *transfer;
862     
863     for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next)
864     {
865         if(transfer->id == id)
866             break;
867     }
868     return(transfer);
869 }
870
871 static int getfnlistcallback(struct dc_response *resp)
872 {
873     struct dc_intresp *ires;
874     struct gencbdata *data;
875     struct dc_fnetnode *fn, *next;
876     
877     data = resp->data;
878     if(resp->code == 200)
879     {
880         for(fn = dc_fnetnodes; fn != NULL; fn = fn->next)
881             fn->found = 0;
882         while((ires = dc_interpret(resp)) != NULL)
883         {
884             if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
885             {
886                 fn->found = 1;
887                 if(fn->fnet != NULL)
888                     free(fn->fnet);
889                 fn->fnet = swcsdup(ires->argv[1].val.str);
890                 if(fn->name != NULL)
891                     free(fn->name);
892                 fn->name = swcsdup(ires->argv[2].val.str);
893                 fn->numusers = ires->argv[3].val.num;
894                 fn->state = ires->argv[4].val.num;
895                 if(fn->pubid != NULL)
896                     free(fn->pubid);
897                 fn->pubid = swcsdup(ires->argv[5].val.str);
898             } else {
899                 fn = newfn();
900                 fn->id = ires->argv[0].val.num;
901                 fn->fnet = swcsdup(ires->argv[1].val.str);
902                 fn->name = swcsdup(ires->argv[2].val.str);
903                 fn->numusers = ires->argv[3].val.num;
904                 fn->state = ires->argv[4].val.num;
905                 fn->pubid = swcsdup(ires->argv[5].val.str);
906                 fn->found = 1;
907             }
908             dc_freeires(ires);
909         }
910         for(fn = dc_fnetnodes; fn != NULL; fn = next)
911         {
912             next = fn->next;
913             if(!fn->found)
914                 freefn(fn);
915         }
916         data->callback(200, data->data);
917         free(resp->data);
918     } else if(resp->code == 201) {
919         while(dc_fnetnodes != NULL)
920             freefn(dc_fnetnodes);
921         data->callback(201, data->data);
922         free(resp->data);
923     } else if(resp->code == 502) {
924         while(dc_fnetnodes != NULL)
925             freefn(dc_fnetnodes);
926         data->callback(502, data->data);
927         free(resp->data);
928     }
929     return(1);
930 }
931
932 static int gettrlistcallback(struct dc_response *resp)
933 {
934     struct dc_intresp *ires;
935     struct gencbdata *data;
936     struct dc_transfer *transfer, *next;
937     
938     data = resp->data;
939     if(resp->code == 200)
940     {
941         for(transfer = dc_transfers; transfer != NULL; transfer = transfer->next)
942             transfer->found = 0;
943         while((ires = dc_interpret(resp)) != NULL)
944         {
945             if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
946             {
947                 transfer->found = 1;
948                 if((transfer->path == NULL) || wcscmp(transfer->path, ires->argv[5].val.str))
949                 {
950                     if(transfer->path != NULL)
951                         free(transfer->path);
952                     transfer->path = swcsdup(ires->argv[5].val.str);
953                 }
954                 if((transfer->peerid == NULL) || wcscmp(transfer->peerid, ires->argv[3].val.str))
955                 {
956                     if(transfer->peerid != NULL)
957                         free(transfer->peerid);
958                     transfer->peerid = swcsdup(ires->argv[3].val.str);
959                 }
960                 if((transfer->peernick == NULL) || wcscmp(transfer->peernick, ires->argv[4].val.str))
961                 {
962                     if(transfer->peernick != NULL)
963                         free(transfer->peernick);
964                     transfer->peernick = swcsdup(ires->argv[4].val.str);
965                 }
966                 transfer->dir = ires->argv[1].val.num;
967                 transfer->state = ires->argv[2].val.num;
968                 transfer->size = ires->argv[6].val.lnum;
969                 transfer->curpos = ires->argv[7].val.lnum;
970                 if(transfer->hash != NULL)
971                 {
972                     free(transfer->hash);
973                     transfer->hash = NULL;
974                 }
975                 if(wcslen(ires->argv[8].val.str) > 0)
976                     transfer->hash = swcsdup(ires->argv[8].val.str);
977             } else {
978                 transfer = newtransfer();
979                 transfer->id = ires->argv[0].val.num;
980                 transfer->dir = ires->argv[1].val.num;
981                 transfer->state = ires->argv[2].val.num;
982                 transfer->peerid = swcsdup(ires->argv[3].val.str);
983                 transfer->peernick = swcsdup(ires->argv[4].val.str);
984                 transfer->path = swcsdup(ires->argv[5].val.str);
985                 transfer->size = ires->argv[6].val.lnum;
986                 transfer->curpos = ires->argv[7].val.lnum;
987                 if(wcslen(ires->argv[8].val.str) > 0)
988                     transfer->hash = swcsdup(ires->argv[8].val.str);
989                 transfer->found = 1;
990             }
991             dc_freeires(ires);
992         }
993         for(transfer = dc_transfers; transfer != NULL; transfer = next)
994         {
995             next = transfer->next;
996             if(!transfer->found)
997                 freetransfer(transfer);
998         }
999         data->callback(200, data->data);
1000         free(data);
1001     } else if(resp->code == 201) {
1002         while(dc_transfers != NULL)
1003             freetransfer(dc_transfers);
1004         data->callback(201, data->data);
1005         free(data);
1006     } else if(resp->code == 502) {
1007         while(dc_transfers != NULL)
1008             freetransfer(dc_transfers);
1009         data->callback(502, data->data);
1010         free(data);
1011     }
1012     return(1);
1013 }
1014
1015 static int sortlist1(const struct dc_respline *l1, const struct dc_respline *l2)
1016 {
1017     return(wcscmp(l1->argv[1], l2->argv[1]));
1018 }
1019
1020 static int sortlist2(const struct dc_fnetpeer **p1, const struct dc_fnetpeer **p2)
1021 {
1022     return(wcscmp((*p1)->id, (*p2)->id));
1023 }
1024
1025 static void fillpeer(struct dc_fnetpeer *peer, struct dc_respline *r)
1026 {
1027     int i;
1028     struct dc_fnetpeerdatum *datum;
1029     
1030     for(i = 3; i < r->argc; i += 2)
1031     {
1032         if((datum = finddatum(peer->fn, r->argv[i])) != NULL)
1033         {
1034             switch(datum->dt)
1035             {
1036             case DC_FNPD_INT:
1037                 peersetnum(peer, datum->id, wcstol(r->argv[i + 1], NULL, 10));
1038                 break;
1039             case DC_FNPD_LL:
1040                 peersetlnum(peer, datum->id, wcstoll(r->argv[i + 1], NULL, 10));
1041                 break;
1042             case DC_FNPD_STR:
1043                 peersetstr(peer, datum->id, r->argv[i + 1]);
1044                 break;
1045             }
1046         }
1047     }
1048 }
1049
1050 static int getpeerlistcallback(struct dc_response *resp)
1051 {
1052     int i, o, c;
1053     struct dc_fnetnode *fn;
1054     struct fnetcbdata *data;
1055     struct dc_fnetpeer *peer;
1056     struct dc_fnetpeer **plist;
1057     size_t plistsize, plistdata;
1058     
1059     data = resp->data;
1060     if((fn = dc_findfnetnode(data->fnid)) == NULL)
1061     {
1062         data->callback(NULL, -1, data->data);
1063         free(data);
1064         return(1);
1065     }
1066     if(resp->code == 200)
1067     {
1068         qsort(resp->rlines, resp->numlines, sizeof(*resp->rlines), (int (*)(const void *, const void *))sortlist1);
1069         plist = NULL;
1070         plistsize = plistdata = 0;
1071         for(i = 0, peer = fn->peers; peer != NULL; peer = peer->next)
1072             addtobuf(plist, peer);
1073         qsort(plist, plistdata, sizeof(*plist), (int (*)(const void *, const void *))sortlist2);
1074         i = o = 0;
1075         while(1)
1076         {
1077             if((i < resp->numlines) && (o < plistdata))
1078             {
1079                 c = wcscmp(resp->rlines[i].argv[1], plist[o]->id);
1080                 if(c < 0)
1081                 {
1082                     peer = addpeer(fn, resp->rlines[i].argv[1], resp->rlines[i].argv[2]);
1083                     fillpeer(peer, resp->rlines + i);
1084                     i++;
1085                 } else if(c > 0) {
1086                     delpeer(plist[o]);
1087                     o++;
1088                 } else {
1089                     fillpeer(plist[o], resp->rlines + i);
1090                     i++;
1091                     o++;
1092                 }
1093             } else if(i < resp->numlines) {
1094                 peer = addpeer(fn, resp->rlines[i].argv[1], resp->rlines[i].argv[2]);
1095                 fillpeer(peer, resp->rlines + i);
1096                 i++;
1097             } else if(o < plistdata) {
1098                 delpeer(plist[o]);
1099                 o++;
1100             } else {
1101                 break;
1102             }
1103         }
1104         free(plist);
1105     } else if(resp->code == 201) {
1106         while(fn->peers != NULL)
1107             delpeer(fn->peers);
1108     }
1109     data->callback(fn, resp->code, data->data);
1110     free(data);
1111     return(1);
1112 }
1113
1114 static int getpalistcallback(struct dc_response *resp)
1115 {
1116     struct dc_fnetnode *fn;
1117     struct dc_intresp *ires;
1118     struct fnetcbdata *data;
1119     
1120     data = resp->data;
1121     if((fn = dc_findfnetnode(data->fnid)) == NULL)
1122     {
1123         data->callback(NULL, -1, data->data);
1124         free(data);
1125         return(1);
1126     }
1127     if(resp->code == 200)
1128     {
1129         while((ires = dc_interpret(resp)) != NULL)
1130         {
1131             adddatum(fn, ires->argv[0].val.str, ires->argv[1].val.num);
1132             dc_freeires(ires);
1133         }
1134         dc_queuecmd(getpeerlistcallback, data, L"lspeers", L"%i", fn->id, NULL);
1135     } else if(resp->code == 201) {
1136         dc_queuecmd(getpeerlistcallback, data, L"lspeers", L"%i", fn->id, NULL);
1137     } else {
1138         data->callback(fn, resp->code, data->data);
1139         free(data);
1140     }
1141     return(1);
1142 }
1143
1144 void dc_getfnlistasync(void (*callback)(int, void *), void *udata)
1145 {
1146     struct gencbdata *data;
1147     
1148     data = smalloc(sizeof(*data));
1149     data->callback = callback;
1150     data->data = udata;
1151     dc_queuecmd(getfnlistcallback, data, L"lsnodes", NULL);
1152 }
1153
1154 void dc_gettrlistasync(void (*callback)(int, void *), void *udata)
1155 {
1156     struct gencbdata *data;
1157     
1158     data = smalloc(sizeof(*data));
1159     data->callback = callback;
1160     data->data = udata;
1161     dc_queuecmd(gettrlistcallback, data, L"lstrans", NULL);
1162 }
1163
1164 void dc_getpeerlistasync(struct dc_fnetnode *fn, void (*callback)(struct dc_fnetnode *, int, void *), void *udata)
1165 {
1166     struct fnetcbdata *data;
1167     
1168     data = smalloc(sizeof(*data));
1169     data->callback = callback;
1170     data->fnid = fn->id;
1171     data->data = udata;
1172     dc_queuecmd(getpalistcallback, data, L"lspa", L"%i", fn->id, NULL);
1173 }
1174
1175 void dc_uimisc_disconnected(void)
1176 {
1177     while(dc_fnetnodes != NULL)
1178         freefn(dc_fnetnodes);
1179     while(dc_transfers != NULL)
1180         freetransfer(dc_transfers);
1181 }
1182
1183 void dc_uimisc_handlenotify(struct dc_response *resp)
1184 {
1185     int i;
1186     struct dc_fnetnode *fn;
1187     struct dc_transfer *transfer;
1188     struct dc_fnetpeer *peer;
1189     struct dc_intresp *ires;
1190     
1191     if((ires = dc_interpret(resp)) == NULL)
1192         return;
1193     switch(resp->code)
1194     {
1195     case 601:
1196         if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
1197             fn->state = ires->argv[1].val.num;
1198         break;
1199     case 602:
1200         if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
1201         {
1202             if(fn->name != NULL)
1203                 free(fn->name);
1204             fn->name = swcsdup(ires->argv[1].val.str);
1205         }
1206         break;
1207     case 603:
1208         if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
1209             freefn(fn);
1210         break;
1211     case 604:
1212         fn = newfn();
1213         fn->id = ires->argv[0].val.num;
1214         if(fn->fnet != NULL)
1215             free(fn->fnet);
1216         fn->fnet = swcsdup(ires->argv[1].val.str);
1217         fn->state = DC_FNN_STATE_SYN;
1218         fn->numusers = 0;
1219         break;
1220     case 605:
1221         if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
1222             fn->numusers = ires->argv[1].val.num;
1223         break;
1224     case 610:
1225         transfer = newtransfer();
1226         transfer->id = ires->argv[0].val.num;
1227         transfer->dir = ires->argv[1].val.num;
1228         if(transfer->dir == DC_TRNSD_UP)
1229             transfer->state = DC_TRNS_HS;
1230         transfer->peerid = swcsdup(ires->argv[2].val.str);
1231         if(ires->argv[3].val.str[0])
1232             transfer->path = swcsdup(ires->argv[3].val.str);
1233         break;
1234     case 611:
1235         if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
1236             transfer->state = ires->argv[1].val.num;
1237         break;
1238     case 612:
1239         if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
1240         {
1241             if(transfer->peernick != NULL)
1242                 free(transfer->peernick);
1243             transfer->peernick = swcsdup(ires->argv[1].val.str);
1244         }
1245         break;
1246     case 613:
1247         if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
1248             transfer->size = ires->argv[1].val.lnum;
1249         break;
1250     case 614:
1251         if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
1252         {
1253             transfer->error = ires->argv[1].val.num;
1254             time(&transfer->errortime);
1255         }
1256         break;
1257     case 615:
1258         if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
1259             transfer->curpos = ires->argv[1].val.lnum;
1260         break;
1261     case 616:
1262         if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
1263         {
1264             if(transfer->path != NULL)
1265                 free(transfer->path);
1266             transfer->path = swcsdup(ires->argv[1].val.str);
1267         }
1268         break;
1269     case 617:
1270         if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
1271             freetransfer(transfer);
1272         break;
1273     case 618:
1274         if((transfer = dc_findtransfer(ires->argv[0].val.num)) != NULL)
1275         {
1276             if(transfer->hash != NULL)
1277             {
1278                 free(transfer->hash);
1279                 transfer->hash = NULL;
1280             }
1281             if(wcslen(ires->argv[1].val.str) > 0)
1282                 transfer->hash = swcsdup(ires->argv[1].val.str);
1283         }
1284         break;
1285     case 630:
1286         if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
1287         {
1288             if((peer = dc_fnetfindpeer(fn, ires->argv[1].val.str)) == NULL)
1289             {
1290                 peer = addpeer(fn, ires->argv[1].val.str, ires->argv[2].val.str);
1291                 if(fn->newpeercb != NULL)
1292                     fn->newpeercb(peer);
1293             }
1294         }
1295         break;
1296     case 631:
1297         if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
1298         {
1299             if((peer = dc_fnetfindpeer(fn, ires->argv[1].val.str)) != NULL)
1300             {
1301                 if(fn->delpeercb != NULL)
1302                     fn->delpeercb(peer);
1303                 delpeer(peer);
1304             }
1305         }
1306         break;
1307     case 632:
1308         if((fn = dc_findfnetnode(ires->argv[0].val.num)) != NULL)
1309         {
1310             if((peer = dc_fnetfindpeer(fn, ires->argv[1].val.str)) != NULL)
1311             {
1312                 if(wcscmp(ires->argv[2].val.str, peer->nick))
1313                 {
1314                     free(peer->nick);
1315                     peer->nick = swcsdup(ires->argv[2].val.str);
1316                 }
1317                 for(i = 4; i < resp->rlines[0].argc; i += 3)
1318                 {
1319                     switch(wcstol(resp->rlines[0].argv[i + 1], NULL, 10))
1320                     {
1321                     case DC_FNPD_INT:
1322                         peersetnum(peer, resp->rlines[0].argv[i], wcstol(resp->rlines[0].argv[i + 2], NULL, 10));
1323                         break;
1324                     case DC_FNPD_LL:
1325                         peersetlnum(peer, resp->rlines[0].argv[i], wcstoll(resp->rlines[0].argv[i + 2], NULL, 10));
1326                         break;
1327                     case DC_FNPD_STR:
1328                         peersetstr(peer, resp->rlines[0].argv[i], resp->rlines[0].argv[i + 2]);
1329                         break;
1330                     }
1331                 }
1332                 if(fn->chpeercb != NULL)
1333                     fn->chpeercb(peer);
1334             }
1335         }
1336         break;
1337     default:
1338         break;
1339     }
1340     dc_freeires(ires);
1341     resp->curline = 0;
1342 }
1343
1344 /* Note the backspace handling - it's not as elegant as possible, but
1345  * it helps avoid the "box-of-toothpicks" syndrome when writing search
1346  * expressions manually. */
1347 wchar_t **dc_lexsexpr(wchar_t *sexpr)
1348 {
1349     wchar_t **ret;
1350     wchar_t *buf;
1351     size_t retsize, retdata, bufsize, bufdata;
1352     int state;
1353     
1354     ret = NULL;
1355     buf = NULL;
1356     retsize = retdata = bufsize = bufdata = 0;
1357     state = 0;
1358     while(*sexpr != L'\0')
1359     {
1360         switch(state)
1361         {
1362         case 0:
1363             if(!iswspace(*sexpr))
1364                 state = 1;
1365             else
1366                 sexpr++;
1367             break;
1368         case 1:
1369             if(iswspace(*sexpr))
1370             {
1371                 if(buf != NULL)
1372                 {
1373                     addtobuf(buf, L'\0');
1374                     addtobuf(ret, buf);
1375                     buf = NULL;
1376                     bufsize = bufdata = 0;
1377                 }
1378                 state = 0;
1379             } else if((*sexpr == L'(') ||
1380                       (*sexpr == L')') ||
1381                       (*sexpr == L'&') ||
1382                       (*sexpr == L'|') ||
1383                       (*sexpr == L'!')) {
1384                 if(buf != NULL)
1385                 {
1386                     addtobuf(buf, L'\0');
1387                     addtobuf(ret, buf);
1388                     buf = NULL;
1389                     bufsize = bufdata = 0;
1390                 }
1391                 addtobuf(buf, *sexpr);
1392                 addtobuf(buf, L'\0');
1393                 addtobuf(ret, buf);
1394                 buf = NULL;
1395                 bufsize = bufdata = 0;
1396                 sexpr++;
1397             } else if(*sexpr == L'\"') {
1398                 sexpr++;
1399                 state = 2;
1400             } else if(*sexpr == L'\\') {
1401                 sexpr++;
1402                 if(*sexpr == L'\0')
1403                 {
1404                     addtobuf(buf, *sexpr);
1405                 } else if((*sexpr == L'\\') || (*sexpr == L'\"')) {
1406                     addtobuf(buf, *sexpr);
1407                     sexpr++;
1408                 } else {
1409                     addtobuf(buf, L'\\');
1410                     addtobuf(buf, *sexpr);
1411                     sexpr++;
1412                 }
1413             } else {
1414                 addtobuf(buf, *(sexpr++));
1415             }
1416             break;
1417         case 2:
1418             if(*sexpr == L'\\')
1419             {
1420                 sexpr++;
1421                 if(*sexpr == L'\0')
1422                 {
1423                     addtobuf(buf, *sexpr);
1424                 } else if((*sexpr == L'\\') || (*sexpr == L'\"')) {
1425                     addtobuf(buf, *sexpr);
1426                     sexpr++;
1427                 } else {
1428                     addtobuf(buf, L'\\');
1429                     addtobuf(buf, *sexpr);
1430                     sexpr++;
1431                 }
1432             } else if(*sexpr == L'\"') {
1433                 state = 1;
1434                 sexpr++;
1435             } else {
1436                 addtobuf(buf, *(sexpr++));
1437             }
1438             break;
1439         }
1440     }
1441     if(buf != NULL)
1442     {
1443         addtobuf(buf, L'\0');
1444         addtobuf(ret, buf);
1445     }
1446     addtobuf(ret, NULL);
1447     return(ret);
1448 }
1449
1450 void dc_freewcsarr(wchar_t **arr)
1451 {
1452     wchar_t **buf;
1453     
1454     if(arr == NULL)
1455         return;
1456     for(buf = arr; *buf != NULL; buf++)
1457         free(*buf);
1458     free(arr);
1459 }