fixed compiler warnings; minor refactoring
[public/netxms.git] / src / server / core / radius.cpp
CommitLineData
5039dede
AK
1/*
2 ** NetXMS - Network Management System
b4cf3199 3 ** Copyright (C) 2003-2016 Victor Kirhenshtein
5039dede
AK
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"
5039dede 27
b5864df1
VK
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 */
38static 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 */
49static 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 */
70static 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
257a4037 84#if !USE_RADCLI
5039dede 85
257a4037 86#include "radius.h"
5039dede 87
257a4037
VK
88/**
89 * Add a pair at the end of a VALUE_PAIR list.
90 */
5039dede
AK
91static 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
257a4037
VK
105/**
106 * Release the memory used by a list of attribute-value pairs.
107 */
5039dede
AK
108static 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
257a4037
VK
120/**
121 * Create a new pair.
122 */
5039dede
AK
123static 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
257a4037
VK
158/**
159 * Generate AUTH_VECTOR_LEN worth of random data.
160 */
5039dede
AK
161static 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];
330a1815 216 i = (unsigned int)time(NULL) + GetCurrentProcessId();
5039dede
AK
217 for (n = 0; n < 8; n++)
218 {
912b994d 219 i += (garbage[n] << i);
5039dede
AK
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
9bfc9a6b 250static int rad_pwencode(const char *pwd_in, char *pwd_out, const char *secret, const char *vector)
5039dede 251{
8ec102ac
VK
252 int passLen = (int)strlen(pwd_in);
253 if (passLen > AUTH_PASS_LEN)
254 passLen = AUTH_PASS_LEN;
5039dede 255
8ec102ac 256 char passbuf[AUTH_PASS_LEN];
5039dede 257 memset(passbuf, 0, AUTH_PASS_LEN);
8ec102ac 258 memcpy(passbuf, pwd_in, passLen);
5039dede 259
8ec102ac 260 int secretlen = (int)strlen(secret);
5039dede
AK
261 if (secretlen + AUTH_VECTOR_LEN > 256)
262 {
263 secretlen = 256 - AUTH_VECTOR_LEN;
264 }
5039dede 265
8ec102ac
VK
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)
5039dede 272 {
8ec102ac
VK
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 }
5039dede
AK
282 }
283
8ec102ac 284 return outLen;
5039dede
AK
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
303static 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;
912b994d 309 WORD salt; /* salt in network order */
5039dede
AK
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 */
b95f153a
VK
360#ifdef _WIN32
361 salt = htons((WORD)((((ULONG_PTR)vp ^ *(ULONG_PTR *)vector) & 0xffff) | 0x8000));
362#else
912b994d 363 salt = htons((WORD)((((long)vp ^ *(long *)vector) & 0xffff) | 0x8000));
b95f153a 364#endif
5039dede
AK
365 memcpy(o, &salt, sizeof(salt));
366 o += sizeof(salt);
367
368 /* Create a first working buffer to calc the MD5 hash over */
a23d8e0d 369 secret_len = (int)strlen(secret); /* already limited by read_clients */
5039dede
AK
370 memcpy(work_buf, secret, secret_len);
371 memcpy(work_buf + secret_len, vector, AUTH_VECTOR_LEN);
372 memcpy(work_buf + secret_len + AUTH_VECTOR_LEN, &salt, sizeof(salt));
373 work_len = secret_len + AUTH_VECTOR_LEN + sizeof(salt);
374
375 for(; clear_len; clear_len--)
376 {
377 /* Get the digest */
378 CalculateMD5Hash((BYTE *)work_buf, work_len, (BYTE *)digest);
379
380 /* Xor the clear text to get the output chunk and next buffer */
381 for(n = 0; n < MD5_LEN; n++)
382 {
383 *(work_buf + secret_len + n) = *o++ = *i++ ^ digest[n];
384 }
385
386 /* This is the size of the next working buffer */
387 work_len = secret_len + MD5_LEN;
388
389 /* Increment the output length */
390 vp->length += MD5_LEN;
391 }
392}
393
394
b5864df1 395/**
5039dede
AK
396 * void encrypt_attr(char *secret, char *vector, VALUE_PAIR *vp);
397 *
398 * Encrypts vp->strvalue using style vp->flags.encrypt, possibly using
399 * a request authenticator passed in vector and the shared secret.
400 *
401 * This should always succeed.
402 */
5039dede
AK
403static void encrypt_attr(char *secret, char *vector, VALUE_PAIR *vp)
404{
405 switch(vp->flags.encrypt)
406 {
407 case 0:
408 /* Normal, cleartext. */
409 break;
410
411 case 1:
412 /* Tunnel Password (see RFC 2868, section 3.5). */
413 encrypt_attr_style_1(secret, vector, vp);
414 break;
415
416 default:
417 /* Unknown style - don't send the cleartext! */
418 vp->length = 19;
419 memcpy(vp->strvalue, "UNKNOWN_ENCR_METHOD", vp->length);
420 nxlog_write(MSG_RADIUS_UNKNOWN_ENCR_METHOD, EVENTLOG_ERROR_TYPE,
421 "dd", vp->flags.encrypt, vp->attribute);
422 }
423}
424
257a4037
VK
425/**
426 * Build radius packet. We assume that the header part
427 * of AUTH_HDR has already been filled in, we just
428 * fill auth->data with the A/V pairs from reply.
429 */
5039dede
AK
430static int rad_build_packet(AUTH_HDR *auth, int auth_len,
431 VALUE_PAIR *reply, char *msg, char *secret,
432 char *vector, int *send_buffer)
433{
434 VALUE_PAIR *vp;
435 u_short total_length;
436 u_char *ptr, *length_ptr;
437 char digest[16];
438 int vendorpec;
439 int len;
967893bb 440 UINT32 lvalue;
5039dede
AK
441
442 total_length = AUTH_HDR_LEN;
443
444 /*
445 * Load up the configuration values for the user
446 */
447 ptr = auth->data;
448 for (vp = reply; vp; vp = vp->next)
449 {
450 /*
451 * Check for overflow.
452 */
453 if (total_length + vp->length + 16 >= auth_len)
454 {
455 break;
456 }
457
458 /*
459 * This could be a vendor-specific attribute.
460 */
461 length_ptr = NULL;
462 if ((vendorpec = VENDOR(vp->attribute)) > 0)
463 {
464 *ptr++ = PW_VENDOR_SPECIFIC;
465 length_ptr = ptr;
466 *ptr++ = 6;
467 lvalue = htonl(vendorpec);
468 memcpy(ptr, &lvalue, 4);
469 ptr += 4;
470 total_length += 6;
471 }
472 else if (vp->attribute > 0xff)
473 {
474 /*
475 * Ignore attributes > 0xff
476 */
477 continue;
478 }
479 else
480 {
481 vendorpec = 0;
482 }
483
484#ifdef ATTRIB_NMC
485 if (vendorpec == VENDORPEC_USR)
486 {
487 lvalue = htonl(vp->attribute & 0xFFFF);
488 memcpy(ptr, &lvalue, 4);
489 total_length += 2;
490 *length_ptr += 2;
491 ptr += 4;
492 }
493 else
494#endif
495 *ptr++ = (vp->attribute & 0xFF);
496
497 switch(vp->type)
498 {
499
500 case PW_TYPE_STRING:
501 /*
502 * FIXME: this is just to make sure but
503 * should NOT be needed. In fact I have no
504 * idea if it is needed :)
505 */
506 if (vp->length == 0 && vp->strvalue[0] != 0)
507 {
a23d8e0d 508 vp->length = (int)strlen(vp->strvalue);
5039dede
AK
509 }
510 if (vp->length >= AUTH_STRING_LEN)
511 {
512 vp->length = AUTH_STRING_LEN - 1;
513 }
514
515 /*
516 * If the flags indicate a encrypted attribute, handle
517 * it here. I don't want to go through the reply list
518 * another time just for transformations like this.
519 */
520 if (vp->flags.encrypt)
521 {
522 encrypt_attr(secret, vector, vp);
523 }
524
525 /*
526 * vp->length is the length of the string value; len
527 * is the length of the string field in the packet.
528 * Normally, these are the same, but if a tag is
529 * inserted only len will reflect this.
530 *
531 * Bug fixed: for tagged attributes with 'tunnel-pwd'
532 * encryption, the tag is *always* inserted, regardless
533 * of its value! (Another strange thing in RFC 2868...)
534 */
535 len = vp->length + (vp->flags.has_tag && (TAG_VALID(vp->flags.tag) || vp->flags.encrypt == 1));
536
537#ifdef ATTRIB_NMC
538 if (vendorpec != VENDORPEC_USR)
539#endif
540 *ptr++ = len + 2;
541 if (length_ptr)
542 *length_ptr += len + 2;
543
544 /* Insert the tag (sorry about the fast ugly test...) */
545 if (len > vp->length) *ptr++ = vp->flags.tag;
546
547 /* Use the original length of the string value */
548 memcpy(ptr, vp->strvalue, vp->length);
549 ptr += vp->length; /* here too */
550 total_length += len + 2;
551 break;
552
553 case PW_TYPE_INTEGER:
554 case PW_TYPE_DATE:
555 case PW_TYPE_IPADDR:
967893bb 556 len = sizeof(UINT32) + (vp->flags.has_tag && vp->type != PW_TYPE_INTEGER);
5039dede
AK
557#ifdef ATTRIB_NMC
558 if (vendorpec != VENDORPEC_USR)
559#endif
560 *ptr++ = len + 2;
561 if (length_ptr) *length_ptr += len + 2;
562
563 /* Handle tags */
564 lvalue = vp->lvalue;
565 if (vp->flags.has_tag)
566 {
567 if (vp->type == PW_TYPE_INTEGER)
568 {
569 /* Tagged integer: MSB is tag */
570 lvalue = (lvalue & 0xffffff) | ((vp->flags.tag & 0xff) << 24);
571 }
572 else
573 {
574 /* Something else: insert the tag */
575 *ptr++ = vp->flags.tag;
576 }
577 }
578 lvalue = htonl(lvalue);
967893bb
VK
579 memcpy(ptr, &lvalue, sizeof(UINT32));
580 ptr += sizeof(UINT32);
5039dede
AK
581 total_length += len + 2;
582 break;
583
584 default:
585 break;
586 }
587 }
588
589 /*
590 * Append the user message
591 * FIXME: add multiple PW_REPLY_MESSAGEs if it
592 * doesn't fit into one.
593 */
594 if (msg && msg[0])
595 {
a23d8e0d 596 len = (int)strlen(msg);
5039dede
AK
597 if (len > 0 && len < AUTH_STRING_LEN-1)
598 {
599 *ptr++ = PW_REPLY_MESSAGE;
600 *ptr++ = len + 2;
601 memcpy(ptr, msg, len);
5039dede
AK
602 total_length += len + 2;
603 }
604 }
605
606 auth->length = htons(total_length);
607
608 if (auth->code != PW_AUTHENTICATION_REQUEST && auth->code != PW_STATUS_SERVER)
609 {
610 /*
611 * Append secret and calculate the response digest
612 */
a23d8e0d 613 len = (int)strlen(secret);
5039dede
AK
614 if (total_length + len < auth_len)
615 {
616 memcpy((char *)auth + total_length, secret, len);
617 CalculateMD5Hash((BYTE *)auth, total_length + len, (BYTE *)digest);
618 memcpy(auth->vector, digest, AUTH_VECTOR_LEN);
619 memset(send_buffer + total_length, 0, len);
620 }
621 }
622
623 return total_length;
624}
625
257a4037
VK
626/**
627 * Receive result from server
628 */
b4cf3199 629static int result_recv(const InetAddress& host, WORD udp_port, char *buffer, int length, BYTE *vector, char *secretkey)
5039dede
AK
630{
631 AUTH_HDR *auth;
632 int totallen, secretlen;
633 char reply_digest[AUTH_VECTOR_LEN];
634 char calc_digest[AUTH_VECTOR_LEN];
35f836fe 635 TCHAR szHostName[32];
5039dede
AK
636
637 auth = (AUTH_HDR *)buffer;
638 totallen = ntohs(auth->length);
639
640 if(totallen != length)
641 {
642 DbgPrintf(3, _T("RADIUS: Received invalid reply length from server (want %d - got %d)"), totallen, length);
643 return 8;
644 }
645
646 // Verify the reply digest
647 memcpy(reply_digest, auth->vector, AUTH_VECTOR_LEN);
648 memcpy(auth->vector, vector, AUTH_VECTOR_LEN);
a23d8e0d 649 secretlen = (int)strlen(secretkey);
5039dede
AK
650 memcpy(buffer + length, secretkey, secretlen);
651 CalculateMD5Hash((BYTE *)auth, length + secretlen, (BYTE *)calc_digest);
652
653 if(memcmp(reply_digest, calc_digest, AUTH_VECTOR_LEN) != 0)
654 {
655 DbgPrintf(3, _T("RADIUS: Received invalid reply digest from server"));
656 }
657
b4cf3199
VK
658 host.toString(szHostName);
659 DbgPrintf(3, _T("RADIUS: Packet from host %s code=%d, id=%d, length=%d"), szHostName, auth->code, auth->id, totallen);
5039dede
AK
660 return (auth->code == PW_AUTHENTICATION_REJECT) ? 1 : 0;
661}
662
b14592e6
VK
663/**
664 * Authenticate user via RADIUS using primary or secondary server
665 */
b5864df1 666static int DoRadiusAuth(const char *login, const char *passwd, bool useSecondaryServer, char *serverName)
5039dede
AK
667{
668 AUTH_HDR *auth;
669 VALUE_PAIR *req, *vp;
9bfc9a6b 670 int port, result = 0, length, i;
5039dede
AK
671 int nRetries, nTimeout;
672 SOCKET sockfd;
673 int send_buffer[512];
674 int recv_buffer[512];
675 BYTE vector[AUTH_VECTOR_LEN];
9bfc9a6b 676 char szSecret[256];
5039dede 677
257a4037 678 ConfigReadStrA(useSecondaryServer ? _T("RADIUSSecondaryServer") : _T("RADIUSServer"), serverName, 256, "none");
35f836fe
VK
679 ConfigReadStrA(useSecondaryServer ? _T("RADIUSSecondarySecret"): _T("RADIUSSecret"), szSecret, 256, "netxms");
680 port = ConfigReadInt(useSecondaryServer ? _T("RADIUSSecondaryPort") : _T("RADIUSPort"), PW_AUTH_UDP_PORT);
681 nRetries = ConfigReadInt(_T("RADIUSNumRetries"), 5);
682 nTimeout = ConfigReadInt(_T("RADIUSTimeout"), 3);
5039dede 683
257a4037 684 if (!strcmp(serverName, "none"))
9bfc9a6b
VK
685 {
686 DbgPrintf(4, _T("RADIUS: %s server set to none, skipping"), useSecondaryServer ? _T("secondary") : _T("primary"));
687 return 10;
688 }
689
5039dede
AK
690 // Set up AUTH structure.
691 memset(send_buffer, 0, sizeof(send_buffer));
692 auth = (AUTH_HDR *)send_buffer;
693 auth->code = PW_AUTHENTICATION_REQUEST;
694 random_vector(auth->vector);
330a1815 695 auth->id = (BYTE)(GetCurrentProcessId() & 255);
5039dede
AK
696
697 // Create attribute chain
698 req = NULL;
699
700 // User name
701 vp = paircreate(PW_USER_NAME, PW_TYPE_STRING, "User-Name");
b5864df1 702 strncpy(vp->strvalue, login, AUTH_STRING_LEN);
879855b3 703 vp->length = std::min((int)strlen(login), AUTH_STRING_LEN);
5039dede
AK
704 pairadd(&req, vp);
705
b5864df1 706 char authMethod[16];
b2ecde3b 707 ConfigReadStrA(_T("RADIUSAuthMethod"), authMethod, 16, "PAP");
b5864df1
VK
708 nxlog_debug(4, _T("RADIUS: authenticating user %hs on server %hs using %hs"), login, serverName, authMethod);
709 if (!stricmp(authMethod, "PAP"))
710 {
711 vp = paircreate(PW_PASSWORD, PW_TYPE_STRING, "User-Password");
712 vp->length = rad_pwencode(passwd, vp->strvalue, szSecret, (char *)auth->vector);
713 pairadd(&req, vp);
714 }
715 else if (!stricmp(authMethod, "CHAP"))
716 {
717 char challenge[16];
718#ifdef _WITH_ENCRYPTION
719 RAND_bytes((BYTE *)challenge, 16);
720#else
721 for(int i = 0; i < 16; i++)
722 challenge[i] = (char)(rand() % 256);
723#endif
724 vp = paircreate(PW_CHAP_CHALLENGE, PW_TYPE_STRING, "CHAP-Challenge");
725 memcpy(vp->strvalue, challenge, 16);
726 vp->length = 16;
727 pairadd(&req, vp);
728
729 BYTE temp[256];
730 temp[0] = (BYTE)(rand() % 255);
731 size_t pwdlen = strlen(passwd);
732 memcpy(&temp[1], passwd, pwdlen);
733 memcpy(&temp[pwdlen + 1], challenge, 16);
734
735 char response[17];
736 response[0] = (char)temp[0];
737 CalculateMD5Hash(temp, pwdlen + 17, (BYTE *)&response[1]);
738 vp = paircreate(PW_CHAP_PASSWORD, PW_TYPE_STRING, "CHAP-Password");
739 memcpy(vp->strvalue, response, 17);
740 vp->length = 17;
741 pairadd(&req, vp);
742 }
743#ifdef _WITH_ENCRYPTION
744 else if (!stricmp(authMethod, "MS-CHAPv1"))
745 {
746 BYTE challenge[8];
747 RAND_bytes(challenge, 8);
748 vp = paircreate(PW_MS_CHAP_CHALLENGE, PW_TYPE_STRING, "MS-CHAP-Challenge");
749 memcpy(vp->strvalue, challenge, 8);
750 vp->length = 8;
751 pairadd(&req, vp);
752
753 UCS2CHAR upasswd[256];
754 utf8_to_ucs2(passwd, -1, upasswd, 256);
755
756 BYTE passwdHash[21];
757 NtPasswordHash(upasswd, passwdHash);
758 memset(&passwdHash[16], 0, 5);
759
760 BYTE response[50];
761 response[0] = (BYTE)(rand() % 255);
762 response[1] = 1;
763 memset(&response[2], 0, 24); // LM challenge response
764 MsChapChallengeResponse(challenge, passwdHash, &response[26]);
765 vp = paircreate(PW_MS_CHAP_RESPONSE, PW_TYPE_STRING, "MS-CHAP-Response");
766 memcpy(vp->strvalue, response, 50);
767 vp->length = 50;
768 pairadd(&req, vp);
769 }
770 else if (!stricmp(authMethod, "MS-CHAPv2"))
771 {
772 BYTE authChallenge[16];
773 RAND_bytes(authChallenge, 16);
774 vp = paircreate(PW_MS_CHAP_CHALLENGE, PW_TYPE_STRING, "MS-CHAP-Challenge");
775 memcpy(vp->strvalue, authChallenge, 16);
776 vp->length = 16;
777 pairadd(&req, vp);
778
779 UCS2CHAR upasswd[256];
780 utf8_to_ucs2(passwd, -1, upasswd, 256);
781
782 BYTE passwdHash[21];
783 NtPasswordHash(upasswd, passwdHash);
784 memset(&passwdHash[16], 0, 5);
785
786 BYTE response[50];
787 response[0] = (BYTE)(rand() % 255);
788 response[1] = 0;
789 RAND_bytes(&response[2], 16); // peer challenge
790 memset(&response[18], 0, 8); // reserved bytes
791 BYTE challenge[8];
792 MsChap2ChallengeHash(&response[2], authChallenge, login, challenge);
793 MsChapChallengeResponse(challenge, passwdHash, &response[26]);
794 vp = paircreate(PW_MS_CHAP2_RESPONSE, PW_TYPE_STRING, "MS-CHAP2-Response");
795 memcpy(vp->strvalue, response, 50);
796 vp->length = 50;
797 pairadd(&req, vp);
798 }
799#endif
800 else
801 {
802 nxlog_debug(3, _T("RADIUS: unknown authentication method %hs"), authMethod);
803 pairfree(req);
804 return 11;
805 }
5039dede
AK
806
807 // Resolve hostname.
b4cf3199
VK
808 InetAddress serverAddr = InetAddress::resolveHostName(serverName);
809 if (!serverAddr.isValidUnicast())
5039dede 810 {
b5864df1 811 nxlog_debug(3, _T("RADIUS: cannot resolve server name \"%hs\""), serverName);
5039dede
AK
812 pairfree(req);
813 return 3;
814 }
815
816 // Open a socket.
b4cf3199 817 sockfd = socket(serverAddr.getFamily(), SOCK_DGRAM, 0);
4c0c75c7 818 if (sockfd == INVALID_SOCKET)
5039dede 819 {
b5864df1 820 nxlog_debug(3, _T("RADIUS: Cannot create socket"));
5039dede
AK
821 pairfree(req);
822 return 5;
823 }
824
b4cf3199
VK
825 SockAddrBuffer sa;
826 serverAddr.fillSockAddr(&sa, port);
5039dede
AK
827
828 // Build final radius packet.
b5864df1 829 length = rad_build_packet(auth, sizeof(send_buffer), req, NULL, szSecret, (char *)auth->vector, send_buffer);
5039dede
AK
830 memcpy(vector, auth->vector, sizeof(vector));
831 pairfree(req);
832
833 // Send the request we've built.
fdd1d56b 834 SocketPoller sp;
5039dede
AK
835 for(i = 0; i < nRetries; i++)
836 {
837 if (i > 0)
838 {
b5864df1 839 nxlog_debug(5, _T("RADIUS: Re-sending request..."));
5039dede 840 }
b4cf3199 841 sendto(sockfd, (char *)auth, length, 0, (struct sockaddr *)&sa, SA_LEN((struct sockaddr *)&sa));
5039dede 842
fdd1d56b
VK
843 sp.reset();
844 sp.add(sockfd);
845 if (sp.poll(nTimeout * 1000) <= 0)
5039dede
AK
846 {
847 continue;
848 }
849
b4cf3199
VK
850 SockAddrBuffer sarecv;
851 socklen_t salen = sizeof(sa);
852 result = recvfrom(sockfd, (char *)recv_buffer, sizeof(recv_buffer), 0, (struct sockaddr *)&sarecv, &salen);
853 InetAddress addrRecv = InetAddress::createFromSockaddr((struct sockaddr *)&sarecv);
854 if ((result >= 0) && addrRecv.equals(serverAddr) && (SA_PORT(&sarecv) == SA_PORT(&sa)))
5039dede
AK
855 {
856 break;
857 }
858
859 ThreadSleepMs(1000);
860 }
861
862 if (result > 0 && i < nRetries)
863 {
b4cf3199 864 result = result_recv(serverAddr, port, (char *)recv_buffer, result, vector, szSecret);
5039dede
AK
865 }
866 else
867 {
868 result = 7;
869 }
870
871 closesocket(sockfd);
5039dede
AK
872 return result;
873}
874
257a4037
VK
875/**
876 * Check if authentication can be retried on other server
877 */
878static bool CanRetry(int result)
879{
880 return (result == 3) || (result == 7) || (result == 10); // Bad server name, timeout, comm. error, or server not configured
881}
5039dede 882
b5864df1 883#else /* USE_RADCLI */
257a4037
VK
884
885#if HAVE_RADCLI_RADCLI_H
886#include <radcli/radcli.h>
887#endif
888
257a4037
VK
889/**
890 * Add pair to request and check for errors
891 */
892#define PAIR_ADD_VENDOR_LEN(vendor, type, value, len) do { \
893if (rc_avpair_add(rh, &request, (type), (value), len, (vendor)) == NULL) \
894{ \
895 rc_destroy(rh); \
896 rc_avpair_free(request); \
897 return ERROR_RC; \
898} } while(0)
899#define PAIR_ADD_LEN(type, value, len) PAIR_ADD_VENDOR_LEN(0, (type), (value), (len))
900#define PAIR_ADD(type, value) PAIR_ADD_VENDOR_LEN(0, (type), (value), -1)
901
902/**
903 * Authenticate user via RADIUS using primary or secondary server
904 */
905static int DoRadiusAuth(const char *login, const char *passwd, bool useSecondaryServer, char *serverName)
906{
907 ConfigReadStrA(useSecondaryServer ? _T("RADIUSSecondaryServer") : _T("RADIUSServer"), serverName, 256, "none");
908 if (!strcmp(serverName, "none"))
909 {
910 DbgPrintf(4, _T("RADIUS: %s server set to none, skipping"), useSecondaryServer ? _T("secondary") : _T("primary"));
911 return -127;
912 }
913
914 int port = ConfigReadInt(useSecondaryServer ? _T("RADIUSSecondaryPort") : _T("RADIUSPort"), PW_AUTH_UDP_PORT);
915
916 char secret[256];
917 ConfigReadStrA(useSecondaryServer ? _T("RADIUSSecondarySecret"): _T("RADIUSSecret"), secret, 256, "netxms");
918
919 rc_handle *rh = rc_new();
920 if (rc_config_init(rh) == NULL)
921 return ERROR_RC;
c8dffae4
VK
922 TCHAR dictionaryFile[MAX_PATH];
923 GetNetXMSDirectory(nxDirShare, dictionaryFile);
924 _tcscat(dictionaryFile, SFILE_RADDICT);
925#ifdef UNICODE
926 char dictionaryFileUTF8[MAX_PATH];
927 WideCharToMultiByte(CP_UTF8, 0, dictionaryFile, -1, dictionaryFileUTF8, MAX_PATH, NULL, NULL);
928 if (rc_read_dictionary(rh, dictionaryFileUTF8) != 0)
929#else
930 if (rc_read_dictionary(rh, dictionaryFile) != 0)
931#endif
257a4037
VK
932 return ERROR_RC; // rc_read_dictionary will destroy rh on failure
933
934 char connectString[1024];
935 snprintf(connectString, 1024, "%s:%d:%s", serverName, port, secret);
936 rc_add_config(rh, "authserver", connectString, __FILE__, __LINE__);
937
938 char temp[64];
939 ConfigReadStrA(_T("RADIUSNumRetries"), temp, 64, "5");
940 rc_add_config(rh, "radius_retries", temp, __FILE__, __LINE__);
941
942 ConfigReadStrA(_T("RADIUSTimeout"), temp, 64, "3");
943 rc_add_config(rh, "radius_timeout", temp, __FILE__, __LINE__);
944
945 VALUE_PAIR *request = NULL;
946 PAIR_ADD(PW_USER_NAME, login);
947 UINT32 service = PW_AUTHENTICATE_ONLY;
948 PAIR_ADD(PW_SERVICE_TYPE, &service);
949
950 char authMethod[16];
b2ecde3b 951 ConfigReadStrA(_T("RADIUSAuthMethod"), authMethod, 16, "PAP");
b5864df1 952 nxlog_debug(4, _T("RADIUS: authenticating user %hs on server %hs using %hs"), login, serverName, authMethod);
257a4037
VK
953 if (!stricmp(authMethod, "PAP"))
954 {
955 PAIR_ADD(PW_USER_PASSWORD, passwd);
956 }
957 else if (!stricmp(authMethod, "CHAP"))
958 {
959 char challenge[16];
960#ifdef _WITH_ENCRYPTION
961 RAND_bytes((BYTE *)challenge, 16);
962#else
963 for(int i = 0; i < 16; i++)
964 challenge[i] = (char)(rand() % 256);
965#endif
966 PAIR_ADD_LEN(PW_CHAP_CHALLENGE, challenge, 16);
967
968 BYTE temp[256];
969 temp[0] = (BYTE)(rand() % 255);
970 size_t pwdlen = strlen(passwd);
971 memcpy(&temp[1], passwd, pwdlen);
972 memcpy(&temp[pwdlen + 1], challenge, 16);
973
974 char response[17];
975 response[0] = (char)temp[0];
976 CalculateMD5Hash(temp, pwdlen + 17, (BYTE *)&response[1]);
977 PAIR_ADD_LEN(PW_CHAP_PASSWORD, response, 17);
978 }
979#ifdef _WITH_ENCRYPTION
980 else if (!stricmp(authMethod, "MS-CHAPv1"))
981 {
982 BYTE challenge[8];
983 RAND_bytes(challenge, 8);
984 PAIR_ADD_VENDOR_LEN(VENDOR_MICROSOFT, PW_MS_CHAP_CHALLENGE, challenge, 8);
985
986 UCS2CHAR upasswd[256];
987 utf8_to_ucs2(passwd, -1, upasswd, 256);
988
989 BYTE passwdHash[21];
990 NtPasswordHash(upasswd, passwdHash);
991 memset(&passwdHash[16], 0, 5);
992
993 BYTE response[50];
994 response[0] = (BYTE)(rand() % 255);
995 response[1] = 1;
996 memset(&response[2], 0, 24); // LM challenge response
997 MsChapChallengeResponse(challenge, passwdHash, &response[26]);
998 PAIR_ADD_VENDOR_LEN(VENDOR_MICROSOFT, PW_MS_CHAP_RESPONSE, response, 50);
999 }
4c04aee5
VK
1000 else if (!stricmp(authMethod, "MS-CHAPv2"))
1001 {
1002 BYTE authChallenge[16];
1003 RAND_bytes(authChallenge, 16);
1004 PAIR_ADD_VENDOR_LEN(VENDOR_MICROSOFT, PW_MS_CHAP_CHALLENGE, authChallenge, 16);
1005
1006 UCS2CHAR upasswd[256];
1007 utf8_to_ucs2(passwd, -1, upasswd, 256);
1008
1009 BYTE passwdHash[21];
1010 NtPasswordHash(upasswd, passwdHash);
1011 memset(&passwdHash[16], 0, 5);
1012
1013 BYTE response[50];
1014 response[0] = (BYTE)(rand() % 255);
1015 response[1] = 0;
1016 RAND_bytes(&response[2], 16); // peer challenge
1017 memset(&response[18], 0, 8); // reserved bytes
1018 BYTE challenge[8];
1019 MsChap2ChallengeHash(&response[2], authChallenge, login, challenge);
1020 MsChapChallengeResponse(challenge, passwdHash, &response[26]);
1021 PAIR_ADD_VENDOR_LEN(VENDOR_MICROSOFT, PW_MS_CHAP2_RESPONSE, response, 50);
1022 }
257a4037
VK
1023#endif
1024 else
1025 {
b5864df1 1026 nxlog_debug(3, _T("RADIUS: unknown authentication method %hs"), authMethod);
257a4037
VK
1027 rc_destroy(rh);
1028 rc_avpair_free(request);
1029 return ERROR_RC;
1030 }
1031
1032 VALUE_PAIR *response = NULL;
1033 int result = rc_auth(rh, 0, request, &response, NULL);
1034 rc_destroy(rh);
1035 rc_avpair_free(request);
1036 rc_avpair_free(response);
1037 return result;
1038}
1039
1040/**
1041 * Check if authentication can be retried on other server
1042 */
1043static bool CanRetry(int result)
1044{
1045 return (result == -127) || (result == BADRESP_RC) || (result == TIMEOUT_RC); // Bad server name, timeout, comm. error, or server not configured
1046}
1047
1048#endif
1049
1050/**
1051 * Authenticate user via RADIUS
1052 */
9bfc9a6b
VK
1053bool RadiusAuth(const TCHAR *login, const TCHAR *passwd)
1054{
1055 static bool useSecondary = false;
5039dede 1056
257a4037 1057 char server[256], rlogin[256], rpasswd[256];
35f836fe 1058#ifdef UNICODE
257a4037
VK
1059 WideCharToMultiByte(CP_UTF8, 0, login, -1, rlogin, 256, NULL, NULL);
1060 WideCharToMultiByte(CP_UTF8, 0, passwd, -1, rpasswd, 256, NULL, NULL);
35f836fe 1061#else
257a4037
VK
1062 mb_to_utf8(login, -1, rlogin, 256);
1063 mb_to_utf8(passwd, -1, rpasswd, 256);
35f836fe 1064#endif
257a4037
VK
1065 rlogin[255] = 0;
1066 rpasswd[255] = 0;
1067 int result = DoRadiusAuth(rlogin, rpasswd, useSecondary, server);
1068 nxlog_debug(4, _T("RADIUS: DoRadiusAuth returned %d for user %s"), result, login);
1069 if (CanRetry(result))
9bfc9a6b
VK
1070 {
1071 useSecondary = !useSecondary;
257a4037
VK
1072 nxlog_debug(3, _T("RADIUS: unable to use %s server, switching to %s"), useSecondary ? _T("primary") : _T("secondary"), useSecondary ? _T("secondary") : _T("primary"));
1073 result = DoRadiusAuth(rlogin, rpasswd, useSecondary, server);
1074 nxlog_debug(4, _T("RADIUS: DoRadiusAuth returned %d for user %s"), result, login);
9bfc9a6b 1075 }
257a4037 1076 nxlog_write((result == 0) ? MSG_RADIUS_AUTH_SUCCESS : MSG_RADIUS_AUTH_FAILED, NXLOG_INFO, "sm", login, server);
9bfc9a6b
VK
1077 return result == 0;
1078}