Handle hostnames with upper-case letters
[webmin.git] / samba / samba-lib.pl
1 # samba-lib.pl
2 # Common functions for editing the samba config file
3 # XXX privileges for groups with 'net' command
4
5 BEGIN { push(@INC, ".."); };
6 use WebminCore;
7 &init_config();
8 %access = &get_module_acl();
9 $has_iconv = &has_command("iconv");
10
11 # Get the samba version
12 if (open(VERSION, "$module_config_directory/version")) {
13         chop($samba_version = <VERSION>);
14         close(VERSION);
15         }
16
17 $has_pdbedit = ( $samba_version >= 3 && &has_command($config{'pdbedit'}) );
18 $has_smbgroupedit = 1 if (&has_command($config{'smbgroupedit'}));
19 $has_net = 1 if ($config{'net'} =~ /^(\S+)/ && &has_command("$1"));
20 $has_groups = ( $samba_version >= 3 && ($has_smbgroupedit || $has_net) );
21
22 # list_shares()
23 # List all the shares from the samba config file
24 sub list_shares
25 {
26 local(@rv, $_);
27 &open_readfile(SAMBA, $config{smb_conf});
28 while(<SAMBA>) {
29         chop; s/;.*$//g; s/^\s*#.*$//g;
30         if (/^\s*\[([^\]]+)\]/) {
31                 push(@rv, $1);
32                 }
33         }
34 close(SAMBA);
35
36 # Check for an include directive in the [global] share
37 local %global;
38 &get_share("global", \%global);
39 local $inc = &getval("include", \%global);
40 if ($inc && $inc !~ /\%/) {
41         # XXX
42         }
43
44 return @rv;
45 }
46
47
48 # get_share(share, [array])
49 # Fills the associative array %share with the parameters from the given share
50 sub get_share
51 {
52 local($found, $_, $first, $arr);
53 $arr = (@_==2 ? $_[1] : "share");
54 undef(%$arr);
55 &open_readfile(SAMBA, $config{smb_conf});
56 while(<SAMBA>) {
57         chop; s/^\s*;.*$//g; s/^\s*#.*$//g;
58         if (/^\s*\[([^\]]+)\]/) {
59                 # Start of share section
60                 $first = 1;
61                 if ($found) {
62                         last;
63                         }
64                 elsif ($1 eq $_[0]) {
65                         $found = 1;
66                         $$arr{share_name} = $1;
67                         }
68                 }
69         elsif ($found && /^\s*([^=]*\S)\s*=\s*(.*)$/) {
70                 # Directives inside a section
71                 if (lc($1) eq "read only") {
72                         # bastard special case.. change to writable
73                         $$arr{'writable'} = $2 =~ /yes|true|1/i ? "no" : "yes";
74                         }
75                 else {
76                         $$arr{lc($1)} = &from_utf8("$2");
77                         }
78                 }
79         elsif (!$first && /^\s*([^=]*\S)\s*=\s*(.*)$/ && $_[0] eq "global") {
80                 # Directives outside a section! Assume to be part of [global]
81                 $$arr{share_name} = "global";
82                 $$arr{lc($1)} = &from_utf8("$2");
83                 $found = 1;
84                 }
85         }
86 close(SAMBA);
87 return $found;
88 }
89
90
91 # create_share(name)
92 # Add an entry to the config file
93 sub create_share
94 {
95 &open_tempfile(CONF, ">> $config{smb_conf}");
96 &print_tempfile(CONF, "\n");
97 &print_tempfile(CONF, "[$_[0]]\n");
98 foreach $k (grep {!/share_name/} (keys %share)) {
99         &print_tempfile(CONF, "\t$k = $share{$k}\n");
100         }
101 &close_tempfile(CONF);
102 }
103
104
105 # modify_share(oldname, newname)
106 # Change a share (and maybe it's name)
107 sub modify_share
108 {
109 local($_, @conf, $replacing, $first);
110 &open_readfile(CONF, $config{smb_conf});
111 @conf = <CONF>;
112 close(CONF);
113 &open_tempfile(CONF, ">$config{smb_conf}");
114 for($i=0; $i<@conf; $i++) {
115         chop($_ = $conf[$i]); s/;.*$//g; s/#.*$//g;
116         if (/^\s*\[([^\]]+)\]/) {
117                 $first = 1;
118                 if ($replacing) { $replacing = 0; }
119                 elsif ($1 eq $_[0]) {
120                         &print_tempfile(CONF, "[$_[1]]\n");
121                         foreach $k (grep {!/share_name/} (keys %share)) {
122                                 &print_tempfile(CONF, "\t$k = ",
123                                         &to_utf8($share{$k}),"\n");
124                                 }
125                         #&print_tempfile(CONF, "\n");
126                         $replacing = 1;
127                         }
128                 }
129         elsif (!$first && /^\s*([^=]*\S)\s*=\s*(.*)$/ && $_[0] eq "global") {
130                 # found start of directives outside any share - assume [global]
131                 $first = 1;
132                 &print_tempfile(CONF, "[$_[1]]\n");
133                 foreach $k (grep {!/share_name/} (keys %share)) {
134                         &print_tempfile(CONF, "\t$k = ",
135                                               &to_utf8($share{$k}),"\n");
136                         }
137                 &print_tempfile(CONF, "\n");
138                 $replacing = 1;
139                 }
140         if (!$replacing || $conf[$i] =~ /^\s*[#;]/ || $conf[$i] =~ /^\s*$/) {
141                 &print_tempfile(CONF, $conf[$i]);
142                 }
143         }
144 &close_tempfile(CONF);
145 }
146
147
148 # delete_share(share)
149 # Delete some share from the config file
150 sub delete_share
151 {
152 local($_, @conf, $deleting);
153 &open_readfile(CONF, $config{smb_conf});
154 @conf = <CONF>;
155 close(CONF);
156 &open_tempfile(CONF, "> $config{smb_conf}");
157 for($i=0; $i<@conf; $i++) {
158         chop($_ = $conf[$i]); s/;.*$//g;
159         if (/^\s*\[([^\]]+)\]/) {
160                 if ($deleting) { $deleting = 0; }
161                 elsif ($1 eq $_[0]) {
162                         &print_tempfile(CONF, "\n");
163                         $deleting = 1;
164                         }
165                 }
166         if (!$deleting) {
167                 &print_tempfile(CONF, $conf[$i]);
168                 }
169         }
170 &close_tempfile(CONF);
171 }
172
173
174 # to_utf8(string)
175 # Converts a string to UTF-8 if needed
176 sub to_utf8
177 {
178 local ($v) = @_;
179 if ($v =~ /^[\000-\177]*$/ || !$has_iconv) {
180         return $v;
181         }
182 else {
183         my $temp = &transname();
184         &open_tempfile(TEMP, ">$temp", 0, 1);
185         &print_tempfile(TEMP, $v);
186         &close_tempfile(TEMP);
187         my $out = &backquote_command("iconv -f iso-8859-1 -t UTF-8 <$temp");
188         &unlink_file($temp);
189         return $? || $out eq '' ? $v : $out;
190         }
191 }
192
193 # from_utf8(string)
194 # Converts a string from UTF-8 if needed
195 sub from_utf8
196 {
197 local ($v) = @_;
198 if ($v =~ /^[\000-\177]*$/ || !$has_iconv) {
199         return $v;
200         }
201 else {
202         my $temp = &transname();
203         &open_tempfile(TEMP, ">$temp", 0, 1);
204         &print_tempfile(TEMP, $v);
205         &close_tempfile(TEMP);
206         my $out = &backquote_command("iconv -f UTF-8 -t iso-8859-1 <$temp");
207         &unlink_file($temp);
208         return $? || $out eq '' ? $v : $out;
209         }
210 }
211
212
213 # list_connections([share])
214 # Uses the smbstatus program to return a list of connections a share. Each
215 # element of the returned list is of the form:
216 #  share, user, group, pid, hostname, date/time
217 sub list_connections
218 {
219 local($l, $started, @rv);
220 if ($samba_version >= 3) {
221         # New samba status format
222         local %pidmap;
223         local $out;
224         local $ex = &execute_command("$config{samba_status_program} -s $config{smb_conf}", undef, \$out, undef);
225         if ($ex) {
226                 # -s option not supported
227                 &execute_command("$config{samba_status_program}",
228                                  undef, \$out, undef);
229                 }
230         foreach $l (split(/\n/ , $out)) {
231                 if ($l =~ /^----/) {
232                         $started++;
233                         }
234                 elsif ($started == 1 &&
235                        $l =~ /^\s*(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+\((\S+)\)/) {
236                         # A line with a PID, username, group and hostname
237                         $pidmap{$1} = [ $1, $2, $3, $4, $5 ];
238                         }
239                 elsif ($started == 2 &&
240                        $l =~ /^\s*(\S+)\s+(\d+)\s+(\S+)\s+(.*)$/) {
241                         # A line with a service, PID and machine. This must be
242                         # combined data from the first type of line to create
243                         # the needed information
244                         local $p = $pidmap{$2};
245                         if (!$_[0] || $1 eq $_[0] ||
246                             $1 eq $p->[1] && $_[0] eq "homes") {
247                                 push(@rv, [ $1, $p->[1], $p->[2], $2, $3, $4 ]);
248                                 }
249                         }
250                 }
251         }
252 else {
253         # Old samba status format
254         local $out;
255         &execute_command("$config{samba_status_program} -S",
256                          undef, \$out, undef);
257         foreach $l (split(/\n/, $out)) {
258                 if ($l =~ /^----/) { $started++; }
259                 if ($started && $l =~ /^(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)\s+\(\S+\)\s+(.*)$/ && (!$_[0] || $1 eq $_[0] || $1 eq $2 && $_[0] eq "homes")) {
260                         push(@rv, [ $1, $2, $3, $4, $5, $6 ]);
261                         }
262                 }
263         }
264 return @rv;
265 }
266
267 # list_locks()
268 # Returns a list of locked files as an array, in the form:
269 #  pid, mode, rw, oplock, file, date
270 sub list_locks
271 {
272 local($l, $started, @rv);
273 local $out;
274 &clean_language();
275 &execute_command("$config{samba_status_program} -L", undef, \$out, undef);
276 &reset_environment();
277 foreach $l (split(/\n/, $out)) {
278         if ($l =~ /^----/) { $started = 1; }
279         if ($started && $l =~ /^(\d+)\s+(\d+)\s+(\S+)\s+(0x\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S.*)\s\s((Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s.*)/i) {
280                 # New-style line
281                 push(@rv, [ $1, $3, $5, $6, $7.'/'.$8, $9 ]);
282                 }
283         elsif ($started && $l =~ /^(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(.*)\s+(\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d+)/) {
284                 # Old-style line
285                 push(@rv, [ $1, $2, $3, $4, $5, $6 ]);
286                 }
287         }
288 return @rv;
289 }
290
291
292 # istrue(key)
293 # Checks if the value of this key (or it's synonyms) in %share is true
294 sub istrue
295 {
296 return &getval($_[0]) =~ /yes|true|1/i;
297 }
298
299
300 # isfalse(key)
301 # Checks if the value of this key (or it's synonyms) in %share is false
302 sub isfalse
303 {
304 return &getval($_[0]) =~ /no|false|0/i;
305 }
306
307
308 # getval(name, [&hash])
309 # Given the name of a key in %share, return the value. Also looks for synonyms.
310 # If the value is not found, a default is looked for.. this can come from
311 # a copied section, the [global] configuration section, or from the SAMBA
312 # defaults. This means that getval() always returns something..
313 sub getval
314 {
315 local $hash = $_[1] || \%share;
316 local($_, $copy);
317 if ($synon{$_[0]}) {
318         foreach (split(/,/, $synon{$_[0]})) {
319                 if (defined($hash->{$_})) { return $hash->{$_}; }
320                 }
321         }
322 if (defined($hash->{$_[0]})) {
323         return $hash->{$_[0]};
324         }
325 elsif ($_[0] ne "copy" && ($copy = $hash->{"copy"})) {
326         # this share is a copy.. get the value from the source
327         local(%share);
328         &get_share($copy);
329         return &getval($_[0]);
330         }
331 else {
332         # return the default value...
333         return &default_value($_[0]);
334         }
335 return undef;
336 }
337
338
339 # setval(name, value, [default])
340 # Sets some value in %share. Synonyms with the same meaning are removed.
341 # If the value is the same as the share or given default, dont store it
342 sub setval
343 {
344 local($_);
345 if (@_ == 3) {
346         # default was given..
347         $def = $_[2];
348         }
349 elsif ($_[0] ne "copy" && ($copy = $share{"copy"})) {
350         # get value from copy source..
351         local(%share);
352         &get_share($copy);
353         $def = &getval($_[0]);
354         }
355 else {
356         # get global/samba default
357         $def = &default_value($_[0]);
358         }
359 if ($_[1] eq $def || ($def !~ /\S/ && $_[1] !~ /\S/) ||
360     ($def =~ /^(true|yes|1)$/i && $_[1] =~ /^(true|yes|1)$/i) ||
361     ($def =~ /^(false|no|0)$/i && $_[1] =~ /^(false|no|0)$/i)) {
362         # The value is the default.. delete this entry
363         &delval($_[0]);
364         }
365 else {
366         if ($synon{$_[0]}) {
367                 foreach (split(/,/, $synon{$_[0]})) {
368                         delete($share{$_});
369                         }
370                 }
371         $share{$_[0]} = $_[1];
372         }
373 }
374
375
376 # delval(name)
377 # Delete a value from %share (and it's synonyms)
378 sub delval
379 {
380 local($_);
381 if ($synon{$_[0]}) {
382         foreach (split(/,/, $synon{$_[0]})) {
383                 delete($share{$_});
384                 }
385         }
386 else { delete($share{$_[0]}); }
387 }
388
389
390 # default_value(name)
391 # Returns the default value for a parameter
392 sub default_value
393 {
394 local($_, %global);
395
396 # First look in the [global] section.. (unless this _is_ the global section)
397 if ($share{share_name} ne "global") {
398         &get_share("global", "global");
399         if ($synon{$_[0]}) {
400                 foreach (split(/,/, $synon{$_[0]})) {
401                         if (defined($global{$_})) { return $global{$_}; }
402                         }
403                 }
404         if (defined($global{$_[0]})) { return $global{$_[0]}; }
405         }
406
407 # Else look in the samba defaults
408 if ($synon{$_[0]}) {
409         foreach (split(/,/, $synon{$_[0]})) {
410                 if (exists($default_values{$_})) {
411                         return $default_values{$_};
412                         }
413                 }
414         }
415 return $default_values{$_[0]};
416 }
417
418
419 # The list of synonyms used by samba for parameter names
420 @synon = (      "writeable,write ok,writable",
421                 "public,guest ok",
422                 "printable,print ok",
423                 "allow hosts,hosts allow",
424                 "deny hosts,hosts deny",
425                 "create mode,create mask",
426                 "directory mode,directory mask",
427                 "path,directory",
428                 "exec,preexec",
429                 "group,force group",
430                 "only guest,guest only",
431                 "user,username,users",
432                 "default,default service",
433                 "auto services,preload",
434                 "lock directory,lock dir",
435                 "max xmit,max packet",
436                 "root directory,root dir,root",
437                 "case sensitive,case sig names",
438                 "idmap uid,winbind uid",
439                 "idmap gid,winbind gid",
440          );
441 foreach $s (@synon) {
442         foreach $ss (split(/,/ , $s)) {
443                 $synon{$ss} = $s;
444                 }
445         }
446
447
448 # Default values for samba configuration parameters
449 %default_values = (     "allow hosts",undef,
450                         "alternate permissions","no",
451                         "available","yes",
452                         "browseable","yes",
453                         "comment",undef,
454                         "create","yes",
455                         "create mode","755",
456                         "directory mode","755",
457                         "default case","lower",
458                         "case sensitive","no",
459                         "mangle case","no",
460                         "preserve case","yes",
461                         "short preserve case","yes",
462                         "delete readonly","no",
463                         "deny hosts",undef,
464                         "dont descend",undef,
465                         "force group",undef,
466                         "force user",undef,
467                         "force create mode","000",
468                         "force directory mode","000",
469                         "guest account","nobody",       # depends
470                         "guest only","no",
471                         "hide dot files","yes",
472                         "invalid users",undef,
473                         "locking","yes",
474                         "lppause command",undef,        # depends
475                         "lpq command",undef,            # depends
476                         "lpresume command",undef,       # depends
477                         "lprm command",undef,           #depends
478                         "magic output",undef,           # odd..
479                         "magic script",undef,
480                         "mangled map",undef,
481                         "mangled names","yes",
482                         "mangling char","~",
483                         "map archive","yes",
484                         "map system","no",
485                         "map hidden","no",
486                         "max connections",0,
487                         "only user","no",
488                         "fake oplocks","no",
489                         "oplocks","yes",
490                         "os level",20,
491                         "level2 oplocks","no",
492                         "load printers","yes",
493                         "min print space",0,
494                         "path",undef,
495                         "postscript","no",
496                         "preexec",undef,
497                         "print command",undef,
498 #                       "print command","lpr -r -P %p %s",
499                         "printer",undef,
500                         "printer driver",undef,
501                         "public","no",
502                         "read list",undef,
503                         "revalidate","no",
504                         "root preexec",undef,
505                         "root postexec",undef,
506                         "set directory","no",
507                         "share modes","yes",
508                         "socket options","TCP_NODELAY",
509                         "strict locking","no",
510                         "sync always","no",
511                         "unix password sync","no",
512                         "user",undef,
513                         "valid chars",undef,
514                         "volume",undef,         # depends
515                         "wide links","yes",
516                         "wins support","no",
517                         "writable","no",
518                         "write list",undef,
519                         "winbind cache time",300,
520                         "winbind enable local accounts","yes",
521                         "winbind trusted domains only","no",
522                         "winbind enum users","yes",
523                         "winbind enum groups","yes",
524                 );
525 $default_values{'encrypt passwords'} = 'yes' if ($samba_version >= 3);
526
527 # user_list(list)
528 # Convert a samba unix user list into a more readable form
529 sub user_list
530 {
531 local($u, @rv);
532 foreach $u (split(/[ \t,]+/ , $_[0])) {
533         if ($u =~ /^\@(.*)$/) {
534                 push(@rv, "group <tt>".&html_escape($1)."</tt>");
535                 }
536         else {
537                 push(@rv, "<tt>".&html_escape($u)."</tt>");
538                 }
539         }
540 return join("," , @rv);
541 }
542
543
544 sub yesno_input
545 {
546 ($n = $_[0]) =~ s/ /_/g;
547 return sprintf "<input type=radio name=$n value=yes %s> $text{'yes'}\n".
548                "<input type=radio name=$n value=no %s> $text{'no'}\n",
549                 &istrue($_[0]) ? "checked" : "",
550                 &isfalse($_[0]) ? "checked" : "";
551 }
552
553 # username_input(name)
554 # Outputs HTML for an username field
555 sub username_input
556 {
557 ($n = $_[0]) =~ s/ /_/g;
558 $v = &getval($_[0]);
559 print "<td><input name=$n size=8 value=\"$v\"> ",
560         &user_chooser_button($n, 0),"</td>\n";
561 }
562
563 # username_input(name, default)
564 sub groupname_input
565 {
566 ($n = $_[0]) =~ s/ /_/g;
567 $v = &getval($_[0]);
568 print "<td><input name=$n size=8 value=\"$v\"> ",
569         &group_chooser_button($n, 0),"</td>\n";
570 }
571
572
573
574 @sock_opts = ("SO_KEEPALIVE", "SO_REUSEADDR", "SO_BROADCAST", "TCP_NODELAY", 
575               "IPTOS_LOWDELAY", "IPTOS_THROUGHPUT", "SO_SNDBUF*", "SO_RCVBUF*",
576               "SO_SNDLOWAT*", "SO_RCVLOWAT*");
577
578 @protocols = ("CORE", "COREPLUS", "LANMAN1", "LANMAN2", "NT1");
579
580
581 # list_users()
582 # Returns an array of all the users from the samba password file
583 sub list_users
584 {
585 local(@rv, @b, $_, $lnum);
586 if ($has_pdbedit) {
587         # Get list of users from the pdbedit command, which uses a configurable
588         # back-end for storage
589         &open_execute_command(PASS, "cd / && $config{'pdbedit'} -L -w -s $config{'smb_conf'}", 1);
590         }
591 else {
592         # Read the password file directly
593         &open_readfile(PASS, $config{'smb_passwd'});
594         }
595 while(<PASS>) {
596         $lnum++;
597         chop;
598         s/#.*$//g;
599         local @b = split(/:/, $_);
600         next if (@b < 4);
601         local $u = { 'name' => $b[0],  'uid' => $b[1],
602                      'pass1' => $b[2], 'pass2' => $b[3],
603                      'oldname' => $b[0] };
604         if ($samba_version >= 2 && $b[4] =~ /^\[/) {
605                 $b[4] =~ s/[\[\] ]//g;
606                 $u->{'opts'} = [ split(//, $b[4]) ];
607                 $u->{'change'} = $b[5];
608                 }
609         else {
610                 $u->{'real'} = $b[4];
611                 $u->{'home'} = $b[5];
612                 $u->{'shell'} = $b[6];
613                 }
614         $u->{'index'} = scalar(@rv);
615         $u->{'line'} = $lnum-1;
616         push(@rv, $u);
617         }
618 close(PASS);
619 return @rv;
620 }
621
622 # smbpasswd_cmd(args)
623 # Returns the full smbpasswd command with extra args
624 sub smbpasswd_cmd
625 {
626 my ($args) = @_;
627 return $config{'samba_password_program'}.
628        ($samba_version >= 3 ? " -c " : " -s ").
629        $config{'smb_conf'}." ".$args;
630 }
631
632 # create_user(&user)
633 # Add a user to the samba password file
634 sub create_user
635 {
636 if ($has_pdbedit) {
637         # Use the pdbedit command
638         local $ws = &indexof("W", @{$_[0]->{'opts'}}) >= 0 ? "-m" : "";
639         local @opts = grep { $_ ne "U" && $_ ne "W" } @{$_[0]->{'opts'}};
640         local $out = &backquote_logged(
641                 "cd / && $config{'pdbedit'} -a -s $config{'smb_conf'} -u ".
642                 quotemeta($_[0]->{'name'}).
643                 ($config{'sync_gid'} ? " -G $config{'sync_gid'}" : "").
644                 " -c '[".join("", @opts)."]' $ws");
645         $? && &error("$config{'pdbedit'} failed : <pre>$out</pre>");
646         }
647 else {
648         # Try using smbpasswd -a
649         local $out = &backquote_logged(
650                 "cd / && ".&smbpasswd_cmd(
651                   "-a ".
652                   (&indexof("D", @{$_[0]->{'opts'}}) >= 0 ? "-d " : "").
653                   (&indexof("N", @{$_[0]->{'opts'}}) >= 0 ? "-n " : "").
654                   (&indexof("W", @{$_[0]->{'opts'}}) >= 0 ? "-m " : "").
655                   quotemeta($_[0]->{'name'})));
656         if ($?) {
657                 # Add direct to Samba password file
658                 &open_tempfile(PASS, ">>$config{'smb_passwd'}");
659                 &print_tempfile(PASS, &user_string($_[0]));
660                 &close_tempfile(PASS);
661                 chown(0, 0, $config{'smb_passwd'});
662                 chmod(0600, $config{'smb_passwd'});
663                 }
664         }
665 }
666
667 # modify_user(&user)
668 # Change an existing samba user
669 sub modify_user
670 {
671 if ($has_pdbedit) {
672         # Use the pdbedit command
673         if ($_[0]->{'oldname'} ne "" && $_[0]->{'oldname'} ne $_[0]->{'name'}) {
674                 # Username changed! Have to delete and re-create
675                 local $out = &backquote_logged(
676                     "cd / && $config{'pdbedit'} -x -s $config{'smb_conf'} -u ".
677                     quotemeta($_[0]->{'oldname'}));
678                 $? && &error("$config{'pdbedit'} failed : <pre>$out</pre>");
679                 &create_user($_[0]);
680                 }
681         else {
682                 # Just update user
683                 local @opts = grep { $_ ne "U" } @{$_[0]->{'opts'}};
684                 &indexof("W", @{$_[0]->{'opts'}}) >= 0 && &error($text{'saveuser_ews'});
685                 $out = &backquote_logged(
686                         "cd / && $config{'pdbedit'} -r -s $config{'smb_conf'} -u ".
687                         quotemeta($_[0]->{'name'}).
688                         " -c '[".join("", @opts)."]' 2>&1");
689                 $? && &error("$config{'pdbedit'} failed : <pre>$out</pre>");
690                 }
691         }
692 else {
693         if (!$_[0]->{'oldname'} || $_[0]->{'oldname'} eq $_[0]->{'name'}) {
694                 # Try using smbpasswd command
695                 local $out = &backquote_logged(
696                         "cd / && ".&smbpasswd_cmd(
697                           (&indexof("D", @{$_[0]->{'opts'}}) >= 0 ? "-d "
698                                                                   : "-e ").
699                           quotemeta($_[0]->{'name'})));
700                 }
701
702         # Also directly update the Samba password file
703         &replace_file_line($config{'smb_passwd'}, $_[0]->{'line'},
704                            &user_string($_[0]));
705         }
706 }
707
708 # delete_user(&user)
709 # Delete a samba user
710 sub delete_user
711 {
712 if ($has_pdbedit) {
713         # Use the pdbedit command
714         local $out = &backquote_logged(
715                 "cd / && $config{'pdbedit'} -x -s $config{'smb_conf'} -u ".
716                 quotemeta($_[0]->{'name'}));
717         $? && &error("$config{'pdbedit'} failed : <pre>$out</pre>");
718         }
719 else {
720         # Try the smbpasswd command
721         local $out = &backquote_logged(
722                 "cd / && ".&smbpasswd_cmd("-x ".quotemeta($_[0]->{'name'})));
723         if ($?) {
724                 # Just remove from the Samba password file
725                 &replace_file_line($config{'smb_passwd'}, $_[0]->{'line'});
726                 }
727         }
728 }
729
730 sub user_string
731 {
732 local @u = ($_[0]->{'name'}, $_[0]->{'uid'},
733             $_[0]->{'pass1'}, $_[0]->{'pass2'});
734 if ($_[0]->{'opts'}) {
735         push(@u, sprintf "[%-11s]", join("", @{$_[0]->{'opts'}}));
736         push(@u, sprintf "LCT-%X", time());
737         push(@u, "");
738         }
739 else {
740         push(@u, $_[0]->{'real'}, $_[0]->{'home'}, $_[0]->{'shell'});
741         }
742 return join(":", @u)."\n";
743 }
744
745 # set_password(user, password, [&output])
746 # Changes the password of a user in the encrypted password file
747 sub set_password
748 {
749 local $qu = quotemeta($_[0]);
750 local $qp = quotemeta($_[1]);
751 if ($samba_version >= 2) {
752         local $passin = "$_[1]\n$_[1]\n";
753         local $ex = &execute_command(
754                 &smbpasswd_cmd("-s $qu"), \$passin, $_[2], $_[2]);
755         unlink($temp);
756         return !$rv;
757         }
758 else {
759         local $out;
760         &execute_command("$config{'samba_password_program'} $qu $qp",
761                          undef, $_[2], $_[2]);
762         return $out =~ /changed/i;
763         }
764 }
765
766 # is_samba_running()
767 # Returns 0 if not, 1 if it is, or 2 if run from (x)inetd
768 sub is_samba_running
769 {
770 local ($found_inet, @smbpids, @nmbpids);
771 if (&foreign_check("inetd")) {
772         &foreign_require("inetd", "inetd-lib.pl");
773         foreach $inet (&foreign_call("inetd", "list_inets")) {
774                 $found_inet++ if (($inet->[8] =~ /smbd/ ||
775                                    $inet->[9] =~ /smbd/) && $inet->[1]);
776                 }
777         }
778 elsif (&foreign_check("xinetd")) {
779         &foreign_require("xinetd", "xinetd-lib.pl");
780         foreach $xi (&foreign_call("xinetd", "get_xinetd_config")) {
781                 local $q = $xi->{'quick'};
782                 $found_inet++ if ($q->{'disable'}->[0] ne 'yes' &&
783                                   $q->{'server'}->[0] =~ /smbd/);
784                 }
785         }
786 @smbpids = &find_byname("smbd");
787 @nmbpids = &find_byname("nmbd");
788 return !$found_inet && !@smbpids && !@nmbpids ? 0 :
789        !$found_inet ? 1 : 2;
790 }
791
792 # is_winbind_running()
793 # Returns 0 if not, 1 if it is
794 sub is_winbind_running
795 {
796 local (@wbpids);
797 @wbpids = &find_byname("winbindd");
798 return !@wbpids ? 0 : 1;
799 }
800
801 # can($permissions_string, \%access, [$sname])
802 # check global and per-share permissions:
803 #
804 # $permissions_string = any exists permissions except 'c' (creation).
805 # \%access = ref on what get_module_acl() returns.
806 sub can
807 {
808 local ($acl, $stype, @perm);
809 local ($perm, $acc, $sname) = @_;
810 @perm  = split(//, $perm);
811 $sname = $in{'old_name'} || $in{'share'} unless $sname;
812
813 {       local %share;
814         &get_share($sname); # use local %share
815         $stype = &istrue('printable') ? 'ps' : 'fs';
816         }
817
818 # check global acl (r,w)
819 foreach (@perm) {
820         next if ($_ ne 'r') && ($_ ne 'w');
821         return 0 unless $acc->{$_ . '_' . $stype};
822         }
823
824 # check per-share acl
825 if ($acc->{'per_' . $stype . '_acls'}) {
826     $acl = $acc->{'ACL' . $stype . '_' . $sname};
827     foreach (@perm) {
828 #        next if $_ eq 'c'; # skip creation perms for per-share acls
829                 return 0 if index($acl, $_) == -1;
830                 }
831         }
832 return 1;       
833 }
834
835 # save_samba_acl($permissions_string, \%access, $share_name)
836 sub save_samba_acl
837 {
838 local ($p, $a, $s)=@_;
839 %share || &get_share($s); # use global %share
840 local $t=&istrue('printable') ? 'ps' : 'fs';
841 $a->{'ACL'. $t .'_'. $s} = $p;
842 #undef($can_cache);
843 return &save_module_acl($a);
844 }
845
846 # drop_samba_acl(\%access, $share_name)
847 sub drop_samba_acl
848 {
849 local ($a, $s)=@_;
850 %share || &get_share($s); # use global %share
851 local $t=&istrue('printable') ? 'ps' : 'fs';
852 delete($a->{'ACL'. $t .'_' . $s});
853 #undef($can_cache);
854 return &save_module_acl($a);
855 }
856
857 # list_groups()
858 # Returns an array containing details of Samba groups
859 sub list_groups
860 {
861 local (@rv, $group, $cmd);
862 if ($has_smbgroupedit) {
863         $cmd = "$config{'smbgroupedit'} -v -l";
864         }
865 else {
866         $cmd = "$config{'net'} -s $config{'smb_conf'} groupmap list verbose";
867         }
868 &open_execute_command(GROUPS, $cmd, 1);
869 while(<GROUPS>) {
870         s/\r|\n//g;
871         if (/^(\S.*)/) {
872                 $group = { 'name' => $1,
873                            'index' => scalar(@rv) };
874                 push(@rv, $group);
875                 }
876         elsif (/^\s+SID\s*:\s+(.*)/i) {
877                 $group->{'sid'} = $1;
878                 }
879         elsif (/^\s+Unix group\s*:\s*(.*)/i) {
880                 $group->{'unix'} = $1;
881                 }
882         elsif (/^\s+Group type\s*:\s*(.*)/i) {
883                 $group->{'type'} = lc($1) eq 'domain group' ? 'd' :
884                                    lc($1) eq 'nt builtin' ? 'b' :
885                                    lc($1) eq 'unknown type' ? 'u' :
886                                    lc($1) eq 'local group' ? 'l' : $1;
887                 }
888         elsif (/^\s+Comment\s*:\s*(.*)/i) {
889                 $group->{'desc'} = $1;
890                 }
891         elsif (/^\s+Privilege\s*:\s*(.*)/i) {
892                 $group->{'priv'} = lc($1) eq 'no privilege' ? undef : $1;
893                 }
894         }
895 close(GROUPS);
896 return @rv;
897 }
898
899 # delete_group(&group)
900 # Delete an existing Samba group
901 sub delete_group
902 {
903 local $out;
904 if ($has_smbgroupedit) {
905         $out = &backquote_logged("$config{'smbgroupedit'} -x ".quotemeta($_[0]->{'name'})." 2>&1");
906         $? && &error("$config{'smbgroupedit'} failed : <pre>$out</pre>");
907         }
908 else {
909         $out = &backquote_logged("$config{'net'} -s $config{'smb_conf'} groupmap delete ntgroup=".quotemeta($_[0]->{'name'})." 2>&1");
910         $? && &error("$config{'net'} failed : <pre>$out</pre>");
911         }
912 }
913
914 # modify_group(&group)
915 # Update the details of an existing Samba group
916 sub modify_group
917 {
918 local $out;
919 if ($has_smbgroupedit) {
920         $out = &backquote_logged(
921                 $config{'smbgroupedit'}.
922                 " -c ".quotemeta($_[0]->{'sid'}).
923                 ($_[0]->{'unix'} == -1 ? "" :" -u ".quotemeta($_[0]->{'unix'})).
924                 ($_[0]->{'desc'} ? " -d ".quotemeta($_[0]->{'desc'}) :" -d ''").
925                 " -t ".$_[0]->{'type'}.
926                 " 2>&1");
927         $? && &error("$config{'smbgroupedit'} failed : <pre>$out</pre>");
928         }
929 else {
930         $out = &backquote_logged(
931                 "$config{'net'} -s $config{'smb_conf'} groupmap modify".
932                 " sid=".quotemeta($_[0]->{'sid'}).
933                 ($_[0]->{'unix'} == -1 ? "" :
934                                 " unixgroup=".quotemeta($_[0]->{'unix'})).
935                 ($_[0]->{'desc'} ? " comment=".quotemeta($_[0]->{'desc'})
936                                  : " 'comment= '").
937                 " type=".quotemeta($_[0]->{'type'})." 2>&1");
938         $? && &error("$config{'net'} failed : <pre>$out</pre>");
939         }
940 }
941
942 # create_group(&group)
943 # Add a Samba new group
944 sub create_group
945 {
946 local $out;
947 if ($has_smbgroupedit) {
948         $out = &backquote_logged(
949                 $config{'smbgroupedit'}.
950                 " -a ".quotemeta($_[0]->{'unix'}).
951                 " -n ".quotemeta($_[0]->{'name'}).
952                 ($_[0]->{'priv'} ? " -p ".quotemeta($_[0]->{'priv'}) : "").
953                 ($_[0]->{'desc'} ? " -d ".quotemeta($_[0]->{'desc'}) :" -d ''").
954                 " -t ".$_[0]->{'type'}." 2>&1");
955         $? && &error("$config{'smbgroupedit'} failed : <pre>$out</pre>");
956         }
957 else {
958         $out = &backquote_logged("$config{'net'} -s $config{'smb_conf'} maxrid 2>&1");
959         local $maxrid = $out =~ /rid:\s+(\d+)/ ? $1 + 1 : undef;
960         $maxrid = 1000 if ($maxrid < 1000);     # Should be >1000
961         $out = &backquote_logged(
962                 "$config{'net'} -s $config{'smb_conf'} groupmap add".
963                 " rid=$maxrid".
964                 " unixgroup=".quotemeta($_[0]->{'unix'}).
965                 " ntgroup=".quotemeta($_[0]->{'name'}).
966                 " type=".quotemeta($_[0]->{'type'})." 2>&1");
967         $? && &error("<pre>$out</pre>");
968         $out = &backquote_logged(
969                 "$config{'net'} groupmap modify".
970                 " ntgroup=".quotemeta($_[0]->{'name'}).
971                 ($_[0]->{'desc'} ? " comment=".quotemeta($_[0]->{'desc'})
972                                  : " 'comment= '").
973                 " type=".quotemeta($_[0]->{'type'})." 2>&1");
974         $? && &error("$config{'net'} failed : <pre>$out</pre>");
975         }
976 }
977
978 # get_samba_version(&out, [keep-original-format])
979 # Returns the Samba version
980 sub get_samba_version
981 {
982 local $flag;
983 foreach $flag ("-V", "-v") {
984         &execute_command("$config{'samba_server'} $flag", undef, $_[0], $_[0]);
985         if (${$_[0]} =~ /(Version|Samba)\s+(CVS\s+)?[^0-9 ]*(\d+)\.(\S+)/i) {
986                 local $v1 = $3;
987                 local $v2 = $4;
988                 if (!$_[1]) {
989                         $v2 =~ s/[^0-9]//g;
990                         }
991                 return "$v1.$v2";
992                 }
993         }
994 return undef;
995 }
996
997 sub check_user_enabled
998 {
999 local %share;
1000 &get_share("global");
1001 if (!&istrue("encrypt passwords")) {
1002         $err = &text('check_user1', $_[0], "conf_pass.cgi");
1003         }
1004 elsif (!$config{'smb_passwd'} && !$has_pdbedit) {
1005         $err = &text('check_user2', $_[0], "../config.cgi?$module_name");
1006         }
1007 if ($err) {
1008         print "<p>$err<p>\n";
1009         print "<hr>\n";
1010         &footer("", $text{'index_sharelist'});
1011         exit;
1012         }
1013 }
1014
1015 sub check_group_enabled
1016 {
1017 if ($samba_version < 3) {
1018         $err = &text('check_groups1', $_[0]);
1019         }
1020 elsif (!$has_groups) {
1021         $err = &text('check_groups2', $_[0], "../config.cgi?$module_name");
1022         }
1023 if ($err) {
1024         print "<p>$err<p>\n";
1025         print "<hr>\n";
1026         &footer("", $text{'index_sharelist'});
1027         exit;
1028         }
1029 }
1030
1031 1;
1032