]> arthur.barton.de Git - netatalk.git/blob - libatalk/util/netatalk_conf.c
Merge branch 'branch-allea' of netafp.com:git/netatalk into branch-allea
[netatalk.git] / libatalk / util / netatalk_conf.c
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
34 #include <atalk/afp.h>
35 #include <atalk/util.h>
36 #include <atalk/logger.h>
37 #include <atalk/ea.h>
38 #include <atalk/globals.h>
39 #include <atalk/errchk.h>
40 #include <atalk/iniparser.h>
41 #include <atalk/unix.h>
42 #include <atalk/cnid.h>
43 #include <atalk/dsi.h>
44 #include <atalk/uuid.h>
45 #include <atalk/netatalk_conf.h>
46
47 #define VOLPASSLEN  8
48
49 #define IS_VAR(a, b) (strncmp((a), (b), 2) == 0)
50
51 /**************************************************************
52  * Locals
53  **************************************************************/
54
55 static int have_uservol = 0; /* whether there's generic user home share in config ("~" or "~/path", but not "~user") */
56 static struct vol *Volumes = NULL;
57 static uint16_t    lastvid = 0;
58
59 /* 
60  * Get a volumes UUID from the config file.
61  * If there is none, it is generated and stored there.
62  *
63  * Returns pointer to allocated storage on success, NULL on error.
64  */
65 static char *get_vol_uuid(const AFPObj *obj, const char *volname)
66 {
67     char *volname_conf;
68     char buf[1024], uuid[UUID_PRINTABLE_STRING_LENGTH], *p;
69     FILE *fp;
70     struct stat tmpstat;
71     int fd;
72     
73     if ((fp = fopen(obj->options.uuidconf, "r")) != NULL) {  /* read open? */
74         /* scan in the conf file */
75         while (fgets(buf, sizeof(buf), fp) != NULL) { 
76             p = buf;
77             while (p && isblank(*p))
78                 p++;
79             if (!p || (*p == '#') || (*p == '\n'))
80                 continue;                             /* invalid line */
81             if (*p == '"') {
82                 p++;
83                 if ((volname_conf = strtok( p, "\"" )) == NULL)
84                     continue;                         /* syntax error */
85             } else {
86                 if ((volname_conf = strtok( p, " \t" )) == NULL)
87                     continue;                         /* syntax error: invalid name */
88             }
89             p = strchr(p, '\0');
90             p++;
91             if (*p == '\0')
92                 continue;                             /* syntax error */
93             
94             if (strcmp(volname, volname_conf) != 0)
95                 continue;                             /* another volume name */
96                 
97             while (p && isblank(*p))
98                 p++;
99
100             if (sscanf(p, "%36s", uuid) == 1 ) {
101                 for (int i=0; uuid[i]; i++)
102                     uuid[i] = toupper(uuid[i]);
103                 LOG(log_debug, logtype_afpd, "get_uuid('%s'): UUID: '%s'", volname, uuid);
104                 fclose(fp);
105                 return strdup(uuid);
106             }
107         }
108     }
109
110     if (fp)
111         fclose(fp);
112
113     /*  not found or no file, reopen in append mode */
114
115     if (stat(obj->options.uuidconf, &tmpstat)) {                /* no file */
116         if (( fd = creat(obj->options.uuidconf, 0644 )) < 0 ) {
117             LOG(log_error, logtype_afpd, "ERROR: Cannot create %s (%s).",
118                 obj->options.uuidconf, strerror(errno));
119             return NULL;
120         }
121         if (( fp = fdopen( fd, "w" )) == NULL ) {
122             LOG(log_error, logtype_afpd, "ERROR: Cannot fdopen %s (%s).",
123                 obj->options.uuidconf, strerror(errno));
124             close(fd);
125             return NULL;
126         }
127     } else if ((fp = fopen(obj->options.uuidconf, "a+")) == NULL) { /* not found */
128         LOG(log_error, logtype_afpd, "Cannot create or append to %s (%s).",
129             obj->options.uuidconf, strerror(errno));
130         return NULL;
131     }
132     fseek(fp, 0L, SEEK_END);
133     if(ftell(fp) == 0) {                     /* size = 0 */
134         fprintf(fp, "# DON'T TOUCH NOR COPY THOUGHTLESSLY!\n");
135         fprintf(fp, "# This file is auto-generated by afpd\n");
136         fprintf(fp, "# and stores UUIDs for TM volumes.\n\n");
137     } else {
138         fseek(fp, -1L, SEEK_END);
139         if(fgetc(fp) != '\n') fputc('\n', fp); /* last char is \n? */
140     }                    
141     
142     /* generate uuid and write to file */
143     atalk_uuid_t id;
144     const char *cp;
145     randombytes((void *)id, 16);
146     cp = uuid_bin2string(id);
147
148     LOG(log_debug, logtype_afpd, "get_uuid('%s'): generated UUID '%s'", volname, cp);
149
150     fprintf(fp, "\"%s\"\t%36s\n", volname, cp);
151     fclose(fp);
152     
153     return strdup(cp);
154 }
155
156 /*
157   Check if the underlying filesystem supports EAs.
158   If not, switch to ea:ad.
159   As we can't check (requires write access) on ro-volumes, we switch ea:auto
160   volumes that are options:ro to ea:none.
161 */
162 static int do_check_ea_support(const struct vol *vol)
163 {
164     int haseas;
165     char eaname[] = {"org.netatalk.supports-eas.XXXXXX"};
166     const char *eacontent = "yes";
167
168     if ((vol->v_flags & AFPVOL_RO) == AFPVOL_RO) {
169         LOG(log_note, logtype_afpd, "read-only volume '%s', can't test for EA support, assuming yes", vol->v_localname);
170         return 1;
171     }
172
173     mktemp(eaname);
174
175     become_root();
176
177     if ((sys_setxattr(vol->v_path, eaname, eacontent, 4, 0)) == 0) {
178         sys_removexattr(vol->v_path, eaname);
179         haseas = 1;
180     } else {
181         LOG(log_warning, logtype_afpd, "volume \"%s\" does not support Extended Attributes",
182             vol->v_localname);
183         haseas = 0;
184     }
185
186     unbecome_root();
187
188     return haseas;
189 }
190
191 static void check_ea_support(struct vol *vol)
192 {
193     int haseas;
194     char eaname[] = {"org.netatalk.supports-eas.XXXXXX"};
195     const char *eacontent = "yes";
196
197     haseas = do_check_ea_support(vol);
198
199     if (vol->v_vfs_ea == AFPVOL_EA_AUTO) {
200         if ((vol->v_flags & AFPVOL_RO) == AFPVOL_RO) {
201             LOG(log_info, logtype_afpd, "read-only volume '%s', can't test for EA support, disabling EAs", vol->v_localname);
202             vol->v_vfs_ea = AFPVOL_EA_NONE;
203             return;
204         }
205
206         if (haseas) {
207             vol->v_vfs_ea = AFPVOL_EA_SYS;
208         } else {
209             LOG(log_warning, logtype_afpd, "volume \"%s\" does not support Extended Attributes, using ea:ad instead",
210                 vol->v_localname);
211             vol->v_vfs_ea = AFPVOL_EA_AD;
212         }
213     }
214
215     if (vol->v_adouble == AD_VERSION_EA) {
216         if (!haseas)
217             vol->v_adouble = AD_VERSION2;
218     }
219 }
220
221 /*!
222  * Check whether a volume supports ACLs
223  *
224  * @param vol  (r) volume
225  *
226  * @returns        0 if not, 1 if yes
227  */
228 static int check_vol_acl_support(const struct vol *vol)
229 {
230     int ret = 0;
231
232 #ifdef HAVE_SOLARIS_ACLS
233     ace_t *aces = NULL;
234     ret = 1;
235     if (get_nfsv4_acl(vol->v_path, &aces) == -1)
236         ret = 0;
237 #endif
238 #ifdef HAVE_POSIX_ACLS
239     acl_t acl = NULL;
240     ret = 1;
241     if ((acl = acl_get_file(vol->v_path, ACL_TYPE_ACCESS)) == NULL)
242         ret = 0;
243 #endif
244
245 #ifdef HAVE_SOLARIS_ACLS
246     if (aces) free(aces);
247 #endif
248 #ifdef HAVE_POSIX_ACLS
249     if (acl) acl_free(acl);
250 #endif /* HAVE_POSIX_ACLS */
251
252     LOG(log_debug, logtype_afpd, "Volume \"%s\" ACL support: %s",
253         vol->v_path, ret ? "yes" : "no");
254     return ret;
255 }
256
257 /*
258  * Handle variable substitutions. here's what we understand:
259  * $b   -> basename of path
260  * $c   -> client ip/appletalk address
261  * $d   -> volume pathname on server
262  * $f   -> full name (whatever's in the gecos field)
263  * $g   -> group
264  * $h   -> hostname
265  * $i   -> client ip/appletalk address without port
266  * $s   -> server name (hostname if it doesn't exist)
267  * $u   -> username (guest is usually nobody)
268  * $v   -> volume name or basename if null
269  * $$   -> $
270  *
271  * This get's called from readvolfile with
272  * path = NULL, volname = NULL for xlating the volumes path
273  * path = path, volname = NULL for xlating the volumes name
274  * ... and from volumes options parsing code when xlating eg dbpath with
275  * path = path, volname = volname
276  *
277  * Using this information we can reject xlation of any variable depeninding on a login
278  * context which is not given in the afp master, where we must evaluate this whole stuff
279  * too for the Zeroconf announcements.
280  */
281 static char *volxlate(const AFPObj *obj,
282                       char *dest,
283                       size_t destlen,
284                       const char *src,
285                       const struct passwd *pwd,
286                       const char *path,
287                       const char *volname)
288 {
289     char *p, *r;
290     const char *q;
291     int len;
292     char *ret;
293     int xlatevolname = 0;
294
295     if (path && !volname)
296         /* cf above */
297         xlatevolname = 1;
298
299     if (!src) {
300         return NULL;
301     }
302     if (!dest) {
303         dest = calloc(destlen +1, 1);
304     }
305     ret = dest;
306     if (!ret) {
307         return NULL;
308     }
309     strlcpy(dest, src, destlen +1);
310     if ((p = strchr(src, '$')) == NULL) /* nothing to do */
311         return ret;
312
313     /* first part of the path. just forward to the next variable. */
314     len = MIN((size_t)(p - src), destlen);
315     if (len > 0) {
316         destlen -= len;
317         dest += len;
318     }
319
320     while (p && destlen > 0) {
321         /* now figure out what the variable is */
322         q = NULL;
323         if (IS_VAR(p, "$b")) {
324             if (path) {
325                 if ((q = strrchr(path, '/')) == NULL)
326                     q = path;
327                 else if (*(q + 1) != '\0')
328                     q++;
329             }
330         } else if (IS_VAR(p, "$c")) {
331             DSI *dsi = obj->dsi;
332             len = sprintf(dest, "%s:%u",
333                           getip_string((struct sockaddr *)&dsi->client),
334                           getip_port((struct sockaddr *)&dsi->client));
335             dest += len;
336             destlen -= len;
337         } else if (IS_VAR(p, "$d")) {
338             q = path;
339         } else if (pwd && IS_VAR(p, "$f")) {
340             if ((r = strchr(pwd->pw_gecos, ',')))
341                 *r = '\0';
342             q = pwd->pw_gecos;
343         } else if (pwd && IS_VAR(p, "$g")) {
344             struct group *grp = getgrgid(pwd->pw_gid);
345             if (grp)
346                 q = grp->gr_name;
347         } else if (IS_VAR(p, "$h")) {
348             q = obj->options.hostname;
349         } else if (IS_VAR(p, "$i")) {
350             DSI *dsi = obj->dsi;
351             q = getip_string((struct sockaddr *)&dsi->client);
352         } else if (IS_VAR(p, "$s")) {
353             q = obj->options.hostname;
354         } else if (obj->username && IS_VAR(p, "$u")) {
355             char* sep = NULL;
356             if ( obj->options.ntseparator && (sep = strchr(obj->username, obj->options.ntseparator[0])) != NULL)
357                 q = sep+1;
358             else
359                 q = obj->username;
360         } else if (IS_VAR(p, "$v")) {
361             if (volname) {
362                 q = volname;
363             }
364             else if (path) {
365                 if ((q = strrchr(path, '/')) == NULL)
366                     q = path;
367                 else if (*(q + 1) != '\0')
368                     q++;
369             }
370         } else if (IS_VAR(p, "$$")) {
371             q = "$";
372         } else
373             q = p;
374
375         /* copy the stuff over. if we don't understand something that we
376          * should, just skip it over. */
377         if (q) {
378             len = MIN(p == q ? 2 : strlen(q), destlen);
379             strncpy(dest, q, len);
380             dest += len;
381             destlen -= len;
382         }
383
384         /* stuff up to next $ */
385         src = p + 2;
386         p = strchr(src, '$');
387         len = p ? MIN((size_t)(p - src), destlen) : destlen;
388         if (len > 0) {
389             strncpy(dest, src, len);
390             dest += len;
391             destlen -= len;
392         }
393     }
394     return ret;
395 }
396
397 /*!
398  * check access list
399  *
400  * this function wants something of the following form:
401  * "@group,name,name2,@group2,name3"
402  * A NULL argument allows everybody to have access.
403  * We return three things:
404  *     -1: no list
405  *      0: list exists, but name isn't in it
406  *      1: in list
407  */
408 static int accessvol(const AFPObj *obj, const char *args, const char *name)
409 {
410     char buf[MAXPATHLEN + 1], *p;
411     struct group *gr;
412
413     if (!args)
414         return -1;
415
416     strlcpy(buf, args, sizeof(buf));
417     if ((p = strtok(buf, ",")) == NULL) /* nothing, return okay */
418         return -1;
419
420     while (p) {
421         if (*p == '@') { /* it's a group */
422             if ((gr = getgrnam(p + 1)) && gmem(gr->gr_gid, obj->ngroups, obj->groups))
423                 return 1;
424         } else if (strcasecmp(p, name) == 0) /* it's a user name */
425             return 1;
426         p = strtok(NULL, ",");
427     }
428
429     return 0;
430 }
431
432 static int hostaccessvol(const AFPObj *obj, const char *volname, const char *args)
433 {
434     int mask_int;
435     char buf[MAXPATHLEN + 1], *p, *b;
436     struct sockaddr_storage client;
437     const DSI *dsi = obj->dsi;
438
439     if (!args)
440         return -1;
441
442     strlcpy(buf, args, sizeof(buf));
443     if ((p = strtok_r(buf, ",", &b)) == NULL) /* nothing, return okay */
444         return -1;
445
446     while (p) {
447         int ret;
448         char *ipaddr, *mask_char;
449         struct addrinfo hints, *ai;
450
451         ipaddr = strtok(p, "/");
452         mask_char = strtok(NULL,"/");
453
454         /* Get address from string with getaddrinfo */
455         memset(&hints, 0, sizeof hints);
456         hints.ai_family = AF_UNSPEC;
457         hints.ai_socktype = SOCK_STREAM;
458         if ((ret = getaddrinfo(ipaddr, NULL, &hints, &ai)) != 0) {
459             LOG(log_error, logtype_afpd, "hostaccessvol: getaddrinfo: %s\n", gai_strerror(ret));
460             continue;
461         }
462
463         /* netmask */
464         if (mask_char != NULL)
465             mask_int = atoi(mask_char); /* apply_ip_mask does range checking on it */
466         else {
467             if (ai->ai_family == AF_INET) /* IPv4 */
468                 mask_int = 32;
469             else                          /* IPv6 */
470                 mask_int = 128;
471         }
472
473         /* Apply mask to addresses */
474         client = dsi->client;
475         apply_ip_mask((struct sockaddr *)&client, mask_int);
476         apply_ip_mask(ai->ai_addr, mask_int);
477
478         if (compare_ip((struct sockaddr *)&client, ai->ai_addr) == 0) {
479             freeaddrinfo(ai);
480             return 1;
481         }
482
483         /* next address */
484         freeaddrinfo(ai);
485         p = strtok_r(NULL, ",", &b);
486     }
487
488     return 0;
489 }
490
491 /*!
492  * Get option string from config, use default value if not set
493  *
494  * @param conf    (r) config handle
495  * @param vol     (r) volume name (must be section name ie wo vars expanded)
496  * @param opt     (r) option
497  * @param def     (r) if "option" is not found in "name", try to find it in section "def"
498  *
499  * @returns       const option string or NULL
500  */
501 static const char *getoption(const dictionary *conf, const char *vol, const char *opt, const char *def)
502 {
503     EC_INIT;
504     const char *result = NULL;
505
506     if (!(result = iniparser_getstring(conf, vol, opt, NULL)))
507         result = iniparser_getstring(conf, def, opt, NULL);
508     
509 EC_CLEANUP:
510     return result;
511 }
512
513 /*!
514  * Create volume struct
515  *
516  * @param obj      (r) handle
517  * @param pwd      (r) struct passwd of logged in user, may be NULL in master afpd
518  * @param section  (r) volume name wo variables expanded (exactly as in iniconfig)
519  * @param name     (r) volume name
520  * @param path     (r) volume path
521  * @param preset   (r) default preset, may be NULL
522  * @returns            vol on success, NULL on error
523  */
524 static struct vol *creatvol(AFPObj *obj,
525                             const struct passwd *pwd,
526                             const char *section,
527                             const char *name,
528                             const char *path,
529                             const char *preset)
530 {
531     EC_INIT;
532     struct vol  *volume = NULL;
533     int         suffixlen, vlen, tmpvlen, u8mvlen, macvlen;
534     char        tmpname[AFPVOL_U8MNAMELEN+1];
535     ucs2_t      u8mtmpname[(AFPVOL_U8MNAMELEN+1)*2], mactmpname[(AFPVOL_MACNAMELEN+1)*2];
536     char        suffix[6]; /* max is #FFFF */
537     uint16_t    flags;
538     const char  *val;
539     char        *p, *q;
540
541     LOG(log_debug, logtype_afpd, "createvol(volume: '%s', path: \"%s\", preset: '%s'): BEGIN",
542         name, path, preset ? preset : "-");
543
544     if ( name == NULL || *name == '\0' ) {
545         if ((name = strrchr( path, '/' )) == NULL) {
546             EC_FAIL;
547         }
548
549         /* if you wish to share /, you need to specify a name. */
550         if (*++name == '\0')
551             EC_FAIL;
552     }
553
554     /* Once volumes are loaded, we never change options again, we just delete em when they're removed from afp.conf */
555     for (struct vol *vol = Volumes; vol; vol = vol->v_next) {
556         if (STRCMP(path, ==, vol->v_path)) {
557             LOG(log_debug, logtype_afpd, "createvol('%s'): already loaded", name);
558             vol->v_deleted = 0;
559             volume = vol;
560             goto EC_CLEANUP;
561         }
562     }
563
564     /*
565      * Check allow/deny lists:
566      * allow -> either no list (-1), or in list (1)
567      * deny -> either no list (-1), or not in list (0)
568      */
569     if (pwd) {
570         if (accessvol(obj, getoption(obj->iniconfig, section, "deny", preset), pwd->pw_name) == 1)
571             goto EC_CLEANUP;
572         if (accessvol(obj, getoption(obj->iniconfig, section, "allow", preset), pwd->pw_name) == 0)
573             goto EC_CLEANUP;
574         if (hostaccessvol(obj, section, getoption(obj->iniconfig, section, "denied_hosts", preset)) == 1)
575             goto EC_CLEANUP;
576         if (hostaccessvol(obj, section, getoption(obj->iniconfig, section, "allowed_hosts", preset)) == 0)
577             goto EC_CLEANUP;
578     }
579
580     EC_NULL( volume = calloc(1, sizeof(struct vol)) );
581
582     volume->v_flags = AFPVOL_USEDOTS | AFPVOL_UNIX_PRIV;
583     EC_NULL( volume->v_configname = strdup(section));
584
585 #ifdef HAVE_ACLS
586     volume->v_flags |= AFPVOL_ACLS;
587 #endif
588     volume->v_vfs_ea = AFPVOL_EA_AUTO;
589     volume->v_umask = obj->options.umask;
590
591     if (val = getoption(obj->iniconfig, section, "password", preset))
592         EC_NULL( volume->v_password = strdup(val) );
593
594     if (val = getoption(obj->iniconfig, section, "veto", preset))
595         EC_NULL( volume->v_password = strdup(val) );
596
597     if (val = getoption(obj->iniconfig, section, "volcharset", preset))
598         EC_NULL( volume->v_volcodepage = strdup(val) );
599     else
600         EC_NULL( volume->v_volcodepage = strdup("UTF8") );
601
602     if (val = getoption(obj->iniconfig, section, "maccharset", preset))
603         EC_NULL( volume->v_maccodepage = strdup(val) );
604     else
605         EC_NULL( volume->v_maccodepage = strdup(obj->options.maccodepage) );
606
607     if (val = getoption(obj->iniconfig, section, "dbpath", preset))
608         EC_NULL( volume->v_dbpath = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
609
610     if (val = getoption(obj->iniconfig, section, "cnidscheme", preset))
611         EC_NULL( volume->v_cnidscheme = strdup(val) );
612
613     if (val = getoption(obj->iniconfig, section, "umask", preset))
614         volume->v_umask = (int)strtol(val, NULL, 8);
615
616     if (val = getoption(obj->iniconfig, section, "dperm", preset))
617         volume->v_dperm = (int)strtol(val, NULL, 8);
618
619     if (val = getoption(obj->iniconfig, section, "fperm", preset))
620         volume->v_fperm = (int)strtol(val, NULL, 8);
621
622     if (val = getoption(obj->iniconfig, section, "perm", preset))
623         volume->v_perm = (int)strtol(val, NULL, 8);
624
625     if (val = getoption(obj->iniconfig, section, "volsizelimit", preset))
626         volume->v_limitsize = (uint32_t)strtoul(val, NULL, 10);
627
628     if (val = getoption(obj->iniconfig, section, "preexec", preset))
629         EC_NULL( volume->v_preexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
630
631     if (val = getoption(obj->iniconfig, section, "postexec", preset))
632         EC_NULL( volume->v_postexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
633
634     if (val = getoption(obj->iniconfig, section, "root_preexec", preset))
635         EC_NULL( volume->v_root_preexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
636
637     if (val = getoption(obj->iniconfig, section, "root_postexec", preset))
638         EC_NULL( volume->v_root_postexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
639
640     if (val = getoption(obj->iniconfig, section, "adouble", preset)) {
641         if (strcmp(val, "v2") == 0)
642             volume->v_adouble = AD_VERSION2;
643         else if (strcmp(val, "ea") == 0)
644             volume->v_adouble = AD_VERSION_EA;
645     } else {
646         volume->v_adouble = AD_VERSION;
647     }
648
649     if (val = getoption(obj->iniconfig, section, "cnidserver", preset)) {
650         EC_NULL( p = strdup(val) );
651         volume->v_cnidserver = p;
652         if (q = strrchr(val, ':')) {
653             *q++ = 0;
654             volume->v_cnidport = q;
655         } else {
656             volume->v_cnidport = "4700";
657         }
658
659     }
660
661     if (val = getoption(obj->iniconfig, section, "ea", preset)) {
662         if (strcasecmp(val, "ad") == 0)
663             volume->v_vfs_ea = AFPVOL_EA_AD;
664         else if (strcasecmp(val, "sys") == 0)
665             volume->v_vfs_ea = AFPVOL_EA_SYS;
666         else if (strcasecmp(val, "none") == 0)
667             volume->v_vfs_ea = AFPVOL_EA_NONE;
668     }
669
670     if (val = getoption(obj->iniconfig, section, "casefold", preset)) {
671         if (strcasecmp(val, "tolower") == 0)
672             volume->v_casefold = AFPVOL_UMLOWER;
673         else if (strcasecmp(val, "toupper") == 0)
674             volume->v_casefold = AFPVOL_UMUPPER;
675         else if (strcasecmp(val, "xlatelower") == 0)
676             volume->v_casefold = AFPVOL_UUPPERMLOWER;
677         else if (strcasecmp(val, "xlateupper") == 0)
678             volume->v_casefold = AFPVOL_ULOWERMUPPER;
679     }
680
681     if (val = getoption(obj->iniconfig, section, "options", preset)) {
682         q = strdup(val);
683         if (p = strtok(q, ", ")) {
684             while (p) {
685                 if (strcasecmp(p, "ro") == 0)
686                     volume->v_flags |= AFPVOL_RO;
687                 else if (strcasecmp(p, "nohex") == 0)
688                     volume->v_flags |= AFPVOL_NOHEX;
689                 else if (strcasecmp(p, "nousedots") == 0)
690                     volume->v_flags &= ~AFPVOL_USEDOTS;
691                 else if (strcasecmp(p, "invisibledots") == 0)
692                     volume->v_flags |= volume->v_flags;
693                 else if (strcasecmp(p, "nostat") == 0)
694                     volume->v_flags |= AFPVOL_NOSTAT;
695                 else if (strcasecmp(p, "noupriv") == 0)
696                     volume->v_flags &= ~AFPVOL_UNIX_PRIV;
697                 else if (strcasecmp(p, "nodev") == 0)
698                     volume->v_flags |= AFPVOL_NODEV;
699                 else if (strcasecmp(p, "caseinsensitive") == 0)
700                     volume->v_flags |= AFPVOL_CASEINSEN;
701                 else if (strcasecmp(p, "illegalseq") == 0)
702                     volume->v_flags |= AFPVOL_EILSEQ;
703                 else if (strcasecmp(p, "tm") == 0)
704                     volume->v_flags |= AFPVOL_TM;
705                 else if (strcasecmp(p, "searchdb") == 0)
706                     volume->v_flags |= AFPVOL_SEARCHDB;
707                 else if (strcasecmp(p, "nonetids") == 0)
708                     volume->v_flags |= AFPVOL_NONETIDS;
709                 else if (strcasecmp(p, "noacls") == 0)
710                     volume->v_flags &= ~AFPVOL_ACLS;
711                 else if (strcasecmp(p, "nov2toeaconv") == 0)
712                     volume->v_flags |= AFPVOL_NOV2TOEACONV;
713                 else if (strcasecmp(p, "preexec_close") == 0)
714                     volume->v_preexec_close = 1;
715                 else if (strcasecmp(p, "root_preexec_close") == 0)
716                     volume->v_root_preexec_close = 1;
717                 p = strtok(NULL, ", ");
718             }
719         }
720         free(q);
721     }
722
723     /*
724      * Handle read-only behaviour. semantics:
725      * 1) neither the rolist nor the rwlist exist -> rw
726      * 2) rolist exists -> ro if user is in it.
727      * 3) rwlist exists -> ro unless user is in it.
728      */
729     if (pwd) {
730         if (accessvol(obj, getoption(obj->iniconfig, section, "rolist", preset), pwd->pw_name) == 1
731             || accessvol(obj, getoption(obj->iniconfig, section, "rwlist", preset), pwd->pw_name) == 0)
732             volume->v_flags |= AFPVOL_RO;
733     }
734
735     if ((volume->v_flags & AFPVOL_NODEV))
736         volume->v_ad_options |= ADVOL_NODEV;
737     if ((volume->v_flags & AFPVOL_UNIX_PRIV))
738         volume->v_ad_options |= ADVOL_UNIXPRIV;
739     if ((volume->v_flags & AFPVOL_INV_DOTS))
740         volume->v_ad_options |= ADVOL_INVDOTS;
741
742     /* Mac to Unix conversion flags*/
743     if (!(volume->v_flags & AFPVOL_NOHEX))
744         volume->v_mtou_flags |= CONV_ESCAPEHEX;
745     if (!(volume->v_flags & AFPVOL_USEDOTS))
746         volume->v_mtou_flags |= CONV_ESCAPEDOTS;
747     if ((volume->v_flags & AFPVOL_EILSEQ))
748         volume->v_mtou_flags |= CONV__EILSEQ;
749
750     if ((volume->v_casefold & AFPVOL_MTOUUPPER))
751         volume->v_mtou_flags |= CONV_TOUPPER;
752     else if ((volume->v_casefold & AFPVOL_MTOULOWER))
753         volume->v_mtou_flags |= CONV_TOLOWER;
754
755     /* Unix to Mac conversion flags*/
756     volume->v_utom_flags = CONV_IGNORE | CONV_UNESCAPEHEX;
757     if ((volume->v_casefold & AFPVOL_UTOMUPPER))
758         volume->v_utom_flags |= CONV_TOUPPER;
759     else if ((volume->v_casefold & AFPVOL_UTOMLOWER))
760         volume->v_utom_flags |= CONV_TOLOWER;
761     if ((volume->v_flags & AFPVOL_EILSEQ))
762         volume->v_utom_flags |= CONV__EILSEQ;
763
764     /* suffix for mangling use (lastvid + 1)   */
765     /* because v_vid has not been decided yet. */
766     suffixlen = sprintf(suffix, "#%X", lastvid + 1 );
767
768
769     vlen = strlen( name );
770
771     /* Unicode Volume Name */
772     /* Firstly convert name from unixcharset to UTF8-MAC */
773     flags = CONV_IGNORE;
774     tmpvlen = convert_charset(obj->options.unixcharset, CH_UTF8_MAC, 0, name, vlen, tmpname, AFPVOL_U8MNAMELEN, &flags);
775     if (tmpvlen <= 0) {
776         strcpy(tmpname, "???");
777         tmpvlen = 3;
778     }
779
780     /* Do we have to mangle ? */
781     if ( (flags & CONV_REQMANGLE) || (tmpvlen > obj->options.volnamelen)) {
782         if (tmpvlen + suffixlen > obj->options.volnamelen) {
783             flags = CONV_FORCE;
784             tmpvlen = convert_charset(obj->options.unixcharset, CH_UTF8_MAC, 0, name, vlen, tmpname, obj->options.volnamelen - suffixlen, &flags);
785             tmpname[tmpvlen >= 0 ? tmpvlen : 0] = 0;
786         }
787         strcat(tmpname, suffix);
788         tmpvlen = strlen(tmpname);
789     }
790
791     /* Secondly convert name from UTF8-MAC to UCS2 */
792     if ( 0 >= ( u8mvlen = convert_string(CH_UTF8_MAC, CH_UCS2, tmpname, tmpvlen, u8mtmpname, AFPVOL_U8MNAMELEN*2)) )
793         EC_FAIL;
794
795     LOG(log_maxdebug, logtype_afpd, "createvol: Volume '%s' -> UTF8-MAC Name: '%s'", name, tmpname);
796
797     /* Maccharset Volume Name */
798     /* Firsty convert name from unixcharset to maccharset */
799     flags = CONV_IGNORE;
800     tmpvlen = convert_charset(obj->options.unixcharset, obj->options.maccharset, 0, name, vlen, tmpname, AFPVOL_U8MNAMELEN, &flags);
801     if (tmpvlen <= 0) {
802         strcpy(tmpname, "???");
803         tmpvlen = 3;
804     }
805
806     /* Do we have to mangle ? */
807     if ( (flags & CONV_REQMANGLE) || (tmpvlen > AFPVOL_MACNAMELEN)) {
808         if (tmpvlen + suffixlen > AFPVOL_MACNAMELEN) {
809             flags = CONV_FORCE;
810             tmpvlen = convert_charset(obj->options.unixcharset,
811                                       obj->options.maccharset,
812                                       0,
813                                       name,
814                                       vlen,
815                                       tmpname,
816                                       AFPVOL_MACNAMELEN - suffixlen,
817                                       &flags);
818             tmpname[tmpvlen >= 0 ? tmpvlen : 0] = 0;
819         }
820         strcat(tmpname, suffix);
821         tmpvlen = strlen(tmpname);
822     }
823
824     /* Secondly convert name from maccharset to UCS2 */
825     if ( 0 >= ( macvlen = convert_string(obj->options.maccharset,
826                                          CH_UCS2,
827                                          tmpname,
828                                          tmpvlen,
829                                          mactmpname,
830                                          AFPVOL_U8MNAMELEN*2)) )
831         EC_FAIL;
832
833     LOG(log_maxdebug, logtype_afpd, "createvol: Volume '%s' ->  Longname: '%s'", name, tmpname);
834
835     EC_NULL( volume->v_localname = strdup(name) );
836     EC_NULL( volume->v_u8mname = strdup_w(u8mtmpname) );
837     EC_NULL( volume->v_macname = strdup_w(mactmpname) );
838     EC_NULL( volume->v_path = malloc(strlen(path) + 1) );
839
840     volume->v_name = utf8_encoding(obj) ? volume->v_u8mname : volume->v_macname;
841     strcpy(volume->v_path, path);
842
843 #ifdef __svr4__
844     volume->v_qfd = -1;
845 #endif /* __svr4__ */
846
847     /* os X start at 1 and use network order ie. 1 2 3 */
848     volume->v_vid = ++lastvid;
849     volume->v_vid = htons(volume->v_vid);
850
851 #ifdef HAVE_ACLS
852     if (!check_vol_acl_support(volume)) {
853         LOG(log_debug, logtype_afpd, "creatvol(\"%s\"): disabling ACL support", volume->v_path);
854         volume->v_flags &= ~AFPVOL_ACLS;
855     }
856 #endif
857
858     volume->v_dperm |= volume->v_perm;
859     volume->v_fperm |= volume->v_perm;
860
861     /* Check EA support on volume */
862     if (volume->v_vfs_ea == AFPVOL_EA_AUTO || volume->v_adouble == AD_VERSION_EA)
863         check_ea_support(volume);
864     initvol_vfs(volume);
865
866     /* get/store uuid from file in afpd master*/
867     if (!(pwd) && (volume->v_flags & AFPVOL_TM)) {
868         char *uuid = get_vol_uuid(obj, volume->v_localname);
869         if (!uuid) {
870             LOG(log_error, logtype_afpd, "Volume '%s': couldn't get UUID",
871                 volume->v_localname);
872         } else {
873             volume->v_uuid = uuid;
874             LOG(log_debug, logtype_afpd, "Volume '%s': UUID '%s'",
875                 volume->v_localname, volume->v_uuid);
876         }
877     }
878
879     /* no errors shall happen beyond this point because the cleanup would mess the volume chain up */
880     volume->v_next = Volumes;
881     Volumes = volume;
882     volume->v_obj = obj;
883
884 EC_CLEANUP:
885     LOG(log_debug, logtype_afpd, "createvol: END: %d", ret);
886     if (ret != 0) {
887         if (volume) {
888             volume_free(volume);
889             free(volume);
890         }
891         return NULL;
892     }
893     return volume;
894 }
895
896 /* ----------------------
897  */
898 static int volfile_changed(struct afp_options *p)
899 {
900     struct stat st;
901
902     if (!stat(p->configfile, &st) && st.st_mtime > p->volfile.mtime) {
903         p->volfile.mtime = st.st_mtime;
904         return 1;
905     }
906     return 0;
907 }
908
909 static int vol_section(const char *sec)
910 {
911     if (STRCMP(sec, ==, INISEC_GLOBAL))
912         return 0;
913     return 1;
914 }
915
916 #define MAXPRESETLEN 100
917 /*!
918  * Read volumes from iniconfig and add the volumes contained within to
919  * the global volume list. This gets called from the forked afpd childs.
920  * The master now reads this too for Zeroconf announcements.
921  */
922 static int readvolfile(AFPObj *obj, const struct passwd *pwent)
923 {
924     EC_INIT;
925     char        path[MAXPATHLEN + 1];
926     char        volname[AFPVOL_U8MNAMELEN + 1];
927     char        tmp[MAXPATHLEN + 1];
928     const char  *preset, *default_preset, *p;
929     char        *q, *u;
930     int         i;
931     struct passwd   *pw;
932
933     LOG(log_debug, logtype_afpd, "readvolfile: BEGIN");
934
935     int secnum = iniparser_getnsec(obj->iniconfig);    
936     LOG(log_debug, logtype_afpd, "readvolfile: sections: %d", secnum);
937     const char *secname;
938
939     if ((default_preset = iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "vol preset", NULL))) {
940         LOG(log_debug, logtype_afpd, "readvolfile: default_preset: %s", default_preset);
941     }
942
943     for (i = 0; i < secnum; i++) { 
944         secname = iniparser_getsecname(obj->iniconfig, i);
945
946         if (!vol_section(secname))
947             continue;
948         if (STRCMP(secname, ==, INISEC_HOMES)) {
949             have_uservol = 1;
950             if (obj->username[0] == 0)
951                 /* not an AFP session, but cnid daemon, dbd or ad util */
952                 continue;
953             if ((p = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "basedir", NULL)) == NULL)
954                 continue;
955             strlcpy(tmp, p, MAXPATHLEN);
956             strlcat(tmp, "/", MAXPATHLEN);
957             strlcat(tmp, obj->username, MAXPATHLEN);
958             strlcat(tmp, "/", MAXPATHLEN);
959             if (p = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "path", NULL))
960                 strlcat(tmp, p, MAXPATHLEN);
961         } else {
962             /* Get path */
963             if ((p = iniparser_getstring(obj->iniconfig, secname, "path", NULL)) == NULL)
964                 continue;
965             strlcpy(tmp, p, MAXPATHLEN);
966         }
967
968         if (volxlate(obj, path, sizeof(path) - 1, tmp, pwent, NULL, NULL) == NULL)
969             continue;
970
971         /* do variable substitution for volume name */
972         if (STRCMP(secname, ==, INISEC_HOMES)) {
973             if (p = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "name", "$u's home"))
974                 strlcpy(tmp, p, MAXPATHLEN);
975             else
976                 strlcpy(tmp, p, MAXPATHLEN);
977         } else {
978             strlcpy(tmp, secname, AFPVOL_U8MNAMELEN);
979         }
980         if (volxlate(obj, volname, sizeof(volname) - 1, tmp, pwent, path, NULL) == NULL)
981             continue;
982
983         preset = iniparser_getstring(obj->iniconfig, secname, "vol preset", NULL);
984
985         creatvol(obj, pwent, secname, volname, path, preset ? preset : default_preset ? default_preset : NULL);
986     }
987
988 EC_CLEANUP:
989     EC_EXIT;
990 }
991
992 /**************************************************************
993  * API functions
994  **************************************************************/
995
996 /*!
997  * Remove a volume from the linked list of volumes
998  */
999 void volume_unlink(struct vol *volume)
1000 {
1001     struct vol *vol, *ovol, *nvol;
1002
1003     if (volume == Volumes) {
1004         Volumes = NULL;
1005         return;
1006     }
1007     for ( vol = Volumes->v_next, ovol = Volumes; vol; vol = nvol) {
1008         nvol = vol->v_next;
1009
1010         if (vol == volume) {
1011             ovol->v_next = nvol;
1012             break;
1013         }
1014         else {
1015             ovol = vol;
1016         }
1017     }
1018 }
1019
1020 /*!
1021  * Free all resources allocated in a struct vol, only struct dir *v_root can't be freed
1022  */
1023 void volume_free(struct vol *vol)
1024 {
1025     LOG(log_debug, logtype_afpd, "volume_free('%s'): BEGIN", vol->v_localname);
1026
1027     free(vol->v_localname);
1028     free(vol->v_u8mname);
1029     free(vol->v_macname);
1030     free(vol->v_path);
1031     free(vol->v_password);
1032     free(vol->v_veto);
1033     free(vol->v_volcodepage);
1034     free(vol->v_maccodepage);
1035     free(vol->v_cnidscheme);
1036     free(vol->v_dbpath);
1037     free(vol->v_gvs);
1038     free(vol->v_uuid);
1039     free(vol->v_cnidserver);
1040 #if 0
1041     /* NO! Just points to v_cnidserver + x */
1042     free(vol->v_cnidport);
1043 #endif
1044     free(vol->v_root_preexec);
1045     free(vol->v_postexec);
1046
1047     LOG(log_debug, logtype_afpd, "volume_free: END");
1048 }
1049
1050 /*!
1051  * Load charsets for a volume
1052  */
1053 int load_charset(struct vol *vol)
1054 {
1055     if ((vol->v_maccharset = add_charset(vol->v_maccodepage)) == (charset_t)-1) {
1056         LOG(log_error, logtype_default, "Setting Mac codepage '%s' failed", vol->v_maccodepage);
1057         return -1;
1058     }
1059
1060     if ((vol->v_volcharset = add_charset(vol->v_volcodepage)) == (charset_t)-1) {
1061         LOG(log_error, logtype_default, "Setting volume codepage '%s' failed", vol->v_volcodepage);
1062         return -1;
1063     }
1064
1065     return 0;
1066 }
1067
1068 /*!
1069  * Initialize volumes and load ini configfile
1070  *
1071  * Depending on the value of obj->uid either access checks are done (!=0) or skipped (=0)
1072  *
1073  * @param obj       (r) handle
1074  * @param delvol_fn (r) callback called for deleted volumes
1075  */
1076 int load_volumes(AFPObj *obj, void (*delvol_fn)(struct vol *))
1077 {
1078     EC_INIT;
1079     int fd = -1;
1080     struct passwd   *pwent = NULL;
1081     struct stat         st;
1082     int retries = 0;
1083     struct vol *vol;
1084
1085     LOG(log_debug, logtype_afpd, "load_volumes: BEGIN");
1086
1087     if (Volumes) {
1088         if (!volfile_changed(&obj->options))
1089             goto EC_CLEANUP;
1090         have_uservol = 0;
1091         for (vol = Volumes; vol; vol = vol->v_next) {
1092             if (vol->v_flags & AFPVOL_UNIX_CTXT)
1093                 continue;
1094             vol->v_deleted = 1;
1095         }
1096     } else {
1097         LOG(log_debug, logtype_afpd, "load_volumes: no volumes yet");
1098         EC_ZERO_LOG( lstat(obj->options.configfile, &st) );
1099         obj->options.volfile.mtime = st.st_mtime;
1100     }
1101
1102     /* try putting a read lock on the volume file twice, sleep 1 second if first attempt fails */
1103
1104     fd = open(obj->options.configfile, O_RDONLY);
1105
1106     while (retries < 2) {
1107         if ((read_lock(fd, 0, SEEK_SET, 0)) != 0) {
1108             retries++;
1109             if (!retries) {
1110                 LOG(log_error, logtype_afpd, "readvolfile: can't lock configfile \"%s\"",
1111                     obj->options.configfile);
1112                 EC_FAIL;
1113             }
1114             sleep(1);
1115             continue;
1116         }
1117         break;
1118     }
1119
1120     if (obj->uid)
1121         pwent = getpwuid(obj->uid);
1122
1123     if (obj->iniconfig)
1124         iniparser_freedict(obj->iniconfig);
1125     LOG(log_debug, logtype_afpd, "load_volumes: loading: %s", obj->options.configfile);
1126     obj->iniconfig = iniparser_load(obj->options.configfile);
1127
1128     EC_ZERO_LOG( readvolfile(obj, pwent) );
1129
1130     for ( vol = Volumes; vol; vol = vol->v_next ) {
1131         if (vol->v_deleted) {
1132             LOG(log_debug, logtype_afpd, "load_volumes: deleted: %s", vol->v_localname);
1133             if (delvol_fn)
1134                 delvol_fn(vol);
1135             vol = Volumes;
1136         }
1137     }
1138
1139 EC_CLEANUP:
1140     if (fd != -1)
1141         (void)close(fd);
1142
1143     LOG(log_debug, logtype_afpd, "load_volumes: END");
1144     EC_EXIT;
1145 }
1146
1147 void unload_volumes(AFPObj *obj)
1148 {
1149     struct vol *vol;
1150
1151     LOG(log_debug, logtype_afpd, "unload_volumes: BEGIN");
1152
1153     for (vol = Volumes; vol; vol = vol->v_next)
1154         volume_free(vol);
1155     Volumes = NULL;
1156     obj->options.volfile.mtime = 0;
1157     
1158     LOG(log_debug, logtype_afpd, "unload_volumes: END");
1159 }
1160
1161 struct vol *getvolumes(void)
1162 {
1163     return Volumes;
1164 }
1165
1166 struct vol *getvolbyvid(const uint16_t vid )
1167 {
1168     struct vol  *vol;
1169
1170     for ( vol = Volumes; vol; vol = vol->v_next ) {
1171         if ( vid == vol->v_vid ) {
1172             break;
1173         }
1174     }
1175     if ( vol == NULL || ( vol->v_flags & AFPVOL_OPEN ) == 0 ) {
1176         return( NULL );
1177     }
1178
1179     return( vol );
1180 }
1181
1182 struct vol *getvolbypath(AFPObj *obj, const char *path)
1183 {
1184     EC_INIT;
1185     struct vol *vol;
1186     struct vol *tmp;
1187     const struct passwd *pw;
1188     char        volname[AFPVOL_U8MNAMELEN + 1];
1189     char        volpath[MAXPATHLEN + 1];
1190     char        tmpbuf[MAXPATHLEN + 1];
1191     const char *secname, *basedir, *p = NULL, *subpath = NULL, *subpathconfig;
1192     char *user = NULL, *prw;
1193
1194     LOG(log_debug, logtype_afpd, "getvolbypath(\"%s\")", path);
1195
1196     for (tmp = Volumes; tmp; tmp = tmp->v_next) {
1197         if (strncmp(path, tmp->v_path, strlen(tmp->v_path)) == 0) {
1198             vol = tmp;
1199             goto EC_CLEANUP;
1200         }
1201     }
1202
1203     /* might be a user home, check for that and create a volume if yes */
1204     if (!have_uservol)
1205         EC_FAIL;
1206
1207     int secnum = iniparser_getnsec(obj->iniconfig);
1208
1209     for (int i = 0; i < secnum; i++) { 
1210         secname = iniparser_getsecname(obj->iniconfig, i);
1211         if (STRCMP(secname, ==, INISEC_HOMES))
1212             break;
1213     }
1214
1215     if (STRCMP(secname, !=, INISEC_HOMES))
1216         EC_FAIL;
1217
1218     EC_NULL_LOG( basedir = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "basedir", NULL) );
1219
1220     LOG(log_debug, logtype_afpd, "getvolbypath: user home section: '%s', basedir: '%s'", secname, basedir);
1221
1222     if (strncmp(path, basedir, strlen(basedir)) != 0)
1223         EC_FAIL;
1224
1225     strlcpy(tmpbuf, basedir, MAXPATHLEN);
1226     strlcat(tmpbuf, "/", MAXPATHLEN);
1227
1228     p = path + strlen(basedir);
1229     while (*p == '/')
1230         p++;
1231     EC_NULL_LOG( user = strdup(p) );
1232
1233     if (prw = strchr(user, '/'))
1234         *prw++ = 0;
1235     if (prw != 0)
1236         subpath = prw;
1237
1238     strlcat(tmpbuf, user, MAXPATHLEN);
1239     strlcat(tmpbuf, "/", MAXPATHLEN);
1240
1241     if (subpathconfig = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "path", NULL)) {
1242         if (!subpath || strncmp(subpathconfig, subpath, strlen(subpathconfig)) != 0) {
1243             EC_FAIL;
1244         }
1245     }
1246
1247     strlcat(tmpbuf, subpathconfig, MAXPATHLEN);
1248     strlcat(tmpbuf, "/", MAXPATHLEN);
1249
1250     if (volxlate(obj, volpath, sizeof(volpath) - 1, tmpbuf, pw, NULL, NULL) == NULL)
1251         return NULL;
1252
1253     EC_NULL( pw = getpwnam(user) );
1254
1255     LOG(log_debug, logtype_afpd, "getvolbypath(\"%s\"): user: %s, homedir: %s => volpath: \"%s\"",
1256         path, user, pw->pw_dir, volpath);
1257
1258     /* do variable substitution for volume name */
1259     p = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "name", "$u's home");
1260     strlcpy(tmpbuf, p, AFPVOL_U8MNAMELEN);
1261     EC_NULL_LOG( volxlate(obj, volname, sizeof(volname) - 1, tmpbuf, pw, volpath, NULL) );
1262
1263     const char  *preset, *default_preset;
1264     default_preset = iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "vol preset", NULL);
1265     preset = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "vol preset", NULL);
1266
1267     vol = creatvol(obj, pw, INISEC_HOMES, volname, volpath, preset ? preset : default_preset ? default_preset : NULL);
1268
1269 EC_CLEANUP:
1270     endpwent();
1271     if (user)
1272         free(user);
1273     if (ret != 0)
1274         vol = NULL;
1275     return vol;
1276 }
1277
1278 struct vol *getvolbyname(const char *name)
1279 {
1280     struct vol *vol = NULL;
1281     struct vol *tmp;
1282
1283     for (tmp = Volumes; tmp; tmp = tmp->v_next) {
1284         if (strncmp(name, tmp->v_configname, strlen(tmp->v_configname)) == 0) {
1285             vol = tmp;
1286             break;
1287         }
1288     }
1289     return vol;
1290 }
1291
1292 #define MAXVAL 1024
1293 /*!
1294  * Initialize an AFPObj and options from ini config file
1295  */
1296 int afp_config_parse(AFPObj *AFPObj)
1297 {
1298     EC_INIT;
1299     dictionary *config;
1300     struct afp_options *options = &AFPObj->options;
1301     int i, c;
1302     const char *p, *tmp;
1303     char *q, *r;
1304     char val[MAXVAL];
1305
1306     AFPObj->afp_version = 11;
1307     options->configfile  = AFPObj->cmdlineconfigfile ? strdup(AFPObj->cmdlineconfigfile) : strdup(_PATH_CONFDIR "afp.conf");
1308     options->sigconffile = strdup(_PATH_CONFDIR "afp_signature.conf");
1309     options->uuidconf    = strdup(_PATH_CONFDIR "afp_voluuid.conf");
1310     options->flags       = OPTION_ACL2MACCESS | OPTION_UUID | OPTION_SERVERNOTIF | AFPObj->cmdlineflags;
1311     
1312     if ((config = iniparser_load(AFPObj->options.configfile)) == NULL)
1313         return -1;
1314     AFPObj->iniconfig = config;
1315
1316     /* [Global] */
1317     options->logconfig = iniparser_getstrdup(config, INISEC_GLOBAL, "loglevel", "default:note");
1318     options->logfile   = iniparser_getstrdup(config, INISEC_GLOBAL, "logfile",  NULL);
1319
1320     /* [AFP] "options" options wo values */
1321     if (p = iniparser_getstrdup(config, INISEC_GLOBAL, "options", NULL)) {
1322         if (p = strtok(q, ", ")) {
1323             while (p) {
1324                 if (strcasecmp(p, "nozeroconf"))
1325                     options->flags |= OPTION_NOZEROCONF;
1326                 if (strcasecmp(p, "icon"))
1327                     options->flags |= OPTION_CUSTOMICON;
1328                 if (strcasecmp(p, "noicon"))
1329                     options->flags &= ~OPTION_CUSTOMICON;
1330                 if (strcasecmp(p, "advertise_ssh"))
1331                     options->flags |= OPTION_ANNOUNCESSH;
1332                 if (strcasecmp(p, "noacl2maccess"))
1333                     options->flags &= ~OPTION_ACL2MACCESS;
1334                 if (strcasecmp(p, "keepsessions"))
1335                     options->flags |= OPTION_KEEPSESSIONS;
1336                 if (strcasecmp(p, "closevol"))
1337                     options->flags |= OPTION_CLOSEVOL;
1338                 if (strcasecmp(p, "client_polling"))
1339                     options->flags &= ~OPTION_SERVERNOTIF;
1340                 if (strcasecmp(p, "nosavepassword"))
1341                     options->passwdbits |= PASSWD_NOSAVE;
1342                 if (strcasecmp(p, "savepassword"))
1343                     options->passwdbits &= ~PASSWD_NOSAVE;
1344                 if (strcasecmp(p, "nosetpassword"))
1345                     options->passwdbits &= ~PASSWD_SET;
1346                 if (strcasecmp(p, "setpassword"))
1347                     options->passwdbits |= PASSWD_SET;
1348                 p = strtok(NULL, ", ");
1349             }
1350         }
1351     }
1352     /* figure out options w values */
1353
1354     options->loginmesg      = iniparser_getstrdup(config, INISEC_GLOBAL, "loginmesg",      "");
1355     options->guest          = iniparser_getstrdup(config, INISEC_GLOBAL, "guestname",      "nobody");
1356     options->passwdfile     = iniparser_getstrdup(config, INISEC_GLOBAL, "passwdfile",     _PATH_AFPDPWFILE);
1357     options->uampath        = iniparser_getstrdup(config, INISEC_GLOBAL, "uampath",        _PATH_AFPDUAMPATH);
1358     options->uamlist        = iniparser_getstrdup(config, INISEC_GLOBAL, "uamlist",        "uams_dhx.so,uams_dhx2.so");
1359     options->port           = iniparser_getstrdup(config, INISEC_GLOBAL, "afp port",       "548");
1360     options->signatureopt   = iniparser_getstrdup(config, INISEC_GLOBAL, "signature",      "auto");
1361     options->k5service      = iniparser_getstrdup(config, INISEC_GLOBAL, "k5service",      NULL);
1362     options->k5realm        = iniparser_getstrdup(config, INISEC_GLOBAL, "k5realm",        NULL);
1363     options->listen         = iniparser_getstrdup(config, INISEC_GLOBAL, "afp listen",     NULL);
1364     options->ntdomain       = iniparser_getstrdup(config, INISEC_GLOBAL, "ntdomain",       NULL);
1365     options->ntseparator    = iniparser_getstrdup(config, INISEC_GLOBAL, "ntseparator",    NULL);
1366     options->mimicmodel     = iniparser_getstrdup(config, INISEC_GLOBAL, "mimicmodel",     NULL);
1367     options->adminauthuser  = iniparser_getstrdup(config, INISEC_GLOBAL, "adminauthuser",  NULL);
1368     options->connections    = iniparser_getint   (config, INISEC_GLOBAL, "maxcon",         200);
1369     options->passwdminlen   = iniparser_getint   (config, INISEC_GLOBAL, "passwdminlen",   0);
1370     options->tickleval      = iniparser_getint   (config, INISEC_GLOBAL, "tickleval",      30);
1371     options->timeout        = iniparser_getint   (config, INISEC_GLOBAL, "timeout",        4);
1372     options->dsireadbuf     = iniparser_getint   (config, INISEC_GLOBAL, "dsireadbuf",     12);
1373     options->server_quantum = iniparser_getint   (config, INISEC_GLOBAL, "server_quantum", DSI_SERVQUANT_DEF);
1374     options->volnamelen     = iniparser_getint   (config, INISEC_GLOBAL, "volnamelen",     80);
1375     options->dircachesize   = iniparser_getint   (config, INISEC_GLOBAL, "dircachesize",   DEFAULT_MAX_DIRCACHE_SIZE);
1376     options->tcp_sndbuf     = iniparser_getint   (config, INISEC_GLOBAL, "tcpsndbuf",      0);
1377     options->tcp_rcvbuf     = iniparser_getint   (config, INISEC_GLOBAL, "tcprcvbuf",      0);
1378     options->fce_fmodwait   = iniparser_getint   (config, INISEC_GLOBAL, "fceholdfmod",    60);
1379     options->sleep          = iniparser_getint   (config, INISEC_GLOBAL, "sleep",          10);
1380     options->disconnected   = iniparser_getint   (config, INISEC_GLOBAL, "disconnect",     24);
1381
1382     if ((p = iniparser_getstring(config, INISEC_GLOBAL, "hostname", NULL))) {
1383         EC_NULL_LOG( options->hostname = strdup(p) );
1384     } else {
1385         if (gethostname(val, sizeof(val)) < 0 ) {
1386             perror( "gethostname" );
1387             EC_FAIL;
1388         }
1389         if ((q = strchr(val, '.')))
1390             *q = '\0';
1391         options->hostname = strdup(val);
1392     }
1393
1394     if ((p = iniparser_getstring(config, INISEC_GLOBAL, "k5keytab", NULL))) {
1395         EC_NULL_LOG( options->k5keytab = malloc(strlen(p) + 14) );
1396         snprintf(options->k5keytab, strlen(p) + 14, "KRB5_KTNAME=%s", p);
1397         putenv(options->k5keytab);
1398     }
1399
1400 #ifdef ADMIN_GRP
1401     if ((p = iniparser_getstring(config, INISEC_GLOBAL, "admingroup",  NULL))) {
1402          struct group *gr = getgrnam(p);
1403          if (gr != NULL)
1404              options->admingid = gr->gr_gid;
1405     }
1406 #endif /* ADMIN_GRP */
1407
1408     q = iniparser_getstrdup(config, INISEC_GLOBAL, "cnidserver", "localhost:4700");
1409     r = strrchr(q, ':');
1410     if (r)
1411         *r = 0;
1412     options->Cnid_srv = strdup(q);
1413     if (r)
1414         options->Cnid_port = strdup(r + 1);
1415     else
1416         options->Cnid_port = strdup("4700");
1417     LOG(log_debug, logtype_afpd, "CNID Server: %s:%s", options->Cnid_srv, options->Cnid_port);
1418     if (q)
1419         free(q);
1420
1421     if ((q = iniparser_getstrdup(config, INISEC_GLOBAL, "fqdn", NULL))) {
1422         /* do a little checking for the domain name. */
1423         r = strchr(q, ':');
1424         if (r)
1425             *r = '\0';
1426         if (gethostbyname(q)) {
1427             if (r)
1428                 *r = ':';
1429             EC_NULL_LOG( options->fqdn = strdup(q) );
1430         } else {
1431             LOG(log_error, logtype_afpd, "error parsing -fqdn, gethostbyname failed for: %s", c);
1432         }
1433         free(q);
1434     }
1435
1436     if (!(p = iniparser_getstring(config, INISEC_GLOBAL, "unixcodepage", NULL))) {
1437         options->unixcharset = CH_UNIX;
1438         options->unixcodepage = strdup("LOCALE");
1439     } else {
1440         if ((options->unixcharset = add_charset(p)) == (charset_t)-1) {
1441             options->unixcharset = CH_UNIX;
1442             options->unixcodepage = strdup("LOCALE");
1443             LOG(log_warning, logtype_afpd, "Setting Unix codepage to '%s' failed", p);
1444         } else {
1445             options->unixcodepage = strdup(p);
1446         }
1447     }
1448         
1449     if (!(p = iniparser_getstring(config, INISEC_GLOBAL, "maccodepage", NULL))) {
1450         options->maccharset = CH_MAC;
1451         options->maccodepage = strdup("MAC_ROMAN");
1452     } else {
1453         if ((options->maccharset = add_charset(p)) == (charset_t)-1) {
1454             options->maccharset = CH_MAC;
1455             options->maccodepage = strdup("MAC_ROMAN");
1456             LOG(log_warning, logtype_afpd, "Setting Mac codepage to '%s' failed", p);
1457         } else {
1458             options->maccodepage = strdup(p);
1459         }
1460     }
1461
1462     /* Check for sane values */
1463     if (options->tickleval <= 0)
1464         options->tickleval = 30;
1465     options->disconnected *= 3600 / options->tickleval;
1466     options->sleep *= 3600 / options->tickleval;
1467     if (options->timeout <= 0)
1468         options->timeout = 4;
1469     if (options->sleep <= 4)
1470         options->disconnected = options->sleep = 4;
1471     if (options->dsireadbuf < 6)
1472         options->dsireadbuf = 6;
1473     if (options->volnamelen < 8)
1474         options->volnamelen = 8; /* max mangled volname "???#FFFF" */
1475     if (options->volnamelen > 255)
1476             options->volnamelen = 255; /* AFP3 spec */
1477
1478 EC_CLEANUP:
1479     EC_EXIT;
1480 }
1481