1 /*
2 Copyright (c) 2012 Frank Lahm <franklahm@gmail.com>
3
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; either version 2 of the License, or
7 (at your option) any later version.
8
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
13 */
14
15 #ifdef HAVE_CONFIG_H
16 #include "config.h"
17 #endif /* HAVE_CONFIG_H */
18
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <ctype.h>
22 #include <pwd.h>
23 #include <grp.h>
24 #include <utime.h>
25 #include <errno.h>
26 #include <string.h>
27 #include <sys/param.h>
28 #include <sys/socket.h>
29 #include <netinet/in.h>
30 #include <arpa/inet.h>
31 #include <inttypes.h>
32 #include <time.h>
33 #include <regex.h>
34 #if HAVE_LOCALE_H
35 #include <locale.h>
36 #endif
37 #if HAVE_LANGINFO_H
38 #include <langinfo.h>
39 #endif
40
41 #include <atalk/afp.h>
42 #include <atalk/util.h>
43 #include <atalk/logger.h>
44 #include <atalk/ea.h>
45 #include <atalk/globals.h>
46 #include <atalk/errchk.h>
47 #include <atalk/iniparser.h>
48 #include <atalk/unix.h>
49 #include <atalk/cnid.h>
50 #include <atalk/dsi.h>
51 #include <atalk/uuid.h>
52 #include <atalk/netatalk_conf.h>
53 #include <atalk/bstrlib.h>
54 #include <atalk/bstradd.h>
55
56 #define VOLPASSLEN 8
57 #ifndef UUID_PRINTABLE_STRING_LENGTH
58 #define UUID_PRINTABLE_STRING_LENGTH 37
59 #endif
60
61 #define IS_VAR(a, b) (strncmp((a), (b), 2) == 0)
62
63 /**************************************************************
64 * Locals
65 **************************************************************/
66
67 static int have_uservol = 0; /* whether there's generic user home share in config ("~" or "~/path", but not "~user") */
68 static struct vol *Volumes = NULL;
69 static uint16_t lastvid = 0;
70
71 /*
72 * Get a volumes UUID from the config file.
73 * If there is none, it is generated and stored there.
74 *
75 * Returns pointer to allocated storage on success, NULL on error.
76 */
get_vol_uuid(const AFPObj * obj,const char * volname)77 static char *get_vol_uuid(const AFPObj *obj, const char *volname)
78 {
79 char *volname_conf;
80 char buf[1024], uuid[UUID_PRINTABLE_STRING_LENGTH], *p;
81 FILE *fp;
82 struct stat tmpstat;
83 int fd;
84
85 if ((fp = fopen(obj->options.uuidconf, "r")) != NULL) { /* read open? */
86 /* scan in the conf file */
87 while (fgets(buf, sizeof(buf), fp) != NULL) {
88 p = buf;
89 while (p && isblank(*p))
90 p++;
91 if (!p || (*p == '#') || (*p == '\n'))
92 continue; /* invalid line */
93 if (*p == '"') {
94 p++;
95 if ((volname_conf = strtok( p, "\"" )) == NULL)
96 continue; /* syntax error */
97 } else {
98 if ((volname_conf = strtok( p, " \t" )) == NULL)
99 continue; /* syntax error: invalid name */
100 }
101 p = strchr(p, '\0');
102 p++;
103 if (*p == '\0')
104 continue; /* syntax error */
105
106 if (strcmp(volname, volname_conf) != 0)
107 continue; /* another volume name */
108
109 while (p && isblank(*p))
110 p++;
111
112 if (sscanf(p, "%36s", uuid) == 1 ) {
113 for (int i=0; uuid[i]; i++)
114 uuid[i] = toupper(uuid[i]);
115 LOG(log_debug, logtype_afpd, "get_uuid('%s'): UUID: '%s'", volname, uuid);
116 fclose(fp);
117 return strdup(uuid);
118 }
119 }
120 }
121
122 if (fp)
123 fclose(fp);
124
125 /* not found or no file, reopen in append mode */
126
127 if (stat(obj->options.uuidconf, &tmpstat)) { /* no file */
128 if (( fd = creat(obj->options.uuidconf, 0644 )) < 0 ) {
129 LOG(log_error, logtype_afpd, "ERROR: Cannot create %s (%s).",
130 obj->options.uuidconf, strerror(errno));
131 return NULL;
132 }
133 if (( fp = fdopen( fd, "w" )) == NULL ) {
134 LOG(log_error, logtype_afpd, "ERROR: Cannot fdopen %s (%s).",
135 obj->options.uuidconf, strerror(errno));
136 close(fd);
137 return NULL;
138 }
139 } else if ((fp = fopen(obj->options.uuidconf, "a+")) == NULL) { /* not found */
140 LOG(log_error, logtype_afpd, "Cannot create or append to %s (%s).",
141 obj->options.uuidconf, strerror(errno));
142 return NULL;
143 }
144 fseek(fp, 0L, SEEK_END);
145 if(ftell(fp) == 0) { /* size = 0 */
146 fprintf(fp, "# DON'T TOUCH NOR COPY THOUGHTLESSLY!\n");
147 fprintf(fp, "# This file is auto-generated by afpd\n");
148 fprintf(fp, "# and stores UUIDs for Time Machine volumes.\n\n");
149 } else {
150 fseek(fp, -1L, SEEK_END);
151 if(fgetc(fp) != '\n') fputc('\n', fp); /* last char is \n? */
152 }
153
154 /* generate uuid and write to file */
155 atalk_uuid_t id;
156 const char *cp;
157 randombytes((void *)id, 16);
158 cp = uuid_bin2string(id);
159
160 LOG(log_debug, logtype_afpd, "get_uuid('%s'): generated UUID '%s'", volname, cp);
161
162 fprintf(fp, "\"%s\"\t%36s\n", volname, cp);
163 fclose(fp);
164
165 return strdup(cp);
166 }
167
168 /*
169 Check if the underlying filesystem supports EAs.
170 If not, switch to ea:ad.
171 As we can't check (requires write access) on ro-volumes, we switch ea:auto
172 volumes that are options:ro to ea:none.
173 */
174 #define EABUFSZ 4
do_check_ea_support(const struct vol * vol)175 static int do_check_ea_support(const struct vol *vol)
176 {
177 int haseas;
178 const char *eaname = "org.netatalk.has-Extended-Attributes";
179 const char *eacontent = "yes";
180 char buf[EABUFSZ];
181
182 if (sys_lgetxattr(vol->v_path, eaname, buf, EABUFSZ) != -1)
183 return 1;
184
185 if (vol->v_flags & AFPVOL_RO) {
186 LOG(log_debug, logtype_afpd, "read-only volume '%s', can't test for EA support, assuming yes", vol->v_localname);
187 return 1;
188 }
189
190 become_root();
191
192 if ((sys_setxattr(vol->v_path, eaname, eacontent, strlen(eacontent) + 1, 0)) == 0) {
193 haseas = 1;
194 } else {
195 LOG(log_warning, logtype_afpd, "volume \"%s\" does not support Extended Attributes or read-only volume",
196 vol->v_localname);
197 haseas = 0;
198 }
199
200 unbecome_root();
201
202 return haseas;
203 }
204
check_ea_support(struct vol * vol)205 static void check_ea_support(struct vol *vol)
206 {
207 int haseas;
208
209 haseas = do_check_ea_support(vol);
210
211 if (vol->v_vfs_ea == AFPVOL_EA_AUTO) {
212 if (haseas)
213 vol->v_vfs_ea = AFPVOL_EA_SYS;
214 else
215 vol->v_vfs_ea = AFPVOL_EA_NONE;
216 }
217
218 if (vol->v_adouble == AD_VERSION_EA) {
219 if (!haseas)
220 vol->v_adouble = AD_VERSION2;
221 }
222 }
223
224 /*!
225 * Check whether a volume supports ACLs
226 *
227 * @param vol (r) volume
228 *
229 * @returns 0 if not, 1 if yes
230 */
check_vol_acl_support(const struct vol * vol)231 static int check_vol_acl_support(const struct vol *vol)
232 {
233 int ret = 0;
234
235 #ifdef HAVE_NFSV4_ACLS
236 ace_t *aces = NULL;
237 ret = 1;
238 if (get_nfsv4_acl(vol->v_path, &aces) == -1)
239 ret = 0;
240 #endif
241 #ifdef HAVE_POSIX_ACLS
242 acl_t acl = NULL;
243 ret = 1;
244 if ((acl = acl_get_file(vol->v_path, ACL_TYPE_ACCESS)) == NULL)
245 ret = 0;
246 #endif
247
248 #ifdef HAVE_NFSV4_ACLS
249 if (aces) free(aces);
250 #endif
251 #ifdef HAVE_POSIX_ACLS
252 if (acl) acl_free(acl);
253 #endif /* HAVE_POSIX_ACLS */
254
255 LOG(log_debug, logtype_afpd, "Volume \"%s\" ACL support: %s",
256 vol->v_path, ret ? "yes" : "no");
257 return ret;
258 }
259
260 /*
261 * Handle variable substitutions. here's what we understand:
262 * $b -> basename of path
263 * $c -> client ip/appletalk address
264 * $d -> volume pathname on server
265 * $f -> full name (whatever's in the gecos field)
266 * $g -> group
267 * $h -> hostname
268 * $i -> client ip/appletalk address without port
269 * $s -> server name (hostname if it doesn't exist)
270 * $u -> username (guest is usually nobody)
271 * $v -> volume name or basename if null
272 * $$ -> $
273 *
274 * This get's called from readvolfile with
275 * path = NULL, volname = NULL for xlating the volumes path
276 * path = path, volname = NULL for xlating the volumes name
277 * ... and from volumes options parsing code when xlating eg dbpath with
278 * path = path, volname = volname
279 *
280 * Using this information we can reject xlation of any variable depeninding on a login
281 * context which is not given in the afp master, where we must evaluate this whole stuff
282 * too for the Zeroconf announcements.
283 */
volxlate(const AFPObj * obj,char * dest,size_t destlen,const char * src,const struct passwd * pwd,const char * path,const char * volname)284 static char *volxlate(const AFPObj *obj,
285 char *dest,
286 size_t destlen,
287 const char *src,
288 const struct passwd *pwd,
289 const char *path,
290 const char *volname)
291 {
292 char *p, *r;
293 const char *q;
294 int len;
295 char *ret;
296 int xlatevolname = 0;
297
298 if (path && !volname)
299 /* cf above */
300 xlatevolname = 1;
301
302 if (!src) {
303 return NULL;
304 }
305 if (!dest) {
306 dest = calloc(destlen +1, 1);
307 }
308 ret = dest;
309 if (!ret) {
310 return NULL;
311 }
312 strlcpy(dest, src, destlen +1);
313 if ((p = strchr(src, '$')) == NULL) /* nothing to do */
314 return ret;
315
316 /* first part of the path. just forward to the next variable. */
317 len = MIN((size_t)(p - src), destlen);
318 if (len > 0) {
319 destlen -= len;
320 dest += len;
321 }
322
323 while (p && destlen > 0) {
324 /* now figure out what the variable is */
325 q = NULL;
326 if (IS_VAR(p, "$b")) {
327 if (path) {
328 if ((q = strrchr(path, '/')) == NULL)
329 q = path;
330 else if (*(q + 1) != '\0')
331 q++;
332 }
333 } else if (IS_VAR(p, "$c")) {
334 if (IS_AFP_SESSION(obj)) {
335 DSI *dsi = obj->dsi;
336 len = sprintf(dest, "%s:%u",
337 getip_string((struct sockaddr *)&dsi->client),
338 getip_port((struct sockaddr *)&dsi->client));
339 dest += len;
340 destlen -= len;
341 }
342 } else if (IS_VAR(p, "$d")) {
343 q = path;
344 } else if (pwd && IS_VAR(p, "$f")) {
345 if ((r = strchr(pwd->pw_gecos, ',')))
346 *r = '\0';
347 q = pwd->pw_gecos;
348 } else if (pwd && IS_VAR(p, "$g")) {
349 struct group *grp = getgrgid(pwd->pw_gid);
350 if (grp)
351 q = grp->gr_name;
352 } else if (IS_VAR(p, "$h")) {
353 q = obj->options.hostname;
354 } else if (IS_VAR(p, "$i")) {
355 DSI *dsi = obj->dsi;
356 q = getip_string((struct sockaddr *)&dsi->client);
357 } else if (IS_VAR(p, "$s")) {
358 q = obj->options.hostname;
359 } else if (obj->username[0] && IS_VAR(p, "$u")) {
360 char* sep = NULL;
361 if ( obj->options.ntseparator && (sep = strchr(obj->username, obj->options.ntseparator[0])) != NULL)
362 q = sep+1;
363 else
364 q = obj->username;
365 } else if (IS_VAR(p, "$v")) {
366 if (volname) {
367 q = volname;
368 }
369 else if (path) {
370 if ((q = strrchr(path, '/')) == NULL)
371 q = path;
372 else if (*(q + 1) != '\0')
373 q++;
374 }
375 } else if (IS_VAR(p, "$$")) {
376 q = "$";
377 } else
378 q = p;
379
380 /* copy the stuff over. if we don't understand something that we
381 * should, just skip it over. */
382 if (q) {
383 len = MIN(p == q ? 2 : strlen(q), destlen);
384 strncpy(dest, q, len);
385 dest += len;
386 destlen -= len;
387 }
388
389 /* stuff up to next $ */
390 src = p + 2;
391 p = strchr(src, '$');
392 len = p ? MIN((size_t)(p - src), destlen) : destlen;
393 if (len > 0) {
394 strncpy(dest, src, len);
395 dest += len;
396 destlen -= len;
397 }
398 }
399 return ret;
400 }
401
402 /*!
403 * check access list
404 *
405 * this function wants a string consisting of names seperated by comma
406 * or space. Names may be quoted within a pair of quotes. Groups are
407 * denoted by a leading @ symbol.
408 * Example:
409 * user1 user2, user3, @group1 @group2, @group3 "user name1", "@group name1"
410 * A NULL argument allows everybody to have access.
411 * We return three things:
412 * -1: no list
413 * 0: list exists, but name isn't in it
414 * 1: in list
415 */
accessvol(const AFPObj * obj,const char * args,const char * name)416 static int accessvol(const AFPObj *obj, const char *args, const char *name)
417 {
418 EC_INIT;
419 char *names = NULL, *p;
420 struct group *gr;
421
422 if (!args)
423 EC_EXIT_STATUS(-1);
424
425 EC_NULL_LOG( names = strdup(args) );
426
427 if ((p = strtok_quote(names, ", ")) == NULL) /* nothing, return okay */
428 EC_EXIT_STATUS(-1);
429
430 while (p) {
431 if (*p == '@') { /* it's a group */
432 if ((gr = getgrnam(p + 1)) && gmem(gr->gr_gid, obj->ngroups, obj->groups))
433 EC_EXIT_STATUS(1);
434 } else if (strcasecmp(p, name) == 0) /* it's a user name */
435 EC_EXIT_STATUS(1);
436 p = strtok_quote(NULL, ", ");
437 }
438
439 EC_CLEANUP:
440 if (names)
441 free(names);
442 EC_EXIT;
443 }
444
hostaccessvol(const AFPObj * obj,const char * volname,const char * args)445 static int hostaccessvol(const AFPObj *obj, const char *volname, const char *args)
446 {
447 int mask_int;
448 char buf[MAXPATHLEN + 1], *p, *b;
449 struct sockaddr_storage client;
450 const DSI *dsi = obj->dsi;
451
452 if (!args || !dsi)
453 return -1;
454
455 strlcpy(buf, args, sizeof(buf));
456 if ((p = strtok_r(buf, ", ", &b)) == NULL) /* nothing, return okay */
457 return -1;
458
459 while (p) {
460 int ret;
461 char *ipaddr, *mask_char;
462 struct addrinfo hints, *ai;
463
464 ipaddr = strtok(p, "/");
465 mask_char = strtok(NULL,"/");
466
467 /* Get address from string with getaddrinfo */
468 memset(&hints, 0, sizeof hints);
469 hints.ai_family = AF_UNSPEC;
470 hints.ai_socktype = SOCK_STREAM;
471 if ((ret = getaddrinfo(ipaddr, NULL, &hints, &ai)) != 0) {
472 LOG(log_error, logtype_afpd, "hostaccessvol: getaddrinfo: %s\n", gai_strerror(ret));
473 continue;
474 }
475
476 /* netmask */
477 if (mask_char != NULL)
478 mask_int = atoi(mask_char); /* apply_ip_mask does range checking on it */
479 else {
480 if (ai->ai_family == AF_INET) /* IPv4 */
481 mask_int = 32;
482 else /* IPv6 */
483 mask_int = 128;
484 }
485
486 /* Apply mask to addresses */
487 client = dsi->client;
488 apply_ip_mask((struct sockaddr *)&client, mask_int);
489 apply_ip_mask(ai->ai_addr, mask_int);
490
491 if (compare_ip((struct sockaddr *)&client, ai->ai_addr) == 0) {
492 freeaddrinfo(ai);
493 return 1;
494 }
495
496 /* next address */
497 freeaddrinfo(ai);
498 p = strtok_r(NULL, ", ", &b);
499 }
500
501 return 0;
502 }
503
504 /*!
505 * Get option string from config, use default value if not set
506 *
507 * @param conf (r) config handle
508 * @param vol (r) volume name (must be section name ie wo vars expanded)
509 * @param opt (r) option
510 * @param defsec (r) if "option" is not found in "vol", try to find it in section "defsec"
511 * @param defval (r) if neither "vol" nor "defsec" contain "opt" return "defval"
512 *
513 * @returns const option string from "vol" or "defsec", or "defval" if not found
514 */
getoption(const dictionary * conf,const char * vol,const char * opt,const char * defsec,const char * defval)515 static const char *getoption(const dictionary *conf, const char *vol, const char *opt, const char *defsec, const char *defval)
516 {
517 const char *result;
518
519 if ((!(result = atalk_iniparser_getstring(conf, vol, opt, NULL))) && (defsec != NULL))
520 result = atalk_iniparser_getstring(conf, defsec, opt, NULL);
521
522 if (result == NULL)
523 result = defval;
524 return result;
525 }
526
527 /*!
528 * Get boolean option from config, use default value if not set
529 *
530 * @param conf (r) config handle
531 * @param vol (r) volume name (must be section name ie wo vars expanded)
532 * @param opt (r) option
533 * @param defsec (r) if "option" is not found in "vol", try to find it in section "defsec"
534 * @param defval (r) if neither "vol" nor "defsec" contain "opt" return "defval"
535 *
536 * @returns const option string from "vol" or "defsec", or "defval" if not found
537 */
getoption_bool(const dictionary * conf,const char * vol,const char * opt,const char * defsec,int defval)538 static int getoption_bool(const dictionary *conf, const char *vol, const char *opt, const char *defsec, int defval)
539 {
540 int result;
541
542 if (((result = atalk_iniparser_getboolean(conf, vol, opt, -1)) == -1) && (defsec != NULL))
543 result = atalk_iniparser_getboolean(conf, defsec, opt, -1);
544
545 if (result == -1)
546 result = defval;
547 return result;
548 }
549
550 /*!
551 * Get boolean option from volume, default section or global - use default value if not set
552 *
553 * Order of precedence: volume -> default section -> global -> default value
554 *
555 * "vdg" means volume, default section or global
556 *
557 * @param conf (r) config handle
558 * @param vol (r) volume name (must be section name ie wo vars expanded)
559 * @param opt (r) option
560 * @param defsec (r) if "option" is not found in "vol", try to find it in section "defsec"
561 * @param defval (r) if neither "vol" nor "defsec" contain "opt" return "defval"
562 *
563 * @returns const option string from "vol" or "defsec", or "defval" if not found
564 */
vdgoption_bool(const dictionary * conf,const char * vol,const char * opt,const char * defsec,int defval)565 static int vdgoption_bool(const dictionary *conf, const char *vol, const char *opt, const char *defsec, int defval)
566 {
567 int result;
568
569 result = atalk_iniparser_getboolean(conf, vol, opt, -1);
570
571 if ((result == -1) && (defsec != NULL))
572 result = atalk_iniparser_getboolean(conf, defsec, opt, -1);
573
574 if (result == -1)
575 result = atalk_iniparser_getboolean(conf, INISEC_GLOBAL, opt, defval);
576
577 return result;
578 }
579
580 /*!
581 * Create volume struct
582 *
583 * @param obj (r) handle
584 * @param pwd (r) struct passwd of logged in user, may be NULL in master afpd
585 * @param section (r) volume name wo variables expanded (exactly as in iniconfig)
586 * @param name (r) volume name
587 * @param path_in (r) volume path
588 * @param preset (r) default preset, may be NULL
589 * @returns vol on success, NULL on error
590 */
creatvol(AFPObj * obj,const struct passwd * pwd,const char * section,const char * name,const char * path_in,const char * preset)591 static struct vol *creatvol(AFPObj *obj,
592 const struct passwd *pwd,
593 const char *section,
594 const char *name,
595 const char *path_in,
596 const char *preset)
597 {
598 EC_INIT;
599 struct vol *volume = NULL;
600 int i, suffixlen, vlen, tmpvlen, u8mvlen, macvlen;
601 char tmpname[AFPVOL_U8MNAMELEN+1];
602 char path[MAXPATHLEN + 1];
603 ucs2_t u8mtmpname[(AFPVOL_U8MNAMELEN+1)*2], mactmpname[(AFPVOL_MACNAMELEN+1)*2];
604 char suffix[6]; /* max is #FFFF */
605 uint16_t flags;
606 const char *val;
607 char *p, *q;
608 bstring dbpath = NULL;
609 bstring global_path_tmp = NULL;
610
611 strlcpy(path, path_in, MAXPATHLEN);
612
613 LOG(log_debug, logtype_afpd, "creatvol(volume: '%s', path: \"%s\", preset: '%s'): BEGIN",
614 name, path, preset ? preset : "-");
615
616 if ( name == NULL || *name == '\0' ) {
617 if ((name = strrchr( path, '/' )) == NULL)
618 EC_FAIL;
619 /* if you wish to share /, you need to specify a name. */
620 if (*++name == '\0')
621 EC_FAIL;
622 }
623
624 /* Once volumes are loaded, we never change options again, we just delete em when they're removed from afp.conf */
625
626 for (struct vol *vol = Volumes; vol; vol = vol->v_next) {
627 if (STRCMP(name, ==, vol->v_localname) && vol->v_deleted) {
628 /*
629 * reloading config, volume still present, nothing else to do,
630 * we don't change options for volumes once they're loaded
631 */
632 vol->v_deleted = 0;
633 volume = vol;
634 EC_EXIT_STATUS(0);
635 }
636 if (STRCMP(path, ==, vol->v_path)) {
637 LOG(log_note, logtype_afpd, "volume \"%s\" path \"%s\" is the same as volumes \"%s\" path",
638 name, path, vol->v_configname);
639 EC_EXIT_STATUS(0);
640 }
641 /*
642 * We could check for nested volume paths here, but
643 * nobody was able to come up with an implementation yet,
644 * that is simple, fast and correct.
645 */
646 }
647
648 /*
649 * Check allow/deny lists:
650 * allow -> either no list (-1), or in list (1)
651 * deny -> either no list (-1), or not in list (0)
652 */
653 if (pwd) {
654 if (accessvol(obj, getoption(obj->iniconfig, section, "invalid users", preset, NULL), pwd->pw_name) == 1)
655 goto EC_CLEANUP;
656 if (accessvol(obj, getoption(obj->iniconfig, section, "valid users", preset, NULL), pwd->pw_name) == 0)
657 goto EC_CLEANUP;
658 if (hostaccessvol(obj, section, getoption(obj->iniconfig, section, "hosts deny", preset, NULL)) == 1)
659 goto EC_CLEANUP;
660 if (hostaccessvol(obj, section, getoption(obj->iniconfig, section, "hosts allow", preset, NULL)) == 0)
661 goto EC_CLEANUP;
662 }
663
664 EC_NULL( volume = calloc(1, sizeof(struct vol)) );
665
666 EC_NULL( volume->v_configname = strdup(section));
667
668 volume->v_vfs_ea = AFPVOL_EA_AUTO;
669 volume->v_umask = obj->options.umask;
670
671 if ((val = getoption(obj->iniconfig, section, "password", preset, NULL)))
672 EC_NULL( volume->v_password = strdup(val) );
673
674 if ((val = getoption(obj->iniconfig, section, "veto files", preset, NULL)))
675 EC_NULL( volume->v_veto = strdup(val) );
676
677 /* vol charset is in [G] and [V] */
678 if ((val = getoption(obj->iniconfig, section, "vol charset", preset, NULL))) {
679 if (strcasecmp(val, "UTF-8") == 0) {
680 val = strdup("UTF8");
681 }
682 EC_NULL( volume->v_volcodepage = strdup(val) );
683 }
684 else
685 EC_NULL( volume->v_volcodepage = strdup(obj->options.volcodepage) );
686
687 /* mac charset is in [G] and [V] */
688 if ((val = getoption(obj->iniconfig, section, "mac charset", preset, NULL))) {
689 if (strncasecmp(val, "MAC", 3) != 0) {
690 LOG(log_warning, logtype_afpd, "Is '%s' really mac charset? ", val);
691 }
692 EC_NULL( volume->v_maccodepage = strdup(val) );
693 }
694 else
695 EC_NULL( volume->v_maccodepage = strdup(obj->options.maccodepage) );
696
697 vlen = strlen(name);
698 strlcpy(tmpname, name, sizeof(tmpname));
699 for(i = 0; i < vlen; i++)
700 if(tmpname[i] == '/') tmpname[i] = ':';
701
702
703 if (atalk_iniparser_getboolean(obj->iniconfig, INISEC_GLOBAL, "vol dbnest", 0)) {
704 EC_NULL( volume->v_dbpath = strdup(path) );
705 } else {
706 char *global_path;
707 val = getoption(obj->iniconfig, section, "vol dbpath", preset, NULL);
708 if (val == NULL) {
709 /* check global option */
710 global_path = atalk_iniparser_getstring(obj->iniconfig,
711 INISEC_GLOBAL,
712 "vol dbpath",
713 NULL);
714 if (global_path) {
715 /* check for pre 3.1.1 behaviour without variable */
716 if (strchr(global_path, '$') == NULL) {
717 global_path_tmp = bformat("%s/%s/", global_path, tmpname);
718 val = cfrombstr(global_path_tmp);
719 } else {
720 val = global_path;
721 }
722 }
723 }
724
725 if (val == NULL) {
726 EC_NULL( dbpath = bformat("%s/%s/", _PATH_STATEDIR "CNID/", tmpname) );
727 } else {
728 EC_NULL( dbpath = bfromcstr(val));
729 }
730 EC_NULL( volume->v_dbpath = volxlate(obj, NULL, MAXPATHLEN + 1,
731 cfrombstr(dbpath), pwd, NULL, tmpname) );
732 }
733
734 if ((val = getoption(obj->iniconfig, section, "cnid scheme", preset, NULL)))
735 EC_NULL( volume->v_cnidscheme = strdup(val) );
736 else
737 volume->v_cnidscheme = strdup(DEFAULT_CNID_SCHEME);
738
739 if ((val = getoption(obj->iniconfig, section, "umask", preset, NULL)))
740 volume->v_umask = (int)strtol(val, NULL, 8);
741
742 if ((val = getoption(obj->iniconfig, section, "directory perm", preset, NULL)))
743 volume->v_dperm = (int)strtol(val, NULL, 8);
744
745 if ((val = getoption(obj->iniconfig, section, "file perm", preset, NULL)))
746 volume->v_fperm = (int)strtol(val, NULL, 8);
747
748 if ((val = getoption(obj->iniconfig, section, "vol size limit", preset, NULL)))
749 volume->v_limitsize = (uint32_t)strtoul(val, NULL, 10);
750
751 if ((val = getoption(obj->iniconfig, section, "preexec", preset, NULL)))
752 EC_NULL( volume->v_preexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
753
754 if ((val = getoption(obj->iniconfig, section, "postexec", preset, NULL)))
755 EC_NULL( volume->v_postexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
756
757 if ((val = getoption(obj->iniconfig, section, "root preexec", preset, NULL)))
758 EC_NULL( volume->v_root_preexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
759
760 if ((val = getoption(obj->iniconfig, section, "root postexec", preset, NULL)))
761 EC_NULL( volume->v_root_postexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
762
763 if ((val = getoption(obj->iniconfig, section, "appledouble", preset, NULL))) {
764 if (strcmp(val, "v2") == 0)
765 volume->v_adouble = AD_VERSION2;
766 else if (strcmp(val, "ea") == 0)
767 volume->v_adouble = AD_VERSION_EA;
768 } else {
769 volume->v_adouble = AD_VERSION;
770 }
771
772 if ((val = getoption(obj->iniconfig, section, "cnid server", preset, NULL))) {
773 EC_NULL( p = strdup(val) );
774 volume->v_cnidserver = p;
775 if ((q = strrchr(val, ':'))) {
776 *q++ = 0;
777 volume->v_cnidport = strdup(q);
778 } else {
779 volume->v_cnidport = strdup("4700");
780 }
781
782 } else {
783 volume->v_cnidserver = strdup(obj->options.Cnid_srv);
784 volume->v_cnidport = strdup(obj->options.Cnid_port);
785 }
786
787 if ((val = getoption(obj->iniconfig, section, "ea", preset, NULL))) {
788 if (strcasecmp(val, "ad") == 0)
789 volume->v_vfs_ea = AFPVOL_EA_AD;
790 else if (strcasecmp(val, "sys") == 0)
791 volume->v_vfs_ea = AFPVOL_EA_SYS;
792 else if (strcasecmp(val, "none") == 0)
793 volume->v_vfs_ea = AFPVOL_EA_NONE;
794 else if (strcasecmp(val, "samba") == 0) {
795 volume->v_vfs_ea = AFPVOL_EA_SYS;
796 volume->v_flags |= AFPVOL_EA_SAMBA;
797 }
798 }
799
800 if ((val = getoption(obj->iniconfig, section, "casefold", preset, NULL))) {
801 if (strcasecmp(val, "tolower") == 0)
802 volume->v_casefold = AFPVOL_UMLOWER;
803 else if (strcasecmp(val, "toupper") == 0)
804 volume->v_casefold = AFPVOL_UMUPPER;
805 else if (strcasecmp(val, "xlatelower") == 0)
806 volume->v_casefold = AFPVOL_UUPPERMLOWER;
807 else if (strcasecmp(val, "xlateupper") == 0)
808 volume->v_casefold = AFPVOL_ULOWERMUPPER;
809 }
810 if (getoption_bool(obj->iniconfig, section, "case sensitive", preset, 1))
811 volume->v_casefold |= AFPVOL_CASESENS;
812
813 if (getoption_bool(obj->iniconfig, section, "read only", preset, 0))
814 volume->v_flags |= AFPVOL_RO;
815 if (getoption_bool(obj->iniconfig, section, "invisible dots", preset, 0))
816 volume->v_flags |= AFPVOL_INV_DOTS;
817 if (!getoption_bool(obj->iniconfig, section, "stat vol", preset, 1))
818 volume->v_flags |= AFPVOL_NOSTAT;
819 if (getoption_bool(obj->iniconfig, section, "unix priv", preset, 1))
820 volume->v_flags |= AFPVOL_UNIX_PRIV;
821 if (!getoption_bool(obj->iniconfig, section, "cnid dev", preset, 1))
822 volume->v_flags |= AFPVOL_NODEV;
823 if (getoption_bool(obj->iniconfig, section, "illegal seq", preset, 0))
824 volume->v_flags |= AFPVOL_EILSEQ;
825 if (getoption_bool(obj->iniconfig, section, "time machine", preset, 0))
826 volume->v_flags |= AFPVOL_TM;
827 if (getoption_bool(obj->iniconfig, section, "search db", preset, 0))
828 volume->v_flags |= AFPVOL_SEARCHDB;
829 if (!getoption_bool(obj->iniconfig, section, "network ids", preset, 1))
830 volume->v_flags |= AFPVOL_NONETIDS;
831 #ifdef HAVE_ACLS
832 if (getoption_bool(obj->iniconfig, section, "acls", preset, 1))
833 volume->v_flags |= AFPVOL_ACLS;
834 #endif
835 if (!getoption_bool(obj->iniconfig, section, "convert appledouble", preset, 1))
836 volume->v_flags |= AFPVOL_NOV2TOEACONV;
837 if (getoption_bool(obj->iniconfig, section, "follow symlinks", preset, 0))
838 volume->v_flags |= AFPVOL_FOLLOWSYM;
839 if (getoption_bool(obj->iniconfig, section, "spotlight", preset, obj->options.flags & OPTION_SPOTLIGHT_VOL)) {
840 volume->v_flags |= AFPVOL_SPOTLIGHT;
841 obj->options.flags |= OPTION_SPOTLIGHT;
842 }
843 if (getoption_bool(obj->iniconfig, section, "delete veto files", preset, 0))
844 volume->v_flags |= AFPVOL_DELVETO;
845
846 if (getoption_bool(obj->iniconfig, section, "preexec close", preset, 0))
847 volume->v_preexec_close = 1;
848 if (getoption_bool(obj->iniconfig, section, "root preexec close", preset, 0))
849 volume->v_root_preexec_close = 1;
850 if (vdgoption_bool(obj->iniconfig, section, "force xattr with sticky bit", preset, 0))
851 volume->v_flags |= AFPVOL_FORCE_STICKY_XATTR;
852
853 if ((val = getoption(obj->iniconfig, section, "ignored attributes", preset, obj->options.ignored_attr))) {
854 if (strstr(val, "all")) {
855 volume->v_ignattr |= ATTRBIT_NOWRITE | ATTRBIT_NORENAME | ATTRBIT_NODELETE;
856 }
857 if (strstr(val, "nowrite")) {
858 volume->v_ignattr |= ATTRBIT_NOWRITE;
859 }
860 if (strstr(val, "norename")) {
861 volume->v_ignattr |= ATTRBIT_NORENAME;
862 }
863 if (strstr(val, "nodelete")) {
864 volume->v_ignattr |= ATTRBIT_NODELETE;
865 }
866 }
867
868 val = getoption(obj->iniconfig, section, "chmod request", preset, NULL);
869 if (val == NULL) {
870 val = atalk_iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "chmod request", "preserve");
871 }
872 if (strcasecmp(val, "ignore") == 0) {
873 volume->v_flags |= AFPVOL_CHMOD_IGNORE;
874 } else if (strcasecmp(val, "preserve") == 0) {
875 volume->v_flags |= AFPVOL_CHMOD_PRESERVE_ACL;
876 } else if (strcasecmp(val, "simple") != 0) {
877 LOG(log_warning, logtype_afpd, "unknown 'chmod request' setting: '%s', using default", val);
878 volume->v_flags |= AFPVOL_CHMOD_PRESERVE_ACL;
879 }
880
881 /*
882 * Handle read-only behaviour. semantics:
883 * 1) neither the rolist nor the rwlist exist -> rw
884 * 2) rolist exists -> ro if user is in it.
885 * 3) rwlist exists -> ro unless user is in it.
886 * 4) cnid scheme = last -> ro forcibly.
887 */
888 if (pwd) {
889 if (accessvol(obj, getoption(obj->iniconfig, section, "rolist", preset, NULL), pwd->pw_name) == 1
890 || accessvol(obj, getoption(obj->iniconfig, section, "rwlist", preset, NULL), pwd->pw_name) == 0)
891 volume->v_flags |= AFPVOL_RO;
892 }
893 if (0 == strcmp(volume->v_cnidscheme, "last"))
894 volume->v_flags |= AFPVOL_RO;
895
896 if ((volume->v_flags & AFPVOL_NODEV))
897 volume->v_ad_options |= ADVOL_NODEV;
898 if ((volume->v_flags & AFPVOL_UNIX_PRIV))
899 volume->v_ad_options |= ADVOL_UNIXPRIV;
900 if ((volume->v_flags & AFPVOL_INV_DOTS))
901 volume->v_ad_options |= ADVOL_INVDOTS;
902 if ((volume->v_flags & AFPVOL_FOLLOWSYM))
903 volume->v_ad_options |= ADVOL_FOLLO_SYML;
904 if ((volume->v_flags & AFPVOL_RO))
905 volume->v_ad_options |= ADVOL_RO;
906 if ((volume->v_flags & AFPVOL_FORCE_STICKY_XATTR))
907 volume->v_ad_options |= ADVOL_FORCE_STICKY_XATTR;
908
909 /* Mac to Unix conversion flags*/
910 if ((volume->v_flags & AFPVOL_EILSEQ))
911 volume->v_mtou_flags |= CONV__EILSEQ;
912
913 if ((volume->v_casefold & AFPVOL_MTOUUPPER))
914 volume->v_mtou_flags |= CONV_TOUPPER;
915 else if ((volume->v_casefold & AFPVOL_MTOULOWER))
916 volume->v_mtou_flags |= CONV_TOLOWER;
917
918 /* Unix to Mac conversion flags*/
919 volume->v_utom_flags = CONV_IGNORE;
920 if ((volume->v_casefold & AFPVOL_UTOMUPPER))
921 volume->v_utom_flags |= CONV_TOUPPER;
922 else if ((volume->v_casefold & AFPVOL_UTOMLOWER))
923 volume->v_utom_flags |= CONV_TOLOWER;
924 if ((volume->v_flags & AFPVOL_EILSEQ))
925 volume->v_utom_flags |= CONV__EILSEQ;
926
927 /* suffix for mangling use (lastvid + 1) */
928 /* because v_vid has not been decided yet. */
929 if (lastvid == UINT16_MAX) {
930 LOG(log_error, logtype_default, "vid overflow");
931 EC_FAIL;
932 }
933 suffixlen = snprintf(suffix, sizeof(suffix), "#%X", lastvid + 1 );
934 if (suffixlen >= sizeof(suffix)) {
935 LOG(log_error, logtype_default, "vid overflow");
936 EC_FAIL;
937 }
938
939 /* Unicode Volume Name */
940 /* Firstly convert name from unixcharset to UTF8-MAC */
941 flags = CONV_IGNORE;
942 tmpvlen = convert_charset(obj->options.unixcharset, CH_UTF8_MAC, 0, name, vlen, tmpname, AFPVOL_U8MNAMELEN, &flags);
943 if (tmpvlen <= 0) {
944 strcpy(tmpname, "???");
945 tmpvlen = 3;
946 }
947
948 /* Do we have to mangle ? */
949 if ( (flags & CONV_REQMANGLE) || (tmpvlen > obj->options.volnamelen)) {
950 if (tmpvlen + suffixlen > obj->options.volnamelen) {
951 flags = CONV_FORCE;
952 tmpvlen = convert_charset(obj->options.unixcharset, CH_UTF8_MAC, 0, name, vlen, tmpname, obj->options.volnamelen - suffixlen, &flags);
953 tmpname[tmpvlen >= 0 ? tmpvlen : 0] = 0;
954 }
955 strcat(tmpname, suffix);
956 tmpvlen = strlen(tmpname);
957 }
958
959 /* Secondly convert name from UTF8-MAC to UCS2 */
960 if ( 0 >= ( u8mvlen = convert_string(CH_UTF8_MAC, CH_UCS2, tmpname, tmpvlen, u8mtmpname, AFPVOL_U8MNAMELEN*2)) )
961 EC_FAIL;
962
963 LOG(log_maxdebug, logtype_afpd, "creatvol: Volume '%s' -> UTF8-MAC Name: '%s'", name, tmpname);
964
965 /* Maccharset Volume Name */
966 /* Firsty convert name from unixcharset to maccharset */
967 flags = CONV_IGNORE;
968 tmpvlen = convert_charset(obj->options.unixcharset, obj->options.maccharset, 0, name, vlen, tmpname, AFPVOL_U8MNAMELEN, &flags);
969 if (tmpvlen <= 0) {
970 strcpy(tmpname, "???");
971 tmpvlen = 3;
972 }
973
974 /* Do we have to mangle ? */
975 if ( (flags & CONV_REQMANGLE) || (tmpvlen > AFPVOL_MACNAMELEN)) {
976 if (tmpvlen + suffixlen > AFPVOL_MACNAMELEN) {
977 flags = CONV_FORCE;
978 tmpvlen = convert_charset(obj->options.unixcharset,
979 obj->options.maccharset,
980 0,
981 name,
982 vlen,
983 tmpname,
984 AFPVOL_MACNAMELEN - suffixlen,
985 &flags);
986 tmpname[tmpvlen >= 0 ? tmpvlen : 0] = 0;
987 }
988 strcat(tmpname, suffix);
989 tmpvlen = strlen(tmpname);
990 }
991
992 /* Secondly convert name from maccharset to UCS2 */
993 if ( 0 >= ( macvlen = convert_string(obj->options.maccharset,
994 CH_UCS2,
995 tmpname,
996 tmpvlen,
997 mactmpname,
998 AFPVOL_U8MNAMELEN*2)) )
999 EC_FAIL;
1000
1001 LOG(log_maxdebug, logtype_afpd, "creatvol: Volume '%s' -> Longname: '%s'", name, tmpname);
1002
1003 EC_NULL( volume->v_localname = strdup(name) );
1004 EC_NULL( volume->v_u8mname = strdup_w(u8mtmpname) );
1005 EC_NULL( volume->v_macname = strdup_w(mactmpname) );
1006 EC_NULL( volume->v_path = strdup(path) );
1007
1008 volume->v_name = utf8_encoding(obj) ? volume->v_u8mname : volume->v_macname;
1009
1010 #ifdef __svr4__
1011 volume->v_qfd = -1;
1012 #endif /* __svr4__ */
1013
1014 /* os X start at 1 and use network order ie. 1 2 3 */
1015 lastvid++;
1016 if (lastvid == UINT16_MAX) {
1017 LOG(log_error, logtype_default, "creatvol(\"%s\"): exceeded maximum number of volumes",
1018 volume->v_path);
1019 EC_FAIL;
1020 }
1021 volume->v_vid = lastvid;
1022 volume->v_vid = htons(volume->v_vid);
1023
1024 #ifdef HAVE_ACLS
1025 if (!check_vol_acl_support(volume)) {
1026 LOG(log_debug, logtype_afpd, "creatvol(\"%s\"): disabling ACL support", volume->v_path);
1027 volume->v_flags &= ~AFPVOL_ACLS;
1028 obj->options.flags &= ~(OPTION_ACL2MODE | OPTION_ACL2MACCESS);
1029 }
1030 #endif
1031
1032 /* Check EA support on volume */
1033 if (volume->v_vfs_ea == AFPVOL_EA_AUTO || volume->v_adouble == AD_VERSION_EA)
1034 check_ea_support(volume);
1035 initvol_vfs(volume);
1036
1037 /* get/store uuid from file in afpd master*/
1038 become_root();
1039 char *uuid = get_vol_uuid(obj, volume->v_localname);
1040 unbecome_root();
1041 if (!uuid) {
1042 LOG(log_error, logtype_afpd, "Volume '%s': couldn't get UUID",
1043 volume->v_localname);
1044 } else {
1045 volume->v_uuid = uuid;
1046 LOG(log_debug, logtype_afpd, "Volume '%s': UUID '%s'",
1047 volume->v_localname, volume->v_uuid);
1048 }
1049
1050 /* no errors shall happen beyond this point because the cleanup would mess the volume chain up */
1051 volume->v_next = Volumes;
1052 Volumes = volume;
1053 volume->v_obj = obj;
1054
1055 EC_CLEANUP:
1056 LOG(log_debug, logtype_afpd, "creatvol: END: %d", ret);
1057 if (dbpath)
1058 bdestroy(dbpath);
1059 if (global_path_tmp)
1060 bdestroy(global_path_tmp);
1061 if (ret != 0) {
1062 if (volume)
1063 volume_free(volume);
1064 return NULL;
1065 }
1066 return volume;
1067 }
1068
1069 /* ----------------------
1070 */
volfile_changed(AFPObj * obj)1071 static int volfile_changed(AFPObj *obj)
1072 {
1073 struct stat st;
1074 struct afp_options *p = &obj->options;
1075 int result;
1076 const char *includefile;
1077
1078 result = stat(p->configfile, &st);
1079 if (result != 0) {
1080 LOG(log_debug, logtype_afpd, "where is the config file %s ?",
1081 p->configfile);
1082 /*
1083 * We return 1 which means "config file changed". The caller
1084 * will re-read config and fail too which is what we want.
1085 */
1086 return 1;
1087 }
1088
1089 if (st.st_mtime > p->volfile.mtime) {
1090 p->volfile.mtime = st.st_mtime;
1091 return 1;
1092 }
1093
1094 includefile = atalk_iniparser_getstring(obj->iniconfig, INISEC_GLOBAL,
1095 "include", NULL);
1096 if (includefile) {
1097 result = stat(includefile, &st);
1098 if (result != 0) {
1099 LOG(log_debug, logtype_afpd, "where is the include file %s ?",
1100 includefile);
1101 return 1;
1102 }
1103
1104 if (st.st_mtime > p->includefile.mtime) {
1105 p->includefile.mtime = st.st_mtime;
1106 return 1;
1107 }
1108 }
1109
1110 return 0;
1111 }
1112
vol_section(const char * sec)1113 static int vol_section(const char *sec)
1114 {
1115 if (STRCMP(sec, ==, INISEC_GLOBAL))
1116 return 0;
1117 return 1;
1118 }
1119
1120 #define MAXPRESETLEN 100
1121 /*!
1122 * Read volumes from iniconfig and add the volumes contained within to
1123 * the global volume list. This gets called from the forked afpd childs.
1124 * The master now reads this too for Zeroconf announcements.
1125 */
readvolfile(AFPObj * obj,const struct passwd * pwent)1126 static int readvolfile(AFPObj *obj, const struct passwd *pwent)
1127 {
1128 EC_INIT;
1129 static int regexerr = -1;
1130 static regex_t reg;
1131 char *realvolpath;
1132 char volname[AFPVOL_U8MNAMELEN + 1];
1133 char path[MAXPATHLEN + 1], tmp[MAXPATHLEN + 1];
1134 const char *preset, *default_preset, *p, *basedir;
1135 int i;
1136 regmatch_t match[1];
1137
1138 LOG(log_debug, logtype_afpd, "readvolfile: BEGIN");
1139
1140 int secnum = atalk_iniparser_getnsec(obj->iniconfig);
1141 LOG(log_debug, logtype_afpd, "readvolfile: sections: %d", secnum);
1142 const char *secname;
1143
1144 if ((default_preset = atalk_iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "vol preset", NULL))) {
1145 LOG(log_debug, logtype_afpd, "readvolfile: default_preset: %s", default_preset);
1146 }
1147
1148 for (i = 0; i < secnum; i++) {
1149 secname = atalk_iniparser_getsecname(obj->iniconfig, i);
1150
1151 if (!vol_section(secname))
1152 continue;
1153 if (STRCMP(secname, ==, INISEC_HOMES)) {
1154 have_uservol = 1;
1155 if (!IS_AFP_SESSION(obj)
1156 || strcmp(obj->username, obj->options.guest) == 0)
1157 /* not an AFP session, but cnid daemon, dbd or ad util, or guest login */
1158 continue;
1159 if (pwent->pw_dir == NULL || STRCMP("", ==, pwent->pw_dir)) {
1160 LOG(log_debug, logtype_afpd, "readvolfile: pwent->pw_dir: NULL or \"\" - no user home");
1161 continue;
1162 }
1163 LOG(log_debug, logtype_afpd, "readvolfile: pwent->pw_dir: '%s'", pwent->pw_dir);
1164
1165 if ((realpath(pwent->pw_dir, tmp)) == NULL) {
1166 LOG(log_debug, logtype_afpd, "readvolfile: Cannot get realpath '%s' (%s).", pwent->pw_dir, strerror(errno));
1167 continue;
1168 }
1169 LOG(log_debug, logtype_afpd, "readvolfile: realpath pwent->pw_dir: '%s'", tmp);
1170
1171 /* check if user home matches our "basedir regex" */
1172 if ((basedir = atalk_iniparser_getstring(obj->iniconfig, INISEC_HOMES, "basedir regex", NULL)) == NULL) {
1173 LOG(log_error, logtype_afpd, "\"basedir regex =\" must be defined in [Homes] section");
1174 continue;
1175 }
1176 LOG(log_debug, logtype_afpd, "readvolfile: basedir regex: '%s'", basedir);
1177
1178 if (regexerr != 0 && (regexerr = regcomp(®, basedir, REG_EXTENDED)) != 0) {
1179 char errbuf[1024];
1180 regerror(regexerr, ®, errbuf, sizeof(errbuf));
1181 LOG(log_debug, logtype_default, "readvolfile: bad basedir regex: %s", errbuf);
1182 continue;
1183 }
1184
1185 if (regexec(®, tmp, 1, match, 0) == REG_NOMATCH) {
1186 LOG(log_error, logtype_default, "readvolfile: user home \"%s\" doesn't match basedir regex \"%s\"",
1187 tmp, basedir);
1188 continue;
1189 }
1190
1191 if ((p = atalk_iniparser_getstring(obj->iniconfig, INISEC_HOMES, "path", NULL))) {
1192 strlcat(tmp, "/", MAXPATHLEN);
1193 strlcat(tmp, p, MAXPATHLEN);
1194 }
1195 } else {
1196 /* Get path */
1197 if ((p = atalk_iniparser_getstring(obj->iniconfig, secname, "path", NULL)) == NULL)
1198 continue;
1199 strlcpy(tmp, p, MAXPATHLEN);
1200 }
1201
1202 if (volxlate(obj, path, sizeof(path) - 1, tmp, pwent, NULL, NULL) == NULL)
1203 continue;
1204
1205 /* do variable substitution for volume name */
1206 if (STRCMP(secname, ==, INISEC_HOMES)) {
1207 p = atalk_iniparser_getstring(obj->iniconfig, INISEC_HOMES, "home name", "$u's home");
1208 if (strstr(p, "$u") == NULL) {
1209 LOG(log_warning, logtype_afpd, "home name must contain $u.");
1210 p = "$u's home";
1211 }
1212 if (strchr(p, ':') != NULL) {
1213 LOG(log_warning, logtype_afpd, "home name must not contain \":\".");
1214 p = "$u's home";
1215 }
1216 strlcpy(tmp, p, MAXPATHLEN);
1217 } else {
1218 strlcpy(tmp, secname, AFPVOL_U8MNAMELEN);
1219 }
1220 if (volxlate(obj, volname, sizeof(volname) - 1, tmp, pwent, path, NULL) == NULL)
1221 continue;
1222
1223 preset = atalk_iniparser_getstring(obj->iniconfig, secname, "vol preset", NULL);
1224
1225 if ((realvolpath = realpath_safe(path)) == NULL)
1226 continue;
1227
1228 creatvol(obj, pwent, secname, volname, realvolpath, preset ? preset : default_preset ? default_preset : NULL);
1229 free(realvolpath);
1230 }
1231
1232 // EC_CLEANUP:
1233 EC_EXIT;
1234 }
1235
1236 static struct extmap *Extmap = NULL, *Defextmap = NULL;
1237 static int Extmap_cnt;
1238
setextmap(char * ext,char * type,char * creator)1239 static int setextmap(char *ext, char *type, char *creator)
1240 {
1241 EC_INIT;
1242 struct extmap *em;
1243 int cnt;
1244
1245 if (Extmap == NULL) {
1246 EC_NULL_LOG( Extmap = calloc(1, sizeof( struct extmap )) );
1247 }
1248
1249 ext++;
1250
1251 for (em = Extmap, cnt = 0; em->em_ext; em++, cnt++)
1252 if ((strdiacasecmp(em->em_ext, ext)) == 0)
1253 goto EC_CLEANUP;
1254
1255 EC_NULL_LOG( Extmap = realloc(Extmap, sizeof(struct extmap) * (cnt + 2)) );
1256 (Extmap + cnt + 1)->em_ext = NULL;
1257 em = Extmap + cnt;
1258
1259 EC_NULL( em->em_ext = strdup(ext) );
1260
1261 if ( *type == '\0' ) {
1262 memcpy(em->em_type, "\0\0\0\0", sizeof( em->em_type ));
1263 } else {
1264 memcpy(em->em_type, type, sizeof( em->em_type ));
1265 }
1266 if ( *creator == '\0' ) {
1267 memcpy(em->em_creator, "\0\0\0\0", sizeof( em->em_creator ));
1268 } else {
1269 memcpy(em->em_creator, creator, sizeof( em->em_creator ));
1270 }
1271
1272 EC_CLEANUP:
1273 EC_EXIT;
1274 }
1275
1276 /* -------------------------- */
extmap_cmp(const void * map1,const void * map2)1277 static int extmap_cmp(const void *map1, const void *map2)
1278 {
1279 const struct extmap *em1 = map1;
1280 const struct extmap *em2 = map2;
1281 return strdiacasecmp(em1->em_ext, em2->em_ext);
1282 }
1283
sortextmap(void)1284 static void sortextmap( void)
1285 {
1286 struct extmap *em;
1287
1288 Extmap_cnt = 0;
1289 if ((em = Extmap) == NULL) {
1290 return;
1291 }
1292 while (em->em_ext) {
1293 em++;
1294 Extmap_cnt++;
1295 }
1296 if (Extmap_cnt) {
1297 qsort(Extmap, Extmap_cnt, sizeof(struct extmap), extmap_cmp);
1298 if (*Extmap->em_ext == 0) {
1299 /* the first line is really "." the default entry,
1300 * we remove the leading '.' in setextmap
1301 */
1302 Defextmap = Extmap;
1303 }
1304 }
1305 }
1306
free_extmap(void)1307 static void free_extmap( void)
1308 {
1309 struct extmap *em;
1310
1311 if (Extmap) {
1312 for ( em = Extmap; em->em_ext; em++) {
1313 free (em->em_ext);
1314 }
1315 free(Extmap);
1316 Extmap = NULL;
1317 Defextmap = Extmap;
1318 Extmap_cnt = 0;
1319 }
1320 }
1321
ext_cmp_key(const void * key,const void * obj)1322 static int ext_cmp_key(const void *key, const void *obj)
1323 {
1324 const char *p = key;
1325 const struct extmap *em = obj;
1326 return strdiacasecmp(p, em->em_ext);
1327 }
1328
getextmap(const char * path)1329 struct extmap *getextmap(const char *path)
1330 {
1331 char *p;
1332 struct extmap *em;
1333
1334 if (!Extmap_cnt || NULL == ( p = strrchr( path, '.' )) ) {
1335 return( Defextmap );
1336 }
1337 p++;
1338 if (!*p) {
1339 return( Defextmap );
1340 }
1341 em = bsearch(p, Extmap, Extmap_cnt, sizeof(struct extmap), ext_cmp_key);
1342 if (em) {
1343 return( em );
1344 } else {
1345 return( Defextmap );
1346 }
1347 }
1348
getdefextmap(void)1349 struct extmap *getdefextmap(void)
1350 {
1351 return( Defextmap );
1352 }
1353
readextmap(const char * file)1354 static int readextmap(const char *file)
1355 {
1356 EC_INIT;
1357 FILE *fp;
1358 char ext[256];
1359 char buf[256];
1360 char type[5], creator[5];
1361
1362 LOG(log_debug, logtype_afpd, "readextmap: loading \"%s\"", file);
1363
1364 EC_NULL_LOGSTR( fp = fopen(file, "r"), "Couldn't open extension maping file %s", file);
1365
1366 while (fgets(buf, sizeof(buf), fp) != NULL) {
1367 initline(strlen(buf), buf);
1368 parseline(sizeof(ext) - 1, ext);
1369
1370 switch (ext[0]) {
1371 case '.' :
1372 parseline(sizeof(type) - 1, type);
1373 parseline(sizeof(creator) - 1, creator);
1374 setextmap(ext, type, creator);
1375 LOG(log_debug, logtype_afpd, "readextmap: mapping: '%s' -> %s/%s", ext, type, creator);
1376 break;
1377 }
1378 }
1379
1380 sortextmap();
1381 EC_ZERO( fclose(fp) );
1382
1383 LOG(log_debug, logtype_afpd, "readextmap: done", file);
1384
1385 EC_CLEANUP:
1386 EC_EXIT;
1387 }
1388
1389 /**************************************************************
1390 * API functions
1391 **************************************************************/
1392
1393 /*!
1394 * Remove a volume from the linked list of volumes
1395 */
volume_unlink(struct vol * volume)1396 void volume_unlink(struct vol *volume)
1397 {
1398 struct vol *vol, *ovol, *nvol;
1399
1400 if (volume == Volumes) {
1401 Volumes = NULL;
1402 return;
1403 }
1404 for ( vol = Volumes->v_next, ovol = Volumes; vol; vol = nvol) {
1405 nvol = vol->v_next;
1406
1407 if (vol == volume) {
1408 ovol->v_next = nvol;
1409 break;
1410 }
1411 else {
1412 ovol = vol;
1413 }
1414 }
1415 }
1416
1417 /*!
1418 * Free all resources allocated in a struct vol in load_volumes()
1419 *
1420 * Actually opening a volume (afp_openvol()) will allocate additional
1421 * ressources which are freed in closevol()
1422 */
volume_free(struct vol * vol)1423 void volume_free(struct vol *vol)
1424 {
1425 free(vol->v_configname);
1426 free(vol->v_localname);
1427 free(vol->v_u8mname);
1428 free(vol->v_macname);
1429 free(vol->v_path);
1430 free(vol->v_password);
1431 free(vol->v_veto);
1432 free(vol->v_volcodepage);
1433 free(vol->v_maccodepage);
1434 free(vol->v_cnidscheme);
1435 free(vol->v_dbpath);
1436 free(vol->v_gvs);
1437 free(vol->v_uuid);
1438 free(vol->v_cnidserver);
1439 free(vol->v_cnidport);
1440 free(vol->v_preexec);
1441 free(vol->v_root_preexec);
1442 free(vol->v_postexec);
1443 free(vol->v_root_postexec);
1444
1445 free(vol);
1446 }
1447
1448 /*!
1449 * Load charsets for a volume
1450 */
load_charset(struct vol * vol)1451 int load_charset(struct vol *vol)
1452 {
1453 if ((vol->v_maccharset = add_charset(vol->v_maccodepage)) == (charset_t)-1) {
1454 LOG(log_error, logtype_default, "Setting mac charset '%s' failed", vol->v_maccodepage);
1455 return -1;
1456 }
1457
1458 if ((vol->v_volcharset = add_charset(vol->v_volcodepage)) == (charset_t)-1) {
1459 LOG(log_error, logtype_default, "Setting vol charset '%s' failed", vol->v_volcodepage);
1460 return -1;
1461 }
1462
1463 return 0;
1464 }
1465
1466 /*!
1467 * Initialize volumes and load ini configfile
1468 *
1469 * @param obj (r) handle
1470 * @param flags (r) flags controlling volume load behaviour:
1471 * LV_DEFAULT: load shares in a user/session context, this honors authorisation
1472 * LV_ALL: load shares that are available in the config file
1473 * LV_FORCE: reload file even though the timestamp wasn't changed
1474 */
load_volumes(AFPObj * obj,lv_flags_t flags)1475 int load_volumes(AFPObj *obj, lv_flags_t flags)
1476 {
1477 EC_INIT;
1478
1479 static long bufsize;
1480 static char *pwbuf = NULL;
1481
1482 int fd = -1;
1483 struct passwd pwent;
1484 struct passwd *pwresult = NULL;
1485 struct stat st;
1486 int retries = 0;
1487 struct vol *vol;
1488 char *includefile;
1489
1490 LOG(log_debug, logtype_afpd, "load_volumes: BEGIN");
1491
1492 if (pwbuf == NULL) {
1493 bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);
1494 if (bufsize == -1) /* Value was indeterminate */
1495 bufsize = 16384; /* Should be more than enough */
1496 EC_NULL( pwbuf = malloc(bufsize) );
1497 }
1498
1499 if (!(flags & LV_ALL)) {
1500 ret = getpwuid_r(obj->uid, &pwent, pwbuf, bufsize, &pwresult);
1501 if (pwresult == NULL) {
1502 LOG(log_error, logtype_afpd, "load_volumes: getpwuid_r: %s", strerror(errno));
1503 EC_FAIL;
1504 }
1505 pwresult = &pwent;
1506 }
1507
1508 if (Volumes) {
1509 if (!(flags & LV_FORCE) && !volfile_changed(obj))
1510 goto EC_CLEANUP;
1511 have_uservol = 0;
1512 for (vol = Volumes; vol; vol = vol->v_next) {
1513 vol->v_deleted = 1;
1514 }
1515 if (obj->uid && pwresult) {
1516 become_root();
1517 ret = set_groups(obj, pwresult);
1518 unbecome_root();
1519 if (ret != 0) {
1520 LOG(log_error, logtype_afpd, "load_volumes: set_groups: %s", strerror(errno));
1521 EC_FAIL;
1522 }
1523 }
1524 } else {
1525 LOG(log_debug, logtype_afpd, "load_volumes: no volumes yet");
1526 EC_ZERO_LOG( lstat(obj->options.configfile, &st) );
1527 obj->options.volfile.mtime = st.st_mtime;
1528
1529 includefile = atalk_iniparser_getstring(obj->iniconfig, INISEC_GLOBAL,
1530 "include", NULL);
1531 if (includefile) {
1532 EC_ZERO_LOG( stat(includefile, &st) );
1533 obj->options.includefile.mtime = st.st_mtime;
1534 }
1535 }
1536
1537 /* try putting a read lock on the volume file twice, sleep 1 second if first attempt fails */
1538
1539 fd = open(obj->options.configfile, O_RDONLY);
1540
1541 while (retries < 2) {
1542 if ((read_lock(fd, 0, SEEK_SET, 0)) != 0) {
1543 retries++;
1544 if (!retries) {
1545 LOG(log_error, logtype_afpd, "readvolfile: can't lock configfile \"%s\"",
1546 obj->options.configfile);
1547 EC_FAIL;
1548 }
1549 sleep(1);
1550 continue;
1551 }
1552 break;
1553 }
1554
1555 if (obj->iniconfig)
1556 atalk_iniparser_freedict(obj->iniconfig);
1557 LOG(log_debug, logtype_afpd, "load_volumes: loading: %s", obj->options.configfile);
1558 obj->iniconfig = atalk_iniparser_load(obj->options.configfile);
1559
1560 EC_ZERO_LOG( readvolfile(obj, pwresult) );
1561
1562 struct vol *nextvol, *prevvol;
1563
1564 vol = Volumes;
1565 prevvol = NULL;
1566
1567 while (vol) {
1568 if (vol->v_deleted && !(vol->v_flags & AFPVOL_OPEN)) {
1569 LOG(log_debug, logtype_afpd, "load_volumes: deleted: %s", vol->v_localname);
1570 nextvol = vol->v_next;
1571 if (prevvol) {
1572 prevvol->v_next = vol->v_next;
1573 } else {
1574 Volumes = nextvol;
1575 }
1576 volume_free(vol);
1577 vol = nextvol;
1578 continue;
1579 }
1580 prevvol = vol;
1581 vol = vol->v_next;
1582 }
1583
1584 EC_CLEANUP:
1585 if (fd != -1)
1586 (void)close(fd);
1587
1588 LOG(log_debug, logtype_afpd, "load_volumes: END");
1589 EC_EXIT;
1590 }
1591
unload_volumes(AFPObj * obj)1592 void unload_volumes(AFPObj *obj)
1593 {
1594 struct vol *vol, *p;
1595
1596 LOG(log_debug, logtype_afpd, "unload_volumes: BEGIN");
1597
1598 p = Volumes;
1599 while (p) {
1600 vol = p;
1601 p = vol->v_next;
1602 volume_free(vol);
1603 }
1604 Volumes = NULL;
1605 obj->options.volfile.mtime = 0;
1606 lastvid = 0;
1607 have_uservol = 0;
1608
1609 LOG(log_debug, logtype_afpd, "unload_volumes: END");
1610 }
1611
getvolumes(void)1612 struct vol *getvolumes(void)
1613 {
1614 return Volumes;
1615 }
1616
getvolbyvid(const uint16_t vid)1617 struct vol *getvolbyvid(const uint16_t vid )
1618 {
1619 struct vol *vol;
1620
1621 for ( vol = Volumes; vol; vol = vol->v_next ) {
1622 if ( vid == vol->v_vid ) {
1623 break;
1624 }
1625 }
1626 if ( vol == NULL || ( vol->v_flags & AFPVOL_OPEN ) == 0 ) {
1627 return( NULL );
1628 }
1629
1630 return( vol );
1631 }
1632
1633 /*
1634 * get username by path
1635 *
1636 * getvolbypath() assumes that the user home directory has the same name as the username.
1637 * If that is not true, getuserbypath() is called and tries to retrieve the username
1638 * from the directory owner, checking its validity.
1639 *
1640 * @param path (r) absolute volume path
1641 * @returns NULL if no match is found, pointer to username if successfull
1642 *
1643 */
getuserbypath(const char * path)1644 static char *getuserbypath(const char *path)
1645 {
1646 EC_INIT;
1647 struct stat sbuf;
1648 struct passwd *pwd;
1649 char *hdir = NULL;
1650
1651 LOG(log_debug, logtype_afpd, "getuserbypath(\"%s\")", path);
1652
1653 /* does folder exists? */
1654 if (stat(path, &sbuf) != 0)
1655 EC_FAIL;
1656
1657 /* get uid of dir owner */
1658 if ((pwd = getpwuid(sbuf.st_uid)) == NULL)
1659 EC_FAIL;
1660
1661 /* does user home directory exists? */
1662 if (stat(pwd->pw_dir, &sbuf) != 0)
1663 EC_FAIL;
1664
1665 /* resolve and remove symlinks */
1666 if ((hdir = realpath_safe(pwd->pw_dir)) == NULL)
1667 EC_FAIL;
1668
1669 /* handle subdirectories, path = */
1670 if (strncmp(path, hdir, strlen(hdir)) != 0)
1671 EC_FAIL;
1672
1673 LOG(log_debug, logtype_afpd, "getuserbypath: match user: %s, home: %s, realhome: %s",
1674 pwd->pw_name, pwd->pw_dir, hdir);
1675
1676 EC_CLEANUP:
1677 if (hdir)
1678 free(hdir);
1679 if (ret != 0)
1680 return NULL;
1681 return pwd->pw_name;
1682 }
1683 /*!
1684 * Search volume by path, creating user home vols as necessary
1685 *
1686 * Path may be absolute or relative. Ordinary volume structs are created when
1687 * the ini config is initially parsed (load_volumes()), but user volumes are
1688 * as load_volumes() only can create the user volume of the logged in user
1689 * in an AFP session in afpd, but not when called from eg cnid_metad or dbd.
1690 * Both cnid_metad and dbd thus need a way to lookup and create struct vols
1691 * for user home by path. This is what this func does as well.
1692 *
1693 * (1) Search "normal" volume list
1694 * (2) Check if theres a [Homes] section, load_volumes() remembers this for us
1695 * (3) If there is, match "path" with "basedir regex" to get the user home parent dir
1696 * (4) Built user home path by appending the basedir matched in (3) and appending the username
1697 * (5) The next path element then is the username
1698 * (5b) getvolbypath() assumes that the user home directory has the same name as the username.
1699 * If that is not true, getuserbypath() is called and tries to retrieve the username
1700 * from the directory owner, checking its validity
1701 * (6) Append [Homes]->path subdirectory if defined
1702 * (7) Create volume
1703 *
1704 * @param obj (rw) handle
1705 * @param path (r) path, may be relative or absolute
1706 */
getvolbypath(AFPObj * obj,const char * path)1707 struct vol *getvolbypath(AFPObj *obj, const char *path)
1708 {
1709 EC_INIT;
1710 static int regexerr = -1;
1711 static regex_t reg;
1712 struct vol *vol;
1713 struct vol *tmp;
1714 const struct passwd *pw;
1715 char volname[AFPVOL_U8MNAMELEN + 1];
1716 char abspath[MAXPATHLEN + 1];
1717 char volpath[MAXPATHLEN + 1], *realvolpath = NULL;
1718 char tmpbuf[MAXPATHLEN + 1];
1719 const char *secname, *basedir, *p = NULL, *subpath = NULL, *subpathconfig;
1720 char *user = NULL, *prw;
1721 regmatch_t match[1];
1722 size_t abspath_len;
1723
1724 LOG(log_debug, logtype_afpd, "getvolbypath(\"%s\")", path);
1725
1726 if (path[0] != '/') {
1727 /* relative path, build absolute path */
1728 EC_NULL_LOG( getcwd(abspath, MAXPATHLEN) );
1729 strlcat(abspath, "/", MAXPATHLEN);
1730 strlcat(abspath, path, MAXPATHLEN);
1731 path = abspath;
1732 } else {
1733 strlcpy(abspath, path, MAXPATHLEN);
1734 path = abspath;
1735 }
1736 /* path now points to a copy of path in the abspath buffer */
1737
1738 /*
1739 * Strip trailing slashes
1740 */
1741 abspath_len = strlen(abspath);
1742 while (abspath[abspath_len - 1] == '/') {
1743 abspath[abspath_len - 1] = 0;
1744 abspath_len--;
1745 }
1746
1747 for (tmp = Volumes; tmp; tmp = tmp->v_next) { /* (1) */
1748 size_t v_path_len = strlen(tmp->v_path);
1749 if (strncmp(path, tmp->v_path, v_path_len) == 0) {
1750 if (v_path_len < strlen(path) && path[v_path_len] != '/') {
1751 LOG(log_debug, logtype_afpd, "getvolbypath: path(\"%s\") != volume(\"%s\")", path, tmp->v_path);
1752 } else {
1753 LOG(log_debug, logtype_afpd, "getvolbypath: path(\"%s\") == volume(\"%s\")", path, tmp->v_path);
1754 vol = tmp;
1755 goto EC_CLEANUP;
1756 }
1757 } else {
1758 LOG(log_debug, logtype_afpd, "getvolbypath: path(\"%s\") != volume(\"%s\")", path, tmp->v_path);
1759 }
1760 }
1761
1762 if (!have_uservol) /* (2) */
1763 EC_FAIL_LOG("getvolbypath(\"%s\"): no volume for path", path);
1764
1765 int secnum = atalk_iniparser_getnsec(obj->iniconfig);
1766
1767 for (int i = 0; i < secnum; i++) {
1768 secname = atalk_iniparser_getsecname(obj->iniconfig, i);
1769 if (STRCMP(secname, ==, INISEC_HOMES))
1770 break;
1771 }
1772
1773 if (STRCMP(secname, !=, INISEC_HOMES))
1774 EC_FAIL_LOG("getvolbypath(\"%s\"): no volume for path", path);
1775
1776 /* (3) */
1777 EC_NULL_LOG( basedir = atalk_iniparser_getstring(obj->iniconfig, INISEC_HOMES, "basedir regex", NULL) );
1778 LOG(log_debug, logtype_afpd, "getvolbypath: user home section: '%s', basedir: '%s'", secname, basedir);
1779
1780 if (regexerr != 0 && (regexerr = regcomp(®, basedir, REG_EXTENDED)) != 0) {
1781 char errbuf[1024];
1782 regerror(regexerr, ®, errbuf, sizeof(errbuf));
1783 printf("error: %s\n", errbuf);
1784 EC_FAIL_LOG("getvolbypath(\"%s\"): bad basedir regex: %s", errbuf);
1785 }
1786
1787 if (regexec(®, path, 1, match, 0) == REG_NOMATCH)
1788 EC_FAIL_LOG("getvolbypath(\"%s\"): no volume for path", path);
1789
1790 if (match[0].rm_eo - match[0].rm_so > MAXPATHLEN)
1791 EC_FAIL_LOG("getvolbypath(\"%s\"): path too long", path);
1792
1793 /* (4) */
1794 strncpy(tmpbuf, path + match[0].rm_so, match[0].rm_eo - match[0].rm_so);
1795 tmpbuf[match[0].rm_eo - match[0].rm_so] = 0;
1796
1797 LOG(log_debug, logtype_afpd, "getvolbypath: basedir regex: '%s', basedir match: \"%s\"",
1798 basedir, tmpbuf);
1799
1800 strlcat(tmpbuf, "/", MAXPATHLEN);
1801
1802 /* (5) */
1803 p = path + match[0].rm_eo - match[0].rm_so;
1804 while (*p == '/')
1805 p++;
1806 EC_NULL_LOG( user = strdup(p) );
1807
1808 if ((prw = strchr(user, '/')))
1809 *prw++ = 0;
1810 if (prw != 0)
1811 subpath = prw;
1812
1813 strlcat(tmpbuf, user, MAXPATHLEN);
1814 if ((pw = getpwnam(user)) == NULL) {
1815 /* (5b) */
1816 char *tuser;
1817 if ((tuser = getuserbypath(tmpbuf)) != NULL) {
1818 free(user);
1819 user = strdup(tuser);
1820 }
1821 if ((pw = getpwnam(user)) == NULL)
1822 EC_FAIL_LOG("unknown user: %s", user);
1823 }
1824 strlcpy(obj->username, user, MAXUSERLEN);
1825 strlcat(tmpbuf, "/", MAXPATHLEN);
1826
1827 /* (6) */
1828 if ((subpathconfig = atalk_iniparser_getstring(obj->iniconfig, INISEC_HOMES, "path", NULL))) {
1829 /*
1830 if (!subpath || strncmp(subpathconfig, subpath, strlen(subpathconfig)) != 0) {
1831 EC_FAIL;
1832 }
1833 */
1834 strlcat(tmpbuf, subpathconfig, MAXPATHLEN);
1835 strlcat(tmpbuf, "/", MAXPATHLEN);
1836 }
1837
1838
1839 /* (7) */
1840 if (volxlate(obj, volpath, sizeof(volpath) - 1, tmpbuf, pw, NULL, NULL) == NULL)
1841 EC_FAIL;
1842
1843 EC_NULL( realvolpath = realpath_safe(volpath) );
1844 EC_NULL( pw = getpwnam(user) );
1845
1846 LOG(log_debug, logtype_afpd, "getvolbypath(\"%s\"): user: %s, homedir: %s => realvolpath: \"%s\"",
1847 path, user, pw->pw_dir, realvolpath);
1848
1849 /* do variable substitution for volume name */
1850 p = atalk_iniparser_getstring(obj->iniconfig, INISEC_HOMES, "home name", "$u's home");
1851 if (strstr(p, "$u") == NULL)
1852 p = "$u's home";
1853 strlcpy(tmpbuf, p, AFPVOL_U8MNAMELEN);
1854 EC_NULL_LOG( volxlate(obj, volname, sizeof(volname) - 1, tmpbuf, pw, realvolpath, NULL) );
1855
1856 const char *preset, *default_preset;
1857 default_preset = atalk_iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "vol preset", NULL);
1858 preset = atalk_iniparser_getstring(obj->iniconfig, INISEC_HOMES, "vol preset", NULL);
1859
1860 vol = creatvol(obj, pw, INISEC_HOMES, volname, realvolpath, preset ? preset : default_preset ? default_preset : NULL);
1861
1862 EC_CLEANUP:
1863 if (user)
1864 free(user);
1865 if (realvolpath)
1866 free(realvolpath);
1867 if (ret != 0)
1868 vol = NULL;
1869 return vol;
1870 }
1871
getvolbyname(const char * name)1872 struct vol *getvolbyname(const char *name)
1873 {
1874 struct vol *vol = NULL;
1875 struct vol *tmp;
1876
1877 for (tmp = Volumes; tmp; tmp = tmp->v_next) {
1878 if (strncmp(name, tmp->v_configname, strlen(tmp->v_configname)) == 0) {
1879 vol = tmp;
1880 break;
1881 }
1882 }
1883 return vol;
1884 }
1885
1886 #define MAXVAL 1024
1887 /*!
1888 * Initialize an AFPObj and options from ini config file
1889 */
afp_config_parse(AFPObj * AFPObj,char * processname)1890 int afp_config_parse(AFPObj *AFPObj, char *processname)
1891 {
1892 EC_INIT;
1893 dictionary *config;
1894 struct afp_options *options = &AFPObj->options;
1895 int c;
1896 const char *p;
1897 char *q, *r;
1898 char val[MAXVAL];
1899
1900 if (processname != NULL)
1901 set_processname(processname);
1902
1903 AFPObj->afp_version = 11;
1904 options->configfile = AFPObj->cmdlineconfigfile ? strdup(AFPObj->cmdlineconfigfile) : strdup(_PATH_CONFDIR "afp.conf");
1905 options->sigconffile = strdup(_PATH_STATEDIR "afp_signature.conf");
1906 options->uuidconf = strdup(_PATH_STATEDIR "afp_voluuid.conf");
1907 options->flags = OPTION_UUID | AFPObj->cmdlineflags;
1908
1909 if ((config = atalk_iniparser_load(AFPObj->options.configfile)) == NULL)
1910 return -1;
1911 AFPObj->iniconfig = config;
1912
1913 /* [Global] */
1914 options->logconfig = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "log level", "default:note");
1915 options->logfile = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "log file", NULL);
1916
1917 setuplog(options->logconfig, options->logfile);
1918
1919 /* "server options" boolean options */
1920 if (!atalk_iniparser_getboolean(config, INISEC_GLOBAL, "zeroconf", 1))
1921 options->flags |= OPTION_NOZEROCONF;
1922 if (atalk_iniparser_getboolean(config, INISEC_GLOBAL, "advertise ssh", 0))
1923 options->flags |= OPTION_ANNOUNCESSH;
1924 if (atalk_iniparser_getboolean(config, INISEC_GLOBAL, "close vol", 0))
1925 options->flags |= OPTION_CLOSEVOL;
1926 if (!atalk_iniparser_getboolean(config, INISEC_GLOBAL, "client polling", 0))
1927 options->flags |= OPTION_SERVERNOTIF;
1928 if (!atalk_iniparser_getboolean(config, INISEC_GLOBAL, "use sendfile", 1))
1929 options->flags |= OPTION_NOSENDFILE;
1930 if (atalk_iniparser_getboolean(config, INISEC_GLOBAL, "recvfile", 0))
1931 options->flags |= OPTION_RECVFILE;
1932 if (atalk_iniparser_getboolean(config, INISEC_GLOBAL, "solaris share reservations", 1))
1933 options->flags |= OPTION_SHARE_RESERV;
1934 if (atalk_iniparser_getboolean(config, INISEC_GLOBAL, "afpstats", 0))
1935 options->flags |= OPTION_DBUS_AFPSTATS;
1936 if (atalk_iniparser_getboolean(config, INISEC_GLOBAL, "afp read locks", 0))
1937 options->flags |= OPTION_AFP_READ_LOCK;
1938 if (atalk_iniparser_getboolean(config, INISEC_GLOBAL, "spotlight", 0))
1939 options->flags |= OPTION_SPOTLIGHT_VOL;
1940 if (atalk_iniparser_getboolean(config, INISEC_GLOBAL, "veto message", 0))
1941 options->flags |= OPTION_VETOMSG;
1942 if (!atalk_iniparser_getboolean(config, INISEC_GLOBAL, "save password", 1))
1943 options->passwdbits |= PASSWD_NOSAVE;
1944 if (atalk_iniparser_getboolean(config, INISEC_GLOBAL, "set password", 0))
1945 options->passwdbits |= PASSWD_SET;
1946 if (atalk_iniparser_getboolean(config, INISEC_GLOBAL, "spotlight expr", 1))
1947 options->flags |= OPTION_SPOTLIGHT_EXPR;
1948
1949 /* figure out options w values */
1950 options->loginmesg = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "login message", NULL);
1951 options->guest = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "guest account", "nobody");
1952 options->extmapfile = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "extmap file", _PATH_CONFDIR "extmap.conf");
1953 options->passwdfile = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "passwd file", _PATH_AFPDPWFILE);
1954 options->uampath = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "uam path", _PATH_AFPDUAMPATH);
1955 options->uamlist = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "uam list", "uams_dhx.so uams_dhx2.so");
1956 options->port = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "afp port", "548");
1957 options->signatureopt = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "signature", "");
1958 options->k5service = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "k5 service", NULL);
1959 options->k5realm = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "k5 realm", NULL);
1960 options->listen = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "afp listen", NULL);
1961 options->interfaces = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "afp interfaces", NULL);
1962 options->ntdomain = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "nt domain", NULL);
1963 options->addomain = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "ad domain", NULL);
1964 options->ntseparator = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "nt separator", NULL);
1965 options->mimicmodel = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "mimic model", NULL);
1966 options->zeroconfname = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "zeroconf name", NULL);
1967 options->adminauthuser = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "admin auth user",NULL);
1968 options->ignored_attr = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "ignored attributes", NULL);
1969 options->cnid_mysql_host = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "cnid mysql host", NULL);
1970 options->cnid_mysql_user = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "cnid mysql user", NULL);
1971 options->cnid_mysql_pw = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "cnid mysql pw", NULL);
1972 options->cnid_mysql_db = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "cnid mysql db", NULL);
1973 options->connections = atalk_iniparser_getint (config, INISEC_GLOBAL, "max connections",200);
1974 options->passwdminlen = atalk_iniparser_getint (config, INISEC_GLOBAL, "passwd minlen", 0);
1975 options->tickleval = atalk_iniparser_getint (config, INISEC_GLOBAL, "tickleval", 30);
1976 options->timeout = atalk_iniparser_getint (config, INISEC_GLOBAL, "timeout", 4);
1977 options->dsireadbuf = atalk_iniparser_getint (config, INISEC_GLOBAL, "dsireadbuf", 12);
1978 options->server_quantum = atalk_iniparser_getint (config, INISEC_GLOBAL, "server quantum", DSI_SERVQUANT_DEF);
1979 options->volnamelen = atalk_iniparser_getint (config, INISEC_GLOBAL, "volnamelen", 80);
1980 options->dircachesize = atalk_iniparser_getint (config, INISEC_GLOBAL, "dircachesize", DEFAULT_MAX_DIRCACHE_SIZE);
1981 options->tcp_sndbuf = atalk_iniparser_getint (config, INISEC_GLOBAL, "tcpsndbuf", 0);
1982 options->tcp_rcvbuf = atalk_iniparser_getint (config, INISEC_GLOBAL, "tcprcvbuf", 0);
1983 options->fce_fmodwait = atalk_iniparser_getint (config, INISEC_GLOBAL, "fce holdfmod", 60);
1984 options->sleep = atalk_iniparser_getint (config, INISEC_GLOBAL, "sleep time", 10);
1985 options->disconnected = atalk_iniparser_getint (config, INISEC_GLOBAL, "disconnect time",24);
1986 options->splice_size = atalk_iniparser_getint (config, INISEC_GLOBAL, "splice size", 64*1024);
1987 options->sparql_limit = atalk_iniparser_getint (config, INISEC_GLOBAL, "sparql results limit", 0);
1988
1989 p = atalk_iniparser_getstring(config, INISEC_GLOBAL, "map acls", "rights");
1990 if (STRCMP(p, ==, "rights"))
1991 options->flags |= OPTION_ACL2MACCESS;
1992 else if (STRCMP(p, ==, "mode"))
1993 options->flags |= OPTION_ACL2MODE | OPTION_ACL2MACCESS;
1994 else {
1995 if (STRCMP(p, !=, "none")) {
1996 LOG(log_error, logtype_afpd, "bad ACL mapping option: %s, defaulting to 'rights'", p);
1997 options->flags |= OPTION_ACL2MACCESS;
1998 }
1999 }
2000
2001 if ((p = atalk_iniparser_getstring(config, INISEC_GLOBAL, "hostname", NULL))) {
2002 EC_NULL_LOG( options->hostname = strdup(p) );
2003 } else {
2004 if (gethostname(val, sizeof(val)) < 0 ) {
2005 perror( "gethostname" );
2006 EC_FAIL;
2007 }
2008 if ((q = strchr(val, '.')))
2009 *q = '\0';
2010 options->hostname = strdup(val);
2011 }
2012
2013 if ((p = atalk_iniparser_getstring(config, INISEC_GLOBAL, "k5 keytab", NULL))) {
2014 EC_NULL_LOG( options->k5keytab = malloc(strlen(p) + 14) );
2015 snprintf(options->k5keytab, strlen(p) + 14, "KRB5_KTNAME=%s", p);
2016 putenv(options->k5keytab);
2017 }
2018
2019 if ((p = atalk_iniparser_getstring(config, INISEC_GLOBAL, "admin group", NULL))) {
2020 struct group *gr = getgrnam(p);
2021 if (gr != NULL)
2022 options->admingid = gr->gr_gid;
2023 }
2024
2025 if ((p = atalk_iniparser_getstring(config, INISEC_GLOBAL, "force user", NULL))) {
2026 struct passwd *pw = getpwnam(p);
2027 if (pw != NULL) {
2028 options->force_uid = pw->pw_uid;
2029 options->force_user = true;
2030 }
2031 }
2032
2033 if ((p = atalk_iniparser_getstring(config, INISEC_GLOBAL, "force group", NULL))) {
2034 struct group *gr = getgrnam(p);
2035 if (gr != NULL) {
2036 options->force_gid = gr->gr_gid;
2037 options->force_group = true;
2038 }
2039 }
2040
2041 q = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "cnid server", "localhost:4700");
2042 r = strrchr(q, ':');
2043 if (r)
2044 *r = 0;
2045 options->Cnid_srv = strdup(q);
2046 if (r)
2047 options->Cnid_port = strdup(r + 1);
2048 else
2049 options->Cnid_port = strdup("4700");
2050 LOG(log_debug, logtype_afpd, "CNID Server: %s:%s", options->Cnid_srv, options->Cnid_port);
2051 if (q)
2052 free(q);
2053
2054 if ((q = atalk_iniparser_getstrdup(config, INISEC_GLOBAL, "fqdn", NULL))) {
2055 /* do a little checking for the domain name. */
2056 r = strchr(q, ':');
2057 if (r)
2058 *r = '\0';
2059 if (gethostbyname(q)) {
2060 if (r)
2061 *r = ':';
2062 EC_NULL_LOG( options->fqdn = strdup(q) );
2063 } else {
2064 LOG(log_error, logtype_afpd, "error parsing -fqdn, gethostbyname failed for: %s", q);
2065 }
2066 free(q);
2067 }
2068
2069 /* Charset Options */
2070
2071 /* unix charset is in [G] only */
2072 if (!(p = atalk_iniparser_getstring(config, INISEC_GLOBAL, "unix charset", NULL))) {
2073 options->unixcodepage = strdup("UTF8");
2074 set_charset_name(CH_UNIX, "UTF8");
2075 } else {
2076 if (strcasecmp(p, "LOCALE") == 0) {
2077 #if defined(CODESET)
2078 setlocale(LC_ALL, "");
2079 p = nl_langinfo(CODESET);
2080 LOG(log_debug, logtype_afpd, "Locale charset is '%s'", p);
2081 #else /* system doesn't have LOCALE support */
2082 LOG(log_warning, logtype_afpd, "system doesn't have LOCALE support");
2083 p = "UTF8";
2084 #endif
2085 }
2086 if (strcasecmp(p, "UTF-8") == 0) {
2087 p = "UTF8";
2088 }
2089 options->unixcodepage = strdup(p);
2090 set_charset_name(CH_UNIX, p);
2091 }
2092 options->unixcharset = CH_UNIX;
2093 LOG(log_debug, logtype_afpd, "Global unix charset is %s", options->unixcodepage);
2094
2095 /* vol charset is in [G] and [V] */
2096 if (!(p = atalk_iniparser_getstring(config, INISEC_GLOBAL, "vol charset", NULL))) {
2097 options->volcodepage = strdup(options->unixcodepage);
2098 } else {
2099 if (strcasecmp(p, "UTF-8") == 0) {
2100 p = "UTF8";
2101 }
2102 options->volcodepage = strdup(p);
2103 }
2104 LOG(log_debug, logtype_afpd, "Global vol charset is %s", options->volcodepage);
2105
2106 /* mac charset is in [G] and [V] */
2107 if (!(p = atalk_iniparser_getstring(config, INISEC_GLOBAL, "mac charset", NULL))) {
2108 options->maccodepage = strdup("MAC_ROMAN");
2109 set_charset_name(CH_MAC, "MAC_ROMAN");
2110 } else {
2111 if (strncasecmp(p, "MAC", 3) != 0) {
2112 LOG(log_warning, logtype_afpd, "Is '%s' really mac charset? ", p);
2113 }
2114 options->maccodepage = strdup(p);
2115 set_charset_name(CH_MAC, p);
2116 }
2117 options->maccharset = CH_MAC;
2118 LOG(log_debug, logtype_afpd, "Global mac charset is %s", options->maccodepage);
2119
2120 if (readextmap(options->extmapfile) != 0) {
2121 LOG(log_error, logtype_afpd, "Couldn't load extension -> type/creator mappings file \"%s\"",
2122 options->extmapfile);
2123 }
2124
2125 /* Check for sane values */
2126 if (options->tickleval <= 0)
2127 options->tickleval = 30;
2128 options->disconnected *= 3600 / options->tickleval;
2129 options->sleep *= 3600 / options->tickleval;
2130 if (options->timeout <= 0)
2131 options->timeout = 4;
2132 if (options->sleep <= 4)
2133 options->disconnected = options->sleep = 4;
2134 if (options->dsireadbuf < 6)
2135 options->dsireadbuf = 6;
2136 if (options->volnamelen < 8)
2137 options->volnamelen = 8; /* max mangled volname "???#FFFF" */
2138 if (options->volnamelen > 255)
2139 options->volnamelen = 255; /* AFP3 spec */
2140
2141 EC_CLEANUP:
2142 EC_EXIT;
2143 }
2144
2145 #define CONFIG_ARG_FREE(a) do { \
2146 free(a); \
2147 a = NULL; \
2148 } while (0);
2149
2150 /* get rid of any allocated afp_option buffers. */
afp_config_free(AFPObj * obj)2151 void afp_config_free(AFPObj *obj)
2152 {
2153 if (obj->options.configfile)
2154 CONFIG_ARG_FREE(obj->options.configfile);
2155 if (obj->options.sigconffile)
2156 CONFIG_ARG_FREE(obj->options.sigconffile);
2157 if (obj->options.uuidconf)
2158 CONFIG_ARG_FREE(obj->options.uuidconf);
2159 if (obj->options.logconfig)
2160 CONFIG_ARG_FREE(obj->options.logconfig);
2161 if (obj->options.logfile)
2162 CONFIG_ARG_FREE(obj->options.logfile);
2163 if (obj->options.loginmesg)
2164 CONFIG_ARG_FREE(obj->options.loginmesg);
2165 if (obj->options.guest)
2166 CONFIG_ARG_FREE(obj->options.guest);
2167 if (obj->options.extmapfile)
2168 CONFIG_ARG_FREE(obj->options.extmapfile);
2169 if (obj->options.passwdfile)
2170 CONFIG_ARG_FREE(obj->options.passwdfile);
2171 if (obj->options.uampath)
2172 CONFIG_ARG_FREE(obj->options.uampath);
2173 if (obj->options.uamlist)
2174 CONFIG_ARG_FREE(obj->options.uamlist);
2175 if (obj->options.port)
2176 CONFIG_ARG_FREE(obj->options.port);
2177 if (obj->options.signatureopt)
2178 CONFIG_ARG_FREE(obj->options.signatureopt);
2179 if (obj->options.k5service)
2180 CONFIG_ARG_FREE(obj->options.k5service);
2181 if (obj->options.k5realm)
2182 CONFIG_ARG_FREE(obj->options.k5realm);
2183 if (obj->options.k5principal)
2184 CONFIG_ARG_FREE(obj->options.k5principal);
2185 if (obj->options.listen)
2186 CONFIG_ARG_FREE(obj->options.listen);
2187 if (obj->options.interfaces)
2188 CONFIG_ARG_FREE(obj->options.interfaces);
2189 if (obj->options.ntdomain)
2190 CONFIG_ARG_FREE(obj->options.ntdomain);
2191 if (obj->options.addomain)
2192 CONFIG_ARG_FREE(obj->options.addomain);
2193 if (obj->options.ntseparator)
2194 CONFIG_ARG_FREE(obj->options.ntseparator);
2195 if (obj->options.mimicmodel)
2196 CONFIG_ARG_FREE(obj->options.mimicmodel);
2197 if (obj->options.zeroconfname)
2198 CONFIG_ARG_FREE(obj->options.zeroconfname);
2199 if (obj->options.adminauthuser)
2200 CONFIG_ARG_FREE(obj->options.adminauthuser);
2201 if (obj->options.hostname)
2202 CONFIG_ARG_FREE(obj->options.hostname);
2203 if (obj->options.k5keytab)
2204 CONFIG_ARG_FREE(obj->options.k5keytab);
2205 if (obj->options.Cnid_srv)
2206 CONFIG_ARG_FREE(obj->options.Cnid_srv);
2207 if (obj->options.Cnid_port)
2208 CONFIG_ARG_FREE(obj->options.Cnid_port);
2209 if (obj->options.fqdn)
2210 CONFIG_ARG_FREE(obj->options.fqdn);
2211 if (obj->options.ignored_attr)
2212 CONFIG_ARG_FREE(obj->options.ignored_attr);
2213 if (obj->options.unixcodepage)
2214 CONFIG_ARG_FREE(obj->options.unixcodepage);
2215 if (obj->options.maccodepage)
2216 CONFIG_ARG_FREE(obj->options.maccodepage);
2217 if (obj->options.volcodepage)
2218 CONFIG_ARG_FREE(obj->options.volcodepage);
2219
2220 obj->options.flags = 0;
2221 obj->options.passwdbits = 0;
2222
2223 /* Free everything called from afp_config_parse() */
2224 free_extmap();
2225 atalk_iniparser_freedict(obj->iniconfig);
2226 free_charset_names();
2227 }
2228