Handle hostnames with upper-case letters
[webmin.git] / exports-nfs4 / exports-lib.pl
1 # export-lib.pl
2 # Common functions for the linux exports file
3
4 BEGIN { push(@INC, ".."); };
5 use WebminCore;
6 &init_config();
7 %access = &get_module_acl();
8 &foreign_require("mount", "mount-lib.pl");
9 our $nfsv4_root;
10
11 #------------------------------------------------------
12 # list_exports()
13 # Returns a list of all exports
14 #------------------------------------------------------
15 sub list_exports
16 {
17     my (@rv, $pos, $lnum, $h, $o, $line);
18     return @list_exports_cache if (@list_exports_cache);
19     open(EXP, $config{'exports_file'});
20     $lnum = 0;
21     while($line = <EXP>) {
22         my $slnum = $lnum;
23         $line =~ s/\s+$//g;
24         while($line =~ /\\$/) {
25                 # continuation character!
26                 $line =~ s/\\$//;
27                 $line .= <EXP>;
28                 $line =~ s/\s+$//g;
29                 $lnum++;
30         }
31         if ($line =~ /^(#*)\s*(\/\S*)\s+(.*)$/) {
32             my $active = !$1;
33             my $dir = $2;
34             my $rest = $3;
35             next if ($dir =~ /^$config{'exports_file'}/);
36             $pos = 0;
37             while($rest =~ /^([^\s+\(\)]*)\(([^\)]*)\)\s*(.*)$/ ||
38                 $rest =~ /^([^\s+\(\)]+)\s*()(.*)$/) 
39             {
40                 my %exp;
41                 $exp{'active'} = $active;
42                 $exp{'dir'} = $dir;
43                 $exp{'host'} = $1;
44                 my $ostr = $2;
45                 $rest = $3;
46                 # Support options with or without r-value
47                 while($ostr =~ /^([a-z_]+)=([0-9,\-]+)\s*,\s*(.*)$/ ||
48                     $ostr =~ /^([a-z_]+)=([0-9,\-]+)(.*)$/ ||
49                     $ostr =~ /^([a-z_]+)=([^,\s]+),(.*)$/ ||
50                     $ostr =~ /^([a-z_]+)=([^,\s]+)(.*)$/ ||
51                     $ostr =~ /^([a-z_]+)()\s*,\s*(.*)$/ ||
52                     $ostr =~ /^([a-z_]+)()(.*)$/) 
53                 {
54                     if ($2 ne "") { $exp{'options'}->{$1} = $2; }
55                     else { $exp{'options'}->{$1} = ""; }
56                     # For NFSv4 root (fsid=0)
57                     if($1 eq "fsid" && $2 eq "0"){
58                           $nfsv4_root=$dir;
59                     }
60                     $ostr = $3;
61                 }
62                 $exp{'line'} = $slnum;
63                 $exp{'eline'} = $lnum;
64                 $exp{'pos'} = $pos++;
65                 $exp{'index'} = scalar(@rv);
66                 push(@rv, \%exp);
67             }
68         }
69         $lnum++;
70     }
71     close(EXP);
72     @list_exports_cache = @rv;
73     return @list_exports_cache;
74 }
75
76 #------------------------------------------------------
77 # delete_export(&export)
78 # Delete an existing export
79 #------------------------------------------------------
80 sub delete_export
81 {
82 my @exps = &list_exports();
83 my @same = grep { $_ ne $_[0] && $_->{'line'} eq $_[0]->{'line'} } @exps;
84 my $lref = &read_file_lines($config{'exports_file'});
85 if (@same) {
86         # other exports on the same line.. cannot totally delete
87         splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'}-$_[0]->{'line'}+1,
88                &make_exports_line(@same));
89         map { $_->{'line'} = $_->{'eline'} = $_[0]->{'line'} } @same;
90         }
91 else {
92         # remove export line
93         splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'}-$_[0]->{'line'}+1);
94         # unmount the directory if it is mounted with --bind
95         my $dir = $_[0]->{'dir'};
96         my @mounted = &mount::list_mounted();
97         for(my $i=0; $i<@mounted; $i++) {
98             my $p = $mounted[$i];
99             if (($p->[0] eq $dir) and ($p->[2] eq "bind")) {
100                 &mount::unmount_dir($p->[1], $p->[0], $p->[2]);
101             }
102         }
103         # remove it from the fstab file
104         my @mounts = &mount::list_mounts();
105         for(my $i=0; $i<@mounts; $i++) {
106             my $p = $mounts[$i];
107             if (($p->[0] eq $dir) and ($p->[2] eq "bind")) {
108                 &mount::delete_mount($i);
109             }
110         }
111     }
112 @list_exports_cache = grep { $_ ne $_[0] } @list_exports_cache;
113 &flush_file_lines();
114 }
115
116 sub create_export_in_root
117 {
118     use File::Basename;
119     use File::Path;
120     my $export_pfs = 1;
121     my $pfs = $_[0]->{'pfs'};
122     $pfs  =~ s/\/$//;
123
124     # Mount the directory in the pfs
125     my $add_line = 1;
126     my $to_be_mounted = 1;
127     my $dir = $_[0]->{'dir'};
128     my $expt_dir = $dir;
129     $expt_dir =~ s/\/$//;
130     $expt_dir = $nfsv4_root."/".basename($expt_dir);
131     
132     # Add it in the fstab file if it is not already in
133     my @mounts = &mount::list_mounts();
134     for(my $i=0; $i<@mounts; $i++) {
135         my $p = $mounts[$i];
136         if (($p->[0] eq $expt_dir) and ($p->[1] eq $dir) and ($p->[2] eq "bind")) {
137             $add_line = 0;
138         }
139     }
140     if ($add_line) {
141         &mount::create_mount($expt_dir, $dir, "bind", "");
142     }
143
144     # Mount it if it is not already mounted
145     my @mounted = &mount::list_mounted();
146     for(my $i=0; $i<@mounted; $i++) {
147         my $p = $mounted[$i];
148         if (($p->[0] eq $expt_dir) and ($p->[1] eq $dir) and ($p->[2] eq "bind")) {
149             $to_be_mounted = 0;
150         }
151     }
152     if ($to_be_mounted) {
153         eval { mkpath($expt_dir) };
154         if ($@) {
155             &error($text{'save_create_dir'}, $expt_dir );
156         }
157         my $err = &mount::mount_dir($expt_dir, $dir, "bind", "");
158         &error($err) if ($err);
159         &webmin_log("mount", undef, undef, { 'dev' => $expt_dir,
160                                              'type' => "bind",
161                                              'dir' => $dir });
162     }
163     
164     # Export the directory $expt_dir
165     $_[0]->{'dir'} = $expt_dir;
166     create_export($_[0]);
167 }
168
169 #------------------------------------------------------
170 # create_export(&export)
171 #------------------------------------------------------
172 sub create_export
173 {
174 &open_tempfile(EXP, ">>$config{'exports_file'}");
175 &print_tempfile(EXP, &make_exports_line($_[0]),"\n");
176 &close_tempfile(EXP);
177 }
178
179 #------------------------------------------------------
180 # modify_export(&export, &old)
181 #------------------------------------------------------
182 sub modify_export
183 {
184 my @exps = &list_exports();
185 my @same = grep { $_->{'line'} eq $_[1]->{'line'} } @exps;
186 my $lref = &read_file_lines($config{'exports_file'});
187 if ($_[0]->{'dir'} eq $_[1]->{'dir'} &&
188     $_[0]->{'active'} == $_[1]->{'active'} || @same == 1) {
189         # directory or active not changed, or on a line of it's own
190         splice(@same, &indexof($_[1],@same), 1, $_[0]);
191         splice(@$lref, $_[1]->{'line'}, $_[1]->{'eline'}-$_[1]->{'line'}+1,
192                &make_exports_line(@same));
193         }
194 else {
195         # move to a line of it's own
196         splice(@same, &indexof($_[1],@same), 1);
197         splice(@$lref, $_[1]->{'line'}, $_[1]->{'eline'}-$_[1]->{'line'}+1,
198                &make_exports_line(@same));
199         push(@$lref, &make_exports_line($_[0]));
200         }
201 &flush_file_lines();
202 }
203
204 # make_exports_line([&export]+)
205 sub make_exports_line
206 {
207 my ($e, @htxt);
208 foreach $e (@_) {
209         my %opts = %{$e->{'options'}};
210         if (%opts || !$e->{'host'}) {
211                 push(@htxt, $e->{'host'}."(".
212                             join(",", map { $opts{$_} eq "" ? $_
213                                                             : "$_=$opts{$_}" }
214                             (keys %opts)).")");
215                 }
216         else { push(@htxt, $e->{'host'}); }
217         }
218 return ($_[0]->{'active'} ? "" : "#").$_[0]->{'dir'}."\t".join(" ", @htxt);
219 }
220
221 #------------------------------------------------------
222 # file_chooser_button2(input, type, name, disabled)
223 # A file_chooser_button which can be disabled
224 #------------------------------------------------------
225 sub file_chooser_button2
226 {
227     my $disabled = ($_[3] == 1) ? "disabled" : "";
228     return "<input type=button name=$_[2] onClick='ifield = document.forms[0].$_[0]; chooser = window.open(\"$gconfig{'webprefix'}/chooser.cgi?type=$_[1]&chroot=/&file=\"+escape(ifield.value), \"chooser\", \"toolbar=no,menubar=no,scrollbar=no,width=400,height=300\"); chooser.ifield = ifield; window.ifield = ifield' value=\"...\" $disabled>\n";
229 }
230
231 #------------------------------------------------------
232 # nfs_max_version(host)
233 # Return the max NFS version allowed on a server
234 #------------------------------------------------------
235 sub nfs_max_version
236 {
237     local($_, $max, $out);
238     $max = 0;
239     $out = `rpcinfo -p $_[0] 2>&1`;
240     if ($?) { return 3; }
241     foreach (split(/\n/, $out)) {
242         if ((/ +(\d) +.*nfs/) && ($1 > $max)) {
243             $max = $1; }
244     }
245     return $max;
246 }
247
248 #------------------------------------------------------
249 # describe_host(host)
250 # Given a host, regexp or netgroup return a human-readable version
251 #------------------------------------------------------
252 sub describe_host
253 {
254     my $h = &html_escape($_[0]);
255     if ($h eq "=public") { return $text{'exports_webnfs'}; }
256     elsif ($h =~ /^(gss\/|krb5|spkm-3|lipkey)/) {
257         my $string="";
258         while ($h =~/^(((gss\/)?(krb5|spkm-3|lipkey))|sys)([ip]?)(([,:](\S+))|$)/ || $h =~/^gss\/(krb5|spkm-3|lipkey)([ip]?)$/) {
259             $auth = $1; # sys, krb5, spkm-3 or lipkey
260             $sec=$5; # authentication(""), integrity("i"), privacy("p")
261             
262             my $suffix="";
263             if($sec eq "i"){$suffix=" (i)" }
264             elsif($sec eq "p"){$suffix=" (p)" }
265             if($auth eq "sys"){
266                 $string.="sys, ";
267             }
268             elsif($auth eq "gss/krb5" || $auth eq "krb5"){
269                $string.="krb5".$suffix.", ";
270             }
271             elsif($auth eq "gss/spkm-3" || $auth eq "spkm-3"){
272                $string.="spkm-3".$suffix.", ";
273             }
274             elsif($auth eq "gss/lipkey" || $auth eq "lipkey"){
275                $string.="lipkey".$suffix.", ";
276             }
277             $h=$8;
278         }
279         chop($string);chop($string);
280         $h="everyone" if($h eq "");
281         return &text('exports_gss', "$string - <i>$h</i>"); 
282     }
283     elsif ($h =~ /^\@(.*)/) { return &text('exports_ngroup', "<i>$1</i>"); }
284     elsif ($h =~ /^(\S+)\/(\S+)$/) {
285             return &text('exports_net', "<i>$1/$2</i>"); }
286     elsif ($h eq "" || $h eq "*") { return $text{'exports_all'}; }
287     elsif ($h =~ /\*/) { return &text('exports_hosts', "<i>$h</i>"); }
288     else { return &text('exports_host', "<i>$h</i>"); }
289 }
290
291 sub has_nfs_commands
292 {
293 return !&has_command("rpc.nfsd") && !&has_command("nfsd") &&
294        !&has_command("rpc.knfsd") ? 0 : 1;
295 }
296
297 #------------------------------------------------------
298 # restart_mountd()
299 # Apply the /etc/exports configuration
300 #------------------------------------------------------
301 sub restart_mountd
302 {
303 # Try exportfs -r first
304     if ($config{'apply_cmd'} && &find_byname("nfsd") && &find_byname("mountd")) {
305         my $out = &backquote_logged("$config{'apply_cmd'} 2>&1 </dev/null");
306         if (!$? && $out !~ /invalid|error|failed/i) {
307                 # Looks like it worked!
308                 return undef;
309         }
310     }
311
312     &system_logged("$config{'portmap_command'} >/dev/null 2>&1 </dev/null")
313     if ($config{'portmap_command'}); 
314     my $temp = &transname();
315     my $rv = &system_logged("($config{'restart_command'}) </dev/null >$temp 2>&1");
316     my $out = `cat $temp`;
317     unlink($temp);
318     if ($rv) {
319         # something went wrong.. return an error
320         return "<pre>$out</pre>";
321     }
322     return undef;
323   }
324
325 1;
326