/* ==================================================================== * The Kannel Software License, Version 1.0 * * Copyright (c) 2001-2005 Kannel Group * Copyright (c) 1998-2001 WapIT Ltd. * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. The end-user documentation included with the redistribution, * if any, must include the following acknowledgment: * "This product includes software developed by the * Kannel Group (http://www.kannel.org/)." * Alternately, this acknowledgment may appear in the software itself, * if and wherever such third-party acknowledgments normally appear. * * 4. The names "Kannel" and "Kannel Group" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please * contact org@kannel.org. * * 5. Products derived from this software may not be called "Kannel", * nor may "Kannel" appear in their name, without prior written * permission of the Kannel Group. * * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE KANNEL GROUP OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, * OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== * * This software consists of voluntary contributions made by many * individuals on behalf of the Kannel Group. For more information on * the Kannel Group, please see . * * Portions of this software are based upon software originally written at * WapIT Ltd., Helsinki, Finland for the Kannel project. */ /* * radius_pdu.c - parse and generate RADIUS Accounting PDUs * * Taken from gw/smsc/smpp_pdu.c writen by Lars Wirzenius. * This makes heavy use of C pre-processor macro magic. * * References: RFC2866 - RADIUS Accounting * * Stipe Tolj */ #include #include "radius_pdu.h" #define MIN_RADIUS_PDU_LEN 20 #define MAX_RADIUS_PDU_LEN 4095 static unsigned long decode_integer(Octstr *os, long pos, int octets) { unsigned long u; int i; gw_assert(octstr_len(os) >= pos + octets); u = 0; for (i = 0; i < octets; ++i) u = (u << 8) | octstr_get_char(os, pos + i); return u; } static void append_encoded_integer(Octstr *os, unsigned long u, long octets) { long i; for (i = 0; i < octets; ++i) octstr_append_char(os, (u >> ((octets - i - 1) * 8)) & 0xFF); } /* static void *get_header_element(RADIUS_PDU *pdu, unsigned char *e) { switch (pdu->type) { #define INTEGER(name, octets) \ if (strcmp(#name, e) == 0) return (void*) *(&p->name); #define NULTERMINATED(name, max_octets) #define OCTETS(name, field_giving_octets) \ if (strcmp(#name, e) == 0) return (void*) p->name; #define PDU(name, id, fields) \ case id: { \ struct name *p = &pdu->u.name; \ } break; #include "radius_pdu.def" default: error(0, "Unknown RADIUS_PDU type, internal error."); gw_free(pdu); return NULL; } } */ RADIUS_PDU *radius_pdu_create(int type, RADIUS_PDU *req) { RADIUS_PDU *pdu; pdu = gw_malloc(sizeof(*pdu)); pdu->type = type; switch (type) { #define INTEGER(name, octets) \ if (strcmp(#name, "code") == 0) p->name = type; \ else p->name = 0; #define OCTETS(name, field_giving_octets) p->name = NULL; #define PDU(name, id, fields) \ case id: { \ struct name *p = &pdu->u.name; \ pdu->type_name = #name; \ fields \ } break; #include "radius_pdu.def" default: error(0, "Unknown RADIUS_PDU type, internal error."); gw_free(pdu); return NULL; } #define ATTR(attr, type, string, min, max) #define UNASSIGNED(attr) #define ATTRIBUTES(fields) \ pdu->attr = dict_create(20, (void (*)(void *))octstr_destroy); #include "radius_attributes.def" return pdu; } void radius_pdu_destroy(RADIUS_PDU *pdu) { if (pdu == NULL) return; switch (pdu->type) { #define INTEGER(name, octets) p->name = 0; #define OCTETS(name, field_giving_octets) octstr_destroy(p->name); #define PDU(name, id, fields) \ case id: { struct name *p = &pdu->u.name; fields } break; #include "radius_pdu.def" default: error(0, "Unknown RADIUS_PDU type, internal error while destroying."); } #define ATTR(attr, type, string, min, max) #define UNASSIGNED(attr) #define ATTRIBUTES(fields) dict_destroy(pdu->attr); #include "radius_attributes.def" gw_free(pdu); } /* static void radius_type_append(Octstr **os, int type, int pmin, int pmax, Octstr *value) { long l; switch (type) { case t_int: octstr_parse_long(&l, value, 0, 10); append_encoded_integer(*os, l, pmin); break; case t_string: octstr_append(*os, value); break; case t_ipaddr: ret = octstr_create(""); for (i = 0; i < 4; i++) { int c = octstr_get_char(value, i); Octstr *b = octstr_format("%d", c); octstr_append(ret, b); i < 3 ? octstr_append_cstr(ret, ".") : NULL; octstr_destroy(b); } break; default: panic(0, "RADIUS: Attribute type %d does not exist.", type); break; } } */ static Octstr *radius_attr_pack(RADIUS_PDU *pdu) { Octstr *os; os = octstr_create(""); gw_assert(pdu != NULL); #define ATTR(atype, type, string, pmin, pmax) \ { \ Octstr *attr_strg = octstr_create(string); \ Octstr *attr_val = dict_get(p->attr, attr_str); \ if (attr_str != NULL) { \ int attr_len = octstr_len(attr_val) + 2; \ octstr_format_append(os, "%02X", atype); \ octstr_append_data(os, (char*) &attr_len, 2); \ radius_type_append(&os, type, pmin, pmax, attr_val); \ } \ octstr_destroy(attr_str); \ } #define UNASSIGNED(attr) #define ATTRIBUTES(fields) #include "radius_attributes.def" return os; } Octstr *radius_pdu_pack(RADIUS_PDU *pdu) { Octstr *os,*oos; Octstr *temp; os = octstr_create(""); gw_assert(pdu != NULL); /* switch (pdu->type) { #define INTEGER(name, octets) p = *(&p); #define NULTERMINATED(name, max_octets) p = *(&p); #define OCTETS(name, field_giving_octets) \ p->field_giving_octets = octstr_len(p->name); #define PDU(name, id, fields) \ case id: { struct name *p = &pdu->u.name; fields } break; #include "radius_pdu.def" default: error(0, "Unknown RADIUS_PDU type, internal error while packing."); } */ switch (pdu->type) { #define INTEGER(name, octets) \ append_encoded_integer(os, p->name, octets); #define OCTETS(name, field_giving_octets) \ octstr_append(os, p->name); #define PDU(name, id, fields) \ case id: { struct name *p = &pdu->u.name; fields; oos = radius_attr_pack(pdu); \ octstr_append(os, oos);octstr_destroy(oos); } break; #include "radius_pdu.def" default: error(0, "Unknown RADIUS_PDU type, internal error while packing."); } /* now set PDU length */ temp = octstr_create(""); append_encoded_integer(temp, octstr_len(os), 2); octstr_delete(os, 2, 2); octstr_insert(os, temp, 2); octstr_destroy(temp); return os; } static Octstr *radius_type_convert(int type, Octstr *value) { Octstr *ret = NULL; int i; switch (type) { case t_int: ret = octstr_format("%ld", decode_integer(value, 0, 4)); break; case t_string: ret = octstr_format("%s", octstr_get_cstr(value)); break; case t_ipaddr: ret = octstr_create(""); for (i = 0; i < 4; i++) { int c = octstr_get_char(value, i); Octstr *b = octstr_format("%d", c); octstr_append(ret, b); if (i < 3) octstr_append_cstr(ret, "."); octstr_destroy(b); } break; default: panic(0, "RADIUS: Attribute type %d does not exist.", type); break; } return ret; } static void radius_attr_unpack(ParseContext **context, RADIUS_PDU **pdu) { #define ATTR(atype, type, string, pmin, pmax) \ if (atype == attr_type) { \ Octstr *tmp, *value; \ if ((attr_len-2) < pmin || (attr_len-2) > pmax) { \ error(0, "RADIUS: Attribute (%d) `%s' has invalid len %d, droppped.", \ attr_type, string, (attr_len-2)); \ continue; \ } \ attr_val = parse_get_octets(*context, attr_len - 2); \ tmp = octstr_format("RADIUS: Attribute (%d) `%s', len %d", \ attr_type, string, attr_len - 2); \ value = radius_type_convert(type, attr_val); \ octstr_destroy(attr_val); \ octstr_dump_short(value, 0, octstr_get_cstr(tmp)); \ octstr_destroy(tmp); \ attr_str = octstr_create(string); \ dict_put((*pdu)->attr, attr_str, value); \ octstr_destroy(attr_str); \ value = NULL; \ } else #define UNASSIGNED(attr) \ if (attr == attr_type) { \ error(0, "RADIUS: Attribute (%d) is unassigned and should not be used.", \ attr_type); \ continue; \ } else #define ATTRIBUTES(fields) \ while (parse_octets_left(*context) > 0 && !parse_error(*context)) { \ int attr_type, attr_len; \ Octstr *attr_val = NULL; \ Octstr *attr_str = NULL; \ attr_type = parse_get_char(*context); \ attr_len = parse_get_char(*context); \ fields \ { \ debug("radius.unpack", 0, "RADIUS: Unknown attribute type (0x%03lx) " \ "len %d in PDU `%s'.", \ (long unsigned int)attr_type, attr_len, (*pdu)->type_name); \ parse_skip(*context, attr_len - 2); \ } \ } #include "radius_attributes.def" } RADIUS_PDU *radius_pdu_unpack(Octstr *data_without_len) { RADIUS_PDU *pdu; int type, ident; long len, pos; ParseContext *context; Octstr *authenticator; len = octstr_len(data_without_len); if (len < 20) { error(0, "RADIUS: PDU was too short (%ld bytes).", octstr_len(data_without_len)); return NULL; } context = parse_context_create(data_without_len); type = parse_get_char(context); ident = parse_get_char(context); pdu = radius_pdu_create(type, NULL); if (pdu == NULL) return NULL; len = decode_integer(data_without_len, 2, 2) - 19; parse_skip(context, 2); debug("radius", 0, "RADIUS: Attributes len is %ld", len); authenticator = parse_get_octets(context, 16); octstr_dump_short(authenticator, 0, "RADIUS: Authenticator (md5) is:"); /* skipping back to context start for macro magic */ parse_context_destroy(context); context = parse_context_create(data_without_len); switch (type) { #define INTEGER(name, octets) \ pos = octstr_len(data_without_len) - parse_octets_left(context); \ p->name = decode_integer(data_without_len, pos, octets); \ parse_skip(context, octets); #define OCTETS(name, field_giving_octets) \ p->name = parse_get_octets(context, field_giving_octets); #define PDU(name, id, fields) \ case id: { struct name *p = &pdu->u.name; fields; \ radius_attr_unpack(&context, &pdu); } break; #include "radius_pdu.def" default: error(0, "Unknown RADIUS_PDU type, internal error while unpacking."); } parse_context_destroy(context); octstr_destroy(authenticator); return pdu; } int radius_authenticate_pdu(RADIUS_PDU *pdu, Octstr **data, Octstr *secret) { int rc = 0; Octstr *stream; Octstr *attributes; Octstr *digest; stream = attributes = digest = NULL; /* first extract attributes from raw data, where * the first 20 octets are code, idendifier, length * and authenticator value as described in RFC2866, sec. 3 */ if (octstr_len(*data) > 20) attributes = octstr_copy(*data, 20, octstr_len(*data)-20); switch (pdu->type) { case 0x04: /* Accounting-Request, see RFC2866, page 6 */ stream = octstr_copy(*data, 0, 4); octstr_append_data(stream, "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0", 16); octstr_append(stream, attributes); octstr_append(stream, secret); digest = md5(stream); rc = octstr_compare(pdu->u.Accounting_Request.authenticator, digest) == 0 ? 1 : 0; break; case 0x05: /* Accounting-Response, create Response authenticator */ stream = octstr_duplicate(*data); octstr_append(stream, secret); digest = md5(stream); octstr_delete(*data, 4, 16); octstr_insert(*data, digest, 4); break; default: break; } octstr_destroy(attributes); octstr_destroy(stream); octstr_destroy(digest); return rc; } static void radius_attr_dump(RADIUS_PDU *pdu) { #define UNASSIGNED(attr) #define ATTR(atype, type, string, pmin, pmax) \ id = atype; \ key = octstr_create(string); \ val = dict_get(pdu->attr, key); \ if (val != NULL) \ octstr_dump_short(val, 2, #atype); \ octstr_destroy(key); #define ATTRIBUTES(fields) \ if (pdu->attr != NULL) { \ Octstr *key = NULL, *val = NULL; \ int id; \ fields \ } #include "radius_attributes.def" } void radius_pdu_dump(RADIUS_PDU *pdu) { debug("radius", 0, "RADIUS PDU %p dump:", (void *) pdu); debug("radius", 0, " type_name: %s", pdu->type_name); switch (pdu->type) { #define INTEGER(name, octets) \ debug("radius", 0, " %s: %lu = 0x%08lx", #name, p->name, p->name); #define OCTETS(name, field_giving_octets) \ octstr_dump_short(p->name, 2, #name); #define PDU(name, id, fields) \ case id: { struct name *p = &pdu->u.name; fields; \ radius_attr_dump(pdu); } break; #include "radius_pdu.def" default: error(0, "Unknown RADIUS_PDU type, internal error."); break; } debug("radius", 0, "RADIUS PDU dump ends."); } Octstr *radius_get_attribute(RADIUS_PDU *pdu, Octstr *attribute) { gw_assert(pdu != NULL); if (pdu->attr == NULL) return NULL; return dict_get(pdu->attr, attribute); }