line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
############################################################################## |
2
|
|
|
|
|
|
|
# The Faq-O-Matic is Copyright 1997 by Jon Howell, all rights reserved. # |
3
|
|
|
|
|
|
|
# # |
4
|
|
|
|
|
|
|
# This program is free software; you can redistribute it and/or # |
5
|
|
|
|
|
|
|
# modify it under the terms of the GNU General Public License # |
6
|
|
|
|
|
|
|
# as published by the Free Software Foundation; either version 2 # |
7
|
|
|
|
|
|
|
# of the License, or (at your option) any later version. # |
8
|
|
|
|
|
|
|
# # |
9
|
|
|
|
|
|
|
# This program is distributed in the hope that it will be useful, # |
10
|
|
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of # |
11
|
|
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # |
12
|
|
|
|
|
|
|
# GNU General Public License for more details. # |
13
|
|
|
|
|
|
|
# # |
14
|
|
|
|
|
|
|
# You should have received a copy of the GNU General Public License # |
15
|
|
|
|
|
|
|
# along with this program; if not, write to the Free Software # |
16
|
|
|
|
|
|
|
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.# |
17
|
|
|
|
|
|
|
# # |
18
|
|
|
|
|
|
|
# Jon Howell can be contacted at: # |
19
|
|
|
|
|
|
|
# 6211 Sudikoff Lab, Dartmouth College # |
20
|
|
|
|
|
|
|
# Hanover, NH 03755-3510 # |
21
|
|
|
|
|
|
|
# jonh@cs.dartmouth.edu # |
22
|
|
|
|
|
|
|
# # |
23
|
|
|
|
|
|
|
# An electronic copy of the GPL is available at: # |
24
|
|
|
|
|
|
|
# http://www.gnu.org/copyleft/gpl.html # |
25
|
|
|
|
|
|
|
# # |
26
|
|
|
|
|
|
|
############################################################################## |
27
|
|
|
|
|
|
|
|
28
|
1
|
|
|
1
|
|
6
|
use strict; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
45
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
### |
31
|
|
|
|
|
|
|
### The FaqAuth module provides identification, authentication, |
32
|
|
|
|
|
|
|
### and authorization services for the Faq-O-Matic. |
33
|
|
|
|
|
|
|
### |
34
|
|
|
|
|
|
|
### Custom authentication schemes should |
35
|
|
|
|
|
|
|
### be implemented by using the hooks in this module. (For |
36
|
|
|
|
|
|
|
### now there are no hooks, but in theory there should be one |
37
|
|
|
|
|
|
|
### replaceable function.) You'd rather not modify this file, |
38
|
|
|
|
|
|
|
### so you can be drop-in compatible with future faqomatic |
39
|
|
|
|
|
|
|
### releases. |
40
|
|
|
|
|
|
|
### |
41
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
package FAQ::OMatic::Auth; |
43
|
|
|
|
|
|
|
|
44
|
1
|
|
|
1
|
|
4
|
use FAQ::OMatic; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
17
|
|
45
|
1
|
|
|
1
|
|
4
|
use FAQ::OMatic::Item; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
15
|
|
46
|
1
|
|
|
1
|
|
499
|
use FAQ::OMatic::AuthLocal; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
24
|
|
47
|
1
|
|
|
1
|
|
491
|
use FAQ::OMatic::Groups; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
31
|
|
48
|
1
|
|
|
1
|
|
9
|
use FAQ::OMatic::I18N; |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
161
|
|
49
|
1
|
|
|
1
|
|
559
|
use FAQ::OMatic::Entropy; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
36
|
|
50
|
1
|
|
|
1
|
|
6
|
use Digest::MD5 qw(md5_hex); |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
170
|
|
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
# a global constant (accessible outside using my namespace) |
53
|
1
|
|
|
1
|
|
7
|
use vars qw($cookieExtra); # constant, visible to maintenance.pm |
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
2765
|
|
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
my $trustedID = undef; |
56
|
|
|
|
|
|
|
# Perm values only: |
57
|
|
|
|
|
|
|
# '7','9' -- returned by perm routines to indicate |
58
|
|
|
|
|
|
|
# authQuality must be 5, and user must be the |
59
|
|
|
|
|
|
|
# moderator of the item. |
60
|
|
|
|
|
|
|
# '6' -- a perm that indicates a group membership |
61
|
|
|
|
|
|
|
# requirement. (actually "6 group_name".) |
62
|
|
|
|
|
|
|
# $authQuality's and Perm* values: |
63
|
|
|
|
|
|
|
# '5' -- user has provided proof that ID is correct |
64
|
|
|
|
|
|
|
# '3' -- user has merely claimed this ID |
65
|
|
|
|
|
|
|
# '1' -- no ID is offered |
66
|
|
|
|
|
|
|
|
67
|
|
|
|
|
|
|
$cookieExtra = 600; # 10 extra minutes to submit forms after filling |
68
|
|
|
|
|
|
|
# them out so you don't have to worry about |
69
|
|
|
|
|
|
|
# losing your text. |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
sub getID { |
72
|
0
|
|
|
0
|
0
|
|
my $params = FAQ::OMatic::getParams(); # get cached params |
73
|
|
|
|
|
|
|
|
74
|
0
|
|
|
|
|
|
my $trustedID = FAQ::OMatic::getLocal('trustedID'); |
75
|
0
|
|
|
|
|
|
my $authQuality = FAQ::OMatic::getLocal('authQuality'); |
76
|
0
|
0
|
|
|
|
|
if (not defined $trustedID) { |
77
|
0
|
0
|
|
|
|
|
if (defined $params->{'auth'}) { |
|
|
0
|
|
|
|
|
|
78
|
|
|
|
|
|
|
# use a user-overridable auth function |
79
|
0
|
|
|
|
|
|
($trustedID,$authQuality) = authenticate($params); |
80
|
|
|
|
|
|
|
} elsif (defined $params->{'id'}) { |
81
|
|
|
|
|
|
|
# id without authorization |
82
|
0
|
|
|
|
|
|
$trustedID = $params->{'id'}; |
83
|
0
|
|
|
|
|
|
$authQuality = 3; |
84
|
|
|
|
|
|
|
} else { |
85
|
|
|
|
|
|
|
# no authorization offered |
86
|
0
|
|
|
|
|
|
$trustedID = 'anonymous'; |
87
|
0
|
|
|
|
|
|
$authQuality = 1; |
88
|
|
|
|
|
|
|
} |
89
|
|
|
|
|
|
|
} |
90
|
|
|
|
|
|
|
|
91
|
0
|
|
|
|
|
|
FAQ::OMatic::setLocal('trustedID', $trustedID); |
92
|
0
|
|
|
|
|
|
FAQ::OMatic::setLocal('authQuality', $authQuality); |
93
|
0
|
|
|
|
|
|
return ($trustedID,$authQuality); |
94
|
|
|
|
|
|
|
} |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
# result is 'false' if user CAN edit the part, else an error message |
97
|
|
|
|
|
|
|
sub checkPerm { |
98
|
0
|
|
|
0
|
0
|
|
my $item = shift; |
99
|
0
|
|
|
|
|
|
my $operation = shift; |
100
|
|
|
|
|
|
|
|
101
|
0
|
|
|
|
|
|
my ($id,$aq) = getID(); |
102
|
|
|
|
|
|
|
|
103
|
0
|
|
|
|
|
|
my $whocan = getInheritedProperty($item, $operation); |
104
|
|
|
|
|
|
|
|
105
|
|
|
|
|
|
|
# if just some low quality of authentication is required, prove |
106
|
|
|
|
|
|
|
# user has provided it: |
107
|
0
|
|
|
|
|
|
$whocan =~ m/^(\d+)/; |
108
|
0
|
|
0
|
|
|
|
my $whocanNum = $1 || 7; |
109
|
|
|
|
|
|
|
# THANKS to Mikel Smith |
110
|
|
|
|
|
|
|
# for pointing out that this code was generating warning messages |
111
|
0
|
0
|
0
|
|
|
|
if ($whocanNum <= 5 and $whocanNum <= $aq) { |
112
|
|
|
|
|
|
|
# users' ID dominates required ID |
113
|
0
|
|
|
|
|
|
return 0; |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
|
|
|
|
|
|
# prove user belongs to required group: |
117
|
0
|
0
|
0
|
|
|
|
if ($whocanNum == 6 |
118
|
|
|
|
|
|
|
and FAQ::OMatic::Groups::checkMembership($whocan, $id)) { |
119
|
|
|
|
|
|
|
# user belongs to the specified group |
120
|
0
|
|
|
|
|
|
return 0; |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# prove user has at least moderator priveleges |
124
|
0
|
0
|
0
|
|
|
|
if ((($whocanNum==7) and ($aq==5)) |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
125
|
|
|
|
|
|
|
and (($id eq getInheritedProperty($item, 'Moderator')) |
126
|
|
|
|
|
|
|
or ($id eq $FAQ::OMatic::Config::adminAuth) |
127
|
|
|
|
|
|
|
or ('anybody' eq getInheritedProperty($item, 'Moderator')) |
128
|
|
|
|
|
|
|
) |
129
|
|
|
|
|
|
|
) { |
130
|
|
|
|
|
|
|
# user has proven authentication, and is the moderator of the item |
131
|
0
|
|
|
|
|
|
return 0; |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
|
134
|
0
|
|
|
|
|
|
return $whocan; |
135
|
|
|
|
|
|
|
} |
136
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
sub getInheritedProperty { |
138
|
0
|
|
|
0
|
0
|
|
my $item = shift; |
139
|
0
|
|
|
|
|
|
my $property = shift; |
140
|
0
|
|
0
|
|
|
|
my $depth = shift || 0; |
141
|
|
|
|
|
|
|
|
142
|
0
|
0
|
|
|
|
|
if (isPropertyGlobal($property)) { |
143
|
|
|
|
|
|
|
# save a recursive walk up the tree -- this property |
144
|
|
|
|
|
|
|
# is defined at the top. |
145
|
|
|
|
|
|
|
# THANKS to John Goerzen for |
146
|
|
|
|
|
|
|
# finding a dumb bug here. |
147
|
0
|
|
|
|
|
|
$item = new FAQ::OMatic::Item('1'); |
148
|
|
|
|
|
|
|
} |
149
|
0
|
0
|
|
|
|
|
if (not ref $item) { |
150
|
|
|
|
|
|
|
# get property from top item if no item specified |
151
|
0
|
|
|
|
|
|
$item = new FAQ::OMatic::Item('1'); |
152
|
|
|
|
|
|
|
} |
153
|
|
|
|
|
|
|
|
154
|
0
|
0
|
0
|
|
|
|
if (defined($item) and defined $item->{$property}) { |
155
|
|
|
|
|
|
|
return wantarray() |
156
|
0
|
0
|
|
|
|
|
? ($item->{$property}, $item) |
157
|
|
|
|
|
|
|
: $item->{$property}; |
158
|
|
|
|
|
|
|
} |
159
|
|
|
|
|
|
|
|
160
|
0
|
0
|
0
|
|
|
|
if (not defined($item) |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
161
|
|
|
|
|
|
|
or ($item eq '') |
162
|
|
|
|
|
|
|
or ($item->getParent() eq $item) |
163
|
|
|
|
|
|
|
or ($depth > 80)) { |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# no-one defines it, all the way up the chain |
166
|
|
|
|
|
|
|
return wantarray() |
167
|
0
|
0
|
|
|
|
|
? (getDefaultProperty($property), undef) |
168
|
|
|
|
|
|
|
: getDefaultProperty($property); |
169
|
|
|
|
|
|
|
} else { |
170
|
0
|
|
|
|
|
|
return getInheritedProperty($item->getParent(), $property, $depth+1); |
171
|
|
|
|
|
|
|
} |
172
|
|
|
|
|
|
|
} |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
# fields: [ default value, isGlobal ] |
175
|
|
|
|
|
|
|
my %defaultProperties = ( |
176
|
|
|
|
|
|
|
'Moderator' => [ 'nobody', 0 ], |
177
|
|
|
|
|
|
|
'MailModerator' => [ 0, 0 ], |
178
|
|
|
|
|
|
|
'Notifier' => [ 'nobody', 0 ], |
179
|
|
|
|
|
|
|
'MailNotifier' => [ 0, 0 ], |
180
|
|
|
|
|
|
|
'PermEditPart' => [ 5, 0 ], # users with proven authentication |
181
|
|
|
|
|
|
|
'PermAddPart' => [ 5, 0 ], |
182
|
|
|
|
|
|
|
'PermAddItem' => [ 5, 0 ], |
183
|
|
|
|
|
|
|
# 'PermEditItem' => [ 5, 0 ], # (deprecated) |
184
|
|
|
|
|
|
|
'PermEditTitle' => [ 5, 0 ], # moderator |
185
|
|
|
|
|
|
|
'PermEditDirectory' => [ 7, 0 ], |
186
|
|
|
|
|
|
|
'PermModOptions' => [ 7, 0 ], |
187
|
|
|
|
|
|
|
'PermUseHTML' => [ 7, 0 ], |
188
|
|
|
|
|
|
|
'PermNewBag' => [ 7, 1 ], |
189
|
|
|
|
|
|
|
'PermReplaceBag' => [ 7, 1 ], |
190
|
|
|
|
|
|
|
'PermInstall' => [ 7, 1 ], |
191
|
|
|
|
|
|
|
'PermEditGroups' => [ 7, 1 ], |
192
|
|
|
|
|
|
|
'RelaxChildPerms' => [ 'norelax', 0], |
193
|
|
|
|
|
|
|
); |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
sub getDefaultProperty { |
196
|
0
|
|
|
0
|
0
|
|
my $property = shift; |
197
|
|
|
|
|
|
|
|
198
|
0
|
|
|
|
|
|
my $result = $defaultProperties{$property}; |
199
|
0
|
0
|
|
|
|
|
if (not defined $result) { |
200
|
0
|
|
|
|
|
|
$result = 7; |
201
|
0
|
|
|
|
|
|
FAQ::OMatic::gripe('panic', |
202
|
|
|
|
|
|
|
"Property $property expected but not defined"); # tell author |
203
|
|
|
|
|
|
|
} |
204
|
0
|
|
|
|
|
|
return $result->[0]; |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
sub isPropertyGlobal { |
208
|
0
|
|
|
0
|
0
|
|
my $property = shift; |
209
|
|
|
|
|
|
|
|
210
|
0
|
|
|
|
|
|
my $result = $defaultProperties{$property}; |
211
|
0
|
|
0
|
|
|
|
return $result->[1] || 0; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
# ensurePerm() |
215
|
|
|
|
|
|
|
# Checks permissions, returns '' if okay, else returns a redirect to |
216
|
|
|
|
|
|
|
# authenticate. |
217
|
|
|
|
|
|
|
# In list context, returns the same value followed by a quality |
218
|
|
|
|
|
|
|
# value, so if you require two ensurePerms, you return the redirect |
219
|
|
|
|
|
|
|
# with the higher qualityf value (so the user gets all the authentication |
220
|
|
|
|
|
|
|
# done at once). See submitMove. |
221
|
|
|
|
|
|
|
sub ensurePerm { |
222
|
0
|
|
|
0
|
0
|
|
my @p = @_; |
223
|
|
|
|
|
|
|
|
224
|
|
|
|
|
|
|
my ( |
225
|
0
|
|
|
|
|
|
$item, |
226
|
|
|
|
|
|
|
$operation, |
227
|
|
|
|
|
|
|
$restart, # which program to run to restart operation |
228
|
|
|
|
|
|
|
# after user presents ID |
229
|
|
|
|
|
|
|
$cgi, |
230
|
|
|
|
|
|
|
$extraTime, # allow slightly stale cookies, so that a |
231
|
|
|
|
|
|
|
# cookie isn't likely to time out between |
232
|
|
|
|
|
|
|
# clicking "edit" and "submit", which annoys. |
233
|
|
|
|
|
|
|
$xreason, # an extra reason, needed to distinguish |
234
|
|
|
|
|
|
|
# two cases (modOptions) in editItem. |
235
|
|
|
|
|
|
|
$failexit # redirect and exit on failure |
236
|
|
|
|
|
|
|
) = FAQ::OMatic::rearrange( |
237
|
|
|
|
|
|
|
['item','operation','restart','cgi','extraTime','xreason', |
238
|
|
|
|
|
|
|
'failexit'], |
239
|
|
|
|
|
|
|
@p); |
240
|
0
|
|
0
|
|
|
|
$item ||= ''; |
241
|
|
|
|
|
|
|
|
242
|
0
|
|
|
|
|
|
my $result = ''; |
243
|
|
|
|
|
|
|
|
244
|
0
|
|
0
|
|
|
|
my $cookieActual = $FAQ::OMatic::Config::cookieLife || 3600; |
245
|
0
|
0
|
|
|
|
|
$cookieActual += $cookieExtra if ($extraTime); |
246
|
0
|
|
|
|
|
|
FAQ::OMatic::setLocal('cookieActual', $cookieActual); |
247
|
|
|
|
|
|
|
|
248
|
0
|
|
|
|
|
|
my $authFailed = checkPerm($item,$operation); |
249
|
|
|
|
|
|
|
|
250
|
0
|
0
|
|
|
|
|
if ($authFailed) { |
251
|
0
|
|
0
|
|
|
|
my $url = FAQ::OMatic::makeAref('authenticate', |
252
|
|
|
|
|
|
|
{'_restart' => $restart, '_reason'=>$authFailed, |
253
|
|
|
|
|
|
|
'_xreason'=>($xreason||'')}, 'url', 'saveTransients'); |
254
|
0
|
|
|
|
|
|
$result = FAQ::OMatic::redirect($cgi, $url, 'asString'); |
255
|
0
|
0
|
0
|
|
|
|
if ($failexit||'') { |
256
|
0
|
|
|
|
|
|
FAQ::OMatic::redirect($cgi, $result); |
257
|
|
|
|
|
|
|
} |
258
|
|
|
|
|
|
|
} |
259
|
|
|
|
|
|
|
|
260
|
0
|
0
|
|
|
|
|
return wantarray ? ($result, $authFailed) |
261
|
|
|
|
|
|
|
: $result; |
262
|
|
|
|
|
|
|
} |
263
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
sub newCookie { |
265
|
0
|
|
|
0
|
0
|
|
my $id = shift; |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
# Use an existing cookie if available. (why is this good? Just |
268
|
|
|
|
|
|
|
# to keep the cookies file slimmer?) |
269
|
0
|
|
|
|
|
|
my ($cookie,$cid,$ctime); |
270
|
0
|
|
|
|
|
|
($cookie,$cid,$ctime) = findCookie($id,'id'); |
271
|
0
|
0
|
|
|
|
|
return $cookie if (defined $cookie); |
272
|
|
|
|
|
|
|
|
273
|
0
|
|
|
|
|
|
$cookie = "ck".FAQ::OMatic::Entropy::gatherRandomString(); |
274
|
|
|
|
|
|
|
|
275
|
0
|
|
|
|
|
|
my $cookiesFile = "$FAQ::OMatic::Config::metaDir/cookies"; |
276
|
0
|
|
|
|
|
|
open COOKIEFILE, ">>$cookiesFile"; |
277
|
0
|
|
|
|
|
|
print COOKIEFILE "$cookie $id ".time()."\n"; |
278
|
0
|
|
|
|
|
|
close COOKIEFILE; |
279
|
0
|
0
|
|
|
|
|
if (not chmod(0600, "$cookiesFile")) { |
280
|
0
|
|
|
|
|
|
FAQ::OMatic::gripe('problem', "chmod failed on $cookiesFile"); |
281
|
|
|
|
|
|
|
} |
282
|
|
|
|
|
|
|
|
283
|
0
|
|
|
|
|
|
return $cookie; |
284
|
|
|
|
|
|
|
} |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
sub findCookie { |
287
|
0
|
|
|
0
|
0
|
|
my $match = shift; |
288
|
0
|
|
|
|
|
|
my $by = shift; |
289
|
|
|
|
|
|
|
|
290
|
0
|
|
|
|
|
|
my ($cookie,$cid,$ctime); |
291
|
0
|
0
|
|
|
|
|
if (not open COOKIEFILE, "<$FAQ::OMatic::Config::metaDir/cookies") { |
292
|
0
|
|
|
|
|
|
return undef; |
293
|
|
|
|
|
|
|
} |
294
|
0
|
|
|
|
|
|
while (defined($_=)) { |
295
|
0
|
|
|
|
|
|
chomp; |
296
|
0
|
|
|
|
|
|
($cookie,$cid,$ctime) = split(' '); |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
# ignore dead cookies |
299
|
0
|
|
0
|
|
|
|
my $cookieActual = FAQ::OMatic::getLocal('cookieActual') |
300
|
|
|
|
|
|
|
|| $FAQ::OMatic::Config::cookieLife |
301
|
|
|
|
|
|
|
|| 3600; |
302
|
0
|
0
|
|
|
|
|
next if ((time() - $ctime) > $cookieActual); |
303
|
|
|
|
|
|
|
|
304
|
0
|
0
|
0
|
|
|
|
if (($by eq 'id') and ($cid eq $match)) { |
305
|
0
|
|
|
|
|
|
close COOKIEFILE; |
306
|
0
|
|
|
|
|
|
return ($cookie,$cid,$ctime); |
307
|
|
|
|
|
|
|
} |
308
|
0
|
0
|
0
|
|
|
|
if (($by eq 'cookie') and ($cookie eq $match)) { |
309
|
0
|
|
|
|
|
|
close COOKIEFILE; |
310
|
0
|
|
|
|
|
|
return ($cookie,$cid,$ctime); |
311
|
|
|
|
|
|
|
} |
312
|
|
|
|
|
|
|
} |
313
|
0
|
|
|
|
|
|
close COOKIEFILE; |
314
|
0
|
|
|
|
|
|
return undef; |
315
|
|
|
|
|
|
|
} |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
# these functions manipulate a file that maps IDs to |
318
|
|
|
|
|
|
|
# (ID,password,...) tuples. (... = future expansion) |
319
|
|
|
|
|
|
|
# Right now it's a flat file, but maybe someday it should be a |
320
|
|
|
|
|
|
|
# dbm file if anyone ever has zillions of authorized posters. |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
# given an ($id,$password,...) array, writes it into idfile |
323
|
|
|
|
|
|
|
sub writeIDfile { |
324
|
0
|
|
|
0
|
0
|
|
my ($id,$password,@rest) = @_; |
325
|
|
|
|
|
|
|
|
326
|
0
|
|
|
|
|
|
my $lockf = FAQ::OMatic::lockFile("idfile"); |
327
|
0
|
0
|
|
|
|
|
FAQ::OMatic::gripe('error', "idfile is locked.") if (not $lockf); |
328
|
|
|
|
|
|
|
|
329
|
0
|
0
|
|
|
|
|
if (not open(IDFILE, "<$FAQ::OMatic::Config::metaDir/idfile")) { |
330
|
0
|
|
|
|
|
|
FAQ::OMatic::unlockFile($lockf); |
331
|
0
|
|
|
|
|
|
FAQ::OMatic::gripe('abort', "FAQ::OMatic::Auth::writeIDfile: Couldn't " |
332
|
|
|
|
|
|
|
."read $FAQ::OMatic::Config::metaDir/idfile because $!"); |
333
|
0
|
|
|
|
|
|
return; |
334
|
|
|
|
|
|
|
} |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
# read id mappings in |
337
|
0
|
|
|
|
|
|
my %idmap; |
338
|
0
|
|
|
|
|
|
my ($idf,$passf,@restf); |
339
|
0
|
|
|
|
|
|
while (defined($_=)) { |
340
|
0
|
|
|
|
|
|
chomp; |
341
|
0
|
|
|
|
|
|
($idf,$passf,@restf) = split(' '); |
342
|
0
|
|
|
|
|
|
$idmap{$idf} = $_; |
343
|
|
|
|
|
|
|
} |
344
|
0
|
|
|
|
|
|
close IDFILE; |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
# change the mapping for id |
347
|
0
|
|
|
|
|
|
$idmap{$id} = join(' ', $id, $password, @rest); |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
# write id mappings. |
350
|
0
|
0
|
|
|
|
|
if (not open(IDFILE, ">$FAQ::OMatic::Config::metaDir/idfile-new")) { |
351
|
0
|
|
|
|
|
|
FAQ::OMatic::unlockFile($lockf); |
352
|
0
|
|
|
|
|
|
FAQ::OMatic::gripe('abort', "FAQ::OMatic::Auth::writeIDfile: Couldn't " |
353
|
|
|
|
|
|
|
."write $FAQ::OMatic::Config::metaDir/idfile-new because $!"); |
354
|
0
|
|
|
|
|
|
return; |
355
|
|
|
|
|
|
|
} |
356
|
|
|
|
|
|
|
|
357
|
0
|
|
|
|
|
|
foreach $idf (sort keys %idmap) { |
358
|
0
|
|
|
|
|
|
print IDFILE $idmap{$idf}."\n"; |
359
|
|
|
|
|
|
|
} |
360
|
0
|
|
|
|
|
|
close IDFILE; |
361
|
|
|
|
|
|
|
|
362
|
0
|
0
|
|
|
|
|
unlink("$FAQ::OMatic::Config::metaDir/idfile") or |
363
|
|
|
|
|
|
|
FAQ::OMatic::gripe('abort', "FAQ::OMatic::Auth::writeIDfile: Couldn't " |
364
|
|
|
|
|
|
|
."unlink $FAQ::OMatic::Config::metaDir/idfile because $!"); |
365
|
0
|
0
|
|
|
|
|
rename("$FAQ::OMatic::Config::metaDir/idfile-new", "$FAQ::OMatic::Config::metaDir/idfile") or |
366
|
|
|
|
|
|
|
FAQ::OMatic::gripe('abort', "FAQ::OMatic::Auth::writeIDfile: Couldn't " |
367
|
|
|
|
|
|
|
."rename $FAQ::OMatic::Config::metaDir/idfile-new to idfile because $!"); |
368
|
0
|
0
|
|
|
|
|
chmod 0600, "$FAQ::OMatic::Config::metaDir/idfile" or |
369
|
|
|
|
|
|
|
FAQ::OMatic::gripe('problem', "FAQ::OMatic::Auth::writeIDfile: Couldn't " |
370
|
|
|
|
|
|
|
."chmod $FAQ::OMatic::Config::metaDir/idfile because $!"); |
371
|
|
|
|
|
|
|
|
372
|
0
|
|
|
|
|
|
FAQ::OMatic::unlockFile($lockf); |
373
|
|
|
|
|
|
|
} |
374
|
|
|
|
|
|
|
|
375
|
|
|
|
|
|
|
# given an id, returns an array starting ($id,$password,...) |
376
|
|
|
|
|
|
|
sub readIDfile { |
377
|
0
|
|
0
|
0
|
0
|
|
my $id = shift || ''; # key to lookup on |
378
|
0
|
|
0
|
|
|
|
my $dontHideVersion = shift || ''; |
379
|
|
|
|
|
|
|
# keep regular lookups from seeing version number |
380
|
|
|
|
|
|
|
# record. (smacks of a hack, but this is Perl!) |
381
|
|
|
|
|
|
|
|
382
|
0
|
0
|
0
|
|
|
|
return undef if (($id eq 'version') and (not $dontHideVersion)); |
383
|
|
|
|
|
|
|
|
384
|
0
|
|
|
|
|
|
my $lockf = FAQ::OMatic::lockFile("idfile"); |
385
|
0
|
0
|
|
|
|
|
FAQ::OMatic::gripe('error', "idfile is locked.") if (not $lockf); |
386
|
|
|
|
|
|
|
|
387
|
0
|
0
|
|
|
|
|
if (not open(IDFILE, "<$FAQ::OMatic::Config::metaDir/idfile")) { |
388
|
0
|
|
|
|
|
|
FAQ::OMatic::unlockFile($lockf); |
389
|
0
|
|
|
|
|
|
FAQ::OMatic::gripe('abort', "FAQ::OMatic::Auth::readIDfile: Couldn't " |
390
|
|
|
|
|
|
|
."read $FAQ::OMatic::Config::metaDir/idfile because $!"); |
391
|
0
|
|
|
|
|
|
return undef; |
392
|
|
|
|
|
|
|
} |
393
|
|
|
|
|
|
|
|
394
|
0
|
|
|
|
|
|
my ($idf,$passf,@restf); |
395
|
0
|
|
|
|
|
|
while (defined($_=)) { |
396
|
0
|
|
|
|
|
|
chomp; |
397
|
0
|
|
|
|
|
|
($idf,$passf,@restf) = split(' '); |
398
|
0
|
0
|
|
|
|
|
last if ($idf eq $id); |
399
|
|
|
|
|
|
|
} |
400
|
0
|
|
|
|
|
|
close IDFILE; |
401
|
|
|
|
|
|
|
|
402
|
0
|
|
|
|
|
|
FAQ::OMatic::unlockFile($lockf); |
403
|
|
|
|
|
|
|
|
404
|
0
|
0
|
0
|
|
|
|
if (defined($idf) and ($idf eq $id)) { |
405
|
0
|
|
|
|
|
|
return ($idf,$passf,@restf); |
406
|
|
|
|
|
|
|
} |
407
|
|
|
|
|
|
|
|
408
|
0
|
|
|
|
|
|
return undef; |
409
|
|
|
|
|
|
|
} |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
sub checkCryptPass { |
412
|
0
|
|
|
0
|
0
|
|
my ($cleartext, $crypted) = @_; |
413
|
0
|
0
|
|
|
|
|
if ($crypted =~ m/md5\((\S+),(\S+)\)/) { |
414
|
|
|
|
|
|
|
# if this record was encoded with the new md5 encoding, then |
415
|
|
|
|
|
|
|
# it'll contain a big salt and then the result: |
416
|
0
|
|
|
|
|
|
my $salt = $1; |
417
|
0
|
|
|
|
|
|
my $cryptedResult = $2; |
418
|
0
|
|
|
|
|
|
my $attemptedCrypt = md5_hex($salt, $cleartext); |
419
|
0
|
|
|
|
|
|
return ($attemptedCrypt eq $cryptedResult); |
420
|
|
|
|
|
|
|
} else { |
421
|
|
|
|
|
|
|
# compatibility mode: use crypt() |
422
|
|
|
|
|
|
|
# We no longer generate passwords with crypt, but we |
423
|
|
|
|
|
|
|
# allow checking against crypt()ed passwords to avoid |
424
|
|
|
|
|
|
|
# annoying users with a password-reset demand. |
425
|
|
|
|
|
|
|
#my $salt = substr($crypted, 0, 2); |
426
|
|
|
|
|
|
|
# specific fix from Evan Torrie : most crypt()s |
427
|
|
|
|
|
|
|
# don't care of there's excess salt, and those with MD5 crypts use |
428
|
|
|
|
|
|
|
# more than the first two bytes as salt. |
429
|
0
|
|
|
|
|
|
my $salt = $crypted; |
430
|
0
|
|
|
|
|
|
return (crypt($cleartext, $salt) eq $crypted); |
431
|
|
|
|
|
|
|
} |
432
|
|
|
|
|
|
|
} |
433
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
sub cryptPass { |
435
|
0
|
|
|
0
|
0
|
|
my $pass = shift; |
436
|
0
|
|
|
|
|
|
my $salt = FAQ::OMatic::Entropy::gatherRandomString(); |
437
|
0
|
|
|
|
|
|
return "md5(".$salt.",".md5_hex($salt.$pass).")"; |
438
|
|
|
|
|
|
|
} |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
sub authenticate { |
441
|
0
|
|
|
0
|
0
|
|
my $params = shift; |
442
|
|
|
|
|
|
|
|
443
|
0
|
|
|
|
|
|
my $auth = $params->{'auth'}; |
444
|
|
|
|
|
|
|
|
445
|
|
|
|
|
|
|
# if there's a cookie... |
446
|
0
|
0
|
|
|
|
|
if ($auth =~ m/^ck/) { |
447
|
0
|
|
|
|
|
|
my ($cookie,$cid,$ctime) = findCookie($auth,'cookie'); |
448
|
|
|
|
|
|
|
# and it's good, then return the implied id |
449
|
0
|
0
|
|
|
|
|
return ($cid,5) if (defined $cid); |
450
|
|
|
|
|
|
|
# if it's bad, fall through and inherit anonymous auth |
451
|
|
|
|
|
|
|
} |
452
|
|
|
|
|
|
|
|
453
|
|
|
|
|
|
|
# if we authenticate... |
454
|
0
|
0
|
0
|
|
|
|
if (($params->{'auth'}||'') eq 'pass' or |
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
455
|
|
|
|
|
|
|
((($params->{'_none_id'}||'') eq '') |
456
|
|
|
|
|
|
|
and (($params->{'_pass_id'}||'') ne ''))) { |
457
|
0
|
|
|
|
|
|
my $id = $params->{'_pass_id'}; |
458
|
0
|
|
|
|
|
|
my $pass = $params->{'_pass_pass'}; |
459
|
0
|
0
|
|
|
|
|
if (FAQ::OMatic::AuthLocal::checkPassword($id, $pass)) { |
460
|
|
|
|
|
|
|
# set up a cookie to use for a shortcut later, |
461
|
|
|
|
|
|
|
# and return the authentication pair |
462
|
0
|
|
|
|
|
|
$params->{'auth'} = newCookie($id); |
463
|
0
|
|
|
|
|
|
return ($id,5); |
464
|
|
|
|
|
|
|
} else { |
465
|
|
|
|
|
|
|
# let authenticate know to report the bad password |
466
|
0
|
|
|
|
|
|
$params->{'badPass'} = 1; |
467
|
|
|
|
|
|
|
# remove the password from the parameters, since |
468
|
|
|
|
|
|
|
# we don't want it ending up in a later GET request |
469
|
|
|
|
|
|
|
# (and then in server logs). (It got here by a POST |
470
|
|
|
|
|
|
|
# from the password form.) |
471
|
0
|
|
|
|
|
|
$params->{'_pass_id'} = ''; |
472
|
0
|
|
|
|
|
|
$params->{'_pass_pass'} = ''; |
473
|
|
|
|
|
|
|
# fall through to inherit some crummier Authentication Quality |
474
|
|
|
|
|
|
|
} |
475
|
|
|
|
|
|
|
} |
476
|
|
|
|
|
|
|
|
477
|
0
|
0
|
0
|
|
|
|
if (($params->{'auth'} eq 'none') |
478
|
|
|
|
|
|
|
and (defined $params->{'_none_id'})) { |
479
|
|
|
|
|
|
|
# move id where we can pass it around |
480
|
0
|
|
|
|
|
|
$params->{'id'} = $params->{'_none_id'}; |
481
|
|
|
|
|
|
|
} |
482
|
|
|
|
|
|
|
|
483
|
|
|
|
|
|
|
# default authentication: whatever id we can come up with, |
484
|
|
|
|
|
|
|
# but quality is at most 3 |
485
|
0
|
|
0
|
|
|
|
my $id = $params->{'id'} || 'anonymous'; |
486
|
0
|
0
|
|
|
|
|
my $aq = $params->{'id'} ? 3 : 1; |
487
|
0
|
|
|
|
|
|
return ($id, $aq); |
488
|
|
|
|
|
|
|
} |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
sub authError { |
491
|
0
|
|
|
0
|
0
|
|
my $reason = shift; |
492
|
0
|
|
|
|
|
|
my $file = shift; |
493
|
|
|
|
|
|
|
|
494
|
0
|
|
|
|
|
|
my %staticErrors = ( |
495
|
|
|
|
|
|
|
9 => gettext("the administrator of this Faq-O-Matic"), |
496
|
|
|
|
|
|
|
5 => gettext("someone who has proven their identification"), |
497
|
|
|
|
|
|
|
3 => gettext("someone who has offered identification"), |
498
|
|
|
|
|
|
|
1 => gettext("anybody") ); |
499
|
|
|
|
|
|
|
|
500
|
0
|
0
|
|
|
|
|
return $staticErrors{$reason} if ($staticErrors{$reason}); |
501
|
|
|
|
|
|
|
|
502
|
0
|
0
|
|
|
|
|
if ($reason eq '7') { |
503
|
0
|
|
|
|
|
|
my $modname = ''; |
504
|
0
|
0
|
0
|
|
|
|
if (defined($file) |
505
|
|
|
|
|
|
|
&& ($file ne '')) { |
506
|
|
|
|
|
|
|
# THANKS "Alan J. Flavell" |
507
|
|
|
|
|
|
|
# for fixing a "Use of uninitialized value" here. |
508
|
0
|
|
|
|
|
|
my $item = new FAQ::OMatic::Item($file); |
509
|
0
|
|
|
|
|
|
$modname = " (".getInheritedProperty($item, 'Moderator').")"; |
510
|
|
|
|
|
|
|
} |
511
|
0
|
|
|
|
|
|
return gettext("the moderator of the item").$modname; |
512
|
|
|
|
|
|
|
} |
513
|
|
|
|
|
|
|
|
514
|
0
|
0
|
|
|
|
|
if ($reason =~ m/^6/) { |
515
|
0
|
|
|
|
|
|
return gettexta("%0 group members",FAQ::OMatic::Groups::groupCodeToName($reason)); |
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
|
518
|
0
|
|
|
|
|
|
return "I don't know who"; |
519
|
|
|
|
|
|
|
} |
520
|
|
|
|
|
|
|
|
521
|
|
|
|
|
|
|
1; |
522
|
|
|
|
|
|
|
|