File indexing completed on 2024-05-05 16:08:22

0001 #!/usr/bin/perl -T
0002 use strict;
0003 
0004 ########################################
0005 # config files
0006 $nfs_exports::default_options = '*(ro,all_squash)';
0007 $nfs_exports::conf_file = '/etc/exports';
0008 $smb_exports::conf_file = '/etc/samba/smb.conf';
0009 my $authorisation_file = '/etc/security/fileshare.conf';
0010 my $authorisation_group = 'fileshare';
0011 
0012 
0013 ########################################
0014 # Copyright (C) 2001-2002 MandrakeSoft (pixel@mandriva.com)
0015 #
0016 # This program is free software; you can redistribute it and/or modify
0017 # it under the terms of the GNU General Public License as published by
0018 # the Free Software Foundation; either version 2, or (at your option)
0019 # any later version.
0020 #
0021 # This program is distributed in the hope that it will be useful,
0022 # but WITHOUT ANY WARRANTY; without even the implied warranty of
0023 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0024 # GNU General Public License for more details.
0025 #
0026 # You should have received a copy of the GNU General Public License
0027 # along with this program; if not, write to the Free Software
0028 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
0029 
0030 
0031 ########################################
0032 my $uid = $<;
0033 my $username = getpwuid($uid);
0034 
0035 ########################################
0036 # errors
0037 my $usage =
0038 "usage: fileshareset --add <dir>
0039        fileshareset --remove <dir>";
0040 my $not_enabled =
0041 qq(File sharing is not enabled.
0042 To enable file sharing put 
0043 "FILESHARING=yes" in $authorisation_file);
0044        
0045 my $not_simple_enabled = 
0046 qq(Simple file sharing is not enabled.
0047 To enable simple file sharing put
0048 "SHARINGMODE=simple" in $authorisation_file);
0049 
0050 my $non_authorised =
0051 qq(You are not authorised to use file sharing
0052 To grant you the rights:
0053 - put "RESTRICT=no" in $authorisation_file
0054 - or put user "$username" in group "$authorisation_group");
0055 
0056 my $no_export_method = "can't export anything: no nfs, no smb";
0057 
0058 my %exit_codes = reverse (
0059   1 => $non_authorised,
0060   2 => $usage,
0061 
0062 # when adding
0063   3 => "already exported", 
0064   4 => "invalid mount point",
0065 
0066 # when removing
0067   5 => "not exported",
0068 
0069   6 => $no_export_method,
0070   
0071   7 => $not_enabled,
0072   
0073   8 => $not_simple_enabled,
0074 
0075   255 => "various",
0076 );
0077 
0078 ################################################################################
0079 # correct PATH needed to call /etc/init.d/... ? seems not, but...
0080 %ENV = ();#(PATH => '/bin:/sbin:/usr/bin:/usr/sbin');
0081 
0082 my $modify = $0 =~ /fileshareset/;
0083 
0084 authorisation::check($modify);
0085 
0086 my @exports = (
0087            -e $nfs_exports::conf_file ? nfs_exports::read() : (),
0088            -e $smb_exports::conf_file ? smb_exports::read() : (),
0089           );
0090 @exports or error($no_export_method);
0091 
0092 if ($modify) {
0093     my ($cmd, $dir) = @ARGV;
0094     $< = $>;
0095     @ARGV == 2 && ($cmd eq '--add' || $cmd eq '--remove') or error($usage);
0096 
0097     verify_mntpoint($dir);
0098 
0099     if ($cmd eq '--add') {
0100     my @errs = map { eval { $_->add($dir) }; $@ } @exports;
0101     grep { !$_ } @errs or error("already exported");
0102     } else {
0103     my @errs = map { eval { $_->remove($dir) }; $@ } @exports;
0104     grep { !$_ } @errs or error("not exported");
0105     }    
0106     foreach my $export (@exports) {
0107     $export->write;
0108     $export->update_server;
0109     }
0110 }
0111 my @mntpoints = grep {$_} uniq(map { map { $_->{mntpoint} } @$_ } @exports);
0112 print "$_\n" foreach grep { own($_) } @mntpoints;
0113 
0114 
0115 sub own { $uid == 0 || (stat($_[0]))[4] == $uid }
0116 
0117 sub verify_mntpoint {
0118     local ($_) = @_;
0119     my $ok = 1;
0120     $ok &&= m|^/|;
0121     $ok &&= !m|/\.\./|;
0122     $ok &&= !m|[\0\n\r]|;
0123     $ok &&= -d $_;
0124     $ok &&= own($_);
0125     $ok or error("invalid mount point");
0126 }
0127 
0128 sub error {
0129     my ($string) = @_;
0130     print STDERR "$string\n";
0131     exit($exit_codes{$string} || 255);
0132 }
0133 sub member { my $e = shift; foreach (@_) { $e eq $_ and return 1 } 0 }
0134 sub uniq { my %l; $l{$_} = 1 foreach @_; grep { delete $l{$_} } @_ }
0135 
0136 
0137 ################################################################################
0138 package authorisation;
0139 
0140 sub read_conf {
0141     my ($exclusive_lock) = @_;
0142     open F_lock, $authorisation_file; # don't care if it's missing
0143     flock(F_lock, $exclusive_lock ? 2 : 1) or die "can't lock $authorisation_file";
0144     my %conf;
0145     foreach (<F_lock>) {
0146     s/#.*//; # remove comments
0147     s/^\s+//; 
0148     s/\s+$//;
0149     /^$/ and next;
0150     my ($cmd, $value) = split('=', $_, 2);
0151     $conf{$cmd} = $value || warn qq(suspicious line "$_" in $authorisation_file\n);
0152     }
0153     # no close F_lock, keep it locked
0154     \%conf
0155 }
0156 
0157 sub check {
0158     my ($exclusive_lock) = @_;
0159     my $conf = read_conf($exclusive_lock);
0160     if (lc($conf->{FILESHARING}) eq 'no') {
0161       ::error($not_enabled);
0162     }
0163     
0164     if (lc($conf->{SHARINGMODE}) eq 'advanced') {
0165       ::error($not_simple_enabled);
0166     }
0167     
0168     if (lc($conf->{FILESHAREGROUP} ne '')) {
0169       $authorisation_group = lc($conf->{FILESHAREGROUP});
0170     }      
0171     
0172     if (lc($conf->{RESTRICT}) eq 'no') {
0173     # ok, access granted for everybody
0174     } else {
0175     my @l;
0176     while (@l = getgrent) {
0177         last if $l[0] eq $authorisation_group;
0178     }
0179     ::member($username, split(' ', $l[3])) or ::error($non_authorised);
0180     }
0181 }
0182 
0183 ################################################################################
0184 package exports;
0185 
0186 sub find {
0187     my ($exports, $mntpoint) = @_;
0188     foreach (@$exports) {
0189     $_->{mntpoint} eq $mntpoint and return $_;
0190     }
0191     undef;
0192 }
0193 
0194 sub add {
0195     my ($exports, $mntpoint) = @_;
0196     foreach (@$exports) {
0197     $_->{mntpoint} eq $mntpoint and die 'add';
0198     }
0199     push @$exports, my $e = { mntpoint => $mntpoint };
0200     $e;
0201 }
0202 
0203 sub remove {
0204     my ($exports, $mntpoint) = @_;
0205     my @l = grep { $_->{mntpoint} ne $mntpoint } @$exports;
0206     @l < @$exports or die 'remove';
0207     @$exports = @l;  
0208 }
0209 
0210 
0211 ################################################################################
0212 package nfs_exports;
0213 
0214 use vars qw(@ISA $conf_file $default_options);
0215 BEGIN { @ISA = 'exports' }
0216 
0217 sub read {
0218     my $file = $conf_file;
0219     local *F;
0220     open F, $file or return [];
0221 
0222     my ($prev_raw, $prev_line, %e, @l);
0223     my $line_nb = 0;
0224     foreach my $raw (<F>) {
0225     $line_nb++;
0226     local $_ = $raw;
0227     $raw .= "\n" if !/\n/;
0228 
0229     s/#.*//; # remove comments
0230 
0231     s/^\s+//; 
0232     s/\s+$//; # remove unuseful spaces to help regexps
0233 
0234     if (/^$/) {
0235         # blank lines ignored
0236         $prev_raw .= $raw;
0237         next;
0238     }
0239 
0240     if (/\\$/) {
0241         # line continue across lines
0242         chop; # remove the backslash
0243         $prev_line .= "$_ ";
0244         $prev_raw .= $raw;
0245         next;
0246     }
0247     my $line = $prev_line . $_;
0248     my $raw_line = $prev_raw . $raw;
0249     ($prev_line, $prev_raw) = ('', '');
0250 
0251     my ($mntpoint, $options) = $line =~ /("[^"]*"|\S+)\s+(.*)/ or die "$file:$line_nb: bad line $line\n";
0252 
0253     # You can also specify spaces or any other unusual characters in the
0254     # export path name using a backslash followed by the character code as
0255     # 3 octal digits.
0256     $mntpoint =~ s/\\(\d{3})/chr(oct $1)/ge;
0257 
0258     # not accepting weird characters that would break the output
0259     $mntpoint =~ m/[\0\n\r]/ and die "i won't handle this";
0260     push @l, { mntpoint => $mntpoint, option => $options, raw => $raw_line };
0261     }
0262     bless \@l, 'nfs_exports';
0263 }
0264 
0265 sub write {
0266     my ($nfs_exports) = @_;
0267     foreach (@$nfs_exports) {
0268     if (!exists $_->{options}) {
0269         $_->{options} = $default_options;
0270     }
0271     if (!exists $_->{raw}) {        
0272         my $mntpoint = $_->{mntpoint} =~ /\s/ ? qq("$_->{mntpoint}") : $_->{mntpoint};
0273         $_->{raw} = sprintf("%s %s\n", $mntpoint, $_->{options});
0274     }
0275     }
0276     local *F;
0277     open F, ">$conf_file" or die "can't write $conf_file";
0278     print F $_->{raw} foreach @$nfs_exports;
0279 }
0280 
0281 sub update_server {
0282     if (fork) {
0283     system('/usr/sbin/exportfs', '-r');
0284     if (system('PATH=/bin:/sbin pidof rpc.mountd >/dev/null') != 0 ||
0285         system('PATH=/bin:/sbin pidof nfsd >/dev/null') != 0) {
0286         # trying to start the server...
0287         system('/etc/init.d/portmap start') if system('/etc/init.d/portmap status') != 0;
0288         system('/etc/init.d/nfs', $_) foreach 'stop', 'start';
0289     }
0290     exit 0;
0291     }
0292 }
0293 
0294 ################################################################################
0295 package smb_exports;
0296 
0297 use vars qw(@ISA $conf_file);
0298 BEGIN { @ISA = 'exports' }
0299 
0300 sub read {
0301     my ($s, @l);
0302     local *F;
0303     open F, $conf_file;
0304     local $_;
0305     while (<F>) {
0306     if (/^\s*\[.*\]/ || eof F) {
0307         #- first line in the category
0308         my ($label) = $s =~ /^\s*\[(.*)\]/;
0309         my ($mntpoint) = $s =~ /^\s*path\s*=\s*(.*)/m;
0310         push @l, { mntpoint => $mntpoint, raw => $s, label => $label };
0311         $s = '';
0312     }
0313     $s .= $_;
0314     }
0315     bless \@l, 'smb_exports';
0316 }
0317 
0318 sub write {
0319     my ($smb_exports) = @_;
0320     foreach (@$smb_exports) {
0321     if (!exists $_->{raw}) {
0322         $_->{raw} = <<EOF;
0323 
0324 [$_->{label}]
0325    path = $_->{mntpoint}
0326    comment = $_->{mntpoint}
0327    public = yes
0328    guest ok = yes
0329    writable = no
0330    wide links = no
0331 EOF
0332     }
0333     }
0334     local *F;
0335     open F, ">$conf_file" or die "can't write $conf_file";
0336     print F $_->{raw} foreach @$smb_exports;
0337 }
0338 
0339 sub add {
0340     my ($exports, $mntpoint) = @_;
0341     my $e = $exports->exports::add($mntpoint);
0342     $e->{label} = name_mangle($mntpoint, map { $_->{label} } @$exports);
0343 }
0344 
0345 sub name_mangle {
0346     my ($input, @others) = @_;
0347 
0348     local $_ = $input;
0349 
0350     # 1. first only keep legal characters. "/" is also kept for the moment
0351     tr|a-z|A-Z|;
0352     s|[^A-Z0-9#\-_!/]|_|g; # "$" is allowed except at the end, remove it in any case
0353     
0354     # 2. removing non-interesting parts
0355     s|^/||;
0356     s|^home/||;
0357     s|_*/_*|/|g;
0358     s|_+|_|g;
0359 
0360     # 3. if size is too small (!), make it bigger
0361     $_ .= "_" while length($_) < 3;
0362 
0363     # 4. if size is too big, shorten it
0364     while (length > 12) {
0365     my ($s) = m|.*?/(.*)|;
0366     if (length($s) > 8 && !grep { /\Q$s/ } @others) {
0367         # dropping leading directories when the resulting is still long and meaningful
0368         $_ = $s;
0369         next;
0370     }
0371     s|(.*)[0-9#\-_!/]|$1| and next;
0372 
0373     # inspired by "Christian Brolin" "Long names are doom" on comp.lang.functional
0374     s|(.+)[AEIOU]|$1| and next; # allButFirstVowels
0375     s|(.*)(.)\2|$1$2| and next; # adjacentDuplicates
0376 
0377     s|(.*).|$1|; # booh, :'-(
0378     }
0379 
0380     # 5. remove "/"s still there
0381     s|/|_|g;
0382 
0383     # 6. resolving conflicts
0384     my $l = join("|", map { quotemeta } @others);
0385     my $conflicts = qr|^($l)$|;
0386     if (/$conflicts/) {
0387       A: while (1) {
0388         for (my $nb = 1; length("$_$nb") <= 12; $nb++) {
0389         if ("$_$nb" !~ /$conflicts/) {
0390             $_ = "$_$nb";
0391             last A;
0392         }
0393         }
0394         $_ or die "can't find a unique name";
0395         # can't find a unique name, dropping the last letter
0396         s|(.*).|$1|;
0397     }
0398     }
0399 
0400     # 7. done
0401     $_;
0402 }
0403 
0404 sub update_server {
0405   if (fork) {
0406     system('/usr/bin/killall -HUP smbd 2>/dev/null');
0407     if (system('PATH=/bin:/sbin pidof smbd >/dev/null') != 0 ||
0408     system('PATH=/bin:/sbin pidof nmbd >/dev/null') != 0) {
0409 # trying to start the server...
0410       if ( -f '/etc/init.d/smb' ) {
0411     system('/etc/init.d/smb', $_) foreach 'stop', 'start';
0412       }
0413       elsif ( -f '/etc/init.d/samba' ) {
0414     system('/etc/init.d/samba', $_) foreach 'stop', 'start';
0415       }
0416       elsif ( -f '/etc/rc.d/rc.samba' ) {
0417     system('/etc/rc.d/rc.samba', $_) foreach 'stop', 'start';
0418       }
0419       else {
0420     print STDERR "Error: Can't find the samba init script \n";
0421       }
0422     }
0423     exit 0;
0424   }
0425 }