--- /dev/null
+Index: configure.in
+===================================================================
+RCS file: /cvsroot/netatalk/netatalk/configure.in,v
+retrieving revision 1.205
+diff -u -w -b -r1.205 configure.in
+--- configure.in 9 Sep 2006 04:30:01 -0000 1.205
++++ configure.in 25 Jul 2008 13:48:05 -0000
+@@ -1053,6 +1053,7 @@
+ contrib/shell_utils/apple_rm
+ contrib/shell_utils/asip-status.pl
+ contrib/shell_utils/cleanappledouble.pl
++ contrib/shell_utils/permtest.pl
+ contrib/timelord/Makefile
+ contrib/a2boot/Makefile
+ distrib/Makefile
+Index: contrib/shell_utils/Makefile.am
+===================================================================
+RCS file: /cvsroot/netatalk/netatalk/contrib/shell_utils/Makefile.am,v
+retrieving revision 1.16
+diff -u -w -b -r1.16 Makefile.am
+--- contrib/shell_utils/Makefile.am 28 Apr 2005 20:49:36 -0000 1.16
++++ contrib/shell_utils/Makefile.am 25 Jul 2008 13:48:05 -0000
+@@ -9,6 +9,9 @@
+ apple_cp apple_mv apple_rm \
+ cleanappledouble.pl \
+ asip-status.pl
++EXTRASCRIPTS = \
++ permtest.pl \
++ permtest.cfg
+
+ SUFFIXES = .tmpl .
+
+@@ -22,4 +25,4 @@
+
+ bin_SCRIPTS = $(PERLSCRIPTS) $(GENERATED_FILES)
+
+-EXTRA_DIST = $(PERLSCRIPTS) $(TEMPLATE_FILES)
++EXTRA_DIST = $(PERLSCRIPTS) $(TEMPLATE_FILES) $(EXTRASCRIPTS)
--- /dev/null
+# Exactly follow this layout! Don't put extra white space in config lines !!
+# Order doesn't matter.
+
+# We use a ssh executed stat command to verify uid,gid and mode. Therefore ssh access with
+# PKI authentication must be setup and working!
+sshLogin = USER@HOST
+
+# self explaining
+mountAFPVolume = afp://USER:PASSWORD@HOST/VOLUME
+
+# These files will be created
+createFile = PATH_TO_FILE_ON_CLIENT
+
+# These files will be stat'ed. You can use different server-side paths here for files
+# created with "createFile" directive
+testFile = PATH_TO_FILE_ON_SERVER,user=USERNAME,group=GROUPNAME,mode=MODE
+
+# These dirs will be created
+createDir = PATH_TO_DIR_ON_CLIENT
+
+# These will be verified.
+testDir = PATH_TO_DIR_ON_CLIENT,user=USERNAME,group=GROUPNAME,mode=MODE
+
+# EOF. Leave this as a last line. We delibaretly chop in perl which might otherwise truncate
+# your last "testDir" definition.
\ No newline at end of file
--- /dev/null
+#!@PERL@ -w
+
+###########################################################################
+#
+# Characterization testing netatalks permission model
+#
+# (c) 2008 by Frank Lahm <franklahm@googlemail.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+###########################################################################
+
+###########################################################################
+#
+# Usage:
+#
+# "permtest.cfg" must be in your CWD. Must be run on a OS X host. Tested
+# with 10.4.11. Uses Applescript through system("osascript ...") to mount
+# AFP Volumes. Uses `ssh LOGIN@HOST stat FILE|DIR`. Therefor PKI
+# authentication must be setup and working!
+# See "permtest.cfg" for more details, it's pretty much self-explaining.
+#
+###########################################################################
+
+use strict;
+
+my $DEBUG = 0;
+
+###########################################################################
+
+sub parseConfig;
+sub mountAPFVols;
+sub createTestFiles;
+sub createTestDirs;
+sub verifyTestFiles;
+sub verifyTestDirs;
+sub unmountAfp;
+
+my ($sshLogin, $sshResult, %sshStat, @AFPVols, @createFiles, @createDirs, @testFiles, @testDirs);
+my ($dir, $file, $user, $group, $perms, $mode, $cmd);
+
+parseConfig();
+mountAPFVols();
+createTestFiles();
+createTestDirs();
+print "\n";
+verifyTestFiles();
+verifyTestDirs();
+unmountAfp();
+
+exit 0;
+
+###########################################################################
+
+# parse config file
+sub parseConfig
+{
+ open CFG, "permtest.cfg" or die "Config file not found!";
+ while (<CFG>) {
+ chop;
+ if (/^#/) { next; };
+ if (/^sshLogin/) {
+ $sshLogin = $_;
+ $sshLogin =~ s/^sshLogin ?= ?// ;
+ next;
+ }
+ if (/^mountAFPVolume/) {
+ s/^mountAFPVolume ?= ?// ;
+ print "Found AFP Volume Definition \"$_\"\n" if $DEBUG;
+ push @AFPVols, $_;
+ next;
+ }
+ if (/^createFile/) {
+ s/^createFile ?= ?// ;
+ push @createFiles, $_;
+ next;
+ }
+ if (/^createDir/) {
+ s/^createDir ?= ?// ;
+ push @createDirs, $_;
+ next;
+ }
+ if (/^testFile/) {
+ push @testFiles, $_;
+ next;
+ }
+ if (/^testDir/) {
+ push @testDirs, $_;
+ next;
+ }
+ }
+ close CFG;
+}
+
+# mount AFP Volumes
+sub mountAPFVols
+{
+ foreach (@AFPVols) {
+ print "Mounting AFP Volume \"$_\"\n";
+ $cmd = "osascript -e 'tell application \"Finder\"' -e 'mount volume \"$_\"' -e 'end tell' &> /dev/null";
+ print "Going to run the following Applescript:\n" . $cmd . "\n\n" if $DEBUG;
+ system($cmd);
+ if ($? != 0) { die "Error mounting \"$_\"\n"; }
+ }
+}
+
+# Create test files
+sub createTestFiles
+{
+ foreach (@createFiles) {
+ s/^createFile ?= ?// ;
+ system("rm \"$_\" &> /dev/null");
+ print "Creating file: \"$_\"\n";
+ system("touch \"$_\"");
+ if ($? != 0) { die "Error creating file \"$_\"\n"; }
+ }
+}
+
+# Create test dirs
+sub createTestDirs
+{
+ foreach (@createDirs) {
+ s/^createDir ?= ?// ;
+ system("rmdir \"$_\" &> /dev/null");
+ print "Creating dir: \"$_\"\n";
+ system("mkdir \"$_\"");
+ if ($? != 0) { die "Error creating dir \"$_\"\n"; }
+ }
+}
+
+# Verify files and dirs
+sub verifyTestFiles
+{
+ foreach (@testFiles) {
+ my @line = split(",");
+ foreach (@line) {
+ if (/^testFile/) {
+ $file = $_;
+ $file =~ s/^testFile ?= ?//;
+ }
+ elsif (/^user/) {
+ $user = $_;
+ $user =~ s/^user ?= ?//;
+ }
+ elsif (/^group/) {
+ $group = $_;
+ $group =~ s/^group ?= ?//;
+ }
+ elsif (/^mode/) {
+ $mode = $_;
+ $mode =~ s/^mode ?= ?//;
+ }
+ } # foreach (@elems)
+ print "File: $file, User: $user, Group: $group, Perms: $perms\n" if $DEBUG;
+
+ $sshResult = `ssh $sshLogin stat -c \"user,%U,group,%G,mode,0%a\" \"$file\"`;
+ if ($? != 0) { die "Error stat'ing file \"$file\"\n"; }
+ chop $sshResult;
+ print "ssh stat $file gave us: $sshResult\n" if $DEBUG;
+
+ %sshStat = split(",", $sshResult);
+ if ( ($sshStat{user} ne $user) or ($sshStat{group} ne $group) or ($sshStat{mode} ne $mode) ) {
+ print "Creatin error for: \"$file\"!\nExpected:\t$user, $group, $mode.\nGot:\t\t$sshStat{user}, $sshStat{group}, $sshStat{mode}.\n\n";
+ }
+ system("rm \"$file\"");
+ if ($? != 0) { die "Couldn't delete \"$file\"\n"; }
+ }
+}
+
+sub verifyTestDirs
+{
+ foreach (@testDirs) {
+ my @line = split(",");
+ foreach (@line) {
+ if (/^testDir/) {
+ $dir = $_;
+ $dir =~ s/^testDir ?= ?//;
+ }
+ elsif (/^user/) {
+ $user = $_;
+ $user =~ s/^user ?= ?//;
+ }
+ elsif (/^group/) {
+ $group = $_;
+ $group =~ s/^group ?= ?//;
+ }
+ elsif (/^mode/) {
+ $mode = $_;
+ $mode =~ s/^mode ?= ?//;
+ }
+ } # foreach (@elems)
+ print "Dir: $dir, User: $user, Group: $group, Perms: $perms\n" if $DEBUG;
+
+ $sshResult = `ssh $sshLogin stat -c \"user,%U,group,%G,mode,0%a\" \"$dir\"`;
+ if ($? != 0) { die "Error stat'ing file \"$dir\"\n"; }
+ chop $sshResult;
+ print "ssh stat $dir gave us: $sshResult\n" if $DEBUG;
+
+ %sshStat = split(",", $sshResult);
+ if ( ($sshStat{user} ne $user) or ($sshStat{group} ne $group) or ($sshStat{mode} ne $mode) ) {
+ print "Creatin error for: \"$dir\"!\nExpected:\t$user, $group, $mode.\nGot:\t\t$sshStat{user}, $sshStat{group}, $sshStat{mode}.\n\n";
+ }
+ system("rmdir \"$dir\""); if ($? != 0) { die "Couldn't delete \"$dir\"\n"; }
+ }
+}
+
+sub unmountAfp
+{
+ foreach (@AFPVols) {
+ print "Goint to eject Volume \"$_\"\n";
+ s#^(.*/)## ;
+ $cmd = "osascript -e 'tell application \"Finder\"' -e 'eject \"$_\"' -e 'end tell' &> /dev/null";
+ print "Going to run the following Applescript:\n" . $cmd . "\n\n" if $DEBUG;
+ system($cmd);
+ }
+}