From acb180395693ed05a4e6a1edb6796c8e6f644a72 Mon Sep 17 00:00:00 2001 From: Frank Lahm Date: Wed, 14 Nov 2012 16:02:41 +0100 Subject: [PATCH] Configurable symlink behaviour Add a new volumes option 'follow symlinks'. The default setting is false, symlinks are not followed on the server. This is the same behaviour as OS X's AFP server. Setting the option to true causes afpd to follow symlinks on the server. symlinks may point outside of the AFP volume, currently afpd doesn't do any checks for "wide symlinks". In order to provide a somewhat consistent interface, add an API of o* function wrappers for filesytem related functions. These functions take an an additonal option arg (hence the o) which tells the functions whether symlinks shall be followed or not. --- NEWS | 6 +++ etc/afpd/acls.c | 2 +- etc/afpd/catsearch.c | 6 +-- etc/afpd/desktop.c | 6 +-- etc/afpd/directory.c | 24 +++++----- etc/afpd/enumerate.c | 4 +- etc/afpd/file.c | 63 ++++++++++++++----------- etc/afpd/filedir.c | 8 ++-- etc/afpd/fork.c | 2 +- etc/afpd/fork.h | 4 +- etc/afpd/ofork.c | 16 +++---- etc/afpd/quota.c | 13 +++--- etc/afpd/unix.c | 11 ++--- include/atalk/acl.h | 3 ++ include/atalk/adouble.h | 3 ++ include/atalk/unix.h | 3 +- include/atalk/util.h | 8 +++- include/atalk/volume.h | 3 +- libatalk/acl/Makefile.am | 2 +- libatalk/adouble/ad_open.c | 13 ++++-- libatalk/util/netatalk_conf.c | 4 ++ libatalk/util/unix.c | 87 ++++++++++++++++++++++++++++++++++- libatalk/vfs/ea_ad.c | 14 +++--- libatalk/vfs/unix.c | 30 +----------- libatalk/vfs/vfs.c | 28 +++++------ man/man5/afp.conf.5.tmpl | 78 ++++++++++++++++++------------- 26 files changed, 272 insertions(+), 169 deletions(-) diff --git a/NEWS b/NEWS index 3dce155f..5768657e 100644 --- a/NEWS +++ b/NEWS @@ -22,6 +22,12 @@ Changes in 3.0.2 when dbd was scanning their volumes. Fixes bug #477. * FIX: Netatalk didn't start when the last line of the config file afp.conf wasn't terminated by a newline. Fixes bug #476. +* NEW: Add a new volumes option 'follow symlinks'. The default setting is + false, symlinks are not followed on the server. This is the same + behaviour as OS X's AFP server. + Setting the option to true causes afpd to follow symlinks on the + server. symlinks may point outside of the AFP volume, currently + afpd doesn't do any checks for "wide symlinks". Changes in 3.0.1 ================ diff --git a/etc/afpd/acls.c b/etc/afpd/acls.c index 305ef06f..b14beb6f 100644 --- a/etc/afpd/acls.c +++ b/etc/afpd/acls.c @@ -1380,7 +1380,7 @@ static int check_acl_access(const AFPObj *obj, } #endif - EC_ZERO_LOG_ERR(lstat(path, &st), AFPERR_PARAM); + EC_ZERO_LOG_ERR(ostat(path, &st, vol_syml_opt(vol)), AFPERR_PARAM); is_dir = !strcmp(path, "."); diff --git a/etc/afpd/catsearch.c b/etc/afpd/catsearch.c index 1a2b8989..09e3ff52 100644 --- a/etc/afpd/catsearch.c +++ b/etc/afpd/catsearch.c @@ -194,7 +194,7 @@ static struct adouble *adl_lkup(struct vol *vol, struct path *path, struct adoub isdir = S_ISDIR(path->st.st_mode); - if (!isdir && (of = of_findname(path))) { + if (!isdir && (of = of_findname(vol, path))) { adp = of->of_ad; } else { ad_init(&ad, vol); @@ -583,7 +583,7 @@ static int catsearch(const AFPObj *obj, memset(&path, 0, sizeof(path)); path.u_name = entry->d_name; - if (of_stat(&path) != 0) { + if (of_stat(vol, &path) != 0) { switch (errno) { case EACCES: case ELOOP: @@ -777,7 +777,7 @@ static int catsearch_db(const AFPObj *obj, path.u_name = name; path.m_name = utompath(vol, name, cnid, utf8_encoding(vol->v_obj)); - if (of_stat(&path) != 0) { + if (of_stat(vol, &path) != 0) { switch (errno) { case EACCES: case ELOOP: diff --git a/etc/afpd/desktop.c b/etc/afpd/desktop.c index c437b7e6..f34729cc 100644 --- a/etc/afpd/desktop.c +++ b/etc/afpd/desktop.c @@ -830,7 +830,7 @@ static int ad_addcomment(const AFPObj *obj, struct vol *vol, struct path *path, } isadir = path_isadir(path); - if (isadir || !(of = of_findname(path))) { + if (isadir || !(of = of_findname(vol, path))) { ad_init(&ad, vol); adp = &ad; } else @@ -905,7 +905,7 @@ static int ad_getcomment(struct vol *vol, struct path *path, char *rbuf, size_t upath = path->u_name; isadir = path_isadir(path); - if (isadir || !(of = of_findname(path))) { + if (isadir || !(of = of_findname(vol, path))) { ad_init(&ad, vol); adp = &ad; } else @@ -982,7 +982,7 @@ static int ad_rmvcomment(const AFPObj *obj, struct vol *vol, struct path *path) } isadir = path_isadir(path); - if (isadir || !(of = of_findname(path))) { + if (isadir || !(of = of_findname(vol, path))) { ad_init(&ad, vol); adp = &ad; } else diff --git a/etc/afpd/directory.c b/etc/afpd/directory.c index 46670e96..d617ea8f 100644 --- a/etc/afpd/directory.c +++ b/etc/afpd/directory.c @@ -134,7 +134,7 @@ static int netatalk_mkdir(const struct vol *vol, const char *name) } /* ------------------- */ -static int deletedir(int dirfd, char *dir) +static int deletedir(const struct vol *vol, int dirfd, char *dir) { char path[MAXPATHLEN + 1]; DIR *dp; @@ -165,11 +165,11 @@ static int deletedir(int dirfd, char *dir) break; } strcpy(path + len, de->d_name); - if (lstatat(dirfd, path, &st)) { + if (ostatat(dirfd, path, &st, vol_syml_opt(vol))) { continue; } if (S_ISDIR(st.st_mode)) { - err = deletedir(dirfd, path); + err = deletedir(vol, dirfd, path); } else { err = netatalk_unlinkat(dirfd, path); } @@ -231,7 +231,7 @@ static int copydir(struct vol *vol, struct dir *ddir, int dirfd, char *src, char } strcpy(spath + slen, de->d_name); - if (lstatat(dirfd, spath, &st) == 0) { + if (ostatat(dirfd, spath, &st, vol_syml_opt(vol)) == 0) { if (strlen(de->d_name) > drem) { err = AFPERR_PARAM; break; @@ -253,7 +253,7 @@ static int copydir(struct vol *vol, struct dir *ddir, int dirfd, char *src, char } /* keep the same time stamp. */ - if (lstatat(dirfd, src, &st) == 0) { + if (ostatat(dirfd, src, &st, vol_syml_opt(vol)) == 0) { ut.actime = ut.modtime = st.st_mtime; utime(dst, &ut); } @@ -643,7 +643,7 @@ struct dir *dirlookup(const struct vol *vol, cnid_t did) LOG(log_debug, logtype_afpd, "dirlookup(did: %u): stating \"%s\"", ntohl(did), cfrombstr(fullpath)); - if (lstat(cfrombstr(fullpath), &st) != 0) { /* 5a */ + if (ostat(cfrombstr(fullpath), &st, vol_syml_opt(vol)) != 0) { /* 5a */ switch (errno) { case ENOENT: afp_errno = AFPERR_NOOBJ; @@ -1181,7 +1181,7 @@ struct path *cname(struct vol *vol, struct dir *dir, char **cpath) * and thus call continue which should terminate the while loop because * len = 0. Ok? */ - if (of_stat(&ret) != 0) { /* 9 */ + if (of_stat(vol, &ret) != 0) { /* 9 */ /* * ret.u_name doesn't exist, might be afp_createfile|dir * that means it should have been the last part @@ -1299,7 +1299,7 @@ int movecwd(const struct vol *vol, struct dir *dir) LOG(log_debug, logtype_afpd, "movecwd(to: did: %u, \"%s\")", ntohl(dir->d_did), cfrombstr(dir->d_fullpath)); - if ((ret = lchdir(cfrombstr(dir->d_fullpath))) != 0 ) { + if ((ret = ochdir(cfrombstr(dir->d_fullpath), vol_syml_opt(vol))) != 0 ) { LOG(log_debug, logtype_afpd, "movecwd(\"%s\"): %s", cfrombstr(dir->d_fullpath), strerror(errno)); if (ret == 1) { @@ -2174,7 +2174,7 @@ int afp_createdir(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf, size_ return err; } - if (of_stat(s_path) < 0) { + if (of_stat(vol, s_path) < 0) { return AFPERR_MISC; } @@ -2240,10 +2240,10 @@ int renamedir(struct vol *vol, /* this needs to copy and delete. bleah. that means we have * to deal with entire directory hierarchies. */ if ((err = copydir(vol, newparent, dirfd, src, dst)) < 0) { - deletedir(-1, dst); + deletedir(vol, -1, dst); return err; } - if ((err = deletedir(dirfd, src)) < 0) + if ((err = deletedir(vol, dirfd, src)) < 0) return err; break; default : @@ -2622,7 +2622,7 @@ int afp_opendir(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, si return path_error(path, AFPERR_NOOBJ); } - if ( !path->st_valid && of_stat(path ) < 0 ) { + if ( !path->st_valid && of_stat(vol, path) < 0 ) { return( AFPERR_NOOBJ ); } if ( path->st_errno ) { diff --git a/etc/afpd/enumerate.c b/etc/afpd/enumerate.c index c3b3acb4..80141e80 100644 --- a/etc/afpd/enumerate.c +++ b/etc/afpd/enumerate.c @@ -284,7 +284,7 @@ static int enumerate(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, if ( sindex == 1 || curdir->d_did != sd.sd_did || vid != sd.sd_vid ) { sd.sd_last = sd.sd_buf; /* if dir was in the cache we don't have the inode */ - if (( !o_path->st_valid && lstat( ".", &o_path->st ) < 0 ) || + if (( !o_path->st_valid && ostat(".", &o_path->st, vol_syml_opt(vol)) < 0 ) || (ret = for_each_dirent(vol, ".", enumerate_loop, (void *)&sd)) < 0) { LOG(log_error, logtype_afpd, "enumerate: loop error: %s (%d)", strerror(errno), errno); @@ -355,7 +355,7 @@ static int enumerate(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, s_path.u_name = (char *)convname; } - if (of_stat( &s_path) < 0 ) { + if (of_stat(vol, &s_path) < 0 ) { /* so the next time it won't try to stat it again * another solution would be to invalidate the cache with * sd.sd_did = 0 but if it's not ENOENT error it will start again diff --git a/etc/afpd/file.c b/etc/afpd/file.c index 8036c300..963268fe 100644 --- a/etc/afpd/file.c +++ b/etc/afpd/file.c @@ -100,7 +100,7 @@ void *get_finderinfo(const struct vol *vol, const char *upath, struct adouble *a } } - if (islink){ + if (islink && !vol_syml_opt(vol)) { uint16_t linkflag; memcpy(&linkflag, (char *)data + FINDERINFO_FRFLAGOFF, 2); linkflag |= htons(FINDERINFO_ISALIAS); @@ -685,7 +685,7 @@ int afp_createfile(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, ad_init(&ad, vol); /* if upath is deleted we already in trouble anyway */ - if ((of = of_findname(s_path))) { + if ((of = of_findname(vol, s_path))) { if (creatf) return AFPERR_BUSY; else @@ -839,6 +839,9 @@ int setfilparams(const AFPObj *obj, struct vol *vol, uint32_t cdate,bdate; u_char finder_buf[32]; int symlinked = S_ISLNK(path->st.st_mode); + int fp; + ssize_t len; + char symbuf[MAXPATHLEN+1]; #ifdef DEBUG LOG(log_debug9, logtype_afpd, "begin setfilparams:"); @@ -880,29 +883,33 @@ int setfilparams(const AFPObj *obj, struct vol *vol, break; case FILPBIT_FINFO : change_mdate = 1; - memcpy(finder_buf, buf, 32 ); - if (memcmp(buf, "slnkrhap", 8) == 0 && !S_ISLNK(path->st.st_mode)) { - int fp; - ssize_t len; - int erc=1; - char buf[PATH_MAX+1]; - if ((fp = open(path->u_name, O_RDONLY)) >= 0) { - if ((len = read(fp, buf, PATH_MAX+1))) { - if (unlink(path->u_name) == 0) { - buf[len] = 0; - erc = symlink(buf, path->u_name); - if (!erc) - of_stat(path); - } - } - close(fp); + if (memcmp(buf,"slnkrhap",8) == 0 + && !(S_ISLNK(path->st.st_mode)) + && !(vol->v_flags & AFPVOL_FOLLOWSYM)) { + /* request to turn this into a symlink */ + if ((fp = open(path->u_name, O_RDONLY)) == -1) { + err = AFPERR_MISC; + goto setfilparam_done; } - if (erc != 0) { - err=AFPERR_BITMAP; + len = read(fp, symbuf, MAXPATHLEN); + close(fp); + if (!(len > 0)) { + err = AFPERR_MISC; goto setfilparam_done; } + if (unlink(path->u_name) != 0) { + err = AFPERR_MISC; + goto setfilparam_done; + } + symbuf[len] = 0; + if (symlink(symbuf, path->u_name) != 0) { + err = AFPERR_MISC; + goto setfilparam_done; + } + of_stat(vol, path); symlinked = 1; } + memcpy(finder_buf, buf, 32 ); buf += 32; break; case FILPBIT_UNIXPR : @@ -1695,7 +1702,7 @@ static int reenumerate_loop(struct dirent *de, char *mname _U_, void *data) cnid_t did = param->did; cnid_t aint; - if ( lstat(de->d_name, &path.st) < 0 ) + if (ostat(de->d_name, &path.st, vol_syml_opt(vol)) < 0) return 0; /* update or add to cnid */ @@ -1720,7 +1727,7 @@ reenumerate_id(struct vol *vol, char *name, struct dir *dir) } /* FIXME use of_statdir ? */ - if (lstat(name, &st)) { + if (ostat(name, &st, vol_syml_opt(vol))) { return -1; } @@ -1799,7 +1806,7 @@ retry: memset(&path, 0, sizeof(path)); path.u_name = upath; - if ( of_stat(&path) < 0 ) { + if (of_stat(vol, &path) < 0 ) { #ifdef ESTALE /* with nfs and our working directory is deleted */ if (errno == ESTALE) { @@ -1894,7 +1901,7 @@ int afp_deleteid(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf _U_ } err = AFP_OK; - if ((movecwd(vol, dir) < 0) || (lstat(upath, &st) < 0)) { + if ((movecwd(vol, dir) < 0) || (ostat(upath, &st, vol_syml_opt(vol)) < 0)) { switch (errno) { case EACCES: case EPERM: @@ -1930,7 +1937,7 @@ delete: } /* ------------------------------ */ -static struct adouble *find_adouble(const AFPObj *obj, struct vol *vol, struct path *path, struct ofork **of, struct adouble *adp) +static struct adouble *find_adouble(const AFPObj *obj, const struct vol *vol, struct path *path, struct ofork **of, struct adouble *adp) { int ret; @@ -1957,7 +1964,7 @@ static struct adouble *find_adouble(const AFPObj *obj, struct vol *vol, struct p return NULL; } - if ((*of = of_findname(path))) { + if ((*of = of_findname(vol, path))) { /* reuse struct adouble so it won't break locks */ adp = (*of)->of_ad; } @@ -2136,10 +2143,10 @@ int afp_exchangefiles(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf _U if (did) cnid_delete(vol->v_cdb, did); - if ((did && ( (crossdev && lstat( upath, &srcst) < 0) || + if ((did && ( (crossdev && ostat(upath, &srcst, vol_syml_opt(vol)) < 0) || cnid_update(vol->v_cdb, did, &srcst, curdir->d_did,upath, dlen) < 0)) || - (sid && ( (crossdev && lstat(p, &destst) < 0) || + (sid && ( (crossdev && ostat(p, &destst, vol_syml_opt(vol)) < 0) || cnid_update(vol->v_cdb, sid, &destst, sdir->d_did,supath, slen) < 0)) ) { switch (errno) { diff --git a/etc/afpd/filedir.c b/etc/afpd/filedir.c index 9a897d1b..543db0dc 100644 --- a/etc/afpd/filedir.c +++ b/etc/afpd/filedir.c @@ -346,7 +346,7 @@ static int moveandrename(struct vol *vol, if ( !isdir ) { path.st_valid = 1; path.st_errno = errno; - if (of_findname(&path)) { + if (of_findname(vol, &path)) { rc = AFPERR_EXIST; /* was AFPERR_BUSY; */ } else { rc = renamefile(vol, curdir, sdir_fd, oldunixname, upath, newname, adp ); @@ -536,7 +536,7 @@ int afp_delete(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf _U_, size fce_register(FCE_DIR_DELETE, fullpathname(cfrombstr(dname)), NULL, fce_dir); bdestroy(dname); } - } else if (of_findname(s_path)) { + } else if (of_findname(vol, s_path)) { rc = AFPERR_BUSY; } else { /* it's a file st_valid should always be true @@ -703,8 +703,8 @@ int afp_moveandrename(AFPObj *obj, char *ibuf, size_t ibuflen _U_, char *rbuf _U if (!isdir && !vol_unix_priv(vol)) { int admode = ad_mode("", 0777) | vol->v_fperm; - setfilmode(upath, admode, NULL, vol->v_umask); - vol->vfs->vfs_setfilmode(vol, upath, admode, NULL); + setfilmode(vol, upath, admode, path->st_valid ? &path->st : NULL); + vol->vfs->vfs_setfilmode(vol, upath, admode, path->st_valid ? &path->st : NULL); } setvoltime(obj, vol ); } diff --git a/etc/afpd/fork.c b/etc/afpd/fork.c index 54e1c149..5af91207 100644 --- a/etc/afpd/fork.c +++ b/etc/afpd/fork.c @@ -317,7 +317,7 @@ int afp_openfork(AFPObj *obj _U_, char *ibuf, size_t ibuflen _U_, char *rbuf, si } } - if ((opened = of_findname(s_path))) { + if ((opened = of_findname(vol, s_path))) { adsame = opened->of_ad; } diff --git a/etc/afpd/fork.h b/etc/afpd/fork.h index d7120e47..5b63601f 100644 --- a/etc/afpd/fork.h +++ b/etc/afpd/fork.h @@ -59,13 +59,13 @@ extern struct ofork *of_alloc (struct vol *, struct dir *, struct stat *); extern void of_dealloc (struct ofork *); extern struct ofork *of_find (const uint16_t); -extern struct ofork *of_findname (struct path *); +extern struct ofork *of_findname (const struct vol *vol, struct path *); extern int of_rename (const struct vol *, struct ofork *, struct dir *, const char *, struct dir *, const char *); extern int of_flush (const struct vol *); -extern int of_stat (struct path *); +extern int of_stat (const struct vol *vol, struct path *); extern int of_statdir (struct vol *vol, struct path *); extern int of_closefork (const AFPObj *obj, struct ofork *ofork); extern void of_closevol (const AFPObj *obj, const struct vol *vol); diff --git a/etc/afpd/ofork.c b/etc/afpd/ofork.c index 3f5a42b0..cf77d490 100644 --- a/etc/afpd/ofork.c +++ b/etc/afpd/ofork.c @@ -228,14 +228,14 @@ struct ofork *of_find(const uint16_t ofrefnum ) } /* -------------------------- */ -int of_stat(struct path *path) +int of_stat(const struct vol *vol, struct path *path) { int ret; path->st_errno = 0; path->st_valid = 1; - if ((ret = lstat(path->u_name, &path->st)) < 0) { + if ((ret = ostat(path->u_name, &path->st, vol_syml_opt(vol))) < 0) { LOG(log_debug, logtype_afpd, "of_stat('%s/%s': %s)", cfrombstr(curdir->d_fullpath), path->u_name, strerror(errno)); path->st_errno = errno; @@ -274,7 +274,7 @@ int of_statdir(struct vol *vol, struct path *path) if (*path->m_name) { /* not curdir */ - return of_stat (path); + return of_stat(vol, path); } path->st_errno = 0; path->st_valid = 1; @@ -286,7 +286,7 @@ int of_statdir(struct vol *vol, struct path *path) LOG(log_debug, logtype_afpd, "of_statdir: stating: '%s'", pathname); - if (!(ret = lstat(pathname, &path->st))) + if (!(ret = ostat(pathname, &path->st, vol_syml_opt(vol)))) return 0; path->st_errno = errno; @@ -297,7 +297,7 @@ int of_statdir(struct vol *vol, struct path *path) return -1; path->st_errno = 0; - if ((ret = lstat(cfrombstr(path->d_dir->d_u_name), &path->st)) < 0) + if ((ret = ostat(cfrombstr(path->d_dir->d_u_name), &path->st, vol_syml_opt(vol))) < 0) path->st_errno = errno; } @@ -305,13 +305,13 @@ int of_statdir(struct vol *vol, struct path *path) } /* -------------------------- */ -struct ofork *of_findname(struct path *path) +struct ofork *of_findname(const struct vol *vol, struct path *path) { struct ofork *of; struct file_key key; if (!path->st_valid) { - of_stat(path); + of_stat(vol, path); } if (path->st_errno) @@ -444,7 +444,7 @@ struct adouble *of_ad(const struct vol *vol, struct path *path, struct adouble * struct ofork *of; struct adouble *adp; - if ((of = of_findname(path))) { + if ((of = of_findname(vol, path))) { adp = of->of_ad; } else { ad_init(ad, vol); diff --git a/etc/afpd/quota.c b/etc/afpd/quota.c index 6903a216..5aa7f3f3 100644 --- a/etc/afpd/quota.c +++ b/etc/afpd/quota.c @@ -26,6 +26,7 @@ #include #include #include +#include #include "auth.h" #include "volume.h" @@ -370,7 +371,7 @@ mountp( char *file, int *nfs) dev_t devno; static struct mnttab mnt; - if ( lstat( file, &sb ) < 0 ) { + if (stat(file, &sb) < 0) { return( NULL ); } devno = sb.st_dev; @@ -381,14 +382,14 @@ mountp( char *file, int *nfs) while ( getmntent( mtab, &mnt ) == 0 ) { /* local fs */ - if ( (lstat( mnt.mnt_special, &sb ) == 0) && (devno == sb.st_rdev)) { + if ( (stat( mnt.mnt_special, &sb ) == 0) && (devno == sb.st_rdev)) { fclose( mtab ); return mnt.mnt_mountp; } /* check for nfs. we probably should use * strcmp(mnt.mnt_fstype, MNTTYPE_NFS), but that's not as fast. */ - if ((lstat(mnt.mnt_mountp, &sb) == 0) && (devno == sb.st_dev) && + if ((stat(mnt.mnt_mountp, &sb) == 0) && (devno == sb.st_dev) && strchr(mnt.mnt_special, ':')) { *nfs = 1; fclose( mtab ); @@ -458,7 +459,7 @@ special(char *file, int *nfs) struct mntent *mnt; int found=0; - if ( lstat( file, &sb ) < 0 ) { + if (stat(file, &sb) < 0 ) { return( NULL ); } devno = sb.st_dev; @@ -469,14 +470,14 @@ special(char *file, int *nfs) while (( mnt = getmntent( mtab )) != NULL ) { /* check for local fs */ - if ( (lstat( mnt->mnt_fsname, &sb ) == 0) && devno == sb.st_rdev) { + if ( (stat( mnt->mnt_fsname, &sb ) == 0) && devno == sb.st_rdev) { found = 1; break; } /* check for an nfs mount entry. the alternative is to use * strcmp(mnt->mnt_type, MNTTYPE_NFS) instead of the strchr. */ - if ((lstat(mnt->mnt_dir, &sb) == 0) && (devno == sb.st_dev) && + if ((stat(mnt->mnt_dir, &sb) == 0) && (devno == sb.st_dev) && strchr(mnt->mnt_fsname, ':')) { *nfs = 1; found = 1; diff --git a/etc/afpd/unix.c b/etc/afpd/unix.c index 16562f3b..31086bb6 100644 --- a/etc/afpd/unix.c +++ b/etc/afpd/unix.c @@ -158,7 +158,7 @@ void accessmode(const AFPObj *obj, const struct vol *vol, char *path, struct mac ma->ma_user = ma->ma_owner = ma->ma_world = ma->ma_group = 0; if (!st) { - if (lstat(path, &sb) != 0) + if (ostat(path, &sb, vol_syml_opt(vol)) != 0) return; st = &sb; } @@ -207,7 +207,7 @@ mode_t mtoumode(struct maccess *ma) int setfilunixmode (const struct vol *vol, struct path* path, mode_t mode) { if (!path->st_valid) { - of_stat(path); + of_stat(vol, path); } if (path->st_errno) { @@ -216,7 +216,7 @@ int setfilunixmode (const struct vol *vol, struct path* path, mode_t mode) mode |= vol->v_fperm; - if (setfilmode( path->u_name, mode, &path->st, vol->v_umask) < 0) + if (setfilmode(vol, path->u_name, mode, &path->st) < 0) return -1; /* we need to set write perm if read set for resource fork */ return vol->vfs->vfs_setfilmode(vol, path->u_name, mode, &path->st); @@ -249,7 +249,7 @@ int setdirunixmode(const struct vol *vol, const char *name, mode_t mode) /* ----------------------------- */ int setfilowner(const struct vol *vol, const uid_t uid, const gid_t gid, struct path* path) { - if (lchown(path->u_name, uid, gid) < 0 && errno != EPERM) { + if (ochown( path->u_name, uid, gid, vol_syml_opt(vol)) < 0 && errno != EPERM ) { LOG(log_debug, logtype_afpd, "setfilowner: chown %d/%d %s: %s", uid, gid, path->u_name, strerror(errno)); return -1; @@ -271,7 +271,7 @@ int setfilowner(const struct vol *vol, const uid_t uid, const gid_t gid, struct * co-opting some bits. */ int setdirowner(const struct vol *vol, const char *name, const uid_t uid, const gid_t gid) { - if (lchown(name, uid, gid ) < 0 && errno != EPERM ) { + if (ochown(name, uid, gid, vol_syml_opt(vol)) < 0 && errno != EPERM ) { LOG(log_debug, logtype_afpd, "setdirowner: chown %d/%d %s: %s", uid, gid, fullpathname(name), strerror(errno) ); } @@ -281,4 +281,3 @@ int setdirowner(const struct vol *vol, const char *name, const uid_t uid, const return( 0 ); } - diff --git a/include/atalk/acl.h b/include/atalk/acl.h index 95aa0753..810be707 100644 --- a/include/atalk/acl.h +++ b/include/atalk/acl.h @@ -21,6 +21,8 @@ #ifdef HAVE_ACLS +#define O_NETATALK_ACL (O_NOFOLLOW << 1) + #ifdef HAVE_SOLARIS_ACLS #include @@ -51,6 +53,7 @@ extern int remove_acl_vfs(const char *name); #else /* HAVE_ACLS=no */ +#define O_NETATALK_ACL #define chmod_acl chmod #endif /* HAVE_ACLS */ diff --git a/include/atalk/adouble.h b/include/atalk/adouble.h index 384486ea..6e76e51b 100644 --- a/include/atalk/adouble.h +++ b/include/atalk/adouble.h @@ -247,6 +247,7 @@ struct adouble { #define ADVOL_NODEV (1 << 0) #define ADVOL_UNIXPRIV (1 << 2) /* adouble unix priv */ #define ADVOL_INVDOTS (1 << 3) /* dot files (.DS_Store) are invisible) */ +#define ADVOL_FOLLO_SYML (1 << 4) /* lock flags */ #define ADLOCK_CLR (0) @@ -369,6 +370,8 @@ struct adouble { #define ad_ref(ad) (ad)->ad_refcount++ #define ad_unref(ad) --((ad)->ad_refcount) +#define ad_get_syml_opt(ad) (((ad)->ad_options & ADVOL_FOLLO_SYML) ? 0 : O_NOFOLLOW) + /* ad_flush.c */ extern int ad_rebuild_adouble_header_v2(struct adouble *); extern int ad_rebuild_adouble_header_ea(struct adouble *); diff --git a/include/atalk/unix.h b/include/atalk/unix.h index 01997e94..067572ed 100644 --- a/include/atalk/unix.h +++ b/include/atalk/unix.h @@ -30,14 +30,13 @@ extern int netatalk_unlink(const char *name); extern int netatalk_unlinkat(int dirfd, const char *name); extern int statat(int dirfd, const char *path, struct stat *st); -extern int lstatat(int dirfd, const char *path, struct stat *st); extern DIR *opendirat(int dirfd, const char *path); /* rmdir ENOENT not an error */ extern int netatalk_rmdir(int dirfd, const char *name); extern int netatalk_rmdir_all_errors(int dirfd, const char *name); -extern int setfilmode(const char *, mode_t, struct stat *, mode_t); +extern int setfilmode(const struct vol *vol, const char *name, mode_t mode, struct stat *st); extern int dir_rx_set(mode_t mode); extern int unix_rename(int sfd, const char *oldpath, int dfd, const char *newpath); extern int copy_file(int sfd, const char *src, const char *dst, mode_t mode); diff --git a/include/atalk/util.h b/include/atalk/util.h index 30aba481..e278872b 100644 --- a/include/atalk/util.h +++ b/include/atalk/util.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -178,7 +179,6 @@ extern int recv_fd(int fd, int nonblocking); extern const char *getcwdpath(void); extern const char *fullpathname(const char *); extern char *stripped_slashes_basename(char *p); -extern int lchdir(const char *dir); extern void randombytes(void *buf, int n); extern int daemonize(int nochdir, int noclose); extern int run_cmd(const char *cmd, char **cmd_argv); @@ -186,6 +186,12 @@ extern char *realpath_safe(const char *path); extern const char *basename_safe(const char *path); extern char *strtok_quote (char *s, const char *delim); +extern int ochdir(const char *dir, int options); +extern int ostat(const char *path, struct stat *buf, int options); +extern int ostatat(int dirfd, const char *path, struct stat *st, int options); +extern int ochown(const char *path, uid_t owner, gid_t group, int options); +extern int ochmod(const char *path, mode_t mode, const struct stat *st, int options); + /****************************************************************** * cnid.c *****************************************************************/ diff --git a/include/atalk/volume.h b/include/atalk/volume.h index e7f87724..b185f994 100644 --- a/include/atalk/volume.h +++ b/include/atalk/volume.h @@ -132,6 +132,7 @@ struct vol { #define AFPVOL_ACLS (1 << 24) /* Volume supports ACLS */ #define AFPVOL_SEARCHDB (1 << 25) /* Use fast CNID db search instead of filesystem */ #define AFPVOL_NONETIDS (1 << 26) /* signal the client it shall do privelege mapping */ +#define AFPVOL_FOLLOWSYM (1 << 27) /* follow symlinks on the server, default is not to */ /* Extended Attributes vfs indirection */ #define AFPVOL_EA_NONE 0 /* No EAs */ @@ -190,6 +191,6 @@ struct vol { #define vol_nodev(vol) (((vol)->v_flags & AFPVOL_NODEV) ? 1 : 0) #define vol_unix_priv(vol) ((vol)->v_obj->afp_version >= 30 && ((vol)->v_flags & AFPVOL_UNIX_PRIV)) #define vol_inv_dots(vol) (((vol)->v_flags & AFPVOL_INV_DOTS) ? 1 : 0) - +#define vol_syml_opt(vol) (((vol)->v_flags & AFPVOL_FOLLOWSYM) ? 0 : O_NOFOLLOW) #endif diff --git a/libatalk/acl/Makefile.am b/libatalk/acl/Makefile.am index 0e75ee9e..1013f46c 100644 --- a/libatalk/acl/Makefile.am +++ b/libatalk/acl/Makefile.am @@ -6,7 +6,7 @@ noinst_LTLIBRARIES = libacl.la libacl_la_SOURCES = cache.c unix.c uuid.c libacl_la_CFLAGS = libacl_la_LDFLAGS = -libacl_la_LIBADD = +libacl_la_LIBADD = @ACL_LIBS@ if HAVE_LDAP libacl_la_SOURCES += ldap.c ldap_config.c diff --git a/libatalk/adouble/ad_open.c b/libatalk/adouble/ad_open.c index 4f3a832c..e66929f2 100644 --- a/libatalk/adouble/ad_open.c +++ b/libatalk/adouble/ad_open.c @@ -808,6 +808,9 @@ static int ad2openflags(const struct adouble *ad, int adfile, int adflags) if (adflags & ADFLAGS_TRUNC) oflags |= O_TRUNC; + if (!(ad->ad_options & ADVOL_FOLLO_SYML)) + oflags |= O_NOFOLLOW; + return oflags; } @@ -841,7 +844,7 @@ static int ad_open_df(const char *path, int adflags, mode_t mode, struct adouble goto EC_CLEANUP; } - oflags = O_NOFOLLOW | ad2openflags(ad, ADFLAGS_DF, adflags); + oflags = ad2openflags(ad, ADFLAGS_DF, adflags); admode = mode; if ((adflags & ADFLAGS_CREATE)) { @@ -929,7 +932,7 @@ static int ad_open_hf_v2(const char *path, int adflags, mode_t mode, struct adou } ad_p = ad->ad_ops->ad_path(path, adflags); - oflags = O_NOFOLLOW | ad2openflags(ad, ADFLAGS_HF, adflags); + oflags = ad2openflags(ad, ADFLAGS_HF, adflags); LOG(log_debug, logtype_ad,"ad_open_hf_v2(\"%s\"): open flags: %s", fullpathname(path), openflags2logstr(oflags)); nocreatflags = oflags & ~(O_CREAT | O_EXCL); @@ -1041,7 +1044,7 @@ static int ad_open_hf_ea(const char *path, int adflags, int mode, struct adouble ad_meta_fileno(ad), ad->ad_mdp->adf_refcount, ad_reso_fileno(ad), ad->ad_rfp->adf_refcount); - oflags = O_NOFOLLOW | (ad2openflags(ad, ADFLAGS_DF, adflags) & ~(O_CREAT | O_TRUNC)); + oflags = ad2openflags(ad, ADFLAGS_DF, adflags) & ~(O_CREAT | O_TRUNC); if (ad_meta_fileno(ad) == AD_SYMLINK) goto EC_CLEANUP; @@ -1221,7 +1224,7 @@ static int ad_open_rf_ea(const char *path, int adflags, int mode, struct adouble LOG(log_debug, logtype_ad, "ad_open_rf(\"%s\"): BEGIN", fullpathname(path)); - oflags = O_NOFOLLOW | (ad2openflags(ad, ADFLAGS_RF, adflags) & ~O_CREAT); + oflags = ad2openflags(ad, ADFLAGS_RF, adflags) & ~O_CREAT; if (ad_reso_fileno(ad) != -1) { /* the file is already open, but we want write access: */ @@ -1500,7 +1503,7 @@ int ad_stat(const char *path, struct stat *stbuf) p = ad_dir(path); if (!p) return -1; - return lstat( p, stbuf ); + return stat( p, stbuf ); } /* ---------------- diff --git a/libatalk/util/netatalk_conf.c b/libatalk/util/netatalk_conf.c index 01c6cb97..824c69f9 100644 --- a/libatalk/util/netatalk_conf.c +++ b/libatalk/util/netatalk_conf.c @@ -773,6 +773,8 @@ static struct vol *creatvol(AFPObj *obj, #endif if (!getoption_bool(obj->iniconfig, section, "convert appledouble", preset, 1)) volume->v_flags |= AFPVOL_NOV2TOEACONV; + if (getoption_bool(obj->iniconfig, section, "follow symlinks", preset, 0)) + volume->v_flags |= AFPVOL_FOLLOWSYM; if (getoption_bool(obj->iniconfig, section, "preexec close", preset, 0)) volume->v_preexec_close = 1; @@ -800,6 +802,8 @@ static struct vol *creatvol(AFPObj *obj, volume->v_ad_options |= ADVOL_UNIXPRIV; if ((volume->v_flags & AFPVOL_INV_DOTS)) volume->v_ad_options |= ADVOL_INVDOTS; + if ((volume->v_flags & AFPVOL_FOLLOWSYM)) + volume->v_ad_options |= ADVOL_FOLLO_SYML; /* Mac to Unix conversion flags*/ if ((volume->v_flags & AFPVOL_EILSEQ)) diff --git a/libatalk/util/unix.c b/libatalk/util/unix.c index bde1a7ca..a74ca80b 100644 --- a/libatalk/util/unix.c +++ b/libatalk/util/unix.c @@ -228,20 +228,103 @@ char *stripped_slashes_basename(char *p) return (strrchr(p, '/') ? strrchr(p, '/') + 1 : p); } +/********************************************************************************* + * chdir(), chmod(), chown(), stat() wrappers taking an additional option. + * Currently the only used options are O_NOFOLLOW, used to switch between symlink + * behaviour, and O_NETATALK_ACL for ochmod() indicating chmod_acl() shall be + * called which does special ACL handling depending on the filesytem + *********************************************************************************/ + +int ostat(const char *path, struct stat *buf, int options) +{ + if (options & O_NOFOLLOW) + return lstat(path, buf); + else + return stat(path, buf); +} + +int ochown(const char *path, uid_t owner, gid_t group, int options) +{ + if (options & O_NOFOLLOW) + return lchown(path, owner, group); + else + return chown(path, owner, group); +} + +/*! + * chmod() wrapper for symlink and ACL handling + * + * @param path (r) path + * @param mode (r) requested mode + * @param sb (r) stat() of path or NULL + * @param option (r) O_NOFOLLOW | O_NETATALK_ACL + * + * Options description: + * O_NOFOLLOW: don't chmod() symlinks, do nothing, return 0 + * O_NETATALK_ACL: call chmod_acl() instead of chmod() + */ +int ochmod(const char *path, mode_t mode, const struct stat *st, int options) +{ + struct stat sb; + + if (!st) { + if (lstat(path, &sb) != 0) + return -1; + st = &sb; + } + + if (options & O_NOFOLLOW) + if (S_ISLNK(st->st_mode)) + return 0; + + if (options & O_NETATALK_ACL) { + return chmod_acl(path, mode); + } else { + return chmod(path, mode); + } +} + +/* + * @brief ostat/fsstatat multiplexer + * + * ostatat mulitplexes ostat and fstatat. If we dont HAVE_ATFUNCS, dirfd is ignored. + * + * @param dirfd (r) Only used if HAVE_ATFUNCS, ignored else, -1 gives AT_FDCWD + * @param path (r) pathname + * @param st (rw) pointer to struct stat + */ +int ostatat(int dirfd, const char *path, struct stat *st, int options) +{ +#ifdef HAVE_ATFUNCS + if (dirfd == -1) + dirfd = AT_FDCWD; + return fstatat(dirfd, path, st, (options & O_NOFOLLOW) ? AT_SYMLINK_NOFOLLOW : 0); +#else + return ostat(path, st, options); +#endif + + /* DEADC0DE */ + return -1; +} + /*! * @brief symlink safe chdir replacement * - * Only chdirs to dir if it doesn't contain symlinks. + * Only chdirs to dir if it doesn't contain symlinks or if symlink checking + * is disabled * * @returns 1 if a path element is a symlink, 0 otherwise, -1 on syserror */ -int lchdir(const char *dir) +int ochdir(const char *dir, int options) { char buf[MAXPATHLEN+1]; char cwd[MAXPATHLEN+1]; char *test; int i; + if (!(options & O_NOFOLLOW)) + return chdir(dir); + /* dir is a canonical path (without "../" "./" "//" ) but may end with a / diff --git a/libatalk/vfs/ea_ad.c b/libatalk/vfs/ea_ad.c index cdc7c828..e229d7be 100644 --- a/libatalk/vfs/ea_ad.c +++ b/libatalk/vfs/ea_ad.c @@ -906,7 +906,7 @@ int ea_close(struct ea * restrict ea) if (ea->ea_count == 0) { /* Check if EA header exists and remove it */ eaname = ea_path(ea, NULL, 0); - if ((lstatat(ea->dirfd, eaname, &st)) == 0) { + if ((statat(ea->dirfd, eaname, &st)) == 0) { if ((netatalk_unlinkat(ea->dirfd, eaname)) != 0) { LOG(log_error, logtype_afpd, "ea_close('%s'): unlink: %s", eaname, strerror(errno)); @@ -1583,7 +1583,7 @@ int ea_chown(VFS_FUNC_ARGS_CHOWN) } } - if ((lchown(ea_path(&ea, NULL, 0), uid, gid)) != 0) { + if ((ochown(ea_path(&ea, NULL, 0), uid, gid, vol_syml_opt(vol))) != 0) { switch (errno) { case EPERM: case EACCES: @@ -1600,7 +1600,7 @@ int ea_chown(VFS_FUNC_ARGS_CHOWN) ret = AFPERR_MISC; goto exit; } - if ((lchown(eaname, uid, gid)) != 0) { + if ((ochown(eaname, uid, gid, vol_syml_opt(vol))) != 0) { switch (errno) { case EPERM: case EACCES: @@ -1644,7 +1644,7 @@ int ea_chmod_file(VFS_FUNC_ARGS_SETFILEMODE) } /* Set mode on EA header file */ - if ((setfilmode(ea_path(&ea, NULL, 0), ea_header_mode(mode), NULL, vol->v_umask)) != 0) { + if ((setfilmode(vol, ea_path(&ea, NULL, 0), ea_header_mode(mode), NULL)) != 0) { LOG(log_error, logtype_afpd, "ea_chmod_file('%s'): %s", ea_path(&ea, NULL, 0), strerror(errno)); switch (errno) { case EPERM: @@ -1663,7 +1663,7 @@ int ea_chmod_file(VFS_FUNC_ARGS_SETFILEMODE) ret = AFPERR_MISC; goto exit; } - if ((setfilmode(eaname, ea_mode(mode), NULL, vol->v_umask)) != 0) { + if ((setfilmode(vol, eaname, ea_mode(mode), NULL)) != 0) { LOG(log_error, logtype_afpd, "ea_chmod_file('%s'): %s", eaname, strerror(errno)); switch (errno) { case EPERM: @@ -1712,7 +1712,7 @@ int ea_chmod_dir(VFS_FUNC_ARGS_SETDIRUNIXMODE) } /* Set mode on EA header */ - if ((setfilmode(ea_path(&ea, NULL, 0), ea_header_mode(mode), NULL, vol->v_umask)) != 0) { + if ((setfilmode(vol, ea_path(&ea, NULL, 0), ea_header_mode(mode), NULL)) != 0) { LOG(log_error, logtype_afpd, "ea_chmod_dir('%s'): %s", ea_path(&ea, NULL, 0), strerror(errno)); switch (errno) { case EPERM: @@ -1742,7 +1742,7 @@ int ea_chmod_dir(VFS_FUNC_ARGS_SETDIRUNIXMODE) ret = AFPERR_MISC; goto exit; } - if ((setfilmode(eaname, ea_mode(mode), NULL, vol->v_umask)) != 0) { + if ((setfilmode(vol, eaname, ea_mode(mode), NULL)) != 0) { LOG(log_error, logtype_afpd, "ea_chmod_dir('%s'): %s", eaname, strerror(errno)); switch (errno) { case EPERM: diff --git a/libatalk/vfs/unix.c b/libatalk/vfs/unix.c index 1c0556d9..40f14fb5 100644 --- a/libatalk/vfs/unix.c +++ b/libatalk/vfs/unix.c @@ -33,7 +33,7 @@ int dir_rx_set(mode_t mode) } /* --------------------- */ -int setfilmode(const char * name, mode_t mode, struct stat *st, mode_t v_umask) +int setfilmode(const struct vol *vol, const char *name, mode_t mode, struct stat *st) { struct stat sb; mode_t mask = S_IRWXU | S_IRWXG | S_IRWXO; /* rwx for owner group and other, by default */ @@ -44,12 +44,9 @@ int setfilmode(const char * name, mode_t mode, struct stat *st, mode_t v_umask) st = &sb; } - if (S_ISLNK(st->st_mode)) - return 0; /* we don't want to change link permissions */ - mode |= st->st_mode & ~mask; /* keep other bits from previous mode */ - if ( chmod_acl( name, mode & ~v_umask ) < 0 && errno != EPERM ) { + if (ochmod(name, mode & ~vol->v_umask, st, vol_syml_opt(vol) | O_NETATALK_ACL) < 0 && errno != EPERM ) { return -1; } return 0; @@ -334,29 +331,6 @@ int statat(int dirfd, const char *path, struct stat *st) return -1; } -/* - * @brief lstat/fsstatat multiplexer - * - * lstatat mulitplexes lstat and fstatat. If we dont HAVE_ATFUNCS, dirfd is ignored. - * - * @param dirfd (r) Only used if HAVE_ATFUNCS, ignored else, -1 gives AT_FDCWD - * @param path (r) pathname - * @param st (rw) pointer to struct stat - */ -int lstatat(int dirfd, const char *path, struct stat *st) -{ -#ifdef HAVE_ATFUNCS - if (dirfd == -1) - dirfd = AT_FDCWD; - return (fstatat(dirfd, path, st, AT_SYMLINK_NOFOLLOW)); -#else - return (lstat(path, st)); -#endif - - /* DEADC0DE */ - return -1; -} - /* * @brief opendir wrapper for *at semantics support * diff --git a/libatalk/vfs/vfs.c b/libatalk/vfs/vfs.c index 8495d8c2..85704976 100644 --- a/libatalk/vfs/vfs.c +++ b/libatalk/vfs/vfs.c @@ -49,11 +49,11 @@ struct perm { gid_t gid; }; -typedef int (*rf_loop)(struct dirent *, char *, void *, int , mode_t ); +typedef int (*rf_loop)(const struct vol *, struct dirent *, char *, void *, int); /* ----------------------------- */ static int -for_each_adouble(const char *from, const char *name, rf_loop fn, void *data, int flag, mode_t v_umask) +for_each_adouble(const char *from, const char *name, rf_loop fn, const struct vol *vol, void *data, int flag) { char buf[ MAXPATHLEN + 1]; char *m; @@ -79,7 +79,7 @@ for_each_adouble(const char *from, const char *name, rf_loop fn, void *data, int } strlcat(buf, de->d_name, sizeof(buf)); - if (fn && (ret = fn(de, buf, data, flag, v_umask))) { + if (fn && (ret = fn(vol, de, buf, data, flag))) { closedir(dp); return ret; } @@ -127,7 +127,7 @@ static int RF_renamedir_adouble(VFS_FUNC_ARGS_RENAMEDIR) } /* ----------------- */ -static int deletecurdir_adouble_loop(struct dirent *de, char *name, void *data _U_, int flag _U_, mode_t v_umask) +static int deletecurdir_adouble_loop(const struct vol *vol, struct dirent *de, char *name, void *data _U_, int flag _U_) { struct stat st; int err; @@ -150,20 +150,20 @@ static int RF_deletecurdir_adouble(VFS_FUNC_ARGS_DELETECURDIR) /* delete stray .AppleDouble files. this happens to get .Parent files as well. */ - if ((err = for_each_adouble("deletecurdir", ".AppleDouble", deletecurdir_adouble_loop, NULL, 1, vol->v_umask))) + if ((err = for_each_adouble("deletecurdir", ".AppleDouble", deletecurdir_adouble_loop, vol, NULL, 1))) return err; return netatalk_rmdir(-1, ".AppleDouble" ); } /* ----------------- */ -static int adouble_setfilmode(const char * name, mode_t mode, struct stat *st, mode_t v_umask) +static int adouble_setfilmode(const struct vol *vol, const char *name, mode_t mode, struct stat *st) { - return setfilmode(name, ad_hf_mode(mode), st, v_umask); + return setfilmode(vol, name, ad_hf_mode(mode), st); } static int RF_setfilmode_adouble(VFS_FUNC_ARGS_SETFILEMODE) { - return adouble_setfilmode(vol->ad_path(name, ADFLAGS_HF ), mode, st, vol->v_umask); + return adouble_setfilmode(vol, vol->ad_path(name, ADFLAGS_HF ), mode, st); } /* ----------------- */ @@ -177,7 +177,7 @@ static int RF_setdirunixmode_adouble(VFS_FUNC_ARGS_SETDIRUNIXMODE) return -1; } - if (adouble_setfilmode(vol->ad_path(name, ADFLAGS_DIR ), mode, st, vol->v_umask) < 0) + if (adouble_setfilmode(vol, vol->ad_path(name, ADFLAGS_DIR ), mode, st) < 0) return -1; if (!dir_rx_set(mode)) { @@ -188,18 +188,18 @@ static int RF_setdirunixmode_adouble(VFS_FUNC_ARGS_SETDIRUNIXMODE) } /* ----------------- */ -static int setdirmode_adouble_loop(struct dirent *de _U_, char *name, void *data, int flag, mode_t v_umask) +static int setdirmode_adouble_loop(const struct vol *vol, struct dirent *de _U_, char *name, void *data, int flag) { mode_t hf_mode = *(mode_t *)data; struct stat st; - if ( stat( name, &st ) < 0 ) { + if (ostat(name, &st, vol_syml_opt(vol)) < 0 ) { if (flag) return 0; LOG(log_error, logtype_afpd, "setdirmode: stat %s: %s", name, strerror(errno) ); } else if (!S_ISDIR(st.st_mode)) { - if (setfilmode(name, hf_mode , &st, v_umask) < 0) { + if (setfilmode(vol, name, hf_mode, &st) < 0) { /* FIXME what do we do then? */ } } @@ -257,7 +257,7 @@ static int RF_renamefile_adouble(VFS_FUNC_ARGS_RENAMEFILE) if (errno == ENOENT) { struct adouble ad; - if (lstatat(dirfd, adsrc, &st)) /* source has no ressource fork, */ + if (ostatat(dirfd, adsrc, &st, vol_syml_opt(vol))) /* source has no ressource fork, */ return 0; /* We are here because : @@ -643,7 +643,7 @@ static int RF_renamefile_ea(VFS_FUNC_ARGS_RENAMEFILE) struct stat st; err = errno; - if (errno == ENOENT && lstatat(dirfd, adsrc, &st)) /* source has no ressource fork, */ + if (errno == ENOENT && ostatat(dirfd, adsrc, &st, vol_syml_opt(vol))) /* source has no ressource fork, */ return 0; errno = err; return -1; diff --git a/man/man5/afp.conf.5.tmpl b/man/man5/afp.conf.5.tmpl index d2bbf43a..4a41167b 100644 --- a/man/man5/afp.conf.5.tmpl +++ b/man/man5/afp.conf.5.tmpl @@ -1,7 +1,7 @@ '\" t .\" Title: afp.conf .\" Author: [FIXME: author] [see http://docbook.sf.net/el/author] -.\" Generator: DocBook XSL Stylesheets v1.75.2 +.\" Generator: DocBook XSL Stylesheets v1.78.0 .\" Date: 06 Dez 2012 .\" Manual: Netatalk 3.0 .\" Source: Netatalk 3.0 @@ -9,6 +9,15 @@ .\" .TH "AFP\&.CONF" "5" "06 Dez 2012" "Netatalk 3.0" "Netatalk 3.0" .\" ----------------------------------------------------------------- +.\" * Define some portability stuff +.\" ----------------------------------------------------------------- +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.\" http://bugs.debian.org/507673 +.\" http://lists.gnu.org/archive/html/groff/2009-02/msg00013.html +.\" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.ie \n(.g .ds Aq \(aq +.el .ds Aq ' +.\" ----------------------------------------------------------------- .\" * set default formatting .\" ----------------------------------------------------------------- .\" disable hyphenation @@ -113,7 +122,7 @@ parameter means that not the whole user home will be shared but the subdirectory option\&. It should be a regex which matches the parent directory of the user homes\&. Parameters denoted by a (H) belong to volume sections\&. The optional parameter \fBhome name\fR can be used to change the AFP volume name which -\fI$u\'s home\fR +\fI$u\*(Aqs home\fR by default\&. See below under VARIABLE SUBSTITUTIONS\&. .PP The following example illustrates this\&. Given all user home directories are stored under @@ -167,7 +176,7 @@ if you specify an unknown variable, it will not get converted\&. .sp -1 .IP " 2." 4.2 .\} -if you specify a known variable, but that variable doesn\'t have a value, it will get ignored\&. +if you specify a known variable, but that variable doesn\*(Aqt have a value, it will get ignored\&. .RE .PP The variables which can be used for substitutions are: @@ -179,7 +188,7 @@ basename .PP $c .RS 4 -client\'s ip address +client\*(Aqs ip address .RE .PP $d @@ -204,7 +213,7 @@ hostname .PP $i .RS 4 -client\'s ip, without port +client\*(Aqs ip, without port .RE .PP $s @@ -310,7 +319,7 @@ With OS X Apple introduced the AFP3 protocol\&. One of the big changes was, that .PP To be able to serve AFP3 and older clients at the same time, \fBafpd\fR -needs to be able to convert between UTF\-8 and Mac charsets\&. Even OS X clients partly still rely on the mac charset\&. As there\'s no way, +needs to be able to convert between UTF\-8 and Mac charsets\&. Even OS X clients partly still rely on the mac charset\&. As there\*(Aqs no way, \fBafpd\fR can detect the codepage a pre AFP3 client uses, you have to specify it using the \fBmac charset\fR @@ -318,12 +327,12 @@ option\&. The default is MacRoman, which should be fine for most western users\& .PP As \fBafpd\fR -needs to interact with UNIX operating system as well, it need\'s to be able to convert from UTF8\-MAC / Mac charset to the UNIX charset\&. By default +needs to interact with UNIX operating system as well, it need\*(Aqs to be able to convert from UTF8\-MAC / Mac charset to the UNIX charset\&. By default \fBafpd\fR uses \fIUTF8\fR\&. You can set the UNIX charset using the \fBunix charset\fR -option\&. If you\'re using extended characters in the configuration files for +option\&. If you\*(Aqre using extended characters in the configuration files for \fBafpd\fR, make sure your terminal matches the \fBunix charset\fR\&. .PP @@ -366,7 +375,7 @@ Sets the minimum password length, if supported by the UAM .PP advertise ssh = \fIBOOLEAN\fR (default: \fIno\fR) \fB(G)\fR .RS 4 -Allows old Mac OS X clients (10\&.3\&.3\-10\&.4) to automagically establish a tunneled AFP connection through SSH\&. If this option is set, the server\'s answers to client\'s FPGetSrvrInfo requests contain an additional entry\&. It depends on both client\'s settings and a correctly configured and running +Allows old Mac OS X clients (10\&.3\&.3\-10\&.4) to automagically establish a tunneled AFP connection through SSH\&. If this option is set, the server\*(Aqs answers to client\*(Aqs FPGetSrvrInfo requests contain an additional entry\&. It depends on both client\*(Aqs settings and a correctly configured and running \fBsshd\fR(8) on the server to let things work\&. .if n \{\ @@ -381,7 +390,7 @@ on the server to let things work\&. \fBNote\fR .ps -1 .br -Setting this option is not recommended since globally encrypting AFP connections via SSH will increase the server\'s load significantly\&. On the other hand, Apple\'s client side implementation of this feature in MacOS X versions prior to 10\&.3\&.4 contained a security flaw\&. +Setting this option is not recommended since globally encrypting AFP connections via SSH will increase the server\*(Aqs load significantly\&. On the other hand, Apple\*(Aqs client side implementation of this feature in MacOS X versions prior to 10\&.3\&.4 contained a security flaw\&. .sp .5v .RE .RE @@ -437,7 +446,7 @@ Sets the maximum number of clients that can simultaneously connect to the server .PP server quantum = \fInumber\fR \fB(G)\fR .RS 4 -This specifies the DSI server quantum\&. The default value is 303840\&. The maximum value is 0xFFFFFFFFF, the minimum is 32000\&. If you specify a value that is out of range, the default value will be set\&. Do not change this value unless you\'re absolutely sure, what you\'re doing +This specifies the DSI server quantum\&. The default value is 303840\&. The maximum value is 0xFFFFFFFFF, the minimum is 32000\&. If you specify a value that is out of range, the default value will be set\&. Do not change this value unless you\*(Aqre absolutely sure, what you\*(Aqre doing .RE .PP sleep time = \fInumber\fR \fB(G)\fR @@ -477,7 +486,7 @@ Allows users of a certain group to be seen as the superuser when they log in\&. .PP afp read locks = \fIBOOLEAN\fR (default: \fIno\fR) \fB(G)\fR .RS 4 -Whether to apply locks to the byte region read in FPRead calls\&. The AFP spec mandates this, but it\'s not really in line with UNIX semantics and is a performance hug\&. +Whether to apply locks to the byte region read in FPRead calls\&. The AFP spec mandates this, but it\*(Aqs not really in line with UNIX semantics and is a performance hug\&. .RE .PP basedir regex = \fIregex\fR \fB(H)\fR @@ -488,7 +497,7 @@ Regular expression which matches the parent directory of the user homes\&. In th .PP close vol = \fIBOOLEAN\fR (default: \fIno\fR) \fB(G)\fR .RS 4 -Whether to close volumes possibly opened by clients when they\'re removed from the configuration and the configuration is reloaded\&. +Whether to close volumes possibly opened by clients when they\*(Aqre removed from the configuration and the configuration is reloaded\&. .RE .PP cnid server = \fIipaddress[:port]\fR \fB(G)/(V)\fR @@ -516,7 +525,7 @@ Specifies the user that guests should use (default is "nobody")\&. The name shou home name = \fIname\fR \fB(H)\fR .RS 4 AFP user home volume name\&. The default is -\fIusers\'s home\fR\&. +\fIusers\*(Aqs home\fR\&. .RE .PP keep sessions = \fIBOOLEAN\fR (default: \fIno\fR) \fB(G)\fR @@ -584,7 +593,7 @@ vol preset = \fIname\fR \fB(G)/(V)\fR .RS 4 Use section \fBname\fR -as option preset for all volumes (when set in the [Global] section) or for one volume (when set in that volume\'s section)\&. +as option preset for all volumes (when set in the [Global] section) or for one volume (when set in that volume\*(Aqs section)\&. .RE .SS "Logging Options" .PP @@ -665,16 +674,16 @@ Specify the number of tickles to send before timing out a connection\&. The defa .PP client polling = \fIBOOLEAN\fR (default: \fIno\fR) \fB(G)\fR .RS 4 -With this option enabled, afpd won\'t advertise that it is capable of server notifications, so that connected clients poll the server every 10 seconds to detect changes in opened server windows\&. -\fINote\fR: Depending on the number of simultaneously connected clients and the network\'s speed, this can lead to a significant higher load on your network! +With this option enabled, afpd won\*(Aqt advertise that it is capable of server notifications, so that connected clients poll the server every 10 seconds to detect changes in opened server windows\&. +\fINote\fR: Depending on the number of simultaneously connected clients and the network\*(Aqs speed, this can lead to a significant higher load on your network! .sp Do not use this option any longer as present Netatalk correctly supports server notifications, allowing connected clients to update folder listings in case another client changed the contents\&. .RE .SS "Options for ACL handling" .PP -For a basic mode of operation there\'s nothing to configure\&. afpd reads ACLs on the fly, calculating effective permissions and returning the calculated permissions via the so called UARights permission bits\&. On a Mac the Finder uses these bits to adjust permission in Finder windows\&. For example folder whos UNIX mode would only result in in read\-only permissions for a user will not be displayed with a read\-only icon and the user will be able to write to the folder given the folder has an ACL giving the user write access\&. +For a basic mode of operation there\*(Aqs nothing to configure\&. afpd reads ACLs on the fly, calculating effective permissions and returning the calculated permissions via the so called UARights permission bits\&. On a Mac the Finder uses these bits to adjust permission in Finder windows\&. For example folder whos UNIX mode would only result in in read\-only permissions for a user will not be displayed with a read\-only icon and the user will be able to write to the folder given the folder has an ACL giving the user write access\&. .PP -However, neither in Finder "Get Info" windows nor in Terminal will you be able to see the ACLs, that\'s a result of how ACLs in OS X are designed\&. If you want to be able to display ACLs on the client, things get more involed as you must then setup both client and server to be part on a authentication domain (directory service, eg LDAP, OpenDirectory)\&. The reason is, that in OS X ACLs are bound to UUIDs, not just uid\'s or gid\'s\&. Therefor afpd must be able to map every filesystem uid and gid to a UUID so that it can return the server side ACLs which are bound to UNIX uid and gid mapped to OS X UUIDs\&. Get it? Read on\&. +However, neither in Finder "Get Info" windows nor in Terminal will you be able to see the ACLs, that\*(Aqs a result of how ACLs in OS X are designed\&. If you want to be able to display ACLs on the client, things get more involed as you must then setup both client and server to be part on a authentication domain (directory service, eg LDAP, OpenDirectory)\&. The reason is, that in OS X ACLs are bound to UUIDs, not just uid\*(Aqs or gid\*(Aqs\&. Therefor afpd must be able to map every filesystem uid and gid to a UUID so that it can return the server side ACLs which are bound to UNIX uid and gid mapped to OS X UUIDs\&. Get it? Read on\&. .PP Netatalk can query a directory server using LDAP queries\&. Either the directory server already provides an UUID attribute for user and groups (Active Directory, Open Directory) or you reuse an unused attribute (or add a new one) to you directory server (eg OpenLDAP)\&. .PP @@ -784,7 +793,7 @@ Name of the LDAP attribute with the groups short name\&. .SS "Parameters" .PP The section name defines the volume name which is the name that appears in the Chooser or the "connect to server" dialog on Macintoshes to represent the appropriate share\&. No two volumes may have the same name\&. The volume name cannot contain the -\':\' +\*(Aq:\*(Aq character\&. The volume name is mangled if it is very long\&. Mac charset volume name is limited to 27 characters\&. UTF8\-MAC volume name is limited to volnamelen parameter\&. .PP path = \fIPATH\fR \fB(V)\fR @@ -792,7 +801,7 @@ path = \fIPATH\fR \fB(V)\fR The path name must be a fully qualified path name, or a path name using either the ~ shell shorthand or any of the substitution variables, which are listed below\&. .sp The volume name is the name that appears in the Chooser ot the "connect to server" dialog on Macintoshes to represent the appropriate share\&. If volumename is unspecified, the last component of pathname is used\&. No two volumes may have the same name\&. If there are spaces in the name, it should be in quotes (i\&.e\&. "File Share")\&. The volume name cannot contain the -\':\' +\*(Aq:\*(Aq character\&. The volume name is mangled if it is very long\&. Mac charset volume name is limited to 27 characters\&. UTF8\-MAC volume name is limited to volnamelen parameter\&. .RE .PP @@ -919,7 +928,7 @@ Add(or) with the client requested permissions: \fBfile perm\fR is for files only, \fBdirectory perm\fR -is for directories only\&. Don\'t use with "\fBunix priv = no\fR"\&. +is for directories only\&. Don\*(Aqt use with "\fBunix priv = no\fR"\&. .PP \fBExample.\ \&Volume for a collaborative workgroup\fR .sp @@ -938,7 +947,7 @@ file perm = 0660 directory perm = .PP umask = \fImode\fR \fB(V)\fR .RS 4 -set perm mask\&. Don\'t use with "\fBunix priv = no\fR"\&. +set perm mask\&. Don\*(Aqt use with "\fBunix priv = no\fR"\&. .RE .PP preexec = \fIcommand\fR \fB(V)\fR @@ -973,7 +982,7 @@ Allows certain users and groups to have read/write access to a share\&. This fol .PP veto files = \fIvetoed names\fR \fB(V)\fR .RS 4 -hide files and directories,where the path matches one of the \'/\' delimited vetoed names\&. The veto string must always be terminated with a \'/\', eg\&. "veto1/", "veto1/veto2/"\&. +hide files and directories,where the path matches one of the \*(Aq/\*(Aq delimited vetoed names\&. The veto string must always be terminated with a \*(Aq/\*(Aq, eg\&. "veto1/", "veto1/veto2/"\&. .RE .SS "Volume options" .PP @@ -995,11 +1004,16 @@ Whether automatic conversion from \fBappledouble = v2\fR to \fBappledouble = ea\fR -is performed when accessing filesystems from clients\&. This is generally useful, but costs some performance\&. It\'s recommdable to run +is performed when accessing filesystems from clients\&. This is generally useful, but costs some performance\&. It\*(Aqs recommdable to run \fBdbd\fR on volumes and do the conversion with that\&. Then this option can be set to no\&. .RE .PP +follow symlinks = \fIBOOLEAN\fR (default: \fIno\fR) \fB(V)\fR +.RS 4 +The default setting is false thus symlinks are not followed on the server\&. This is the same behaviour as OS X\*(Aqs AFP server\&. Setting the option to true causes afpd to follow symlinks on the server\&. symlinks may point outside of the AFP volume, currently afpd doesn\*(Aqt do any checks for "wide symlinks"\&. +.RE +.PP invisible dots = \fIBOOLEAN\fR (default: \fIno\fR) \fB(V)\fR .RS 4 make dot files invisible\&. @@ -1055,7 +1069,7 @@ and .RE .SH "CNID BACKENDS" .PP -The AFP protocol mostly refers to files and directories by ID and not by name\&. Netatalk needs a way to store these ID\'s in a persistent way, to achieve this several different CNID backends are available\&. The CNID Databases are by default located in the +The AFP protocol mostly refers to files and directories by ID and not by name\&. Netatalk needs a way to store these ID\*(Aqs in a persistent way, to achieve this several different CNID backends are available\&. The CNID Databases are by default located in the :STATEDIR:/netatalk/CNID/(volumename)/\&.AppleDB/ directory\&. .PP @@ -1082,7 +1096,7 @@ processes communicate with the daemon for database reads and updates\&. If built .PP last .RS 4 -This backend is an exception, in terms of ID persistency\&. ID\'s are only valid for the current session\&. This is basically what +This backend is an exception, in terms of ID persistency\&. ID\*(Aqs are only valid for the current session\&. This is basically what \fBafpd\fR did in the 1\&.5 (and 1\&.6) versions\&. This backend is still available, as it is useful for e\&.g\&. sharing cdroms\&. Starting with Netatalk 3\&.0, it becomes the \fIread only mode\fR @@ -1097,20 +1111,20 @@ now relies heavily on a persistent ID database\&. Aliases will likely not work a .PP Even though \fB\&./configure \-\-help\fR -might show that there are other CNID backends available, be warned those are likely broken or mainly used for testing\&. Don\'t use them unless you know what you\'re doing, they may be removed without further notice from future versions\&. +might show that there are other CNID backends available, be warned those are likely broken or mainly used for testing\&. Don\*(Aqt use them unless you know what you\*(Aqre doing, they may be removed without further notice from future versions\&. .SH "CHARSET OPTIONS" .PP With OS X Apple introduced the AFP3 protocol\&. One of the most important changes was that AFP3 uses unicode names encoded as UTF\-8 decomposed\&. Previous AFP/OS versions used codepages, like MacRoman, MacCentralEurope, etc\&. .PP \fBafpd\fR needs a way to preserve extended macintosh characters, or characters illegal in unix filenames, when saving files on a unix filesystem\&. Earlier versions used the the so called CAP encoding\&. An extended character (>0x7F) would be converted to a :xx sequence, e\&.g\&. the Apple Logo (MacRoman: 0xF0) was saved as -:f0\&. Some special characters will be converted as to :xx notation as well\&. \'/\' will be encoded to +:f0\&. Some special characters will be converted as to :xx notation as well\&. \*(Aq/\*(Aq will be encoded to :2f, if \fBusedots\fR -is not specified, a leading dot \'\&.\' will be encoded as +is not specified, a leading dot \*(Aq\&.\*(Aq will be encoded as :2e\&. .PP -This version now uses UTF\-8 as the default encoding for names\&. \'/\' will be converted to \':\'\&. +This version now uses UTF\-8 as the default encoding for names\&. \*(Aq/\*(Aq will be converted to \*(Aq:\*(Aq\&. .PP The \fBvol charset\fR @@ -1126,7 +1140,7 @@ to the selected will convert the UTF\-8 character to \fBmac charset\fR -first\&. If this conversion fails, you\'ll receive a \-50 error on the mac\&. +first\&. If this conversion fails, you\*(Aqll receive a \-50 error on the mac\&. .PP \fINote\fR: Whenever you can, please stick with the default UTF\-8 volume format\&. .SH "SEE ALSO" -- 2.39.2