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