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        LOG(log_error, logtype_afpd, "dsi_start: session error: %s", strerror(errno));
227        return NULL;
228    }
229
230    /* we've forked. */
231    if (parent_or_child == 1) {
232        configfree(configs, config);
233        config->obj.ipc_fd = child->ipc_fds[1];
234        close(child->ipc_fds[0]); /* Close parent IPC fd */
235        free(child);
236        afp_over_dsi(&config->obj); /* start a session */
237        exit (0);
238    }
239
240    return child;
241}
242
243#ifndef NO_DDP
244static AFPConfig *ASPConfigInit(const struct afp_options *options,
245                                unsigned char *refcount)
246{
247    AFPConfig *config;
248    ATP atp;
249    ASP asp;
250    char *Obj, *Type = "AFPServer", *Zone = "*";
251    char *convname = NULL;
252
253    if ((config = (AFPConfig *) calloc(1, sizeof(AFPConfig))) == NULL)
254        return NULL;
255
256    if ((atp = atp_open(ATADDR_ANYPORT, &options->ddpaddr)) == NULL)  {
257        LOG(log_error, logtype_afpd, "main: atp_open: %s", strerror(errno) );
258        free(config);
259        return NULL;
260    }
261
262    if ((asp = asp_init( atp )) == NULL) {
263        LOG(log_error, logtype_afpd, "main: asp_init: %s", strerror(errno) );
264        atp_close(atp);
265        free(config);
266        return NULL;
267    }
268
269    /* register asp server */
270    Obj = (char *) options->hostname;
271    if (options->server && (size_t)-1 ==(convert_string_allocate( options->unixcharset, options->maccharset,
272                         options->server, strlen(options->server), &convname)) ) {
273        if ((convname = strdup(options->server)) == NULL ) {
274            LOG(log_error, logtype_afpd, "malloc: %s", strerror(errno) );
275            goto serv_free_return;
276        }
277    }
278
279    if (nbp_name(convname, &Obj, &Type, &Zone )) {
280        LOG(log_error, logtype_afpd, "main: can't parse %s", options->server );
281        goto serv_free_return;
282    }
283    if (convname)
284        free (convname);
285
286    /* dup Obj, Type and Zone as they get assigned to a single internal
287     * buffer by nbp_name */
288    if ((config->obj.Obj  = strdup(Obj)) == NULL)
289        goto serv_free_return;
290
291    if ((config->obj.Type = strdup(Type)) == NULL) {
292        free(config->obj.Obj);
293        goto serv_free_return;
294    }
295
296    if ((config->obj.Zone = strdup(Zone)) == NULL) {
297        free(config->obj.Obj);
298        free(config->obj.Type);
299        goto serv_free_return;
300    }
301
302    /* make sure we're not registered */
303    nbp_unrgstr(Obj, Type, Zone, &options->ddpaddr);
304    if (nbp_rgstr( atp_sockaddr( atp ), Obj, Type, Zone ) < 0 ) {
305        LOG(log_error, logtype_afpd, "Can't register %s:%s@%s", Obj, Type, Zone );
306        free(config->obj.Obj);
307        free(config->obj.Type);
308        free(config->obj.Zone);
309        goto serv_free_return;
310    }
311
312    LOG(log_info, logtype_afpd, "%s:%s@%s started on %u.%u:%u (%s)", Obj, Type, Zone,
313        ntohs( atp_sockaddr( atp )->sat_addr.s_net ),
314        atp_sockaddr( atp )->sat_addr.s_node,
315        atp_sockaddr( atp )->sat_port, VERSION );
316
317    config->fd = atp_fileno(atp);
318    config->obj.handle = asp;
319    config->obj.config = config;
320    config->obj.proto = AFPPROTO_ASP;
321
322    memcpy(&config->obj.options, options, sizeof(struct afp_options));
323    config->optcount = refcount;
324    (*refcount)++;
325
326    config->server_start = asp_start;
327    config->server_cleanup = asp_cleanup;
328
329    return config;
330
331serv_free_return:
332                    asp_close(asp);
333    free(config);
334    return NULL;
335}
336#endif /* no afp/asp */
337
338
339static AFPConfig *DSIConfigInit(const struct afp_options *options,
340                                unsigned char *refcount,
341                                const dsi_proto protocol)
342{
343    AFPConfig *config;
344    DSI *dsi;
345    char *p, *q;
346
347    if ((config = (AFPConfig *) calloc(1, sizeof(AFPConfig))) == NULL) {
348        LOG(log_error, logtype_afpd, "DSIConfigInit: malloc(config): %s", strerror(errno) );
349        return NULL;
350    }
351
352    LOG(log_debug, logtype_afpd, "DSIConfigInit: hostname: %s, ip/port: %s/%s, ",
353        options->hostname,
354        options->ipaddr ? options->ipaddr : "default",
355        options->port ? options->port : "548");
356
357    if ((dsi = dsi_init(protocol, "afpd", options->hostname,
358                        options->ipaddr, options->port,
359                        options->flags & OPTION_PROXY,
360                        options->server_quantum)) == NULL) {
361        LOG(log_error, logtype_afpd, "main: dsi_init: %s", strerror(errno) );
362        free(config);
363        return NULL;
364    }
365    dsi->dsireadbuf = options->dsireadbuf;
366
367    if (options->flags & OPTION_PROXY) {
368        LOG(log_note, logtype_afpd, "AFP/TCP proxy initialized for %s:%d (%s)",
369            getip_string((struct sockaddr *)&dsi->server), getip_port((struct sockaddr *)&dsi->server), VERSION);
370    } else {
371        LOG(log_note, logtype_afpd, "AFP/TCP started, advertising %s:%d (%s)",
372            getip_string((struct sockaddr *)&dsi->server), getip_port((struct sockaddr *)&dsi->server), VERSION);
373    }
374
375#ifdef USE_SRVLOC
376    dsi->srvloc_url[0] = '\0';	/*  Mark that we haven't registered.  */
377    if (!(options->flags & OPTION_NOSLP)) {
378        SLPError err;
379        SLPError callbackerr;
380        SLPHandle hslp;
381        unsigned int afp_port;
382        int   l;
383        char *srvloc_hostname;
384        const char *hostname;
385
386	err = SLPOpen("en", SLP_FALSE, &hslp);
387	if (err != SLP_OK) {
388	    LOG(log_error, logtype_afpd, "DSIConfigInit: Error opening SRVLOC handle");
389	    goto srvloc_reg_err;
390	}
391
392	/* XXX We don't want to tack on the port number if we don't have to.
393	 * Why?
394	 * Well, this seems to break MacOS < 10.  If the user _really_ wants to
395	 * use a non-default port, they can, but be aware, this server might
396	 * not show up int the Network Browser.
397	 */
398	afp_port = getip_port((struct sockaddr *)&dsi->server);
399	/* If specified use the FQDN to register with srvloc, otherwise use IP. */
400	p = NULL;
401	if (options->fqdn) {
402	    hostname = options->fqdn;
403	    p = strchr(hostname, ':');
404	}
405	else
406	    hostname = getip_string((struct sockaddr *)&dsi->server);
407
408	srvloc_hostname = srvloc_encode(options, (options->server ? options->server : options->hostname));
409
410	if ((p) || afp_port == 548) {
411	    l = snprintf(dsi->srvloc_url, sizeof(dsi->srvloc_url), "afp://%s/?NAME=%s", hostname, srvloc_hostname);
412	}
413	else {
414	    l = snprintf(dsi->srvloc_url, sizeof(dsi->srvloc_url), "afp://%s:%d/?NAME=%s", hostname, afp_port, srvloc_hostname);
415	}
416
417	if (l == -1 || l >= (int)sizeof(dsi->srvloc_url)) {
418	    LOG(log_error, logtype_afpd, "DSIConfigInit: Hostname is too long for SRVLOC");
419	    dsi->srvloc_url[0] = '\0';
420	    goto srvloc_reg_err;
421	}
422
423	err = SLPReg(hslp,
424		     dsi->srvloc_url,
425		     SLP_LIFETIME_MAXIMUM,
426		     "afp",
427		     "",
428		     SLP_TRUE,
429		     SRVLOC_callback,
430		     &callbackerr);
431	if (err != SLP_OK) {
432	    LOG(log_error, logtype_afpd, "DSIConfigInit: Error registering %s with SRVLOC", dsi->srvloc_url);
433	    dsi->srvloc_url[0] = '\0';
434	    goto srvloc_reg_err;
435	}
436
437	if (callbackerr != SLP_OK) {
438	    LOG(log_error, logtype_afpd, "DSIConfigInit: Error in callback trying to register %s with SRVLOC", dsi->srvloc_url);
439	    dsi->srvloc_url[0] = '\0';
440	    goto srvloc_reg_err;
441	}
442
443	LOG(log_info, logtype_afpd, "Sucessfully registered %s with SRVLOC", dsi->srvloc_url);
444	config->server_cleanup = dsi_cleanup;
445
446srvloc_reg_err:
447	SLPClose(hslp);
448    }
449#endif /* USE_SRVLOC */
450
451    config->fd = dsi->serversock;
452    config->obj.handle = dsi;
453    config->obj.config = config;
454    config->obj.proto = AFPPROTO_DSI;
455
456    memcpy(&config->obj.options, options, sizeof(struct afp_options));
457    /* get rid of any appletalk info. we use the fact that the DSI
458     * stuff is done after the ASP stuff. */
459    p = config->obj.options.server;
460    if (p && (q = strchr(p, ':')))
461        *q = '\0';
462
463    config->optcount = refcount;
464    (*refcount)++;
465
466    config->server_start = dsi_start;
467    return config;
468}
469
470/* allocate server configurations. this should really store the last
471 * entry in config->last or something like that. that would make
472 * supporting multiple dsi transports easier. */
473static AFPConfig *AFPConfigInit(struct afp_options *options,
474                                const struct afp_options *defoptions)
475{
476    AFPConfig *config = NULL, *next = NULL;
477    unsigned char *refcount;
478
479    if ((refcount = (unsigned char *)
480                    calloc(1, sizeof(unsigned char))) == NULL) {
481        LOG(log_error, logtype_afpd, "AFPConfigInit: calloc(refcount): %s", strerror(errno) );
482        return NULL;
483    }
484
485#ifndef NO_DDP
486    /* handle asp transports */
487    if ((options->transports & AFPTRANS_DDP) &&
488            (config = ASPConfigInit(options, refcount)))
489        config->defoptions = defoptions;
490#endif /* NO_DDP */
491
492
493    /* set signature */
494    set_signature(options);
495
496    /* handle dsi transports and dsi proxies. we only proxy
497     * for DSI connections. */
498
499    /* this should have something like the following:
500     * for (i=mindsi; i < maxdsi; i++)
501     *   if (options->transports & (1 << i) &&
502     *     (next = DSIConfigInit(options, refcount, i)))
503     *     next->defoptions = defoptions;
504     */
505    if ((options->transports & AFPTRANS_TCP) &&
506            (((options->flags & OPTION_PROXY) == 0) ||
507             ((options->flags & OPTION_PROXY) && config))
508            && (next = DSIConfigInit(options, refcount, DSI_TCPIP)))
509        next->defoptions = defoptions;
510
511    /* load in all the authentication modules. we can load the same
512       things multiple times if necessary. however, loading different
513       things with the same names will cause complaints. by not loading
514       in any uams with proxies, we prevent ddp connections from succeeding.
515    */
516    auth_load(options->uampath, options->uamlist);
517
518    /* this should be able to accept multiple dsi transports. i think
519     * the only thing that gets affected is the net addresses. */
520    status_init(config, next, options);
521
522    /* attach dsi config to tail of asp config */
523    if (config) {
524        config->next = next;
525        return config;
526    }
527
528    return next;
529}
530
531/* fill in the appropriate bits for each interface */
532AFPConfig *configinit(struct afp_options *cmdline)
533{
534    FILE *fp;
535    char buf[LINESIZE + 1], *p, have_option = 0;
536    size_t len;
537    struct afp_options options;
538    AFPConfig *config=NULL, *first = NULL;
539
540    /* if config file doesn't exist, load defaults */
541    if ((fp = fopen(cmdline->configfile, "r")) == NULL)
542    {
543        LOG(log_debug, logtype_afpd, "ConfigFile %s not found, assuming defaults",
544            cmdline->configfile);
545        return AFPConfigInit(cmdline, cmdline);
546    }
547
548    /* scan in the configuration file */
549    len = 0;
550    while (!feof(fp)) {
551	if (!fgets(&buf[len], LINESIZE - len, fp) || buf[len] == '#')
552            continue;
553	len = strlen(buf);
554	if ( len >= 2 && buf[len-2] == '\\' ) {
555	    len -= 2;
556	    continue;
557	} else
558	    len = 0;
559
560        /* a little pre-processing to get rid of spaces and end-of-lines */
561        p = buf;
562        while (p && isspace(*p))
563            p++;
564        if (!p || (*p == '\0'))
565            continue;
566
567        have_option = 1;
568
569        memcpy(&options, cmdline, sizeof(options));
570        if (!afp_options_parseline(p, &options))
571            continue;
572
573        /* AFPConfigInit can return two linked configs due to DSI and ASP */
574        if (!first) {
575            if ((first = AFPConfigInit(&options, cmdline)))
576                config = first->next ? first->next : first;
577        } else if ((config->next = AFPConfigInit(&options, cmdline))) {
578            config = config->next->next ? config->next->next : config->next;
579        }
580    }
581
582#ifdef HAVE_LDAP
583    /* Parse afp_ldap.conf */
584    acl_ldap_readconfig(_PATH_ACL_LDAPCONF);
585#endif /* HAVE_LDAP */
586
587    LOG(log_debug, logtype_afpd, "Finished parsing Config File");
588    fclose(fp);
589
590    if (!have_option)
591        first = AFPConfigInit(cmdline, cmdline);
592
593    /* Now register with zeroconf, we also need the volumes for that */
594    if (! (first->obj.options.flags & OPTION_NOZEROCONF)) {
595        load_volumes(&first->obj);
596        zeroconf_register(first);
597    }
598
599    return first;
600}
601