← Index
NYTProf Performance Profile   « block view • line view • sub view »
For /usr/local/src/github.com/foswiki/core/bin/view
  Run on Sun Dec 4 17:17:59 2011
Reported on Sun Dec 4 17:27:10 2011

Filename/usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/HtPasswdUser.pm
StatementsExecuted 5759 statements in 31.3ms
Subroutines
Calls P F Exclusive
Time
Inclusive
Time
Subroutine
642118.2ms20.1msFoswiki::Users::HtPasswdUser::::_readPasswdFoswiki::Users::HtPasswdUser::_readPasswd
63212.73ms27.7msFoswiki::Users::HtPasswdUser::::fetchPassFoswiki::Users::HtPasswdUser::fetchPass
63111.18ms21.2msFoswiki::Users::HtPasswdUser::::__ANON__[:484]Foswiki::Users::HtPasswdUser::__ANON__[:484]
40211971µs971µsFoswiki::Users::HtPasswdUser::::CORE:readlineFoswiki::Users::HtPasswdUser::CORE:readline (opcode)
111918µs1.05msFoswiki::Users::HtPasswdUser::::BEGIN@18Foswiki::Users::HtPasswdUser::BEGIN@18
39711787µs787µsFoswiki::Users::HtPasswdUser::::CORE:matchFoswiki::Users::HtPasswdUser::CORE:match (opcode)
111415µs662µsFoswiki::Users::HtPasswdUser::::fetchUsersFoswiki::Users::HtPasswdUser::fetchUsers
6431240µs240µsFoswiki::Users::HtPasswdUser::::canFetchUsersFoswiki::Users::HtPasswdUser::canFetchUsers
111201µs9.08msFoswiki::Users::HtPasswdUser::::newFoswiki::Users::HtPasswdUser::new
111190µs190µsFoswiki::Users::HtPasswdUser::::CORE:sortFoswiki::Users::HtPasswdUser::CORE:sort (opcode)
11154µs100µsFoswiki::Users::HtPasswdUser::::readOnlyFoswiki::Users::HtPasswdUser::readOnly
11146µs63µsFoswiki::Users::HtPasswdUser::::finishFoswiki::Users::HtPasswdUser::finish
11135µs60µsFoswiki::Users::HtPasswdUser::::_lockPasswdFileFoswiki::Users::HtPasswdUser::_lockPasswdFile
32135µs35µsFoswiki::Users::HtPasswdUser::::CORE:ftisFoswiki::Users::HtPasswdUser::CORE:ftis (opcode)
11127µs36µsFoswiki::Users::HtPasswdUser::::BEGIN@15Foswiki::Users::HtPasswdUser::BEGIN@15
11121µs134µsFoswiki::Users::HtPasswdUser::::BEGIN@26Foswiki::Users::HtPasswdUser::BEGIN@26
11120µs44µsFoswiki::Users::HtPasswdUser::::BEGIN@16Foswiki::Users::HtPasswdUser::BEGIN@16
11120µs20µsFoswiki::Users::HtPasswdUser::::CORE:sysopenFoswiki::Users::HtPasswdUser::CORE:sysopen (opcode)
11120µs20µsFoswiki::Users::HtPasswdUser::::CORE:openFoswiki::Users::HtPasswdUser::CORE:open (opcode)
11118µs1.73msFoswiki::Users::HtPasswdUser::::BEGIN@23Foswiki::Users::HtPasswdUser::BEGIN@23
11118µs55µsFoswiki::Users::HtPasswdUser::::BEGIN@21Foswiki::Users::HtPasswdUser::BEGIN@21
11117µs416µsFoswiki::Users::HtPasswdUser::::BEGIN@22Foswiki::Users::HtPasswdUser::BEGIN@22
22115µs15µsFoswiki::Users::HtPasswdUser::::CORE:closeFoswiki::Users::HtPasswdUser::CORE:close (opcode)
11115µs22µsFoswiki::Users::HtPasswdUser::::_unlockPasswdFileFoswiki::Users::HtPasswdUser::_unlockPasswdFile
11110µs10µsFoswiki::Users::HtPasswdUser::::BEGIN@30Foswiki::Users::HtPasswdUser::BEGIN@30
1115µs5µsFoswiki::Users::HtPasswdUser::::CORE:ftereadFoswiki::Users::HtPasswdUser::CORE:fteread (opcode)
1115µs5µsFoswiki::Users::HtPasswdUser::::CORE:ftdirFoswiki::Users::HtPasswdUser::CORE:ftdir (opcode)
1115µs5µsFoswiki::Users::HtPasswdUser::::CORE:ftewriteFoswiki::Users::HtPasswdUser::CORE:ftewrite (opcode)
1114µs4µsFoswiki::Users::HtPasswdUser::::CORE:flockFoswiki::Users::HtPasswdUser::CORE:flock (opcode)
0000s0sFoswiki::Users::HtPasswdUser::::__ANON__[:487]Foswiki::Users::HtPasswdUser::__ANON__[:487]
0000s0sFoswiki::Users::HtPasswdUser::::__ANON__[:528]Foswiki::Users::HtPasswdUser::__ANON__[:528]
0000s0sFoswiki::Users::HtPasswdUser::::__ANON__[:536]Foswiki::Users::HtPasswdUser::__ANON__[:536]
0000s0sFoswiki::Users::HtPasswdUser::::__ANON__[:539]Foswiki::Users::HtPasswdUser::__ANON__[:539]
0000s0sFoswiki::Users::HtPasswdUser::::__ANON__[:564]Foswiki::Users::HtPasswdUser::__ANON__[:564]
0000s0sFoswiki::Users::HtPasswdUser::::__ANON__[:567]Foswiki::Users::HtPasswdUser::__ANON__[:567]
0000s0sFoswiki::Users::HtPasswdUser::::__ANON__[:570]Foswiki::Users::HtPasswdUser::__ANON__[:570]
0000s0sFoswiki::Users::HtPasswdUser::::__ANON__[:645]Foswiki::Users::HtPasswdUser::__ANON__[:645]
0000s0sFoswiki::Users::HtPasswdUser::::__ANON__[:648]Foswiki::Users::HtPasswdUser::__ANON__[:648]
0000s0sFoswiki::Users::HtPasswdUser::::_dumpPasswdFoswiki::Users::HtPasswdUser::_dumpPasswd
0000s0sFoswiki::Users::HtPasswdUser::::_savePasswdFoswiki::Users::HtPasswdUser::_savePasswd
0000s0sFoswiki::Users::HtPasswdUser::::checkPasswordFoswiki::Users::HtPasswdUser::checkPassword
0000s0sFoswiki::Users::HtPasswdUser::::encryptFoswiki::Users::HtPasswdUser::encrypt
0000s0sFoswiki::Users::HtPasswdUser::::findUserByEmailFoswiki::Users::HtPasswdUser::findUserByEmail
0000s0sFoswiki::Users::HtPasswdUser::::getEmailsFoswiki::Users::HtPasswdUser::getEmails
0000s0sFoswiki::Users::HtPasswdUser::::isManagingEmailsFoswiki::Users::HtPasswdUser::isManagingEmails
0000s0sFoswiki::Users::HtPasswdUser::::removeUserFoswiki::Users::HtPasswdUser::removeUser
0000s0sFoswiki::Users::HtPasswdUser::::setEmailsFoswiki::Users::HtPasswdUser::setEmails
0000s0sFoswiki::Users::HtPasswdUser::::setPasswordFoswiki::Users::HtPasswdUser::setPassword
Call graph for these subroutines as a Graphviz dot language file.
Line State
ments
Time
on line
Calls Time
in subs
Code
1# See bottom of file for license and copyright information
2
3=begin TML
4
5---+ package Foswiki::Users::HtPasswdUser
6
7Support for htpasswd and htdigest format password files.
8
9Subclass of =[[%SCRIPTURL{view}%/%SYSTEMWEB%/PerlDoc?module=Foswiki::Users::Password][Foswiki::Users::Password]]=.
10See documentation of that class for descriptions of the methods of this class.
11
12=cut
13
14package Foswiki::Users::HtPasswdUser;
15250µs246µs
# spent 36µs (27+10) within Foswiki::Users::HtPasswdUser::BEGIN@15 which was called: # once (27µs+10µs) by Foswiki::Users::TopicUserMapping::new at line 15
use strict;
# spent 36µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@15 # spent 10µs making 1 call to strict::import
16247µs269µs
# spent 44µs (20+24) within Foswiki::Users::HtPasswdUser::BEGIN@16 which was called: # once (20µs+24µs) by Foswiki::Users::TopicUserMapping::new at line 16
use warnings;
# spent 44µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@16 # spent 24µs making 1 call to warnings::import
17
182168µs11.05ms
# spent 1.05ms (918µs+135µs) within Foswiki::Users::HtPasswdUser::BEGIN@18 which was called: # once (918µs+135µs) by Foswiki::Users::TopicUserMapping::new at line 18
use Foswiki::Users::Password ();
# spent 1.05ms making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@18
19113µsour @ISA = ('Foswiki::Users::Password');
20
21249µs292µs
# spent 55µs (18+37) within Foswiki::Users::HtPasswdUser::BEGIN@21 which was called: # once (18µs+37µs) by Foswiki::Users::TopicUserMapping::new at line 21
use Assert;
# spent 55µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@21 # spent 37µs making 1 call to Assert::import
22254µs2816µs
# spent 416µs (17+400) within Foswiki::Users::HtPasswdUser::BEGIN@22 which was called: # once (17µs+400µs) by Foswiki::Users::TopicUserMapping::new at line 22
use Error qw( :try );
# spent 416µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@22 # spent 400µs making 1 call to Error::import
23265µs23.44ms
# spent 1.73ms (18µs+1.71) within Foswiki::Users::HtPasswdUser::BEGIN@23 which was called: # once (18µs+1.71ms) by Foswiki::Users::TopicUserMapping::new at line 23
use Fcntl qw( :DEFAULT :flock );
# spent 1.73ms making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@23 # spent 1.71ms making 1 call to Exporter::import
24
25# Set TRACE to 1 to enable detailed trace of password activity
26297µs2246µs
# spent 134µs (21+112) within Foswiki::Users::HtPasswdUser::BEGIN@26 which was called: # once (21µs+112µs) by Foswiki::Users::TopicUserMapping::new at line 26
use constant TRACE => 0;
# spent 134µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@26 # spent 112µs making 1 call to constant::import
27
28# 'Use locale' for internationalisation of Perl sorting in getTopicNames
29# and other routines - main locale settings are done in Foswiki::setupLocale
30
# spent 10µs within Foswiki::Users::HtPasswdUser::BEGIN@30 which was called: # once (10µs+0s) by Foswiki::Users::TopicUserMapping::new at line 37
BEGIN {
31
32 # Do a dynamic 'use locale' for this module
33110µs if ( $Foswiki::cfg{UseLocale} ) {
34 require locale;
35 import locale();
36 }
3715.49ms110µs}
# spent 10µs making 1 call to Foswiki::Users::HtPasswdUser::BEGIN@30
38
39
# spent 9.08ms (201µs+8.88) within Foswiki::Users::HtPasswdUser::new which was called: # once (201µs+8.88ms) by Foswiki::Users::TopicUserMapping::new at line 66 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm
sub new {
4013µs my ( $class, $session ) = @_;
41125µs122µs my $this = bless( $class->SUPER::new($session), $class );
# spent 22µs making 1 call to Foswiki::Users::Password::new
4212µs $this->{error} = undef;
43
4414µs if ( $Foswiki::cfg{Htpasswd}{AutoDetect} ) {
45
46 # For autodetect, soft errors are allowed. If the .htpasswd file contains
47 # a password for an unsupported encoding, it will not match.
48160µs eval 'use Digest::SHA';
# spent 172µs executing statements in string eval
# includes 5.00ms spent executing 1 call to 1 sub defined therein.
4913µs $this->{SHA} = 1 unless ($@);
50164µs eval 'use Crypt::PasswdMD5';
# spent 247µs executing statements in string eval
# includes 1.26ms spent executing 1 call to 1 sub defined therein.
5113µs $this->{APR} = 1 unless ($@);
52
53 }
54
5516µs if ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'md5'
56 || $Foswiki::cfg{Htpasswd}{Encoding} eq 'htdigest-md5' )
57 {
58 require Digest::MD5;
59 if ( $Foswiki::cfg{AuthRealm} =~ /\:/ ) {
60 print STDERR
61"ERROR: the AuthRealm cannot contain a ':' (colon) as it corrupts the password file\n";
62 throw Error::Simple(
63"ERROR: the AuthRealm cannot contain a ':' (colon) as it corrupts the password file"
64 );
65 }
66 }
67 elsif ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'crypt' ) {
68 }
69 elsif ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'plain' ) {
70 }
71 elsif ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'sha1' ) {
7212µs require Digest::SHA;
7312µs $this->{SHA} = 1;
74 }
75 elsif ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'apache-md5' ) {
76 require Crypt::PasswdMD5;
77 $this->{APR} = 1;
78 }
79 elsif ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'crypt-md5' ) {
80 eval 'use Crypt::PasswdMD5';
81 $this->{APR} = 1 unless ($@);
82 }
83 else {
84 print STDERR "ERROR: unknown {Htpasswd}{Encoding} setting : "
85 . $Foswiki::cfg{Htpasswd}{Encoding} . "\n";
86 throw Error::Simple( "ERROR: unknown {Htpasswd}{Encoding} setting : "
87 . $Foswiki::cfg{Htpasswd}{Encoding}
88 . "\n" );
89 }
90
9119µs return $this;
92}
93
94=begin TML
95
96---++ ObjectMethod finish()
97Break circular references.
98
99=cut
100
101# Note to developers; please undef *all* fields in the object explicitly,
102# whether they are references or not. That way this method is "golden
103# documentation" of the live fields in the object.
104
# spent 63µs (46+18) within Foswiki::Users::HtPasswdUser::finish which was called: # once (46µs+18µs) by Foswiki::Users::TopicUserMapping::finish at line 106 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm
sub finish {
10513µs my $this = shift;
106131µs118µs $this->SUPER::finish();
# spent 18µs making 1 call to Foswiki::Users::Password::finish
107111µs undef $this->{passworddata};
108}
109
110=begin TML
111
112---++ ObjectMethod readOnly( ) -> boolean
113
114returns true if the password file is not currently modifyable
115
116=cut
117
118
# spent 100µs (54+46) within Foswiki::Users::HtPasswdUser::readOnly which was called: # once (54µs+46µs) by Foswiki::Users::TopicUserMapping::new at line 71 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm
sub readOnly {
11912µs my $this = shift;
12013µs my $path = $Foswiki::cfg{Htpasswd}{FileName};
121
122 #TODO: what if the data dir is also read only?
123168µs534µs if ( ( !-e $path ) || ( -e $path && -r $path && !-d $path && -w $path ) ) {
# spent 19µs making 2 calls to Foswiki::Users::HtPasswdUser::CORE:ftis, avg 9µs/call # spent 5µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:fteread # spent 5µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:ftdir # spent 5µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:ftewrite
12417µs112µs $this->{session}->enterContext('passwords_modifyable');
# spent 12µs making 1 call to Foswiki::enterContext
12517µs return 0;
126 }
127 return 1;
128}
129
130
# spent 240µs within Foswiki::Users::HtPasswdUser::canFetchUsers which was called 64 times, avg 4µs/call: # 61 times (229µs+0s) by Foswiki::Users::TopicUserMapping::_userReallyExists at line 241 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm, avg 4µs/call # 2 times (7µs+0s) by Foswiki::Users::TopicUserMapping::userExists at line 539 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm, avg 4µs/call # once (4µs+0s) by Foswiki::Users::TopicUserMapping::_loadMapping at line 1568 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm
sub canFetchUsers {
13164362µs return 1;
132}
133
134
# spent 662µs (415+247) within Foswiki::Users::HtPasswdUser::fetchUsers which was called: # once (415µs+247µs) by Foswiki::Users::TopicUserMapping::_loadMapping at line 1597 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm
sub fetchUsers {
13512µs my $this = shift;
136
137 # Read passwords with shared lock
13817µs18µs my $db = $this->_readPasswd(1);
# spent 8µs making 1 call to Foswiki::Users::HtPasswdUser::_readPasswd
1391568µs1190µs my @users = sort keys %$db;
# spent 190µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:sort
14013µs require Foswiki::ListIterator;
141122µs149µs return new Foswiki::ListIterator( \@users );
# spent 49µs making 1 call to Foswiki::ListIterator::new
142}
143
144# Lock the htpasswd semaphore file (create if it does not exist)
145# Returns a file handle that you can later simply close with _unlockPasswdFile
146
# spent 60µs (35+24) within Foswiki::Users::HtPasswdUser::_lockPasswdFile which was called: # once (35µs+24µs) by Foswiki::Users::HtPasswdUser::_readPasswd at line 184
sub _lockPasswdFile {
14712µs my $operator = @_;
14814µs my $lockFileName = $Foswiki::cfg{WorkingDir} . '/htpasswd.lock';
149
150135µs120µs sysopen( my $fh, $lockFileName, O_RDWR | O_CREAT, 0666 )
# spent 20µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:sysopen
151 || throw Error::Simple( $lockFileName
152 . ' open or create password lock file failed -'
153 . 'check access rights: '
154 . $! );
155113µs14µs flock $fh, $operator;
# spent 4µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:flock
156
15718µs return $fh;
158}
159
160# Unlock the semaphore file. You must pass the filehandle for the lock file
161# which was returned by _lockPasswdFile
162
# spent 22µs (15+7) within Foswiki::Users::HtPasswdUser::_unlockPasswdFile which was called: # once (15µs+7µs) by Foswiki::Users::HtPasswdUser::_readPasswd at line 284
sub _unlockPasswdFile {
16312µs my $fh = shift;
164121µs17µs close($fh);
# spent 7µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:close
165}
166
167# Read the password file. The content of the file is cached in
168# the password object.
169# We put a shared lock while reading if requested to prevent
170# other processes from writing while we read but still allows
171# parallel reading. The caller must never request a shared lock
172# if there is already an exclusive lock.
173
# spent 20.1ms (18.2+1.88) within Foswiki::Users::HtPasswdUser::_readPasswd which was called 64 times, avg 313µs/call: # 63 times (18.2ms+1.88ms) by Foswiki::Users::HtPasswdUser::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/HtPasswdUser.pm:484] at line 475, avg 318µs/call # once (8µs+0s) by Foswiki::Users::HtPasswdUser::fetchUsers at line 138
sub _readPasswd {
17464110µs my ( $this, $lockShared ) = @_;
175
17664471µs return $this->{passworddata} if ( defined( $this->{passworddata} ) );
177
17812µs my $data = {};
179125µs116µs if ( !-e $Foswiki::cfg{Htpasswd}{FileName} ) {
# spent 16µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:ftis
180 return $data;
181 }
182
18312µs $lockShared |= 0;
18417µs160µs my $lockHandle = _lockPasswdFile(LOCK_SH) if $lockShared;
# spent 60µs making 1 call to Foswiki::Users::HtPasswdUser::_lockPasswdFile
18511µs my $IN_FILE;
186
187134µs120µs open( $IN_FILE, '<', "$Foswiki::cfg{Htpasswd}{FileName}" )
# spent 20µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:open
188 || throw Error::Simple(
189 $Foswiki::cfg{Htpasswd}{FileName} . ' open failed: ' . $! );
19012µs my $line = '';
19112µs my $tID;
19213.14ms402971µs while ( defined( $line = <$IN_FILE> ) ) {
# spent 971µs making 402 calls to Foswiki::Users::HtPasswdUser::CORE:readline, avg 2µs/call
193401751µs chomp $line;
1944012.38ms my @fields = split( /:/, $line, 5 );
195
196 if (TRACE) {
197 print "\nSplit LINE $line\n";
198 foreach my $f (@fields) { print "split: $f\n"; }
199 }
200
201401799µs my $hID = shift @fields;
202
203401777µs if ( $Foswiki::cfg{Htpasswd}{AutoDetect} ) {
204401782µs my $tPass = shift @fields;
205
206 # tPass is either a password or a realm
2074011.37ms if (
208 $tPass eq $Foswiki::cfg{AuthRealm}
209 || ( defined $fields[0]
210 && length( $fields[0] ) eq 32
211 && defined $fields[1]
212 && $fields[1] =~ m/@/ )
213 )
214 {
215 $data->{$hID}->{enc} = 'htdigest-md5';
216 $data->{$hID}->{realm} = $tPass;
217 $data->{$hID}->{pass} = shift @fields;
218 $data->{$hID}->{emails} = shift @fields || '';
219 print STDERR "Auto ENCODING-1 $data->{$hID}->{enc} \n"
220 if (TRACE);
221 next;
222 }
223
2244014.15ms397787µs if ( length($tPass) eq 33 && $tPass =~ m/^\{SHA\}/ ) {
# spent 787µs making 397 calls to Foswiki::Users::HtPasswdUser::CORE:match, avg 2µs/call
225 $data->{$hID}->{enc} = 'sha1';
226 }
227 elsif ( length($tPass) eq 34 && $tPass =~ m/^\$1\$/ ) {
228 $data->{$hID}->{enc} = 'crypt-md5';
229 }
230 elsif ( length($tPass) eq 37 && $tPass =~ m/^\$apr1\$/ ) {
231 $data->{$hID}->{enc} = 'apache-md5';
232 }
233 elsif ( length($tPass) eq 13
234 && ( !$fields[0] || $fields[0] =~ m/@/ ) )
235 {
2364011.57ms $data->{$hID}->{enc} = 'crypt';
237 }
238 elsif ( length($tPass) gt 0 && !$fields[0]
239 || $fields[0] =~ m/@/ )
240 {
241 $data->{$hID}->{enc} = 'plain';
242 }
243 elsif ( length($tPass) eq 0 && !$fields[0]
244 || $fields[0] =~ m/@/ )
245 {
246 $data->{$hID}->{enc} = 'sha';
247 }
248
249401740µs if ( $data->{$hID}->{enc} ) {
2504011.01ms $data->{$hID}->{pass} = $tPass;
2514011.20ms $data->{$hID}->{emails} = shift @fields || '';
252 print STDERR "Auto ENCODING-2 $data->{$hID}->{enc} \n"
253 if (TRACE);
254401782µs next;
255 }
256
257 print STDERR "Fell through - must be htdigest-md5 "
258 . length($tPass)
259 . "--$tPass \n"
260 if (TRACE);
261
262 # Fell through - only thing left is digest encoding
263 $data->{$hID}->{enc} = 'htdigest-md5';
264 $data->{$hID}->{realm} = $tPass;
265 $data->{$hID}->{pass} = shift @fields;
266 $data->{$hID}->{emails} = shift @fields || '';
267 print STDERR "Auto ENCODING-3 $data->{$hID}->{enc} \n" if (TRACE);
268 }
269
270 # Static configuration
271 else {
272 $data->{$hID}->{enc} = $Foswiki::cfg{Htpasswd}{Encoding};
273 $data->{$hID}->{realm} = shift @fields
274 if ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'md5'
275 || $Foswiki::cfg{Htpasswd}{Encoding} eq 'htdigest-md5' );
276 $data->{$hID}->{pass} = shift @fields;
277 $data->{$hID}->{emails} = shift @fields || '';
278 print STDERR
279"Static Encoding - $hID: $data->{$hID}->{enc} pass $data->{$hID}->{pass} emails $data->{$hID}->{emails} \n"
280 if (TRACE);
281 }
282 }
283121µs18µs close($IN_FILE);
# spent 8µs making 1 call to Foswiki::Users::HtPasswdUser::CORE:close
28418µs122µs _unlockPasswdFile($lockHandle) if $lockShared;
# spent 22µs making 1 call to Foswiki::Users::HtPasswdUser::_unlockPasswdFile
285
28613µs $this->{passworddata} = $data;
287129µs return $data;
288}
289
290# Dumps the memory password database to a newline separated string
291sub _dumpPasswd {
292 my $db = shift;
293 my @entries;
294 foreach my $login ( sort( keys(%$db) ) ) {
295
296 my $entry = "$login:";
297 if (
298 $db->{$login}->{pass}
299 && $db->{$login}->{enc}
300 && ( $db->{$login}->{enc} eq 'md5'
301 || $db->{$login}->{enc} eq 'htdigest-md5' )
302 )
303 {
304 print STDERR
305"Writing realm - $db->{$login}->{enc} for $login pass ($db->{$login}->{pass})\n"
306 if (TRACE);
307
308 # htdigest format
309 $entry .= "$Foswiki::cfg{AuthRealm}:";
310 }
311 $db->{$login}->{pass} ||= '';
312 $db->{$login}->{emails} ||= '';
313 $entry .= $db->{$login}->{pass} . ':' . $db->{$login}->{emails};
314 push( @entries, $entry );
315 }
316 return join( "\n", @entries ) . "\n";
317}
318
319sub _savePasswd {
320 my $db = shift;
321
322 unless ( -e "$Foswiki::cfg{Htpasswd}{FileName}" ) {
323
324 # Item4544: Document special format used in .htpasswd for email addresses
325 open( my $readme, '>', "$Foswiki::cfg{Htpasswd}{FileName}.README" )
326 or throw Error::Simple(
327 $Foswiki::cfg{Htpasswd}{FileName} . '.README open failed: ' . $! );
328
329 print $readme <<'EoT';
330Foswiki uses a specially crafted .htpasswd file format that should not be
331manipulated using a standard htpasswd utility or loss of registered emails might occur.
332(3rd-party utilities do not support the email address format used by Foswiki).
333
334More information available at: http://foswiki.org/System/UserAuthentication.
335EoT
336 close($readme);
337 }
338
339 my $content = _dumpPasswd($db);
340 print STDERR "CONTENT $content\n" if (TRACE);
341
342 my $oldMask = umask(077); # Access only by owner
343 my $fh;
344
345 open( $fh, '>', $Foswiki::cfg{Htpasswd}{FileName} )
346 || throw Error::Simple(
347 "$Foswiki::cfg{Htpasswd}{FileName} open failed: $!");
348 print $fh $content;
349
350 close($fh);
351 umask($oldMask); # Restore original umask
352}
353
354sub encrypt {
355 my ( $this, $login, $passwd, $fresh, $entry ) = @_;
356
357 $passwd ||= '';
358
359 my $enc = $entry->{enc};
360 $enc ||= $Foswiki::cfg{Htpasswd}{Encoding};
361
362 if ( $enc eq 'sha1' ) {
363
364 unless ( $this->{SHA} ) {
365 $this->{error} = "Unsupported Encoding";
366 return 0;
367 }
368
369 my $encodedPassword = '{SHA}' . Digest::SHA::sha1_base64($passwd) . '=';
370
371 # don't use chomp, it relies on $/
372 $encodedPassword =~ s/\s+$//;
373 return $encodedPassword;
374
375 }
376 elsif ( $enc eq 'crypt' ) {
377
378 # by David Levy, Internet Channel, 1997
379 # found at http://world.inch.com/Scripts/htpasswd.pl.html
380
381 my $salt;
382 $salt = $this->fetchPass($login) unless $fresh;
383 if ( $fresh || !$salt ) {
384 my @saltchars = ( 'a' .. 'z', 'A' .. 'Z', '0' .. '9', '.', '/' );
385 $salt =
386 $saltchars[ int( rand( $#saltchars + 1 ) ) ]
387 . $saltchars[ int( rand( $#saltchars + 1 ) ) ];
388 }
389 return crypt( $passwd, substr( $salt, 0, 2 ) );
390
391 }
392 elsif ( $enc eq 'md5' || $enc eq 'htdigest-md5' ) {
393
394 # SMELL: what does this do if we are using a htpasswd file?
395 my $realm = $entry->{realm} || $Foswiki::cfg{AuthRealm};
396 my $toEncode = "$login:$realm:$passwd";
397 return Digest::MD5::md5_hex($toEncode);
398
399 }
400 elsif ( $enc eq 'apache-md5' ) {
401
402 unless ( $this->{APR} ) {
403 $this->{error} = "Unsupported Encoding";
404 return 0;
405 }
406
407 my $salt;
408 $salt = $this->fetchPass($login) unless $fresh;
409 if ( $fresh || !$salt ) {
410 $salt = '$apr1$';
411 my @saltchars = ( '.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z' );
412 foreach my $i ( 0 .. 7 ) {
413
414 # generate a salt not only from rand() but also mixing
415 # in the users login name: unecessary
416 $salt .= $saltchars[
417 (
418 int( rand( $#saltchars + 1 ) ) +
419 $i +
420 ord( substr( $login, $i % length($login), 1 ) ) )
421 % ( $#saltchars + 1 )
422 ];
423 }
424 }
425 return Crypt::PasswdMD5::apache_md5_crypt( $passwd,
426 substr( $salt, 0, 14 ) );
427 }
428 elsif ( $enc eq 'crypt-md5' ) {
429 my $salt;
430 $salt = $this->fetchPass($login) unless $fresh;
431 if ( $fresh || !$salt ) {
432 $salt = '$1$';
433 my @saltchars = ( '.', '/', 0 .. 9, 'A' .. 'Z', 'a' .. 'z' );
434 foreach my $i ( 0 .. 7 ) {
435
436 # generate a salt not only from rand() but also mixing
437 # in the users login name: unecessary
438 $salt .= $saltchars[
439 (
440 int( rand( $#saltchars + 1 ) ) +
441 $i +
442 ord( substr( $login, $i % length($login), 1 ) ) )
443 % ( $#saltchars + 1 )
444 ];
445 }
446 }
447
448 # crypt is not cross-plaform, so use Crypt::PasswdMD5 if it's available
449 if ( $this->{APR} ) {
450 return Crypt::PasswdMD5::unix_md5_crypt( $passwd,
451 substr( $salt, 0, 11 ) );
452 }
453 else {
454 return crypt( $passwd, substr( $salt, 0, 11 ) );
455 }
456
457 }
458 elsif ( $enc eq 'plain' ) {
459 return $passwd;
460
461 }
462 die 'Unsupported password encoding ' . $enc;
463}
464
465
# spent 27.7ms (2.73+25.0) within Foswiki::Users::HtPasswdUser::fetchPass which was called 63 times, avg 440µs/call: # 61 times (2.66ms+24.9ms) by Foswiki::Users::TopicUserMapping::_userReallyExists at line 246 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm, avg 451µs/call # 2 times (79µs+157µs) by Foswiki::Users::TopicUserMapping::userExists at line 539 of /usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/TopicUserMapping.pm, avg 118µs/call
sub fetchPass {
46663111µs my ( $this, $login ) = @_;
4676382µs my $ret = 0;
4686382µs my $enc = '';
4696379µs my $db;
470
47163529µs if ($login) {
472
# spent 21.2ms (1.18+20.0) within Foswiki::Users::HtPasswdUser::__ANON__[/usr/local/src/github.com/foswiki/core/lib/Foswiki/Users/HtPasswdUser.pm:484] which was called 63 times, avg 337µs/call: # 63 times (1.18ms+20.0ms) by Error::subs::try at line 416 of Error.pm, avg 337µs/call
try {
473
474 # Read passwords with shared lock
47563328µs6320.0ms $db = $this->_readPasswd(1);
# spent 20.0ms making 63 calls to Foswiki::Users::HtPasswdUser::_readPasswd, avg 318µs/call
47663471µs if ( exists $db->{$login} ) {
4771968µs $ret = $db->{$login}->{pass};
4781936µs $enc = $db->{$login}->{enc};
479 }
480 else {
48144137µs $this->{error} = "Login $login invalid";
4824465µs $ret = undef;
483 }
484 }
485 catch Error::Simple with {
486 $this->{error} = $!;
487631.32ms1891.15ms };
# spent 882µs making 63 calls to Error::catch, avg 14µs/call # spent 265µs making 63 calls to Error::subs::with, avg 4µs/call # spent 23.9ms making 63 calls to Error::subs::try, avg 379µs/call, recursion: max depth 2, sum of overlapping time 23.9ms
488 }
489 else {
490 $this->{error} = 'No user';
491 }
49263432µs return (wantarray) ? ( $ret, $db->{$login} ) : $ret;
493}
494
495sub setPassword {
496 my ( $this, $login, $newUserPassword, $oldUserPassword ) = @_;
497 ASSERT($login) if DEBUG;
498
499 if ( defined($oldUserPassword) ) {
500 unless ( $oldUserPassword eq '1' ) {
501 return 0 unless $this->checkPassword( $login, $oldUserPassword );
502 }
503 }
504 elsif ( $this->fetchPass($login) ) {
505 $this->{error} = $login . ' already exists';
506 return 0;
507 }
508
509 my $lockHandle;
510 try {
511 $lockHandle = _lockPasswdFile(LOCK_EX);
512
513 # Read password without shared lock as we have already exclusive lock
514 my $db = $this->_readPasswd(0);
515
516 $db->{$login}->{pass} = $this->encrypt( $login, $newUserPassword, 1 );
517 $db->{$login}->{enc} = $Foswiki::cfg{Htpasswd}{Encoding};
518 $db->{$login}->{realm} =
519 ( $Foswiki::cfg{Htpasswd}{Encoding} eq 'md5'
520 || $Foswiki::cfg{Htpasswd}{Encoding} eq 'htdigest-md5' )
521 ? $Foswiki::cfg{AuthRealm}
522 : '';
523 $db->{$login}->{emails} ||= '';
524 print STDERR
525"setPassword login $login pass $db->{$login}->{pass} enc $db->{$login}->{enc} realm $db->{$login}->{realm} emails $db->{$login}->{emails}\n"
526 if (TRACE);
527 _savePasswd($db);
528 }
529 catch Error::Simple with {
530 my $e = shift;
531 $this->{error} = $!;
532 print STDERR "ERROR: failed to resetPassword - $! ($e)";
533 $this->{error} = 'unknown error in resetPassword'
534 unless ( $this->{error} && length( $this->{error} ) );
535 return undef;
536 }
537 finally {
538 _unlockPasswdFile($lockHandle) if $lockHandle;
539 };
540
541 $this->{error} = undef;
542 return 1;
543}
544
545sub removeUser {
546 my ( $this, $login ) = @_;
547 my $result = undef;
548 $this->{error} = undef;
549
550 my $lockHandle;
551 try {
552 $lockHandle = _lockPasswdFile(LOCK_EX);
553
554 # Read password without shared lock as we have already exclusive lock
555 my $db = $this->_readPasswd(0);
556 unless ( $db->{$login} ) {
557 $this->{error} = 'No such user ' . $login;
558 }
559 else {
560 delete $db->{$login};
561 _savePasswd($db);
562 $result = 1;
563 }
564 }
565 catch Error::Simple with {
566 $this->{error} = shift->{-text};
567 }
568 finally {
569 _unlockPasswdFile($lockHandle) if $lockHandle;
570 };
571
572 return $result;
573}
574
575sub checkPassword {
576 my ( $this, $login, $password ) = @_;
577 my ( $pw, $entry ) = $this->fetchPass($login);
578
579 # $pw will be 0 if there is no pw
580 return 0 unless defined $pw;
581
582 my $encryptedPassword = $this->encrypt( $login, $password, 0, $entry );
583 return 0 unless ($encryptedPassword);
584
585 $this->{error} = undef;
586
587 #print STDERR "Checking $pw against $encryptedPassword\n" if (TRACE);
588
589 if ( length($pw) != length($encryptedPassword) ) {
590
591 #print STDERR "Fail on length mismatch ($pw) vs enc ($encryptedPassword)\n";
592 $this->{error} = 'Invalid user/password';
593 return 0;
594 }
595 return 1 if ( $pw && ( $encryptedPassword eq $pw ) );
596
597 # pw may validly be '', and must match an unencrypted ''. This is
598 # to allow for sysadmins removing the password field in .htpasswd in
599 # order to reset the password.
600 return 1 if ( defined $password && $pw eq '' && $password eq '' );
601
602 $this->{error} = 'Invalid user/password';
603 return 0;
604}
605
606sub isManagingEmails {
607 return 1;
608}
609
610sub getEmails {
611 my ( $this, $login ) = @_;
612
613 # first try the mapping cache
614 # read passwords with shared lock
615 my $db = $this->_readPasswd(1);
616 if ( $db->{$login}->{emails} ) {
617 return split( /;/, $db->{$login}->{emails} );
618 }
619
620 return;
621}
622
623sub setEmails {
624 my $this = shift;
625 my $login = shift;
626 my $emails = join( ';', @_ );
627 ASSERT($login) if DEBUG;
628 my $lockHandle;
629
630 try {
631 $lockHandle = _lockPasswdFile(LOCK_EX);
632
633 # Read password without shared lock as we have already exclusive lock
634 my $db = $this->_readPasswd(0);
635 unless ( $db->{$login} ) {
636
637 # Make sure the user is in the auth system, by adding them with
638 # a null password if not.
639 $db->{$login}->{pass} = '';
640 }
641
642 $db->{$login}->{emails} = $emails;
643
644 _savePasswd($db);
645 }
646 finally {
647 _unlockPasswdFile($lockHandle) if $lockHandle;
648 };
649 return 1;
650}
651
652# Searches the password DB for users who have set this email.
653sub findUserByEmail {
654 my ( $this, $email ) = @_;
655 my $logins = [];
656
657 # read passwords with shared lock
658 my $db = $this->_readPasswd(1);
659 while ( my ( $k, $v ) = each %$db ) {
660 my %ems = map { $_ => 1 } split( ';', $v->{emails} );
661 if ( $ems{$email} ) {
662 push( @$logins, $k );
663 }
664 }
665 return $logins;
666}
667
66816µs1;
669__END__
 
# spent 15µs within Foswiki::Users::HtPasswdUser::CORE:close which was called 2 times, avg 8µs/call: # once (8µs+0s) by Foswiki::Users::HtPasswdUser::_readPasswd at line 283 # once (7µs+0s) by Foswiki::Users::HtPasswdUser::_unlockPasswdFile at line 164
sub Foswiki::Users::HtPasswdUser::CORE:close; # opcode
# spent 4µs within Foswiki::Users::HtPasswdUser::CORE:flock which was called: # once (4µs+0s) by Foswiki::Users::HtPasswdUser::_lockPasswdFile at line 155
sub Foswiki::Users::HtPasswdUser::CORE:flock; # opcode
# spent 5µs within Foswiki::Users::HtPasswdUser::CORE:ftdir which was called: # once (5µs+0s) by Foswiki::Users::HtPasswdUser::readOnly at line 123
sub Foswiki::Users::HtPasswdUser::CORE:ftdir; # opcode
# spent 5µs within Foswiki::Users::HtPasswdUser::CORE:fteread which was called: # once (5µs+0s) by Foswiki::Users::HtPasswdUser::readOnly at line 123
sub Foswiki::Users::HtPasswdUser::CORE:fteread; # opcode
# spent 5µs within Foswiki::Users::HtPasswdUser::CORE:ftewrite which was called: # once (5µs+0s) by Foswiki::Users::HtPasswdUser::readOnly at line 123
sub Foswiki::Users::HtPasswdUser::CORE:ftewrite; # opcode
# spent 35µs within Foswiki::Users::HtPasswdUser::CORE:ftis which was called 3 times, avg 12µs/call: # 2 times (19µs+0s) by Foswiki::Users::HtPasswdUser::readOnly at line 123, avg 9µs/call # once (16µs+0s) by Foswiki::Users::HtPasswdUser::_readPasswd at line 179
sub Foswiki::Users::HtPasswdUser::CORE:ftis; # opcode
# spent 787µs within Foswiki::Users::HtPasswdUser::CORE:match which was called 397 times, avg 2µs/call: # 397 times (787µs+0s) by Foswiki::Users::HtPasswdUser::_readPasswd at line 224, avg 2µs/call
sub Foswiki::Users::HtPasswdUser::CORE:match; # opcode
# spent 20µs within Foswiki::Users::HtPasswdUser::CORE:open which was called: # once (20µs+0s) by Foswiki::Users::HtPasswdUser::_readPasswd at line 187
sub Foswiki::Users::HtPasswdUser::CORE:open; # opcode
# spent 971µs within Foswiki::Users::HtPasswdUser::CORE:readline which was called 402 times, avg 2µs/call: # 402 times (971µs+0s) by Foswiki::Users::HtPasswdUser::_readPasswd at line 192, avg 2µs/call
sub Foswiki::Users::HtPasswdUser::CORE:readline; # opcode
# spent 190µs within Foswiki::Users::HtPasswdUser::CORE:sort which was called: # once (190µs+0s) by Foswiki::Users::HtPasswdUser::fetchUsers at line 139
sub Foswiki::Users::HtPasswdUser::CORE:sort; # opcode
# spent 20µs within Foswiki::Users::HtPasswdUser::CORE:sysopen which was called: # once (20µs+0s) by Foswiki::Users::HtPasswdUser::_lockPasswdFile at line 150
sub Foswiki::Users::HtPasswdUser::CORE:sysopen; # opcode