/** 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 **/

/*
 * Admin Superuser username/password get/set.
 *
 * Get Operation
 * in:  op=get
 * out: admpw.uid=<userid from config/admpw file>
 *
 * Set Operation
 * in:op=set&admpw.uid=<newuid>&admpw.pw=<new password>
 * If admpw.uid is found in cgi parameters, update uid in the config/admpw file
 * If admpw.pw is found in cgi parameters, update password in:
 *   1) "userPassword" attribute in the SIE on the DS
 *   2) config/admpw file. The password is stored in encrypted form
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <prtypes.h>

#include <stdio.h>
#include <stdarg.h>  /* for vsprintf */
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

/* NSS - for password hashing */
#include <pk11func.h>
#include <pk11pqg.h>
#include <base64.h>

/* NSS - for password hashing */
#include <pk11func.h>
#include <pk11pqg.h>
#include <base64.h>

#include "libadminutil/resource.h"
#include "libadminutil/admutil.h"
#include "libadminutil/distadm.h"
#include "libadmsslutil/psetcssl.h"
#include "libadmsslutil/admsslutil.h"

#include "libadmin/libadmin.h"

static void output_admuid(AdmldapInfo admInfo);
static void update_uidpwd();
static void update_admpwd(char *newuid, char *newpw, const char *filename);

static char *
sha1_pw_enc(const char *pwd)
{
   unsigned char hash[SHA1_LENGTH];
   char *enc;
   char *retval;
   int32 pwdlen = strlen(pwd);

   /* SHA1 hash the user's key */
   PK11_HashBuf(SEC_OID_SHA1, hash, (unsigned char *)pwd, pwdlen);
   /* convert to base64 */
   if (!(enc = BTOA_DataToAscii(hash, sizeof(hash)))) {
      return NULL;
   }
   /* add "{SHA}" in front of string */
   retval = PR_smprintf("{SHA}%s", enc);
   PORT_Free(enc);

   return retval;
}

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

	if (!log_enabled) return;

	if (logfp==NULL) {
        const char *logdir = util_get_log_dir();
        if (logdir) {
            PR_snprintf(logfile, sizeof(logfile), "%s/admpw.dbg", logdir);
            logfp = fopen(logfile, "w");
        }
	}

	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  = "admpw.properties"
 */
#define RESOURCE_FILE "admpw"
#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_OPEN_ADMPW       		resource_key(RESOURCE_FILE, "6")
#define DBT_READ_ADMPW            	resource_key(RESOURCE_FILE, "7")
#define DBT_ADMPW_CORRUPTED		resource_key(RESOURCE_FILE, "8")
#define DBT_NO_PARAM			resource_key(RESOURCE_FILE, "9")
#define DBT_OPEN_ADMPW_WR		resource_key(RESOURCE_FILE, "10")
#define DBT_WR_ADMPW			resource_key(RESOURCE_FILE, "11")
#define DBT_OPEN_ADMCONF_RD		resource_key(RESOURCE_FILE, "12")
#define DBT_NO_SIEPID            	resource_key(RESOURCE_FILE, "13")
#define DBT_OPEN_ADMCONF_WR		resource_key(RESOURCE_FILE, "14")
#define DBT_NO_USERNAME			resource_key(RESOURCE_FILE, "15")
#define DBT_NO_USERDN			resource_key(RESOURCE_FILE, "16")
#define DBT_SECURITY_INIT		resource_key(RESOURCE_FILE, "17")
#define DBT_PSET_CREATE			resource_key(RESOURCE_FILE, "18")
#define DBT_PSET_ERROR			resource_key(RESOURCE_FILE, "19")
#define DBT_PSET_SET			resource_key(RESOURCE_FILE, "20")

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;
}

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

    char *method = getenv("REQUEST_METHOD");
    char *qs = 0, *op=0;
    char error_info[128];
    AdmldapInfo admInfo;
    int errorcode = 0;
    char *configdir = util_get_conf_dir();
	    
    (void)_ai; /* get rid of unused variable warning */
    logMsg(" In %s\n", argv[0]);

    i18nInit();
    admInfo = admldapBuildInfoOnly(configdir, &errorcode);
    if (!admInfo || errorcode) {
	rpt_err(SYSTEM_ERROR, i18nMsg(DBT_OPEN_ADMPW, "Can not open admpw file"), NULL, NULL);
    }

    /* 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 defined"), NULL, NULL);
    }
    else if (strcmp(op,"get")==0) {
	output_admuid(admInfo);
    }
    else if (strcmp(op,"set")==0) {		
	update_uidpwd(admInfo);
    }
    else {
        PR_snprintf(error_info, sizeof(error_info), i18nMsg(DBT_UNKNOWN_OPERATION, "Unknown Operation (%s)"), op);
        rpt_err(INCORRECT_USAGE, error_info, NULL, NULL);
    }

   destroyAdmldap(admInfo);
   exit(0);
   return 0;
}



/*
 * Output user name from the admpwd. The password does not need to be sent
 */

static void output_admuid(AdmldapInfo admInfo) {
	char *uid=admldapGetLocalAdminName(admInfo);

	if (uid == NULL) {
		rpt_err(SYSTEM_ERROR, i18nMsg(DBT_ADMPW_CORRUPTED, "admpw file corrupted"), NULL, NULL);
	}
	
	logMsg("uid=%s, pw=%s\n", uid, "(secret)");

	fprintf(stdout, "Content-type: text/html\n\n");
	fprintf(stdout, "admpw.uid: %s\n", uid);
	PL_strfree(uid);
}

/*
 * Modify local superuser username and password
 */
static void update_uidpwd(AdmldapInfo admInfo) {

	FILE *f;
	char *uid=NULL, *pw=NULL, *col=NULL;
	char *newuid=NULL, *newpw=NULL;
	static char filename[BIG_LINE];
	static char inbuf[BIG_LINE];
	char *tmpfile = admldapGetAdmpwFilePath(admInfo);

	PR_snprintf(filename, sizeof(filename), tmpfile);
	PL_strfree(tmpfile);

	newuid = get_cgi_var("admpw.uid",NULL, NULL);
	newpw  = get_cgi_var("admpw.pw",NULL, NULL);
	if (newuid==NULL && newpw==NULL) {
		rpt_err(INCORRECT_USAGE, i18nMsg(DBT_NO_PARAM, "No parameters to set"), NULL, NULL);
	}

	logMsg("newuid=%s, newpw=%s\n", newuid ? newuid:"", newpw ? newpw : "");

	if ((f=fopen(filename, "r"))==NULL) {
		rpt_err(SYSTEM_ERROR, i18nMsg(DBT_OPEN_ADMPW,"Can not open admpw file"), NULL, NULL);
	}
	if (fgets(inbuf, sizeof(inbuf), f) == NULL) {
		fclose(f);
		rpt_err(SYSTEM_ERROR, i18nMsg(DBT_READ_ADMPW, "Can not read admpw file"), NULL, NULL);
	}
	logMsg("line=%s@\n", inbuf);
	fclose(f);

	col = strchr(inbuf, ':');
	if (col == NULL) {
		rpt_err(SYSTEM_ERROR, i18nMsg(DBT_ADMPW_CORRUPTED,"admpw file corrupted"), NULL, NULL);
	}
	
	uid = inbuf; *col=0; pw=col+1;
	logMsg("uid=%s, pw=%s\n", uid, pw);
	
	if (newuid == NULL) {
		newuid = uid;
	}

	if (newpw != NULL) {
        char *configdir = util_get_conf_dir();
        char *secdir = util_get_security_dir();
		int errorCode = ADMSSL_InitSimple(configdir, secdir, 1 /* force to use hashing */);
		if (errorCode) {
			rpt_err(APP_ERROR,
				i18nMsg(DBT_SECURITY_INIT, "Security Initialization Failed"),
				NULL, NULL);
		}

		update_admpwd(newuid, sha1_pw_enc(newpw), filename);
	}
	else {
		update_admpwd(newuid, pw, filename);
	}

	rpt_success(NULL);
}

/*
 * Modify admpw.
 */
static void update_admpwd(char *newuid, char *newpw, const char *filename) {

	FILE *f;
	int cnt;
	static char outbuf[BIG_LINE];

	f = fopen(filename, "w");
	if (f==NULL) {
		rpt_err(SYSTEM_ERROR, i18nMsg(DBT_OPEN_ADMPW_WR, "Can not open admpw file for writing"), NULL, NULL);
	}

	PR_snprintf(outbuf, sizeof(outbuf),"%s:%s", newuid, newpw);
	logMsg("output=%s\n", outbuf);

	cnt = fprintf(f,"%s", outbuf);

	if (cnt != (int)strlen(outbuf)) {
		rpt_err(SYSTEM_ERROR, i18nMsg(DBT_WR_ADMPW, "Failed to write to admpw"), NULL, NULL);
	}
	
	fclose(f);
}
