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