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