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