Turn on super pedantic warnings in CLANG
[freeradius.git] / src / modules / rlm_passwd / rlm_passwd.c
1 /*
2  *   This program is is free software; you can redistribute it and/or modify
3  *   it under the terms of the GNU General Public License, version 2 if the
4  *   License as published by the Free Software Foundation.
5  *
6  *   This program is distributed in the hope that it will be useful,
7  *   but WITHOUT ANY WARRANTY; without even the implied warranty of
8  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
9  *   GNU General Public License for more details.
10  *
11  *   You should have received a copy of the GNU General Public License
12  *   along with this program; if not, write to the Free Software
13  *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
14  */
15
16 /**
17  * $Id$
18  * @file rlm_passwd.c
19  * @brief Enables authentication against unix passwd files.
20  *
21  * @copyright 2000,2006  The FreeRADIUS server project
22  */
23 RCSID("$Id$")
24
25 #include <freeradius-devel/radiusd.h>
26 #include <freeradius-devel/modules.h>
27 #include <freeradius-devel/rad_assert.h>
28
29 struct mypasswd {
30         struct mypasswd *next;
31         char *listflag;
32         char *field[1];
33 };
34
35 struct hashtable {
36         int tablesize;
37         int keyfield;
38         int nfields;
39         int islist;
40         int ignorenis;
41         char * filename;
42         struct mypasswd **table;
43         char buffer[1024];
44         FILE *fp;
45         char delimiter;
46 };
47
48
49 #ifdef TEST
50
51 #define rad_malloc(s) malloc(s)
52
53 void printpw(struct mypasswd *pw, int nfields){
54         int i;
55         if (pw) {
56                 for( i = 0; i < nfields; i++ ) printf("%s:", pw->field[i]);
57                 printf("\n");
58         }
59         else printf ("Not found\n");
60         fflush(stdout);
61 }
62 #endif
63
64
65 static struct mypasswd * mypasswd_malloc(char const* buffer, int nfields, size_t* len)
66 {
67         struct mypasswd *t;
68         /* reserve memory for (struct mypasswd) + listflag (nfields * sizeof (char*)) +
69         ** fields (nfields * sizeof (char)) + strlen (inst->format) + 1 */
70
71         *len=sizeof (struct mypasswd) + nfields * sizeof (char*) + nfields * sizeof (char ) + strlen(buffer) + 1;
72         t = (struct mypasswd *) rad_malloc(*len);
73         if (t) memset(t, 0, *len);
74         return (t);
75 }
76
77 static int string_to_entry(char const* string, int nfields, char delimiter,
78                            struct mypasswd *passwd, size_t bufferlen)
79 {
80         char *str;
81         size_t len, i;
82         int fn=0;
83         char *data_beg;
84
85
86         len = strlen(string);
87         if(!len) return 0;
88         if (string[len-1] == '\n') len--;
89         if(!len) return 0;
90         if (string[len-1] == '\r') len--;
91         if(!len) return 0;
92         if (!len || !passwd ||
93             bufferlen < (len + nfields * sizeof (char*) + nfields * sizeof (char) + sizeof (struct mypasswd) + 1) ) return 0;
94         passwd->next = NULL;
95         data_beg=(char *)passwd + sizeof(struct mypasswd);
96         str = data_beg + nfields * sizeof (char) + nfields * sizeof (char*);
97         memcpy (str, string, len);
98         str[len] = 0;
99         passwd->field[fn++] = str;
100         passwd->listflag = data_beg + nfields * sizeof (char *);
101         for(i=0; i < len; i++){
102                 if (str[i] == delimiter) {
103                         str[i] = 0;
104                         passwd->field[fn++] = str + i + 1;
105                         if (fn == nfields) break;
106                 }
107         }
108         for (; fn < nfields; fn++) passwd->field[fn] = NULL;
109         return len + nfields * sizeof (char) + nfields * sizeof (char*) + sizeof (struct mypasswd) + 1;
110 }
111
112
113 static void destroy_password (struct mypasswd * pass)
114 {
115         struct mypasswd *p;
116         while ((p=pass)!=NULL) {
117                 pass = pass->next;
118                 free(p);
119         }
120 }
121
122
123 static unsigned int hash(char const * username, unsigned int tablesize)
124 {
125         int h=1;
126         while (*username) {
127                 h = h * 7907 + *username++;
128         }
129         return h%tablesize;
130 }
131
132 static void release_hash_table(struct hashtable * ht){
133         int i;
134
135         if (!ht) return;
136         for (i = 0; i < ht->tablesize; i++)
137                 if (ht->table[i])
138                         destroy_password(ht->table[i]);
139         if (ht->table) {
140                 free(ht->table);
141                 ht->table = NULL;
142         }
143         if (ht->fp) {
144                 fclose(ht->fp);
145                 ht->fp = NULL;
146         }
147         ht->tablesize = 0;
148 }
149
150 static void release_ht(struct hashtable * ht){
151         if (!ht) return;
152         release_hash_table(ht);
153         if (ht->filename) {
154                 free(ht->filename);
155                 ht->filename = NULL;
156         }
157         free(ht);
158 }
159
160 static struct hashtable * build_hash_table (char const * file, int nfields,
161                                             int keyfield, int islist, int tablesize, int ignorenis, char delimiter)
162 {
163         struct hashtable* ht;
164         size_t len;
165         unsigned int h;
166         struct mypasswd *hashentry, *hashentry1;
167         char *list;
168         char *nextlist=0;
169         int i;
170         char buffer[1024];
171
172         ht = (struct hashtable *) rad_malloc(sizeof(struct hashtable));
173         if(!ht) {
174                 return NULL;
175         }
176         memset(ht, 0, sizeof(struct hashtable));
177         ht->filename = strdup(file);
178         if(!ht->filename) {
179                 free(ht);
180                 return NULL;
181         }
182         ht->tablesize = tablesize;
183         ht->nfields = nfields;
184         ht->keyfield = keyfield;
185         ht->islist = islist;
186         ht->ignorenis = ignorenis;
187         if (delimiter) ht->delimiter = delimiter;
188         else ht->delimiter = ':';
189         if(!tablesize) return ht;
190         if(!(ht->fp = fopen(file,"r"))) {
191                 free(ht->filename);
192                 free(ht);
193                 return NULL;
194         }
195
196         /*
197          *      @todo: This code is SHIT.  It's badly formatted.  It's
198          *      hard to understand.  It re-implements tons of things
199          *      which are already in the server core.
200          */
201         memset(ht->buffer, 0, 1024);
202         ht->table = (struct mypasswd **) rad_malloc (tablesize * sizeof(struct mypasswd *));
203         if (!ht->table) {
204                 /*
205                  * Unable allocate memory for hash table
206                  * Still work without it
207                  */
208                 ht->tablesize = 0;
209                 return ht;
210         }
211         memset(ht->table, 0, tablesize * sizeof(struct mypasswd *));
212         while (fgets(buffer, 1024, ht->fp)) {
213                 if(*buffer && *buffer!='\n' && (!ignorenis || (*buffer != '+' && *buffer != '-')) ){
214                         if(!(hashentry = mypasswd_malloc(buffer, nfields, &len))){
215                                 release_hash_table(ht);
216                                 return ht;
217                         }
218                         len = string_to_entry(buffer, nfields, ht->delimiter, hashentry, len);
219                         if(!hashentry->field[keyfield] || *hashentry->field[keyfield] == '\0') {
220                                 free(hashentry);
221                                 continue;
222                         }
223
224                         if (islist) {
225                                 list = hashentry->field[keyfield];
226                                 for (nextlist = list; *nextlist && *nextlist!=','; nextlist++);
227                                 if (*nextlist) *nextlist++ = 0;
228                                 else nextlist = 0;
229                         }
230                         h = hash(hashentry->field[keyfield], tablesize);
231                         hashentry->next = ht->table[h];
232                         ht->table[h] = hashentry;
233                         if (islist) {
234                                 for(list=nextlist; nextlist; list = nextlist){
235                                         for (nextlist = list; *nextlist && *nextlist!=','; nextlist++);
236                                         if (*nextlist) *nextlist++ = 0;
237                                         else nextlist = 0;
238                                         if(!(hashentry1 = mypasswd_malloc("", nfields, &len))){
239                                                 release_hash_table(ht);
240                                                 return ht;
241                                         }
242                                         for (i=0; i<nfields; i++) hashentry1->field[i] = hashentry->field[i];
243                                         hashentry1->field[keyfield] = list;
244                                         h = hash(list, tablesize);
245                                         hashentry1->next = ht->table[h];
246                                         ht->table[h] = hashentry1;
247                                 }
248                         }
249                 }
250         }
251         fclose(ht->fp);
252         ht->fp = NULL;
253         return ht;
254 #undef passwd
255 }
256
257 static struct mypasswd * get_next(char *name, struct hashtable *ht,
258                                   struct mypasswd **last_found)
259 {
260         struct mypasswd * passwd;
261         struct mypasswd * hashentry;
262         char buffer[1024];
263         char *list, *nextlist;
264
265         if (ht->tablesize > 0) {
266                 /* get saved address of next item to check from buffer */
267                 hashentry = *last_found;
268                 for (; hashentry; hashentry = hashentry->next) {
269                         if (!strcmp(hashentry->field[ht->keyfield], name)) {
270                                 /* save new address */
271                                 *last_found = hashentry->next;
272                                 return hashentry;
273                         }
274                 }
275                 return NULL;
276         }
277         /*      printf("try to find in file\n"); */
278         if (!ht->fp) return NULL;
279
280         passwd = (struct mypasswd *) ht->buffer;
281
282         while (fgets(buffer, 1024,ht->fp)) {
283                 if(*buffer && *buffer!='\n' && string_to_entry(buffer, ht->nfields, ht->delimiter, passwd, sizeof(ht->buffer)-1) &&
284                    (!ht->ignorenis || (*buffer !='-' && *buffer != '+') ) ){
285                         if(!ht->islist) {
286                                 if(!strcmp(passwd->field[ht->keyfield], name))
287                                         return passwd;
288                         }
289                         else {
290                                 for (list = passwd->field[ht->keyfield], nextlist = list; nextlist; list = nextlist) {
291                                         for(nextlist = list; *nextlist && *nextlist!=','; nextlist++);
292                                         if(!*nextlist) {
293                                                 nextlist = 0;
294                                         } else {
295                                                 *nextlist++ = 0;
296                                         }
297                                         if (!strcmp(list, name)) {
298                                                 return passwd;
299                                         }
300                                 }
301                         }
302
303                 }
304         }
305         fclose(ht->fp);
306         ht->fp = NULL;
307         return NULL;
308 }
309
310 static struct mypasswd * get_pw_nam(char * name, struct hashtable* ht,
311                                     struct mypasswd **last_found)
312 {
313         int h;
314         struct mypasswd * hashentry;
315
316         if (!ht || !name || *name == '\0') return NULL;
317         *last_found = NULL;
318         if (ht->tablesize > 0) {
319                 h = hash (name, ht->tablesize);
320                 for (hashentry = ht->table[h]; hashentry; hashentry = hashentry->next) {
321                         if (!strcmp(hashentry->field[ht->keyfield], name)){
322                                 /* save address of next item to check into buffer */
323                                 *last_found=hashentry->next;
324                                 return hashentry;
325                         }
326                 }
327
328                 return NULL;
329         }
330         if (ht->fp) {
331                 fclose(ht->fp);
332                 ht->fp = NULL;
333         }
334         if (!(ht->fp=fopen(ht->filename, "r"))) return NULL;
335         return get_next(name, ht, last_found);
336 }
337
338 #ifdef TEST
339
340 #define MALLOC_CHECK_ 1
341
342 int main(void){
343         struct hashtable *ht;
344         char *buffer;
345         struct mypasswd* pw, *last_found;
346         int i;
347
348         ht = build_hash_table("/etc/group", 4, 3, 1, 100, 0, ":");
349         if(!ht) {
350                 printf("Hash table not built\n");
351                 return -1;
352         }
353         for (i = 0; i < ht->tablesize; i++) {
354                 if (ht->table[i]) {
355                         printf("%d:\n", i);
356                         for (pw = ht->table[i]; pw; pw = pw->next) {
357                                 printpw(pw, 4);
358                         }
359                 }
360         }
361
362         while(fgets(buffer, 1024, stdin)){
363                 buffer[strlen(buffer)-1] = 0;
364                 pw = get_pw_nam(buffer, ht, &last_found);
365                 printpw(pw,4);
366                 while ((pw = get_next(buffer, ht, &last_found))) printpw(pw,4);
367         }
368         release_ht(ht);
369 }
370
371 #else  /* TEST */
372 struct passwd_instance {
373         struct hashtable        *ht;
374         struct mypasswd         *pwdfmt;
375         char const              *filename;
376         char const              *format;
377         char const              *delimiter;
378         bool                    allow_multiple;
379         bool                    ignore_nislike;
380         uint32_t                hash_size;
381         uint32_t                nfields;
382         uint32_t                keyfield;
383         uint32_t                listable;
384         DICT_ATTR const         *keyattr;
385         bool                    ignore_empty;
386 };
387
388 static const CONF_PARSER module_config[] = {
389         { "filename", FR_CONF_OFFSET(PW_TYPE_FILE_INPUT | PW_TYPE_REQUIRED, struct passwd_instance, filename), NULL },
390         { "format", FR_CONF_OFFSET(PW_TYPE_STRING | PW_TYPE_REQUIRED, struct passwd_instance, format), NULL },
391         { "delimiter", FR_CONF_OFFSET(PW_TYPE_STRING, struct passwd_instance, delimiter), ":" },
392
393         { "ignorenislike", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, struct passwd_instance, ignore_nislike), NULL },
394         { "ignore_nislike", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, struct passwd_instance, ignore_nislike), "yes" },
395
396         { "ignoreempty", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, struct passwd_instance, ignore_empty), NULL },
397         { "ignore_empty",  FR_CONF_OFFSET(PW_TYPE_BOOLEAN, struct passwd_instance, ignore_empty), "yes" },
398
399         { "allowmultiplekeys", FR_CONF_OFFSET(PW_TYPE_BOOLEAN | PW_TYPE_DEPRECATED, struct passwd_instance, allow_multiple), NULL },
400         { "allow_multiple_keys", FR_CONF_OFFSET(PW_TYPE_BOOLEAN, struct passwd_instance, allow_multiple), "no" },
401
402         { "hashsize", FR_CONF_OFFSET(PW_TYPE_INTEGER | PW_TYPE_DEPRECATED, struct passwd_instance, hash_size), NULL },
403         { "hash_size", FR_CONF_OFFSET(PW_TYPE_INTEGER, struct passwd_instance, hash_size), "100" },
404
405         { NULL, -1, 0, NULL, NULL }
406 };
407
408 static int mod_instantiate(CONF_SECTION *conf, void *instance)
409 {
410         int nfields=0, keyfield=-1, listable=0;
411         char const *s;
412         char *lf=NULL; /* destination list flags temporary */
413         size_t len;
414         int i;
415         DICT_ATTR const * da;
416         struct passwd_instance *inst = instance;
417
418         rad_assert(inst->filename && *inst->filename);
419         rad_assert(inst->format && *inst->format);
420
421         if (inst->hash_size == 0) {
422                 cf_log_err_cs(conf, "Invalid value '0' for hash_size");
423                 return -1;
424         }
425
426         lf = talloc_typed_strdup(inst, inst->format);
427         if ( !lf) {
428                 ERROR("rlm_passwd: memory allocation failed for lf");
429                 return -1;
430         }
431         memset(lf, 0, strlen(inst->format));
432         s = inst->format - 1;
433         do {
434                 if(s == inst->format - 1 || *s == ':'){
435                         if(*(s+1) == '*'){
436                                 keyfield = nfields;
437                                 s++;
438                         }
439                         if(*(s+1) == ','){
440                                 listable = 1;
441                                 s++;
442                         }
443                         if(*(s+1) == '='){
444                                 lf[nfields]=1;
445                                 s++;
446                         }
447                         if(*(s+1) == '~'){
448                                 lf[nfields]=2;
449                                 s++;
450                         }
451                         nfields++;
452                 }
453                 s++;
454         }while(*s);
455         if(keyfield < 0) {
456                 cf_log_err_cs(conf, "no field marked as key in format: %s",
457                               inst->format);
458                 return -1;
459         }
460         if (! (inst->ht = build_hash_table (inst->filename, nfields, keyfield, listable, inst->hash_size, inst->ignore_nislike, *inst->delimiter)) ){
461                 ERROR("rlm_passwd: can't build hashtable from passwd file");
462                 return -1;
463         }
464         if (! (inst->pwdfmt = mypasswd_malloc(inst->format, nfields, &len)) ){
465                 ERROR("rlm_passwd: memory allocation failed");
466                 release_ht(inst->ht);
467                 inst->ht = NULL;
468                 return -1;
469         }
470         if (!string_to_entry(inst->format, nfields, ':', inst->pwdfmt , len)) {
471                 ERROR("rlm_passwd: unable to convert format entry");
472                 release_ht(inst->ht);
473                 inst->ht = NULL;
474                 return -1;
475         }
476
477         memcpy(inst->pwdfmt->listflag, lf, nfields);
478
479         talloc_free(lf);
480         for (i=0; i<nfields; i++) {
481                 if (*inst->pwdfmt->field[i] == '*') inst->pwdfmt->field[i]++;
482                 if (*inst->pwdfmt->field[i] == ',') inst->pwdfmt->field[i]++;
483                 if (*inst->pwdfmt->field[i] == '=') inst->pwdfmt->field[i]++;
484                 if (*inst->pwdfmt->field[i] == '~') inst->pwdfmt->field[i]++;
485         }
486         if (!*inst->pwdfmt->field[keyfield]) {
487                 cf_log_err_cs(conf, "key field is empty");
488                 release_ht(inst->ht);
489                 inst->ht = NULL;
490                 return -1;
491         }
492         if (! (da = dict_attrbyname (inst->pwdfmt->field[keyfield])) ) {
493                 ERROR("rlm_passwd: unable to resolve attribute: %s", inst->pwdfmt->field[keyfield]);
494                 release_ht(inst->ht);
495                 inst->ht = NULL;
496                 return -1;
497         }
498         inst->keyattr = da;
499         inst->nfields = nfields;
500         inst->keyfield = keyfield;
501         inst->listable = listable;
502         DEBUG2("rlm_passwd: nfields: %d keyfield %d(%s) listable: %s", nfields, keyfield, inst->pwdfmt->field[keyfield], listable?"yes":"no");
503         return 0;
504
505 #undef inst
506 }
507
508 static int mod_detach (void *instance) {
509 #define inst ((struct passwd_instance *)instance)
510         if(inst->ht) {
511                 release_ht(inst->ht);
512                 inst->ht = NULL;
513         }
514         free(inst->pwdfmt);
515         return 0;
516 #undef inst
517 }
518
519 static void addresult (TALLOC_CTX *ctx, struct passwd_instance *inst, REQUEST *request,
520                        VALUE_PAIR **vps, struct mypasswd * pw, char when, char const *listname)
521 {
522         uint32_t i;
523         VALUE_PAIR *vp;
524
525         for (i = 0; i < inst->nfields; i++) {
526                 if (inst->pwdfmt->field[i] && *inst->pwdfmt->field[i] && pw->field[i] && i != inst->keyfield  && inst->pwdfmt->listflag[i] == when) {
527                         if ( !inst->ignore_empty || pw->field[i][0] != 0 ) { /* if value in key/value pair is not empty */
528                                 vp = pairmake(ctx, vps, inst->pwdfmt->field[i], pw->field[i], T_OP_EQ);
529                                 if (vp) {
530                                         RDEBUG("Added %s: '%s' to %s ", inst->pwdfmt->field[i], pw->field[i], listname);
531                                 }
532                         } else
533                                 RDEBUG("NOOP %s: '%s' to %s ", inst->pwdfmt->field[i], pw->field[i], listname);
534                 }
535         }
536 }
537
538 static rlm_rcode_t CC_HINT(nonnull) mod_passwd_map(void *instance, REQUEST *request)
539 {
540 #define inst ((struct passwd_instance *)instance)
541         char buffer[1024];
542         VALUE_PAIR *key, *i;
543         struct mypasswd * pw, *last_found;
544         vp_cursor_t cursor;
545
546         key = pair_find_by_da(request->packet->vps, inst->keyattr, TAG_ANY);
547         if (!key) {
548                 return RLM_MODULE_NOTFOUND;
549         }
550
551         for (i = fr_cursor_init(&cursor, &key);
552              i;
553              i = fr_cursor_next_by_num(&cursor, inst->keyattr->attr, inst->keyattr->vendor, TAG_ANY)) {
554                 /*
555                  *      Ensure we have the string form of the attribute
556                  */
557                 vp_prints_value(buffer, sizeof(buffer), i, 0);
558                 if (!(pw = get_pw_nam(buffer, inst->ht, &last_found)) ) {
559                         continue;
560                 }
561                 do {
562                         addresult(request, inst, request, &request->config_items, pw, 0, "config_items");
563                         addresult(request->reply, inst, request, &request->reply->vps, pw, 1, "reply_items");
564                         addresult(request->packet, inst, request, &request->packet->vps, pw, 2, "request_items");
565                 } while ((pw = get_next(buffer, inst->ht, &last_found)));
566
567                 if (!inst->allow_multiple) {
568                         break;
569                 }
570         }
571
572         return RLM_MODULE_OK;
573
574 #undef inst
575 }
576
577 extern module_t rlm_passwd;
578 module_t rlm_passwd = {
579         RLM_MODULE_INIT,
580         "passwd",
581         RLM_TYPE_HUP_SAFE,      /* type */
582         sizeof(struct passwd_instance),
583         module_config,
584         mod_instantiate,                /* instantiation */
585         mod_detach,                     /* detach */
586         {
587                 NULL,                   /* authentication */
588                 mod_passwd_map,         /* authorization */
589                 NULL,                   /* pre-accounting */
590                 mod_passwd_map,         /* accounting */
591                 NULL,                   /* checksimul */
592                 NULL,                   /* pre-proxy */
593                 NULL,                   /* post-proxy */
594                 mod_passwd_map          /* post-auth */
595 #ifdef WITH_COA
596                 , mod_passwd_map,
597                 mod_passwd_map
598 #endif
599         },
600 };
601 #endif /* TEST */