/*!
  @file           RTE_VerifyPassword.cpp
  @author         JoergM
  @brief          DBM RunTime: User Authentification

  To compile as stand alone password checking tool use 

    CC -DSTAND_ALONE_VERSION -o dbmcheckpw RTE_VerifyPassword.cpp

  Must be executed by root to verify any passwords...

\if EMIT_LICENCE

    ========== licence begin  GPL
    Copyright (c) 2001-2005 SAP AG

    This program is free software; you can redistribute it and/or
    modify it under the terms of the GNU General Public License
    as published by the Free Software Foundation; either version 2
    of the License, or (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    ========== licence end



\endif
*/

#ifndef STAND_ALONE_VERSION
#include "RunTime/RTE_UNIXAuthenticate.hpp"
#include "RunTime/RTE_saveUNIXcalls.h"
#include "SAPDBCommon/ErrorsAndMessages/SAPDBErr_MessageList.hpp"
#else
#include <unistd.h>
#include <string.h>
#include <shadow.h>
typedef int  RTE_FileHandle;
typedef char SAPDB_Char;
typedef int  SAPDB_Int4;
#define SDB_PASSWORD_TEST
#endif

#include <unistd.h>
#include <grp.h>
#ifndef AIX
/* on AIX this produces a conflict with stdlib.h definition of setkey.... */
#include <crypt.h>
#endif /* AIX */
#include <pwd.h>

#if defined(HPUX) || defined(SUN) || defined(LINUX) || defined(NMP)
#include <shadow.h>
#endif /* SUN | LINUX | NMP */

#if defined(AIX)
#include <userpw.h>
#endif /* AIX */

#if defined(OSF1)
#include <sys/types.h>
#include <sys/security.h>
#include <prot.h>
#endif /* OSF1 */

/*!
  @brief find crypted password for given username
  @param Username [in]
  @param pwEntry [in]
  @return crypted password
 */
static const char *FindCryptedPassword(char *Username, const struct passwd *pwEntry)
{
    const char *FoundCryptedPassword;

    // always prefer shadow password entries! 

#if defined(AIX)
    struct userpw *spwdEntry = getuserpw (Username);
    if (spwdEntry != NULL) 
    {
        FoundCryptedPassword = spwdEntry->upw_passwd;
    }
#elif defined(OSF1)
    struct pr_passwd *spwdEntry = getprpwnam(Username);
    if ( spwdEntry != NULL )
    {
        FoundCryptedPassword = spwdEntry->ufld.fd_encrypt;
    }
#else
    struct spwd *spwdEntry = getspnam (Username);
    if (spwdEntry != NULL) 
    {
        FoundCryptedPassword = spwdEntry->sp_pwdp;
    }
#endif
    else
    {
        // fallback to passwd entry only, if no shadow entry found. Needed at least for SOLARIS with LDAP...
        // Thanks to Oliver Lutz and Ingo Dahm who helped to debug this...
        FoundCryptedPassword = pwEntry->pw_passwd;
#ifdef SDB_PASSWORD_TEST
        printf("Fallback to pwEntry->pw_passwd\n");
#endif
    }

    if ( !FoundCryptedPassword
      || !FoundCryptedPassword[0]
      || !FoundCryptedPassword[1] ) /* check minimum length = 2, core dump free version of strlen() < 2 ;-) */
    {
        FoundCryptedPassword = "?";
    }

    return FoundCryptedPassword;
}

/*---------------------------------------*/
#ifndef STAND_ALONE_VERSION
#include <sys/resource.h>   /* for getrlimit() */

#ifdef OSF1
// bug in DEC standard header file...
extern "C" int setrlimit(int, const struct rlimit *);
#endif
#endif

/*!
  @brief dbverifypasswd main call
  @param argc [in] argument count
  @param argv [in] arguemnt vector
  @return exit code
 */
int main(int argc, char *argv[])
{
#if defined(OSF1)
    /* Manual page forces this call to be first in main() */
    set_auth_parameters(argc, argv);
#endif

#ifdef SDB_PASSWORD_TEST
    printf("%s compiled for password check\n", argv[0]);
#endif

#ifndef STAND_ALONE_VERSION
    struct rlimit limit;

    // suppress core dump...
    if ( getrlimit(RLIMIT_CORE, &limit) != 0 )
    {
        memset(&limit, 0, sizeof(limit));
    }
    else
    {
        limit.rlim_cur = 0;
    }
    setrlimit(RLIMIT_CORE, &limit);

    /*
      First check that caller is really 'SAPDB_Owner' and no one else...
     */
    SAPDB_Int4 userId;
    SAPDB_Int4 groupId;
    SAPDBErr_MessageList errList;

    if ( RTE_GetDefaultSapdbUserAndGroupIds( userId,
                                             groupId,
                                             errList) )
    {
        if ( RTE_save_getuid() != 0 && RTE_save_getuid() != userId )
        {
            printf("Authorization failed\n");
            return -1;
        }
    }
    else
    {
        printf("Failed to get SDB_OWNER and/or SDB_GROUP entry\n");
        return -1;
    }
#endif

    /*
      Now we check arguments
     */
    SAPDB_Char *username;
    SAPDB_Char  seedBuffer[64];
    memset( &seedBuffer[0], 0, 64);

#ifdef SDB_PASSWORD_TEST
    if ( argc != 3 )
    {
        printf("Syntax: dbmcheckpw username password\n");
        printf("Usage: Verifies the password of given username\n");
        return (1);
    }
#else
    if ( argc != 4 )
    {
        printf("Syntax: dbmverifypw username pipein pipeout\n");
        printf("Usage: Verifies the password of given username\n");
        return (1);
    }
#endif

    username = argv[1];
    SAPDB_Char *pNumber;

    struct passwd *pwEntry = getpwnam (username);
    if (pwEntry == NULL) 
    {
        printf("User '%s' unknown\n", username);
        return 2;
    }

    int saltLength = 0;
    const char *cryptedPassword = FindCryptedPassword(username, pwEntry);
    if ( !cryptedPassword
      || !cryptedPassword[0]
      || !cryptedPassword[1] ) /* check for minimum length = 2 */
    {
        cryptedPassword = 0;
        seedBuffer[0] = 'X';
        seedBuffer[1] = 'X';
        seedBuffer[2] = 0;
        saltLength = 2;
    }
    else
    {
        if ( cryptedPassword[0] != '$' )
        {
            if ( cryptedPassword[0] != '_' )
            {
                // fixed loop count DES: 2 byte salt
                saltLength = 2;
            }
            else
            {
                // looping DES:  '_' + 4 byte iteration count + 4 byte salt
                for ( saltLength = 2; saltLength <= 9; ++saltLength )
                {
                    if ( cryptedPassword[saltLength] == 0 )
                    {
                        printf("crypted DES password too short\n");
                        return 3;
                    }
                }
            }
        }
        else
        {
            // skip until second '$' detected
            for ( saltLength = 1; cryptedPassword[saltLength] != '$'; ++saltLength )
            {
                if ( cryptedPassword[saltLength] == 0 )
                {
                    printf("crypted %s password too short\n", cryptedPassword[1] == '1' ? "MD5" : "blowfish" );
                    return 3;
                }
            }

            // skip until third '$' detected
            for ( ++saltLength; cryptedPassword[saltLength] != '$'; ++saltLength )
            {
                if ( cryptedPassword[saltLength] == 0 )
                {
                    printf("crypted %s password too short\n", cryptedPassword[1] == '1' ? "MD5" : "blowfish" );
                    return 3;
                }
            }
            
            // add third '$' to salt length
            ++saltLength;
            if ( cryptedPassword[saltLength] == 0 )
            {
                printf("crypted %s password too short\n", cryptedPassword[1] == '1' ? "MD5" : "blowfish" );
                return 3;
            }

            if ( cryptedPassword[1] == '2' )
            {
                // add 128 bit salt coded in base64 (6 bits per character)
                int saltBytes = ((128+5)/6);

                while( saltBytes > 0 )
                {
                    if ( cryptedPassword[saltLength+saltBytes] == 0 )
                    {
                        printf("crypted %s password too short\n", cryptedPassword[1] == '1' ? "MD5" : "blowfish" );
                        return 3;
                    }
                    ++saltLength;
                    --saltBytes;
                }
            }
        }

        if ( saltLength > 0 )
        {
            memcpy( &seedBuffer[0], &cryptedPassword[0], saltLength);
        }
        seedBuffer[saltLength] = 0;
    }

#ifdef SDB_PASSWORD_TEST
    printf("seed buffer %s crypted password %s\n",  &seedBuffer[0], &cryptedPassword[0]);

    char *cryptBuffer = crypt(argv[2], &seedBuffer[0]);

    if ( !cryptBuffer )
    {
        printf("Got no crypted password\n");
        sleep(1); /* slows out brute force attack */
        return 1;
    }
    else
    {
        printf("CryptedPassword %s\n", cryptBuffer);
    }
#else
    RTE_FileHandle inputPipe = 0;
    RTE_FileHandle outputPipe = 0;
    pNumber = argv[2];
    while ( *pNumber >= '0' && *pNumber <= '9' )
    {
        inputPipe *= 10;
        inputPipe += (*pNumber) - '0';
        ++pNumber;
    }

    pNumber = argv[3];
    while ( *pNumber >= '0' && *pNumber <= '9' )
    {
        outputPipe *= 10;
        outputPipe += (*pNumber) - '0';
        ++pNumber;
    }

    if ( write(outputPipe, seedBuffer, strlen(seedBuffer)) != strlen(seedBuffer) )
    {
        printf("Writing seed failed\n");
        sleep(1); /* slows out brute force attack */
        return 4;
    }

    char cryptBuffer[1024];
    int bytesIn = 0;

    memset(cryptBuffer, 0, sizeof(cryptBuffer));

    while ( bytesIn < (sizeof(cryptBuffer)-1) )
    {
        int bytesRead = (int)read(inputPipe, cryptBuffer + bytesIn, (sizeof(cryptBuffer)-1) - bytesIn);
        if ( bytesRead < 0 )
        {
            printf("Read error\n");
            sleep(1); /* slows out brute force attack */
            return 5;
        }
        if ( bytesRead == 0 )
        {
            break;
        }
        else
        {
            bytesIn += bytesRead;
        }
    }
    cryptBuffer[bytesIn] = 0;
#endif

    if ( !cryptedPassword ) /* delayed to prevent ugly messages... */
    {
        printf("Password not found\n");
        sleep(1); /* slows out brute force attack */
        return 3;
    }

    if ( strncmp(cryptedPassword, cryptBuffer, strlen(cryptBuffer)) != 0 )
    {
        printf("Password mismatch\n");
        sleep(1); /* slows out brute force attack */
        return 5;
    }

#ifdef SDB_PASSWORD_TEST
    printf("Password matched\n");
#endif

    return 0;
}
