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