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