/// @file passwd
/// @brief password related sefuns
/// @author Gwenhwyvar
/// @version 0.0.0
/// @date 2016-04-02

// magics for sefun::crypt
#define _PWD_CRYPT_MD2          "$M2$"
#define _PWD_CRYPT_MD4          "$M4$"
#define _PWD_CRYPT_MD5          "$M5$"
#define _PWD_CRYPT_MDC2         "$C2$"
#define _PWD_CRYPT_RIPEMD160    "$R1$"
#define _PWD_CRYPT_SHA1         "$S1$"
#define _PWD_CRYPT_SHA224       "$S2$"
#define _PWD_CRYPT_SHA256       "$S3$"
#define _PWD_CRYPT_SHA384       "$S4$"
#define _PWD_CRYPT_SHA512       "$S5$"

// symbol set for random salts
#define _PWD_CRYPT_SALT_SET     "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_'"

// --------------------------------------------------------------------------
/// @brief check_valid_pwd
///
/// This function checks if a given string qualifies as password
/// @Param pwd
/// @Returns - SEC_PWD_OK if all tests passed
///            bitfield (int) depending on which tests failed
// --------------------------------------------------------------------------
public int check_valid_pwd(string pwd)
{
    int ret = SEC_PWD_OK;

    // a little short the password...
    if(strlen(pwd) <= MIN_LEN_PASSWD)
        ret |= SEC_PWD_SHORT;
    // without any lowercase letter? not good...
    if(!sizeof(regexp(({pwd}), "[a-z]")))
        ret |= SEC_PWD_LOWER;
    // neither is without any upper case letter...
    if(!sizeof(regexp(({pwd}), "[A-Z]")))
        ret |= SEC_PWD_UPPER;
    // any number...
    if(!sizeof(regexp(({pwd}), "[0-9]")))
        ret |= SEC_PWD_NUMBER;
    // or any punctuation and other symbols...
    if(!sizeof(regexp(({pwd}), "[.,;:!?_ +-]")))
        ret |= SEC_PWD_OTHER;
    // possible other checks:
    // - simple dictionary lookup
    // - cracklib support
    // - ...
    return ret;
}

// --------------------------------------------------------------------------
/// @brief oldcrypt
///
/// security override (forbidden)
/// @Param pwd
/// @Param salt
/// @Returns
// --------------------------------------------------------------------------
public string oldcrypt(string pwd, mixed salt)
{
    error("Usage of 'efun::oldcrypt' is forbidden! Use 'sefun::crypt' instead!");
}

// --------------------------------------------------------------------------
/// @brief crypt
///
/// extended version
/// @Param pwd - password string to be crypted
/// @Param salt - either "" or return value of previous call
/// @Param hash - if provided the hash function to be used (usefull only if
///               salt == "")
/// @Returns - crypted password (string)
// --------------------------------------------------------------------------
public string crypt(string pwd, string salt = "", string hash_fun = PWD_CRYPT_DFLT)
{
    string ret;
    int salt_ok = FALSE;

    // check if previous hashed pwd provides salt
    if((strlen(salt) > 4) && (salt[0] == '$'))
    {
        switch(salt[0..3])
        {
#if 0
            // digest length ?? bytes
            case _PWD_CRYPT_MD2:
                if((strlen(salt) == 45) && (salt[12] == '$'))
                {
                    hash_fun = PWD_CRYPT_MD2;
                    salt_ok = TRUE;
                }
                break;
#endif
                // digest length 16 bytes
            case _PWD_CRYPT_MD4:
                if((strlen(salt) == 45) && (salt[12] == '$'))
                {
                    hash_fun = PWD_CRYPT_MD4;
                    salt_ok = TRUE;
                }
                break;
            case _PWD_CRYPT_MD5:
                if((strlen(salt) == 45) && (salt[12] == '$'))
                {
                    hash_fun = PWD_CRYPT_MD5;
                    salt_ok = TRUE;
                }
                break;
            case _PWD_CRYPT_MDC2:
                if((strlen(salt) == 45) && (salt[12] == '$'))
                {
                    hash_fun = PWD_CRYPT_MDC2;
                    salt_ok = TRUE;
                }
                break;
                // digest length 20 bytes
            case _PWD_CRYPT_RIPEMD160:
                if((strlen(salt) == 53) && (salt[12] == '$'))
                {
                    hash_fun = PWD_CRYPT_RIPEMD160;
                    salt_ok = TRUE;
                }
                break;
            case _PWD_CRYPT_SHA1:
                if((strlen(salt) == 53) && (salt[12] == '$'))
                {
                    hash_fun = _PWD_CRYPT_SHA1;
                    salt_ok = TRUE;
                }
                break;
                // digest length 28 bytes
            case _PWD_CRYPT_SHA224:
                if((strlen(salt) == 69) && (salt[12] == '$'))
                {
                    hash_fun = PWD_CRYPT_SHA224;
                    salt_ok = TRUE;
                }
                break;
                // digest length 32 bytes
            case _PWD_CRYPT_SHA256:
                if((strlen(salt) == 77) && (salt[12] == '$'))
                {
                    hash_fun = PWD_CRYPT_SHA256;
                    salt_ok = TRUE;
                }
                break;
                // digest length 48 bytes
            case _PWD_CRYPT_SHA384:
                if((strlen(salt) == 109) && (salt[12] == '$'))
                {
                    hash_fun = PWD_CRYPT_SHA384;
                    salt_ok = TRUE;
                }
                break;
                // digest length 64 bytes
            case _PWD_CRYPT_SHA512:
                if((strlen(salt) == 141) && (salt[12] == '$'))
                {
                    hash_fun = PWD_CRYPT_SHA512;
                    salt_ok = TRUE;
                }
                break;
            default:
                error("sefun::crypt: unknown hash_fun type\n");
                break;
        }
        if(salt_ok)
            salt = salt[4..11];
        else
            error("sefun::crypt: got invalid salt");
    }
    if(!salt_ok)
    {
        int len = strlen(_PWD_CRYPT_SALT_SET);

        salt="        ";
        for(int i = 0; i < 8; i++)
            salt[i] = _PWD_CRYPT_SALT_SET[random(len)];
    }
    switch(hash_fun)
    {
#if 0
        case PWD_CRYPT_MD2:
                ret  = _PWD_CRYPT_MD2;
            break;
#endif
        case PWD_CRYPT_MD4:
                ret  = _PWD_CRYPT_MD4;
            break;
        case PWD_CRYPT_MD5:
                ret  = _PWD_CRYPT_MD5;
            break;
        case PWD_CRYPT_MDC2:
                ret  = _PWD_CRYPT_MDC2;
            break;
        case PWD_CRYPT_RIPEMD160:
                ret  = _PWD_CRYPT_RIPEMD160;
            break;
        case PWD_CRYPT_SHA1:
                ret  = __PWD_CRYPT_SHA1;
            break;
        case PWD_CRYPT_SHA224:
                ret  = _PWD_CRYPT_SHA224;
            break;
        case PWD_CRYPT_SHA256:
                ret  = _PWD_CRYPT_SHA256;
            break;
        case PWD_CRYPT_SHA384:
                ret  = _PWD_CRYPT_SHA384;
            break;
        case PWD_CRYPT_SHA512:
                ret  = _PWD_CRYPT_SHA512;
            break;
        default:
            error("sefun::crypt: unknown hash_fun type\n");
            break;
    }
    ret += salt  + "$";
    ret += hash(salt + pwd, hash_fun);

    return ret;
}
