]> arthur.barton.de Git - netatalk.git/blob - etc/afpd/afp_config.c
- merge branch-netatalk-afp-3x-dev, HEAD was tagged before
[netatalk.git] / etc / afpd / afp_config.c
1 /*
2  * $Id: afp_config.c,v 1.23 2005-04-28 20:49:39 bfernhomberg Exp $
3  *
4  * Copyright (c) 1997 Adrian Sun (asun@zoology.washington.edu)
5  * All Rights Reserved.  See COPYRIGHT.
6  */
7
8 #ifdef HAVE_CONFIG_H
9 #include "config.h"
10 #endif /* HAVE_CONFIG_H */
11
12 #include <stdio.h>
13 #include <stdlib.h>
14 #include <string.h>
15 #include <errno.h>
16
17 /* STDC check */
18 #if STDC_HEADERS
19 #include <string.h>
20 #else /* STDC_HEADERS */
21 #ifndef HAVE_STRCHR
22 #define strchr index
23 #define strrchr index
24 #endif /* HAVE_STRCHR */
25 char *strchr (), *strrchr ();
26 #ifndef HAVE_MEMCPY
27 #define memcpy(d,s,n) bcopy ((s), (d), (n))
28 #define memmove(d,s,n) bcopy ((s), (d), (n))
29 #endif /* ! HAVE_MEMCPY */
30 #endif /* STDC_HEADERS */
31
32 #ifdef HAVE_UNISTD_H
33 #include <unistd.h>
34 #endif /* HAVE_UNISTD_H */
35 #include <ctype.h>
36 #include <atalk/logger.h>
37 #include <atalk/util.h>
38
39 #include <sys/socket.h>
40 #include <netinet/in.h>
41 #include <arpa/inet.h>
42
43 #include <atalk/dsi.h>
44 #include <atalk/atp.h>
45 #include <atalk/asp.h>
46 #include <atalk/nbp.h>
47 #include <atalk/afp.h>
48 #include <atalk/compat.h>
49 #include <atalk/server_child.h>
50 #ifdef USE_SRVLOC
51 #include <slp.h>
52 #endif /* USE_SRVLOC */
53
54 #include "globals.h"
55 #include "afp_config.h"
56 #include "uam_auth.h"
57 #include "status.h"
58
59 #define LINESIZE 1024  
60
61 /* get rid of unneeded configurations. i use reference counts to deal
62  * w/ multiple configs sharing the same afp_options. oh, to dream of
63  * garbage collection ... */
64 void configfree(AFPConfig *configs, const AFPConfig *config)
65 {
66     AFPConfig *p, *q;
67
68     for (p = configs; p; p = q) {
69         q = p->next;
70         if (p == config)
71             continue;
72
73         /* do a little reference counting */
74         if (--(*p->optcount) < 1) {
75             afp_options_free(&p->obj.options, p->defoptions);
76             free(p->optcount);
77         }
78
79         switch (p->obj.proto) {
80 #ifndef NO_DDP
81         case AFPPROTO_ASP:
82             free(p->obj.Obj);
83             free(p->obj.Type);
84             free(p->obj.Zone);
85             atp_close(((ASP) p->obj.handle)->asp_atp);
86             free(p->obj.handle);
87             break;
88 #endif /* no afp/asp */
89         case AFPPROTO_DSI:
90             close(p->fd);
91             free(p->obj.handle);
92             break;
93         }
94         free(p);
95     }
96 }
97
98 #ifdef USE_SRVLOC
99 static void SRVLOC_callback(SLPHandle hslp _U_, SLPError errcode, void *cookie) {
100     *(SLPError*)cookie = errcode;
101 }
102
103 static char hex[17] = "0123456789abcdef";
104
105 static char * srvloc_encode(const struct afp_options *options, const char *name)
106 {
107         static char buf[512];
108         char *conv_name;
109         unsigned char *p;
110         unsigned int i = 0;
111 #ifndef NO_DDP
112         char *Obj, *Type = "", *Zone = "";
113 #endif
114
115         /* Convert name to maccharset */
116         if ((size_t)-1 ==(convert_string_allocate( options->unixcharset, options->maccharset,
117                          name, strlen(name), &conv_name)) )
118                 return (char*)name;
119
120         /* Escape characters */
121         p = conv_name;
122         while (*p && i<(sizeof(buf)-4)) {
123             if (*p == '@')
124                 break;
125             else if (isspace(*p)) {
126                 buf[i++] = '%';
127                 buf[i++] = '2';
128                 buf[i++] = '0';
129                 p++;
130             }   
131             else if ((!isascii(*p)) || *p <= 0x2f || *p == 0x3f ) {
132                 buf[i++] = '%';
133                 buf[i++] = hex[*p >> 4];
134                 buf[i++] = hex[*p++ & 15];
135             }
136             else {
137                 buf[i++] = *p++;
138             }
139         }
140         buf[i] = '\0';
141
142 #ifndef NO_DDP
143         /* Add ZONE,  */
144         if (nbp_name(options->server, &Obj, &Type, &Zone )) {
145                 LOG(log_error, logtype_afpd, "srvloc_encode: can't parse %s", options->server );
146         }
147         else {
148                 snprintf( buf+i, sizeof(buf)-i-1 ,"&ZONE=%s", Zone);
149         }
150 #endif
151         free (conv_name);
152
153         return buf;
154 }
155 #endif /* USE_SRVLOC */
156
157 #ifdef USE_SRVLOC
158 static void dsi_cleanup(const AFPConfig *config)
159 {
160     SLPError err;
161     SLPError callbackerr;
162     SLPHandle hslp;
163     DSI *dsi = (DSI *)config->obj.handle;
164
165     /*  Do nothing if we didn't register.  */
166     if (!dsi || dsi->srvloc_url[0] == '\0')
167         return;
168
169     err = SLPOpen("en", SLP_FALSE, &hslp);
170     if (err != SLP_OK) {
171         LOG(log_error, logtype_afpd, "dsi_cleanup: Error opening SRVLOC handle");
172         goto srvloc_dereg_err;
173     }
174
175     err = SLPDereg(hslp,
176                    dsi->srvloc_url,
177                    SRVLOC_callback,
178                    &callbackerr);
179     if (err != SLP_OK) {
180         LOG(log_error, logtype_afpd, "dsi_cleanup: Error unregistering %s from SRVLOC", dsi->srvloc_url);
181         goto srvloc_dereg_err;
182     }
183
184     if (callbackerr != SLP_OK) {
185         LOG(log_error, logtype_afpd, "dsi_cleanup: Error in callback while trying to unregister %s from SRVLOC (%d)", dsi->srvloc_url, callbackerr);
186         goto srvloc_dereg_err;
187     }
188
189 srvloc_dereg_err:
190     dsi->srvloc_url[0] = '\0';
191     SLPClose(hslp);
192 }
193 #endif /* USE_SRVLOC */
194
195 #ifndef NO_DDP
196 static void asp_cleanup(const AFPConfig *config)
197 {
198     /* we need to stop tickle handler */
199     asp_stop_tickle();
200     nbp_unrgstr(config->obj.Obj, config->obj.Type, config->obj.Zone,
201                 &config->obj.options.ddpaddr);
202 }
203
204 /* these two are almost identical. it should be possible to collapse them
205  * into one with minimal junk. */
206 static int asp_start(AFPConfig *config, AFPConfig *configs,
207                      server_child *server_children)
208 {
209     ASP asp;
210
211     if (!(asp = asp_getsession(config->obj.handle, server_children,
212                                config->obj.options.tickleval))) {
213         LOG(log_error, logtype_afpd, "main: asp_getsession: %s", strerror(errno) );
214         exit( EXITERR_CLNT );
215     }
216
217     if (asp->child) {
218         configfree(configs, config); /* free a bunch of stuff */
219         afp_over_asp(&config->obj);
220         exit (0);
221     }
222
223     return 0;
224 }
225 #endif /* no afp/asp */
226
227 static int dsi_start(AFPConfig *config, AFPConfig *configs,
228                      server_child *server_children)
229 {
230     DSI *dsi;
231
232     if (!(dsi = dsi_getsession(config->obj.handle, server_children,
233                                config->obj.options.tickleval))) {
234         LOG(log_error, logtype_afpd, "main: dsi_getsession: %s", strerror(errno) );
235         exit( EXITERR_CLNT );
236     }
237
238     /* we've forked. */
239     if (dsi->child) {
240         configfree(configs, config);
241         afp_over_dsi(&config->obj); /* start a session */
242         exit (0);
243     }
244
245     return 0;
246 }
247
248 #ifndef NO_DDP
249 static AFPConfig *ASPConfigInit(const struct afp_options *options,
250                                 unsigned char *refcount)
251 {
252     AFPConfig *config;
253     ATP atp;
254     ASP asp;
255     char *Obj, *Type = "AFPServer", *Zone = "*";
256     char *convname = NULL;
257
258     if ((config = (AFPConfig *) calloc(1, sizeof(AFPConfig))) == NULL)
259         return NULL;
260
261     if ((atp = atp_open(ATADDR_ANYPORT, &options->ddpaddr)) == NULL)  {
262         LOG(log_error, logtype_afpd, "main: atp_open: %s", strerror(errno) );
263         free(config);
264         return NULL;
265     }
266
267     if ((asp = asp_init( atp )) == NULL) {
268         LOG(log_error, logtype_afpd, "main: asp_init: %s", strerror(errno) );
269         atp_close(atp);
270         free(config);
271         return NULL;
272     }
273
274     /* register asp server */
275     Obj = (char *) options->hostname;
276     if (options->server && (size_t)-1 ==(convert_string_allocate( options->unixcharset, options->maccharset,
277                          options->server, strlen(options->server), &convname)) ) {
278         if ((convname = strdup(options->server)) == NULL ) {
279             LOG(log_error, logtype_afpd, "malloc: %s", strerror(errno) );
280             goto serv_free_return;
281         }
282     }
283
284     if (nbp_name(convname, &Obj, &Type, &Zone )) {
285         LOG(log_error, logtype_afpd, "main: can't parse %s", options->server );
286         goto serv_free_return;
287     }
288     if (convname)
289         free (convname);
290
291     /* dup Obj, Type and Zone as they get assigned to a single internal
292      * buffer by nbp_name */
293     if ((config->obj.Obj  = strdup(Obj)) == NULL)
294         goto serv_free_return;
295
296     if ((config->obj.Type = strdup(Type)) == NULL) {
297         free(config->obj.Obj);
298         goto serv_free_return;
299     }
300
301     if ((config->obj.Zone = strdup(Zone)) == NULL) {
302         free(config->obj.Obj);
303         free(config->obj.Type);
304         goto serv_free_return;
305     }
306
307     /* make sure we're not registered */
308     nbp_unrgstr(Obj, Type, Zone, &options->ddpaddr);
309     if (nbp_rgstr( atp_sockaddr( atp ), Obj, Type, Zone ) < 0 ) {
310         LOG(log_error, logtype_afpd, "Can't register %s:%s@%s", Obj, Type, Zone );
311         free(config->obj.Obj);
312         free(config->obj.Type);
313         free(config->obj.Zone);
314         goto serv_free_return;
315     }
316
317     LOG(log_info, logtype_afpd, "%s:%s@%s started on %u.%u:%u (%s)", Obj, Type, Zone,
318         ntohs( atp_sockaddr( atp )->sat_addr.s_net ),
319         atp_sockaddr( atp )->sat_addr.s_node,
320         atp_sockaddr( atp )->sat_port, VERSION );
321
322     config->fd = atp_fileno(atp);
323     config->obj.handle = asp;
324     config->obj.config = config;
325     config->obj.proto = AFPPROTO_ASP;
326
327     memcpy(&config->obj.options, options, sizeof(struct afp_options));
328     config->optcount = refcount;
329     (*refcount)++;
330
331     config->server_start = asp_start;
332     config->server_cleanup = asp_cleanup;
333
334     return config;
335
336 serv_free_return:
337                     asp_close(asp);
338     free(config);
339     return NULL;
340 }
341 #endif /* no afp/asp */
342
343
344 static AFPConfig *DSIConfigInit(const struct afp_options *options,
345                                 unsigned char *refcount,
346                                 const dsi_proto protocol)
347 {
348     AFPConfig *config;
349     DSI *dsi;
350     char *p, *q;
351 #ifdef USE_SRVLOC
352     SLPError err;
353     SLPError callbackerr;
354     SLPHandle hslp;
355     struct servent *afpovertcp;
356     int afp_port = 548;
357     char *srvloc_hostname, *hostname;
358 #endif /* USE_SRVLOC */
359
360     if ((config = (AFPConfig *) calloc(1, sizeof(AFPConfig))) == NULL) {
361         LOG(log_error, logtype_afpd, "DSIConfigInit: malloc(config): %s", strerror(errno) );
362         return NULL;
363     }
364
365     if ((dsi = dsi_init(protocol, "afpd", options->hostname,
366                         options->ipaddr, options->port,
367                         options->flags & OPTION_PROXY,
368                         options->server_quantum)) == NULL) {
369         LOG(log_error, logtype_afpd, "main: dsi_init: %s", strerror(errno) );
370         free(config);
371         return NULL;
372     }
373
374     if (options->flags & OPTION_PROXY) {
375         LOG(log_info, logtype_afpd, "ASIP proxy initialized for %s:%d (%s)",
376             inet_ntoa(dsi->server.sin_addr), ntohs(dsi->server.sin_port),
377             VERSION);
378     } else {
379         LOG(log_info, logtype_afpd, "ASIP started on %s:%d(%d) (%s)",
380             inet_ntoa(dsi->server.sin_addr), ntohs(dsi->server.sin_port),
381             dsi->serversock, VERSION);
382     }
383
384 #ifdef USE_SRVLOC
385     dsi->srvloc_url[0] = '\0';  /*  Mark that we haven't registered.  */
386     if (!(options->flags & OPTION_NOSLP)) {
387         err = SLPOpen("en", SLP_FALSE, &hslp);
388         if (err != SLP_OK) {
389             LOG(log_error, logtype_afpd, "DSIConfigInit: Error opening SRVLOC handle");
390             goto srvloc_reg_err;
391         }
392
393         /* XXX We don't want to tack on the port number if we don't have to.
394          * Why?
395          * Well, this seems to break MacOS < 10.  If the user _really_ wants to
396          * use a non-default port, they can, but be aware, this server might
397          * not show up int the Network Browser.
398          */
399         afpovertcp = getservbyname("afpovertcp", "tcp");
400         if (afpovertcp != NULL) {
401             afp_port = afpovertcp->s_port;
402         }
403         /* If specified use the FQDN to register with srvloc, otherwise use IP. */
404         p = NULL;
405         if (options->fqdn) {
406             hostname = options->fqdn;
407             p = strchr(hostname, ':');
408         }       
409         else 
410             hostname = inet_ntoa(dsi->server.sin_addr);
411         srvloc_hostname = srvloc_encode(options, (options->server ? options->server : options->hostname));
412
413         if (strlen(srvloc_hostname) > (sizeof(dsi->srvloc_url) - strlen(hostname) - 21)) {
414             LOG(log_error, logtype_afpd, "DSIConfigInit: Hostname is too long for SRVLOC");
415             dsi->srvloc_url[0] = '\0';
416             goto srvloc_reg_err;
417         }
418         if ((p) || dsi->server.sin_port == afp_port) {
419             sprintf(dsi->srvloc_url, "afp://%s/?NAME=%s", hostname, srvloc_hostname);
420         }
421         else {
422             sprintf(dsi->srvloc_url, "afp://%s:%d/?NAME=%s", hostname, ntohs(dsi->server.sin_port), srvloc_hostname);
423         }
424
425         err = SLPReg(hslp,
426                      dsi->srvloc_url,
427                      SLP_LIFETIME_MAXIMUM,
428                      "afp",
429                      "",
430                      SLP_TRUE,
431                      SRVLOC_callback,
432                      &callbackerr);
433         if (err != SLP_OK) {
434             LOG(log_error, logtype_afpd, "DSIConfigInit: Error registering %s with SRVLOC", dsi->srvloc_url);
435             dsi->srvloc_url[0] = '\0';
436             goto srvloc_reg_err;
437         }
438
439         if (callbackerr != SLP_OK) {
440             LOG(log_error, logtype_afpd, "DSIConfigInit: Error in callback trying to register %s with SRVLOC", dsi->srvloc_url);
441             dsi->srvloc_url[0] = '\0';
442             goto srvloc_reg_err;
443         }
444
445         LOG(log_info, logtype_afpd, "Sucessfully registered %s with SRVLOC", dsi->srvloc_url);
446
447 srvloc_reg_err:
448         SLPClose(hslp);
449     }
450 #endif /* USE_SRVLOC */
451
452
453     config->fd = dsi->serversock;
454     config->obj.handle = dsi;
455     config->obj.config = config;
456     config->obj.proto = AFPPROTO_DSI;
457
458     memcpy(&config->obj.options, options, sizeof(struct afp_options));
459     /* get rid of any appletalk info. we use the fact that the DSI
460      * stuff is done after the ASP stuff. */
461     p = config->obj.options.server;
462     if (p && (q = strchr(p, ':')))
463         *q = '\0';
464
465     config->optcount = refcount;
466     (*refcount)++;
467
468     config->server_start = dsi_start;
469 #ifdef USE_SRVLOC
470     config->server_cleanup = dsi_cleanup;
471 #endif 
472     return config;
473 }
474
475 /* allocate server configurations. this should really store the last
476  * entry in config->last or something like that. that would make
477  * supporting multiple dsi transports easier. */
478 static AFPConfig *AFPConfigInit(const struct afp_options *options,
479                                 const struct afp_options *defoptions)
480 {
481     AFPConfig *config = NULL, *next = NULL;
482     unsigned char *refcount;
483
484     if ((refcount = (unsigned char *)
485                     calloc(1, sizeof(unsigned char))) == NULL) {
486         LOG(log_error, logtype_afpd, "AFPConfigInit: calloc(refcount): %s", strerror(errno) );
487         return NULL;
488     }
489
490 #ifndef NO_DDP
491     /* handle asp transports */
492     if ((options->transports & AFPTRANS_DDP) &&
493             (config = ASPConfigInit(options, refcount)))
494         config->defoptions = defoptions;
495 #endif /* NO_DDP */
496
497     /* handle dsi transports and dsi proxies. we only proxy
498      * for DSI connections. */
499
500     /* this should have something like the following:
501      * for (i=mindsi; i < maxdsi; i++)
502      *   if (options->transports & (1 << i) && 
503      *     (next = DSIConfigInit(options, refcount, i)))
504      *     next->defoptions = defoptions;
505      */
506     if ((options->transports & AFPTRANS_TCP) &&
507             (((options->flags & OPTION_PROXY) == 0) ||
508              ((options->flags & OPTION_PROXY) && config))
509             && (next = DSIConfigInit(options, refcount, DSI_TCPIP)))
510         next->defoptions = defoptions;
511
512     /* load in all the authentication modules. we can load the same
513        things multiple times if necessary. however, loading different
514        things with the same names will cause complaints. by not loading
515        in any uams with proxies, we prevent ddp connections from succeeding.
516     */
517     auth_load(options->uampath, options->uamlist);
518
519     /* this should be able to accept multiple dsi transports. i think
520      * the only thing that gets affected is the net addresses. */
521     status_init(config, next, options);
522
523     /* attach dsi config to tail of asp config */
524     if (config) {
525         config->next = next;
526         return config;
527     }
528
529     return next;
530 }
531
532 /* fill in the appropriate bits for each interface */
533 AFPConfig *configinit(struct afp_options *cmdline)
534 {
535     FILE *fp;
536     char buf[LINESIZE + 1], *p, have_option = 0;
537     struct afp_options options;
538     AFPConfig *config=NULL, *first = NULL; 
539
540     status_reset();
541     /* if config file doesn't exist, load defaults */
542     if ((fp = fopen(cmdline->configfile, "r")) == NULL)
543     {
544         LOG(log_debug, logtype_afpd, "ConfigFile %s not found, assuming defaults",
545             cmdline->configfile);
546         return AFPConfigInit(cmdline, cmdline);
547     }
548
549     LOG(log_debug, logtype_afpd, "Loading ConfigFile"); 
550
551     /* scan in the configuration file */
552     while (!feof(fp)) {
553         if (!fgets(buf, sizeof(buf), fp) || buf[0] == '#')
554             continue;
555
556         /* a little pre-processing to get rid of spaces and end-of-lines */
557         p = buf;
558         while (p && isspace(*p))
559             p++;
560         if (!p || (*p == '\0'))
561             continue;
562
563         have_option = 1;
564
565         memcpy(&options, cmdline, sizeof(options));
566         if (!afp_options_parseline(p, &options))
567             continue;
568
569         /* this should really get a head and a tail to simplify things. */
570         if (!first) {
571             if ((first = AFPConfigInit(&options, cmdline)))
572                 config = first->next ? first->next : first;
573         } else if ((config->next = AFPConfigInit(&options, cmdline))) {
574             config = config->next->next ? config->next->next : config->next;
575         }
576     }
577
578     LOG(log_debug, logtype_afpd, "Finished parsing Config File");
579     fclose(fp);
580
581     if (!have_option)
582         return AFPConfigInit(cmdline, cmdline);
583
584     return first;
585 }