/*
* password.c:
* Verify a submitted password against the real one, subject to interpretation
* of an optional {scheme} prefix.
*
* Collects various crypting routines in one place.
*
* Copyright (c) 2001 Chris Lightfoot.
* Refactoring (c) 2003 Paul Makepeace.
* All rights reserved.
*
*/
#ifdef HAVE_CONFIG_H
#include "configuration.h"
#endif /* HAVE_CONFIG_H */
#ifdef HAVE_CRYPT_H
#include <crypt.h>
#endif
#ifdef AUTH_MYSQL
#include <mysql.h>
#endif
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#define _XOPEN_SRC /* crypt(3), on some systems */
#include <unistd.h>
#include "md5.h"
#include "util.h"
static const char rcsid[] = "$Id: password.c,v 1.6 2003/09/09 22:52:30 chris Exp $";
/*
* MD5 crypt(3) routines. This is here so that you can migrate passwords from
* the modern /etc/shadow crypt_md5 format used (optionally) by recent
* Linux-PAM distributions. This code was taken from Linux-PAM 0.75.
*
* (Note that on most Linux systems this won't be necessary, since the system
* crypt(3) function is `smart' in the sense that it looks for a constant
* string `$1$' at the beginning of the password hash, and if that string is
* present, uses crypt_md5 instead of traditional crypt. However, I include
* this function in the interests of portability and future compatibility.)
*
* Original author's notice:
*
* "THE BEER-WARE LICENSE" (Revision 42):
* <phk@login.dknet.dk> wrote this file. As long as you retain this notice you
* can do whatever you want with this stuff. If we meet some day, and you think
* this stuff is worth it, you can buy me a beer in return. Poul-Henning Kamp
*/
static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */
"./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
/* to64 BUFFER VALUE NUM
* Write NUM base64 characters of VALUE into BUFFER. */
static void to64(char *s, unsigned long v, int n)
{
while (--n >= 0) {
*s++ = itoa64[v&0x3f];
v >>= 6;
}
}
/* crypt_md5 PASSWORD SALT
* Poul-Henning Kamp's crypt(3)-alike using MD5. */
static char *crypt_md5(const char *pw, const char *salt)
{
const char *magic = "$1$";
/* This string is magic for this algorithm. Having
* it this way, we can get get better later on */
static char passwd[120], *p;
static const char *sp,*ep;
unsigned char final[16];
int sl,pl,i,j;
md5_ctx ctx,ctx1;
unsigned long l;
/* Refine the Salt first */
sp = salt;
/* If it starts with the magic string, then skip that */
if(!strncmp(sp,magic,strlen(magic)))
sp += strlen(magic);
/* It stops at the first '$', max 8 chars */
for(ep=sp;*ep && *ep != '$' && ep < (sp+8);ep++)
continue;
/* get the length of the true salt */
sl = ep - sp;
MD5Init(&ctx);
/* The password first, since that is what is most unknown */
MD5Update(&ctx,(unsigned char *)pw,strlen(pw));
/* Then our magic string */
MD5Update(&ctx,(unsigned char *)magic,strlen(magic));
/* Then the raw salt */
MD5Update(&ctx,(unsigned char *)sp,sl);
/* Then just as many characters of the MD5(pw,salt,pw) */
MD5Init(&ctx1);
MD5Update(&ctx1,(unsigned char *)pw,strlen(pw));
MD5Update(&ctx1,(unsigned char *)sp,sl);
MD5Update(&ctx1,(unsigned char *)pw,strlen(pw));
MD5Final(final,&ctx1);
for(pl = strlen(pw); pl > 0; pl -= 16)
MD5Update(&ctx,(unsigned char *)final,pl>16 ? 16 : pl);
/* Don't leave anything around in vm they could use. */
memset(final,0,sizeof final);
/* Then something really weird... */
for (j=0,i = strlen(pw); i ; i >>= 1)
if(i&1)
MD5Update(&ctx, (unsigned char *)final+j, 1);
else
MD5Update(&ctx, (unsigned char *)pw+j, 1);
/* Now make the output string */
strcpy(passwd,magic);
strncat(passwd,sp,sl);
strcat(passwd,"$");
MD5Final(final,&ctx);
/*
* and now, just to make sure things don't run too fast
* On a 60 Mhz Pentium this takes 34 msec, so you would
* need 30 seconds to build a 1000 entry dictionary...
*/
for(i=0;i<1000;i++) {
MD5Init(&ctx1);
if(i & 1)
MD5Update(&ctx1,(unsigned char *)pw,strlen(pw));
else
MD5Update(&ctx1,(unsigned char *)final,16);
if(i % 3)
MD5Update(&ctx1,(unsigned char *)sp,sl);
if(i % 7)
MD5Update(&ctx1,(unsigned char *)pw,strlen(pw));
if(i & 1)
MD5Update(&ctx1,(unsigned char *)final,16);
else
MD5Update(&ctx1,(unsigned char *)pw,strlen(pw));
MD5Final(final,&ctx1);
}
p = passwd + strlen(passwd);
l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p,l,4); p += 4;
l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p,l,4); p += 4;
l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p,l,4); p += 4;
l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p,l,4); p += 4;
l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p,l,4); p += 4;
l = final[11] ; to64(p,l,2); p += 2;
*p = '\0';
/* Don't leave anything around in vm they could use. */
memset(final,0,sizeof final);
return passwd;
}
/* MD5 crypt(3) routines end. */
/*
* MySQL PASSWORD() routines. This is here so that you can use the MySQL
* proprietary password-hashing routine with tpop3d. The code is inserted here
* to avoid having to do an explicit query to get the MySQL password hash.
* Observe that this is not completely safe, since the machine on which the
* MySQL server is running may use a different character set to this machine.
* However, it is probably not worth worrying about this in reality.
*
* In fact, these functions will probably be available in libmysqlclient, but
* that doesn't appear to be documented, so better safe than sorry.
*
* We make these functions available whether or not MySQL support is
* available, since they don't depend on MySQL and it's possible that somebody
* might want to migrate passwords from a MySQL database to some other system.
*
* This code is taken from the MySQL distribution. The original license for
* the code in sql/password.c states:
*
* Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB
* This file is public domain and comes with NO WARRANTY of any kind
*/
/* mysql_hash_password RESULT PASSWORD
* Basic MySQL password-hashing routine. */
static void mysql_hash_password(unsigned long *result, const char *password) {
register unsigned long nr=1345345333L, add=7, nr2=0x12345671L;
unsigned long tmp;
for (; *password; password++) {
if (*password == ' ' || *password == '\t')
continue; /* skip space in password */
tmp = (unsigned long) (unsigned char) *password;
nr ^= (((nr & 63) + add) * tmp) + (nr << 8);
nr2 += (nr2 << 8) ^ nr;
add += tmp;
}
result[0] = nr & (((unsigned long) 1L << 31) -1L); /* Don't use sign bit (str2int) */;
result[1] = nr2 & (((unsigned long) 1L << 31) -1L);
return;
}
/* mysql_make_scrambled_password RESULT PASSWORD
* MySQL function to form a password hash and turn it into a string. */
static void mysql_make_scrambled_password(char *to, const char *password) {
unsigned long hash_res[2];
mysql_hash_password(hash_res, password);
sprintf(to, "%08lx%08lx", hash_res[0], hash_res[1]);
}
/* MySQL PASSWORD() routines end. */
/* check_password USER HASH PASSWORD SCHEME
* Determine whether the given PASSWORD for the named USER matches the known
* password HASH. If there is no scheme specified in {} at the beginning of
* HASH, assume that it is SCHEME, which must be specified with the enclosing
* {}. Returns 1 if PASSWORD matches HASH, or 0 otherwise. */
int check_password(const char *who, const char *pwhash, const char *pass, const char *default_crypt_scheme) {
const char *realhash;
if (*pwhash == '{' && (realhash = strchr(pwhash + 1, '}')))
++realhash;
else
realhash = pwhash;
/* Helper macro to detect schemes. */
# define IS_SCHEME(hash, scheme, def) \
((*hash == '{' && strncmp(hash, scheme, strlen(scheme)) == 0) \
|| (*hash != '{' && strcmp(scheme, def) == 0))
if (IS_SCHEME(pwhash, "{crypt}", default_crypt_scheme)) {
/* Password hashed by system crypt function. */
return strcmp(crypt(pass, realhash), realhash) == 0;
} else if (IS_SCHEME(pwhash, "{crypt_md5}", default_crypt_scheme)) {
/* Password hashed by crypt_md5. */
return strcmp(crypt_md5(pass, realhash), realhash) == 0;
} else if (IS_SCHEME(pwhash, "{plaintext}", default_crypt_scheme)) {
/* Plain text password, as used for APOP. */
return strcmp(pass, realhash) == 0;
} else if (IS_SCHEME(pwhash, "{mysql}", default_crypt_scheme)) {
/* MySQL PASSWORD() type password hash. */
char hash[17] = {0};
int n;
mysql_make_scrambled_password(hash, pass);
/* The MySQL password format changed, and we should accept either a 16-
* or 8-character long hash. */
switch (n = strlen(realhash)) {
case 8:
return strncmp(hash, realhash, 8) == 0;
case 16:
return strcmp(hash, realhash) == 0;
default:
log_print(LOG_ERR, _("password: %s has password type mysql, but hash is of incorrect length %d (expecting 8 or 16)"), who, n);
return 0;
}
} else if (IS_SCHEME(pwhash, "{md5}", default_crypt_scheme)) {
/* Straight MD5 password. But this might be either in hex or base64
* encoding. */
if (strlen(realhash) == 32) {
/* Hex. */
return strcasecmp(realhash, md5_digest_str(pass, strlen(pass), 0)) == 0;
} else if (strlen(pwhash) == 24) {
/* Base 64. */
return strcmp(realhash, md5_digest_str(pass, strlen(pass), 1)) == 0;
} else
/* Doesn't make sense. */
log_print(LOG_ERR, _("password: %s has password type md5, but hash is of incorrect length"), who);
return 0;
} else {
/* Unknown format. */
log_print(LOG_ERR, _("password: %s has unknown password format `%.*s'"), who, strcspn(pwhash, "}"), pwhash);
return 0;
}
}
/* check_password_apop USER HASH TIMESTAMP DIGEST
* Determine whether the MD5 DIGEST supplied by USER matches the given
* password HASH and known TIMESTAMP. Returns 1 if the user has supplied a
* correct DIGEST, and 0 otherwise. Requires that HASH is of type
* {plaintext}. */
int check_password_apop(const char *who, const char *pwhash, const char *timestamp, const unsigned char *digest) {
md5_ctx ctx;
unsigned char this_digest[16];
/* Verify that this user has a plaintext password. */
if (strncmp(pwhash, "{plaintext}", 11) != 0) {
log_print(LOG_WARNING, _("password: attempted APOP login by %s, who does not have a plaintext password"), who);
return 0;
}
pwhash += 11;
/* Calculate our idea of the digest */
MD5Init(&ctx);
MD5Update(&ctx, (unsigned char*)timestamp, strlen(timestamp));
MD5Update(&ctx, (unsigned char*)pwhash, strlen(pwhash));
MD5Final(this_digest, &ctx);
return memcmp(this_digest, digest, 16) == 0;
}
syntax highlighted by Code2HTML, v. 0.9.1