imported svn:keywords properties
[public/netxms.git] / src / server / core / radius.cpp
1 /* $Id$ */
2
3 /*
4 ** NetXMS - Network Management System
5 ** Copyright (C) 2003, 2004, 2005, 2006 Victor Kirhenshtein
6 **
7 ** RADIUS client
8 ** This code is based on uRadiusLib (C) Gary Wallis, 2006.
9 **
10 ** This program is free software; you can redistribute it and/or modify
11 ** it under the terms of the GNU General Public License as published by
12 ** the Free Software Foundation; either version 2 of the License, or
13 ** (at your option) any later version.
14 **
15 ** This program is distributed in the hope that it will be useful,
16 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 ** GNU General Public License for more details.
19 **
20 ** You should have received a copy of the GNU General Public License
21 ** along with this program; if not, write to the Free Software
22 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 **
24 ** File: radius.cpp
25 **
26 **/
27
28 #include "nxcore.h"
29 #include "radius.h"
30
31
32 //
33 // Add a pair at the end of a VALUE_PAIR list.
34 //
35
36 static void pairadd(VALUE_PAIR **first, VALUE_PAIR *newPair)
37 {
38 VALUE_PAIR *i;
39
40 if (*first == NULL)
41 {
42 *first = newPair;
43 return;
44 }
45 for(i = *first; i->next; i = i->next)
46 ;
47 i->next = newPair;
48 }
49
50
51 //
52 // Release the memory used by a list of attribute-value pairs.
53 //
54
55 static void pairfree(VALUE_PAIR *pair)
56 {
57 VALUE_PAIR *next;
58
59 while(pair != NULL)
60 {
61 next = pair->next;
62 free(pair);
63 pair = next;
64 }
65 }
66
67
68 //
69 // Create a new pair.
70 //
71
72 static VALUE_PAIR *paircreate(int attr, int type, const char *pszName)
73 {
74 VALUE_PAIR *vp;
75
76 if ((vp = (VALUE_PAIR *)malloc(sizeof(VALUE_PAIR))) == NULL)
77 {
78 return NULL;
79 }
80 memset(vp, 0, sizeof(VALUE_PAIR));
81 vp->attribute = attr;
82 vp->op = PW_OPERATOR_EQUAL;
83 vp->type = type;
84 if (pszName != NULL)
85 {
86 strcpy(vp->name, pszName);
87 }
88 else
89 {
90 sprintf(vp->name, "Attr-%d", attr);
91 }
92 switch (vp->type)
93 {
94 case PW_TYPE_INTEGER:
95 case PW_TYPE_IPADDR:
96 case PW_TYPE_DATE:
97 vp->length = 4;
98 break;
99 default:
100 vp->length = 0;
101 break;
102 }
103
104 return vp;
105 }
106
107
108 //
109 // Generate AUTH_VECTOR_LEN worth of random data.
110 //
111
112 static void random_vector(unsigned char *vector)
113 {
114 static int did_srand = 0;
115 unsigned int i;
116 int n;
117
118 #if defined(__linux__) || defined(BSD)
119 if (!did_srand)
120 {
121 /*
122 * Try to use /dev/urandom to seed the
123 * random generator. Not all *BSDs have it
124 * but it doesn't hurt to try.
125 */
126 if ((n = open("/dev/urandom", O_RDONLY)) >= 0 &&
127 read(n, (char *)&i, sizeof(i)) == sizeof(i))
128 {
129 srandom(i);
130 did_srand = 1;
131 }
132 if (n >= 0)
133 {
134 close(n);
135 }
136 }
137
138 if (!did_srand)
139 {
140 /*
141 * Use more traditional way to seed.
142 */
143 srandom(time(NULL) + getpid());
144 did_srand = 1;
145 }
146
147 for (i = 0; i < AUTH_VECTOR_LEN;)
148 {
149 n = random();
150 memcpy(vector, &n, sizeof(int));
151 vector += sizeof(int);
152 i += sizeof(int);
153 }
154 #else
155
156 #ifndef RAND_MAX
157 # define RAND_MAX 32768
158 #endif
159 /*
160 * Assume the system has neither /dev/urandom
161 * nor random()/srandom() but just the old
162 * rand() / srand() functions.
163 */
164 if (!did_srand)
165 {
166 char garbage[8];
167 i = time(NULL) + getpid();
168 for (n = 0; n < 8; n++)
169 {
170 i+= (garbage[n] << i);
171 }
172 srand(i);
173 did_srand = 1;
174 }
175
176 for (i = 0; i < AUTH_VECTOR_LEN;)
177 {
178 unsigned char c;
179 /*
180 * Don't use the lower bits, also don't use
181 * parts > RAND_MAX since they are zero.
182 */
183 n = rand() / (RAND_MAX / 256);
184 c = n;
185 memcpy(vector, &c, sizeof(c));
186 vector += sizeof(c);
187 i += sizeof(c);
188 }
189 #endif
190 }
191
192
193 //
194 // Encode password.
195 //
196 // Assume that "pwd_out" points to a buffer of at least AUTH_PASS_LEN bytes.
197 //
198 // Returns new length.
199 //
200
201 static int rad_pwencode(char *pwd_in, char *pwd_out, char *secret, char *vector)
202 {
203 char passbuf[AUTH_PASS_LEN];
204 char md5buf[256];
205 int i, secretlen;
206
207 i = strlen(pwd_in);
208 memset(passbuf, 0, AUTH_PASS_LEN);
209 memcpy(passbuf, pwd_in, i > AUTH_PASS_LEN ? AUTH_PASS_LEN : i);
210
211 secretlen = strlen(secret);
212 if (secretlen + AUTH_VECTOR_LEN > 256)
213 {
214 secretlen = 256 - AUTH_VECTOR_LEN;
215 }
216 nx_strncpy(md5buf, secret, sizeof(md5buf));
217 memcpy(md5buf + secretlen, vector, AUTH_VECTOR_LEN);
218 CalculateMD5Hash((BYTE *)md5buf, secretlen + AUTH_VECTOR_LEN, (BYTE *)pwd_out);
219
220 for(i = 0; i < AUTH_PASS_LEN; i++)
221 {
222 *pwd_out++ ^= passbuf[i];
223 }
224
225 return AUTH_PASS_LEN;
226 }
227
228
229 /*
230 * Encrypt/decrypt string attributes, style 1.
231 *
232 * See RFC 2868, section 3.5 for details. Currently probably indeed
233 * only useful for Tunnel-Password, but why make it a special case,
234 * now we have a generic flags mechanism in place anyway...
235 *
236 * It's optimized a little for speed, but it could probably be better.
237 */
238
239 #define CLEAR_STRING_LEN 256 /* The RFC says it is */
240 #define SECRET_LEN 32 /* Max. in client.c */
241 #define MD5_LEN 16 /* The algorithm specifies it */
242 #define SALT_LEN 2 /* The RFC says it is */
243
244 static void encrypt_attr_style_1(char *secret, char *vector, VALUE_PAIR *vp)
245 {
246 char clear_buf[CLEAR_STRING_LEN];
247 char work_buf[SECRET_LEN + AUTH_VECTOR_LEN + SALT_LEN];
248 char digest[MD5_LEN];
249 char *i, *o;
250 unsigned short salt; /* salt in network order */
251 int clear_len;
252 int work_len;
253 int secret_len;
254 int n;
255
256 /*
257 * Create the string we'll actually be processing by copying up to 255
258 * bytes of original cleartext, padding it with zeroes to a multiple of
259 * 16 bytes and inserting a length octet in front.
260 */
261
262 /* Limit length */
263 clear_len = vp->length;
264 if (clear_len > CLEAR_STRING_LEN - 1)
265 {
266 clear_len = CLEAR_STRING_LEN - 1;
267 }
268
269 /* Write the 'original' length byte and copy the buffer */
270 *clear_buf = clear_len;
271 memcpy(clear_buf + 1, vp->strvalue, clear_len);
272
273 /* From now on, the length byte is included with the byte count */
274 clear_len++;
275
276 /* Pad the string to a multiple of 1 chunk */
277 if (clear_len % MD5_LEN)
278 {
279 memset(clear_buf+clear_len, 0, MD5_LEN - (clear_len % MD5_LEN));
280 }
281
282 /* Define input and number of chunks to process */
283 i = clear_buf;
284 clear_len = (clear_len + (MD5_LEN - 1)) / MD5_LEN;
285
286 /* Define output and starting length */
287 o = vp->strvalue;
288 vp->length = sizeof(salt);
289
290 /*
291 * Fill in salt. Must be unique per attribute that uses it in the same
292 * packet, and the most significant bit must be set - see RFC 2868.
293 *
294 * FIXME: this _may_ be too simple. For now we just take the vp
295 * pointer, which should be different between attributes, xor-ed with
296 * the first longword of the vector to make it a little more unique.
297 *
298 * Oh, and sizeof(long) always == sizeof(void*) in our part of the
299 * universe, right? (*BSD, Solaris, Linux, DEC Unix...)
300 */
301 salt = htons( ( ((long)vp ^ *(long *)vector) & 0xffff ) | 0x8000 );
302 memcpy(o, &salt, sizeof(salt));
303 o += sizeof(salt);
304
305 /* Create a first working buffer to calc the MD5 hash over */
306 secret_len = strlen(secret); /* already limited by read_clients */
307 memcpy(work_buf, secret, secret_len);
308 memcpy(work_buf + secret_len, vector, AUTH_VECTOR_LEN);
309 memcpy(work_buf + secret_len + AUTH_VECTOR_LEN, &salt, sizeof(salt));
310 work_len = secret_len + AUTH_VECTOR_LEN + sizeof(salt);
311
312 for(; clear_len; clear_len--)
313 {
314 /* Get the digest */
315 CalculateMD5Hash((BYTE *)work_buf, work_len, (BYTE *)digest);
316
317 /* Xor the clear text to get the output chunk and next buffer */
318 for(n = 0; n < MD5_LEN; n++)
319 {
320 *(work_buf + secret_len + n) = *o++ = *i++ ^ digest[n];
321 }
322
323 /* This is the size of the next working buffer */
324 work_len = secret_len + MD5_LEN;
325
326 /* Increment the output length */
327 vp->length += MD5_LEN;
328 }
329 }
330
331
332 /*
333 * void encrypt_attr(char *secret, char *vector, VALUE_PAIR *vp);
334 *
335 * Encrypts vp->strvalue using style vp->flags.encrypt, possibly using
336 * a request authenticator passed in vector and the shared secret.
337 *
338 * This should always succeed.
339 */
340
341 static void encrypt_attr(char *secret, char *vector, VALUE_PAIR *vp)
342 {
343 switch(vp->flags.encrypt)
344 {
345 case 0:
346 /* Normal, cleartext. */
347 break;
348
349 case 1:
350 /* Tunnel Password (see RFC 2868, section 3.5). */
351 encrypt_attr_style_1(secret, vector, vp);
352 break;
353
354 default:
355 /* Unknown style - don't send the cleartext! */
356 vp->length = 19;
357 memcpy(vp->strvalue, "UNKNOWN_ENCR_METHOD", vp->length);
358 WriteLog(MSG_RADIUS_UNKNOWN_ENCR_METHOD, EVENTLOG_ERROR_TYPE,
359 "dd", vp->flags.encrypt, vp->attribute);
360 }
361 }
362
363
364 //
365 // Build radius packet. We assume that the header part
366 // of AUTH_HDR has already been filled in, we just
367 // fill auth->data with the A/V pairs from reply.
368 //
369
370 static int rad_build_packet(AUTH_HDR *auth, int auth_len,
371 VALUE_PAIR *reply, char *msg, char *secret,
372 char *vector, int *send_buffer)
373 {
374 VALUE_PAIR *vp;
375 u_short total_length;
376 u_char *ptr, *length_ptr;
377 char digest[16];
378 int vendorpec;
379 int len;
380 DWORD lvalue;
381
382 total_length = AUTH_HDR_LEN;
383
384 /*
385 * Load up the configuration values for the user
386 */
387 ptr = auth->data;
388 for (vp = reply; vp; vp = vp->next)
389 {
390 /*
391 * Check for overflow.
392 */
393 if (total_length + vp->length + 16 >= auth_len)
394 {
395 break;
396 }
397
398 /*
399 * This could be a vendor-specific attribute.
400 */
401 length_ptr = NULL;
402 if ((vendorpec = VENDOR(vp->attribute)) > 0)
403 {
404 *ptr++ = PW_VENDOR_SPECIFIC;
405 length_ptr = ptr;
406 *ptr++ = 6;
407 lvalue = htonl(vendorpec);
408 memcpy(ptr, &lvalue, 4);
409 ptr += 4;
410 total_length += 6;
411 }
412 else if (vp->attribute > 0xff)
413 {
414 /*
415 * Ignore attributes > 0xff
416 */
417 continue;
418 }
419 else
420 {
421 vendorpec = 0;
422 }
423
424 #ifdef ATTRIB_NMC
425 if (vendorpec == VENDORPEC_USR)
426 {
427 lvalue = htonl(vp->attribute & 0xFFFF);
428 memcpy(ptr, &lvalue, 4);
429 total_length += 2;
430 *length_ptr += 2;
431 ptr += 4;
432 }
433 else
434 #endif
435 *ptr++ = (vp->attribute & 0xFF);
436
437 switch(vp->type)
438 {
439
440 case PW_TYPE_STRING:
441 /*
442 * FIXME: this is just to make sure but
443 * should NOT be needed. In fact I have no
444 * idea if it is needed :)
445 */
446 if (vp->length == 0 && vp->strvalue[0] != 0)
447 {
448 vp->length = strlen(vp->strvalue);
449 }
450 if (vp->length >= AUTH_STRING_LEN)
451 {
452 vp->length = AUTH_STRING_LEN - 1;
453 }
454
455 /*
456 * If the flags indicate a encrypted attribute, handle
457 * it here. I don't want to go through the reply list
458 * another time just for transformations like this.
459 */
460 if (vp->flags.encrypt)
461 {
462 encrypt_attr(secret, vector, vp);
463 }
464
465 /*
466 * vp->length is the length of the string value; len
467 * is the length of the string field in the packet.
468 * Normally, these are the same, but if a tag is
469 * inserted only len will reflect this.
470 *
471 * Bug fixed: for tagged attributes with 'tunnel-pwd'
472 * encryption, the tag is *always* inserted, regardless
473 * of its value! (Another strange thing in RFC 2868...)
474 */
475 len = vp->length + (vp->flags.has_tag && (TAG_VALID(vp->flags.tag) || vp->flags.encrypt == 1));
476
477 #ifdef ATTRIB_NMC
478 if (vendorpec != VENDORPEC_USR)
479 #endif
480 *ptr++ = len + 2;
481 if (length_ptr)
482 *length_ptr += len + 2;
483
484 /* Insert the tag (sorry about the fast ugly test...) */
485 if (len > vp->length) *ptr++ = vp->flags.tag;
486
487 /* Use the original length of the string value */
488 memcpy(ptr, vp->strvalue, vp->length);
489 ptr += vp->length; /* here too */
490 total_length += len + 2;
491 break;
492
493 case PW_TYPE_INTEGER:
494 case PW_TYPE_DATE:
495 case PW_TYPE_IPADDR:
496 len = sizeof(DWORD) + (vp->flags.has_tag && vp->type != PW_TYPE_INTEGER);
497 #ifdef ATTRIB_NMC
498 if (vendorpec != VENDORPEC_USR)
499 #endif
500 *ptr++ = len + 2;
501 if (length_ptr) *length_ptr += len + 2;
502
503 /* Handle tags */
504 lvalue = vp->lvalue;
505 if (vp->flags.has_tag)
506 {
507 if (vp->type == PW_TYPE_INTEGER)
508 {
509 /* Tagged integer: MSB is tag */
510 lvalue = (lvalue & 0xffffff) | ((vp->flags.tag & 0xff) << 24);
511 }
512 else
513 {
514 /* Something else: insert the tag */
515 *ptr++ = vp->flags.tag;
516 }
517 }
518 lvalue = htonl(lvalue);
519 memcpy(ptr, &lvalue, sizeof(DWORD));
520 ptr += sizeof(DWORD);
521 total_length += len + 2;
522 break;
523
524 default:
525 break;
526 }
527 }
528
529 /*
530 * Append the user message
531 * FIXME: add multiple PW_REPLY_MESSAGEs if it
532 * doesn't fit into one.
533 */
534 if (msg && msg[0])
535 {
536 len = strlen(msg);
537 if (len > 0 && len < AUTH_STRING_LEN-1)
538 {
539 *ptr++ = PW_REPLY_MESSAGE;
540 *ptr++ = len + 2;
541 memcpy(ptr, msg, len);
542 ptr += len;
543 total_length += len + 2;
544 }
545 }
546
547 auth->length = htons(total_length);
548
549 if (auth->code != PW_AUTHENTICATION_REQUEST && auth->code != PW_STATUS_SERVER)
550 {
551 /*
552 * Append secret and calculate the response digest
553 */
554 len = strlen(secret);
555 if (total_length + len < auth_len)
556 {
557 memcpy((char *)auth + total_length, secret, len);
558 CalculateMD5Hash((BYTE *)auth, total_length + len, (BYTE *)digest);
559 memcpy(auth->vector, digest, AUTH_VECTOR_LEN);
560 memset(send_buffer + total_length, 0, len);
561 }
562 }
563
564 return total_length;
565 }
566
567
568 //
569 // Receive result from server
570 //
571
572 static int result_recv(DWORD host, WORD udp_port, char *buffer, int length, BYTE *vector, char *secretkey)
573 {
574 AUTH_HDR *auth;
575 int totallen, secretlen;
576 char reply_digest[AUTH_VECTOR_LEN];
577 char calc_digest[AUTH_VECTOR_LEN];
578 char szHostName[32];
579
580 auth = (AUTH_HDR *)buffer;
581 totallen = ntohs(auth->length);
582
583 if(totallen != length)
584 {
585 DbgPrintf(3, _T("RADIUS: Received invalid reply length from server (want %d - got %d)"), totallen, length);
586 return 8;
587 }
588
589 // Verify the reply digest
590 memcpy(reply_digest, auth->vector, AUTH_VECTOR_LEN);
591 memcpy(auth->vector, vector, AUTH_VECTOR_LEN);
592 secretlen = strlen(secretkey);
593 memcpy(buffer + length, secretkey, secretlen);
594 CalculateMD5Hash((BYTE *)auth, length + secretlen, (BYTE *)calc_digest);
595
596 if(memcmp(reply_digest, calc_digest, AUTH_VECTOR_LEN) != 0)
597 {
598 DbgPrintf(3, _T("RADIUS: Received invalid reply digest from server"));
599 }
600
601 IpToStr(ntohl(host), szHostName);
602 DbgPrintf(3, _T("RADIUS: Packet from host %s code=%d, id=%d, length=%d"),
603 szHostName, auth->code, auth->id, totallen);
604 return (auth->code == PW_AUTHENTICATION_REJECT) ? 1 : 0;
605 }
606
607
608 //
609 // Authenticate user via RADIUS
610 //
611
612 int RadiusAuth(char *cLogin, char *cPasswd)
613 {
614 AUTH_HDR *auth;
615 VALUE_PAIR *req, *vp;
616 DWORD server_ip, local_ip = 0;
617 struct sockaddr saremote;
618 struct sockaddr_in *sin;
619 struct timeval tv;
620 fd_set readfds;
621 socklen_t salen;
622 int port, result, length, i;
623 int nRetries, nTimeout;
624 SOCKET sockfd;
625 int send_buffer[512];
626 int recv_buffer[512];
627 BYTE vector[AUTH_VECTOR_LEN];
628 char szServer[256], szSecret[256];
629
630 ConfigReadStr("RADIUSServer", szServer, 256, "localhost");
631 ConfigReadStr("RADIUSSecret", szSecret, 256, "netxms");
632 port = ConfigReadInt("RADIUSPort", PW_AUTH_UDP_PORT);
633 nRetries = ConfigReadInt("RADIUSNumRetries", 5);
634 nTimeout = ConfigReadInt("RADIUSTimeout", 3);
635
636 // Set up AUTH structure.
637 memset(send_buffer, 0, sizeof(send_buffer));
638 auth = (AUTH_HDR *)send_buffer;
639 auth->code = PW_AUTHENTICATION_REQUEST;
640 random_vector(auth->vector);
641 auth->id = getpid() & 255;
642
643 // Create attribute chain
644 req = NULL;
645
646 // User name
647 vp = paircreate(PW_USER_NAME, PW_TYPE_STRING, "User-Name");
648 strncpy(vp->strvalue, cLogin, AUTH_STRING_LEN);
649 vp->length = min(strlen(cLogin), AUTH_STRING_LEN);
650 pairadd(&req, vp);
651
652 // Password
653 vp = paircreate(PW_PASSWORD, PW_TYPE_STRING, "User-Password");
654 vp->length = rad_pwencode(cPasswd, vp->strvalue, szSecret, (char *)auth->vector);
655 pairadd(&req, vp);
656
657 // Resolve hostname.
658 server_ip = ResolveHostName(szServer);
659 if ((server_ip == INADDR_NONE) || (server_ip == INADDR_ANY))
660 {
661 DbgPrintf(3, "RADIUS: cannot resolve server name \"%s\"", szServer);
662 pairfree(req);
663 return 3;
664 }
665
666 // Open a socket.
667 sockfd = socket(AF_INET, SOCK_DGRAM, 0);
668 if (sockfd < 0)
669 {
670 DbgPrintf(3, "RADIUS: Cannot create socket");
671 pairfree(req);
672 return 5;
673 }
674
675 sin = (struct sockaddr_in *)&saremote;
676 memset(sin, 0, sizeof (saremote));
677 sin->sin_family = AF_INET;
678 sin->sin_addr.s_addr = server_ip;
679 sin->sin_port = htons(port);
680
681 // Build final radius packet.
682 length = rad_build_packet(auth, sizeof(send_buffer),
683 req, NULL, szSecret, (char *)auth->vector, send_buffer);
684 memcpy(vector, auth->vector, sizeof(vector));
685 pairfree(req);
686
687 // Send the request we've built.
688 for(i = 0; i < nRetries; i++)
689 {
690 if (i > 0)
691 {
692 DbgPrintf(3, "RADIUS: Re-sending request...");
693 }
694 sendto(sockfd, (char *)auth, length, 0, &saremote, sizeof(struct sockaddr_in));
695
696 FD_ZERO(&readfds);
697 FD_SET(sockfd, &readfds);
698 tv.tv_sec = nTimeout;
699 tv.tv_usec = 0;
700 if (select(sockfd + 1, &readfds, NULL, NULL, &tv) == 0)
701 {
702 continue;
703 }
704
705 salen = sizeof(saremote);
706 result = recvfrom(sockfd, (char *)recv_buffer, sizeof(recv_buffer), 0, &saremote, &salen);
707 if (result >= 0)
708 {
709 break;
710 }
711
712 ThreadSleepMs(1000);
713 }
714
715 if (result > 0 && i < nRetries)
716 {
717 result = result_recv(sin->sin_addr.s_addr, sin->sin_port,
718 (char *)recv_buffer, result, vector,
719 szSecret);
720 }
721 else
722 {
723 result = 7;
724 }
725
726 closesocket(sockfd);
727 WriteLog((result == 0) ? MSG_RADIUS_AUTH_SUCCESS : MSG_RADIUS_AUTH_FAILED,
728 EVENTLOG_INFORMATION_TYPE, "ss", cLogin, szServer);
729
730 return result;
731 }
732
733 ///////////////////////////////////////////////////////////////////////////////
734 /*
735
736 $Log: not supported by cvs2svn $
737 Revision 1.5 2007/09/20 13:04:00 victor
738 - Most of GCC 4.2 warnings cleaned up
739 - Other minor fixes
740
741 Revision 1.4 2006/12/14 23:27:33 victor
742 fprintf(stderr, ...) changed to DbgPrintf(...)
743
744 Revision 1.3 2006/07/21 18:18:43 alk
745 code reformatted
746
747
748 */