Handle hostnames with upper-case letters
[webmin.git] / htpasswd-file / htpasswd-file-lib.pl
1 # htpasswd-file-lib.pl
2 # Functions for reading and writing a .htpasswd format file
3 # XXX md5 and old password
4
5 BEGIN { push(@INC, ".."); };
6 use WebminCore;
7 if (!$module_name) {
8         &init_config();
9         %access = &get_module_acl();
10         }
11 do 'md5-lib.pl';
12 $htdigest_command = &has_command("htdigest") || &has_command("htdigest2");
13
14 # list_users([file])
15 # Returns an array of user and password details from the given file
16 sub list_users
17 {
18 local $file = $_[0] || $config{'file'};
19 if (!defined($list_authusers_cache{$file})) {
20         $list_authusers_cache{$file} = [ ];
21         local $_;
22         local $lnum = 0;
23         local $count = 0;
24         open(HTPASSWD, $file);
25         while(<HTPASSWD>) {
26                 if (/^(#?)\s*([^:]+):(\S*)/) {
27                         push(@{$list_authusers_cache{$file}},
28                                   { 'user' => $2,
29                                     'pass' => $3,
30                                     'enabled' => !$1,
31                                     'file' => $file,
32                                     'line' => $lnum,
33                                     'index' => $count++ });
34                         }
35                 $lnum++;
36                 }
37         close(HTPASSWD);
38         }
39 return $list_authusers_cache{$file};
40 }
41
42 # list_digest_users([file])
43 # Returns an array of user, domain and password details from the given file
44 sub list_digest_users
45 {
46 local $file = $_[0] || $config{'file'};
47 if (!defined($list_authusers_cache{$file})) {
48         $list_authusers_cache{$file} = [ ];
49         local $_;
50         local $lnum = 0;
51         local $count = 0;
52         open(HTPASSWD, $file);
53         while(<HTPASSWD>) {
54                 if (/^(#?)\s*(\S+):(\S+):(\S*)/) {
55                         push(@{$list_authusers_cache{$file}},
56                                   { 'user' => $2,
57                                     'dom' => $3,
58                                     'pass' => $4,
59                                     'enabled' => !$1,
60                                     'digest' => 1,
61                                     'file' => $file,
62                                     'line' => $lnum,
63                                     'index' => $count++ });
64                         }
65                 $lnum++;
66                 }
67         close(HTPASSWD);
68         }
69 return $list_authusers_cache{$file};
70 }
71
72 # modify_user(&user)
73 sub modify_user
74 {
75 local $lref = &read_file_lines($_[0]->{'file'});
76 if ($_[0]->{'digest'}) {
77         $lref->[$_[0]->{'line'}] = ($_[0]->{'enabled'} ? "" : "#").
78                            "$_[0]->{'user'}:$_[0]->{'dom'}:$_[0]->{'pass'}";
79         }
80 else {
81         $lref->[$_[0]->{'line'}] = ($_[0]->{'enabled'} ? "" : "#").
82                                    "$_[0]->{'user'}:$_[0]->{'pass'}";
83         }
84 &flush_file_lines($_[0]->{'file'});
85 }
86
87 # create_user(&user, [file])
88 sub create_user
89 {
90 $_[0]->{'file'} = $_[1] || $config{'file'};
91 local $lref = &read_file_lines($_[0]->{'file'});
92 $_[0]->{'line'} = @$lref;
93 if ($_[0]->{'digest'}) {
94         push(@$lref, ($_[0]->{'enabled'} ? "" : "#").
95                      "$_[0]->{'user'}:$_[0]->{'dom'}:$_[0]->{'pass'}");
96         }
97 else {
98         push(@$lref, ($_[0]->{'enabled'} ? "" : "#").
99                      "$_[0]->{'user'}:$_[0]->{'pass'}");
100         }
101 &flush_file_lines($_[0]->{'file'});
102 $_[0]->{'index'} = @{$list_authusers_cache{$_[0]->{'file'}}};
103 push(@{$list_authusers_cache{$_[0]->{'file'}}}, $_[0]);
104 }
105
106 # delete_user(&user)
107 sub delete_user
108 {
109 local $lref = &read_file_lines($_[0]->{'file'});
110 splice(@$lref, $_[0]->{'line'}, 1);
111 &flush_file_lines($_[0]->{'file'});
112 splice(@{$list_authusers_cache{$_[0]->{'file'}}}, $_[0]->{'index'}, 1);
113 map { $_->{'line'}-- if ($_->{'line'} > $_[0]->{'line'}) }
114     @{$list_authusers_cache{$_[0]->{'file'}}};
115 }
116
117 # encrypt_password(string, [old], md5mode)
118 sub encrypt_password
119 {
120 &seed_random();
121 if ($_[2] == 1) {
122         # MD5
123         return &encrypt_md5($_[0], $_[1]);
124         }
125 elsif ($_[2] == 2) {
126         # SHA1
127         return &encrypt_sha1($_[0]);
128         }
129 elsif ($_[2] == 3) {
130         # Digest
131         return &digest_password(undef, undef, $_[0]);
132         }
133 else {
134         # Crypt
135         if ($gconfig{'os_type'} eq 'windows' && &has_command("htpasswd")) {
136                 # Call htpasswd program
137                 local $qp = quotemeta($_[0]);
138                 local $out = `htpasswd -n -b foo $qp 2>&1 <$null_file`;
139                 if ($out =~ /^foo:(\S+)/) {
140                         return $1;
141                         }
142                 else {
143                         &error("htpasswd failed : $out");
144                         }
145                 }
146         else {
147                 # Use built-in encryption code
148                 local $salt = $_[1] ||
149                               chr(int(rand(26))+65).chr(int(rand(26))+65);
150                 return &unix_crypt($_[0], $salt);
151                 }
152         }
153 }
154
155 # digest_password(user, realm, pass)
156 # Encrypts a password in the format used by htdigest
157 sub digest_password
158 {
159 local ($user, $dom, $pass) = @_;
160 local $temp = &tempname();
161 eval "use Digest::MD5";
162 if (!$@) {
163         # Use the digest::MD5 module to do the encryption directly
164         return Digest::MD5::md5_hex("$user:$dom:$pass");
165         }
166 else {
167         # Shell out to htdigest command
168         &foreign_require("proc", "proc-lib.pl");
169         local ($fh, $fpid) = &proc::pty_process_exec("$htdigest_command -c $temp ".quotemeta($dom)." ".quotemeta($user));
170         &wait_for($fh, "password:");
171         &sysprint($fh, "$pass\n");
172         &wait_for($fh, "password:");
173         &sysprint($fh, "$pass\n");
174         &wait_for($fh);
175         close($fh);
176         local $tempusers = &list_digest_users($temp);
177         unlink($temp);
178         return $tempusers->[0]->{'pass'};
179         }
180 }
181
182 # list_groups(file)
183 # Returns an array of group details from the given file
184 sub list_groups
185 {
186 local $file = $_[0];
187 if (!defined($list_authgroups_cache{$file})) {
188         $list_authgroups_cache{$file} = [ ];
189         local $_;
190         local $lnum = 0;
191         local $count = 0;
192         open(HTPASSWD, $file);
193         while(<HTPASSWD>) {
194                 if (/^(#?)\s*(\S+):\s*(.*)/) {
195                         push(@{$list_authgroups_cache{$file}},
196                                   { 'group' => $2,
197                                     'enabled' => !$1,
198                                     'members' => [ split(/\s+/, $3) ],
199                                     'file' => $file,
200                                     'line' => $lnum,
201                                     'index' => $count++ });
202                         }
203                 $lnum++;
204                 }
205         close(HTPASSWD);
206         }
207 return $list_authgroups_cache{$file};
208 }
209
210 # modify_group(&group)
211 sub modify_group
212 {
213 local $lref = &read_file_lines($_[0]->{'file'});
214 $lref->[$_[0]->{'line'}] = ($_[0]->{'enabled'} ? "" : "#").
215                            "$_[0]->{'group'}: ".
216                            join(" ", @{$_[0]->{'members'}});
217 &flush_file_lines();
218 }
219
220 # create_group(&group, [file])
221 sub create_group
222 {
223 $_[0]->{'file'} = $_[1] || $config{'file'};
224 local $lref = &read_file_lines($_[0]->{'file'});
225 $_[0]->{'line'} = @$lref;
226 push(@$lref, ($_[0]->{'enabled'} ? "" : "#").
227               "$_[0]->{'group'}: ".
228               join(" ", @{$_[0]->{'members'}}));
229 &flush_file_lines();
230 $_[0]->{'index'} = @{$list_authgroups_cache{$_[0]->{'file'}}};
231 push(@{$list_authgroups_cache{$_[0]->{'file'}}}, $_[0]);
232 }
233
234 # delete_group(&group)
235 sub delete_group
236 {
237 local $lref = &read_file_lines($_[0]->{'file'});
238 splice(@$lref, $_[0]->{'line'}, 1);
239 &flush_file_lines();
240 splice(@{$list_authgroups_cache{$_[0]->{'file'}}}, $_[0]->{'index'}, 1);
241 map { $_->{'line'}-- if ($_->{'line'} > $_[0]->{'line'}) }
242     @{$list_authgroups_cache{$_[0]->{'file'}}};
243 }
244
245
246 1;
247