/** BEGIN COPYRIGHT BLOCK
 * Copyright (C) 2001 Sun Microsystems, Inc.  Used by permission.
 * Copyright (C) 2005 Red Hat, Inc.
 * All rights reserved.
 *
 * 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; version 2
 * of the License.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 * END COPYRIGHT BLOCK **/
/*
 * User/Group Directory Server get/set for the Admin Group
 *
 * Note:
 *     Terms "Global" and "Domain" are used interchangeable
 *     Terms "Local" and "Admin Group" are used interchangeable
 *
 * Get Operation
 * in:  op=getconfig
 * out: ugdsconfig.inforef=<reference (dn) to the global U/G directory>
 * out: ugdsconfig.globaldirurl=<url for the global U/G directory>
 * out: ugdsconfig.dirurl=<url for the local U/G directory>
 * out: ugdsconfig.binddn=<bind dn for the local U/G directory>
 * out: ugdsconfig.bindpw=<bind password for the local U/G directory>
 *
 * Set Operation
 * in: op=setconfig&ugdsconfig.inforef=<inforef dn | "default">
 * "default" is a special key-word which instructs that the inforef should be set
 * to reference the global domain U/G Directory entry
 *
 * or
 *
 * in: op=setconfig&ugdsconfig.dirurl=<url>&
 *      ugdsconfig.binddn=<binddn>&ugdsconfig.bindpw=<bindpw>
 *
 * If inforef parameter is present, the CGI does not look for the parameters dirurl, binddn, bindpw
 *
 */

#include "libadmin/libadmin.h"
#include "string.h"

#include "libadminutil/admutil.h"
#include "libadminutil/resource.h"
#include "libadminutil/admutil.h"
#include "libadminutil/distadm.h"

#include "libadmsslutil/psetcssl.h"
#include "libadmsslutil/admsslutil.h"
#include "libadmsslutil/certmgt.h"
#include "util.h"
#include "cert.h"

#include "libadmin/libadmin.h"

#include "config.h"

static void handle_getconfig(const char *configdir, const char *securitydir);
static void handle_setconfig(const char *configdir, const char *securitydir);
static char *getGlobalInfoDN(char *siedn);
static char *nonull_value(char *str);


/*
 * Logging function
 */
static FILE * logfp = NULL;
static int log_enabled = 0, enable_verified=0;
static void logMsg(char *format, ...) {
	char logfile[512];

	if (!log_enabled && enable_verified) return;

	/* Automatically enable logging if <cgi-name>.dbg file exists in the logs directory */
	if (!log_enabled && !enable_verified) {
		const char *logdir = util_get_log_dir();
		enable_verified = 1;
		if (util_is_dir_ok(logdir)) {
			PR_snprintf(logfile, sizeof(logfile), "%s/ugdsconfig.dbg", logdir);

			/* Attempt to optn the log for reading
			 * to check if it exists. */
			logfp = fopen(logfile, "r");
			if (logfp == NULL) {
				return;
			}
			fclose(logfp);

			/* Attempt to open the file for writing. */
			logfp = fopen(logfile, "w");

			/* If we opened the log for writing, go
			 * ahead and enable logging. */
			if (logfp != NULL) {
				log_enabled = 1;
			}
		}
	}

	if (logfp != NULL) {
		va_list ap;
		va_start(ap,format);
		vfprintf(logfp,format,ap);
		va_end(ap);
		fflush(logfp);
	}
}

/*
 * i18n conversions defines and function;
 * properties file  = "ugdsconfig.properties"
 */
#define RESOURCE_FILE "ugdsconfig"
#define resource_key(a,b)   a b

#define DBT_NO_METHOD 			resource_key(RESOURCE_FILE, "1")
#define DBT_UNKNOWN_METHOD 		resource_key(RESOURCE_FILE, "2")
#define DBT_NO_QUERY_STRING 		resource_key(RESOURCE_FILE, "3")
#define DBT_NO_OPERATION 		resource_key(RESOURCE_FILE, "4")
#define DBT_UNKNOWN_OPERATION		resource_key(RESOURCE_FILE, "5")
#define DBT_BUILD_LDAPINFO		resource_key(RESOURCE_FILE, "6")
#define DBT_GLOBAL_GETUSERDIR		resource_key(RESOURCE_FILE, "7")
#define DBT_LOCAL_GETUSERDIR		resource_key(RESOURCE_FILE, "8")
#define DBT_PARSE_SIEDN			resource_key(RESOURCE_FILE, "9")
#define DBT_GLOBAL_SETUSERDIR		resource_key(RESOURCE_FILE, "10")
#define DBT_LOCAL_SETUSERDIR		resource_key(RESOURCE_FILE, "11")
#define DBT_NO_SETPARAM			resource_key(RESOURCE_FILE, "12")
#define DBT_BAD_DIRURL			resource_key(RESOURCE_FILE, "13")
#define DBT_ADMSSL_INIT_FAILED	        resource_key(RESOURCE_FILE, "14")

static  char *acceptLanguage = (char*)"en";
static  Resource *i18nResource = NULL;

static void i18nInit() {
	i18nResource = res_find_and_init_resource(PROPERTYDIR, RESOURCE_FILE);

	if (getenv("HTTP_ACCEPT_LANGUAGE")) {
		acceptLanguage = getenv("HTTP_ACCEPT_LANGUAGE");
	}
}
static const char *i18nMsg(char *msgid, char *defaultMsg) {
	const char *msg=NULL;
	static char buf[BUFSIZ]; /* ok - not threaded code */

	if (i18nResource) {
            msg = res_getstring(i18nResource, msgid, acceptLanguage, buf, sizeof(buf), NULL);
	}
	if (msg == NULL) {
		msg = (const char*)defaultMsg;
	}
	return msg;
}

static char * nonull_value(char *str) {
	return (str!=NULL) ? str : (char *)"";
}

static char error_info[1024];

/*
 *  Main
 */
int main(int argc, char *argv[])
{
    int _ai=ADMUTIL_Init();

    char *method = getenv("REQUEST_METHOD");
    char *qs = 0, *op=0;
    const char *configdir = util_get_conf_dir();
    const char *secdir = util_get_security_dir();

    (void)_ai; /* get rid of unused variable warning */
    logMsg(" In %s\n", argv[0]);

    i18nInit();
    fprintf(stdout, "Content-type: text/html;charset=utf-8\n\n");

    /* GET or POST method */
    if (!method || !*method) {
        /* non UI CGI */
        rpt_err(SYSTEM_ERROR, i18nMsg(DBT_NO_METHOD,"No method is specified"), NULL, NULL);
    }
    else if (!strcmp(method, "GET")) {
        /* UI CGI - either upgrade or normal admin CGI */
        qs = getenv("QUERY_STRING");

        if (!qs || !*qs) {
            rpt_err(INCORRECT_USAGE, i18nMsg(DBT_NO_QUERY_STRING ,"NO QUERY_STRING DATA"), NULL, NULL);
        }
        else {
            get_begin(qs);
        }
    }
    else if (!strcmp(method, "POST")) {
        post_begin(stdin);
    }
    else {
        PR_snprintf(error_info, sizeof(error_info), i18nMsg(DBT_UNKNOWN_METHOD,"Unknown Method (%s)"), method);
        rpt_err(INCORRECT_USAGE, error_info, NULL, NULL);
    }

    logMsg("method=%s\n", method);

    op=get_cgi_var("op", NULL, NULL);

	logMsg("op=%s\n", op);

	if (op == NULL) {
        rpt_err(INCORRECT_USAGE, i18nMsg(DBT_NO_OPERATION,"No operation is defined"), NULL, NULL);
    }
	if (strcmp(op,"getconfig") == 0) {
        handle_getconfig(configdir, secdir);
    }
    else if (strcmp(op,"setconfig") == 0) {
		handle_setconfig(configdir, secdir);
    }
	else {
        PR_snprintf(error_info, sizeof(error_info), i18nMsg(DBT_UNKNOWN_OPERATION,"Unknown Operation (%s)"), op);
        rpt_err(INCORRECT_USAGE, error_info, NULL, NULL);
    }

    return 0;
}

static AdmldapInfo
local_get_admldapinfo(const char *configdir, const char *securitydir)
{
	AdmldapInfo adminfo;
	int rc;

	if(ADMSSL_InitSimple((char *)configdir, (char *)securitydir, 1)) {
		rpt_err(SYSTEM_ERROR, i18nMsg(DBT_ADMSSL_INIT_FAILED,"Cannot initialize SSL"), NULL, NULL);
		return NULL;
	}

	adminfo = admldapBuildInfo((char *)configdir, &rc);
	if (adminfo == NULL) {
		logMsg("admldapBuildInfo failed, rc=%d, admroot=%s\n", rc, nonull_value((char *)configdir));
		PR_snprintf(error_info, sizeof(error_info), i18nMsg(DBT_BUILD_LDAPINFO,"Failed to build ldap info (err=%d)"), rc);
		rpt_err(SYSTEM_ERROR, error_info, NULL, NULL);
		return NULL;
	}

	if (admldapGetSecurity(adminfo)) {
        /* Temporarily override the siedn.  This needs to be
         * done to get a valid LDAP handle.
         */
        char *siedn = NULL;
		char *userdn = NULL;
		char *siePasswd = NULL;

		/* returned value from ADM_Get... should NOT be freed */
		ADM_GetCurrentPassword(&rc, &siePasswd); /* via PIPE */
		/*  if userdn is initialized, override the siedn to make bind succeed */
		ADM_GetUserDNString(&rc, &userdn);
		if (strcasecmp(userdn, ADM_NOT_INITIALIZED)) {
			siedn = admldapGetSIEDN(adminfo);
			admldapSetSIEDN(adminfo, userdn);
			admSetCachedSIEPWD(siePasswd);
		}

		if (!admldapBuildInfoSSL(adminfo, &rc)) {
			logMsg("admldapBuildInfo failed, rc=%d, admroot=%s\n", rc, nonull_value((char *)configdir));
			PR_snprintf(error_info, sizeof(error_info), i18nMsg(DBT_BUILD_LDAPINFO,"Failed to build ldap info (err=%d)"), rc);
			rpt_err(SYSTEM_ERROR, error_info, NULL, NULL);
			return NULL;
		}

		/* reset if we changed it */
		if (siedn) {
			admldapSetSIEDN(adminfo, siedn);
			PL_strfree(siedn);
		}
	}

    return adminfo;
}

/*
 * Return current U/G directory setting
 */
static void handle_getconfig(const char *configdir, const char *securitydir)
{
	char *inforef=NULL, *globaldirurl=NULL, *dirurl=NULL, *binddn=NULL, *bindpw=NULL;
	AdmldapInfo adminfo;
	int rc;

	logMsg("In handle_getconfig\n");

	if (!(adminfo = local_get_admldapinfo(configdir, securitydir))) {
		return;
	}

	if (!admldapGetDomainUserDirectory(adminfo, &globaldirurl, &binddn, &bindpw, &inforef, &rc)) {
		logMsg("admldapGetDomainUserDirectory failed, rc=%d\n", rc);
		PR_snprintf(error_info, sizeof(error_info), i18nMsg(DBT_GLOBAL_GETUSERDIR,"Failed to read Domain User Directory Info (err=%d)"), rc);
		rpt_err(SYSTEM_ERROR, error_info, NULL, NULL);
	}

	if (!admldapGetAdmGrpUserDirectory(adminfo, &dirurl, &binddn, &bindpw, &inforef, &rc)) {
		logMsg("admldapGetAdmGrpUserDirectory failed, rc=%d\n", rc);
		PR_snprintf(error_info, sizeof(error_info), i18nMsg(DBT_LOCAL_GETUSERDIR,"Failed to read Admin Group User Directory Info (err=%d)"), rc);
		rpt_err(SYSTEM_ERROR, error_info, NULL, NULL);
	}

	rpt_success(NULL);

	fprintf(stdout, "ugdsconfig.inforef:%s\n", nonull_value(inforef));
	logMsg("ugdsconfig.inforef:%s\n", nonull_value(inforef));

	fprintf(stdout, "ugdsconfig.globaldirurl:%s\n", nonull_value(globaldirurl));
	logMsg("ugdsconfig.globaldirurl:%s\n", nonull_value(globaldirurl));

	fprintf(stdout, "ugdsconfig.dirurl:%s\n", nonull_value(dirurl));
	logMsg("ugdsconfig.dirurl:%s\n", nonull_value(dirurl));

	fprintf(stdout, "ugdsconfig.binddn:%s\n", nonull_value(binddn));
	logMsg("ugdsconfig.binddn:%s\n", nonull_value(binddn));

	fprintf(stdout, "ugdsconfig.bindpw:%s\n", nonull_value(bindpw));
	logMsg("ugdsconfig.bindpw:%s\n", nonull_value(bindpw));
}


/*
 * Modify U/G Directory setting
 */
static void handle_setconfig(const char *configdir, const char *securitydir) {

	char *inforef=NULL, *dirurl=NULL, *binddn=NULL, *bindpw=NULL;
	char *siedn;
	AdmldapInfo adminfo;
	LDAPURLDesc *url;
	int rc;

	logMsg("In handle_setconfig\n");

	if (!(adminfo = local_get_admldapinfo(configdir, securitydir))) {
		return;
	}

	inforef = get_cgi_var( "ugdsconfig.inforef", NULL, NULL );
	dirurl = util_local_to_utf8((const char*)get_cgi_var( "ugdsconfig.dirurl", NULL, NULL ));
	binddn = get_cgi_var( "ugdsconfig.binddn", NULL, NULL );
	bindpw = get_cgi_var( "ugdsconfig.bindpw", NULL, NULL );

	logMsg("inforef=%s\n", nonull_value(inforef));
	logMsg("dirurl=%s\n",  nonull_value(dirurl));
	logMsg("binddn=%s\n", nonull_value(binddn));
	logMsg("bindpw size=%d\n", strlen(nonull_value(bindpw)));

	if (inforef != NULL) {
		if (strcasecmp(inforef,"default")==0) {
			siedn = admldapGetSIEDN(adminfo);
			inforef = getGlobalInfoDN(siedn);
		}

		if (!admldapSetAdmGrpUserDirectoryCGI(adminfo, NULL, NULL, NULL, inforef, &rc)) {
			adminfo = NULL; /* gets destroyed upon failure */
			logMsg("admldapSetAdmGrpDirectoryCGI failed, rc=%d, inforef=%s\n", rc, nonull_value(inforef));
			PR_snprintf(error_info, sizeof(error_info), i18nMsg(DBT_GLOBAL_SETUSERDIR,"Failed to set link to the Domain User Directory Info (err=%d)"), rc);
			rpt_err(SYSTEM_ERROR, error_info, NULL, NULL);
		}
	}
	else {
		if (dirurl==NULL) {
			logMsg("param ugdsconfig.dirurl missing");
			PR_snprintf(error_info, sizeof(error_info), i18nMsg(DBT_NO_SETPARAM,"Parameter %s missing"), "ugdsconfig.dirurl");
			rpt_err(INCORRECT_USAGE, error_info, NULL, NULL);
		}

		if ((rc = ldap_url_parse(dirurl, & url)) != 0) {
			logMsg("Bad ugdsconfig.dirurl");
			PR_snprintf(error_info, sizeof(error_info), i18nMsg(DBT_BAD_DIRURL,"Bad LDAP URL value (%s)"), dirurl);
			rpt_err(INCORRECT_USAGE, error_info, NULL, NULL);
		}

		if (!admldapSetAdmGrpUserDirectoryCGI(adminfo, dirurl, binddn, bindpw, NULL, &rc)) {
			adminfo = NULL; /* gets destroyed upon failure */
			logMsg("admldapSetAdmGrpDirectoryCGI failed, rc=%d\n", rc);
			PR_snprintf(error_info, sizeof(error_info), i18nMsg(DBT_LOCAL_SETUSERDIR,"Failed to set User Directory Parameters for the Admin Group (err=%d)"), rc);
			rpt_err(SYSTEM_ERROR, error_info, NULL, NULL);
		}
	}

	if (adminfo) {
		destroyAdmldap(adminfo);
	}

	rpt_success(NULL);
}


/*
 * Return the U/G Directory Info DN for the domain:
 * "cn=UserDirectory, ou=Globalpreferences,ou=<Admin Domain>,o=NetscapeSuiteSpot"
 * The <Admin Domain> name is extractioned from the sie DN. The domain name is the second
 * RDN in the sie DN from right to left.
 *
 * Example: sie dn="admin-serv-goo,ou=Netscape Adminstration Server,cn=SuiteSpot,
 * cn=goo.mcom.com, ou=Netscape SuiteSpot, o=NetscapeRoot"
 * The admin domain name is "Netscape SuiteSpot"
 * Returning  cn=UserDirectory, ou=Globalpreferences,ou=Netscape SuiteSpot,o=NetscapeSuiteSpot
 */
static char *getGlobalInfoDN(char *siedn_p) {
	char *comma, *domain;
	char *siedn=strdup(siedn_p);
	char buf[1024];

	memset(buf,0,sizeof(buf));
	logMsg("getGlobalInfoDN for %s\n", nonull_value(siedn));
	comma = strrchr(siedn, ',');
	if (comma == NULL) {
		logMsg("getGlobalInfoDN comma1 not found\n");
		rpt_err(SYSTEM_ERROR, i18nMsg(DBT_PARSE_SIEDN,"Failed to parse SIE DN"), NULL, NULL);
	}
	*comma=0;

	comma = strrchr(siedn, ',');
	if (comma == NULL) {
		logMsg("getGlobalInfoDN comma2 not found\n");
		rpt_err(SYSTEM_ERROR, i18nMsg(DBT_PARSE_SIEDN,"Failed to parse SIE DN"), NULL, NULL);
	}

	domain=comma+1;

	PR_snprintf(buf, sizeof(buf),"cn=UserDirectory,ou=Global Preferences,%s,o=NetscapeRoot", domain);
	logMsg("global info dn=\"%s\"\n", buf);
	return strdup(buf);
}
