/*
 * Copyright (C) 2002-2004 Novell, Inc.
 *
 * edir_ldapext.c  LDAP extension for reading eDirectory universal password
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of version 2 of the GNU General Public License as published
 * by the Free Software Foundation.
 *
 * 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, contact Novell, Inc.
 *
 * To contact Novell about this file by physical or electronic mail, you may
 * find current contact  information at www.novell.com.
 *
 * Copyright 2006 The FreeRADIUS Server Project.
 */

#include <freeradius-devel/ident.h>
RCSID("$Id$")

#include <ldap.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>

/* NMAS error codes */
#define NMAS_E_BASE                       (-1600)

#define NMAS_SUCCESS                      0
#define NMAS_E_SUCCESS                    NMAS_SUCCESS         /* Alias  */
#define NMAS_OK                           NMAS_SUCCESS         /* Alias  */

#define NMAS_E_FRAG_FAILURE               (NMAS_E_BASE-31)     /* -1631 0xFFFFF9A1 */
#define NMAS_E_BUFFER_OVERFLOW            (NMAS_E_BASE-33)     /* -1633 0xFFFFF99F */
#define NMAS_E_SYSTEM_RESOURCES           (NMAS_E_BASE-34)     /* -1634 0xFFFFF99E */
#define NMAS_E_INSUFFICIENT_MEMORY        (NMAS_E_BASE-35)     /* -1635 0xFFFFF99D */
#define NMAS_E_NOT_SUPPORTED              (NMAS_E_BASE-36)     /* -1636 0xFFFFF99C */
#define NMAS_E_INVALID_PARAMETER          (NMAS_E_BASE-43)     /* -1643 0xFFFFF995 */
#define NMAS_E_INVALID_VERSION            (NMAS_E_BASE-52)     /* -1652 0xFFFFF98C */

/* OID of LDAP extenstion calls to read Universal Password */
#define NMASLDAP_GET_PASSWORD_REQUEST         "2.16.840.1.113719.1.39.42.100.13"
#define NMASLDAP_GET_PASSWORD_RESPONSE        "2.16.840.1.113719.1.39.42.100.14"

#define NMAS_LDAP_EXT_VERSION 1

/* OID of LDAP extension call to perform NMAS authentication */
#define RADAUTH_OID_NMAS_AUTH_REQUEST         "2.16.840.1.113719.1.510.100.1"
#define RADAUTH_OID_NMAS_AUTH_REPLY           "2.16.840.1.113719.1.510.100.2"

#define RADAUTH_LDAP_EXT_VERSION 1

#define REQUEST_CHALLENGED 1


/* ------------------------------------------------------------------------
 *	berEncodePasswordData
 *	==============================
 *	RequestBer contents:
 *		clientVersion				INTEGER
 *		targetObjectDN				OCTET STRING
 *		password1					OCTET STRING
 *		password2					OCTET STRING
 *
 *	Description:
 *		This function takes the request BER value and input data items
 *		and BER encodes the data into the BER value
 *
 * ------------------------------------------------------------------------ */
int berEncodePasswordData(
	struct berval **requestBV,
	char    *objectDN,
	char    *password,
	char    *password2)
{
	int err = 0, rc=0;
	BerElement *requestBer = NULL;

	char    * utf8ObjPtr = NULL;
	int     utf8ObjSize = 0;
	char    * utf8PwdPtr = NULL;
	int     utf8PwdSize = 0;
	char    * utf8Pwd2Ptr = NULL;
	int     utf8Pwd2Size = 0;


	utf8ObjSize = strlen(objectDN)+1;
	utf8ObjPtr = objectDN;

	if (password != NULL)
	{
		utf8PwdSize = strlen(password)+1;
		utf8PwdPtr = password;
	}

	if (password2 != NULL)
	{
		utf8Pwd2Size = strlen(password2)+1;
		utf8Pwd2Ptr = password2;
	}

	/* Allocate a BerElement for the request parameters.*/
	if((requestBer = ber_alloc()) == NULL)
	{
		err = NMAS_E_FRAG_FAILURE;
		goto Cleanup;
	}

	if (password != NULL && password2 != NULL)
	{
		/* BER encode the NMAS Version, the objectDN, and the password */
		rc = ber_printf(requestBer, "{iooo}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize, utf8PwdPtr, utf8PwdSize, utf8Pwd2Ptr, utf8Pwd2Size);
	}
	else if (password != NULL)
	{
		/* BER encode the NMAS Version, the objectDN, and the password */
		rc = ber_printf(requestBer, "{ioo}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize, utf8PwdPtr, utf8PwdSize);
	}
	else
	{
		/* BER encode the NMAS Version and the objectDN */
		rc = ber_printf(requestBer, "{io}", NMAS_LDAP_EXT_VERSION, utf8ObjPtr, utf8ObjSize);
	}

	if (rc < 0)
	{
		err = NMAS_E_FRAG_FAILURE;
		goto Cleanup;
	}
	else
	{
		err = 0;
	}

	/*
	 * Convert the BER we just built to a berval that we'll send with the extended request.
	 */
	if(ber_flatten(requestBer, requestBV) == LBER_ERROR)
	{
		err = NMAS_E_FRAG_FAILURE;
		goto Cleanup;
	}

Cleanup:

	if(requestBer)
	{
		ber_free(requestBer, 1);
	}

	return err;
} /* End of berEncodePasswordData */

/* ------------------------------------------------------------------------
 *	berDecodeLoginData()
 *	==============================
 *	ResponseBer contents:
 *		serverVersion				INTEGER
 *		error       				INTEGER
 *		data						OCTET STRING
 *
 *	Description:
 *		This function takes the reply BER Value and decodes the
 *		NMAS server version and return code and if a non null retData
 *		buffer was supplied, tries to decode the the return data and length
 *
 * ------------------------------------------------------------------------ */
int berDecodeLoginData(
	struct berval *replyBV,
	int      *serverVersion,
	size_t   *retDataLen,
	void     *retData )
{
	int rc=0, err = 0;
	BerElement *replyBer = NULL;
	char    *retOctStr = NULL;
	size_t  retOctStrLen = 0;

	if((replyBer = ber_init(replyBV)) == NULL)
	{
		err = NMAS_E_SYSTEM_RESOURCES;
		goto Cleanup;
	}

	if(retData)
	{
		retOctStrLen = *retDataLen + 1;
		retOctStr = (char *)malloc(retOctStrLen);
		if(!retOctStr)
		{
			err = NMAS_E_SYSTEM_RESOURCES;
			goto Cleanup;
		}

		if( (rc = ber_scanf(replyBer, "{iis}", serverVersion, &err, retOctStr, &retOctStrLen)) != -1)
		{
			if (*retDataLen >= retOctStrLen)
			{
				memcpy(retData, retOctStr, retOctStrLen);
			}
			else if (!err)
			{
				err = NMAS_E_BUFFER_OVERFLOW;
			}

			*retDataLen = retOctStrLen;
		}
		else if (!err)
		{
			err = NMAS_E_FRAG_FAILURE;
		}
	}
	else
	{
		if( (rc = ber_scanf(replyBer, "{ii}", serverVersion, &err)) == -1)
		{
			if (!err)
			{
				err = NMAS_E_FRAG_FAILURE;
			}
		}
	}

Cleanup:

	if(replyBer)
	{
		ber_free(replyBer, 1);
	}

	if (retOctStr != NULL)
	{
		memset(retOctStr, 0, retOctStrLen);
		free(retOctStr);
	}

	return err;
} /* End of berDecodeLoginData */

/* -----------------------------------------------------------------------
 *	nmasldap_get_password()
 *	==============================
 *
 *	Description:
 *		This API attempts to get the universal password
 *
 * ------------------------------------------------------------------------ */
int nmasldap_get_password(
	LDAP	 *ld,
	char     *objectDN,
	size_t   *pwdSize,	// in bytes
	char     *pwd )
{
	int err = 0;

	struct berval *requestBV = NULL;
	char *replyOID = NULL;
	struct berval *replyBV = NULL;
	int serverVersion;
	char *pwdBuf;
	size_t pwdBufLen, bufferLen;

#ifdef	NOT_N_PLAT_NLM
	int currentThreadGroupID;
#endif

	/* Validate char    parameters. */
	if(objectDN == NULL || (strlen(objectDN) == 0) || pwdSize == NULL || ld == NULL)
	{
		return NMAS_E_INVALID_PARAMETER;
	}

	bufferLen = pwdBufLen = *pwdSize;
	pwdBuf = (char *)malloc(pwdBufLen+2);
	if(pwdBuf == NULL)
	{
		return NMAS_E_INSUFFICIENT_MEMORY;
	}

#ifdef	NOT_N_PLAT_NLM
	currentThreadGroupID = SetThreadGroupID(nmasLDAPThreadGroupID);
#endif

	err = berEncodePasswordData(&requestBV, objectDN, NULL, NULL);
	if(err)
	{
		goto Cleanup;
	}

	/* Call the ldap_extended_operation (synchronously) */
	if((err = ldap_extended_operation_s(ld, NMASLDAP_GET_PASSWORD_REQUEST, requestBV, NULL, NULL, &replyOID, &replyBV)))
	{
		goto Cleanup;
	}

	/* Make sure there is a return OID */
	if(!replyOID)
	{
		err = NMAS_E_NOT_SUPPORTED;
		goto Cleanup;
	}

	/* Is this what we were expecting to get back. */
	if(strcmp(replyOID, NMASLDAP_GET_PASSWORD_RESPONSE))
	{
		err = NMAS_E_NOT_SUPPORTED;
		goto Cleanup;
	}

	/* Do we have a good returned berval? */
	if(!replyBV)
	{
		/*
		 * No; returned berval means we experienced a rather drastic error.
		 * Return operations error.
		 */
		err = NMAS_E_SYSTEM_RESOURCES;
		goto Cleanup;
	}

	err = berDecodeLoginData(replyBV, &serverVersion, &pwdBufLen, pwdBuf);

	if(serverVersion != NMAS_LDAP_EXT_VERSION)
	{
		err = NMAS_E_INVALID_VERSION;
		goto Cleanup;
	}

	if (!err && pwdBufLen != 0)
	{
		if (*pwdSize >= pwdBufLen+1 && pwd != NULL)
		{
			memcpy(pwd, pwdBuf, pwdBufLen);
			pwd[pwdBufLen] = 0; /* add null termination */
		}
		*pwdSize = pwdBufLen; /* does not include null termination */
	}

Cleanup:

	if(replyBV)
	{
		ber_bvfree(replyBV);
	}

	/* Free the return OID string if one was returned. */
	if(replyOID)
	{
		ldap_memfree(replyOID);
	}

	/* Free memory allocated while building the request ber and berval. */
	if(requestBV)
	{
		ber_bvfree(requestBV);
	}

	if (pwdBuf != NULL)
	{
		memset(pwdBuf, 0, bufferLen);
		free(pwdBuf);
	}

#ifdef	NOT_N_PLAT_NLM
	SetThreadGroupID(currentThreadGroupID);
#endif

	/* Return the appropriate error/success code. */
	return err;
} /* end of nmasldap_get_password */

/* ------------------------------------------------------------------------
 *      berEncodeAuthData
 *      ==============================
 *      RequestBer contents:
 *              targetObjectDN                                  OCTET STRING
 *              pwd                                             OCTET STRING
 *              NasIP                                           OCTET STRING
 *              stete                                           OCTET STRING
 *
 *      Description:
 *              This function takes the request BER value and input data items
 *              and BER encodes the data into the BER value
 *
 * ------------------------------------------------------------------------ */
int berEncodeAuthData(
        struct berval **requestBV,
        char    *objectDN,
        char    *pwd,
        char    *sequence,
        char    *NasIP,
        char    *state,
        int     *auth_state)
{
        int err = 0, rc=0;
        BerElement *requestBer = NULL;

        char    * utf8ObjPtr = NULL;
        int     utf8ObjSize = 0;
        char    * utf8PwdPtr = NULL;
        int     utf8PwdSize = 0;
        char    * utf8NasIPPtr = NULL;
        int     utf8NasIPSize = 0;
        char    * utf8StatePtr = NULL;
        int     utf8StateSize = 0;
        char    * utf8SeqPtr = NULL;
        int     utf8SeqSize = 0;
        int state_present = 0;

        utf8ObjSize = strlen(objectDN)+1;
        utf8ObjPtr = objectDN;

        utf8PwdSize = strlen(pwd);
        utf8PwdPtr = pwd;

        utf8SeqSize = strlen(sequence)+1;
        utf8SeqPtr = sequence;

        utf8NasIPSize = strlen(NasIP)+1;
        utf8NasIPPtr = NasIP;

        /* Allocate a BerElement for the request parameters.*/
        if((requestBer = ber_alloc()) == NULL)
        {
                err = NMAS_E_FRAG_FAILURE;
                goto Cleanup;
        }

        /* BER encode the NMAS Version, the objectDN, and the password */
        rc = ber_printf(requestBer, "{i", RADAUTH_LDAP_EXT_VERSION);
        rc = ber_printf(requestBer, "o", utf8ObjPtr, utf8ObjSize);
        rc = ber_printf(requestBer, "o", utf8PwdPtr, utf8PwdSize);
        rc = ber_printf(requestBer, "o", utf8SeqPtr, utf8SeqSize);
        rc = ber_printf(requestBer, "o", utf8NasIPPtr, utf8NasIPSize);

        if( *auth_state == -2)
        {
                utf8StateSize = strlen(state)+1;
                utf8StatePtr = state;
                state_present = 1;
                rc = ber_printf(requestBer, "io}", state_present, utf8StatePtr, utf8StateSize);
        }
        else
        {
                rc = ber_printf(requestBer, "i}", state_present);
        }

        if (rc < 0)
        {
                err = NMAS_E_FRAG_FAILURE;
                goto Cleanup;
        }
        else
        {
                err = 0;
        }
        /*
         * Convert the BER we just built to a berval that we'll send with the extended request.
         */
        if(ber_flatten(requestBer, requestBV) == -1)
        {
                err = NMAS_E_FRAG_FAILURE;
                goto Cleanup;
        }

Cleanup:

        if(requestBer)
        {
                ber_free(requestBer, 1);
        }

        return err;
} /* End of berEncodeAuthData */

/* ------------------------------------------------------------------------
 *      berDecodeAuthData()
 *      ==============================
 *      ResponseBer contents:
 *              serverVersion                           INTEGER
 *              auth_state                              INTEGER
 *              challenge_data                          OCTET STRING
 *
 *      Description:
 *              This function takes the reply BER Value and decodes the
 *              server version and return code and if a non null retData
 *              buffer was supplied, tries to decode the the return data and length
 *
 * ------------------------------------------------------------------------ */
int berDecodeAuthData(
        struct berval *replyBV,
        int      *errCode,
        size_t   *retDataLen,
        char     *retData,
        int      *auth_state )
{
        int rc=0, err = 0;
        BerElement *replyBer = NULL;
        struct berval   challenge = {0};

        if((replyBer = ber_init(replyBV)) == NULL)
        {
                err = NMAS_E_SYSTEM_RESOURCES; // fix err code
                goto Cleanup;
        }
        if( (rc = ber_scanf(replyBer, "{ii", errCode, auth_state)) != -1)
        {
                if ( *auth_state != REQUEST_CHALLENGED )
                {
                        if( (rc = ber_scanf(replyBer, "}")) != -1)
                                return err;
                }
                else
                {
                        if( (rc = ber_scanf(replyBer, "o}", &challenge)) != -1)
                        {
                                if (*retDataLen >= challenge.bv_len)
                                {
                                        memcpy(retData, challenge.bv_val, challenge.bv_len);
                                }
                                *retDataLen = challenge.bv_len;
                        }
                }
        }

Cleanup:
        if(replyBer)
        {
                ber_free(replyBer, 1);
        }

        return err;
}/* End of berDecodeLoginData */

/* -----------------------------------------------------------------------
 *      radLdapXtnNMASAuth()
 *      ==============================
 *
 *      Description:
 *              This API attempts to perform NMAS authentication.
 *
 * ------------------------------------------------------------------------ */
int radLdapXtnNMASAuth(
        LDAP    *ld,
        char    *objectDN,
        char    *pwd,
        char    *sequence,
        char    *NasIPaddr,
        size_t  *statesize,
        char    *state,
        int     *auth_state
)
{
        int err = 0;

        struct berval *requestBV = NULL;
        char *replyOID = NULL;
        struct berval *replyBV = NULL;
        int errCode;
        char *challenge;
        size_t challengesize;

        challengesize = *statesize;
        challenge = (char *)malloc(challengesize+2);
                if(challenge == NULL)
                        {
                                return NMAS_E_INSUFFICIENT_MEMORY;
                        }

         /* Validate char    parameters. */
        if(objectDN == NULL || (strlen(objectDN) == 0) || statesize == NULL || NasIPaddr == NULL || ld == NULL)
        {
                return NMAS_E_INVALID_PARAMETER;
        }

        err = berEncodeAuthData(&requestBV, objectDN, pwd, sequence, NasIPaddr, state, auth_state);

        if(err)
        {
                goto Cleanup;
        }

        /* Call the ldap_extended_operation (synchronously) */
        if((err = ldap_extended_operation_s(ld, RADAUTH_OID_NMAS_AUTH_REQUEST, requestBV, NULL, NULL, &replyOID, &replyBV))!=0)
        {
                goto Cleanup;
        }
        /* Make sure there is a return OID */
        if(!replyOID)
        {
                err = NMAS_E_NOT_SUPPORTED; // change error values
                goto Cleanup;
        }

        /* Is this what we were expecting to get back. */
        if(strcmp(replyOID, RADAUTH_OID_NMAS_AUTH_REPLY))
        {
                err = NMAS_E_NOT_SUPPORTED; // change return value
                goto Cleanup;
        }

        /* Do we have a good returned berval? */
        if(!replyBV)
        {
                /*
                 * No; returned berval means we experienced a rather drastic error.
                 * Return operations error.
                 */
                err = NMAS_E_SYSTEM_RESOURCES; //change return value
                goto Cleanup;
        }
        err = berDecodeAuthData(replyBV, &errCode, &challengesize, challenge, auth_state);

/* errCode return error in case of AUTH-REJECT */
        if (!err && challengesize!= 0)
        {
                if (*statesize >= challengesize+1 && challenge != NULL)
                {
                        memcpy(state, challenge, challengesize);
                        state[challengesize] = 0; /* add null termination */
                }
                *statesize = challengesize; /* does not include null termination */
        }

Cleanup:
        /* Free memory allocated for challenge  */
        if(challenge)
        {
                free(challenge);
        }

        if(replyBV)
        {
                ber_bvfree(replyBV);
        }

        /* Free the return OID string if one was returned. */
        if(replyOID)
        {
                ldap_memfree(replyOID);
        }

        /* Free memory allocated while building the request ber and berval. */
        if(requestBV)
        {
                ber_bvfree(requestBV);
        }

#ifdef  NOT_N_PLAT_NLM
        SetThreadGroupID(currentThreadGroupID);
#endif

        /* Return the appropriate error/success code. */
        return err;
}/* End of radLdapXtnNMASAuth */

