c3311e445064c8749ab83f990d3f6548dac1f1fc
[freeradius.git] / src / modules / rlm_unix / rlm_unix.c
1 /*
2  * rlm_unix.c   authentication: Unix user authentication
3  *              accounting:     Functions to write radwtmp file.
4  *              Also contains handler for "Group".
5  *
6  * Version:     $Id$
7  *
8  */
9 static const char rcsid[] = "$Id$";
10
11 #include        "autoconf.h"
12
13 #include        <stdlib.h>
14 #include        <string.h>
15 #include        <grp.h>
16 #include        <pwd.h>
17
18 #include "config.h"
19
20 #if HAVE_MALLOC_H
21 #  include <malloc.h>
22 #endif
23
24 #if HAVE_SHADOW_H
25 #  include      <shadow.h>
26 #endif
27
28 #if HAVE_CRYPT_H
29 #  include <crypt.h>
30 #endif
31
32 #ifdef OSFC2
33 #  include      <sys/security.h>
34 #  include      <prot.h>
35 #endif
36
37 #include        "radiusd.h"
38 #include        "modules.h"
39 #include        "sysutmp.h"
40 #include        "cache.h"
41 #include        "conffile.h"
42
43 static char trans[64] =
44    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
45 #define ENC(c) trans[c]
46
47 /*
48  *      Cache the password by default.
49  */
50 static int cache_passwd = TRUE;
51 static char *passwd_file = NULL;
52 static char *shadow_file = NULL;
53 static char *group_file = NULL;
54
55 static CONF_PARSER module_config[] = {
56         { "cache",  PW_TYPE_BOOLEAN,    &cache_passwd, "yes" },
57         { "passwd", PW_TYPE_STRING_PTR, &passwd_file,  NULL },
58         { "shadow", PW_TYPE_STRING_PTR, &shadow_file,  NULL },
59         { "group",  PW_TYPE_STRING_PTR, &group_file,   NULL },
60         
61         { NULL, -1, NULL, NULL }                /* end the list */
62 };
63
64 /*
65  *      The Group = handler.
66  */
67 static int groupcmp(VALUE_PAIR *request, VALUE_PAIR *check,
68         VALUE_PAIR *check_pairs, VALUE_PAIR **reply_pairs)
69 {
70         struct passwd   *pwd;
71         struct group    *grp;
72         char            **member;
73         char            *username;
74         int             retval;
75         check_pairs = check_pairs; reply_pairs = reply_pairs;
76
77         username = (char *)request->strvalue;
78
79         if (cache_passwd && (retval = H_groupcmp(check, username)) != -2)
80                 return retval;
81
82         if ((pwd = getpwnam(username)) == NULL)
83                 return -1;
84
85         if ((grp = getgrnam((char *)check->strvalue)) == NULL)
86                 return -1;
87
88         retval = (pwd->pw_gid == grp->gr_gid) ? 0 : -1;
89         if (retval < 0) {
90                 for (member = grp->gr_mem; *member && retval; member++) {
91                         if (strcmp(*member, pwd->pw_name) == 0)
92                                 retval = 0;
93                 }
94         }
95         return retval;
96 }
97
98
99 /*
100  *      FIXME:  We really should have an 'init' which makes
101  *      System auth == Unix
102  */
103 static int unix_init(void)
104 {
105         paircompare_register(PW_GROUP, PW_USER_NAME, groupcmp);
106 #ifdef PW_GROUP_NAME /* compat */
107         paircompare_register(PW_GROUP_NAME, PW_USER_NAME, groupcmp);
108 #endif
109         return 0;
110 }
111
112 static int unix_instantiate(CONF_SECTION *conf, void **instance)
113 {
114         /*
115          *      Not yet multiple-instance-aware. groupcmp is a real
116          *      obstacle.
117          */
118         static int alreadydone=0;
119
120         if (alreadydone) {
121                 radlog(L_ERR,
122                     "rlm_unix: can't handle multiple authentication instances");
123                 return -1;
124         }
125         if (cf_section_parse(conf, module_config) < 0) {
126                 return -1;
127         }
128
129         if (cache_passwd) {
130                 radlog(L_INFO, "HASH:  Reinitializing hash structures "
131                         "and lists for caching...");
132                 if (unix_buildHashTable(passwd_file, shadow_file) < 0) {
133                         radlog(L_ERR, "HASH:  unable to create user "
134                                 "hash table.  disable caching and run debugs");
135                         return -1;
136                 }
137                 if (unix_buildGrpList() < 0) {
138                         radlog(L_ERR, "HASH:  unable to cache groups file.  "
139                                 "disable caching and run debugs");
140                         return -1;
141                 }
142         }
143
144         alreadydone = 1;
145         *instance = 0;
146         return 0;
147 }
148
149 /*
150  *      Detach.
151  */
152 static int unix_destroy(void)
153 {
154         paircompare_unregister(PW_GROUP, groupcmp);
155 #ifdef PW_GROUP_NAME
156         paircompare_unregister(PW_GROUP_NAME, groupcmp);
157 #endif
158         return 0;
159 }
160
161
162 /*
163  *      Check the users password against the standard UNIX
164  *      password table.
165  */
166 static int unix_authenticate(void *instance, REQUEST *request)
167 {
168         char *name, *passwd;
169         struct passwd   *pwd;
170         char            *encpw;
171         char            *encrypted_pass;
172         int             ret;
173 #if HAVE_GETSPNAM
174 #if defined(M_UNIX)
175         struct passwd   *spwd;
176 #else
177         struct spwd     *spwd;
178 #endif
179 #endif
180 #ifdef OSFC2
181         struct pr_passwd *pr_pw;
182 #endif
183 #ifdef HAVE_GETUSERSHELL
184         char            *shell;
185 #endif
186         instance = instance;
187
188         /*
189          *      We can only authenticate user requests which HAVE
190          *      a User-Name attribute.
191          */
192         if (!request->username) {
193                 radlog(L_AUTH, "rlm_unix: Attribute \"User-Name\" is required for authentication.");
194                 return RLM_MODULE_INVALID;
195         }
196
197         /*
198          *      We can only authenticate user requests which HAVE
199          *      a Password attribute.
200          */
201         if (!request->password) {
202                 radlog(L_AUTH, "rlm_unix: Attribute \"Password\" is required for authentication.");
203                 return RLM_MODULE_INVALID;
204         }
205
206         /*
207          *  Ensure that we're being passed a plain-text password,
208          *  and not anything else.
209          */
210         if (request->password->attribute != PW_PASSWORD) {
211                 radlog(L_AUTH, "rlm_unix: Attribute \"Password\" is required for authentication.  Cannot use \"%s\".", request->password->name);
212                 return RLM_MODULE_INVALID;
213         }
214
215         name = (char *)request->username->strvalue;
216         passwd = (char *)request->password->strvalue;
217
218         if (cache_passwd && (ret = H_unix_pass(name, passwd, &request->reply->vps)) != -2)
219                 return (ret == 0) ? RLM_MODULE_OK : RLM_MODULE_REJECT;
220
221 #ifdef OSFC2
222         if ((pr_pw = getprpwnam(name)) == NULL)
223                 return RLM_MODULE_NOTFOUND;
224         encrypted_pass = pr_pw->ufld.fd_encrypt;
225 #else /* OSFC2 */
226         /*
227          *      Get encrypted password from password file
228          */
229         if ((pwd = getpwnam(name)) == NULL) {
230                 return RLM_MODULE_NOTFOUND;
231         }
232         encrypted_pass = pwd->pw_passwd;
233 #endif /* OSFC2 */
234
235 #if HAVE_GETSPNAM
236         /*
237          *      See if there is a shadow password.
238          */
239         if ((spwd = getspnam(name)) != NULL)
240 #if defined(M_UNIX)
241                 encrypted_pass = spwd->pw_passwd;
242 #else
243                 encrypted_pass = spwd->sp_pwdp;
244 #endif  /* M_UNIX */
245 #endif  /* HAVE_GETSPNAM */
246
247 #ifdef DENY_SHELL
248         /*
249          *      Undocumented temporary compatibility for iphil.NET
250          *      Users with a certain shell are always denied access.
251          */
252         if (strcmp(pwd->pw_shell, DENY_SHELL) == 0) {
253                 radlog(L_AUTH, "rlm_unix: [%s]: invalid shell", name);
254                 return RLM_MODULE_REJECT;
255         }
256 #endif
257
258 #if HAVE_GETUSERSHELL
259         /*
260          *      Check /etc/shells for a valid shell. If that file
261          *      contains /RADIUSD/ANY/SHELL then any shell will do.
262          */
263         while ((shell = getusershell()) != NULL) {
264                 if (strcmp(shell, pwd->pw_shell) == 0 ||
265                     strcmp(shell, "/RADIUSD/ANY/SHELL") == 0) {
266                         break;
267                 }
268         }
269         endusershell();
270         if (shell == NULL)
271                 return RLM_MODULE_REJECT;
272 #endif
273
274 #if defined(HAVE_GETSPNAM) && !defined(M_UNIX)
275         /*
276          *      Check if password has expired.
277          */
278         if (spwd && spwd->sp_expire > 0 &&
279             (request->timestamp / 86400) > spwd->sp_expire) {
280                 radlog(L_AUTH, "rlm_unix: [%s]: password has expired", name);
281                 return RLM_MODULE_REJECT;
282         }
283 #endif
284
285 #if defined(__FreeBSD__) || defined(bsdi) || defined(_PWF_EXPIRE)
286         /*
287          *      Check if password has expired.
288          */
289         if ((pwd->pw_expire > 0) &&
290             (request->timestamp > pwd->pw_expire)) {
291                 radlog(L_AUTH, "rlm_unix: [%s]: password has expired", name);
292                 return RLM_MODULE_REJECT;
293         }
294 #endif
295
296 #ifdef OSFC2
297         /*
298          *      Check if account is locked.
299          */
300         if (pr_pw->uflg.fg_lock!=1) {
301                 radlog(L_AUTH, "rlm_unix: [%s]: account locked", name);
302                 return RLM_MODULE_USERLOCK;
303         }
304 #endif /* OSFC2 */
305
306         /*
307          *      We might have a passwordless account.
308          */
309         if (encrypted_pass[0] == 0)
310                 return RLM_MODULE_OK;
311
312         /*
313          *      Check encrypted password.
314          */
315         encpw = crypt(passwd, encrypted_pass);
316         if (strcmp(encpw, encrypted_pass))
317                 return RLM_MODULE_REJECT;
318
319         return RLM_MODULE_OK;
320 }
321
322 /*
323  *      UUencode 4 bits base64. We use this to turn a 4 byte field
324  *      (an IP address) into 6 bytes of ASCII. This is used for the
325  *      wtmp file if we didn't find a short name in the naslist file.
326  */
327 static char *uue(void *in)
328 {
329         int i;
330         static unsigned char res[7];
331         unsigned char *data = (unsigned char *)in;
332
333         res[0] = ENC( data[0] >> 2 );
334         res[1] = ENC( ((data[0] << 4) & 060) + ((data[1] >> 4) & 017) );
335         res[2] = ENC( ((data[1] << 2) & 074) + ((data[2] >> 6) & 03) );
336         res[3] = ENC( data[2] & 077 );
337
338         res[4] = ENC( data[3] >> 2 );
339         res[5] = ENC( (data[3] << 4) & 060 );
340         res[6] = 0;
341
342         for(i = 0; i < 6; i++) {
343                 if (res[i] == ' ') res[i] = '`';
344                 if (res[i] < 32 || res[i] > 127)
345                         printf("uue: protocol error ?!\n");
346         }
347         return (char *)res;
348 }
349
350
351 /*
352  *      Unix accounting - write a wtmp file.
353  */
354 static int unix_accounting(void *instance, REQUEST *request)
355 {
356         VALUE_PAIR      *vp;
357         NAS             *cl;
358         FILE            *fp;
359         struct utmp     ut;
360         time_t          t;
361         char            buf[64];
362         const char      *s;
363         int             delay = 0;
364         int             status = -1;
365         int             nas_address = 0;
366         int             framed_address = 0;
367         int             protocol = -1;
368         int             nas_port = 0;
369         int             port_seen = 0;
370         int             nas_port_type = 0;
371
372         instance = instance;
373
374         /*
375          *      Which type is this.
376          */
377         if ((vp = pairfind(request->packet->vps, PW_ACCT_STATUS_TYPE))==NULL) {
378                 radlog(L_ERR, "Accounting: no Accounting-Status-Type record.");
379                 return RLM_MODULE_NOOP;
380         }
381         status = vp->lvalue;
382
383         /*
384          *      FIXME: handle PW_STATUS_ALIVE like 1.5.4.3 did.
385          */
386         if (status != PW_STATUS_START &&
387             status != PW_STATUS_STOP)
388                 return RLM_MODULE_NOOP;
389
390         /*
391          *      We're only interested in accounting messages
392          *      with a username in it.
393          */
394         if ((vp = pairfind(request->packet->vps, PW_USER_NAME)) == NULL)
395                 return RLM_MODULE_NOOP;
396
397         t = request->timestamp;
398         memset(&ut, 0, sizeof(ut));
399
400         /*
401          *      First, find the interesting attributes.
402          */
403         for (vp = request->packet->vps; vp; vp = vp->next) {
404                 switch (vp->attribute) {
405                         case PW_USER_NAME:
406                                 strNcpy(ut.ut_name, (char *)vp->strvalue, sizeof(ut.ut_name));
407                                 break;
408                         case PW_LOGIN_IP_HOST:
409                         case PW_FRAMED_IP_ADDRESS:
410                                 framed_address = vp->lvalue;
411                                 break;
412                         case PW_FRAMED_PROTOCOL:
413                                 protocol = vp->lvalue;
414                                 break;
415                         case PW_NAS_IP_ADDRESS:
416                                 nas_address = vp->lvalue;
417                                 break;
418                         case PW_NAS_PORT_ID:
419                                 nas_port = vp->lvalue;
420                                 port_seen = 1;
421                                 break;
422                         case PW_ACCT_DELAY_TIME:
423                                 delay = vp->lvalue;
424                                 break;
425                         case PW_NAS_PORT_TYPE:
426                                 nas_port_type = vp->lvalue;
427                                 break;
428                 }
429         }
430
431         /*
432          *      We don't store !root sessions, or sessions
433          *      where we didn't see a PW_NAS_PORT_ID.
434          */
435         if (strncmp(ut.ut_name, "!root", sizeof(ut.ut_name)) == 0 || !port_seen)
436                 return RLM_MODULE_NOOP;
437
438         /*
439          *      If we didn't find out the NAS address, use the
440          *      originator's IP address.
441          */
442         if (nas_address == 0)
443                 nas_address = request->packet->src_ipaddr;
444
445 #ifdef __linux__
446         /*
447          *      Linux has a field for the client address.
448          */
449         ut.ut_addr = framed_address;
450 #endif
451         /*
452          *      We use the tty field to store the terminal servers' port
453          *      and address so that the tty field is unique.
454          */
455         s = "";
456         if ((cl = nas_find(nas_address)) != NULL)
457                 s = cl->shortname;
458         if (s == NULL || s[0] == 0) s = uue(&(nas_address));
459         sprintf(buf, "%03d:%s", nas_port, s);
460         strNcpy(ut.ut_line, buf, sizeof(ut.ut_line));
461
462         /*
463          *      We store the dynamic IP address in the hostname field.
464          */
465 #ifdef UT_HOSTSIZE
466         if (framed_address) {
467                 ip_ntoa(buf, framed_address);
468                 strncpy(ut.ut_host, buf, UT_HOSTSIZE);
469         }
470 #endif
471 #ifdef HAVE_UTMPX_H
472         ut.ut_xtime = t- delay;
473 #else
474         ut.ut_time = t - delay;
475 #endif
476 #ifdef USER_PROCESS
477         /*
478          *      And we can use the ID field to store
479          *      the protocol.
480          */
481         if (protocol == PW_PPP)
482                 strcpy(ut.ut_id, "P");
483         else if (protocol == PW_SLIP)
484                 strcpy(ut.ut_id, "S");
485         else
486                 strcpy(ut.ut_id, "T");
487         ut.ut_type = status == PW_STATUS_STOP ? DEAD_PROCESS : USER_PROCESS;
488 #endif
489         if (status == PW_STATUS_STOP)
490                 ut.ut_name[0] = 0;
491
492         /*
493          *      Write a RADIUS wtmp log file.
494          *      FIXME: return correct error.
495          *      Check if file is there. If not, we don't write the
496          *      wtmp file. If it is, we try to write. If we fail,
497          *      return RLM_MODULE_FAIL ..
498          */
499         if ((fp = fopen(RADWTMP, "a")) != NULL) {
500                 fwrite(&ut, sizeof(ut), 1, fp);
501                 fclose(fp);
502         }
503
504         return RLM_MODULE_OK;
505 }
506
507 /* globally exported name */
508 module_t rlm_unix = {
509   "System",
510   0,                            /* type: reserved */
511   unix_init,                    /* initialization */
512   unix_instantiate,             /* instantiation */
513   NULL,                         /* authorization */
514   unix_authenticate,            /* authentication */
515   NULL,                         /* preaccounting */
516   unix_accounting,              /* accounting */
517   NULL,                         /* detach */
518   unix_destroy,                  /* destroy */
519 };
520