Updated functionality for rlm_pap
authoraland <aland>
Tue, 19 Dec 2006 00:35:57 +0000 (00:35 +0000)
committeraland <aland>
Tue, 19 Dec 2006 00:35:57 +0000 (00:35 +0000)
man/man5/rlm_pap.5
raddb/radiusd.conf.in
src/modules/rlm_pap/rlm_pap.c

index 6ae6c22..b4dbfac 100644 (file)
 .SH NAME
 rlm_pap \- FreeRADIUS Module
 .SH DESCRIPTION
-The \fIrlm_pap\fP module provides PAP functionality.
+The \fIrlm_pap\fP module performs PAP authentication.
 .PP
-PAP authentication works with passwords stored in clear-text
-format or in an encrypted ( via crypt() ) format.
+This module performs authentication when the Access-Request contains a
+User-Password attribute AND when a "known good" password has been
+configured for the user.  In addition, it takes care of decoding the
+"known good" password from hex or Base64 encoding to a form it can use
+for authentication.
+.PP
+As a result, as of 1.1.4, the "encryption_scheme" configuration item
+SHOULD NOT BE USED, and the rlm_ldap configuration
+item "password_header" SHOULD NOT BE USED.  Those items will continue to work
+in 1.1.4, and existing systems will work un-changed after upgrading to
+1.1.4.  We recommend, though, that sites using multiple instances of
+rlm_pap see if they can replace those multiple instances with one instance,
+using the new "auto_header" configuration, and remove the "password_header"
+configuration from rlm_ldap.
 .PP
 The configuration item(s):
+.IP auto_header
+Automatically discover password headers.  Permitted values are "yes"
+and "no".  For backwards compatibility, the default is "no".
+.IP
+The recommended value is "yes".
 .IP encryption_scheme
-The method used to encrypt the password.  Valid values are:
+No longer used, and therefore no longer documented.
+.PP
+When "auto_header" is set to "yes", the module will look in the
+configuration list for the User-Password attribute or the new
+Password-With-Header attribute.  If found, it will then look at the
+string value of those attributes, for one of the following headers:
+.PP
 .DS
 .br    
-       clear
+       {clear}
+.br
+       {cleartext}
+.br
+       {crypt}
+.br
+       {md5}
+.br
+       {smd5}
 .br
-       crypt (DEFAULT)
+       {sha1}
 .br
-       md5
+       {ssha1}
 .br
-       sha1
+       {nt}
+.br
+       {x-nthash}
+.br
+       {ns-mta-md5}
 .DE
+.PP
+The text following the header is taken as the "known good" password,
+either cleartext, crypted, hashed, or hashed with a salt.  If the text
+is hex or Base64 encoded, it will be decoded to obtain the correct
+form of the "known good" password.  The User-Password in the
+Access-Request will then be crypted, or hashed as appropriate, and
+compared to the "known good" password.  If they match, the user is
+authenticated, otherwise the module returns reject.
+.SH CAVEATS
+In order for the "auto_header = yes" functionality to work, the
+\fIpap\fP module MUST be listed LAST in the \fIauthorize\fP section of
+\fIradiusd.conf\fP.  This lets other modules such as LDAP blindly add
+a "known good" password to the configuration items, and the PAP module
+will just figure it out.  In most cases, multiple instances of the PAP
+module, along with complex logic to determine which one to call when,
+can be replaced with one instance of the module, with it listed last
+in the \fIauthorize\fP section.
+.PP
+Note that the \fIns_mta_md5\fP module is no longer necessary, and can
+be removed.
+.PP
+Also, setting "Auth-Type = Local" or "Auth-Type = Crypt-Local" is no
+longer necessary.  Any such settings SHOULD BE DELETED.  Simply list
+\fIpap\fP LAST in the \fIauthorize\fP section, and the module will
+take care of figuring out what to do.  (Have we emphasized that enough?)
+.PP
+Another reason to list the module last is that it will take care of
+normalizing any crypt'd or hashed password retrieved from a database.
+So it is now safe to have clear-text passwords as "{clear}...",
+because the PAP module will take care of removing the "{clear}" prefix
+from the password.  Any other modules that need access to the
+cleartext password will
+.PP
+The module uses a number of new attributes.
+.IP Password-With-Header
+This attribute should contain a "known good" password, with a header
+such as "{crypt}, or "{md5}", etc.  It should be used when the
+passwords retrieved from a DB may have different headers.  When
+\fIpap\fP is listed in the \fIauthorize\fP section, the module will
+examine this attribute, and use it to create one of the other
+attributes listed below.  That other attribute is then used for
+authentication.
+.IP
+If the passwords in a DB do not have a header, and are always in one
+particular form, then the attributes listed below can be used
+directly.  In that case, the PAP module will do hex or Base64 decoding
+of the attribute contents, if necessary.  So \fIpap\fP should still be
+listed in the \fIauthorize\fP section, because it will enable the
+maximum flexibility for the server, and minimize configuration for the
+administrator.
+.IP Cleartext-Password
+This attribute should contain the cleartext for a "known good"
+password.  Previously, the User-Password attribute was overloaded to
+contain this, both in the FAQ and in databases in many sites.  Any
+configuration that sets a cleartext form of the password using
+User-Password SHOULD UPDATE to using Cleartext-Password.  Doing so
+will simplify a number of debugging issues.
+.IP Crypt-Password
+This attribute has been around for a while, but is documented here for
+completeness.  It contains the crypt'd form of the password.
+.IP MD5-Password
+This attribute contains the MD5 hashed form of the password.
+.IP SMD5-Password
+This attribute contains the MD5 hashed form of the password, with a salt.
+.IP SHA1-Password
+This attribute contains the SHA1 hashed form of the password.
+.IP SSHA1-Password
+This attribute contains the SHA1 hashed form of the password, with a salt.
+.IP NT-Password
+This attribute has been around for a while, but is documented here for
+completeness.  It contains the NT hash form of the password, as used
+by Active Directory and Samba.
 .SH SECTIONS
+.BR authorize
 .BR authenticate
 .PP
 .SH FILES
@@ -30,5 +138,4 @@ The method used to encrypt the password.  Valid values are:
 .BR radiusd (8),
 .BR radiusd.conf (5)
 .SH AUTHOR
-Chris Parker, cparker@segv.org
-
+Alan DeKok <aland@freeradius.org>
index a6c5a79..4401ad7 100644 (file)
@@ -559,14 +559,22 @@ modules {
 
        # PAP module to authenticate users based on their stored password
        #
-       #  Supports multiple encryption schemes
-       #  clear: Clear text
-       #  crypt: Unix crypt
-       #    md5: MD5 ecnryption
-       #   sha1: SHA1 encryption.
-       #  DEFAULT: crypt
+       #  As of 1.1.4, the "encryption_scheme" configuration should
+       #  no longer be used.  For backwards compatibility, it will still
+       #  work as before, but we recommend that it is not used.
+       # 
+       #  The replacement is "auto_header = yes".
+       #  For backwards compatibility, the default is "auto_header = no",
+       #  but we recommend reviewing your use of the PAP module, based
+       #  on the documentation in "man rlm_pap".
+       #
+       #  The new capability in this module makes it MUCH easier to
+       #  configure the server for multiple crypt/hash schemes, AND
+       #  it supports more methods than before.  Please read "man rlm_pap"
+       #  for more detailed documentation.
+       #
        pap {
-               encryption_scheme = crypt
+               auto_header = yes
        }
 
        # CHAP module
@@ -1863,6 +1871,11 @@ authorize {
        #
        # Use the checkval module
 #      checkval
+
+       #
+       # As of 1.1.4, you should list "pap" last in this section.
+       # See "man rlm_pap" for more information.
+       pap
 }
 
 
index c0de84e..3d3a40a 100644 (file)
 #define PAP_ENC_MD5            2
 #define PAP_ENC_SHA1           3
 #define PAP_ENC_NT             4
-#define PAP_MAX_ENC            4
+#define PAP_ENC_LM             5
+#define PAP_ENC_SMD5           6
+#define PAP_ENC_SSHA           7
+#define PAP_ENC_NS_MTA_MD5     8
+#define PAP_ENC_AUTO           9
+#define PAP_MAX_ENC            9
 
-#define PAP_INST_FREE(inst) \
-       free((char *)inst->scheme); \
-       free(inst)
 
 static const char rcsid[] = "$Id$";
 
@@ -57,8 +59,11 @@ static const char rcsid[] = "$Id$";
  *      be used as the instance handle.
  */
 typedef struct rlm_pap_t {
+       const char *name;       /* CONF_SECTION->name, not strdup'd */
         char *scheme;  /* password encryption scheme */
        int sch;
+       char norm_passwd;
+       int auto_header;
 } rlm_pap_t;
 
 /*
@@ -72,29 +77,53 @@ typedef struct rlm_pap_t {
  */
 static CONF_PARSER module_config[] = {
   { "encryption_scheme", PW_TYPE_STRING_PTR, offsetof(rlm_pap_t,scheme), NULL, "crypt" },
+       { "auto_header", PW_TYPE_BOOLEAN, offsetof(rlm_pap_t,auto_header), NULL, "no" },
   { NULL, -1, 0, NULL, NULL }
 };
 
-static const char *pap_hextab = "0123456789abcdef";
+static const LRAD_NAME_NUMBER schemes[] = {
+       { "clear", PAP_ENC_CLEAR },
+       { "crypt", PAP_ENC_CRYPT },
+       { "md5", PAP_ENC_MD5 },
+       { "sha1", PAP_ENC_SHA1 },
+       { "nt", PAP_ENC_NT },
+       { "lm", PAP_ENC_LM },
+       { "smd5", PAP_ENC_SMD5 },
+       { "ssha", PAP_ENC_SSHA },
+       { "auto", PAP_ENC_AUTO },
+       { NULL, PAP_ENC_INVALID }
+};
+
 
 /*
- *  Smaller & faster than snprintf("%x");
- *  Completely stolen from ns_mta_md5 module
+ *     For auto-header discovery.
  */
-static void pap_hexify(char *buffer, char *str, int len)
+static const LRAD_NAME_NUMBER header_names[] = {
+       { "{clear}",    PW_CLEARTEXT_PASSWORD },
+       { "{cleartext}", PW_CLEARTEXT_PASSWORD },
+       { "{md5}",      PW_MD5_PASSWORD },
+       { "{smd5}",     PW_SMD5_PASSWORD },
+       { "{crypt}",    PW_CRYPT_PASSWORD },
+       { "{sha}",      PW_SHA_PASSWORD },
+       { "{ssha}",     PW_SSHA_PASSWORD },
+       { "{nt}",       PW_NT_PASSWORD },
+       { "{x-nthash}", PW_NT_PASSWORD },
+       { "{ns-mta-md5}", PW_NS_MTA_MD5_PASSWORD },
+       { NULL, 0 }
+};
+
+
+static int pap_detach(void *instance)
 {
-       char *pch = str;
-       char ch;
-       int i;
+       rlm_pap_t *inst = (rlm_pap_t *) instance;
 
-       for(i = 0;i < len; i ++) {
-               ch = pch[i];
-               buffer[2*i] = pap_hextab[(ch>>4) & 15];
-               buffer[2*i + 1] = pap_hextab[ch & 15];
-       }
-       return;
+       free((char *)inst->scheme);
+       free(inst);
+
+       return 0;
 }
 
+
 static int pap_instantiate(CONF_SECTION *conf, void **instance)
 {
         rlm_pap_t *inst;
@@ -113,73 +142,362 @@ static int pap_instantiate(CONF_SECTION *conf, void **instance)
          *      fail.
          */
         if (cf_section_parse(conf, inst, module_config) < 0) {
-                free(inst);
+                pap_detach(inst);
                 return -1;
         }
-       inst->sch = PAP_ENC_INVALID;
+
        if (inst->scheme == NULL || strlen(inst->scheme) == 0){
-               radlog(L_ERR, "rlm_pap: Wrong password scheme passed");
-               PAP_INST_FREE(inst);
+               radlog(L_ERR, "rlm_pap: No scheme defined");
+               pap_detach(inst);
                return -1;
        }
-       if (strcasecmp(inst->scheme,"clear") == 0)
-               inst->sch = PAP_ENC_CLEAR;
-       else if (strcasecmp(inst->scheme,"crypt") == 0){
-               inst->sch = PAP_ENC_CRYPT;
-       }
-       else if (strcasecmp(inst->scheme,"md5") == 0)
-               inst->sch = PAP_ENC_MD5;
-       else if (strcasecmp(inst->scheme,"sha1") == 0)
-               inst->sch = PAP_ENC_SHA1;
-       else if (strcasecmp(inst->scheme,"nt") == 0)
-               inst->sch = PAP_ENC_NT;
-       else{
-               radlog(L_ERR, "rlm_pap: Wrong password scheme passed");
-               PAP_INST_FREE(inst);
+
+       inst->sch = lrad_str2int(schemes, inst->scheme, PAP_ENC_INVALID);
+       if (inst->sch == PAP_ENC_INVALID) {
+               radlog(L_ERR, "rlm_pap: Unknown scheme \"%s\"", inst->scheme);
+               pap_detach(inst);
                return -1;
        }
 
         *instance = inst;
+       inst->name = cf_section_name2(conf);
+       if (!inst->name) {
+               inst->name = cf_section_name1(conf);
+       }
 
         return 0;
 }
 
+
+/*
+ *     Decode one base64 chunk
+ */
+static int decode_it(const char *src, uint8_t *dst)
+{
+       int i;
+       unsigned int x = 0;
+
+       for(i = 0; i < 4; i++) {
+               if (src[i] >= 'A' && src[i] <= 'Z')
+                       x = (x << 6) + (unsigned int)(src[i] - 'A' + 0);
+               else if (src[i] >= 'a' && src[i] <= 'z')
+                        x = (x << 6) + (unsigned int)(src[i] - 'a' + 26);
+               else if(src[i] >= '0' && src[i] <= '9') 
+                        x = (x << 6) + (unsigned int)(src[i] - '0' + 52);
+               else if(src[i] == '+')
+                       x = (x << 6) + 62;
+               else if (src[i] == '/')
+                       x = (x << 6) + 63;
+               else if (src[i] == '=')
+                       x = (x << 6);
+               else return 0;
+       }
+       
+       dst[2] = (unsigned char)(x & 255); x >>= 8;
+       dst[1] = (unsigned char)(x & 255); x >>= 8;
+       dst[0] = (unsigned char)(x & 255); x >>= 8;
+       
+       return 1;
+}
+
+
+/*
+ *     Base64 decoding.
+ */
+static int base64_decode (const char *src, uint8_t *dst)
+{
+       int length, equals;
+       int i, num;
+       uint8_t last[3];
+
+       length = equals = 0;
+       while (src[length] && src[length] != '=') length++;
+
+       if (src[length] != '=') return 0; /* no trailing '=' */
+
+       while (src[length + equals] == '=') equals++;
+
+       num = (length + equals) / 4;
+       
+       for (i = 0; i < num - 1; i++) {
+               if (!decode_it(src, dst)) return 0;
+               src += 4;
+               dst += 3;
+       }
+
+       decode_it(src, last);
+       for (i = 0; i < (3 - equals); i++) {
+               dst[i] = last[i];
+       }
+
+       return (num * 3) - equals;
+}
+
+
 /*
- *     Find the named user in this modules database.  Create the set
- *     of attribute-value pairs to check and reply with for this user
- *     from the database. The authentication code only needs to check
- *     the password, the rest is done here.
+ *     Hex or base64 or bin auto-discovery.
+ */
+static void normify(VALUE_PAIR *vp, int min_length)
+{
+       int decoded;
+       char buffer[64];
+       
+       if ((size_t) min_length >= sizeof(buffer)) return; /* paranoia */
+
+       /*
+        *      Hex encoding.
+        */
+       if (vp->length >= (2 * min_length)) {
+               decoded = lrad_hex2bin(vp->strvalue, buffer, vp->length >> 1);
+               if (decoded == (vp->length >> 1)) {
+                       DEBUG2("rlm_pap: Normalizing %s from hex encoding", vp->name);
+                       memcpy(vp->strvalue, buffer, decoded);
+                       vp->length = decoded;
+                       return;
+               }
+       }
+
+       /*
+        *      Base 64 encoding.  It's at least 4/3 the original size,
+        *      and we want to avoid division...
+        */
+       if ((vp->length * 3) >= ((min_length * 4))) {
+               decoded = base64_decode(vp->strvalue, buffer);
+               if (decoded >= min_length) {
+                       DEBUG2("rlm_pap: Normalizing %s from base64 encoding", vp->name);
+                       memcpy(vp->strvalue, buffer, decoded);
+                       vp->length = decoded;
+                       return;
+               }
+       }
+
+       /*
+        *      Else unknown encoding, or already binary.  Leave it.
+        */
+}
+
+
+/*
+ *     Authorize the user for PAP authentication.
+ *
+ *     This isn't strictly necessary, but it does make the
+ *     server simpler to configure.
+ */
+static int pap_authorize(void *instance, REQUEST *request)
+{
+       rlm_pap_t *inst = instance;
+       int auth_type = FALSE;
+       int found_pw = FALSE;
+       int user_pw = FALSE;
+       VALUE_PAIR *vp;
+       VALUE_PAIR *cleartext_pw = NULL;
+
+       for (vp = request->config_items; vp != NULL; vp = vp->next) {
+               switch (vp->attribute) {
+               case PW_USER_PASSWORD: /* deprecated */
+                       user_pw = TRUE;
+                       found_pw = TRUE;
+
+                       /*
+                        *      Look for '{foo}', and use them
+                        */
+                       if (!inst->auto_header ||
+                           (vp->strvalue[0] != '{')) {
+                               break;
+                       }
+                       /* FALL-THROUGH */
+
+               case PW_PASSWORD_WITH_HEADER: /* preferred */
+               {
+                       int attr;
+                       uint8_t *p, *q;
+                       char buffer[64];
+                       VALUE_PAIR *new_vp;
+                       
+                       found_pw = TRUE;
+                       q = vp->strvalue;
+                       p = strchr(q + 1, '}');
+                       if (!p) {
+                               /*
+                                *      FIXME: Turn it into a
+                                *      cleartext-password, unless it,
+                                *      or user-password already
+                                *      exists.
+                                */
+                               break;
+                       }
+                       
+                       if ((size_t) (p - q) > sizeof(buffer)) break;
+                       
+                       memcpy(buffer, q, p - q + 1);
+                       buffer[p - q + 1] = '\0';
+                       
+                       attr = lrad_str2int(header_names, buffer, 0);
+                       if (!attr) {
+                               DEBUG2("rlm_pap: Found unknown header {%s}: Not doing anything", buffer);
+                               break;
+                       }
+                       
+                       new_vp = paircreate(attr, PW_TYPE_STRING);
+                       if (!new_vp) break; /* OOM */
+                       
+                       strcpy(new_vp->strvalue, p + 1);/* bounds OK */
+                       new_vp->length = strlen(new_vp->strvalue);
+                       pairadd(&request->config_items, new_vp);
+
+                       /*
+                        *      May be old-style User-Password with header.
+                        *      We've found the header & created the proper
+                        *      attribute, so we should delete the old
+                        *      User-Password here.
+                        */
+                       pairdelete(&request->config_items, PW_USER_PASSWORD);
+               }
+                       break;
+
+               case PW_CLEARTEXT_PASSWORD:
+                       cleartext_pw = vp;
+
+               case PW_CRYPT_PASSWORD:
+               case PW_NS_MTA_MD5_PASSWORD:
+                       found_pw = TRUE;
+                       break;  /* don't touch these */
+
+               case PW_MD5_PASSWORD:
+               case PW_SMD5_PASSWORD:
+               case PW_NT_PASSWORD:
+               case PW_LM_PASSWORD:
+                       normify(vp, 16); /* ensure it's in the right format */
+                       found_pw = TRUE;
+                       break;
+
+               case PW_SHA_PASSWORD:
+               case PW_SSHA_PASSWORD:
+                       normify(vp, 20); /* ensure it's in the right format */
+                       found_pw = TRUE;
+                       break;
+
+                       /*
+                        *      If it's proxied somewhere, don't complain
+                        *      about not having passwords or Auth-Type.
+                        */
+               case PW_PROXY_TO_REALM:
+               {
+                       REALM *realm = realm_find(vp->strvalue, 0);
+                       if (realm &&
+                           (realm->ipaddr != htonl(INADDR_NONE))) {
+                               return RLM_MODULE_NOOP;
+                       }
+                       break;
+               }
+
+               case PW_AUTH_TYPE:
+                       auth_type = TRUE;
+
+                       /*
+                        *      Auth-Type := Accept
+                        *      Auth-Type := Reject
+                        */
+                       if ((vp->lvalue == 254) ||
+                           (vp->lvalue == 4)) {
+                           found_pw = 1;
+                       }
+                       break;
+
+               default:
+                       break;  /* ignore it */
+                       
+               }
+       }
+
+       /*
+        *      Print helpful warnings if there was no password.
+        */
+       if (!found_pw) {
+               DEBUG("rlm_pap: WARNING! No \"known good\" password found for the user.  Authentication may fail because of this.");
+               return RLM_MODULE_NOOP;
+       }
+
+       /*
+        *      For backwards compatibility with all of the other
+        *      modules, copy Cleartext-Password to User-Password
+        */
+       if (cleartext_pw && !user_pw) {
+               vp = paircreate(PW_USER_PASSWORD, PW_TYPE_STRING);
+               if (!vp) return RLM_MODULE_FAIL;
+
+               memcpy(vp, cleartext_pw, sizeof(*vp));
+               vp->next = NULL;
+               pairadd(&request->config_items, vp);
+       }
+
+       /*
+        *      Don't touch existing Auth-Types.
+        */
+       if (auth_type) {
+               DEBUG2("rlm_pap: Found existing Auth-Type, not changing it.");
+               return RLM_MODULE_NOOP;
+       }       
+
+       /*
+        *      Can't do PAP if there's no password.
+        */
+       if (!request->password ||
+           (request->password->attribute != PW_USER_PASSWORD)) {
+               /*
+                *      Don't print out debugging messages if we know
+                *      they're useless.
+                */
+               if (request->packet->code == PW_ACCESS_CHALLENGE) {
+                       return RLM_MODULE_NOOP;
+               }
+
+               DEBUG2("rlm_pap: No clear-text password in the request.  Not performing PAP.");
+               return RLM_MODULE_NOOP;
+       }
+
+       vp = paircreate(PW_AUTH_TYPE, PW_TYPE_INTEGER);
+       if (!vp) return RLM_MODULE_FAIL;
+       pairparsevalue(vp, inst->name);
+
+       pairadd(&request->config_items, vp);
+
+       return RLM_MODULE_UPDATED;
+}
+
+
+/*
+ *     Authenticate the user via one of any well-known password.
  */
 static int pap_authenticate(void *instance, REQUEST *request)
 {
-       VALUE_PAIR *passwd_item;
+       rlm_pap_t *inst = instance;
+       VALUE_PAIR *vp;
        VALUE_PAIR *module_fmsg_vp;
        char module_fmsg[MAX_STRING_LEN];
        MD5_CTX md5_context;
        SHA1_CTX sha1_context;
-       unsigned char digest[20];
+       uint8_t digest[40];
        char buff[MAX_STRING_LEN];
-       rlm_pap_t *inst = (rlm_pap_t *) instance;
-
-       /* quiet the compiler */
-       instance = instance;
-       request = request;
-
-       if(!request->username){
-               radlog(L_AUTH, "rlm_pap: Attribute \"User-Name\" is required for authentication.\n");
-               return RLM_MODULE_INVALID;
-       }
+       char buff2[MAX_STRING_LEN + 50];
+       int scheme = PAP_ENC_INVALID;
 
        if (!request->password){
                radlog(L_AUTH, "rlm_pap: Attribute \"Password\" is required for authentication.");
                return RLM_MODULE_INVALID;
        }
 
-       if (request->password->attribute != PW_PASSWORD) {
-               radlog(L_AUTH, "rlm_pap: Attribute \"Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
+       /*
+        *      Clear-text passwords are the only ones we support.
+        */
+       if (request->password->attribute != PW_USER_PASSWORD) {
+               radlog(L_AUTH, "rlm_pap: Attribute \"User-Password\" is required for authentication. Cannot use \"%s\".", request->password->name);
                return RLM_MODULE_INVALID;
        }
 
+       /*
+        *      The user MUST supply a non-zero-length password.
+        */
        if (request->password->length == 0) {
                snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: empty password supplied");
                module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
@@ -187,159 +505,311 @@ static int pap_authenticate(void *instance, REQUEST *request)
                return RLM_MODULE_INVALID;
        }
 
-       DEBUG("rlm_pap: login attempt by \"%s\" with password %s",
-               request->username->strvalue, request->password->strvalue);
+       DEBUG("rlm_pap: login attempt with password %s",
+             request->password->strvalue);
+
+       /*
+        *      First, auto-detect passwords, by attribute in the
+        *      config items.
+        */
+       if ((inst->sch == PAP_ENC_AUTO) || inst->auto_header) {
+               for (vp = request->config_items; vp != NULL; vp = vp->next) {
+                       switch (vp->attribute) {
+                       case PW_USER_PASSWORD: /* deprecated */
+                       case PW_CLEARTEXT_PASSWORD: /* preferred */
+                               goto do_clear;
+                               
+                       case PW_CRYPT_PASSWORD:
+                               goto do_crypt;
+                               
+                       case PW_MD5_PASSWORD:
+                               goto do_md5;
+                               
+                       case PW_SHA_PASSWORD:
+                               goto do_sha;
+                               
+                       case PW_NT_PASSWORD:
+                               goto do_nt;
 
-       if ((((passwd_item = pairfind(request->config_items, PW_PASSWORD)) == NULL) &&
-               ((passwd_item = pairfind(request->config_items, PW_CRYPT_PASSWORD)) == NULL)) ||
-           (passwd_item->length == 0) || (passwd_item->strvalue[0] == 0)) {
-               DEBUG("rlm_pap: No password (or empty password) to check against for user %s",request->username->strvalue);
-               snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: User password not available");
-               module_fmsg_vp = pairmake("Module-Failure-Message", module_fmsg, T_OP_EQ);
-               pairadd(&request->packet->vps, module_fmsg_vp);
-               return RLM_MODULE_INVALID;
-       }
-       if (passwd_item->attribute == PW_CRYPT_PASSWORD){
-               if (inst->sch != PAP_ENC_CRYPT){
-                       radlog(L_ERR, "rlm_pap: Crypt-Password attribute but encryption scheme is not set to CRYPT");
-                       return RLM_MODULE_FAIL;
-               }       
-       }
+                       case PW_LM_PASSWORD:
+                               goto do_lm;
 
-       DEBUG("rlm_pap: Using password \"%s\" for user %s authentication.",
-             passwd_item->strvalue, request->username->strvalue);
+                       case PW_SMD5_PASSWORD:
+                               goto do_smd5;
 
-       if (inst->sch == PAP_ENC_INVALID || inst->sch > PAP_MAX_ENC){
-               radlog(L_ERR, "rlm_pap: Wrong password scheme");
-               return RLM_MODULE_FAIL;
-       }
-       switch(inst->sch){
-               default:
-                       radlog(L_ERR, "rlm_pap: Wrong password scheme");
-                       return RLM_MODULE_FAIL;
-                       break;
-               case PAP_ENC_CLEAR:
-                       DEBUG("rlm_pap: Using clear text password.");
-                       if (strcmp((char *) passwd_item->strvalue,
-                                  (char *) request->password->strvalue) != 0){
-                               DEBUG("rlm_pap: Passwords don't match");
-                               snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: CLEAR TEXT password check failed");
-                               module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
-                               pairadd(&request->packet->vps, module_fmsg_vp);
-                               return RLM_MODULE_REJECT;
-                       }
-                       break;
-               case PAP_ENC_CRYPT:
-                       DEBUG("rlm_pap: Using CRYPT encryption.");
-                       if (lrad_crypt_check((char *) request->password->strvalue,
-                                                                (char *) passwd_item->strvalue) != 0) {
-                               DEBUG("rlm_pap: Passwords don't match");
-                               snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: CRYPT password check failed");
-                               module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
-                               pairadd(&request->packet->vps, module_fmsg_vp);
-                               return RLM_MODULE_REJECT;
-                       }
-                       break;
-               case PAP_ENC_MD5:
-                       DEBUG("rlm_pap: Using MD5 encryption.");
-
-                       if (passwd_item->length != 32) {
-                               DEBUG("rlm_pap: Configured MD5 password has incorrect length");
-                               snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured MD5 password has incorrect length");
-                               module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
-                               pairadd(&request->packet->vps, module_fmsg_vp);
-                               return RLM_MODULE_REJECT;
-                       }
+                       case PW_SSHA_PASSWORD:
+                               goto do_ssha;
 
-                       MD5Init(&md5_context);
-                       MD5Update(&md5_context, request->password->strvalue, request->password->length);
-                       MD5Final(digest, &md5_context);
-                       pap_hexify(buff,digest,16);
-                       buff[32] = '\0';
-                       if (strcmp((char *)passwd_item->strvalue, buff) != 0){
-                               DEBUG("rlm_pap: Passwords don't match");
-                               snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: MD5 password check failed");
-                               module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
-                               pairadd(&request->packet->vps, module_fmsg_vp);
-                               return RLM_MODULE_REJECT;
-                       }
-                       break;
-               case PAP_ENC_SHA1:
-
-                       DEBUG("rlm_pap: Using SHA1 encryption.");
-
-                       if (passwd_item->length != 40) {
-                               DEBUG("rlm_pap: Configured SHA1 password has incorrect length");
-                               snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SHA1 password has incorrect length");
-                               module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
-                               pairadd(&request->packet->vps, module_fmsg_vp);
-                               return RLM_MODULE_REJECT;
-                       }
+                       case PW_NS_MTA_MD5_PASSWORD:
+                               goto do_ns_mta_md5;
 
-                       SHA1Init(&sha1_context);
-                       SHA1Update(&sha1_context, request->password->strvalue, request->password->length);
-                       SHA1Final(digest,&sha1_context);
-                       pap_hexify(buff,digest,20);
-                       buff[40] = '\0';
-                       if (strcmp((char *)passwd_item->strvalue, buff) != 0){
-                               DEBUG("rlm_pap: Passwords don't match");
-                               snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: SHA1 password check failed");
-                               module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
-                               pairadd(&request->packet->vps, module_fmsg_vp);
-                               return RLM_MODULE_REJECT;
-                       }
-                       break;
-               case PAP_ENC_NT:
-                       DEBUG("rlm_pap: Using NT HASH encryption.");
-
-                       if (passwd_item->length != 32) {
-                               DEBUG("rlm_pap: Configured NT password has incorrect length");
-                               snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NT password has incorrect length");
-                               module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
-                               pairadd(&request->packet->vps, module_fmsg_vp);
-                               return RLM_MODULE_REJECT;
-                       } else {
-                               char szUnicodePass[513];
-                               int nPasswordLen;
-                               int i;
+                       default:
+                               break;  /* ignore it */
                                
-                               /*
-                                *      NT passwords are unicode.  Convert plain text password
-                                *      to unicode by inserting a zero every other byte
-                                */
-                               nPasswordLen = strlen(request->password->strvalue);
-                               for (i = 0; i < nPasswordLen; i++) {
-                                       szUnicodePass[i << 1] = request->password->strvalue[i];
-                                       szUnicodePass[(i << 1) + 1] = 0;
-                               }
-                               
-                               /* Encrypt Unicode password to a 16-byte MD4 hash */
-                               md4_calc(digest, szUnicodePass, (nPasswordLen<<1) );
-                               
-                               pap_hexify(buff,digest,16);
-                               buff[32] = '\0';
                        }
-                       if (strcmp((char *)passwd_item->strvalue, buff) != 0){
-                               DEBUG("rlm_pap: Passwords don't match");
-                               snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: NT HASH password check failed");
-                               module_fmsg_vp = pairmake("Module-Failure-Message",module_fmsg, T_OP_EQ);
-                               pairadd(&request->packet->vps, module_fmsg_vp);
-                               return RLM_MODULE_REJECT;
-                       }
-                       break;
-       }
+               }
 
-       DEBUG("rlm_pap: User authenticated succesfully");
+       fail:
+               DEBUG("rlm_pap: No password configured for the user.  Cannot do authentication");
+               return RLM_MODULE_FAIL;
 
-       return RLM_MODULE_OK;
-}
+       } else {
+               vp = NULL;
+               
+               if (inst->sch == PAP_ENC_CRYPT) {
+                       vp = pairfind(request->config_items, PW_CRYPT_PASSWORD);
+               }
+
+               /*
+                *      Old-style: all passwords are in User-Password.
+                */
+               if (!vp) {
+                       vp = pairfind(request->config_items, PW_USER_PASSWORD);
+                       if (!vp) goto fail;
+               }
+               scheme = inst->sch;
+       }
 
-static int pap_detach(void *instance)
-{
-       rlm_pap_t *inst = (rlm_pap_t *) instance;
+       /*
+        *      Now that we've decided what to do, go do it.
+        */
+       switch (scheme) {
+       case PAP_ENC_CLEAR:
+       do_clear:
+               DEBUG("rlm_pap: Using clear text password.");
+               if (strcmp((char *) vp->strvalue,
+                          (char *) request->password->strvalue) != 0){
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: CLEAR TEXT password check failed");
+                       goto make_msg;
+               }
+       done:
+               DEBUG("rlm_pap: User authenticated succesfully");
+               return RLM_MODULE_OK;
+               break;
+               
+       case PAP_ENC_CRYPT:
+       do_crypt:
+               DEBUG("rlm_pap: Using CRYPT encryption.");
+               if (lrad_crypt_check((char *) request->password->strvalue,
+                                    (char *) vp->strvalue) != 0) {
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: CRYPT password check failed");
+                       goto make_msg;
+               }
+               goto done;
+               break;
+               
+       case PW_MD5_PASSWORD:
+       do_md5:
+               DEBUG("rlm_pap: Using MD5 encryption.");
+
+               normify(vp, 16);
+               if (vp->length != 16) {
+               DEBUG("rlm_pap: Configured MD5 password has incorrect length");
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured MD5 password has incorrect length");
+                       goto make_msg;
+               }
+               
+               MD5Init(&md5_context);
+               MD5Update(&md5_context, request->password->strvalue,
+                         request->password->length);
+               MD5Final(digest, &md5_context);
+               if (memcmp(digest, vp->strvalue, vp->length) != 0) {
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: MD5 password check failed");
+                       goto make_msg;
+               }
+               goto done;
+               break;
+               
+       case PW_SMD5_PASSWORD:
+       do_smd5:
+               DEBUG("rlm_pap: Using SMD5 encryption.");
+
+               normify(vp, 16);
+               if (vp->length <= 16) {
+                       DEBUG("rlm_pap: Configured SMD5 password has incorrect length");
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SMD5 password has incorrect length");
+                       goto make_msg;
+               }
+               
+               MD5Init(&md5_context);
+               MD5Update(&md5_context, request->password->strvalue,
+                         request->password->length);
+               MD5Update(&md5_context, &vp->strvalue[16], vp->length - 16);
+               MD5Final(digest, &md5_context);
+
+               /*
+                *      Compare only the MD5 hash results, not the salt.
+                */
+               if (memcmp(digest, vp->strvalue, 16) != 0) {
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: SMD5 password check failed");
+                       goto make_msg;
+               }
+               goto done;
+               break;
+               
+       case PW_SHA_PASSWORD:
+       do_sha:
+               DEBUG("rlm_pap: Using SHA1 encryption.");
+               
+               normify(vp, 20);
+               if (vp->length != 20) {
+                       DEBUG("rlm_pap: Configured SHA1 password has incorrect length");
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SHA1 password has incorrect length");
+                       goto make_msg;
+               }
+               
+               SHA1Init(&sha1_context);
+               SHA1Update(&sha1_context, request->password->strvalue,
+                          request->password->length);
+               SHA1Final(digest,&sha1_context);
+               if (memcmp(digest, vp->strvalue, vp->length) != 0) {
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: SHA1 password check failed");
+                       goto make_msg;
+               }
+               goto done;
+               break;
+               
+       case PW_SSHA_PASSWORD:
+       do_ssha:
+               DEBUG("rlm_pap: Using SSHA encryption.");
+               
+               normify(vp, 20);
+               if (vp->length <= 20) {
+                       DEBUG("rlm_pap: Configured SSHA password has incorrect length");
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured SHA password has incorrect length");
+                       goto make_msg;
+               }
+
+               
+               SHA1Init(&sha1_context);
+               SHA1Update(&sha1_context, request->password->strvalue,
+                          request->password->length);
+               SHA1Update(&sha1_context, &vp->strvalue[20], vp->length - 20);
+               SHA1Final(digest,&sha1_context);
+               if (memcmp(digest, vp->strvalue, 20) != 0) {
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: SSHA password check failed");
+                       goto make_msg;
+               }
+               goto done;
+               break;
+               
+       case PW_NT_PASSWORD:
+       do_nt:
+               DEBUG("rlm_pap: Using NT encryption.");
+
+               normify(vp, 16);
+               if (vp->length != 16) {
+                       DEBUG("rlm_pap: Configured NT-Password has incorrect length");
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NT-Password has incorrect length");
+                       goto make_msg;
+               }
+               
+               sprintf(buff2,"%%{mschap:NT-Hash %s}",
+                       request->password->strvalue);
+               if (!radius_xlat(digest,sizeof(digest),buff2,request,NULL)){
+                       DEBUG("rlm_pap: mschap xlat failed");
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: mschap xlat failed");
+                       goto make_msg;
+               }
+               if ((lrad_hex2bin(digest, digest, 16) != vp->length) ||
+                   (memcmp(digest, vp->strvalue, vp->length) != 0)) {
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: NT password check failed");
+                       goto make_msg;
+               }
+               goto done;
+               break;
+               
+       case PW_LM_PASSWORD:
+       do_lm:
+               DEBUG("rlm_pap: Using LM encryption.");
+               
+               normify(vp, 16);
+               if (vp->length != 16) {
+                       DEBUG("rlm_pap: Configured LM-Password has incorrect length");
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured LM-Password has incorrect length");
+                       goto make_msg;
+               }
+               sprintf(buff2,"%%{mschap:LM-Hash %s}",
+                       request->password->strvalue);
+               if (!radius_xlat(digest,sizeof(digest),buff2,request,NULL)){
+                       DEBUG("rlm_pap: mschap xlat failed");
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: mschap xlat failed");
+                       goto make_msg;
+               }
+               if ((lrad_hex2bin(digest, digest, 16) != vp->length) ||
+                   (memcmp(digest, vp->strvalue, vp->length) != 0)) {
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: LM password check failed");
+               make_msg:
+                       DEBUG("rlm_pap: Passwords don't match");
+                       module_fmsg_vp = pairmake("Module-Failure-Message",
+                                                 module_fmsg, T_OP_EQ);
+                       pairadd(&request->packet->vps, module_fmsg_vp);
+                       return RLM_MODULE_REJECT;
+               }
+               goto done;
+               break;
+
+       case PAP_ENC_NS_MTA_MD5:
+       do_ns_mta_md5:
+               DEBUG("rlm_pap: Using NT-MTA-MD5 password");
+
+               if (vp->length != 64) {
+                       DEBUG("rlm_pap: Configured NS-MTA-MD5-Password has incorrect length");
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NS-MTA-MD5-Password has incorrect length");
+                       goto make_msg;
+               }
+
+               /*
+                *      Sanity check the value of NS-MTA-MD5-Password
+                */
+               if (lrad_hex2bin(vp->strvalue, buff, 32) != 16) {
+                       DEBUG("rlm_pap: Configured NS-MTA-MD5-Password has invalid value");
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: Configured NS-MTA-MD5-Password has invalid value");
+                       goto make_msg;
+               }
+
+               /*
+                *      Ensure we don't have buffer overflows.
+                *
+                *      This really: sizeof(buff) - 2 - 2*32 - strlen(passwd)
+                */
+               if (strlen(request->password->strvalue) >= (sizeof(buff2) - 2 - 2 * 32)) {
+                       DEBUG("rlm_pap: Configured password is too long");
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: password is too long");
+                       goto make_msg;
+               }
+
+               /*
+                *      Set up the algorithm.
+                */
+               {
+                       char *p = buff2;
+
+                       memcpy(p, &vp->strvalue[32], 32);
+                       p += 32;
+                       *(p++) = 89;
+                       strcpy(p, request->password->strvalue);
+                       p += strlen(p);
+                       *(p++) = 247;
+                       memcpy(p, &vp->strvalue[32], 32);
+                       p += 32;
 
-       PAP_INST_FREE(inst);
-       return 0;
+                       MD5Init(&md5_context);
+                       MD5Update(&md5_context, buff2, p - buff2);
+                       MD5Final(digest, &md5_context);
+               }
+               if (memcmp(digest, buff, 16) != 0) {
+                       snprintf(module_fmsg,sizeof(module_fmsg),"rlm_pap: NS-MTA-MD5 password check failed");
+                       goto make_msg;
+               }
+               goto done;
+
+       default:
+               break;
+       }
+
+       DEBUG("rlm_pap: No password configured for the user.  Cannot do authentication");
+       return RLM_MODULE_FAIL;
 }
 
 
@@ -359,7 +829,7 @@ module_t rlm_pap = {
        pap_instantiate,                /* instantiation */
        {
                pap_authenticate,       /* authentication */
-               NULL,                   /* authorization */
+               pap_authorize,          /* authorization */
                NULL,                   /* preaccounting */
                NULL,                   /* accounting */
                NULL,                   /* checksimul */