Add hash attrib to transfers.
[doldaconnect.git] / daemon / transfer.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 #include <stdlib.h>
20 #include <string.h>
21 #include <time.h>
22 #include <unistd.h>
23 #include <fcntl.h>
24 #include <signal.h>
25 #include <pwd.h>
26 #include <grp.h>
27 #include <errno.h>
28 #include <sys/wait.h>
29
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 #include "log.h"
34 #include "utils.h"
35 #include "sysevents.h"
36 #include "auth.h"
37 #include "transfer.h"
38 #include "module.h"
39 #include "client.h"
40
41 static void killfilter(struct transfer *transfer);
42
43 struct transfer *transfers = NULL;
44 int numtransfers = 0;
45 GCBCHAIN(newtransfercb, struct transfer *);
46
47 void freetransfer(struct transfer *transfer)
48 {
49     struct transarg *ta;
50     
51     if(transfer == transfers)
52         transfers = transfer->next;
53     if(transfer->next != NULL)
54         transfer->next->prev = transfer->prev;
55     if(transfer->prev != NULL)
56         transfer->prev->next = transfer->next;
57     CBCHAINDOCB(transfer, trans_destroy, transfer);
58     CBCHAINFREE(transfer, trans_ac);
59     CBCHAINFREE(transfer, trans_act);
60     CBCHAINFREE(transfer, trans_p);
61     CBCHAINFREE(transfer, trans_destroy);
62     CBCHAINFREE(transfer, trans_filterout);
63     while((ta = transfer->args) != NULL)
64     {
65         transfer->args = ta->next;
66         free(ta->rec);
67         free(ta->val);
68         free(ta);
69     }
70     if(transfer->filter != -1)
71         killfilter(transfer);
72     if(transfer->etimer != NULL)
73         canceltimer(transfer->etimer);
74     if(transfer->auth != NULL)
75         authputhandle(transfer->auth);
76     if(transfer->peerid != NULL)
77         free(transfer->peerid);
78     if(transfer->peernick != NULL)
79         free(transfer->peernick);
80     if(transfer->path != NULL)
81         free(transfer->path);
82     if(transfer->actdesc != NULL)
83         free(transfer->actdesc);
84     if(transfer->filterbuf != NULL)
85         free(transfer->filterbuf);
86     if(transfer->hash != NULL)
87         freehash(transfer->hash);
88     if(transfer->localend != NULL)
89     {
90         transfer->localend->readcb = NULL;
91         transfer->localend->writecb = NULL;
92         transfer->localend->errcb = NULL;
93         putsock(transfer->localend);
94     }
95     if(transfer->filterout != NULL)
96     {
97         transfer->filterout->readcb = NULL;
98         transfer->filterout->writecb = NULL;
99         transfer->filterout->errcb = NULL;
100         putsock(transfer->filterout);
101     }
102     if(transfer->fn != NULL)
103         putfnetnode(transfer->fn);
104     free(transfer);
105     numtransfers--;
106 }
107
108 struct transfer *newtransfer(void)
109 {
110     struct transfer *new;
111     static int curid = 0;
112     
113     new = smalloc(sizeof(*new));
114     memset(new, 0, sizeof(*new));
115     new->id = curid++;
116     new->size = -1;
117     new->endpos = -1;
118     new->filter = -1;
119     CBCHAININIT(new, trans_ac);
120     CBCHAININIT(new, trans_act);
121     CBCHAININIT(new, trans_p);
122     CBCHAININIT(new, trans_destroy);
123     CBCHAININIT(new, trans_filterout);
124     new->next = NULL;
125     new->prev = NULL;
126     time(&new->activity);
127     numtransfers++;
128     return(new);
129 }
130
131 void transferaddarg(struct transfer *transfer, wchar_t *rec, wchar_t *val)
132 {
133     struct transarg *ta;
134     
135     ta = smalloc(sizeof(*ta));
136     ta->rec = swcsdup(rec);
137     ta->val = swcsdup(val);
138     ta->next = transfer->args;
139     transfer->args = ta;
140 }
141
142 void transferattach(struct transfer *transfer, struct transferiface *iface, void *data)
143 {
144     if(transfer->iface != NULL)
145         transferdetach(transfer);
146     transfer->iface = iface;
147     transfer->ifacedata = data;
148 }
149
150 void transferdetach(struct transfer *transfer)
151 {
152     if(transfer->iface != NULL)
153     {
154         transfer->iface->detach(transfer, transfer->ifacedata);
155         transfer->iface = NULL;
156         transfer->ifacedata = NULL;
157     }
158 }
159
160 struct transfer *newupload(struct fnetnode *fn, struct fnet *fnet, wchar_t *nickid, struct transferiface *iface, void *data)
161 {
162     struct transfer *transfer;
163     
164     transfer = newtransfer();
165     if(fnet != NULL)
166         transfer->fnet = fnet;
167     else
168         transfer->fnet = fn->fnet;
169     transfer->peerid = swcsdup(nickid);
170     transfer->state = TRNS_HS;
171     transfer->dir = TRNSD_UP;
172     if(fn != NULL)
173         getfnetnode(transfer->fn = fn);
174     transferattach(transfer, iface, data);
175     linktransfer(transfer);
176     bumptransfer(transfer);
177     return(transfer);
178 }
179
180 void linktransfer(struct transfer *transfer)
181 {
182     transfer->next = transfers;
183     transfer->prev = NULL;
184     if(transfers != NULL)
185         transfers->prev = transfer;
186     transfers = transfer;
187     GCBCHAINDOCB(newtransfercb, transfer);
188 }
189
190 void resettransfer(struct transfer *transfer)
191 {
192     if(transfer->dir == TRNSD_DOWN)
193     {
194         if(transfer->iface != NULL)
195             transferdetach(transfer);
196         killfilter(transfer);
197         transfersetstate(transfer, TRNS_WAITING);
198         transfersetactivity(transfer, L"reset");
199         return;
200     }
201 }
202
203 struct transfer *findtransfer(int id)
204 {
205     struct transfer *transfer;
206     
207     for(transfer = transfers; transfer != NULL; transfer = transfer->next)
208     {
209         if(transfer->id == id)
210             break;
211     }
212     return(transfer);
213 }
214
215 static void transexpire(int cancelled, struct transfer *transfer)
216 {
217     transfer->etimer = NULL;
218     if(!cancelled)
219         bumptransfer(transfer);
220     else
221         transfer->timeout = 0;
222 }
223
224 static void transferread(struct socket *sk, struct transfer *transfer)
225 {
226     if(sockgetdatalen(sk) >= 65536)
227         sk->ignread = 1;
228     if((transfer->iface != NULL) && (transfer->iface->gotdata != NULL))
229         transfer->iface->gotdata(transfer, transfer->ifacedata);
230 }
231
232 static void transferwrite(struct socket *sk, struct transfer *transfer)
233 {
234     if((transfer->iface != NULL) && (transfer->iface->wantdata != NULL))
235         transfer->iface->wantdata(transfer, transfer->ifacedata);
236 }
237
238 static void transfererr(struct socket *sk, int errno, struct transfer *transfer)
239 {
240     if((transfer->iface != NULL) && (transfer->iface->endofdata != NULL))
241         transfer->iface->endofdata(transfer, transfer->ifacedata);
242 }
243
244 void transferputdata(struct transfer *transfer, void *buf, size_t size)
245 {
246     time(&transfer->activity);
247     sockqueue(transfer->localend, buf, size);
248     transfer->curpos += size;
249     CBCHAINDOCB(transfer, trans_p, transfer);
250 }
251
252 void transferendofdata(struct transfer *transfer)
253 {
254     if(transfer->curpos >= transfer->size)
255     {
256         transfersetstate(transfer, TRNS_DONE);
257         transfer->localend->readcb = NULL;
258         transfer->localend->writecb = NULL;
259         transfer->localend->errcb = NULL;
260         putsock(transfer->localend);
261         transfer->localend = NULL;
262     } else {
263         resettransfer(transfer);
264     }
265 }
266
267 size_t transferdatasize(struct transfer *transfer)
268 {
269     return(sockqueuesize(transfer->localend));
270 }
271
272 void *transfergetdata(struct transfer *transfer, size_t *size)
273 {
274     void *buf;
275     
276     if(transfer->localend == NULL)
277         return(NULL);
278     transfer->localend->ignread = 0;
279     time(&transfer->activity);
280     if((buf = sockgetinbuf(transfer->localend, size)) == NULL)
281         return(NULL);
282     if((transfer->endpos >= 0) && (transfer->curpos + *size >= transfer->endpos))
283     {
284         *size = transfer->endpos - transfer->curpos;
285         buf = srealloc(buf, *size);
286     }
287     transfer->curpos += *size;
288     CBCHAINDOCB(transfer, trans_p, transfer);
289     return(buf);
290 }
291
292 void transferprepul(struct transfer *transfer, size_t size, size_t start, size_t end, struct socket *lesk)
293 {
294     transfersetsize(transfer, size);
295     transfer->curpos = start;
296     transfer->endpos = end;
297     lesk->ignread = 1;
298     transfersetlocalend(transfer, lesk);
299 }
300
301 void transferstartul(struct transfer *transfer, struct socket *sk)
302 {
303     transfersetstate(transfer, TRNS_MAIN);
304     socksettos(sk, confgetint("transfer", "ultos"));
305     if(transfer->localend != NULL)
306         transfer->localend->ignread = 0;
307 }
308
309 void transfersetlocalend(struct transfer *transfer, struct socket *sk)
310 {
311     if(transfer->localend != NULL)
312         putsock(transfer->localend);
313     getsock(transfer->localend = sk);
314     sk->data = transfer;
315     sk->readcb = (void (*)(struct socket *, void *))transferread;
316     sk->writecb = (void (*)(struct socket *, void *))transferwrite;
317     sk->errcb = (void (*)(struct socket *, int, void *))transfererr;
318 }
319
320 void bumptransfer(struct transfer *transfer)
321 {
322     struct fnetnode *fn;
323     struct fnetpeer *peer;
324     time_t now;
325     
326     if((now = time(NULL)) < transfer->timeout)
327     {
328         if(transfer->etimer == NULL)
329             transfer->etimer = timercallback(transfer->timeout, (void (*)(int, void *))transexpire, transfer);
330         return;
331     }
332     if(transfer->etimer != NULL)
333         canceltimer(transfer->etimer);
334     switch(transfer->state)
335     {
336     case TRNS_WAITING:
337         if(transfer->fn != NULL)
338         {
339             fn = transfer->fn;
340             if(fn->state != FNN_EST)
341             {
342                 transfer->close = 1;
343                 return;
344             }
345             peer = fnetfindpeer(fn, transfer->peerid);
346         } else {
347             peer = NULL;
348             for(fn = fnetnodes; fn != NULL; fn = fn->next)
349             {
350                 if((fn->state == FNN_EST) && (fn->fnet == transfer->fnet) && ((peer = fnetfindpeer(fn, transfer->peerid)) != NULL))
351                     break;
352             }
353         }
354         transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 30), (void (*)(int, void *))transexpire, transfer);
355         if(now - transfer->lastreq > 30)
356         {
357             if(peer != NULL)
358             {
359                 fn->fnet->reqconn(peer);
360                 time(&transfer->lastreq);
361             }
362         }
363         break;
364     case TRNS_HS:
365         if(transfer->dir == TRNSD_UP)
366         {
367             if(now - transfer->activity < 60)
368                 transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 60), (void (*)(int, void *))transexpire, transfer);
369             else
370                 transfer->close = 1;
371         } else if(transfer->dir == TRNSD_DOWN) {
372             if(now - transfer->activity < 60)
373                 transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 60), (void (*)(int, void *))transexpire, transfer);
374             else
375                 resettransfer(transfer);
376         }
377         break;
378     case TRNS_MAIN:
379         if(transfer->dir == TRNSD_UP)
380         {
381             if(now - transfer->activity < 300)
382                 transfer->etimer = timercallback(transfer->timeout = (time(NULL) + 300), (void (*)(int, void *))transexpire, transfer);
383             else
384                 transfer->close = 1;
385         }
386         break;
387     }
388 }
389
390 void transfersetactivity(struct transfer *transfer, wchar_t *desc)
391 {
392     time(&transfer->activity);
393     if(desc != NULL)
394     {
395         if(transfer->actdesc != NULL)
396             free(transfer->actdesc);
397         transfer->actdesc = swcsdup(desc);
398     }
399     bumptransfer(transfer);
400     CBCHAINDOCB(transfer, trans_act, transfer);
401 }
402
403 void transfersetstate(struct transfer *transfer, int newstate)
404 {
405     transfer->state = newstate;
406     if(transfer->etimer != NULL)
407         canceltimer(transfer->etimer);
408     transfersetactivity(transfer, NULL);
409     CBCHAINDOCB(transfer, trans_ac, transfer, L"state");
410 }
411
412 void transfersetnick(struct transfer *transfer, wchar_t *newnick)
413 {
414     if(transfer->peernick != NULL)
415         free(transfer->peernick);
416     transfer->peernick = swcsdup(newnick);
417     CBCHAINDOCB(transfer, trans_ac, transfer, L"nick");
418 }
419
420 void transfersetsize(struct transfer *transfer, int newsize)
421 {
422     transfer->size = newsize;
423     CBCHAINDOCB(transfer, trans_ac, transfer, L"size");
424 }
425
426 void transferseterror(struct transfer *transfer, int error)
427 {
428     transfer->error = error;
429     CBCHAINDOCB(transfer, trans_ac, transfer, L"error");
430 }
431
432 void transfersetpath(struct transfer *transfer, wchar_t *path)
433 {
434     if(transfer->path != NULL)
435         free(transfer->path);
436     transfer->path = swcsdup(path);
437     CBCHAINDOCB(transfer, trans_ac, transfer, L"path");
438 }
439
440 int slotsleft(void)
441 {
442     struct transfer *transfer;
443     int slots;
444     
445     slots = confgetint("transfer", "slots");
446     for(transfer = transfers; (transfer != NULL) && (slots > 0); transfer = transfer->next)
447     {
448         if((transfer->dir == TRNSD_UP) && (transfer->state == TRNS_MAIN) && !transfer->flags.b.minislot)
449             slots--;
450     }
451     return(slots);
452 }
453
454 static void killfilter(struct transfer *transfer)
455 {
456     if(transfer->filter != -1)
457     {
458         kill(-transfer->filter, SIGHUP);
459         transfer->filter = -1;
460     }
461     if(transfer->localend)
462     {
463         transfer->localend->readcb = NULL;
464         transfer->localend->writecb = NULL;
465         transfer->localend->errcb = NULL;
466         putsock(transfer->localend);
467         transfer->localend = NULL;
468     }
469     if(transfer->filterout)
470     {
471         transfer->filterout->readcb = NULL;
472         putsock(transfer->filterout);
473         transfer->filterout = NULL;
474     }
475     if(transfer->filterbuf)
476     {
477         free(transfer->filterbuf);
478         transfer->filterbuf = NULL;
479     }
480     transfer->filterbufsize = transfer->filterbufdata = 0;
481 }
482
483 static char *findfilter(struct passwd *pwd)
484 {
485     char *path, *filtername;
486
487     if((path = sprintf2("%s/.dcdl-filter", pwd->pw_dir)) != NULL)
488     {
489         if(!access(path, X_OK))
490             return(path);
491         free(path);
492     }
493     if((filtername = icwcstombs(confgetstr("transfer", "filter"), NULL)) == NULL)
494     {
495         flog(LOG_WARNING, "could not convert filter name into local charset: %s", strerror(errno));
496     } else {
497         if(strchr(filtername, '/') == NULL)
498         {
499             if((path = sprintf2("/etc/%s", filtername)) != NULL)
500             {
501                 if(!access(path, X_OK))
502                 {
503                     free(filtername);
504                     return(path);
505                 }
506                 free(path);
507             }
508             if((path = sprintf2("/usr/etc/%s", filtername)) != NULL)
509             {
510                 if(!access(path, X_OK))
511                 {
512                     free(filtername);
513                     return(path);
514                 }
515                 free(path);
516             }
517             if((path = sprintf2("/usr/local/etc/%s", filtername)) != NULL)
518             {
519                 if(!access(path, X_OK))
520                 {
521                     free(filtername);
522                     return(path);
523                 }
524                 free(path);
525             }
526         } else {
527             if(!access(filtername, X_OK))
528                 return(filtername);
529         }
530         free(filtername);
531     }
532     return(NULL);
533 }
534
535 static void filterread(struct socket *sk, struct transfer *transfer)
536 {
537     char *buf, *p, *p2;
538     size_t bufsize;
539     wchar_t *cmd, *arg;
540     
541     if((buf = sockgetinbuf(sk, &bufsize)) == NULL)
542         return;
543     bufcat(transfer->filterbuf, buf, bufsize);
544     free(buf);
545     if((p = memchr(transfer->filterbuf, '\n', transfer->filterbufdata)) != NULL)
546     {
547         *(p++) = 0;
548         if((p2 = strchr(transfer->filterbuf, ' ')) != NULL)
549             *(p2++) = 0;
550         if((cmd = icmbstowcs(transfer->filterbuf, NULL)) != NULL)
551         {
552             arg = NULL;
553             if(p2 != NULL)
554             {
555                 if((arg = icmbstowcs(p2, NULL)) == NULL)
556                     flog(LOG_WARNING, "filter sent a string which could not be converted into the local charset: %s: %s", transfer->filterbuf, strerror(errno));
557             }
558             CBCHAINDOCB(transfer, trans_filterout, transfer, cmd, arg);
559             if(arg != NULL)
560                 free(arg);
561             free(cmd);
562         } else {
563             flog(LOG_WARNING, "filter sent a string which could not be converted into the local charset: %s: %s", transfer->filterbuf, strerror(errno));
564         }
565         memmove(transfer->filterbuf, p, transfer->filterbufdata -= (p - transfer->filterbuf));
566     }
567 }
568
569 static void filterexit(pid_t pid, int status, void *data)
570 {
571     struct transfer *transfer;
572     
573     for(transfer = transfers; transfer != NULL; transfer = transfer->next)
574     {
575         if(transfer->filter == pid)
576         {
577             transfer->filter = -1;
578             killfilter(transfer);
579             if(WEXITSTATUS(status))
580             {
581                 resettransfer(transfer);
582             } else {
583                 freetransfer(transfer);
584             }
585             break;
586         }
587     }
588 }
589
590 int forkfilter(struct transfer *transfer)
591 {
592     char *filtername, *filename, *peerid, *buf;
593     wchar_t *wfilename;
594     struct passwd *pwent;
595     pid_t pid;
596     int inpipe, outpipe;
597     char **argv;
598     size_t argvsize, argvdata;
599     struct socket *insock, *outsock;
600     struct transarg *ta;
601     char *rec, *val;
602
603     wfilename = transfer->path;
604     if(transfer->fnet->filebasename != NULL)
605         wfilename = transfer->fnet->filebasename(wfilename);
606     if(transfer->auth == NULL)
607     {
608         flog(LOG_WARNING, "tried to fork filter for transfer with NULL authhandle (tranfer %i)", transfer->id);
609         errno = EACCES;
610         return(-1);
611     }
612     if((pwent = getpwuid(transfer->owner)) == NULL)
613     {
614         flog(LOG_WARNING, "no passwd entry for uid %i (found in transfer %i)", transfer->owner, transfer->id);
615         errno = EACCES;
616         return(-1);
617     }
618     if((filtername = findfilter(pwent)) == NULL)
619     {
620         flog(LOG_WARNING, "could not find filter for user %s", pwent->pw_name);
621         errno = ENOENT;
622         return(-1);
623     }
624     if((filename = icwcstombs(wfilename, NULL)) == NULL)
625     {
626         if((buf = icwcstombs(wfilename, "UTF-8")) == NULL)
627         {
628             flog(LOG_WARNING, "could convert transfer filename to neither local charset nor UTF-8: %s", strerror(errno));
629             return(-1);
630         }
631         filename = sprintf2("utf8-%s", buf);
632         free(buf);
633     }
634     if((peerid = icwcstombs(transfer->peerid, NULL)) == NULL)
635     {
636         if((buf = icwcstombs(transfer->peerid, "UTF-8")) == NULL)
637         {
638             flog(LOG_WARNING, "could convert transfer peerid to neither local charset nor UTF-8: %s", strerror(errno));
639             free(filename);
640             return(-1);
641         }
642         peerid = sprintf2("utf8-%s", buf);
643         free(buf);
644     }
645     if((pid = forksess(transfer->owner, transfer->auth, filterexit, NULL, FD_PIPE, 0, O_WRONLY, &inpipe, FD_PIPE, 1, O_RDONLY, &outpipe, FD_FILE, 2, O_RDWR, "/dev/null", FD_END)) < 0)
646     {
647         flog(LOG_WARNING, "could not fork session for filter for transfer %i: %s", transfer->id, strerror(errno));
648         return(-1);
649     }
650     if(pid == 0)
651     {
652         argv = NULL;
653         argvsize = argvdata = 0;
654         buf = sprintf2("%i", transfer->size);
655         addtobuf(argv, filtername);
656         addtobuf(argv, filename);
657         addtobuf(argv, buf);
658         addtobuf(argv, peerid);
659         for(ta = transfer->args; ta != NULL; ta = ta->next)
660         {
661             if((rec = icwcstombs(ta->rec, NULL)) == NULL)
662                 continue;
663             if((val = icwcstombs(ta->val, NULL)) == NULL)
664                 continue;
665             addtobuf(argv, rec);
666             addtobuf(argv, val);
667         }
668         addtobuf(argv, NULL);
669         execv(filtername, argv);
670         flog(LOG_WARNING, "could not exec filter %s: %s", filtername, strerror(errno));
671         exit(127);
672     }
673     insock = wrapsock(inpipe);
674     outsock = wrapsock(outpipe);
675     /* Really, really strange thing here - sometimes the kernel would
676      * return POLLIN on insock, even though it's a write-side
677      * pipe. The corresponding read on the pipe naturally returns
678      * EBADF, causing doldacond to think there's something wrong with
679      * the fd, and thus it closes it. Until I can find out whyever the
680      * kernel gives a POLLIN on the fd (if I can at all...), I'll just
681      * set ignread on insock for now. */
682     insock->ignread = 1;
683     transfer->filter = pid;
684     transfersetlocalend(transfer, insock);
685     getsock(transfer->filterout = outsock);
686     outsock->data = transfer;
687     outsock->readcb = (void (*)(struct socket *, void *))filterread;
688     putsock(insock);
689     putsock(outsock);
690     free(filtername);
691     free(filename);
692     free(peerid);
693     return(0);
694 }
695
696 static int run(void)
697 {
698     struct transfer *transfer, *next;
699     
700     for(transfer = transfers; transfer != NULL; transfer = transfer->next)
701     {
702         if((transfer->endpos >= 0) && (transfer->state == TRNS_MAIN) && (transfer->localend != NULL) && (transfer->localend->state == SOCK_EST) && (transfer->curpos >= transfer->endpos))
703         {
704             if((transfer->iface != NULL) && (transfer->iface->endofdata != NULL))
705                 transfer->iface->endofdata(transfer, transfer->ifacedata);
706             closesock(transfer->localend);
707         }
708     }
709     for(transfer = transfers; transfer != NULL; transfer = next)
710     {
711         next = transfer->next;
712         if(transfer->close)
713         {
714             transferdetach(transfer);
715             freetransfer(transfer);
716             continue;
717         }
718     }
719     return(0);
720 }
721
722 static struct configvar myvars[] =
723 {
724     {CONF_VAR_INT, "slots", {.num = 3}},
725     {CONF_VAR_INT, "ultos", {.num = SOCK_TOS_MAXTP}},
726     {CONF_VAR_INT, "dltos", {.num = SOCK_TOS_MAXTP}},
727     {CONF_VAR_STRING, "filter", {.str = L"dc-filter"}},
728     {CONF_VAR_END}
729 };
730
731 static struct module me =
732 {
733     .conf =
734     {
735         .vars = myvars
736     },
737     .name = "transfer",
738     .run = run
739 };
740
741 MODULE(me);