62322e2290f3f65ff0e7b320e63856f38e1969b6
[public/netxms.git] / src / server / core / radius.cpp
1 /*
2 ** NetXMS - Network Management System
3 ** Copyright (C) 2003-2016 Victor Kirhenshtein
4 **
5 ** RADIUS client
6 ** This code is based on uRadiusLib (C) Gary Wallis, 2006.
7 **
8 ** This program is free software; you can redistribute it and/or modify
9 ** it under the terms of the GNU General Public License as published by
10 ** the Free Software Foundation; either version 2 of the License, or
11 ** (at your option) any later version.
12 **
13 ** This program is distributed in the hope that it will be useful,
14 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
15 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 ** GNU General Public License for more details.
17 **
18 ** You should have received a copy of the GNU General Public License
19 ** along with this program; if not, write to the Free Software
20 ** Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
21 **
22 ** File: radius.cpp
23 **
24 **/
25
26 #include "nxcore.h"
27
28 #ifdef _WITH_ENCRYPTION
29
30 #include <openssl/rand.h>
31 #include <openssl/md4.h>
32 #include <openssl/sha.h>
33 #include <openssl/des.h>
34
35 /**
36 * Calculate NT password hash
37 */
38 static void NtPasswordHash(const UCS2CHAR *passwd, BYTE *hash)
39 {
40 MD4_CTX ctx;
41 MD4_Init(&ctx);
42 MD4_Update(&ctx, passwd, ucs2_strlen(passwd) * sizeof(UCS2CHAR));
43 MD4_Final(hash, &ctx);
44 }
45
46 /**
47 * Encrypt given 8 byte data block using DES
48 */
49 static void MsChapChallengeResponse(const BYTE *challenge, const BYTE *passwdHash, BYTE *response)
50 {
51 for(int i = 0; i < 3; i++)
52 {
53 const BYTE *ki = &passwdHash[i * 7];
54 DES_cblock k;
55 k[0] = ki[0];
56 for(int b = 1; b < 7; b++)
57 k[b] = (ki[b - 1] << (8 - b)) | (ki[b] >> b);
58 k[7] = ki[6] << 1;
59 DES_set_odd_parity(&k);
60
61 DES_key_schedule ks;
62 DES_set_key_unchecked(&k, &ks);
63 DES_ecb_encrypt((DES_cblock *)challenge, (DES_cblock *)&response[i * 8], &ks, DES_ENCRYPT);
64 }
65 }
66
67 /**
68 * Calculate MS-CHAPv2 challenge
69 */
70 static void MsChap2ChallengeHash(const BYTE *peerChallenge, const BYTE *authChallenge, const char *login, BYTE *challenge)
71 {
72 SHA_CTX ctx;
73 SHA1_Init(&ctx);
74 SHA1_Update(&ctx, peerChallenge, 16);
75 SHA1_Update(&ctx, authChallenge, 16);
76 SHA1_Update(&ctx, login, strlen(login));
77 BYTE hash[SHA1_DIGEST_SIZE];
78 SHA1_Final(hash, &ctx);
79 memcpy(challenge, hash, 8);
80 }
81
82 #endif /* _WITH_ENCRYPTION */
83
84 #if !USE_RADCLI
85
86 #include "radius.h"
87
88 /**
89 * Add a pair at the end of a VALUE_PAIR list.
90 */
91 static void pairadd(VALUE_PAIR **first, VALUE_PAIR *newPair)
92 {
93 VALUE_PAIR *i;
94
95 if (*first == NULL)
96 {
97 *first = newPair;
98 return;
99 }
100 for(i = *first; i->next; i = i->next)
101 ;
102 i->next = newPair;
103 }
104
105 /**
106 * Release the memory used by a list of attribute-value pairs.
107 */
108 static void pairfree(VALUE_PAIR *pair)
109 {
110 VALUE_PAIR *next;
111
112 while(pair != NULL)
113 {
114 next = pair->next;
115 free(pair);
116 pair = next;
117 }
118 }
119
120 /**
121 * Create a new pair.
122 */
123 static VALUE_PAIR *paircreate(int attr, int type, const char *pszName)
124 {
125 VALUE_PAIR *vp;
126
127 if ((vp = (VALUE_PAIR *)malloc(sizeof(VALUE_PAIR))) == NULL)
128 {
129 return NULL;
130 }
131 memset(vp, 0, sizeof(VALUE_PAIR));
132 vp->attribute = attr;
133 vp->op = PW_OPERATOR_EQUAL;
134 vp->type = type;
135 if (pszName != NULL)
136 {
137 strcpy(vp->name, pszName);
138 }
139 else
140 {
141 sprintf(vp->name, "Attr-%d", attr);
142 }
143 switch (vp->type)
144 {
145 case PW_TYPE_INTEGER:
146 case PW_TYPE_IPADDR:
147 case PW_TYPE_DATE:
148 vp->length = 4;
149 break;
150 default:
151 vp->length = 0;
152 break;
153 }
154
155 return vp;
156 }
157
158 /**
159 * Generate AUTH_VECTOR_LEN worth of random data.
160 */
161 static void random_vector(unsigned char *vector)
162 {
163 static int did_srand = 0;
164 unsigned int i;
165 int n;
166
167 #if defined(__linux__) || defined(BSD)
168 if (!did_srand)
169 {
170 /*
171 * Try to use /dev/urandom to seed the
172 * random generator. Not all *BSDs have it
173 * but it doesn't hurt to try.
174 */
175 if ((n = open("/dev/urandom", O_RDONLY)) >= 0 &&
176 read(n, (char *)&i, sizeof(i)) == sizeof(i))
177 {
178 srandom(i);
179 did_srand = 1;
180 }
181 if (n >= 0)
182 {
183 close(n);
184 }
185 }
186
187 if (!did_srand)
188 {
189 /*
190 * Use more traditional way to seed.
191 */
192 srandom(time(NULL) + getpid());
193 did_srand = 1;
194 }
195
196 for (i = 0; i < AUTH_VECTOR_LEN;)
197 {
198 n = random();
199 memcpy(vector, &n, sizeof(int));
200 vector += sizeof(int);
201 i += sizeof(int);
202 }
203 #else
204
205 #ifndef RAND_MAX
206 # define RAND_MAX 32768
207 #endif
208 /*
209 * Assume the system has neither /dev/urandom
210 * nor random()/srandom() but just the old
211 * rand() / srand() functions.
212 */
213 if (!did_srand)
214 {
215 char garbage[8];
216 i = (unsigned int)time(NULL) + GetCurrentProcessId();
217 for (n = 0; n < 8; n++)
218 {
219 i += (garbage[n] << i);
220 }
221 srand(i);
222 did_srand = 1;
223 }
224
225 for (i = 0; i < AUTH_VECTOR_LEN;)
226 {
227 unsigned char c;
228 /*
229 * Don't use the lower bits, also don't use
230 * parts > RAND_MAX since they are zero.
231 */
232 n = rand() / (RAND_MAX / 256);
233 c = n;
234 memcpy(vector, &c, sizeof(c));
235 vector += sizeof(c);
236 i += sizeof(c);
237 }
238 #endif
239 }
240
241
242 //
243 // Encode password.
244 //
245 // Assume that "pwd_out" points to a buffer of at least AUTH_PASS_LEN bytes.
246 //
247 // Returns new length.
248 //
249
250 static int rad_pwencode(const char *pwd_in, char *pwd_out, const char *secret, const char *vector)
251 {
252 int passLen = (int)strlen(pwd_in);
253 if (passLen > AUTH_PASS_LEN)
254 passLen = AUTH_PASS_LEN;
255
256 char passbuf[AUTH_PASS_LEN];
257 memset(passbuf, 0, AUTH_PASS_LEN);
258 memcpy(passbuf, pwd_in, passLen);
259
260 int secretlen = (int)strlen(secret);
261 if (secretlen + AUTH_VECTOR_LEN > 256)
262 {
263 secretlen = 256 - AUTH_VECTOR_LEN;
264 }
265
266 char key[256];
267 strncpy(key, secret, sizeof(key) - AUTH_VECTOR_LEN);
268
269 int outLen = 0;
270 const char *currVector = vector;
271 while(outLen < passLen)
272 {
273 memcpy(key + secretlen, currVector, AUTH_VECTOR_LEN);
274 CalculateMD5Hash((BYTE *)key, secretlen + AUTH_VECTOR_LEN, (BYTE *)&pwd_out[outLen]);
275 currVector = &pwd_out[outLen];
276
277 for(int i = 0; i < AUTH_VECTOR_LEN; i++)
278 {
279 pwd_out[outLen] ^= passbuf[outLen];
280 outLen++;
281 }
282 }
283
284 return outLen;
285 }
286
287
288 /*
289 * Encrypt/decrypt string attributes, style 1.
290 *
291 * See RFC 2868, section 3.5 for details. Currently probably indeed
292 * only useful for Tunnel-Password, but why make it a special case,
293 * now we have a generic flags mechanism in place anyway...
294 *
295 * It's optimized a little for speed, but it could probably be better.
296 */
297
298 #define CLEAR_STRING_LEN 256 /* The RFC says it is */
299 #define SECRET_LEN 32 /* Max. in client.c */
300 #define MD5_LEN 16 /* The algorithm specifies it */
301 #define SALT_LEN 2 /* The RFC says it is */
302
303 static void encrypt_attr_style_1(char *secret, char *vector, VALUE_PAIR *vp)
304 {
305 char clear_buf[CLEAR_STRING_LEN];
306 char work_buf[SECRET_LEN + AUTH_VECTOR_LEN + SALT_LEN];
307 char digest[MD5_LEN];
308 char *i, *o;
309 WORD salt; /* salt in network order */
310 int clear_len;
311 int work_len;
312 int secret_len;
313 int n;
314
315 /*
316 * Create the string we'll actually be processing by copying up to 255
317 * bytes of original cleartext, padding it with zeroes to a multiple of
318 * 16 bytes and inserting a length octet in front.
319 */
320
321 /* Limit length */
322 clear_len = vp->length;
323 if (clear_len > CLEAR_STRING_LEN - 1)
324 {
325 clear_len = CLEAR_STRING_LEN - 1;
326 }
327
328 /* Write the 'original' length byte and copy the buffer */
329 *clear_buf = clear_len;
330 memcpy(clear_buf + 1, vp->strvalue, clear_len);
331
332 /* From now on, the length byte is included with the byte count */
333 clear_len++;
334
335 /* Pad the string to a multiple of 1 chunk */
336 if (clear_len % MD5_LEN)
337 {
338 memset(clear_buf+clear_len, 0, MD5_LEN - (clear_len % MD5_LEN));
339 }
340
341 /* Define input and number of chunks to process */
342 i = clear_buf;
343 clear_len = (clear_len + (MD5_LEN - 1)) / MD5_LEN;
344
345 /* Define output and starting length */
346 o = vp->strvalue;
347 vp->length = sizeof(salt);
348
349 /*
350 * Fill in salt. Must be unique per attribute that uses it in the same
351 * packet, and the most significant bit must be set - see RFC 2868.
352 *
353 * FIXME: this _may_ be too simple. For now we just take the vp
354 * pointer, which should be different between attributes, xor-ed with
355 * the first longword of the vector to make it a little more unique.
356 *
357 * Oh, and sizeof(long) always == sizeof(void*) in our part of the
358 * universe, right? (*BSD, Solaris, Linux, DEC Unix...)
359 */
360 salt = htons((WORD)((((long)vp ^ *(long *)vector) & 0xffff) | 0x8000));
361 memcpy(o, &salt, sizeof(salt));
362 o += sizeof(salt);
363
364 /* Create a first working buffer to calc the MD5 hash over */
365 secret_len = (int)strlen(secret); /* already limited by read_clients */
366 memcpy(work_buf, secret, secret_len);
367 memcpy(work_buf + secret_len, vector, AUTH_VECTOR_LEN);
368 memcpy(work_buf + secret_len + AUTH_VECTOR_LEN, &salt, sizeof(salt));
369 work_len = secret_len + AUTH_VECTOR_LEN + sizeof(salt);
370
371 for(; clear_len; clear_len--)
372 {
373 /* Get the digest */
374 CalculateMD5Hash((BYTE *)work_buf, work_len, (BYTE *)digest);
375
376 /* Xor the clear text to get the output chunk and next buffer */
377 for(n = 0; n < MD5_LEN; n++)
378 {
379 *(work_buf + secret_len + n) = *o++ = *i++ ^ digest[n];
380 }
381
382 /* This is the size of the next working buffer */
383 work_len = secret_len + MD5_LEN;
384
385 /* Increment the output length */
386 vp->length += MD5_LEN;
387 }
388 }
389
390
391 /**
392 * void encrypt_attr(char *secret, char *vector, VALUE_PAIR *vp);
393 *
394 * Encrypts vp->strvalue using style vp->flags.encrypt, possibly using
395 * a request authenticator passed in vector and the shared secret.
396 *
397 * This should always succeed.
398 */
399 static void encrypt_attr(char *secret, char *vector, VALUE_PAIR *vp)
400 {
401 switch(vp->flags.encrypt)
402 {
403 case 0:
404 /* Normal, cleartext. */
405 break;
406
407 case 1:
408 /* Tunnel Password (see RFC 2868, section 3.5). */
409 encrypt_attr_style_1(secret, vector, vp);
410 break;
411
412 default:
413 /* Unknown style - don't send the cleartext! */
414 vp->length = 19;
415 memcpy(vp->strvalue, "UNKNOWN_ENCR_METHOD", vp->length);
416 nxlog_write(MSG_RADIUS_UNKNOWN_ENCR_METHOD, EVENTLOG_ERROR_TYPE,
417 "dd", vp->flags.encrypt, vp->attribute);
418 }
419 }
420
421 /**
422 * Build radius packet. We assume that the header part
423 * of AUTH_HDR has already been filled in, we just
424 * fill auth->data with the A/V pairs from reply.
425 */
426 static int rad_build_packet(AUTH_HDR *auth, int auth_len,
427 VALUE_PAIR *reply, char *msg, char *secret,
428 char *vector, int *send_buffer)
429 {
430 VALUE_PAIR *vp;
431 u_short total_length;
432 u_char *ptr, *length_ptr;
433 char digest[16];
434 int vendorpec;
435 int len;
436 UINT32 lvalue;
437
438 total_length = AUTH_HDR_LEN;
439
440 /*
441 * Load up the configuration values for the user
442 */
443 ptr = auth->data;
444 for (vp = reply; vp; vp = vp->next)
445 {
446 /*
447 * Check for overflow.
448 */
449 if (total_length + vp->length + 16 >= auth_len)
450 {
451 break;
452 }
453
454 /*
455 * This could be a vendor-specific attribute.
456 */
457 length_ptr = NULL;
458 if ((vendorpec = VENDOR(vp->attribute)) > 0)
459 {
460 *ptr++ = PW_VENDOR_SPECIFIC;
461 length_ptr = ptr;
462 *ptr++ = 6;
463 lvalue = htonl(vendorpec);
464 memcpy(ptr, &lvalue, 4);
465 ptr += 4;
466 total_length += 6;
467 }
468 else if (vp->attribute > 0xff)
469 {
470 /*
471 * Ignore attributes > 0xff
472 */
473 continue;
474 }
475 else
476 {
477 vendorpec = 0;
478 }
479
480 #ifdef ATTRIB_NMC
481 if (vendorpec == VENDORPEC_USR)
482 {
483 lvalue = htonl(vp->attribute & 0xFFFF);
484 memcpy(ptr, &lvalue, 4);
485 total_length += 2;
486 *length_ptr += 2;
487 ptr += 4;
488 }
489 else
490 #endif
491 *ptr++ = (vp->attribute & 0xFF);
492
493 switch(vp->type)
494 {
495
496 case PW_TYPE_STRING:
497 /*
498 * FIXME: this is just to make sure but
499 * should NOT be needed. In fact I have no
500 * idea if it is needed :)
501 */
502 if (vp->length == 0 && vp->strvalue[0] != 0)
503 {
504 vp->length = (int)strlen(vp->strvalue);
505 }
506 if (vp->length >= AUTH_STRING_LEN)
507 {
508 vp->length = AUTH_STRING_LEN - 1;
509 }
510
511 /*
512 * If the flags indicate a encrypted attribute, handle
513 * it here. I don't want to go through the reply list
514 * another time just for transformations like this.
515 */
516 if (vp->flags.encrypt)
517 {
518 encrypt_attr(secret, vector, vp);
519 }
520
521 /*
522 * vp->length is the length of the string value; len
523 * is the length of the string field in the packet.
524 * Normally, these are the same, but if a tag is
525 * inserted only len will reflect this.
526 *
527 * Bug fixed: for tagged attributes with 'tunnel-pwd'
528 * encryption, the tag is *always* inserted, regardless
529 * of its value! (Another strange thing in RFC 2868...)
530 */
531 len = vp->length + (vp->flags.has_tag && (TAG_VALID(vp->flags.tag) || vp->flags.encrypt == 1));
532
533 #ifdef ATTRIB_NMC
534 if (vendorpec != VENDORPEC_USR)
535 #endif
536 *ptr++ = len + 2;
537 if (length_ptr)
538 *length_ptr += len + 2;
539
540 /* Insert the tag (sorry about the fast ugly test...) */
541 if (len > vp->length) *ptr++ = vp->flags.tag;
542
543 /* Use the original length of the string value */
544 memcpy(ptr, vp->strvalue, vp->length);
545 ptr += vp->length; /* here too */
546 total_length += len + 2;
547 break;
548
549 case PW_TYPE_INTEGER:
550 case PW_TYPE_DATE:
551 case PW_TYPE_IPADDR:
552 len = sizeof(UINT32) + (vp->flags.has_tag && vp->type != PW_TYPE_INTEGER);
553 #ifdef ATTRIB_NMC
554 if (vendorpec != VENDORPEC_USR)
555 #endif
556 *ptr++ = len + 2;
557 if (length_ptr) *length_ptr += len + 2;
558
559 /* Handle tags */
560 lvalue = vp->lvalue;
561 if (vp->flags.has_tag)
562 {
563 if (vp->type == PW_TYPE_INTEGER)
564 {
565 /* Tagged integer: MSB is tag */
566 lvalue = (lvalue & 0xffffff) | ((vp->flags.tag & 0xff) << 24);
567 }
568 else
569 {
570 /* Something else: insert the tag */
571 *ptr++ = vp->flags.tag;
572 }
573 }
574 lvalue = htonl(lvalue);
575 memcpy(ptr, &lvalue, sizeof(UINT32));
576 ptr += sizeof(UINT32);
577 total_length += len + 2;
578 break;
579
580 default:
581 break;
582 }
583 }
584
585 /*
586 * Append the user message
587 * FIXME: add multiple PW_REPLY_MESSAGEs if it
588 * doesn't fit into one.
589 */
590 if (msg && msg[0])
591 {
592 len = (int)strlen(msg);
593 if (len > 0 && len < AUTH_STRING_LEN-1)
594 {
595 *ptr++ = PW_REPLY_MESSAGE;
596 *ptr++ = len + 2;
597 memcpy(ptr, msg, len);
598 total_length += len + 2;
599 }
600 }
601
602 auth->length = htons(total_length);
603
604 if (auth->code != PW_AUTHENTICATION_REQUEST && auth->code != PW_STATUS_SERVER)
605 {
606 /*
607 * Append secret and calculate the response digest
608 */
609 len = (int)strlen(secret);
610 if (total_length + len < auth_len)
611 {
612 memcpy((char *)auth + total_length, secret, len);
613 CalculateMD5Hash((BYTE *)auth, total_length + len, (BYTE *)digest);
614 memcpy(auth->vector, digest, AUTH_VECTOR_LEN);
615 memset(send_buffer + total_length, 0, len);
616 }
617 }
618
619 return total_length;
620 }
621
622 /**
623 * Receive result from server
624 */
625 static int result_recv(const InetAddress& host, WORD udp_port, char *buffer, int length, BYTE *vector, char *secretkey)
626 {
627 AUTH_HDR *auth;
628 int totallen, secretlen;
629 char reply_digest[AUTH_VECTOR_LEN];
630 char calc_digest[AUTH_VECTOR_LEN];
631 TCHAR szHostName[32];
632
633 auth = (AUTH_HDR *)buffer;
634 totallen = ntohs(auth->length);
635
636 if(totallen != length)
637 {
638 DbgPrintf(3, _T("RADIUS: Received invalid reply length from server (want %d - got %d)"), totallen, length);
639 return 8;
640 }
641
642 // Verify the reply digest
643 memcpy(reply_digest, auth->vector, AUTH_VECTOR_LEN);
644 memcpy(auth->vector, vector, AUTH_VECTOR_LEN);
645 secretlen = (int)strlen(secretkey);
646 memcpy(buffer + length, secretkey, secretlen);
647 CalculateMD5Hash((BYTE *)auth, length + secretlen, (BYTE *)calc_digest);
648
649 if(memcmp(reply_digest, calc_digest, AUTH_VECTOR_LEN) != 0)
650 {
651 DbgPrintf(3, _T("RADIUS: Received invalid reply digest from server"));
652 }
653
654 host.toString(szHostName);
655 DbgPrintf(3, _T("RADIUS: Packet from host %s code=%d, id=%d, length=%d"), szHostName, auth->code, auth->id, totallen);
656 return (auth->code == PW_AUTHENTICATION_REJECT) ? 1 : 0;
657 }
658
659 /**
660 * Authenticate user via RADIUS using primary or secondary server
661 */
662 static int DoRadiusAuth(const char *login, const char *passwd, bool useSecondaryServer, char *serverName)
663 {
664 AUTH_HDR *auth;
665 VALUE_PAIR *req, *vp;
666 int port, result = 0, length, i;
667 int nRetries, nTimeout;
668 SOCKET sockfd;
669 int send_buffer[512];
670 int recv_buffer[512];
671 BYTE vector[AUTH_VECTOR_LEN];
672 char szSecret[256];
673
674 ConfigReadStrA(useSecondaryServer ? _T("RADIUSSecondaryServer") : _T("RADIUSServer"), serverName, 256, "none");
675 ConfigReadStrA(useSecondaryServer ? _T("RADIUSSecondarySecret"): _T("RADIUSSecret"), szSecret, 256, "netxms");
676 port = ConfigReadInt(useSecondaryServer ? _T("RADIUSSecondaryPort") : _T("RADIUSPort"), PW_AUTH_UDP_PORT);
677 nRetries = ConfigReadInt(_T("RADIUSNumRetries"), 5);
678 nTimeout = ConfigReadInt(_T("RADIUSTimeout"), 3);
679
680 if (!strcmp(serverName, "none"))
681 {
682 DbgPrintf(4, _T("RADIUS: %s server set to none, skipping"), useSecondaryServer ? _T("secondary") : _T("primary"));
683 return 10;
684 }
685
686 // Set up AUTH structure.
687 memset(send_buffer, 0, sizeof(send_buffer));
688 auth = (AUTH_HDR *)send_buffer;
689 auth->code = PW_AUTHENTICATION_REQUEST;
690 random_vector(auth->vector);
691 auth->id = (BYTE)(GetCurrentProcessId() & 255);
692
693 // Create attribute chain
694 req = NULL;
695
696 // User name
697 vp = paircreate(PW_USER_NAME, PW_TYPE_STRING, "User-Name");
698 strncpy(vp->strvalue, login, AUTH_STRING_LEN);
699 vp->length = std::min((int)strlen(login), AUTH_STRING_LEN);
700 pairadd(&req, vp);
701
702 char authMethod[16];
703 ConfigReadStrA(_T("RADIUSAuthMethod"), authMethod, 16, "PAP");
704 nxlog_debug(4, _T("RADIUS: authenticating user %hs on server %hs using %hs"), login, serverName, authMethod);
705 if (!stricmp(authMethod, "PAP"))
706 {
707 vp = paircreate(PW_PASSWORD, PW_TYPE_STRING, "User-Password");
708 vp->length = rad_pwencode(passwd, vp->strvalue, szSecret, (char *)auth->vector);
709 pairadd(&req, vp);
710 }
711 else if (!stricmp(authMethod, "CHAP"))
712 {
713 char challenge[16];
714 #ifdef _WITH_ENCRYPTION
715 RAND_bytes((BYTE *)challenge, 16);
716 #else
717 for(int i = 0; i < 16; i++)
718 challenge[i] = (char)(rand() % 256);
719 #endif
720 vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_STRING, "CHAP-Challenge");
721 memcpy(vp->strvalue, challenge, 16);
722 vp->length = 16;
723 pairadd(&req, vp);
724
725 BYTE temp[256];
726 temp[0] = (BYTE)(rand() % 255);
727 size_t pwdlen = strlen(passwd);
728 memcpy(&temp[1], passwd, pwdlen);
729 memcpy(&temp[pwdlen + 1], challenge, 16);
730
731 char response[17];
732 response[0] = (char)temp[0];
733 CalculateMD5Hash(temp, pwdlen + 17, (BYTE *)&response[1]);
734 vp = paircreate(PW_CHAP_PASSWORD, PW_TYPE_STRING, "CHAP-Password");
735 memcpy(vp->strvalue, response, 17);
736 vp->length = 17;
737 pairadd(&req, vp);
738 }
739 #ifdef _WITH_ENCRYPTION
740 else if (!stricmp(authMethod, "MS-CHAPv1"))
741 {
742 BYTE challenge[8];
743 RAND_bytes(challenge, 8);
744 vp = paircreate(PW_MS_CHAP_CHALLENGE, PW_TYPE_STRING, "MS-CHAP-Challenge");
745 memcpy(vp->strvalue, challenge, 8);
746 vp->length = 8;
747 pairadd(&req, vp);
748
749 UCS2CHAR upasswd[256];
750 utf8_to_ucs2(passwd, -1, upasswd, 256);
751
752 BYTE passwdHash[21];
753 NtPasswordHash(upasswd, passwdHash);
754 memset(&passwdHash[16], 0, 5);
755
756 BYTE response[50];
757 response[0] = (BYTE)(rand() % 255);
758 response[1] = 1;
759 memset(&response[2], 0, 24); // LM challenge response
760 MsChapChallengeResponse(challenge, passwdHash, &response[26]);
761 vp = paircreate(PW_MS_CHAP_RESPONSE, PW_TYPE_STRING, "MS-CHAP-Response");
762 memcpy(vp->strvalue, response, 50);
763 vp->length = 50;
764 pairadd(&req, vp);
765 }
766 else if (!stricmp(authMethod, "MS-CHAPv2"))
767 {
768 BYTE authChallenge[16];
769 RAND_bytes(authChallenge, 16);
770 vp = paircreate(PW_MS_CHAP_CHALLENGE, PW_TYPE_STRING, "MS-CHAP-Challenge");
771 memcpy(vp->strvalue, authChallenge, 16);
772 vp->length = 16;
773 pairadd(&req, vp);
774
775 UCS2CHAR upasswd[256];
776 utf8_to_ucs2(passwd, -1, upasswd, 256);
777
778 BYTE passwdHash[21];
779 NtPasswordHash(upasswd, passwdHash);
780 memset(&passwdHash[16], 0, 5);
781
782 BYTE response[50];
783 response[0] = (BYTE)(rand() % 255);
784 response[1] = 0;
785 RAND_bytes(&response[2], 16); // peer challenge
786 memset(&response[18], 0, 8); // reserved bytes
787 BYTE challenge[8];
788 MsChap2ChallengeHash(&response[2], authChallenge, login, challenge);
789 MsChapChallengeResponse(challenge, passwdHash, &response[26]);
790 vp = paircreate(PW_MS_CHAP2_RESPONSE, PW_TYPE_STRING, "MS-CHAP2-Response");
791 memcpy(vp->strvalue, response, 50);
792 vp->length = 50;
793 pairadd(&req, vp);
794 }
795 #endif
796 else
797 {
798 nxlog_debug(3, _T("RADIUS: unknown authentication method %hs"), authMethod);
799 pairfree(req);
800 return 11;
801 }
802
803 // Resolve hostname.
804 InetAddress serverAddr = InetAddress::resolveHostName(serverName);
805 if (!serverAddr.isValidUnicast())
806 {
807 nxlog_debug(3, _T("RADIUS: cannot resolve server name \"%hs\""), serverName);
808 pairfree(req);
809 return 3;
810 }
811
812 // Open a socket.
813 sockfd = socket(serverAddr.getFamily(), SOCK_DGRAM, 0);
814 if (sockfd == INVALID_SOCKET)
815 {
816 nxlog_debug(3, _T("RADIUS: Cannot create socket"));
817 pairfree(req);
818 return 5;
819 }
820
821 SockAddrBuffer sa;
822 serverAddr.fillSockAddr(&sa, port);
823
824 // Build final radius packet.
825 length = rad_build_packet(auth, sizeof(send_buffer), req, NULL, szSecret, (char *)auth->vector, send_buffer);
826 memcpy(vector, auth->vector, sizeof(vector));
827 pairfree(req);
828
829 // Send the request we've built.
830 SocketPoller sp;
831 for(i = 0; i < nRetries; i++)
832 {
833 if (i > 0)
834 {
835 nxlog_debug(5, _T("RADIUS: Re-sending request..."));
836 }
837 sendto(sockfd, (char *)auth, length, 0, (struct sockaddr *)&sa, SA_LEN((struct sockaddr *)&sa));
838
839 sp.reset();
840 sp.add(sockfd);
841 if (sp.poll(nTimeout * 1000) <= 0)
842 {
843 continue;
844 }
845
846 SockAddrBuffer sarecv;
847 socklen_t salen = sizeof(sa);
848 result = recvfrom(sockfd, (char *)recv_buffer, sizeof(recv_buffer), 0, (struct sockaddr *)&sarecv, &salen);
849 InetAddress addrRecv = InetAddress::createFromSockaddr((struct sockaddr *)&sarecv);
850 if ((result >= 0) && addrRecv.equals(serverAddr) && (SA_PORT(&sarecv) == SA_PORT(&sa)))
851 {
852 break;
853 }
854
855 ThreadSleepMs(1000);
856 }
857
858 if (result > 0 && i < nRetries)
859 {
860 result = result_recv(serverAddr, port, (char *)recv_buffer, result, vector, szSecret);
861 }
862 else
863 {
864 result = 7;
865 }
866
867 closesocket(sockfd);
868 return result;
869 }
870
871 /**
872 * Check if authentication can be retried on other server
873 */
874 static bool CanRetry(int result)
875 {
876 return (result == 3) || (result == 7) || (result == 10); // Bad server name, timeout, comm. error, or server not configured
877 }
878
879 #else /* USE_RADCLI */
880
881 #if HAVE_RADCLI_RADCLI_H
882 #include <radcli/radcli.h>
883 #endif
884
885 /**
886 * Add pair to request and check for errors
887 */
888 #define PAIR_ADD_VENDOR_LEN(vendor, type, value, len) do { \
889 if (rc_avpair_add(rh, &request, (type), (value), len, (vendor)) == NULL) \
890 { \
891 rc_destroy(rh); \
892 rc_avpair_free(request); \
893 return ERROR_RC; \
894 } } while(0)
895 #define PAIR_ADD_LEN(type, value, len) PAIR_ADD_VENDOR_LEN(0, (type), (value), (len))
896 #define PAIR_ADD(type, value) PAIR_ADD_VENDOR_LEN(0, (type), (value), -1)
897
898 /**
899 * Authenticate user via RADIUS using primary or secondary server
900 */
901 static int DoRadiusAuth(const char *login, const char *passwd, bool useSecondaryServer, char *serverName)
902 {
903 ConfigReadStrA(useSecondaryServer ? _T("RADIUSSecondaryServer") : _T("RADIUSServer"), serverName, 256, "none");
904 if (!strcmp(serverName, "none"))
905 {
906 DbgPrintf(4, _T("RADIUS: %s server set to none, skipping"), useSecondaryServer ? _T("secondary") : _T("primary"));
907 return -127;
908 }
909
910 int port = ConfigReadInt(useSecondaryServer ? _T("RADIUSSecondaryPort") : _T("RADIUSPort"), PW_AUTH_UDP_PORT);
911
912 char secret[256];
913 ConfigReadStrA(useSecondaryServer ? _T("RADIUSSecondarySecret"): _T("RADIUSSecret"), secret, 256, "netxms");
914
915 rc_handle *rh = rc_new();
916 if (rc_config_init(rh) == NULL)
917 return ERROR_RC;
918 TCHAR dictionaryFile[MAX_PATH];
919 GetNetXMSDirectory(nxDirShare, dictionaryFile);
920 _tcscat(dictionaryFile, SFILE_RADDICT);
921 #ifdef UNICODE
922 char dictionaryFileUTF8[MAX_PATH];
923 WideCharToMultiByte(CP_UTF8, 0, dictionaryFile, -1, dictionaryFileUTF8, MAX_PATH, NULL, NULL);
924 if (rc_read_dictionary(rh, dictionaryFileUTF8) != 0)
925 #else
926 if (rc_read_dictionary(rh, dictionaryFile) != 0)
927 #endif
928 return ERROR_RC; // rc_read_dictionary will destroy rh on failure
929
930 char connectString[1024];
931 snprintf(connectString, 1024, "%s:%d:%s", serverName, port, secret);
932 rc_add_config(rh, "authserver", connectString, __FILE__, __LINE__);
933
934 char temp[64];
935 ConfigReadStrA(_T("RADIUSNumRetries"), temp, 64, "5");
936 rc_add_config(rh, "radius_retries", temp, __FILE__, __LINE__);
937
938 ConfigReadStrA(_T("RADIUSTimeout"), temp, 64, "3");
939 rc_add_config(rh, "radius_timeout", temp, __FILE__, __LINE__);
940
941 VALUE_PAIR *request = NULL;
942 PAIR_ADD(PW_USER_NAME, login);
943 UINT32 service = PW_AUTHENTICATE_ONLY;
944 PAIR_ADD(PW_SERVICE_TYPE, &service);
945
946 char authMethod[16];
947 ConfigReadStrA(_T("RADIUSAuthMethod"), authMethod, 16, "PAP");
948 nxlog_debug(4, _T("RADIUS: authenticating user %hs on server %hs using %hs"), login, serverName, authMethod);
949 if (!stricmp(authMethod, "PAP"))
950 {
951 PAIR_ADD(PW_USER_PASSWORD, passwd);
952 }
953 else if (!stricmp(authMethod, "CHAP"))
954 {
955 char challenge[16];
956 #ifdef _WITH_ENCRYPTION
957 RAND_bytes((BYTE *)challenge, 16);
958 #else
959 for(int i = 0; i < 16; i++)
960 challenge[i] = (char)(rand() % 256);
961 #endif
962 PAIR_ADD_LEN(PW_CHAP_CHALLENGE, challenge, 16);
963
964 BYTE temp[256];
965 temp[0] = (BYTE)(rand() % 255);
966 size_t pwdlen = strlen(passwd);
967 memcpy(&temp[1], passwd, pwdlen);
968 memcpy(&temp[pwdlen + 1], challenge, 16);
969
970 char response[17];
971 response[0] = (char)temp[0];
972 CalculateMD5Hash(temp, pwdlen + 17, (BYTE *)&response[1]);
973 PAIR_ADD_LEN(PW_CHAP_PASSWORD, response, 17);
974 }
975 #ifdef _WITH_ENCRYPTION
976 else if (!stricmp(authMethod, "MS-CHAPv1"))
977 {
978 BYTE challenge[8];
979 RAND_bytes(challenge, 8);
980 PAIR_ADD_VENDOR_LEN(VENDOR_MICROSOFT, PW_MS_CHAP_CHALLENGE, challenge, 8);
981
982 UCS2CHAR upasswd[256];
983 utf8_to_ucs2(passwd, -1, upasswd, 256);
984
985 BYTE passwdHash[21];
986 NtPasswordHash(upasswd, passwdHash);
987 memset(&passwdHash[16], 0, 5);
988
989 BYTE response[50];
990 response[0] = (BYTE)(rand() % 255);
991 response[1] = 1;
992 memset(&response[2], 0, 24); // LM challenge response
993 MsChapChallengeResponse(challenge, passwdHash, &response[26]);
994 PAIR_ADD_VENDOR_LEN(VENDOR_MICROSOFT, PW_MS_CHAP_RESPONSE, response, 50);
995 }
996 else if (!stricmp(authMethod, "MS-CHAPv2"))
997 {
998 BYTE authChallenge[16];
999 RAND_bytes(authChallenge, 16);
1000 PAIR_ADD_VENDOR_LEN(VENDOR_MICROSOFT, PW_MS_CHAP_CHALLENGE, authChallenge, 16);
1001
1002 UCS2CHAR upasswd[256];
1003 utf8_to_ucs2(passwd, -1, upasswd, 256);
1004
1005 BYTE passwdHash[21];
1006 NtPasswordHash(upasswd, passwdHash);
1007 memset(&passwdHash[16], 0, 5);
1008
1009 BYTE response[50];
1010 response[0] = (BYTE)(rand() % 255);
1011 response[1] = 0;
1012 RAND_bytes(&response[2], 16); // peer challenge
1013 memset(&response[18], 0, 8); // reserved bytes
1014 BYTE challenge[8];
1015 MsChap2ChallengeHash(&response[2], authChallenge, login, challenge);
1016 MsChapChallengeResponse(challenge, passwdHash, &response[26]);
1017 PAIR_ADD_VENDOR_LEN(VENDOR_MICROSOFT, PW_MS_CHAP2_RESPONSE, response, 50);
1018 }
1019 #endif
1020 else
1021 {
1022 nxlog_debug(3, _T("RADIUS: unknown authentication method %hs"), authMethod);
1023 rc_destroy(rh);
1024 rc_avpair_free(request);
1025 return ERROR_RC;
1026 }
1027
1028 VALUE_PAIR *response = NULL;
1029 int result = rc_auth(rh, 0, request, &response, NULL);
1030 rc_destroy(rh);
1031 rc_avpair_free(request);
1032 rc_avpair_free(response);
1033 return result;
1034 }
1035
1036 /**
1037 * Check if authentication can be retried on other server
1038 */
1039 static bool CanRetry(int result)
1040 {
1041 return (result == -127) || (result == BADRESP_RC) || (result == TIMEOUT_RC); // Bad server name, timeout, comm. error, or server not configured
1042 }
1043
1044 #endif
1045
1046 /**
1047 * Authenticate user via RADIUS
1048 */
1049 bool RadiusAuth(const TCHAR *login, const TCHAR *passwd)
1050 {
1051 static bool useSecondary = false;
1052
1053 char server[256], rlogin[256], rpasswd[256];
1054 #ifdef UNICODE
1055 WideCharToMultiByte(CP_UTF8, 0, login, -1, rlogin, 256, NULL, NULL);
1056 WideCharToMultiByte(CP_UTF8, 0, passwd, -1, rpasswd, 256, NULL, NULL);
1057 #else
1058 mb_to_utf8(login, -1, rlogin, 256);
1059 mb_to_utf8(passwd, -1, rpasswd, 256);
1060 #endif
1061 rlogin[255] = 0;
1062 rpasswd[255] = 0;
1063 int result = DoRadiusAuth(rlogin, rpasswd, useSecondary, server);
1064 nxlog_debug(4, _T("RADIUS: DoRadiusAuth returned %d for user %s"), result, login);
1065 if (CanRetry(result))
1066 {
1067 useSecondary = !useSecondary;
1068 nxlog_debug(3, _T("RADIUS: unable to use %s server, switching to %s"), useSecondary ? _T("primary") : _T("secondary"), useSecondary ? _T("secondary") : _T("primary"));
1069 result = DoRadiusAuth(rlogin, rpasswd, useSecondary, server);
1070 nxlog_debug(4, _T("RADIUS: DoRadiusAuth returned %d for user %s"), result, login);
1071 }
1072 nxlog_write((result == 0) ? MSG_RADIUS_AUTH_SUCCESS : MSG_RADIUS_AUTH_FAILED, NXLOG_INFO, "sm", login, server);
1073 return result == 0;
1074 }