Hello everybody, first, thank you for all the work that is done on freeradius! I would like to propose a patch in order to use a the pam_radius module as a radius client supporting digest authentication. The provided patch applies to the pam_radius CVS version available at this time (eg v 1.3.17). Here is a rough description : - three files modified : pam_radius_auth.c, pam_radius_auth.h, radius.h - added support for an argument 'digest' in the /etc/pam.d/client_app configuration file, in order to use the feature - two new structures added in pam_radius_auth.h : digest_attr and digest_parameters In digest authentication mode (activated with the 'digest' argument in the corresponding pam.d configuration file), a call to the conversation function (provided by the client application) is expected to fill a digest_parameters structure with the necessary attributes. The pam_radius module will parse the digest_parameters structure, build a radius packet with all the Digest-Attributes and Digest-Response, and send it to a RADIUS server. A client application using this feature will have to be aware of these structs as they are expected to be filled by the 'conversation' function, for further processing by the pam_radius module. This module could eventually be used by an application such as Asterisk for SIP user authentication, as described in the draft-sterman-aaa-sip-00.txt doc. I successfully tested the module, along with a locally running freeradius server in version 1.0.2, and a client application I wrote, after I recorded a real SIP registration in order to grab two matching Nonce and Digest-Response attributes. I can give an example of such application if someone is interested. Bye, Philippe Sultan INRIA diff -urN pam_radius/pam_radius_auth.c pam_radius-digest_auth/pam_radius_auth.c --- pam_radius/pam_radius_auth.c 2005-04-06 01:08:30.000000000 +0200 +++ pam_radius-digest_auth/pam_radius_auth.c 2005-07-25 17:46:13.000000000 +0200 @@ -140,7 +140,11 @@ ctrl |= PAM_DEBUG_ARG; conf->debug = 1; - } else { + } else if (!strcmp(*argv, "digest")) { + ctrl |= DIGEST_AUTH; + conf->authmethod = DIGEST_AUTH; + } + else { _pam_log(LOG_WARNING, "unrecognized option '%s'", *argv); } } @@ -473,6 +477,28 @@ } /* + * Add a Digest-Attribute to a RADIUS packet. + */ +static void +add_digest_attribute(AUTH_HDR *request, unsigned char subtype, CONST unsigned char *subdata, int sublength) +{ + attribute_t *p; + attribute_t *sub; + + p = (attribute_t *) ((unsigned char *)request + ntohs(request->length)); + p->attribute = PW_DIGEST_ATTRIBUTES; + p->length = sublength + 4; /* the total size of the attribute */ + + sub = (attribute_t *)p->data; + sub->attribute = subtype; + sub->length = sublength + 2; + + request->length = htons(ntohs(request->length) + p->length); + memcpy(sub->data, subdata, sublength); + +} + +/* * Add an integer attribute to a RADIUS packet. */ static void @@ -709,16 +735,23 @@ add_attribute(request, PW_USER_NAME, (unsigned char *) user, strlen(user)); /* - * Add a password, if given. + * Add a password, if given. In case of digest authentication, the + * crendetials given by the application or user are added as a + * Digest-Response attribute. */ - if (password) { - add_password(request, PW_PASSWORD, password, conf->server->secret); - - /* - * Add a NULL password to non-accounting requests. - */ - } else if (request->code != PW_ACCOUNTING_REQUEST) { - add_password(request, PW_PASSWORD, "", conf->server->secret); + if (conf -> authmethod == DIGEST_AUTH) { + add_attribute(request, PW_DIGEST_RESPONSE, password, strlen(password)); + } + else if (conf->authmethod == PASSWD_AUTH) { + if (password) { + add_password(request, PW_PASSWORD, password, conf->server->secret); + + /* + * Add a NULL password to non-accounting requests. + */ + } else if (request->code != PW_ACCOUNTING_REQUEST) { + add_password(request, PW_PASSWORD, "", conf->server->secret); + } } /* the packet is from localhost if on localhost, to make configs easier */ @@ -1037,6 +1070,38 @@ return PAM_SUCCESS; } +static int rad_digest_converse(pam_handle_t *pamh, int msg_style, char *message, struct digest_parameters **params) +{ + CONST struct pam_conv *conv; + struct pam_message resp_msg; + CONST struct pam_message *msg[1]; + struct pam_response *resp = NULL; + struct digest_parameters *aux; + int retval; + + resp_msg.msg_style = msg_style; + resp_msg.msg = message; + msg[0] = &resp_msg; + + /* grab the digest response and the digest attributes */ + retval = pam_get_item(pamh, PAM_CONV, (CONST void **) &conv); + PAM_FAIL_CHECK; + + retval = conv->conv(1, msg, &resp,conv->appdata_ptr); + PAM_FAIL_CHECK; + + aux = (struct digest_parameters *)resp->resp; + + *params = (struct digest_parameters *)malloc(sizeof(struct digest_parameters)); + + /* copy the content of appdata_ptr into the params structure */ + memcpy(*params, resp->resp, sizeof(struct digest_parameters)); + + free(resp); + + return PAM_SUCCESS; +} + /************************************************************************** * GENERAL CODE **************************************************************************/ @@ -1054,6 +1119,7 @@ { CONST char *user; char *password = NULL; + struct digest_parameters *d_params; CONST char *rhost; char *resp2challenge = NULL; int ctrl; @@ -1066,6 +1132,10 @@ radius_conf_t config; int tries; + /* sets the authentication method to AUTH_PASSWD by default. Can be modified + in the configuration file */ + config.authmethod = PASSWD_AUTH; + ctrl = _pam_parse(argc, argv, &config); tries = ((ctrl & PAM_RETRY) >> 4) + 1; @@ -1126,16 +1196,28 @@ retval = PAM_AUTH_ERR; /* use one pass only, stopping if it fails */ goto error; } - + + /* which authentication method are we using? */ + if (ctrl & DIGEST_AUTH) { + retval = rad_digest_converse(pamh, PAM_PROMPT_ECHO_OFF, "Digest: ", &d_params); + PAM_FAIL_CHECK; + } + /* check to see if we send a NULL password the first time around */ - if (!(ctrl & PAM_SKIP_PASSWD)) { + else if (!(ctrl & PAM_SKIP_PASSWD)) { retval = rad_converse(pamh, PAM_PROMPT_ECHO_OFF, "Password: ", &password); PAM_FAIL_CHECK; } } /* end of password == NULL */ - build_radius_packet(request, user, password, &config); + /* for digest authentication, we give the digest response from + the application as credentials */ + if (ctrl & DIGEST_AUTH) { + build_radius_packet(request, user, d_params->digest_response, &config); + } + else + build_radius_packet(request, user, password, &config); /* not all servers understand this service type, but some do */ add_int_attribute(request, PW_USER_SERVICE_TYPE, PW_AUTHENTICATE_ONLY); @@ -1152,6 +1234,30 @@ strlen(rhost)); } + /* + * In case of digest authentication, build the necessary Digest-Attributes + * by parsing the d_params->d_attrs_list table. + */ + if (ctrl & DIGEST_AUTH) { + int i; + digest_attr_t *aux = d_params->d_attrs_list; + DPRINT(LOG_DEBUG, "Digest response : %s", + d_params->digest_response); + DPRINT(LOG_DEBUG, "number of digest attributes = %d", d_params->num_attrs); + for (i=0; i < d_params->num_attrs; i++) { + if (i >= MAX_DIGEST_ATTRS) { + DPRINT(LOG_DEBUG, "Number of maximum digest attributes (%d) exceeded. Ignoring new ones", MAX_DIGEST_ATTRS); + break; + } + DPRINT(LOG_DEBUG, "Type = %d", (int)aux->type); + DPRINT(LOG_DEBUG, "Length = %d", (int)aux->length); + DPRINT(LOG_DEBUG, "Value = %s", aux->value); + add_digest_attribute(request, aux->type, aux->value, strlen(aux->value)); + aux ++; + } + + } + DPRINT(LOG_DEBUG, "Sending RADIUS request code %d", request->code); retval = talk_radius(&config, request, response, password, NULL, tries); diff -urN pam_radius/pam_radius_auth.h pam_radius-digest_auth/pam_radius_auth.h --- pam_radius/pam_radius_auth.h 2003-09-19 16:41:32.000000000 +0200 +++ pam_radius-digest_auth/pam_radius_auth.h 2005-07-25 16:22:23.000000000 +0200 @@ -51,8 +51,23 @@ int accounting_bug; int sockfd; int debug; + int authmethod; } radius_conf_t; +typedef struct digest_attr { + char type; + char length; + char value[256]; +} digest_attr_t; + +#define MAX_DIGEST_ATTRS 10 + +struct digest_parameters { + char *digest_response; + int num_attrs; + digest_attr_t d_attrs_list[MAX_DIGEST_ATTRS]; +}; + /************************************************************************* * Platform specific defines @@ -83,6 +98,9 @@ #define PAM_USE_FIRST_PASS 4 #define PAM_TRY_FIRST_PASS 8 +#define PASSWD_AUTH 16 +#define DIGEST_AUTH 32 + #define PAM_RETRY 0x30 /* Module defines */ diff -urN pam_radius/radius.h pam_radius-digest_auth/radius.h --- pam_radius/radius.h 2000-08-22 17:24:47.000000000 +0200 +++ pam_radius-digest_auth/radius.h 2005-07-25 16:22:30.000000000 +0200 @@ -125,6 +125,21 @@ #define PW_LOGIN_LAT_PORT 63 /* string */ #define PW_PROMPT 64 /* integer */ +/* Experimental SIP-specific attributes (draft-sterman-aaa-sip-00.txt etc) */ + +#define PW_DIGEST_RESPONSE 206 /* string */ +#define PW_DIGEST_ATTRIBUTES 207 /* string */ +#define PW_DIGEST_REALM 1 /* string */ +#define PW_DIGEST_NONCE 2 /* string */ +#define PW_DIGEST_METHOD 3 /* string */ +#define PW_DIGEST_URI 4 /* string */ +#define PW_DIGEST_QOP 5 /* string */ +#define PW_DIGEST_ALGORITHM 6 /* string */ +#define PW_DIGEST_BODY_DIGEST 7 /* string */ +#define PW_DIGEST_CNONCE 8 /* string */ +#define PW_DIGEST_NONCE_COUNT 9 /* string */ +#define PW_DIGEST_USER_NAME 10 /* string */ + /* * INTEGER TRANSLATIONS */