"Fossies" - the Fresh Open Source Software Archive

Member "tpop3d-1.5.5/password.c" (31 Mar 2008, 13054 Bytes) of archive /linux/privat/tpop3d-1.5.5.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "password.c" see the Fossies "Dox" file reference documentation.

    1 /*
    2  * password.c:
    3  * Verify a submitted password against the real one, subject to interpretation
    4  * of an optional {scheme} prefix.
    5  *
    6  * Collects various crypting routines in one place.
    7  *
    8  * Copyright (c) 2001 Chris Lightfoot.
    9  * Refactoring (c) 2003 Paul Makepeace.
   10  *
   11  * This program is free software; you can redistribute it and/or modify
   12  * it under the terms of the GNU General Public License as published by
   13  * the Free Software Foundation; either version 2, or (at your option)
   14  * any later version.
   15  *
   16  * This program is distributed in the hope that it will be useful,
   17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
   18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   19  * GNU General Public License for more details.
   20  *
   21  * You should have received a copy of the GNU General Public License
   22  * along with this program; if not, write to the Free Software Foundation,
   23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
   24  */
   25 
   26 
   27 #ifdef HAVE_CONFIG_H
   28 #include "configuration.h"
   29 #endif /* HAVE_CONFIG_H */
   30 
   31 #ifdef HAVE_CRYPT_H
   32 #include <crypt.h>
   33 #endif
   34 
   35 #ifdef AUTH_MYSQL
   36 #include <mysql.h>
   37 #endif
   38 
   39 #include <stdio.h>
   40 #include <string.h>
   41 #include <syslog.h>
   42 
   43 #define _XOPEN_SRC      /* crypt(3), on some systems */
   44 #include <unistd.h>
   45 
   46 #ifdef SHA1_PASSWORDS
   47 #include <openssl/sha.h>
   48 #endif /* SHA1_PASSWORDS */
   49 
   50 #include "md5.h"
   51 #include "util.h"
   52 
   53 static const char rcsid[] = "$Id$";
   54 
   55 /* 
   56  * MD5 crypt(3) routines. This is here so that you can migrate passwords from
   57  * the modern /etc/shadow crypt_md5 format used (optionally) by recent
   58  * Linux-PAM distributions. This code was taken from Linux-PAM 0.75.
   59  *
   60  * (Note that on most Linux systems this won't be necessary, since the system
   61  * crypt(3) function is `smart' in the sense that it looks for a constant
   62  * string `$1$' at the beginning of the password hash, and if that string is
   63  * present, uses crypt_md5 instead of traditional crypt. However, I include
   64  * this function in the interests of portability and future compatibility.)
   65  *
   66  * Original author's notice:
   67  * 
   68  * "THE BEER-WARE LICENSE" (Revision 42):
   69  * <phk@login.dknet.dk> wrote this file.  As long as you retain this notice you
   70  * can do whatever you want with this stuff. If we meet some day, and you think
   71  * this stuff is worth it, you can buy me a beer in return.   Poul-Henning Kamp
   72  */
   73 
   74 static unsigned char itoa64[] = /* 0 ... 63 => ascii - 64 */
   75         "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
   76 
   77 /* to64 BUFFER VALUE NUM
   78  * Write NUM base64 characters of VALUE into BUFFER. */
   79 static void to64(char *s, unsigned long v, int n)
   80 {
   81     while (--n >= 0) {
   82         *s++ = itoa64[v&0x3f];
   83         v >>= 6;
   84     }
   85 }
   86 
   87 /* crypt_md5 PASSWORD SALT
   88  * Poul-Henning Kamp's crypt(3)-alike using MD5. */
   89 static char *crypt_md5(const char *pw, const char *salt)
   90 {
   91     const char *magic = "$1$";
   92     /* This string is magic for this algorithm.  Having
   93      * it this way, we can get get better later on */
   94     static char passwd[120], *p;
   95     static const char *sp,*ep;
   96     unsigned char   final[16];
   97     int sl,pl,i,j;
   98     md5_ctx ctx,ctx1;
   99     unsigned long l;
  100 
  101     /* Refine the Salt first */
  102     sp = salt;
  103 
  104     /* If it starts with the magic string, then skip that */
  105     if(!strncmp(sp,magic,strlen(magic)))
  106         sp += strlen(magic);
  107 
  108     /* It stops at the first '$', max 8 chars */
  109     for(ep=sp;*ep && *ep != '$' && ep < (sp+8);ep++)
  110         continue;
  111 
  112     /* get the length of the true salt */
  113     sl = ep - sp;
  114 
  115     MD5Init(&ctx);
  116 
  117     /* The password first, since that is what is most unknown */
  118     MD5Update(&ctx,(unsigned char *)pw,strlen(pw));
  119 
  120     /* Then our magic string */
  121     MD5Update(&ctx,(unsigned char *)magic,strlen(magic));
  122 
  123     /* Then the raw salt */
  124     MD5Update(&ctx,(unsigned char *)sp,sl);
  125 
  126     /* Then just as many characters of the MD5(pw,salt,pw) */
  127     MD5Init(&ctx1);
  128     MD5Update(&ctx1,(unsigned char *)pw,strlen(pw));
  129     MD5Update(&ctx1,(unsigned char *)sp,sl);
  130     MD5Update(&ctx1,(unsigned char *)pw,strlen(pw));
  131     MD5Final(final,&ctx1);
  132     for(pl = strlen(pw); pl > 0; pl -= 16)
  133         MD5Update(&ctx,(unsigned char *)final,pl>16 ? 16 : pl);
  134 
  135     /* Don't leave anything around in vm they could use. */
  136     memset(final,0,sizeof final);
  137 
  138     /* Then something really weird... */
  139     for (j=0,i = strlen(pw); i ; i >>= 1)
  140         if(i&1)
  141             MD5Update(&ctx, (unsigned char *)final+j, 1);
  142         else
  143             MD5Update(&ctx, (unsigned char *)pw+j, 1);
  144 
  145     /* Now make the output string */
  146     strcpy(passwd,magic);
  147     strncat(passwd,sp,sl);
  148     strcat(passwd,"$");
  149 
  150     MD5Final(final,&ctx);
  151 
  152     /*
  153      * and now, just to make sure things don't run too fast
  154      * On a 60 Mhz Pentium this takes 34 msec, so you would
  155      * need 30 seconds to build a 1000 entry dictionary...
  156      */
  157     for(i=0;i<1000;i++) {
  158         MD5Init(&ctx1);
  159         if(i & 1)
  160             MD5Update(&ctx1,(unsigned char *)pw,strlen(pw));
  161         else
  162             MD5Update(&ctx1,(unsigned char *)final,16);
  163 
  164         if(i % 3)
  165             MD5Update(&ctx1,(unsigned char *)sp,sl);
  166 
  167         if(i % 7)
  168             MD5Update(&ctx1,(unsigned char *)pw,strlen(pw));
  169 
  170         if(i & 1)
  171             MD5Update(&ctx1,(unsigned char *)final,16);
  172         else
  173             MD5Update(&ctx1,(unsigned char *)pw,strlen(pw));
  174         MD5Final(final,&ctx1);
  175     }
  176 
  177     p = passwd + strlen(passwd);
  178 
  179     l = (final[ 0]<<16) | (final[ 6]<<8) | final[12]; to64(p,l,4); p += 4;
  180     l = (final[ 1]<<16) | (final[ 7]<<8) | final[13]; to64(p,l,4); p += 4;
  181     l = (final[ 2]<<16) | (final[ 8]<<8) | final[14]; to64(p,l,4); p += 4;
  182     l = (final[ 3]<<16) | (final[ 9]<<8) | final[15]; to64(p,l,4); p += 4;
  183     l = (final[ 4]<<16) | (final[10]<<8) | final[ 5]; to64(p,l,4); p += 4;
  184     l =                    final[11]                ; to64(p,l,2); p += 2;
  185     *p = '\0';
  186 
  187     /* Don't leave anything around in vm they could use. */
  188     memset(final,0,sizeof final);
  189 
  190     return passwd;
  191 }
  192 
  193 /* MD5 crypt(3) routines end. */
  194 
  195 
  196 /* 
  197  * MySQL PASSWORD() routines. This is here so that you can use the MySQL
  198  * proprietary password-hashing routine with tpop3d. The code is inserted here
  199  * to avoid having to do an explicit query to get the MySQL password hash.
  200  * Observe that this is not completely safe, since the machine on which the
  201  * MySQL server is running may use a different character set to this machine.
  202  * However, it is probably not worth worrying about this in reality.
  203  *
  204  * In fact, these functions will probably be available in libmysqlclient, but
  205  * that doesn't appear to be documented, so better safe than sorry.
  206  *
  207  * We make these functions available whether or not MySQL support is
  208  * available, since they don't depend on MySQL and it's possible that somebody
  209  * might want to migrate passwords from a MySQL database to some other system.
  210  *
  211  * This code is taken from the MySQL distribution. The original license for
  212  * the code in sql/password.c states:
  213  *
  214  * Copyright Abandoned 1996 TCX DataKonsult AB & Monty Program KB & Detron HB
  215  * This file is public domain and comes with NO WARRANTY of any kind
  216  */
  217 
  218 /* mysql_hash_password RESULT PASSWORD
  219  * Basic MySQL password-hashing routine. */
  220 static void mysql_hash_password(unsigned long *result, const char *password) {
  221     register unsigned long nr=1345345333L, add=7, nr2=0x12345671L;
  222     unsigned long tmp;
  223     for (; *password; password++) {
  224         if (*password == ' ' || *password == '\t')
  225             continue;           /* skip space in password */
  226         tmp  = (unsigned long) (unsigned char) *password;
  227         nr  ^= (((nr & 63) + add) * tmp) + (nr << 8);
  228         nr2 += (nr2 << 8) ^ nr;
  229         add += tmp;
  230     }
  231     result[0] =  nr & (((unsigned long) 1L << 31) -1L); /* Don't use sign bit (str2int) */;
  232     result[1] = nr2 & (((unsigned long) 1L << 31) -1L);
  233     return;
  234 }
  235 
  236 /* mysql_make_scrambled_password RESULT PASSWORD
  237  * MySQL function to form a password hash and turn it into a string. */
  238 static void mysql_make_scrambled_password(char *to, const char *password) {
  239     unsigned long hash_res[2];
  240     mysql_hash_password(hash_res, password);
  241     sprintf(to, "%08lx%08lx", hash_res[0], hash_res[1]);
  242 }
  243 
  244 /* MySQL PASSWORD() routines end. */
  245 
  246 /* check_password USER HASH PASSWORD SCHEME
  247  * Determine whether the given PASSWORD for the named USER matches the known
  248  * password HASH. If there is no scheme specified in {} at the beginning of
  249  * HASH, assume that it is SCHEME, which must be specified with the enclosing
  250  * {}. Returns 1 if PASSWORD matches HASH, or 0 otherwise. */
  251 int check_password(const char *who, const char *pwhash, const char *pass, const char *default_crypt_scheme) {
  252     const char *realhash;
  253 
  254     if (*pwhash == '{' && (realhash = strchr(pwhash + 1, '}')))
  255         ++realhash;
  256     else
  257         realhash = pwhash;
  258 
  259     /* Helper macro to detect schemes. */
  260 #   define IS_SCHEME(hash, scheme, def)                                 \
  261         ((*hash == '{' && strncmp(hash, scheme, strlen(scheme)) == 0)   \
  262          || (*hash != '{' && strcmp(scheme, def) == 0))
  263     
  264     if (IS_SCHEME(pwhash, "{crypt}", default_crypt_scheme)) {
  265         /* Password hashed by system crypt function. */
  266         return strcmp(crypt(pass, realhash), realhash) == 0;
  267     } else if (IS_SCHEME(pwhash, "{crypt_md5}", default_crypt_scheme)) {
  268         /* Password hashed by crypt_md5. */
  269         return strcmp(crypt_md5(pass, realhash), realhash) == 0;
  270     } else if (IS_SCHEME(pwhash, "{plaintext}", default_crypt_scheme)) {
  271         /* Plain text password, as used for APOP. */
  272         return strcmp(pass, realhash) == 0;
  273     } else if (IS_SCHEME(pwhash, "{mysql}", default_crypt_scheme)) {
  274         /* MySQL PASSWORD() type password hash. */
  275         char hash[17] = {0};
  276         int n;
  277         mysql_make_scrambled_password(hash, pass);
  278         /* The MySQL password format changed, and we should accept either a 16-
  279          * or 8-character long hash. */
  280         switch (n = strlen(realhash)) {
  281             case 8:
  282                 return strncmp(hash, realhash, 8) == 0;
  283 
  284             case 16:
  285                 return strcmp(hash, realhash) == 0;
  286 
  287             default:
  288                 log_print(LOG_ERR, _("password: %s has password type mysql, but hash is of incorrect length %d (expecting 8 or 16)"), who, n);
  289                 return 0;
  290         }
  291 #ifdef SHA1_PASSWORDS
  292     } else if (IS_SCHEME(pwhash, "{sha1}", default_crypt_scheme)) {
  293         /* XXX Messy. We should generalise hashing, probably by just using
  294          * OpenSSL's code for it. Further, this only supports hex-encoded
  295          * passwords, not base64 as well, because the base64 code is tied to
  296          * the MD5 code.... */
  297         unsigned char h[20], hh[41];
  298         SHA_CTX c;
  299         int i;
  300 
  301         if (strlen(realhash) != 40) {
  302             log_print(LOG_ERR, _("password: %s has password type sha1, but has is of incorrect length"), who);
  303             return 0;
  304         }
  305         
  306         SHA1_Init(&c);
  307         SHA1_Update(&c, pass, strlen(pass));
  308         SHA1_Final(h, &c);
  309 
  310         for (i = 0; i < 20; ++i)
  311             sprintf(hh + 2 * i, "%02x", (unsigned int)h[i]);
  312 
  313         return strcasecmp(realhash, hh) == 0;
  314 #endif /* SHA1_PASSWORDS */
  315     } else if (IS_SCHEME(pwhash, "{md5}", default_crypt_scheme)) {
  316         /* Straight MD5 password. But this might be either in hex or base64
  317          * encoding. */
  318         if (strlen(realhash) == 32) {
  319             /* Hex. */
  320             return strcasecmp(realhash, md5_digest_str(pass, strlen(pass), 0)) == 0;
  321         } else if (strlen(realhash) == 24) {
  322             /* Base 64. */
  323             return strcmp(realhash, md5_digest_str(pass, strlen(pass), 1)) == 0;
  324         } else
  325             /* Doesn't make sense. */
  326             log_print(LOG_ERR, _("password: %s has password type md5, but hash is of incorrect length"), who);
  327             return 0;
  328     } else {
  329         /* Unknown format. */
  330         log_print(LOG_ERR, _("password: %s has unknown password format `%.*s'"), who, strcspn(pwhash, "}"), pwhash);
  331         return 0;
  332     }
  333 }
  334 
  335 /* check_password_apop USER HASH TIMESTAMP DIGEST
  336  * Determine whether the MD5 DIGEST supplied by USER matches the given
  337  * password HASH and known TIMESTAMP. Returns 1 if the user has supplied a
  338  * correct DIGEST, and 0 otherwise. Requires that HASH is of type
  339  * {plaintext}. */
  340 int check_password_apop(const char *who, const char *pwhash, const char *timestamp, const unsigned char *digest) {
  341     md5_ctx ctx;
  342     unsigned char this_digest[16];
  343 
  344     /* Verify that this user has a plaintext password. */
  345     if (strncmp(pwhash, "{plaintext}", 11) != 0) {
  346         log_print(LOG_WARNING, _("password: attempted APOP login by %s, who does not have a plaintext password"), who);
  347         return 0;
  348     }
  349     pwhash += 11;
  350 
  351     /* Calculate our idea of the digest */
  352     MD5Init(&ctx);
  353     MD5Update(&ctx, (unsigned char*)timestamp, strlen(timestamp));
  354     MD5Update(&ctx, (unsigned char*)pwhash,    strlen(pwhash));
  355     MD5Final(this_digest, &ctx);
  356 
  357     return memcmp(this_digest, digest, 16) == 0;
  358 }