| /* |
| * $Id: sendserver.c,v 1.1 2004/11/14 07:26:26 paulus Exp $ |
| * |
| * Copyright (C) 1995,1996,1997 Lars Fenneberg |
| * |
| * Copyright 1992 Livingston Enterprises, Inc. |
| * |
| * Copyright 1992,1993, 1994,1995 The Regents of the University of Michigan |
| * and Merit Network, Inc. All Rights Reserved |
| * |
| * See the file COPYRIGHT for the respective terms and conditions. |
| * If the file is missing contact me at lf@elemental.net |
| * and I'll send you a copy. |
| * |
| */ |
| |
| #include <includes.h> |
| #include <radiusclient.h> |
| #include <pathnames.h> |
| |
| static void rc_random_vector (unsigned char *); |
| static int rc_check_reply (AUTH_HDR *, int, char *, unsigned char *, unsigned char); |
| |
| /* |
| * Function: rc_pack_list |
| * |
| * Purpose: Packs an attribute value pair list into a buffer. |
| * |
| * Returns: Number of octets packed. |
| * |
| */ |
| |
| static int rc_pack_list (VALUE_PAIR *vp, char *secret, AUTH_HDR *auth) |
| { |
| int length, i, pc, secretlen, padded_length; |
| int total_length = 0; |
| UINT4 lvalue; |
| unsigned char passbuf[MAX(AUTH_PASS_LEN, CHAP_VALUE_LENGTH)]; |
| unsigned char md5buf[256]; |
| unsigned char *buf, *vector, *lenptr; |
| |
| buf = auth->data; |
| |
| while (vp != (VALUE_PAIR *) NULL) |
| { |
| |
| if (vp->vendorcode != VENDOR_NONE) { |
| *buf++ = PW_VENDOR_SPECIFIC; |
| |
| /* Place-holder for where to put length */ |
| lenptr = buf++; |
| |
| /* Insert vendor code */ |
| *buf++ = 0; |
| *buf++ = (((unsigned int) vp->vendorcode) >> 16) & 255; |
| *buf++ = (((unsigned int) vp->vendorcode) >> 8) & 255; |
| *buf++ = ((unsigned int) vp->vendorcode) & 255; |
| |
| /* Insert vendor-type */ |
| *buf++ = vp->attribute; |
| |
| /* Insert value */ |
| switch(vp->type) { |
| case PW_TYPE_STRING: |
| length = vp->lvalue; |
| *lenptr = length + 8; |
| *buf++ = length+2; |
| memcpy(buf, vp->strvalue, (size_t) length); |
| buf += length; |
| total_length += length+8; |
| break; |
| case PW_TYPE_INTEGER: |
| case PW_TYPE_IPADDR: |
| length = sizeof(UINT4); |
| *lenptr = length + 8; |
| *buf++ = length+2; |
| lvalue = htonl(vp->lvalue); |
| memcpy(buf, (char *) &lvalue, sizeof(UINT4)); |
| buf += length; |
| total_length += length+8; |
| break; |
| default: |
| break; |
| } |
| } else { |
| *buf++ = vp->attribute; |
| switch (vp->attribute) { |
| case PW_USER_PASSWORD: |
| |
| /* Encrypt the password */ |
| |
| /* Chop off password at AUTH_PASS_LEN */ |
| length = vp->lvalue; |
| if (length > AUTH_PASS_LEN) length = AUTH_PASS_LEN; |
| |
| /* Calculate the padded length */ |
| padded_length = (length+(AUTH_VECTOR_LEN-1)) & ~(AUTH_VECTOR_LEN-1); |
| |
| /* Record the attribute length */ |
| *buf++ = padded_length + 2; |
| |
| /* Pad the password with zeros */ |
| memset ((char *) passbuf, '\0', AUTH_PASS_LEN); |
| memcpy ((char *) passbuf, vp->strvalue, (size_t) length); |
| |
| secretlen = strlen (secret); |
| vector = (char *)auth->vector; |
| for(i = 0; i < padded_length; i += AUTH_VECTOR_LEN) { |
| /* Calculate the MD5 digest*/ |
| strcpy ((char *) md5buf, secret); |
| memcpy ((char *) md5buf + secretlen, vector, |
| AUTH_VECTOR_LEN); |
| rc_md5_calc (buf, md5buf, secretlen + AUTH_VECTOR_LEN); |
| |
| /* Remeber the start of the digest */ |
| vector = buf; |
| |
| /* Xor the password into the MD5 digest */ |
| for (pc = i; pc < (i + AUTH_VECTOR_LEN); pc++) { |
| *buf++ ^= passbuf[pc]; |
| } |
| } |
| |
| total_length += padded_length + 2; |
| |
| break; |
| #if 0 |
| case PW_CHAP_PASSWORD: |
| |
| *buf++ = CHAP_VALUE_LENGTH + 2; |
| |
| /* Encrypt the Password */ |
| length = vp->lvalue; |
| if (length > CHAP_VALUE_LENGTH) { |
| length = CHAP_VALUE_LENGTH; |
| } |
| memset ((char *) passbuf, '\0', CHAP_VALUE_LENGTH); |
| memcpy ((char *) passbuf, vp->strvalue, (size_t) length); |
| |
| /* Calculate the MD5 Digest */ |
| secretlen = strlen (secret); |
| strcpy ((char *) md5buf, secret); |
| memcpy ((char *) md5buf + secretlen, (char *) auth->vector, |
| AUTH_VECTOR_LEN); |
| rc_md5_calc (buf, md5buf, secretlen + AUTH_VECTOR_LEN); |
| |
| /* Xor the password into the MD5 digest */ |
| for (i = 0; i < CHAP_VALUE_LENGTH; i++) { |
| *buf++ ^= passbuf[i]; |
| } |
| total_length += CHAP_VALUE_LENGTH + 2; |
| |
| break; |
| #endif |
| default: |
| switch (vp->type) { |
| case PW_TYPE_STRING: |
| length = vp->lvalue; |
| *buf++ = length + 2; |
| memcpy (buf, vp->strvalue, (size_t) length); |
| buf += length; |
| total_length += length + 2; |
| break; |
| |
| case PW_TYPE_INTEGER: |
| case PW_TYPE_IPADDR: |
| *buf++ = sizeof (UINT4) + 2; |
| lvalue = htonl (vp->lvalue); |
| memcpy (buf, (char *) &lvalue, sizeof (UINT4)); |
| buf += sizeof (UINT4); |
| total_length += sizeof (UINT4) + 2; |
| break; |
| |
| default: |
| break; |
| } |
| break; |
| } |
| } |
| vp = vp->next; |
| } |
| return total_length; |
| } |
| |
| /* |
| * Function: rc_send_server |
| * |
| * Purpose: send a request to a RADIUS server and wait for the reply |
| * |
| */ |
| |
| int rc_send_server (SEND_DATA *data, char *msg, REQUEST_INFO *info) |
| { |
| int sockfd; |
| struct sockaddr salocal; |
| struct sockaddr saremote; |
| struct sockaddr_in *sin; |
| struct timeval authtime; |
| fd_set readfds; |
| AUTH_HDR *auth, *recv_auth; |
| UINT4 auth_ipaddr; |
| char *server_name; /* Name of server to query */ |
| int salen; |
| int result; |
| int total_length; |
| int length; |
| int retry_max; |
| int secretlen; |
| char secret[MAX_SECRET_LENGTH + 1]; |
| unsigned char vector[AUTH_VECTOR_LEN]; |
| char recv_buffer[BUFFER_LEN]; |
| char send_buffer[BUFFER_LEN]; |
| int retries; |
| VALUE_PAIR *vp; |
| |
| server_name = data->server; |
| if (server_name == (char *) NULL || server_name[0] == '\0') |
| return (ERROR_RC); |
| |
| if ((vp = rc_avpair_get(data->send_pairs, PW_SERVICE_TYPE)) && \ |
| (vp->lvalue == PW_ADMINISTRATIVE)) |
| { |
| strcpy(secret, MGMT_POLL_SECRET); |
| if ((auth_ipaddr = rc_get_ipaddr(server_name)) == 0) |
| return (ERROR_RC); |
| } |
| else |
| { |
| if (rc_find_server (server_name, &auth_ipaddr, secret) != 0) |
| { |
| return (ERROR_RC); |
| } |
| } |
| |
| sockfd = socket (AF_INET, SOCK_DGRAM, 0); |
| if (sockfd < 0) |
| { |
| memset (secret, '\0', sizeof (secret)); |
| error("rc_send_server: socket: %s", strerror(errno)); |
| return (ERROR_RC); |
| } |
| |
| length = sizeof (salocal); |
| sin = (struct sockaddr_in *) & salocal; |
| memset ((char *) sin, '\0', (size_t) length); |
| sin->sin_family = AF_INET; |
| sin->sin_addr.s_addr = htonl(INADDR_ANY); |
| sin->sin_port = htons ((unsigned short) 0); |
| if (bind (sockfd, (struct sockaddr *) sin, length) < 0 || |
| getsockname (sockfd, (struct sockaddr *) sin, &length) < 0) |
| { |
| close (sockfd); |
| memset (secret, '\0', sizeof (secret)); |
| error("rc_send_server: bind: %s: %m", server_name); |
| return (ERROR_RC); |
| } |
| |
| retry_max = data->retries; /* Max. numbers to try for reply */ |
| retries = 0; /* Init retry cnt for blocking call */ |
| |
| /* Build a request */ |
| auth = (AUTH_HDR *) send_buffer; |
| auth->code = data->code; |
| auth->id = data->seq_nbr; |
| |
| if (data->code == PW_ACCOUNTING_REQUEST) |
| { |
| total_length = rc_pack_list(data->send_pairs, secret, auth) + AUTH_HDR_LEN; |
| |
| auth->length = htons ((unsigned short) total_length); |
| |
| memset((char *) auth->vector, 0, AUTH_VECTOR_LEN); |
| secretlen = strlen (secret); |
| memcpy ((char *) auth + total_length, secret, secretlen); |
| rc_md5_calc (vector, (char *) auth, total_length + secretlen); |
| memcpy ((char *) auth->vector, (char *) vector, AUTH_VECTOR_LEN); |
| } |
| else |
| { |
| rc_random_vector (vector); |
| memcpy (auth->vector, vector, AUTH_VECTOR_LEN); |
| |
| total_length = rc_pack_list(data->send_pairs, secret, auth) + AUTH_HDR_LEN; |
| |
| auth->length = htons ((unsigned short) total_length); |
| } |
| |
| sin = (struct sockaddr_in *) & saremote; |
| memset ((char *) sin, '\0', sizeof (saremote)); |
| sin->sin_family = AF_INET; |
| sin->sin_addr.s_addr = htonl (auth_ipaddr); |
| sin->sin_port = htons ((unsigned short) data->svc_port); |
| |
| for (;;) |
| { |
| sendto (sockfd, (char *) auth, (unsigned int) total_length, (int) 0, |
| (struct sockaddr *) sin, sizeof (struct sockaddr_in)); |
| |
| authtime.tv_usec = 0L; |
| authtime.tv_sec = (long) data->timeout; |
| FD_ZERO (&readfds); |
| FD_SET (sockfd, &readfds); |
| if (select (sockfd + 1, &readfds, NULL, NULL, &authtime) < 0) |
| { |
| if (errno == EINTR) |
| continue; |
| error("rc_send_server: select: %m"); |
| memset (secret, '\0', sizeof (secret)); |
| close (sockfd); |
| return (ERROR_RC); |
| } |
| if (FD_ISSET (sockfd, &readfds)) |
| break; |
| |
| /* |
| * Timed out waiting for response. Retry "retry_max" times |
| * before giving up. If retry_max = 0, don't retry at all. |
| */ |
| if (++retries >= retry_max) |
| { |
| error("rc_send_server: no reply from RADIUS server %s:%u", |
| rc_ip_hostname (auth_ipaddr), data->svc_port); |
| close (sockfd); |
| memset (secret, '\0', sizeof (secret)); |
| return (TIMEOUT_RC); |
| } |
| } |
| salen = sizeof (saremote); |
| length = recvfrom (sockfd, (char *) recv_buffer, |
| (int) sizeof (recv_buffer), |
| (int) 0, &saremote, &salen); |
| |
| if (length <= 0) |
| { |
| error("rc_send_server: recvfrom: %s:%d: %m", server_name,\ |
| data->svc_port); |
| close (sockfd); |
| memset (secret, '\0', sizeof (secret)); |
| return (ERROR_RC); |
| } |
| |
| recv_auth = (AUTH_HDR *)recv_buffer; |
| |
| result = rc_check_reply (recv_auth, BUFFER_LEN, secret, vector, data->seq_nbr); |
| |
| data->receive_pairs = rc_avpair_gen(recv_auth); |
| |
| close (sockfd); |
| if (info) |
| { |
| memcpy(info->secret, secret, sizeof(info->secret)); |
| memcpy(info->request_vector, vector, |
| sizeof(info->request_vector)); |
| } |
| memset (secret, '\0', sizeof (secret)); |
| |
| if (result != OK_RC) return (result); |
| |
| *msg = '\0'; |
| vp = data->receive_pairs; |
| while (vp) |
| { |
| if ((vp = rc_avpair_get(vp, PW_REPLY_MESSAGE))) |
| { |
| strcat(msg, vp->strvalue); |
| strcat(msg, "\n"); |
| vp = vp->next; |
| } |
| } |
| |
| if ((recv_auth->code == PW_ACCESS_ACCEPT) || |
| (recv_auth->code == PW_PASSWORD_ACK) || |
| (recv_auth->code == PW_ACCOUNTING_RESPONSE)) |
| { |
| result = OK_RC; |
| } |
| else |
| { |
| result = BADRESP_RC; |
| } |
| |
| return (result); |
| } |
| |
| /* |
| * Function: rc_check_reply |
| * |
| * Purpose: verify items in returned packet. |
| * |
| * Returns: OK_RC -- upon success, |
| * BADRESP_RC -- if anything looks funny. |
| * |
| */ |
| |
| static int rc_check_reply (AUTH_HDR *auth, int bufferlen, char *secret, |
| unsigned char *vector, unsigned char seq_nbr) |
| { |
| int secretlen; |
| int totallen; |
| unsigned char calc_digest[AUTH_VECTOR_LEN]; |
| unsigned char reply_digest[AUTH_VECTOR_LEN]; |
| |
| totallen = ntohs (auth->length); |
| |
| secretlen = strlen (secret); |
| |
| /* Do sanity checks on packet length */ |
| if ((totallen < 20) || (totallen > 4096)) |
| { |
| error("rc_check_reply: received RADIUS server response with invalid length"); |
| return (BADRESP_RC); |
| } |
| |
| /* Verify buffer space, should never trigger with current buffer size and check above */ |
| if ((totallen + secretlen) > bufferlen) |
| { |
| error("rc_check_reply: not enough buffer space to verify RADIUS server response"); |
| return (BADRESP_RC); |
| } |
| /* Verify that id (seq. number) matches what we sent */ |
| if (auth->id != seq_nbr) |
| { |
| error("rc_check_reply: received non-matching id in RADIUS server response"); |
| return (BADRESP_RC); |
| } |
| |
| /* Verify the reply digest */ |
| memcpy ((char *) reply_digest, (char *) auth->vector, AUTH_VECTOR_LEN); |
| memcpy ((char *) auth->vector, (char *) vector, AUTH_VECTOR_LEN); |
| memcpy ((char *) auth + totallen, secret, secretlen); |
| rc_md5_calc (calc_digest, (char *) auth, totallen + secretlen); |
| |
| #ifdef DIGEST_DEBUG |
| { |
| int i; |
| |
| fputs("reply_digest: ", stderr); |
| for (i = 0; i < AUTH_VECTOR_LEN; i++) |
| { |
| fprintf(stderr,"%.2x ", (int) reply_digest[i]); |
| } |
| fputs("\ncalc_digest: ", stderr); |
| for (i = 0; i < AUTH_VECTOR_LEN; i++) |
| { |
| fprintf(stderr,"%.2x ", (int) calc_digest[i]); |
| } |
| fputs("\n", stderr); |
| } |
| #endif |
| |
| if (memcmp ((char *) reply_digest, (char *) calc_digest, |
| AUTH_VECTOR_LEN) != 0) |
| { |
| #ifdef RADIUS_116 |
| /* the original Livingston radiusd v1.16 seems to have |
| a bug in digest calculation with accounting requests, |
| authentication request are ok. i looked at the code |
| but couldn't find any bugs. any help to get this |
| kludge out are welcome. preferably i want to |
| reproduce the calculation bug here to be compatible |
| to stock Livingston radiusd v1.16. -lf, 03/14/96 |
| */ |
| if (auth->code == PW_ACCOUNTING_RESPONSE) |
| return (OK_RC); |
| #endif |
| error("rc_check_reply: received invalid reply digest from RADIUS server"); |
| return (BADRESP_RC); |
| } |
| |
| return (OK_RC); |
| |
| } |
| |
| /* |
| * Function: rc_random_vector |
| * |
| * Purpose: generates a random vector of AUTH_VECTOR_LEN octets. |
| * |
| * Returns: the vector (call by reference) |
| * |
| */ |
| |
| static void rc_random_vector (unsigned char *vector) |
| { |
| int randno; |
| int i; |
| int fd; |
| |
| /* well, I added this to increase the security for user passwords. |
| we use /dev/urandom here, as /dev/random might block and we don't |
| need that much randomness. BTW, great idea, Ted! -lf, 03/18/95 */ |
| |
| if ((fd = open(_PATH_DEV_URANDOM, O_RDONLY)) >= 0) |
| { |
| unsigned char *pos; |
| int readcount; |
| |
| i = AUTH_VECTOR_LEN; |
| pos = vector; |
| while (i > 0) |
| { |
| readcount = read(fd, (char *)pos, i); |
| pos += readcount; |
| i -= readcount; |
| } |
| |
| close(fd); |
| return; |
| } /* else fall through */ |
| |
| for (i = 0; i < AUTH_VECTOR_LEN;) |
| { |
| randno = magic(); |
| memcpy ((char *) vector, (char *) &randno, sizeof (int)); |
| vector += sizeof (int); |
| i += sizeof (int); |
| } |
| |
| return; |
| } |