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

/*
 * Description (config.c)
 *
 *	This module contains routines used by administration CGI
 *	programs to set/get the attribute(s) of Admin server
 *
 */
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <prtypes.h>

#include <stdio.h>
#ifdef XP_UNIX
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <dirent.h>
#include <errno.h>
#endif
#include <string.h>

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

#include "libadmin/install.h"
#include "libadmin/libadmin.h"

#ifdef XP_WIN32
#define EADDRINUSE WSAEADDRINUSE
#endif

static char msgbuf[BUFSIZ]; /* ok - not threaded code */

/*
  properties file name = "config.properties"
 */
#define RESOURCE_FILE "config"

#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_USER_NAME 		resource_key(RESOURCE_FILE, "4")
#define DBT_NO_USER_DN 			resource_key(RESOURCE_FILE, "5") 
#define DBT_PSET_CREATE_ERROR 	resource_key(RESOURCE_FILE, "6") 
#define DBT_NO_OP 				resource_key(RESOURCE_FILE, "7") 
#define DBT_ILLEGAL_OP 			resource_key(RESOURCE_FILE, "8") 
#define DBT_NO_ATTRS 			resource_key(RESOURCE_FILE, "9") 
#define DBT_PSET_GET_ERROR 		resource_key(RESOURCE_FILE, "10") 
#define DBT_PSET_PARTIAL_GET 	resource_key(RESOURCE_FILE, "11") 
#define DBT_ATTR_NOT_EXIST 		resource_key(RESOURCE_FILE, "12") 
#define DBT_ATTR_NO_VALUE 		resource_key(RESOURCE_FILE, "13") 
#define DBT_PSET_SET_ERROR 		resource_key(RESOURCE_FILE, "14") 
#define DBT_PSET_ADD_ERROR 		resource_key(RESOURCE_FILE, "15") 
#define DBT_PSET_SET_NOT_EXIST 	resource_key(RESOURCE_FILE, "16") 
#define DBT_SSL_INIT_ERROR 		resource_key(RESOURCE_FILE, "17")
#define DBT_ATTR_INVALID_VALUE  	resource_key(RESOURCE_FILE, "18") 
#define DBT_PORT_INVALID        resource_key(RESOURCE_FILE, "19")
#define DBT_PORT_INUSE          resource_key(RESOURCE_FILE, "20")
#define DBT_PORT_EACCESS        resource_key(RESOURCE_FILE, "21")
#define DBT_PORT_NOT_AVAIL      resource_key(RESOURCE_FILE, "22")
#define DBT_ERROR_OPENING_LOG   resource_key(RESOURCE_FILE, "23")
#define DBT_LOG_INVALID         resource_key(RESOURCE_FILE, "24")

static char * read_conf(char *file, char *name);
static int update_conf(char *file, char *name, char *val);
static int validate_addr(char* ip);
static int validate_logfile(char *logdir, char* name);
#ifdef XP_UNIX
static int rename_pidlog_file(PsetHndl pset, char* newname);
static int change_uid_all(char *dir, int curuid, int newuid);
static int change_uid(char *fpath, int curuid, int newuid);
static int verify_server_uname(char *newuname);
static int change_server_uid(PsetHndl pset, char* newuname);
#endif

static int
update_port_addr(char *port, char *addr)
{
    char line[BIG_LINE];
    char *val = read_conf("console.conf", "Listen");
    char *ptr = NULL;
    int err;

    if (val) {
        ptr = strchr(val, ':');
    }

    if (ptr) {
        *ptr = '\0';
        ++ptr; /* ptr points to the start of the port number in host:port */
    } else if (val) {
        ptr = val; /* Listen only specifies the port number */
        val = "0.0.0.0"; /* default listen on all interfaces */
    } else {
        /* console.conf should always have Listen with the server port */
        ptr = "80"; /* default httpd port? */
        val = "0.0.0.0";
    }
    /*
      If ip address is not defined, it means that server should
      listen on all interfaces. This is dependant upon console
      to always include the port string first.
    */
    PR_snprintf(line, sizeof(line), "%s:%s",
                (addr && *addr) ? addr : val,
                (port && *port) ? port : ptr);
    err = update_conf("console.conf", "Listen", line); 
    PL_strfree(val);

    return err;
}

int main(int argc, char *argv[])
{
  int            _ai = ADMUTIL_Init();
  PsetHndl       pset;
  char           *method;
  int            rv, cnt, i, j, x, errorCode;
  int            err;
  int            *errp = &err;
  char           *username = 0;
  char           *localAdmin = 0;
  char           *binddn = 0;
  char           *bindpw = 0;
  char           **inputs = 0;
  char           *operation = 0;
  char           *qs = 0;
  char           *nameptr, *valptr;
  char           error_info[128];
  int            setFlag = 0, getFlag = 0, forceSetFlag = 0;
  int            ignorePsetErrors = 0;
  AttributeList  resultList, nvl;
  AttributeList  updateList = NULL;
  AttributeList  addList = NULL;
  AttrNameList   nl;
  AttributePtr   nv;
  char           *acceptLanguage = (char*)"en";
  Resource       *i18nResource = NULL;
  char     *configdir = util_get_conf_dir();
  char     *secdir = util_get_security_dir();
  char     *logdir = util_get_log_dir();
#if 0
  int            waitforever = 1;

  while (waitforever);
#endif

  (void)_ai; /* get rid of unused variable warning */
  i18nResource = res_find_and_init_resource(PROPERTYDIR, RESOURCE_FILE);

  memset((void *)errp, 0, sizeof(int));
  method = getenv("REQUEST_METHOD");

  /* Get new port number */
  if (!method || !*method) {
    /* run from command line - args are attr=value .... attr=value */
    /* fake out code below to set */
    operation = "set";
    inputs = &argv[1]; /* everything after argv[0] */
    ignorePsetErrors = 1; /* we don't care about pset errors during install/upgrade */
  }
  else if (!strcmp(method, "GET")) {
    /* UI CGI - either upgrade or normal admin CGI */
    qs = getenv("QUERY_STRING");
	
    if (!qs || !*qs) {
      if (i18nResource) 
	rpt_err(INCORRECT_USAGE,
		res_getstring(i18nResource, DBT_NO_QUERY_STRING,
			      acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		NULL, NULL);
      else rpt_err(INCORRECT_USAGE, "NO QUERY_STRING DATA", NULL, NULL);
    }
    else {
      get_begin(qs);
    }
  }
  else if (!strcmp(method, "POST")) {
    post_begin(stdin);
  }
  else {
    if (i18nResource) 
      PR_snprintf(error_info, sizeof(error_info), 
	      res_getstring(i18nResource, DBT_UNKNOWN_METHOD, acceptLanguage,
			    msgbuf, sizeof(msgbuf), NULL),
	      method);
    else PR_snprintf(error_info, sizeof(error_info), "Unknown Method (%s)", method);
    rpt_err(INCORRECT_USAGE, error_info, NULL, NULL);
  }

  /* Get UserDN and User Password  */

  rv = ADM_GetUserDNString(&err, &binddn);
  if (rv < 0 || !binddn || !*binddn) {
    rv = ADM_GetCurrentUsername(&err, &username);
    if (rv < 0 || !username || !*username) {
      if (i18nResource) 
	rpt_err(ELEM_MISSING,
		res_getstring(i18nResource, DBT_NO_USER_NAME,
			      acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		NULL, NULL);
      else rpt_err(ELEM_MISSING, "NO USER NAME", NULL, NULL);
    }
    else {
      /* No DN, maybe it is local super */
      localAdmin = admGetLocalAdmin(NULL, &rv);
      if (localAdmin) {
	if (strcmp(username, localAdmin)) {
	  if (i18nResource) 
	    rpt_err(ELEM_MISSING,
		    res_getstring(i18nResource, DBT_NO_USER_DN,
				  acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		    NULL, NULL);
	  else rpt_err(ELEM_MISSING, "NO USER DN!", NULL, NULL);
	}
	else {
	  binddn = NULL;
	  bindpw = NULL;
	}
      }
      else {
	if (i18nResource) 
	  rpt_err(ELEM_MISSING,
		  res_getstring(i18nResource, DBT_NO_USER_DN,
				acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		  NULL, NULL);
	else rpt_err(ELEM_MISSING, "NO USER DN!", NULL, NULL);
      }
    }
  }

  if (binddn) rv = ADM_GetCurrentPassword(&err, &bindpw);

  errorCode = ADMSSL_InitSimple(configdir, secdir, 1);
  if (errorCode) {
    if (i18nResource) 
      rpt_err(APP_ERROR,
	      res_getstring(i18nResource, 
			    DBT_SSL_INIT_ERROR,
			    acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
	      NULL, NULL);
    else rpt_err(APP_ERROR, "SSL related initialization failed", NULL, NULL);
  }

  /* Initialize the pset  */
  pset = psetCreateSSL("admin-serv", 
		       /* configRoot */ configdir,
		       /* userDN */ binddn, 
		       /* passwd */ bindpw,
		       /* errorcode */ &rv);

  if (!pset && !ignorePsetErrors) {
    PR_snprintf(error_info, sizeof(error_info),
	    "PSET_ERROR_NUMBER: %d\nPSET_ERROR_INFO: %s", 
	    rv,
	    psetErrorString(rv, NULL, msgbuf, sizeof(msgbuf), NULL));
    if (i18nResource) 
      rpt_err(APP_ERROR,
	      res_getstring(i18nResource, DBT_PSET_CREATE_ERROR, 
			    acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
	      NULL, error_info);
    else rpt_err(APP_ERROR, "PSET Creation Failed", NULL, error_info);
  }

  /* Process the input  */

  if (!inputs) {
    inputs = get_input_ptr();
  }

  if (!operation) {
    operation = get_cgi_var("op", NULL, NULL);
  }

  if (!operation) {
    psetDelete(pset);
    if (i18nResource) 
      rpt_err(INCORRECT_USAGE,
	      res_getstring(i18nResource, DBT_NO_OP, acceptLanguage,
			    msgbuf, sizeof(msgbuf), NULL),
	      NULL, NULL);
    else rpt_err(INCORRECT_USAGE, "No operation defined", NULL, NULL);
  }

  if (!strcmp(operation, "get")) getFlag = 1;
  else if (!strcmp(operation, "set")) setFlag = 1;
  else if (!strcmp(operation, "force_set")) { 
    setFlag = 1; 
    forceSetFlag = 1; 
  }
  else {
    if (i18nResource) 
      PR_snprintf(error_info, sizeof(error_info),
	      res_getstring(i18nResource, DBT_ILLEGAL_OP, acceptLanguage,
			    msgbuf, sizeof(msgbuf), NULL),
	      operation);
    else PR_snprintf(error_info, sizeof(error_info), "Illegal operation defined (%s)", operation);
    rpt_err(INCORRECT_USAGE, error_info, NULL, NULL);
  }

  cnt = 0;
  x = 0;
  while (inputs[x] && *(inputs[x++]) != '\0') cnt++;

  if (cnt <= 1) {
    psetDelete(pset);
    if (i18nResource) 
      rpt_err(INCORRECT_USAGE, 
	      res_getstring(i18nResource, DBT_NO_ATTRS, acceptLanguage,
			    msgbuf, sizeof(msgbuf), NULL),
	      NULL, NULL);
    else rpt_err(INCORRECT_USAGE, "No attribute specified ", NULL, NULL);
  }

  if (getFlag) { /* get operation */
    nl = createAttrNameList(cnt);
    x = 0; i = 0;
    while (inputs[x] && *(inputs[x]) != '\0') {
      char *begin, *end;
      begin = inputs[x];
      end = strchr(inputs[x++], '=');
      if (end) {
          char *name = PL_strndup(begin, end-begin);
      /* Ignore "op" */
          if (strncasecmp(name, "op", 2)) addName(nl, i++, name);
          PL_strfree(name);
      }
    }

    resultList = psetGetAttrList(pset, nl, &errorCode);

    if (errorCode && 
	errorCode != PSET_PARTIAL_OP && 
	errorCode != PSET_PARTIAL_GET ) {
      psetDelete(pset);
      PR_snprintf(error_info, sizeof(error_info),
	      "PSET_ERROR_NUMBER: %d\nPSET_ERROR_INFO: %s", 
	      errorCode,
	      psetErrorString(errorCode, NULL, msgbuf, sizeof(msgbuf), NULL));
      if (i18nResource) 
	rpt_err(APP_ERROR,
		res_getstring(i18nResource, DBT_PSET_GET_ERROR, 
			      acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		NULL, error_info);
      else rpt_err(APP_ERROR, "PSET Get Failed", NULL, error_info);
    }

    deleteAttrNameList(nl);
    if (errorCode) {
      if (resultList) {
	if (i18nResource) 
	  rpt_warning(WARNING, 
		      res_getstring(i18nResource, DBT_PSET_PARTIAL_GET,
				    acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		      NULL, NULL);
	else rpt_warning(WARNING, "Partial Get\n", NULL, NULL);
      }
      else {
	psetDelete(pset);
	pset = NULL;
	if (i18nResource) 
	  rpt_warning(APP_ERROR,
		      res_getstring(i18nResource, DBT_ATTR_NOT_EXIST,
				    acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		      NULL, NULL);
	else rpt_err(APP_ERROR, "Attribute(s) does not exist", NULL, NULL);
      }
    }
    else rpt_success(NULL);
    
    if (resultList) {
      nvl = resultList;
      while ((nv = *nvl++)) {
	fprintf(stdout, "%s: %s\n", 
		nv->attrName, 
		nv->attrVal ? (nv->attrVal[0] ? nv->attrVal[0] : "") : "");
      }
      deleteAttributeList(resultList);
    }
#if 0
    if (execPath) fprintf(stdout, "execPath: %s\n", execPath);
    else  fprintf(stdout, "execPath: EMPTY\n");
    if (resPath[0] != '\0') fprintf(stdout, "resPath: %s\n", resPath);
    else  fprintf(stdout, "resPath: EMPTY\n");
    if (i18nResource) fprintf(stdout, "info1: Property file OK!");
    else fprintf(stdout, "info1: Can not find Property file!");
#endif
    psetDelete(pset);
    exit(0);
  }
  else if (setFlag) {  /* Set/Force_Set operation */
    updateList = createAttributeList(cnt);
    if (forceSetFlag) addList = createAttributeList(cnt);
    x = 0; i = 0; j = 0;
    while (inputs[x]) {
      valptr = strchr(inputs[x], '=');
      if (valptr) {
	*valptr = '\0';
	valptr++;
	while(*valptr != '\0' && *valptr == ' ') valptr++;
	if (*valptr == '\0') {

	  /* some values are allowed to be empty string*/	  
	  if ((strcasecmp(inputs[x], "configuration.nsAdminAccessHosts") != 0) &&
	      (strcasecmp(inputs[x], "configuration.nsAdminAccessAddresses") != 0) &&
	      (strcasecmp(inputs[x], "configuration.nsServerAddress") != 0) &&
	      (strcasecmp(inputs[x], "configuration.nsAdminEnableDSGW") != 0) &&
	      (strcasecmp(inputs[x], "configuration.nsAdminEnableEnduser") != 0)) {	
	    psetDelete(pset);
	    if (i18nResource) 
	      PR_snprintf(error_info, sizeof(error_info), 
		      res_getstring(i18nResource, DBT_ATTR_NO_VALUE,
				    acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		      inputs[x]);
	    else PR_snprintf(error_info, sizeof(error_info), "Attribute[%s] does not have value", 
			 inputs[x]);
	    rpt_err(INCORRECT_USAGE, error_info, NULL, NULL);
	  }
	}
      }
      else {
	psetDelete(pset);
	if (i18nResource) 
	  PR_snprintf(error_info, sizeof(error_info), 
		  res_getstring(i18nResource, DBT_ATTR_NO_VALUE,
				acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		  inputs[x]);
	else PR_snprintf(error_info, sizeof(error_info), "Attribute[%s] does not have value", 
		     inputs[x]);
	rpt_err(INCORRECT_USAGE, error_info, NULL, NULL);
      }
      nameptr = inputs[x];
      /* Ignore the OP  */
      if (strncasecmp(nameptr, "op", 2)) {

	/* Check if port is being changed */
	if (strcasecmp(nameptr,"configuration.nsServerPort") == 0) {
	  int port;
	  
	  port = atoi(valptr);
	  
	  err = 0;
	  if (port < 1 || port > 65535)
	    {
	      err = -1;
	    }
	   
	  if (try_bind(NULL, port) == -1)
	    {
	      if(errno == EADDRINUSE)
		err = -2;
	      else if(errno == EACCES) 
		err = -3;
	      else 
		err = -4;
	    }
	   
	  if(err == 0) {
	    err = update_port_addr(valptr, NULL); /* update port only */
	  }

	  if (err < 0)
	    {
              psetDelete(pset);
              if (i18nResource)
		{
		  if (err == -1)
                    PR_snprintf(error_info, sizeof(error_info), res_getstring(i18nResource, DBT_PORT_INVALID, acceptLanguage,
									      msgbuf, sizeof(msgbuf), NULL));
		  else if (err == -2)
                    PR_snprintf(error_info, sizeof(error_info), res_getstring(i18nResource, DBT_PORT_INUSE, acceptLanguage,
									      msgbuf, sizeof(msgbuf), NULL));
		  else if (err == -3)
                    PR_snprintf(error_info, sizeof(error_info), res_getstring(i18nResource, DBT_PORT_EACCESS, acceptLanguage,
									      msgbuf, sizeof(msgbuf), NULL));
		  else
                    PR_snprintf(error_info, sizeof(error_info), res_getstring(i18nResource, DBT_PORT_NOT_AVAIL, acceptLanguage,
									      msgbuf, sizeof(msgbuf), NULL));
		}
              else
		{
		  PR_snprintf(error_info, sizeof(error_info), "Port %s is either in use or invalid", valptr);
		}
              rpt_err(APP_ERROR, error_info, NULL, NULL);
	    }
	}

	/* Check if the IP address is valid */
	if (strcasecmp(nameptr,"configuration.nsserveraddress") == 0) {
	  if (!validate_addr(valptr)) {
	    /* report error */
	    psetDelete(pset);
	    if (i18nResource) 
	      PR_snprintf(error_info, sizeof(error_info), 
		      res_getstring(i18nResource, DBT_ATTR_INVALID_VALUE,
				    acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		      nameptr);
	    else PR_snprintf(error_info, sizeof(error_info), "Attribute[%s] has invalid value", 
			 nameptr);
	    rpt_err(APP_ERROR, error_info, NULL, NULL);
	  }
	  else {
	    err = update_port_addr(NULL, valptr); /* update address only */
	  }
	}
	
	/* Check if access log file is being changed */
	if (strcasecmp(nameptr,"configuration.nsaccesslog") == 0) {
	  int rc = validate_logfile(logdir, valptr);
	  if (rc == 1) {
	    /* report error */
	    psetDelete(pset);
	    if (i18nResource) 
	      PR_snprintf(error_info, sizeof(error_info), 
		      res_getstring(i18nResource, DBT_LOG_INVALID,
				    acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		      "Access", nameptr);
	    else PR_snprintf(error_info, sizeof(error_info), "%s log file name must be a valid file name and must not include the path.  [%s] is invalid.",
                         "Access", nameptr);
	    rpt_err(APP_ERROR, error_info, NULL, NULL);
	  } else if (rc == 2) {
	    /* report error */
	    psetDelete(pset);
	    if (i18nResource) 
	      PR_snprintf(error_info, sizeof(error_info), 
		      res_getstring(i18nResource, DBT_ERROR_OPENING_LOG,
				    acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		      "access", nameptr, strerror(errno));
	    else PR_snprintf(error_info, sizeof(error_info), "Could not open %s log file [%s].  Error: %s",
                         "access", nameptr, strerror(errno));
	    rpt_err(APP_ERROR, error_info, NULL, NULL);
	  }
	  else {
	    char *pathline = PR_smprintf("%s %s", valptr, "common");
	    err = update_conf("console.conf", "CustomLog", pathline);
	    PR_smprintf_free(pathline);
	  }
	}

	/* Check if error log file is being changed */
	if (strcasecmp(nameptr,"configuration.nserrorlog") == 0) {
	  int rc = validate_logfile(logdir, valptr);
	  if (rc == 1) {
	    /* report error */
	    psetDelete(pset);
	    if (i18nResource) 
	      PR_snprintf(error_info, sizeof(error_info), 
		      res_getstring(i18nResource, DBT_LOG_INVALID,
				    acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		      "Error", nameptr);
	    else PR_snprintf(error_info, sizeof(error_info), "%s log file name must be a valid file name and must not include the path.  [%s] is invalid.",
                         "Error", nameptr);
	    rpt_err(APP_ERROR, error_info, NULL, NULL);
	  } else if (rc == 2) {
	    /* report error */
	    psetDelete(pset);
	    if (i18nResource) 
	      PR_snprintf(error_info, sizeof(error_info), 
		      res_getstring(i18nResource, DBT_ERROR_OPENING_LOG,
				    acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		      "error", nameptr, strerror(errno));
	    else PR_snprintf(error_info, sizeof(error_info), "Could not open %s log file [%s].  Error: %s",
                         "error", nameptr, strerror(errno));
	    rpt_err(APP_ERROR, error_info, NULL, NULL);
	  }
	  else {
	    err = update_conf("console.conf", "ErrorLog", valptr);
	  }
	}

#ifdef XP_UNIX
	/* Check if pidlog  is being changed */
	if (strcasecmp(nameptr,"configuration.nsPidLog") == 0) {

	  if (!rename_pidlog_file(pset, valptr)) {
	    rpt_err(APP_ERROR, "Failed to rename log file", NULL, NULL);
	  }
	}

	/* Check if server uid is being changed */
	if (strcasecmp(nameptr,"configuration.nsSuiteSpotUser") == 0) {

	  if (change_server_uid(pset, valptr) < 0) {
	    rpt_err(APP_ERROR, "Failed to change server uid", NULL, NULL);
	  }
	}
#endif

	errorCode = PSET_OP_OK;
	(void)psetGetAttrSingleValue(pset, nameptr, &errorCode);
	if (errorCode && !ignorePsetErrors) {
	  if (forceSetFlag) 
	    addSingleValueAttribute(addList, j++, nameptr, valptr);
	  else j++;
	}
	else {
	  addSingleValueAttribute(updateList, i++, nameptr, valptr);
	}
      }
      x++;
    }
    errorCode = PSET_OP_OK;
    if (i) errorCode = psetSetAttrList(pset, updateList);
    if (errorCode && !ignorePsetErrors) {
      psetDelete(pset);
      PR_snprintf(error_info, sizeof(error_info),
	      "PSET_ERROR_NUMBER: %d\nPSET_ERROR_INFO: %s\n", 
	      errorCode,
	      psetErrorString(errorCode, NULL, msgbuf, sizeof(msgbuf), NULL));
      if (i18nResource) 
	rpt_err(APP_ERROR,
		res_getstring(i18nResource, DBT_PSET_SET_ERROR, 
			      acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		NULL, error_info);
      else rpt_err(APP_ERROR, "PSET Set Failed", NULL, error_info);
    }

    if (forceSetFlag) {
      if (j) {
	for (i=0; i < j; i++) {
	  errorCode = psetAddAttribute(pset, 
				       addList[i]->attrName, 
				       addList[i]->attrVal);
	  if (errorCode && !ignorePsetErrors) {
	    psetDelete(pset);
	    pset = NULL;
	    PR_snprintf(error_info, sizeof(error_info),
		    "PSET_ERROR_NUMBER: %d\nPSET_ERROR_INFO: %s\n", 
		    errorCode,
		    psetErrorString(errorCode, NULL, msgbuf, sizeof(msgbuf), NULL));
	    if (i18nResource) 
	      rpt_err(APP_ERROR,
		      res_getstring(i18nResource, DBT_PSET_ADD_ERROR, 
				    acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		      NULL, error_info);
	    else rpt_err(APP_ERROR, "PSET Add Failed", NULL, error_info);
	  }
	}
      }
    }
    else {
      if (j) {
	psetDelete(pset);
	pset = NULL;
	if (i18nResource) 
	  rpt_err(APP_ERROR,
		  res_getstring(i18nResource, DBT_PSET_SET_NOT_EXIST, 
				acceptLanguage, msgbuf, sizeof(msgbuf), NULL),
		  NULL, NULL);
	else rpt_err(APP_ERROR,
		     "PSET SET OP Failed for setting non-existing attribute",
		     NULL, NULL);
      }
    }
    
    rpt_success(NULL);
    psetDelete(pset);
    if (i18nResource != NULL) {
      res_destroy_resource(i18nResource);
    }
    exit (0);
  }
  if (updateList) deleteAttributeList(updateList);
  if (addList) deleteAttributeList(addList);  
  psetDelete(pset);

  return 1;
}


/*
 * Get Val from the specified conf file
 * The returned value is allocated - caller must free
 */
static char * read_conf(char *file, char *name) {
  FILE *f;
  static char filename[BIG_LINE];
  static char inbuf[BIG_LINE];
  char * retval = NULL;
  char *configdir = util_get_conf_dir();

  util_find_file_in_paths(filename, sizeof(filename), file, configdir, "", "admin-serv/config");

  f = fopen(filename, "r");
  if (f==NULL) {
    char msg[BIG_LINE];
    PR_snprintf(msg, BIG_LINE, "Cannot open file %s for reading", filename);
    rpt_err(SYSTEM_ERROR, msg, NULL, NULL);
  }

  while(fgets(inbuf, sizeof(inbuf), f) != NULL) {
	if (strncasecmp(inbuf,name,strlen(name)) == 0) { /* Line starts with name */
      char *p = strtok(inbuf, " ");
      p = strtok(NULL, " ");
      retval = PL_strdup(p);
    }
  }	
  fclose(f);

  return retval;
}

#define CONF_LINES 16*1024
/*
 * Modify any attribute in a configuration file with a name/value pair
 * If the attribute value is NULL, remove it from the file completely.
 */
static int update_conf(char *file, char *name, char *val) {

  FILE *f;
  int i, modified=0;
  static char filename[BIG_LINE];
  static char inbuf[BIG_LINE];
  static char buf[BIG_LINE];
  int linecnt=0;	
  char *lines[CONF_LINES];
  char *configdir = util_get_conf_dir();
  int rc = 0;

  util_find_file_in_paths(filename, sizeof(filename), file, configdir, "", "admin-serv/config");

  f = fopen(filename, "r");
  if (f==NULL) {
    char msg[BIG_LINE];
    PR_snprintf(msg, BIG_LINE, "Cannot open file %s for reading", filename);
    rpt_err(SYSTEM_ERROR, msg, NULL, NULL);
  }

  while(fgets(inbuf, sizeof(inbuf), f) != NULL) {
    if (linecnt >= CONF_LINES) {
       rc = 1; /* our buffer ran out, give up */
       goto bail;
    }

    if (strncasecmp(inbuf,name,strlen(name)) == 0) { /* Line starts with the attribute name */
      if(val && *val != '\0') {
	PR_snprintf(buf, sizeof(buf), "%s %s\n", name, val);
	lines[linecnt++] = strdup(buf);
	modified=1;
      }
      else {
	modified=1;
      }
    }
    else {
      lines[linecnt++] = strdup(inbuf);
    }
  }	
  fclose(f);

  if (!modified && (val && *val != '\0')) { /* Add the attribute name/val pair*/
    PR_snprintf(buf, sizeof(buf), "%s %s\n", name, val);
    lines[linecnt++] = strdup(buf);
  }

  f = fopen(filename, "w");
  if (f==NULL) {
    char msg[BIG_LINE];
    PR_snprintf(msg, sizeof(msg), "Cannot open file %s for writing", filename);
    rpt_err(SYSTEM_ERROR, msg, NULL, NULL);
  }

  for (i=0; i < linecnt; i++) {
    fprintf(f, "%s", lines[i]);
  }

bail:
  fclose(f);
  return rc;
}


/*
 * Rename logs/pid file when configuration.nsPidLog is changed
*/
#ifdef XP_UNIX
static int  rename_pidlog_file(PsetHndl pset, char* newname) {
  char oldpath[BIG_LINE];
  char newpath[BIG_LINE];
  int errorCode;

  if (!newname || !*newname ||
      !util_is_valid_path_string(newname)) {
    return 0;
  }

  char *oldname = psetGetAttrSingleValue(pset, 
					 "configuration.nsPidLog", 
					 &errorCode);
  if (oldname != NULL && strcmp(oldname, newname) != 0) {
    char *piddir = util_get_pid_dir();
    PR_snprintf(oldpath, sizeof(oldpath), "%s/%s", piddir, oldname);
    PR_snprintf(newpath, sizeof(newpath), "%s/%s", piddir, newname);
    if(rename (oldpath, newpath) != 0)
      return 0;
    return !update_conf("console.conf", "PidFile", newname);
  }
  else return 1;
}
#endif


/*
 * Change admin server uiuserd
*/
#ifdef XP_UNIX
static int  change_server_uid(PsetHndl pset, char* newuname) {
  int errorCode;
  int newuid;
  char *configdir = util_get_conf_dir();
  char *secdir = util_get_security_dir();
  char *logdir = util_get_log_dir();
  char *olduname = psetGetAttrSingleValue(pset, 
					  "configuration.nsSuiteSpotUser", 
					  &errorCode);

  char *pidfile = psetGetAttrSingleValue(pset, 
					 "configuration.nsPidLog", 
					 &errorCode);

  newuid = verify_server_uname(newuname);

  if (newuid < 0) {
      return -1;
  } else if (update_conf("console.conf", "User", newuname)) {
      return -1;
  } else if ((olduname != NULL) && (strcmp(olduname, newuname) != 0)) {

    /* Can change uid only id running as a root */
    if (getuid() != 0) {
      rpt_err(SYSTEM_ERROR, "Can not change Server UID, "
	      "Admin Server instance in not running under ROOT UID",
	      NULL,NULL);
    }


    if (change_uid_all(configdir, 0, newuid) || change_uid_all(secdir, 0, newuid) ||
        change_uid_all(logdir, 0, newuid) || (pidfile && change_uid(pidfile, 0, newuid))) {
        return -1;
    }
  }

  else return 0;

  return -1;
}
#endif

/*
 * Verify if newuname can be used as the admin server uid. The new uid must
 * belong to the sysgroup (recorded in adm.conf) 
 *
 * Returns error : -1 
 *         ok    :  uid for the newuname
 */
#ifdef XP_UNIX
static int verify_server_uname(char *newuname) {
  AdmldapInfo admInfo = NULL;
  char *grpname;
  struct group  *grp;
  struct passwd *pwd;
  int errorcode = 0;
  int i = 0;
  int grmem_found = 0;
  char buf[BUFSIZ];
  char *configdir = util_get_conf_dir();

  admInfo = admldapBuildInfoOnly(configdir, &errorcode);
  if (!admInfo || errorcode) {
    rpt_err(SYSTEM_ERROR, "Can not open adm.conf for reading", NULL, NULL);
    return -1;
  }

  grpname = admldapGetSysGroup(admInfo); /* makes a copy */
  destroyAdmldap(admInfo);

  if (!grpname) {
    rpt_err(SYSTEM_ERROR, "sysgroup not found in adm.conf", NULL, NULL);
    return -1;
  }

  grp = getgrnam(grpname);
  if (grp == NULL) {
    PR_snprintf(buf, sizeof(buf), "sysgroup '%s' does not exist", grpname);
    rpt_err(SYSTEM_ERROR, buf, NULL, NULL);
	PL_strfree(grpname);
    return -1;
  }

  /*
   * newuname must belong to the admin server group
   */
  pwd = getpwnam(newuname);
  if (pwd == NULL) {
    PR_snprintf(buf, sizeof(buf), "Can not change Server UID, "
	    "User '%s' does not exist", newuname);
    rpt_err(INCORRECT_USAGE, buf, NULL, NULL);
	PL_strfree(grpname);
    return -1;
  }

  /* Check all grp members */
  while (grp->gr_mem[i] && !grmem_found) {
    if (strcmp(grp->gr_mem[i], newuname) == 0) {
      grmem_found = 1;
    }
    else i++;	
  }

  if (!grmem_found && pwd->pw_gid != grp->gr_gid) {
    PR_snprintf(buf, sizeof(buf), "Can not change Server UID, "
	    "User '%s' does not belong to the group '%s'", newuname, grpname);
    rpt_err(INCORRECT_USAGE, buf, NULL, NULL);
	PL_strfree(grpname);
    return -1;
  }

  PL_strfree(grpname);
  return pwd->pw_uid;
}
#endif

/*
 * Change recursivly file ownership to newuid for all files and subdirecrories
 * for wich the current owner is olduid
 */
#ifdef XP_UNIX
static int change_uid_all(char *dir, int olduid, int newuid) {
  DIR *dirp; 
  struct dirent *dp;
  char fpath[BIG_LINE];
  char buf[BIG_LINE];
  struct stat filestat;
	

	
  dirp = opendir(dir);

  if (dirp == NULL) {
    PR_snprintf(buf, sizeof(buf), "Change Server UID: opendir(%s) failed, errno=%d\n", dir, errno);
    rpt_err(SYSTEM_ERROR, buf, NULL, NULL);
    return -1;
  }

  if (change_uid(dir, olduid, newuid) != 0) {
    closedir(dirp);
    return -1;
  }

  for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp)) {
    if (strcmp(dp->d_name,".") == 0 ||
	strcmp(dp->d_name, "..") == 0) {
      continue;
    }

    PR_snprintf(fpath, sizeof(fpath), "%s/%s", dir, dp->d_name);

    if (stat(fpath, &filestat) != 0) {
      closedir(dirp);
      PR_snprintf(buf, sizeof(buf), "Change Server UID: stat(%s) failed, errno=%d\n", fpath, errno);
      rpt_err(SYSTEM_ERROR, buf, NULL, NULL);
      return -1;
    }


    if (S_ISDIR(filestat.st_mode)) {
      if (change_uid_all(fpath, olduid, newuid) != 0) {
	closedir(dirp);
	return -1;
      }
    }
    else if (change_uid(fpath, olduid, newuid) != 0) {
      closedir(dirp);
      return -1;
    }
  }

  closedir(dirp);
  return 0;
}
#endif

#ifdef XP_UNIX
/*
 * Change file ownership for a files
 */
static int change_uid(char *fpath, int olduid, int newuid) {
  struct stat filestat;
  char buf[BIG_LINE];
  int fd = -1;

  if (0 > (fd = open(fpath, O_RDONLY))) {
    PR_snprintf(buf, sizeof(buf), "Change Server UID: open(%s) failed, errno=%d\n", fpath, errno);
    rpt_err(SYSTEM_ERROR, buf, NULL, NULL);
    return -1;
  }
	
  if (fstat(fd, &filestat) != 0) {
    close(fd);
    PR_snprintf(buf, sizeof(buf), "Change Server UID: stat(%s) failed, errno=%d\n", fpath, errno);
    rpt_err(SYSTEM_ERROR, buf, NULL, NULL);
    return -1;
  }

  if (filestat.st_uid == olduid) {
    int rc = fchown(fd, newuid, -1);
    if (rc != 0) {
      close(fd);
      PR_snprintf(buf, sizeof(buf), "Change Server UID: Can not change file owner for %s, errno=%d\n", fpath, errno);
      rpt_err(SYSTEM_ERROR, buf, NULL, NULL);
      return -1;
    }
  }
  close(fd);
  return 0;
}
#endif

static int validate_addr(char* ip) {
  PRNetAddr  netaddr;
  PRFileDesc *sock = NULL;
  int ret = 0;

  /* If ip address is not define, it means that server should listen on all interfaces */
  if (ip==NULL || *ip=='\0') return 1;

  /* If ip address is equal to localhost (127.0.0.1), no need to validate the address */
  if (!strcmp(ip, "127.0.0.1")) return 1;
  if (!strcmp(ip, "0.0.0.0")) return 1;

  if (PR_StringToNetAddr(ip, &netaddr) == PR_SUCCESS) {
    if ((sock = PR_NewTCPSocket()) != NULL) {
      if (PR_Bind(sock, &netaddr) == PR_SUCCESS) {
        ret = 1;
      }
      PR_Close(sock);
    }
  }

  return ret;
}

/*
 * Try to open a log file in append mode. If the file is not present, it will be created
 * Returns :  1 ok
 *            0 error
 *           
 */
static int validate_logfile(char *logdir, char *name)  {
  FILE *f;
  char *filename;
  int n;

  if (!name || !*name) {
    return 1; /* invalid path */
  }

  n = strlen(logdir);

  if (strncmp(logdir, name, n)) {
    return 1; /* invalid path */
  }

  filename = &name[n+1];

  if (!util_is_valid_path_string(filename)) {
    return 1; /* invalid path */
  }

  f = fopen(name, "a+");
  if (!f) {
    return 2; /* can't open file */
  }

  fclose(f);
  return 0; /* ok */
}
