]> arthur.barton.de Git - netatalk.git/blobdiff - libatalk/util/netatalk_conf.c
Merge remote branch 'netafp/master' into branch-allea
[netatalk.git] / libatalk / util / netatalk_conf.c
index fe3b956aef071f21eb31458ebe4271630aadfe91..8a4cfb2afe67ca42d3ee4e4eb837bd82ed843874 100644 (file)
@@ -30,6 +30,7 @@
 #include <arpa/inet.h>
 #include <inttypes.h>
 #include <time.h>
+#include <regex.h>
 
 #include <atalk/afp.h>
 #include <atalk/util.h>
@@ -45,6 +46,9 @@
 #include <atalk/netatalk_conf.h>
 
 #define VOLPASSLEN  8
+#ifndef UUID_PRINTABLE_STRING_LENGTH
+#define UUID_PRINTABLE_STRING_LENGTH 37
+#endif
 
 #define IS_VAR(a, b) (strncmp((a), (b), 2) == 0)
 
@@ -52,6 +56,7 @@
  * Locals
  **************************************************************/
 
+static int have_uservol = 0; /* whether there's generic user home share in config ("~" or "~/path", but not "~user") */
 static struct vol *Volumes = NULL;
 static uint16_t    lastvid = 0;
 
@@ -177,7 +182,7 @@ static int do_check_ea_support(const struct vol *vol)
         sys_removexattr(vol->v_path, eaname);
         haseas = 1;
     } else {
-        LOG(log_warning, logtype_afpd, "volume \"%s\" does not support Extended Attributes",
+        LOG(log_warning, logtype_afpd, "volume \"%s\" does not support Extended Attributes or read-only volume root",
             vol->v_localname);
         haseas = 0;
     }
@@ -320,8 +325,6 @@ static char *volxlate(const AFPObj *obj,
         /* now figure out what the variable is */
         q = NULL;
         if (IS_VAR(p, "$b")) {
-            if (!obj->uid && xlatevolname)
-                return NULL;
             if (path) {
                 if ((q = strrchr(path, '/')) == NULL)
                     q = path;
@@ -329,8 +332,6 @@ static char *volxlate(const AFPObj *obj,
                     q++;
             }
         } else if (IS_VAR(p, "$c")) {
-            if (!obj->uid  && xlatevolname)
-                return NULL;
             DSI *dsi = obj->dsi;
             len = sprintf(dest, "%s:%u",
                           getip_string((struct sockaddr *)&dsi->client),
@@ -338,41 +339,29 @@ static char *volxlate(const AFPObj *obj,
             dest += len;
             destlen -= len;
         } else if (IS_VAR(p, "$d")) {
-            if (!obj->uid  && xlatevolname)
-                return NULL;
             q = path;
         } else if (pwd && IS_VAR(p, "$f")) {
-            if (!obj->uid  && xlatevolname)
-                return NULL;
             if ((r = strchr(pwd->pw_gecos, ',')))
                 *r = '\0';
             q = pwd->pw_gecos;
         } else if (pwd && IS_VAR(p, "$g")) {
-            if (!obj->uid  && xlatevolname)
-                return NULL;
             struct group *grp = getgrgid(pwd->pw_gid);
             if (grp)
                 q = grp->gr_name;
         } else if (IS_VAR(p, "$h")) {
             q = obj->options.hostname;
         } else if (IS_VAR(p, "$i")) {
-            if (!obj->uid  && xlatevolname)
-                return NULL;
             DSI *dsi = obj->dsi;
             q = getip_string((struct sockaddr *)&dsi->client);
         } else if (IS_VAR(p, "$s")) {
             q = obj->options.hostname;
         } else if (obj->username && IS_VAR(p, "$u")) {
-            if (!obj->uid  && xlatevolname)
-                return NULL;
             char* sep = NULL;
             if ( obj->options.ntseparator && (sep = strchr(obj->username, obj->options.ntseparator[0])) != NULL)
                 q = sep+1;
             else
                 q = obj->username;
         } else if (IS_VAR(p, "$v")) {
-            if (!obj->uid  && xlatevolname)
-                return NULL;
             if (volname) {
                 q = volname;
             }
@@ -451,7 +440,7 @@ static int hostaccessvol(const AFPObj *obj, const char *volname, const char *arg
     struct sockaddr_storage client;
     const DSI *dsi = obj->dsi;
 
-    if (!args)
+    if (!args || !dsi)
         return -1;
 
     strlcpy(buf, args, sizeof(buf));
@@ -507,7 +496,7 @@ static int hostaccessvol(const AFPObj *obj, const char *volname, const char *arg
  * Get option string from config, use default value if not set
  *
  * @param conf    (r) config handle
- * @param vol     (r) volume name
+ * @param vol     (r) volume name (must be section name ie wo vars expanded)
  * @param opt     (r) option
  * @param def     (r) if "option" is not found in "name", try to find it in section "def"
  *
@@ -518,8 +507,8 @@ static const char *getoption(const dictionary *conf, const char *vol, const char
     EC_INIT;
     const char *result = NULL;
 
-    if (!(result = iniparser_getstring(conf, vol, opt, NULL)))
-        result = iniparser_getstring(conf, vol, def, NULL);
+    if ((!(result = iniparser_getstring(conf, vol, opt, NULL))) && (def != NULL))
+        result = iniparser_getstring(conf, def, opt, NULL);
     
 EC_CLEANUP:
     return result;
@@ -530,16 +519,18 @@ EC_CLEANUP:
  *
  * @param obj      (r) handle
  * @param pwd      (r) struct passwd of logged in user, may be NULL in master afpd
- * @param path     (r) volume path
+ * @param section  (r) volume name wo variables expanded (exactly as in iniconfig)
  * @param name     (r) volume name
+ * @param path     (r) volume path
  * @param preset   (r) default preset, may be NULL
- * @returns               0 on success, -1 on error
+ * @returns            vol on success, NULL on error
  */
-static int creatvol(AFPObj *obj,
-                    const struct passwd *pwd,
-                    const char *path,
-                    const char *name,
-                    const char *preset)
+static struct vol *creatvol(AFPObj *obj,
+                            const struct passwd *pwd,
+                            const char *section,
+                            const char *name,
+                            const char *path,
+                            const char *preset)
 {
     EC_INIT;
     struct vol  *volume = NULL;
@@ -551,7 +542,8 @@ static int creatvol(AFPObj *obj,
     const char  *val;
     char        *p, *q;
 
-    LOG(log_debug, logtype_afpd, "createvol: Volume '%s'", name);
+    LOG(log_debug, logtype_afpd, "createvol(volume: '%s', path: \"%s\", preset: '%s'): BEGIN",
+        name, path, preset ? preset : "-");
 
     if ( name == NULL || *name == '\0' ) {
         if ((name = strrchr( path, '/' )) == NULL) {
@@ -565,9 +557,10 @@ static int creatvol(AFPObj *obj,
 
     /* Once volumes are loaded, we never change options again, we just delete em when they're removed from afp.conf */
     for (struct vol *vol = Volumes; vol; vol = vol->v_next) {
-        if (STRCMP(name, ==, vol->v_localname)) {
+        if (STRCMP(path, ==, vol->v_path)) {
             LOG(log_debug, logtype_afpd, "createvol('%s'): already loaded", name);
             vol->v_deleted = 0;
+            volume = vol;
             goto EC_CLEANUP;
         }
     }
@@ -578,71 +571,79 @@ static int creatvol(AFPObj *obj,
      * deny -> either no list (-1), or not in list (0)
      */
     if (pwd) {
-        if (accessvol(obj, getoption(obj->iniconfig, name, "deny", preset), pwd->pw_name) == 1)
+        if (accessvol(obj, getoption(obj->iniconfig, section, "deny", preset), pwd->pw_name) == 1)
             goto EC_CLEANUP;
-        if (accessvol(obj, getoption(obj->iniconfig, name, "allow", preset), pwd->pw_name) == 0)
+        if (accessvol(obj, getoption(obj->iniconfig, section, "allow", preset), pwd->pw_name) == 0)
             goto EC_CLEANUP;
-        if (hostaccessvol(obj, name, getoption(obj->iniconfig, name, "denied_hosts", preset)) == 1)
+        if (hostaccessvol(obj, section, getoption(obj->iniconfig, section, "denied_hosts", preset)) == 1)
             goto EC_CLEANUP;
-        if (hostaccessvol(obj, name, getoption(obj->iniconfig, name, "allowed_hosts", preset)) == 0)
+        if (hostaccessvol(obj, section, getoption(obj->iniconfig, section, "allowed_hosts", preset)) == 0)
             goto EC_CLEANUP;
     }
 
     EC_NULL( volume = calloc(1, sizeof(struct vol)) );
 
     volume->v_flags = AFPVOL_USEDOTS | AFPVOL_UNIX_PRIV;
+    EC_NULL( volume->v_configname = strdup(section));
+
 #ifdef HAVE_ACLS
     volume->v_flags |= AFPVOL_ACLS;
 #endif
     volume->v_vfs_ea = AFPVOL_EA_AUTO;
     volume->v_umask = obj->options.umask;
 
-    if (val = getoption(obj->iniconfig, name, "password", preset))
+    if (val = getoption(obj->iniconfig, section, "password", preset))
         EC_NULL( volume->v_password = strdup(val) );
 
-    if (val = getoption(obj->iniconfig, name, "veto", preset))
-        EC_NULL( volume->v_password = strdup(val) );
+    if (val = getoption(obj->iniconfig, section, "veto", preset))
+        EC_NULL( volume->v_veto = strdup(val) );
 
-    if (val = getoption(obj->iniconfig, name, "volcharset", preset))
+    if (val = getoption(obj->iniconfig, section, "volcharset", preset))
         EC_NULL( volume->v_volcodepage = strdup(val) );
+    else
+        EC_NULL( volume->v_volcodepage = strdup("UTF8") );
 
-    if (val = getoption(obj->iniconfig, name, "maccharset", preset))
+    if (val = getoption(obj->iniconfig, section, "maccharset", preset))
         EC_NULL( volume->v_maccodepage = strdup(val) );
+    else
+        EC_NULL( volume->v_maccodepage = strdup(obj->options.maccodepage) );
 
-    if (val = getoption(obj->iniconfig, name, "dbpath", preset))
+    if (val = getoption(obj->iniconfig, section, "dbpath", preset))
         EC_NULL( volume->v_dbpath = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
 
-    if (val = getoption(obj->iniconfig, name, "cnidscheme", preset))
+    if (val = getoption(obj->iniconfig, section, "cnidscheme", preset))
         EC_NULL( volume->v_cnidscheme = strdup(val) );
+    else
+        volume->v_cnidscheme = strdup(DEFAULT_CNID_SCHEME);
 
-    if (val = getoption(obj->iniconfig, name, "umask", preset))
+    if (val = getoption(obj->iniconfig, section, "umask", preset))
         volume->v_umask = (int)strtol(val, NULL, 8);
 
-    if (val = getoption(obj->iniconfig, name, "dperm", preset))
+    if (val = getoption(obj->iniconfig, section, "dperm", preset))
         volume->v_dperm = (int)strtol(val, NULL, 8);
 
-    if (val = getoption(obj->iniconfig, name, "fperm", preset))
+    if (val = getoption(obj->iniconfig, section, "fperm", preset))
         volume->v_fperm = (int)strtol(val, NULL, 8);
 
-    if (val = getoption(obj->iniconfig, name, "perm", preset))
+    if (val = getoption(obj->iniconfig, section, "perm", preset))
         volume->v_perm = (int)strtol(val, NULL, 8);
 
-    if (val = getoption(obj->iniconfig, name, "volsizelimit", preset))
+    if (val = getoption(obj->iniconfig, section, "volsizelimit", preset))
         volume->v_limitsize = (uint32_t)strtoul(val, NULL, 10);
 
-    if (val = getoption(obj->iniconfig, name, "preexec", preset))
+    if (val = getoption(obj->iniconfig, section, "preexec", preset))
         EC_NULL( volume->v_preexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
 
-    if (val = getoption(obj->iniconfig, name, "postexec", preset))
+    if (val = getoption(obj->iniconfig, section, "postexec", preset))
         EC_NULL( volume->v_postexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
 
-    if (val = getoption(obj->iniconfig, name, "root_preexec", preset))
+    if (val = getoption(obj->iniconfig, section, "root_preexec", preset))
         EC_NULL( volume->v_root_preexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
 
-    if (val = getoption(obj->iniconfig, name, "root_postexec", preset))
+    if (val = getoption(obj->iniconfig, section, "root_postexec", preset))
         EC_NULL( volume->v_root_postexec = volxlate(obj, NULL, MAXPATHLEN, val, pwd, path, name) );
 
-    if (val = getoption(obj->iniconfig, name, "adouble", preset)) {
+    if (val = getoption(obj->iniconfig, section, "adouble", preset)) {
         if (strcmp(val, "v2") == 0)
             volume->v_adouble = AD_VERSION2;
         else if (strcmp(val, "ea") == 0)
@@ -651,19 +652,22 @@ static int creatvol(AFPObj *obj,
         volume->v_adouble = AD_VERSION;
     }
 
-    if (val = getoption(obj->iniconfig, name, "cnidserver", preset)) {
+    if (val = getoption(obj->iniconfig, section, "cnidserver", preset)) {
         EC_NULL( p = strdup(val) );
         volume->v_cnidserver = p;
         if (q = strrchr(val, ':')) {
             *q++ = 0;
-            volume->v_cnidport = q;
+            volume->v_cnidport = strdup(q);
         } else {
-            volume->v_cnidport = "4700";
+            volume->v_cnidport = strdup("4700");
         }
 
+    } else {
+        volume->v_cnidserver = strdup(obj->options.Cnid_srv);
+        volume->v_cnidport = strdup(obj->options.Cnid_port);
     }
 
-    if (val = getoption(obj->iniconfig, name, "ea", preset)) {
+    if (val = getoption(obj->iniconfig, section, "ea", preset)) {
         if (strcasecmp(val, "ad") == 0)
             volume->v_vfs_ea = AFPVOL_EA_AD;
         else if (strcasecmp(val, "sys") == 0)
@@ -672,7 +676,7 @@ static int creatvol(AFPObj *obj,
             volume->v_vfs_ea = AFPVOL_EA_NONE;
     }
 
-    if (val = getoption(obj->iniconfig, name, "casefold", preset)) {
+    if (val = getoption(obj->iniconfig, section, "casefold", preset)) {
         if (strcasecmp(val, "tolower") == 0)
             volume->v_casefold = AFPVOL_UMLOWER;
         else if (strcasecmp(val, "toupper") == 0)
@@ -683,7 +687,7 @@ static int creatvol(AFPObj *obj,
             volume->v_casefold = AFPVOL_ULOWERMUPPER;
     }
 
-    if (val = getoption(obj->iniconfig, name, "options", preset)) {
+    if (val = getoption(obj->iniconfig, section, "options", preset)) {
         q = strdup(val);
         if (p = strtok(q, ", ")) {
             while (p) {
@@ -732,8 +736,8 @@ static int creatvol(AFPObj *obj,
      * 3) rwlist exists -> ro unless user is in it.
      */
     if (pwd) {
-        if (accessvol(obj, getoption(obj->iniconfig, name, "rolist", preset), pwd->pw_name) == 1
-            || accessvol(obj, getoption(obj->iniconfig, name, "rwlist", preset), pwd->pw_name) == 0)
+        if (accessvol(obj, getoption(obj->iniconfig, section, "rolist", preset), pwd->pw_name) == 1
+            || accessvol(obj, getoption(obj->iniconfig, section, "rwlist", preset), pwd->pw_name) == 0)
             volume->v_flags |= AFPVOL_RO;
     }
 
@@ -770,6 +774,7 @@ static int creatvol(AFPObj *obj,
     /* because v_vid has not been decided yet. */
     suffixlen = sprintf(suffix, "#%X", lastvid + 1 );
 
+
     vlen = strlen( name );
 
     /* Unicode Volume Name */
@@ -886,13 +891,15 @@ static int creatvol(AFPObj *obj,
     volume->v_obj = obj;
 
 EC_CLEANUP:
+    LOG(log_debug, logtype_afpd, "createvol: END: %d", ret);
     if (ret != 0) {
         if (volume) {
             volume_free(volume);
             free(volume);
         }
+        return NULL;
     }
-    EC_EXIT;
+    return volume;
 }
 
 /* ----------------------
@@ -912,10 +919,6 @@ static int vol_section(const char *sec)
 {
     if (STRCMP(sec, ==, INISEC_GLOBAL))
         return 0;
-    if (STRCMP(sec, ==, INISEC_AFP))
-        return 0;
-    if (STRCMP(sec, ==, INISEC_CNID))
-        return 0;
     return 1;
 }
 
@@ -931,13 +934,10 @@ static int readvolfile(AFPObj *obj, const struct passwd *pwent)
     char        path[MAXPATHLEN + 1];
     char        volname[AFPVOL_U8MNAMELEN + 1];
     char        tmp[MAXPATHLEN + 1];
-    char        preset[MAXPRESETLEN + 1];
-    char        *presettype;
-    const char  *p;
-    char        *default_preset = NULL;
-    int         fd;
-    int         i, havepreset;
-    struct passwd     *pw;
+    const char  *preset, *default_preset, *p;
+    char        *q, *u;
+    int         i;
+    struct passwd   *pw;
 
     LOG(log_debug, logtype_afpd, "readvolfile: BEGIN");
 
@@ -945,53 +945,50 @@ static int readvolfile(AFPObj *obj, const struct passwd *pwent)
     LOG(log_debug, logtype_afpd, "readvolfile: sections: %d", secnum);
     const char *secname;
 
+    if ((default_preset = iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "vol preset", NULL))) {
+        LOG(log_debug, logtype_afpd, "readvolfile: default_preset: %s", default_preset);
+    }
+
     for (i = 0; i < secnum; i++) { 
         secname = iniparser_getsecname(obj->iniconfig, i);
+
         if (!vol_section(secname))
             continue;
-
-        havepreset = 0;
-        if ((p = iniparser_getstring(obj->iniconfig, secname, "preset", NULL))) {
-            if (strcmp(p, "yes") == 0)
-                /* it's a preset, we needn't do anything more with it here */
+        if (STRCMP(secname, ==, INISEC_HOMES)) {
+            have_uservol = 1;
+            if (obj->username[0] == 0
+                || strcmp(obj->username, obj->options.guest) == 0)
+                /* not an AFP session, but cnid daemon, dbd or ad util, or guest login */
                 continue;
-
-            /* check for "preset = XXX", "preset = XXX:once", "preset = XXX:sub" and "preset = none"*/
-            if (STRCMP(p, ==, "none")) {
-                default_preset = NULL;
-            } else {
-                /* in case no ":" is found or the part after ":" != "sub" we just copy the value to preset[]*/
-                strlcpy(preset, p, MAXPRESETLEN);
-
-                if ((presettype = strchr(preset, ':')))
-                    *presettype++ = 0;
-
-                if (presettype && STRCMP(presettype, ==, "sub")) {
-                    if (default_preset)
-                        free(default_preset);
-                    default_preset = strdup(preset);
-                }
-                havepreset = 1;
-            }
+            strlcpy(tmp, pwent->pw_dir, MAXPATHLEN);
+            strlcat(tmp, "/", MAXPATHLEN);
+            if (p = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "path", NULL))
+                strlcat(tmp, p, MAXPATHLEN);
+        } else {
+            /* Get path */
+            if ((p = iniparser_getstring(obj->iniconfig, secname, "path", NULL)) == NULL)
+                continue;
+            strlcpy(tmp, p, MAXPATHLEN);
         }
 
-        strlcpy(volname, secname, AFPVOL_U8MNAMELEN);
-        LOG(log_debug, logtype_afpd, "readvolfile: volume: %s", volname);
-
-        if ((p = iniparser_getstring(obj->iniconfig, secname, "path", NULL)) == NULL)
-            continue;
-        strlcpy(path, p, MAXPATHLEN);
-        strcpy(tmp, path);
-
         if (volxlate(obj, path, sizeof(path) - 1, tmp, pwent, NULL, NULL) == NULL)
             continue;
 
-        /* do variable substitution for volname */
-        if (volxlate(obj, tmp, sizeof(tmp) - 1, volname, pwent, path, NULL) == NULL) {
-            continue;
+        /* do variable substitution for volume name */
+        if (STRCMP(secname, ==, INISEC_HOMES)) {
+            if (p = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "name", "$u's home"))
+                strlcpy(tmp, p, MAXPATHLEN);
+            else
+                strlcpy(tmp, p, MAXPATHLEN);
+        } else {
+            strlcpy(tmp, secname, AFPVOL_U8MNAMELEN);
         }
+        if (volxlate(obj, volname, sizeof(volname) - 1, tmp, pwent, path, NULL) == NULL)
+            continue;
+
+        preset = iniparser_getstring(obj->iniconfig, secname, "vol preset", NULL);
 
-        creatvol(obj, pwent, path, tmp, havepreset ? preset : default_preset ? default_preset : NULL);
+        creatvol(obj, pwent, secname, volname, path, preset ? preset : default_preset ? default_preset : NULL);
     }
 
 EC_CLEANUP:
@@ -1046,16 +1043,31 @@ void volume_free(struct vol *vol)
     free(vol->v_gvs);
     free(vol->v_uuid);
     free(vol->v_cnidserver);
-#if 0
-    /* NO! Just points to v_cnidserver + x */
     free(vol->v_cnidport);
-#endif
     free(vol->v_root_preexec);
     free(vol->v_postexec);
 
     LOG(log_debug, logtype_afpd, "volume_free: END");
 }
 
+/*!
+ * Load charsets for a volume
+ */
+int load_charset(struct vol *vol)
+{
+    if ((vol->v_maccharset = add_charset(vol->v_maccodepage)) == (charset_t)-1) {
+        LOG(log_error, logtype_default, "Setting Mac codepage '%s' failed", vol->v_maccodepage);
+        return -1;
+    }
+
+    if ((vol->v_volcharset = add_charset(vol->v_volcodepage)) == (charset_t)-1) {
+        LOG(log_error, logtype_default, "Setting volume codepage '%s' failed", vol->v_volcodepage);
+        return -1;
+    }
+
+    return 0;
+}
+
 /*!
  * Initialize volumes and load ini configfile
  *
@@ -1078,8 +1090,12 @@ int load_volumes(AFPObj *obj, void (*delvol_fn)(struct vol *))
     if (Volumes) {
         if (!volfile_changed(&obj->options))
             goto EC_CLEANUP;
-        for (vol = Volumes; vol; vol = vol->v_next)
+        have_uservol = 0;
+        for (vol = Volumes; vol; vol = vol->v_next) {
+            if (vol->v_flags & AFPVOL_UNIX_CTXT)
+                continue;
             vol->v_deleted = 1;
+        }
     } else {
         LOG(log_debug, logtype_afpd, "load_volumes: no volumes yet");
         EC_ZERO_LOG( lstat(obj->options.configfile, &st) );
@@ -1166,13 +1182,161 @@ struct vol *getvolbyvid(const uint16_t vid )
     return( vol );
 }
 
-struct vol *getvolbypath(const char *path)
+/*!
+ * Search volume by path, creating user home vols as necessary
+ *
+ * Path may be absolute or relative. Ordinary volume structs are created when
+ * the ini config is initially parsed (load_volumes()), but user volumes are
+ * as load_volumes() only can create the user volume of the logged in user
+ * in an AFP session in afpd, but not when called from eg cnid_metad or dbd.
+ * Both cnid_metad and dbd thus need a way to lookup and create struct vols
+ * for user home by path. This is what this func does as well.
+ *
+ * (1) Search "normal" volume list 
+ * (2) Check if theres a [Homes] section, load_volumes() remembers this for us
+ * (3) If there is, match "path" with "basedir regex" to get the user home parent dir
+ * (4) Built user home path by appending the basedir matched in (3) and appending the username
+ * (5) The next path element then is the username
+ * (6) Append [Homes]->path subdirectory if defined
+ * (7) Create volume
+ *
+ * @param obj  (rw) handle
+ * @param path (r)  path, may be relative or absolute
+ */
+struct vol *getvolbypath(AFPObj *obj, const char *path)
+{
+    EC_INIT;
+    struct vol *vol;
+    struct vol *tmp;
+    const struct passwd *pw;
+    char        volname[AFPVOL_U8MNAMELEN + 1];
+    char        abspath[MAXPATHLEN + 1];
+    char        volpath[MAXPATHLEN + 1];
+    char        tmpbuf[MAXPATHLEN + 1];
+    const char *secname, *basedir, *p = NULL, *subpath = NULL, *subpathconfig;
+    char *user = NULL, *prw;
+    int regexerr = -1;
+    static regex_t reg;
+    regmatch_t match[1];
+
+    LOG(log_debug, logtype_afpd, "getvolbypath(\"%s\")", path);
+
+    if (path[0] != '/') {
+        /* relative path, build absolute path */
+        EC_NULL_LOG( getcwd(abspath, MAXPATHLEN) );
+        strlcat(abspath, "/", MAXPATHLEN);
+        strlcat(abspath, path, MAXPATHLEN);
+        path = abspath;
+    }
+
+
+    for (tmp = Volumes; tmp; tmp = tmp->v_next) { /* (1) */
+        if (strncmp(path, tmp->v_path, strlen(tmp->v_path)) == 0) {
+            vol = tmp;
+            goto EC_CLEANUP;
+        }
+    }
+
+    if (!have_uservol) /* (2) */
+        EC_FAIL_LOG("getvolbypath(\"%s\"): no volume for path", path);
+
+    int secnum = iniparser_getnsec(obj->iniconfig);
+
+    for (int i = 0; i < secnum; i++) { 
+        secname = iniparser_getsecname(obj->iniconfig, i);
+        if (STRCMP(secname, ==, INISEC_HOMES))
+            break;
+    }
+
+    if (STRCMP(secname, !=, INISEC_HOMES))
+        EC_FAIL_LOG("getvolbypath(\"%s\"): no volume for path", path);
+
+    /* (3) */
+    EC_NULL_LOG( basedir = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "basedir regex", NULL) );
+    LOG(log_debug, logtype_afpd, "getvolbypath: user home section: '%s', basedir: '%s'", secname, basedir);
+
+    if (regexerr != 0 && (regexerr = regcomp(&reg, basedir, REG_EXTENDED)) != 0) {
+        char errbuf[1024];
+        regerror(regexerr, &reg, errbuf, sizeof(errbuf));
+        printf("error: %s\n", errbuf);
+        EC_FAIL_LOG("getvolbypath(\"%s\"): bad basedir regex: %s", errbuf);
+    }
+
+    if (regexec(&reg, path, 1, match, 0) == REG_NOMATCH)
+        EC_FAIL_LOG("getvolbypath(\"%s\"): no volume for path", path);
+
+    if (match[0].rm_eo - match[0].rm_so > MAXPATHLEN)
+        EC_FAIL_LOG("getvolbypath(\"%s\"): path too long", path);
+
+    /* (4) */
+    strncpy(tmpbuf, path + match[0].rm_so, match[0].rm_eo - match[0].rm_so);
+    tmpbuf[match[0].rm_eo - match[0].rm_so] = 0;
+
+    LOG(log_debug, logtype_afpd, "getvolbypath: basedir regex: '%s', basedir match: \"%s\"",
+        basedir, tmpbuf);
+
+    strlcat(tmpbuf, "/", MAXPATHLEN);
+
+    /* (5) */
+    p = path + strlen(basedir);
+    while (*p == '/')
+        p++;
+    EC_NULL_LOG( user = strdup(p) );
+
+    if (prw = strchr(user, '/'))
+        *prw++ = 0;
+    if (prw != 0)
+        subpath = prw;
+
+    strlcpy(obj->username, user, MAXUSERLEN);
+    strlcat(tmpbuf, user, MAXPATHLEN);
+    strlcat(tmpbuf, "/", MAXPATHLEN);
+
+    /* (6) */
+    if (subpathconfig = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "path", NULL)) {
+        if (!subpath || strncmp(subpathconfig, subpath, strlen(subpathconfig)) != 0) {
+            EC_FAIL;
+        }
+        strlcat(tmpbuf, subpathconfig, MAXPATHLEN);
+        strlcat(tmpbuf, "/", MAXPATHLEN);
+    }
+
+
+    /* (7) */
+    if (volxlate(obj, volpath, sizeof(volpath) - 1, tmpbuf, pw, NULL, NULL) == NULL)
+        return NULL;
+
+    EC_NULL( pw = getpwnam(user) );
+
+    LOG(log_debug, logtype_afpd, "getvolbypath(\"%s\"): user: %s, homedir: %s => volpath: \"%s\"",
+        path, user, pw->pw_dir, volpath);
+
+    /* do variable substitution for volume name */
+    p = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "name", "$u's home");
+    strlcpy(tmpbuf, p, AFPVOL_U8MNAMELEN);
+    EC_NULL_LOG( volxlate(obj, volname, sizeof(volname) - 1, tmpbuf, pw, volpath, NULL) );
+
+    const char  *preset, *default_preset;
+    default_preset = iniparser_getstring(obj->iniconfig, INISEC_GLOBAL, "vol preset", NULL);
+    preset = iniparser_getstring(obj->iniconfig, INISEC_HOMES, "vol preset", NULL);
+
+    vol = creatvol(obj, pw, INISEC_HOMES, volname, volpath, preset ? preset : default_preset ? default_preset : NULL);
+
+EC_CLEANUP:
+    if (user)
+        free(user);
+    if (ret != 0)
+        vol = NULL;
+    return vol;
+}
+
+struct vol *getvolbyname(const char *name)
 {
     struct vol *vol = NULL;
     struct vol *tmp;
 
     for (tmp = Volumes; tmp; tmp = tmp->v_next) {
-        if (strncmp(path, tmp->v_path, strlen(tmp->v_path)) == 0) {
+        if (strncmp(name, tmp->v_configname, strlen(tmp->v_configname)) == 0) {
             vol = tmp;
             break;
         }
@@ -1209,7 +1373,7 @@ int afp_config_parse(AFPObj *AFPObj)
     options->logfile   = iniparser_getstrdup(config, INISEC_GLOBAL, "logfile",  NULL);
 
     /* [AFP] "options" options wo values */
-    if (p = iniparser_getstrdup(config, INISEC_AFP, "options", NULL)) {
+    if (q = iniparser_getstrdup(config, INISEC_GLOBAL, "options", NULL)) {
         if (p = strtok(q, ", ")) {
             while (p) {
                 if (strcasecmp(p, "nozeroconf"))
@@ -1239,38 +1403,39 @@ int afp_config_parse(AFPObj *AFPObj)
                 p = strtok(NULL, ", ");
             }
         }
+        free(q);
     }
     /* figure out options w values */
 
-    options->loginmesg      = iniparser_getstrdup(config, INISEC_AFP, "loginmesg",      "");
-    options->guest          = iniparser_getstrdup(config, INISEC_AFP, "guestname",      "nobody");
-    options->passwdfile     = iniparser_getstrdup(config, INISEC_AFP, "passwdfile",     _PATH_AFPDPWFILE);
-    options->uampath        = iniparser_getstrdup(config, INISEC_AFP, "uampath",        _PATH_AFPDUAMPATH);
-    options->uamlist        = iniparser_getstrdup(config, INISEC_AFP, "uamlist",        "uams_dhx.so,uams_dhx2.so");
-    options->port           = iniparser_getstrdup(config, INISEC_AFP, "port",           "548");
-    options->signatureopt   = iniparser_getstrdup(config, INISEC_AFP, "signature",      "auto");
-    options->k5service      = iniparser_getstrdup(config, INISEC_AFP, "k5service",      NULL);
-    options->k5realm        = iniparser_getstrdup(config, INISEC_AFP, "k5realm",        NULL);
-    options->listen         = iniparser_getstrdup(config, INISEC_AFP, "listen",         NULL);
-    options->ntdomain       = iniparser_getstrdup(config, INISEC_AFP, "ntdomain",       NULL);
-    options->ntseparator    = iniparser_getstrdup(config, INISEC_AFP, "ntseparator",    NULL);
-    options->mimicmodel     = iniparser_getstrdup(config, INISEC_AFP, "mimicmodel",     NULL);
-    options->adminauthuser  = iniparser_getstrdup(config, INISEC_AFP, "adminauthuser",  NULL);
-    options->connections    = iniparser_getint   (config, INISEC_AFP, "maxcon",         200);
-    options->passwdminlen   = iniparser_getint   (config, INISEC_AFP, "passwdminlen",   0);
-    options->tickleval      = iniparser_getint   (config, INISEC_AFP, "tickleval",      30);
-    options->timeout        = iniparser_getint   (config, INISEC_AFP, "timeout",        4);
-    options->dsireadbuf     = iniparser_getint   (config, INISEC_AFP, "dsireadbuf",     12);
-    options->server_quantum = iniparser_getint   (config, INISEC_AFP, "server_quantum", DSI_SERVQUANT_DEF);
-    options->volnamelen     = iniparser_getint   (config, INISEC_AFP, "volnamelen",     80);
-    options->dircachesize   = iniparser_getint   (config, INISEC_AFP, "dircachesize",   DEFAULT_MAX_DIRCACHE_SIZE);
-    options->tcp_sndbuf     = iniparser_getint   (config, INISEC_AFP, "tcpsndbuf",      0);
-    options->tcp_rcvbuf     = iniparser_getint   (config, INISEC_AFP, "tcprcvbuf",      0);
-    options->fce_fmodwait   = iniparser_getint   (config, INISEC_AFP, "fceholdfmod",    60);
-    options->sleep          = iniparser_getint   (config, INISEC_AFP, "sleep",          10) * 60 * 2;
-    options->disconnected   = iniparser_getint   (config, INISEC_AFP, "disconnect",     24) * 60 * 2;
-
-    if ((p = iniparser_getstring(config, INISEC_AFP, "hostname", NULL))) {
+    options->loginmesg      = iniparser_getstrdup(config, INISEC_GLOBAL, "loginmesg",      "");
+    options->guest          = iniparser_getstrdup(config, INISEC_GLOBAL, "guestname",      "nobody");
+    options->passwdfile     = iniparser_getstrdup(config, INISEC_GLOBAL, "passwdfile",     _PATH_AFPDPWFILE);
+    options->uampath        = iniparser_getstrdup(config, INISEC_GLOBAL, "uampath",        _PATH_AFPDUAMPATH);
+    options->uamlist        = iniparser_getstrdup(config, INISEC_GLOBAL, "uamlist",        "uams_dhx.so,uams_dhx2.so");
+    options->port           = iniparser_getstrdup(config, INISEC_GLOBAL, "afp port",       "548");
+    options->signatureopt   = iniparser_getstrdup(config, INISEC_GLOBAL, "signature",      "auto");
+    options->k5service      = iniparser_getstrdup(config, INISEC_GLOBAL, "k5service",      NULL);
+    options->k5realm        = iniparser_getstrdup(config, INISEC_GLOBAL, "k5realm",        NULL);
+    options->listen         = iniparser_getstrdup(config, INISEC_GLOBAL, "afp listen",     NULL);
+    options->ntdomain       = iniparser_getstrdup(config, INISEC_GLOBAL, "ntdomain",       NULL);
+    options->ntseparator    = iniparser_getstrdup(config, INISEC_GLOBAL, "ntseparator",    NULL);
+    options->mimicmodel     = iniparser_getstrdup(config, INISEC_GLOBAL, "mimicmodel",     NULL);
+    options->adminauthuser  = iniparser_getstrdup(config, INISEC_GLOBAL, "adminauthuser",  NULL);
+    options->connections    = iniparser_getint   (config, INISEC_GLOBAL, "maxcon",         200);
+    options->passwdminlen   = iniparser_getint   (config, INISEC_GLOBAL, "passwdminlen",   0);
+    options->tickleval      = iniparser_getint   (config, INISEC_GLOBAL, "tickleval",      30);
+    options->timeout        = iniparser_getint   (config, INISEC_GLOBAL, "timeout",        4);
+    options->dsireadbuf     = iniparser_getint   (config, INISEC_GLOBAL, "dsireadbuf",     12);
+    options->server_quantum = iniparser_getint   (config, INISEC_GLOBAL, "server_quantum", DSI_SERVQUANT_DEF);
+    options->volnamelen     = iniparser_getint   (config, INISEC_GLOBAL, "volnamelen",     80);
+    options->dircachesize   = iniparser_getint   (config, INISEC_GLOBAL, "dircachesize",   DEFAULT_MAX_DIRCACHE_SIZE);
+    options->tcp_sndbuf     = iniparser_getint   (config, INISEC_GLOBAL, "tcpsndbuf",      0);
+    options->tcp_rcvbuf     = iniparser_getint   (config, INISEC_GLOBAL, "tcprcvbuf",      0);
+    options->fce_fmodwait   = iniparser_getint   (config, INISEC_GLOBAL, "fceholdfmod",    60);
+    options->sleep          = iniparser_getint   (config, INISEC_GLOBAL, "sleep",          10);
+    options->disconnected   = iniparser_getint   (config, INISEC_GLOBAL, "disconnect",     24);
+
+    if ((p = iniparser_getstring(config, INISEC_GLOBAL, "hostname", NULL))) {
         EC_NULL_LOG( options->hostname = strdup(p) );
     } else {
         if (gethostname(val, sizeof(val)) < 0 ) {
@@ -1282,21 +1447,21 @@ int afp_config_parse(AFPObj *AFPObj)
         options->hostname = strdup(val);
     }
 
-    if ((p = iniparser_getstring(config, INISEC_AFP, "k5keytab", NULL))) {
+    if ((p = iniparser_getstring(config, INISEC_GLOBAL, "k5keytab", NULL))) {
         EC_NULL_LOG( options->k5keytab = malloc(strlen(p) + 14) );
         snprintf(options->k5keytab, strlen(p) + 14, "KRB5_KTNAME=%s", p);
         putenv(options->k5keytab);
     }
 
 #ifdef ADMIN_GRP
-    if ((p = iniparser_getstring(config, INISEC_AFP, "admingroup",  NULL))) {
+    if ((p = iniparser_getstring(config, INISEC_GLOBAL, "admingroup",  NULL))) {
          struct group *gr = getgrnam(p);
          if (gr != NULL)
              options->admingid = gr->gr_gid;
     }
 #endif /* ADMIN_GRP */
 
-    q = iniparser_getstrdup(config, INISEC_AFP, "cnidserver", "localhost:4700");
+    q = iniparser_getstrdup(config, INISEC_GLOBAL, "cnidserver", "localhost:4700");
     r = strrchr(q, ':');
     if (r)
         *r = 0;
@@ -1309,7 +1474,7 @@ int afp_config_parse(AFPObj *AFPObj)
     if (q)
         free(q);
 
-    if ((q = iniparser_getstrdup(config, INISEC_AFP, "fqdn", NULL))) {
+    if ((q = iniparser_getstrdup(config, INISEC_GLOBAL, "fqdn", NULL))) {
         /* do a little checking for the domain name. */
         r = strchr(q, ':');
         if (r)
@@ -1324,7 +1489,7 @@ int afp_config_parse(AFPObj *AFPObj)
         free(q);
     }
 
-    if (!(p = iniparser_getstring(config, INISEC_AFP, "unixcodepage", NULL))) {
+    if (!(p = iniparser_getstring(config, INISEC_GLOBAL, "unixcodepage", NULL))) {
         options->unixcharset = CH_UNIX;
         options->unixcodepage = strdup("LOCALE");
     } else {
@@ -1337,7 +1502,7 @@ int afp_config_parse(AFPObj *AFPObj)
         }
     }
        
-    if (!(p = iniparser_getstring(config, INISEC_AFP, "maccodepage", NULL))) {
+    if (!(p = iniparser_getstring(config, INISEC_GLOBAL, "maccodepage", NULL))) {
         options->maccharset = CH_MAC;
         options->maccodepage = strdup("MAC_ROMAN");
     } else {
@@ -1353,6 +1518,8 @@ int afp_config_parse(AFPObj *AFPObj)
     /* Check for sane values */
     if (options->tickleval <= 0)
         options->tickleval = 30;
+    options->disconnected *= 3600 / options->tickleval;
+    options->sleep *= 3600 / options->tickleval;
     if (options->timeout <= 0)
         options->timeout = 4;
     if (options->sleep <= 4)