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