Mercurial > noffle
comparison src/authenticate.c @ 288:c02c4eb95f95 noffle
[svn] * src/configfile.h,src/configfile.c,docs/noffle.conf.5: Add noffle-user
and noffle-group configs.
* src/configfile.c,src/fetch.c,src/fetchlist.c,src/protocol.c,
src/server.c: Replace strcpy() with Utl_cpyStr() where appropriate.
See Debian bug 168128.
* src/control.c,src/configfile.c,src/noffle.c: Replace [s]scanf("%s")
with [s]scanf(MAXCHAR_FMT).
* src/noffle.c: Log warning if noffle.conf is world readable.
* src/noffle.c: Restrict most options to news admins; i.e. those who
are root or news on running Noffle.
* Makefile.in,acconfig.h,aclocal.m4,config.h.in,configure,configure.in,
docs/Makefile.in,docs/noffle.conf.5,packages/Makefile.in,
packages/redhat/Makefile.in,src/Makefile.am,src/Makefile.in,
src/authenticate.c,src/authenticate.h,src/noffle.c,src/server.c:
Add basic authentication using either Noffle-specific user file
or authenticating via PAM (service 'noffle'). PAM authentication
needs to run as root, so a Noffle server that needs PAM
must be started by root. Helpful (?) error messages will be logged
if not. Noffle will switch ruid and euid to 'news' (or whatever
is configured) ASAP.
* src/noffle.c: Add uid checking.
author | bears |
---|---|
date | Fri, 10 Jan 2003 23:25:45 +0000 |
parents | |
children | bf200dccbce5 |
comparison
equal
deleted
inserted
replaced
287:01755687c565 | 288:c02c4eb95f95 |
---|---|
1 /* | |
2 authenticate.c | |
3 | |
4 Do client authentication | |
5 | |
6 $Id: authenticate.c 420 2003-01-10 23:25:45Z bears $ | |
7 */ | |
8 | |
9 #if HAVE_CONFIG_H | |
10 #include <config.h> | |
11 #endif | |
12 | |
13 #include <errno.h> | |
14 #include <grp.h> | |
15 #include <pwd.h> | |
16 #include <stdio.h> | |
17 #include <string.h> | |
18 #include <sys/types.h> | |
19 #include <sys/stat.h> | |
20 #include <unistd.h> | |
21 #include "common.h" | |
22 #include "authenticate.h" | |
23 #include "configfile.h" | |
24 #include "log.h" | |
25 #include "portable.h" | |
26 #include "util.h" | |
27 | |
28 #if USE_AUTH | |
29 | |
30 #if USE_PAM | |
31 #include <security/pam_appl.h> | |
32 | |
33 static const char *password; | |
34 | |
35 /* | |
36 * It's a bit tricky to go around asking PAM questions at this stage, | |
37 * as well as not fitting NNTP, so just repond to all PAM questions | |
38 * with the password and hope that works. | |
39 */ | |
40 static int noffle_conv( int num_msg, | |
41 const struct pam_message **msgm, | |
42 struct pam_response **response, | |
43 void *appdata_ptr ) | |
44 { | |
45 struct pam_response *reply; | |
46 | |
47 UNUSED(appdata_ptr); | |
48 UNUSED(msgm); | |
49 | |
50 reply = calloc( num_msg, sizeof (struct pam_response) ); | |
51 reply->resp = strdup( password ); | |
52 reply->resp_retcode = 0; | |
53 *response = reply; | |
54 return PAM_SUCCESS; | |
55 } | |
56 | |
57 static struct pam_conv conv = { | |
58 noffle_conv, | |
59 NULL | |
60 }; | |
61 | |
62 static pam_handle_t *pamh = NULL; | |
63 static Bool pam_session_opened = FALSE; | |
64 static Bool pam_set_cred = FALSE; | |
65 static uid_t oldEuid; | |
66 | |
67 static Bool | |
68 PAM_open( void ) | |
69 { | |
70 int retval; | |
71 | |
72 /* To use PAM successfully we need to be root. */ | |
73 ASSERT ( getuid() == 0 ); | |
74 | |
75 ASSERT( pamh == NULL ); | |
76 | |
77 /* | |
78 * Preserve old eUid to be restored when PAM closes and set | |
79 * current euid to root for PAMs benefit. | |
80 */ | |
81 oldEuid = geteuid(); | |
82 if ( seteuid( 0 ) < 0 ) | |
83 { | |
84 Log_err( "Cannot set euid to root: %s", strerror( errno ) ); | |
85 return FALSE; | |
86 } | |
87 | |
88 retval = pam_start( "noffle", NULL, &conv, &pamh ); | |
89 if ( retval != PAM_SUCCESS ) | |
90 { | |
91 Log_err( "Cannot starting authentication: %s", | |
92 pam_strerror( pamh, retval ) ); | |
93 return FALSE; | |
94 } | |
95 | |
96 return TRUE; | |
97 } | |
98 | |
99 static enum AuthResult | |
100 PAM_authenticate( const char *user, const char *pass ) | |
101 { | |
102 int retval; | |
103 | |
104 ASSERT( pamh != NULL ); | |
105 | |
106 password = pass; | |
107 | |
108 retval = pam_set_item( pamh, PAM_USER, user ); | |
109 if ( retval != PAM_SUCCESS ) | |
110 Log_dbg( LOG_DBG_AUTH, "pam_set_item failed: %s", | |
111 pam_strerror( pamh, retval ) ); | |
112 | |
113 if ( retval == PAM_SUCCESS ) | |
114 { | |
115 retval = pam_authenticate( pamh, PAM_SILENT ); | |
116 if ( retval != PAM_SUCCESS ) | |
117 Log_dbg( LOG_DBG_AUTH, "pam_authenticate failed: %s", | |
118 pam_strerror( pamh, retval ) ); | |
119 } | |
120 | |
121 if ( retval == PAM_SUCCESS ) | |
122 { | |
123 retval = pam_setcred( pamh, PAM_ESTABLISH_CRED ); | |
124 if ( retval != PAM_SUCCESS ) | |
125 Log_dbg( LOG_DBG_AUTH, "pam_setcred failed: %s", | |
126 pam_strerror( pamh, retval ) ); | |
127 else | |
128 pam_set_cred = TRUE; | |
129 } | |
130 | |
131 if ( retval == PAM_SUCCESS ) | |
132 { | |
133 retval = pam_open_session( pamh, 0 ); | |
134 if ( retval != PAM_SUCCESS ) | |
135 Log_dbg( LOG_DBG_AUTH, "pam_open_session failed: %s", | |
136 pam_strerror( pamh, retval ) ); | |
137 else | |
138 pam_session_opened = TRUE; | |
139 } | |
140 | |
141 switch ( retval ) | |
142 { | |
143 case PAM_SUCCESS: | |
144 return AUTH_OK; | |
145 | |
146 case PAM_MAXTRIES: | |
147 return AUTH_DISCONNECT; | |
148 | |
149 case PAM_ABORT: | |
150 return AUTH_ERROR; | |
151 } | |
152 | |
153 return AUTH_FAILED; | |
154 } | |
155 | |
156 static void | |
157 PAM_close( void ) | |
158 { | |
159 int retval = 0; | |
160 | |
161 ASSERT ( pamh != NULL ); | |
162 | |
163 if ( pam_session_opened ) | |
164 { | |
165 pam_session_opened = FALSE; | |
166 retval = pam_close_session( pamh, 0 ); | |
167 if ( retval != PAM_SUCCESS ) | |
168 Log_dbg( LOG_DBG_AUTH, "pam_close_session failed: %s", | |
169 pam_strerror( pamh, retval ) ); | |
170 } | |
171 | |
172 if ( pam_set_cred ) | |
173 { | |
174 pam_set_cred = FALSE; | |
175 retval = pam_setcred( pamh, PAM_DELETE_CRED ); | |
176 if ( retval != PAM_SUCCESS ) | |
177 Log_dbg( LOG_DBG_AUTH, "pam_set_cred failed: %s", | |
178 pam_strerror( pamh, retval ) ); | |
179 } | |
180 | |
181 retval = pam_end( pamh, retval ); | |
182 if ( retval != PAM_SUCCESS ) | |
183 Log_dbg( LOG_DBG_AUTH, "pam_end failed: %s", | |
184 pam_strerror( pamh, retval ) ); | |
185 pamh = NULL; | |
186 | |
187 /* | |
188 * For completeness set euid back to original value, though it'll | |
189 * probably be set again by Auth_dropPrivs. | |
190 */ | |
191 if ( seteuid( oldEuid ) < 0 ) | |
192 Log_err( "Cannot set euid back to %d: %s", | |
193 oldEuid, strerror( errno ) ); | |
194 } | |
195 | |
196 #else | |
197 | |
198 /* | |
199 * No PAM, so provide a simple alternative. | |
200 * | |
201 * USERSFILE is a simple plain-text file consisting of username password | |
202 * pairs, one pair per line. Comments are prefixed by '#'. Blank lines | |
203 * are ignored. | |
204 * | |
205 * By way of a simple security check, the users file MUST be only | |
206 * readable and writable by the owner. | |
207 */ | |
208 | |
209 #define AUTH_MAX_TRIES 3 | |
210 | |
211 static int authTries = 0; | |
212 | |
213 static enum AuthResult | |
214 file_authenticate( const char *user, const char *pass ) | |
215 { | |
216 Str file, line; | |
217 FILE *f; | |
218 struct stat statBuf; | |
219 enum AuthResult res = AUTH_FAILED; | |
220 | |
221 Utl_cpyStr( file, USERSFILE ); | |
222 if ( stat( file, &statBuf ) < 0 ) | |
223 { | |
224 Log_err( "Cannot read %s (%s)", file, strerror( errno ) ); | |
225 return AUTH_ERROR; | |
226 } | |
227 if ( !S_ISREG( statBuf.st_mode ) ) | |
228 { | |
229 Log_err( "%s must be a regular file, not a link", file ); | |
230 return AUTH_ERROR; | |
231 } | |
232 if ( ( statBuf.st_mode & ( S_IRWXG | S_IRWXO ) ) != 0 ) | |
233 { | |
234 Log_err( "%s must be readable only by its owner", file ); | |
235 return AUTH_ERROR; | |
236 } | |
237 | |
238 if ( ! ( f = fopen( file, "r" ) ) ) | |
239 { | |
240 Log_err( "Cannot read %s (%s)", file, strerror( errno ) ); | |
241 return AUTH_ERROR; | |
242 } | |
243 while ( res == AUTH_FAILED && fgets( line, MAXCHAR, f ) ) | |
244 { | |
245 Str theUser, thePass; | |
246 char *p; | |
247 | |
248 p = Utl_stripWhiteSpace( line ); | |
249 Utl_stripComment( p ); | |
250 | |
251 if ( *p == '\0' ) | |
252 continue; | |
253 | |
254 if ( sscanf( p, MAXCHAR_FMT " " MAXCHAR_FMT, theUser, thePass ) != 2 ) | |
255 { | |
256 res = AUTH_ERROR; | |
257 Log_err( "Badly formatted line %s in %s", p, file ); | |
258 break; | |
259 } | |
260 | |
261 if ( strcmp( user, theUser ) == 0 ) | |
262 { | |
263 if ( strcmp( pass, thePass ) == 0 ) | |
264 res = AUTH_OK; | |
265 break; | |
266 } | |
267 } | |
268 | |
269 fclose( f ); | |
270 | |
271 if ( res == AUTH_FAILED ) | |
272 { | |
273 authTries++; | |
274 sleep( authTries * authTries ); | |
275 if ( authTries >= AUTH_MAX_TRIES ) | |
276 res = AUTH_DISCONNECT; | |
277 } | |
278 | |
279 return res; | |
280 } | |
281 | |
282 #endif /* USE_PAM */ | |
283 #endif /* USE_AUTH */ | |
284 | |
285 /* Open authentication session. */ | |
286 Bool | |
287 Auth_open( void ) | |
288 { | |
289 #if USE_AUTH | |
290 #if USE_PAM | |
291 return PAM_open(); | |
292 #else | |
293 return TRUE; | |
294 #endif | |
295 #else | |
296 return TRUE; | |
297 #endif | |
298 } | |
299 | |
300 /* Authenticate a user and password. */ | |
301 enum AuthResult | |
302 Auth_authenticate( const char *user, const char *pass ) | |
303 { | |
304 #if USE_AUTH | |
305 #if USE_PAM | |
306 return PAM_authenticate( user, pass ); | |
307 #else | |
308 return file_authenticate( user, pass ); | |
309 #endif | |
310 #else | |
311 UNUSED(user); | |
312 UNUSED(pass); | |
313 | |
314 return TRUE; | |
315 #endif | |
316 } | |
317 | |
318 /* Authentication session now closed. */ | |
319 void | |
320 Auth_close( void ) | |
321 { | |
322 #if USE_AUTH && USE_PAM | |
323 PAM_close(); | |
324 #endif | |
325 } | |
326 | |
327 static uid_t noffleUid = (uid_t) -1; | |
328 static gid_t noffleGid= (gid_t) -1; | |
329 static Bool adminUser = FALSE; | |
330 | |
331 /* Check we have appropriate privs for authentication. */ | |
332 Bool | |
333 Auth_checkPrivs( void ) | |
334 { | |
335 uid_t euid; | |
336 gid_t egid; | |
337 uid_t ruid; | |
338 struct passwd* pwnam; | |
339 struct group* grnam; | |
340 | |
341 euid = geteuid(); | |
342 egid = getegid(); | |
343 | |
344 pwnam = getpwnam( Cfg_noffleUser() ); | |
345 if ( pwnam == NULL ) | |
346 { | |
347 Log_err( "Noffle user %s is not a known user", Cfg_noffleUser() ); | |
348 return FALSE; | |
349 } | |
350 noffleUid = pwnam->pw_uid; | |
351 | |
352 grnam = getgrnam( Cfg_noffleGroup() ); | |
353 if ( grnam == NULL ) | |
354 { | |
355 Log_err( "Noffle group %s is not a known group", Cfg_noffleGroup() ); | |
356 return FALSE; | |
357 } | |
358 noffleGid = grnam->gr_gid; | |
359 | |
360 ruid = getuid(); | |
361 adminUser = ( ruid == 0 || ruid == noffleUid ); | |
362 | |
363 /* | |
364 * If we're really root, we will set the privs we require later. Otherwise | |
365 * we need to check that everything is as it should be. | |
366 */ | |
367 if ( ruid != 0 ) | |
368 { | |
369 #if USE_AUTH && USE_PAM | |
370 if( Cfg_needClientAuth() ) | |
371 { | |
372 Log_err( "Noffle must run as root to use PAM authentication" ); | |
373 return FALSE; | |
374 } | |
375 #endif | |
376 | |
377 if ( noffleUid != euid ) | |
378 { | |
379 Log_err( "Noffle needs to run as root or user %s", Cfg_noffleUser() ); | |
380 return FALSE; | |
381 } | |
382 | |
383 if ( noffleGid != egid ) | |
384 { | |
385 Log_err( "Noffle needs to run as root or as group %s", | |
386 Cfg_noffleGroup() ); | |
387 return FALSE; | |
388 } | |
389 } | |
390 | |
391 return TRUE; | |
392 } | |
393 | |
394 /* | |
395 * See if we should be permitted admin access. Admins can do anything, | |
396 * non-admins can only read articles, list groups and post. | |
397 * | |
398 * This must be called after Auth_checkPrivs. | |
399 */ | |
400 Bool | |
401 Auth_admin( void ) | |
402 { | |
403 ASSERT( noffleUid != (uid_t) -1 && noffleGid != (gid_t) -1 ); | |
404 | |
405 return adminUser; | |
406 } | |
407 | |
408 | |
409 /* | |
410 * Drop any privs required for authentication. | |
411 * | |
412 * Must be called AFTER Auth_checkPrivs. | |
413 */ | |
414 Bool | |
415 Auth_dropPrivs( void ) | |
416 { | |
417 uid_t euid; | |
418 | |
419 ASSERT( noffleUid != (uid_t) -1 && noffleGid != (gid_t) -1 ); | |
420 | |
421 /* | |
422 * We only need to drop privs if we're currently root. We | |
423 * should have already checked we're the news user on startup. | |
424 */ | |
425 euid = geteuid(); | |
426 if ( euid != 0 ) | |
427 return TRUE; | |
428 | |
429 if ( setgid( noffleGid ) != 0 ) | |
430 { | |
431 Log_err( "Can't set group %s: %s", | |
432 Cfg_noffleGroup(), strerror( errno ) ); | |
433 return FALSE; | |
434 } | |
435 | |
436 if ( setuid( noffleUid ) != 0 ) | |
437 { | |
438 Log_err( "Can't set user to %s: %s", | |
439 Cfg_noffleUser(), strerror( errno ) ); | |
440 return FALSE; | |
441 } | |
442 | |
443 return TRUE; | |
444 } | |
445 |