/* This module is derived from the original libiiimp.
   And the file name is role-data-client.c.
   In order to inspect client environment, currently use
   this file also in libiiimcf.  But it will be at least
   partly rewritten mainly for procuring portability. */


#include <stdlib.h>
#include <ctype.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <pwd.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <errno.h>
#include <dirent.h>
#include <sys/utsname.h>
#if !defined(OS_ARCH) && defined(HAVE_SYSINFO) && defined(HAVE_SYS_SYSTEMINFO_H)
#include <sys/systeminfo.h>
#endif

#include "iiimcfint.h"

#define NODE_KEY	  "node="
#define NODE_KEY_LEN	  (5)
#define SERVICE_KEY	  "service="
#define SERVICE_KEY_LEN	  (8)
#define CERT_FILE         "tls_cert="
#define CERT_FILE_LEN     (9)
#define CERT_KEY_FILE     "tls_key="
#define CERT_KEY_FILE_LEN (8)
#define CERT_CA_FILE      "tls_cafile="
#define CERT_CA_FILE_LEN  (11)
#define CERT_CA_PATH      "tls_capath="
#define CERT_CA_PATH_LEN  (11)
#define USE_TLS           "use_tls="
#define USE_TLS_LEN       (8)

#define SERVER_FILE		".iiim/server"
#define SERVER_FILE_LEN		(12)

#define SERVER_COMPAT_FILE	".iiimp"
#define SERVER_COMPAT_FILE_LEN	(6)
#define SERVER_COMPAT_KEY	"iiimp.server=iiimp://"
#define SERVER_COMPAT_KEY_LEN	(21)

#define CONFIG_DIR_BASE		".iiim"
#define CONFIG_DIR_AUTH		".iiim/auth"
#define CONFIG_FILE_PASSWD	".iiim/auth/passwd"
#define PASSWORD_FILE_LEN	(17)
#define AUTH_PASSWORD_LEN	(32)
#define HOME_ENV		"HOME"
#define IIIM_SERVER_ENV		"IIIM_SERVER"

#define CLIENT_TYPE		"generic IIIMP client"


#define POSITION_HEAD(p, n) \
	for (; 0 < n; --(n), (p)++) { \
		if ((' ' != *(p)) && ('\t' != *(p)) && \
		    ('\r' != *(p)) && ('\n' != *(p))) { \
			break; \
		} \
	} \
	if ((0 < (n)) && ((',' == *(p)) || (';' == *(p)))) { \
		--(n); \
		(p)++; \
	} \
	for (; 0 < n; --(n), (p)++) { \
		if ((' ' != *(p)) && ('\t' != *(p)) && \
		    ('\r' != *(p)) && ('\n' != *(p))) { \
			break; \
		} \
	}

#define POSITION_TAIL(p, n) \
	for (; 0 < n; --(n), (p)++) { \
		if ((',' == *(p)) || (';' == *(p)) || \
		    (' ' == *(p)) || ('\t' == *(p)) || \
		    ('\r' == *(p)) || ('\n' == *(p))) { \
			break; \
		} \
	}

#define POSITION_TAIL_COMPAT(p, n) \
	for (; 0 < n; --(n), (p)++) { \
		if ((':' == *(p)) || ('\r' == *(p)) || ('\n' == *(p))) { \
			break; \
		} \
	}


static IIIMF_status
get_param(
    const char **	buf,
    size_t *		nbyte,
    const char *	key,
    size_t		key_len,
    char **		param_ret
)
{
    const char *	b;
    const char *	p;
    size_t		n;
    size_t		m;
    size_t		len;
    char *		param;

    b = *buf;
    n = *nbyte;

    if (n < key_len) return IIIMF_STATUS_CONFIG;

    if (0 != strncasecmp(b, key, key_len)) {
	return IIIMF_STATUS_CONFIG;
    }

    b += key_len;
    n -= key_len;
    p = b;
    m = n;

    POSITION_TAIL(b, n);

    len = (m - n);

    param = (char *)malloc(len + 1);
    if (NULL == param) return IIIMF_STATUS_MALLOC;

    (void)memcpy(param, p, len);
    *(param + len) = '\0';

    *buf = b;
    *nbyte = n;
    *param_ret = param;

    return IIIMF_STATUS_SUCCESS;
}


static IIIMF_status
get_param_compat(
    const char **	buf,
    size_t *		nbyte,
    const char *	key,
    size_t		key_len,
    char **		param_ret
)
{
    const char *	b;
    const char *	p;
    size_t		n;
    size_t		m;
    size_t		len;
    char *		param;

    b = *buf;
    n = *nbyte;

    if (n < key_len) return IIIMF_STATUS_CONFIG;

    if (0 != strncasecmp(b, key, key_len)) {
	return IIIMF_STATUS_CONFIG;
    }

    b += key_len;
    n -= key_len;
    p = b;
    m = n;

    POSITION_TAIL_COMPAT(b, n);

    len = (m - n);

    param = (char *)malloc(len + 1);
    if (NULL == param) return IIIMF_STATUS_MALLOC;

    (void)memcpy(param, p, len);
    *(param + len) = '\0';

    *buf = b;
    *nbyte = n;
    *param_ret = param;

    return IIIMF_STATUS_SUCCESS;
}

static void
iiimcf_client_config_free(
    IIIMCF_client_conf *pconf
)
{
    IIIMCF_client_conf *pc;
    IIIMCF_client_conf *pc2;

    if (!pconf) return;
    for (pc = pconf; pc; ) {
        pc2 = pc->next;
        if(pc->server_node) free(pc->server_node);
        if(pc->service) free(pc->service);
        if(pc->cert_file) free(pc->cert_file);
        if(pc->cert_key) free(pc->cert_key);
        if(pc->ca_file) free(pc->ca_file);
        if(pc->ca_path) free(pc->ca_path);
        free(pc);
        pc = pc2;
    }
}

static IIIMCF_client_conf *
iiimcf_client_config_new(
    char *node,
    char *service
)
{
    IIIMCF_client_conf *ret;
    ret = malloc(sizeof(*ret));
    if (!ret) return NULL;
    memset(ret, 0, sizeof(*ret));

    ret->server_node = node;
    ret->service = service;
    return ret;
}

static IIIMF_status
iiimcf_client_file_server(
    IIIMCF_client_env *penv
)
{
    int			fd;
    IIIMF_status	status;
    char *		server_file;
    size_t		server_file_len;
    size_t		home_dir_len;
    struct stat		st;
    int			stat_ret;
    char *		pa;
    const char *	p;
    size_t		rest;
    IIIMCF_client_conf  *pconf = NULL;
    char *		node = NULL;

    if (!penv->home_dir) return IIIMF_STATUS_CONFIG;

    home_dir_len = strlen(penv->home_dir);
    server_file_len = (home_dir_len + 1 + SERVER_FILE_LEN);

    server_file = (char *)malloc(server_file_len + 1);
    if (NULL == server_file) {
	return IIIMF_STATUS_MALLOC;
    }

    (void)strcpy(server_file, penv->home_dir);
    *(server_file + home_dir_len) = '/';
    (void)strcpy(server_file + home_dir_len + 1, SERVER_FILE);

    fd = open(server_file, O_RDONLY, 0);
    free(server_file);
    if (fd < 0) return IIIMF_STATUS_CONFIG;

    stat_ret = fstat(fd, &st);
    if (stat_ret < 0) {
	return IIIMF_STATUS_CONFIG;
    }

    pa = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
    (void)close(fd);
    if (NULL == pa) return IIIMF_STATUS_CONFIG;

    p = pa;
    rest = st.st_size;

    /* create client pconfig */
    for (; rest > 0; ) {
        POSITION_HEAD(p, rest);
        if (rest == 0) break;

        if (!strncasecmp(p, NODE_KEY, NODE_KEY_LEN)) {
            IIIMCF_client_conf *pconf_next;
            status = get_param(&p, &rest, NODE_KEY, NODE_KEY_LEN, &node);
            if (IIIMF_STATUS_SUCCESS != status) {
                goto error;
            }
            if (!penv->pconf) {
                /* if penv->pconf is not yet initialized, the first one */
                penv->pconf = iiimcf_client_config_new(node, NULL);
                if (!penv->pconf) {
                    free(node);
                    goto error;
                }
                pconf = penv->pconf;
            } else {
                /* otherwise, create new pconfig and link to penv->pconf */
                pconf_next = iiimcf_client_config_new(node, NULL);
                if (!pconf_next) {
                    free(node);
                    goto error;
                }
                pconf->next = pconf_next;
                pconf = pconf_next;
            }
        } else if (!strncasecmp(p, SERVICE_KEY, SERVICE_KEY_LEN)) {
            if (!pconf) goto error;
            status = get_param(&p, &rest, SERVICE_KEY, SERVICE_KEY_LEN, &pconf->service);
            if (IIIMF_STATUS_SUCCESS != status) {
               goto error;
            }
        } else if (!strncasecmp(p, CERT_FILE, CERT_FILE_LEN)) {
            if (!pconf) goto error;
            status = get_param(&p, &rest, CERT_FILE, CERT_FILE_LEN, &pconf->cert_file);
            if (IIIMF_STATUS_SUCCESS != status) {
               goto error;
            }
        } else if (!strncasecmp(p, CERT_KEY_FILE, CERT_KEY_FILE_LEN)) {
            if (!pconf) goto error;
            status = get_param(&p, &rest, CERT_FILE, CERT_FILE_LEN, &pconf->cert_key);
            if (IIIMF_STATUS_SUCCESS != status) {
               goto error;
            }
        } else if (!strncasecmp(p, CERT_CA_FILE, CERT_CA_FILE_LEN)) {
            if (!pconf) goto error;
            status = get_param(&p, &rest, CERT_CA_FILE, CERT_CA_FILE_LEN, &pconf->ca_file);
            if (IIIMF_STATUS_SUCCESS != status) {
               goto error;
            }
        } else if (!strncasecmp(p, CERT_CA_PATH, CERT_CA_PATH_LEN)) {
            if (!pconf) goto error;
            status = get_param(&p, &rest, CERT_CA_PATH, CERT_CA_PATH_LEN, &pconf->ca_path);
            if (IIIMF_STATUS_SUCCESS != status) {
               goto error;
            }
        } else if (!strncasecmp(p, USE_TLS, USE_TLS_LEN)) {
            char *use_tls;
            if (!pconf) goto error;
            status = get_param(&p, &rest, USE_TLS, USE_TLS_LEN, &use_tls);
            if (IIIMF_STATUS_SUCCESS != status) {
               goto error;
            }
            if (!strcasecmp(use_tls, "yes")) {
                pconf->use_tls = 1;
            }
            free(use_tls);
        } else {
            POSITION_TAIL(p, rest);
        }
    }

    (void)munmap(pa, st.st_size);
    return IIIMF_STATUS_SUCCESS;

error:

    iiimcf_client_config_free(penv->pconf);
    penv->pconf = NULL;
    (void)munmap(pa, st.st_size);
    return IIIMF_STATUS_CONFIG;
}


static IIIMF_status
iiimcf_client_file_compat_server(
    IIIMCF_client_env *penv
)
{
    int			fd;
    IIIMF_status	status;
    char *		server_file;
    size_t		server_file_len;
    size_t		home_dir_len;
    struct stat		st;
    int			stat_ret;
    char *		pa;
    const char *	p;
    size_t		rest;
    char *		node = NULL;
    char *		service;

    if (!penv->home_dir) return IIIMF_STATUS_CONFIG;
    home_dir_len = strlen(penv->home_dir);
    server_file_len = (home_dir_len + 1 + SERVER_COMPAT_FILE_LEN);

    server_file = (char *)malloc(server_file_len + 1);
    if (NULL == server_file) {
	return IIIMF_STATUS_MALLOC;
    }

    (void)strcpy(server_file, penv->home_dir);
    *(server_file + home_dir_len) = '/';
    (void)strcpy(server_file + home_dir_len + 1, SERVER_COMPAT_FILE);

    fd = open(server_file, O_RDONLY, 0);
    free(server_file);
    if (fd < 0) return IIIMF_STATUS_CONFIG;
    stat_ret = fstat(fd, &st);
    if (stat_ret < 0) {
	return IIIMF_STATUS_CONFIG;
    }

    pa = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
    (void)close(fd);
    if (NULL == pa) return IIIMF_STATUS_CONFIG;

    p = pa;
    rest = st.st_size;

    while (0 < rest) {
	for (; 0 < rest; --rest, p++) {
	    if (('\r' == *p) || ('\n' == *p)) {
		p += 1;
		rest -= 1;
		break;
	    }
	}
	if (rest < SERVER_COMPAT_KEY_LEN) break;

	status = get_param_compat(&p, &rest,
				  SERVER_COMPAT_KEY,
				  SERVER_COMPAT_KEY_LEN,
				  &node);
	if (IIIMF_STATUS_SUCCESS == status) {
	    p += 1;
	    rest -= 1;
	    break;
	}
    }
    if (!node) {
	return IIIMF_STATUS_CONFIG;
    }

    status = get_param_compat(&p, &rest, "", 0, &service);

    (void)munmap(pa, st.st_size);

    if ('\0' == *service) {
	free(service);
	service = NULL;
    }

    if (IIIMF_STATUS_SUCCESS == status) {
        penv->pconf = iiimcf_client_config_new(node, service);
    }

    return status;
}

static IIIMF_status
iiimcf_client_environ_server(
    IIIMCF_client_env *penv
)
{
    IIIMF_status	status;
    const char *	p;
    size_t		rest;
    char *		node;
    char *		service;

    p = getenv(IIIM_SERVER_ENV);
    if (NULL == p) return IIIMF_STATUS_FAIL;

    rest = strlen(p);

    status = get_param(&p, &rest, "", 0, &node);
    if (IIIMF_STATUS_SUCCESS != status) {
	return status;
    }

    POSITION_HEAD(p, rest);

    status = get_param(&p, &rest, "", 0, &service);

    penv->pconf = iiimcf_client_config_new(node, service);

    return IIIMF_STATUS_SUCCESS;
}

static void
auth_password_generate(
    char * password,
    size_t length
)
{
    int			fd;
    unsigned int	seed;
    int			c;
    int			i;
    int			r;
    unsigned int *	p;
    size_t		n;
    char *		c62;

    *(password + length) = '\0';

    c62 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    fd = open("/dev/random", O_RDONLY, 0);
    if (0 <= fd) {
	r = 0;
	n = ((sizeof (unsigned int)) * length);
	p = (unsigned int * )malloc(n);
	if (NULL != p) {
	    r = read(fd, p, n);
	}
	(void)close(fd);
	if (r == length) {
	    for (i = 0; i < length; i++) {
		*(password + i) = *(c62 + (*(p + i) % 62));
	    }
	    free(p);
	    return;
	}
	free(p);
    }

    seed = (time(NULL) + getpid());
    srand(seed);
    for (i = 0; i < length; i++) {
	c = rand();
	*(password + i) = *(c62 + (c % 62));
    }

    return;
}

static int
auth_password_file_init(
    char * password_file,
    size_t home_dir_len
)
{
    int		fd;
    char	pwd_buf[AUTH_PASSWORD_LEN + 1];
    struct stat	st;
    ssize_t	len;

    /* ${HOME}/.iiim */
    (void)strcpy(password_file + home_dir_len, CONFIG_DIR_BASE);
    if (0 != stat(password_file, &st)) {
	if (ENOENT != errno) return -1;
	if (0 != mkdir(password_file, 0777)) return -1;
    }

    /* ${HOME}/.iiim/auth */
    (void)strcpy(password_file + home_dir_len, CONFIG_DIR_AUTH);
    if (0 != stat(password_file, &st)) {
	if (ENOENT != errno) return -1;
	if (0 != mkdir(password_file, 0700)) return -1;
    }

    /* ${HOME}/.iiim/auth/password */
    (void)strcpy(password_file + home_dir_len, CONFIG_FILE_PASSWD);
    fd = open(password_file, O_CREAT | O_WRONLY, 0600);
    if (-1 == fd) return -1;

    auth_password_generate(pwd_buf, AUTH_PASSWORD_LEN);

    len = write(fd, pwd_buf, AUTH_PASSWORD_LEN + 1);
    (void)close(fd);

    if ((AUTH_PASSWORD_LEN + 1) == len) {
	return 0;
    } else {
	return -1;
    }
}


static int
auth_password_file_open(
    const char * home_dir
)
{
    char *	password_file;
    char *	home_env;
    int		home_dir_len;
    int		len;
    struct stat	st;
    int		fd;

    password_file = NULL;

    if (NULL == home_dir) {
	home_env = getenv("HOME");
	if (NULL != home_env) {
	    home_dir = home_env;
	}
	if (NULL == home_dir) {
	    return -1;
	}
    }

    home_dir_len = strlen(home_dir);
    len = (home_dir_len + 1 + PASSWORD_FILE_LEN);

    password_file = malloc(len + 1);
    if (NULL == password_file) {
	return -1;
    }

    (void)strcpy(password_file, home_dir);
    *(password_file + home_dir_len) = '/';
    home_dir_len += 1;
    (void)strcpy(password_file + home_dir_len, CONFIG_FILE_PASSWD);

    if (0 != stat(password_file, &st)) {
	(void)auth_password_file_init(password_file, home_dir_len);
    }

    fd = open(password_file, O_RDONLY, 0);
    free(password_file);

    return fd;
}

static IIIMF_status
iiimcf_client_auth_password(
    IIIMCF_client_env *penv
)
{
    char *	password;

    if (penv->password) return IIIMF_STATUS_SUCCESS;

    password = strdup("");
#if 0
    int		fd;
    int		len;
    int		i;
    char	pwd_buf[AUTH_PASSWORD_LEN + 1];

    password = NULL;
    fd = -1;

    if (!penv->home_dir) return IIIMF_STATUS_CONFIG;
    fd = auth_password_file_open(penv->home_dir);
    if (-1 == fd) return IIIMF_STATUS_CONFIG;

    len = read(fd, pwd_buf, AUTH_PASSWORD_LEN);
    (void)close(fd);

    if (AUTH_PASSWORD_LEN != len) return IIIMF_STATUS_CONFIG;

    for (i = 0; i < AUTH_PASSWORD_LEN; i++) {
	if (0 == isalnum(pwd_buf[i])) {
	    return IIIMF_STATUS_CONFIG;
	}
    }
    if (AUTH_PASSWORD_LEN != i) {
	return IIIMF_STATUS_CONFIG;
    }

    password = malloc(AUTH_PASSWORD_LEN + 1);
    if (NULL == password) return IIIMF_STATUS_MALLOC;
    (void)memcpy(password, pwd_buf, AUTH_PASSWORD_LEN);
    *(password + AUTH_PASSWORD_LEN) = '\0';

    penv->password = password;

#endif
    return IIIMF_STATUS_SUCCESS;
}

static IIIMF_status
iiimcf_client_os_arch(
    IIIMCF_client_env *penv
)
{

#if defined(OS_ARCH)
    penv->os_arch = strdup(OS_ARCH);
#else /* !OS_ARCH */
#if defined(HAVE_SYSINFO) && defined(SI_ARCHITECTURE)
    {
        char arch[16];
	int ret;
	ret = sysinfo(SI_ARCHITECTURE, arch, sizeof (arch));
	if (-1 == ret) {
	    penv->os_arch = NULL;
	} else if ((0 == strcmp(arch, "sparc")) ||
		   (0 == strcmp(arch, "ppc"))) {
	    penv->os_arch = strdup(arch);
	} else if (0 == strcmp(arch, "i386")) {
	    penv->os_arch = strdup("x86");
	} else {
	    penv->os_arch = strdup("Unknown");
	}
    }
#else /* !HAVE_SYSINFO || !SI_ARCHITECTURE */
    penv->os_arch = strdup("Unknown");
#endif /* !HAVE_SYSINFO || !SI_ARCHITECTURE */
#endif /* !OS_ARCH */
    if (!penv->os_arch)
	return IIIMF_STATUS_MALLOC;

    return IIIMF_STATUS_SUCCESS;
}


static IIIMF_status
dir_valid(const char * path_name, uid_t euid)
{
	struct stat	st;

#if !defined(S_IAMB)
#define	S_IAMB	0x1FF
#endif /* !S_IAMB */

	if ((0 == lstat(path_name, &st)) &&
	    (0 != S_ISDIR(st.st_mode)) &&
	    (euid == st.st_uid) &&
	    ((S_IAMB & st.st_mode) == S_IRWXU)) {
	    return IIIMF_STATUS_SUCCESS;
	} else {
	    return IIIMF_STATUS_FAIL;
	}
}


static IIIMF_status
iiimcf_alternate_unix_domain_socket_file_dir(
    IIIMCF_client_env *	penv,
    char **		path_name
)
{
    DIR *		dir;
    struct dirent *	dirent;
    char *		base_name;
    size_t		base_name_len;
    char *		dir_name;
    size_t		dir_name_len;
    IIIMF_status	found;
    uid_t		euid;

    dir = opendir("/tmp");
    if (NULL == dir) return IIIMF_STATUS_FAIL;

    base_name_len = (strlen(".iiimp-unix-") + strlen(penv->user_name) + 1);
    base_name = (char *)malloc(base_name_len + 1);
    if (NULL == base_name) {
	closedir(dir);
	return IIIMF_STATUS_MALLOC;
    }
    snprintf(base_name, base_name_len + 1, ".iiimp-unix-%s-", penv->user_name);

    euid = geteuid();

    found = IIIMF_STATUS_FAIL;
    while (NULL != (dirent = readdir(dir))) {
	if (0 != strncmp(dirent->d_name, base_name, base_name_len)) {
	    continue;
	}

	dir_name_len = (strlen("/tmp") + 1 + strlen(dirent->d_name) + 1);
	dir_name = (char *)malloc(dir_name_len);
	if (NULL == dir_name) {
	    found = IIIMF_STATUS_MALLOC;
	    break;
	}
	snprintf(dir_name, dir_name_len, "/tmp/%s", dirent->d_name);
	found = dir_valid(dir_name, euid);

	if (IIIMF_STATUS_SUCCESS == found) {
	    (*path_name) = dir_name;
	    break;
	}

	free(dir_name);
    }

    free(base_name);
    closedir(dir);

    return found;
}


IIIMF_status
iiimcf_create_client_env(
    IIIMCF_attr attr,
    IIIMCF_client_env **ppenv
)
{
    IIIMCF_client_env *penv;
    IIIMCF_client_conf *pconf;
    IIIMF_status st;
    const char *server_node, *service, *user_name, *password;
    const char *clientname, *x_display_name, *x_server_vendor;

    server_node = service = user_name = password = NULL;
    clientname = x_display_name = x_server_vendor = NULL;

    penv = (IIIMCF_client_env *)malloc(sizeof (IIIMCF_client_env));
    if (!penv) return IIIMF_STATUS_MALLOC;
    memset(penv, 0, sizeof(*penv));

    st = iiimcf_attr_get_string_value(attr,
				      IIIMCF_ATTR_SERVER_ADDRESS,
				      &server_node);

    if (st != IIIMF_STATUS_SUCCESS && st != IIIMF_STATUS_NO_ATTR_VALUE) goto error;

    st = iiimcf_attr_get_string_value(attr,
				      IIIMCF_ATTR_SERVER_SERVICE,
				      &service);
    if (st != IIIMF_STATUS_SUCCESS && st != IIIMF_STATUS_NO_ATTR_VALUE) goto error;

    if (server_node || service) {
        penv->pconf = iiimcf_client_config_new(server_node ? strdup(server_node) : NULL,
                                              service ?  strdup(service) : NULL);
        if (!penv->pconf) goto memory_error;
    }


    st = iiimcf_attr_get_string_value(attr,
				      IIIMCF_ATTR_USERNAME,
				      &user_name);
    if (st == IIIMF_STATUS_SUCCESS) {
	penv->user_name = strdup(user_name);
	if (!penv->user_name) goto memory_error;
    } else if (st != IIIMF_STATUS_NO_ATTR_VALUE) {
	goto error;
    }
    {
	struct passwd *pwd;
	/* TODO!  Use penv->user_name to check the
	   home directory. */
	if ((pwd = getpwuid(geteuid()))) {
	    if (!penv->user_name)
		penv->user_name = strdup(pwd->pw_name);
	    if (!penv->user_name) goto memory_error;
	    penv->home_dir = strdup(pwd->pw_dir);
	    if (!penv->home_dir) goto memory_error;
	    endpwent();
	}
    }
    /* We still fail to get user_name, set "Unknown" with force.  */
    if (!penv->user_name) {
	penv->user_name = strdup("Unknown");
	if (!penv->user_name) goto memory_error;
    }

    st = iiimcf_attr_get_string_value(attr,
				      IIIMCF_ATTR_PASSWORD,
				      &password);
    if (st == IIIMF_STATUS_SUCCESS) {
	penv->password = strdup(password);
	if (!penv->password) goto memory_error;
    } else if (st == IIIMF_STATUS_NO_ATTR_VALUE) {
	st = iiimcf_client_auth_password(penv);
	if ((st != IIIMF_STATUS_SUCCESS)
	    && (st != IIIMF_STATUS_CONFIG))
	    goto error;
    } else {
	goto error;
    }

    if (!server_node && !service) {
	st = iiimcf_client_environ_server(penv);
	if (st != IIIMF_STATUS_SUCCESS) {
	    st = iiimcf_client_file_server(penv);
	    if (st != IIIMF_STATUS_SUCCESS) {
		st = iiimcf_client_file_compat_server(penv);
	    }
	}
    }
    if (!penv->pconf) {
        /* 
         * we don't have any pconfiguration.
         * prepare default one.
         */
        int node_len;
        char *tmp_node;

        /* 
         * first try to /tmp/.iiimp-unix-${USERNAME}/
         */
	node_len = strlen("/tmp/.iiimp-unix-") + strlen(penv->user_name);
        tmp_node = malloc(node_len + 1);
        if (!tmp_node) goto memory_error;
        snprintf(tmp_node, node_len + 1, "/tmp/.iiimp-unix-%s", penv->user_name);
        tmp_node[node_len] = '\0';

	if (IIIMF_STATUS_SUCCESS != dir_valid(tmp_node, geteuid())) {
	    /* 
	     * try /tmp/.iiimp-unix-${USERNAME}-xxxxxxxx/
	     */
	    free(tmp_node);
	    tmp_node = NULL;
	    st = iiimcf_alternate_unix_domain_socket_file_dir(penv, &tmp_node);
	}

	if (NULL != tmp_node) {
	    penv->pconf = iiimcf_client_config_new(tmp_node, NULL);
	    if (!penv->pconf) {
		free(tmp_node);
		goto memory_error;
	    }
	}

        /* 
         * second try to $SOCKETDIR/.iiimp-unix/
         */
        tmp_node = strdup(SOCKETDIR "/.iiimp-unix");
        if (!tmp_node) goto memory_error;
	pconf = iiimcf_client_config_new(tmp_node, NULL);
        if (NULL == pconf) {
            free(tmp_node);
            goto memory_error;
        }

	if (NULL == penv->pconf) {
	    penv->pconf = pconf;
	} else {
	    penv->pconf->next = pconf;
	}
    }

    st = iiimcf_attr_get_string_value(attr,
				      IIIMCF_ATTR_CLIENT_TYPE,
				      &clientname);
    if (st == IIIMF_STATUS_SUCCESS) {
	penv->type = strdup(clientname);
    } else if (st == IIIMF_STATUS_NO_ATTR_VALUE) {
	penv->type = strdup(CLIENT_TYPE);
    } else {
	goto error;
    }
    if (!penv->type) goto memory_error;

    st = iiimcf_attr_get_string_value(attr,
				      IIIMCF_ATTR_CLIENT_X_DISPLAY_NAME,
				      &x_display_name);
    if (st == IIIMF_STATUS_SUCCESS) {
	penv->X_display_name = strdup(x_display_name);
	if (!penv->X_display_name) goto memory_error;
    } else if (st != IIIMF_STATUS_NO_ATTR_VALUE) {
	goto error;
    }

    st = iiimcf_attr_get_string_value(attr,
				      IIIMCF_ATTR_CLIENT_X_SERVER_VENDOR,
				      &x_server_vendor);
    if (st == IIIMF_STATUS_SUCCESS) {
	penv->X_server_vendor = strdup(x_server_vendor);
	if (!penv->X_server_vendor) goto memory_error;
    } else if (st != IIIMF_STATUS_NO_ATTR_VALUE) {
	goto error;
    }

    {
	struct utsname name;
	if (-1 != uname(&name)) {
	    penv->node = strdup(name.nodename);
	    if (!penv->node) goto memory_error;
	    penv->os_name = strdup(name.sysname);
	    if (!penv->os_name) goto memory_error;
	    penv->os_version = strdup(name.release);
	    if (!penv->os_version) goto memory_error;
	}
    }

    st = iiimcf_client_os_arch(penv);
    if ((st != IIIMF_STATUS_SUCCESS)
	&& (st != IIIMF_STATUS_CONFIG))
	goto error;

    *ppenv = penv;

    return IIIMF_STATUS_SUCCESS;

memory_error:
    st = IIIMF_STATUS_MALLOC;
error:
    iiimcf_delete_client_env(penv);
    return st;
}

IIIMF_status
iiimcf_delete_client_env(
    IIIMCF_client_env *penv
)
{
    if (penv->user_name) free(penv->user_name);
    if (penv->password) free(penv->password);
    if (penv->home_dir) free(penv->home_dir);
    if (penv->node) free(penv->node);
    if (penv->type) free(penv->type);
    if (penv->os_name) free(penv->os_name);
    if (penv->os_arch) free(penv->os_arch);
    if (penv->os_version) free(penv->os_version);
    if (penv->X_display_name) free(penv->X_display_name);
    if (penv->X_server_vendor) free(penv->X_server_vendor);
    if (penv->pconf)  iiimcf_client_config_free(penv->pconf);

    free(penv);
    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_create_im_connect_message(
    IIIMCF_handle_rec *ph,
    IIIMP_message **ppmes
)
{
    int len;
    char *user;
    IIIMF_status st;
    IIIMP_message *pmes;
    IIIMP_string *pim_user;
    IIIMCF_client_env *penv = ph->penv;

    len = strlen(penv->user_name);
    if (penv->node) len += strlen(penv->node);
    if (penv->password) len += strlen(penv->password);
    user = (char*) malloc(sizeof(char) * (len + 1 + 1 + 1)); /* for @, #, and \0. */
    if (!user) return IIIMF_STATUS_MALLOC;
    strcpy(user, penv->user_name);
    if (penv->node) {
	strcat(user, "@");
	strcat(user, penv->node);
    }
    if (penv->password) {
	strcat(user, "#");
	strcat(user, penv->password);
    }
    st = iiimf_data_string_ascii_new(ph->data_s, user, &pim_user);
    free(user);
    if (st != IIIMF_STATUS_SUCCESS) return st;
    pmes = iiimp_connect_new(ph->data_s, pim_user, NULL);
    if (!pmes) {
	iiimp_string_delete(ph->data_s, pim_user);
	return IIIMF_STATUS_MALLOC;
    }

    *ppmes = pmes;

    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimcf_create_client_descriptor_message(
    IIIMCF_handle_rec *ph,
    IIIMP_message **ppmes
)
{
    IIIMF_status st;
    IIIMCF_client_env *penv = ph->penv;
    IIIMP_message *pmes;
    IIIMP_imattribute *pimattr;
    IIIMP_client_descriptor *cdesc;
    IIIMP_string *type, *os_name, *os_arch, *os_ver;
    IIIMP_string *X_display_name, *X_server_vendor;

    pmes = NULL;
    pimattr = NULL;
    cdesc = NULL;
    type = os_name = os_arch = os_ver = NULL;
    X_display_name = X_server_vendor = NULL;

    st = iiimf_data_string_ascii_new(ph->data_s, penv->type, &type);
    if (st != IIIMF_STATUS_SUCCESS) goto error;
    st = iiimf_data_string_ascii_new(ph->data_s, penv->os_name, &os_name);
    if (st != IIIMF_STATUS_SUCCESS) goto error;
    st = iiimf_data_string_ascii_new(ph->data_s, penv->os_arch, &os_arch);
    if (st != IIIMF_STATUS_SUCCESS) goto error;
    st = iiimf_data_string_ascii_new(ph->data_s, penv->os_version, &os_ver);
    if (st != IIIMF_STATUS_SUCCESS) goto error;
    if (penv->X_display_name) {
	st = iiimf_data_string_ascii_new(ph->data_s,
					 penv->X_display_name,
					 &X_display_name);
	if (st != IIIMF_STATUS_SUCCESS) goto error;
    }
    if (penv->X_server_vendor) {
	st = iiimf_data_string_ascii_new(ph->data_s,
					 penv->X_server_vendor,
					 &X_server_vendor);
	if (st != IIIMF_STATUS_SUCCESS) goto error;
    }

    cdesc = iiimp_client_descriptor_new(ph->data_s,
					type, os_name, os_arch, os_ver,
					X_display_name, X_server_vendor);
    if (!cdesc) goto error;

    pimattr = iiimp_imattribute_client_descriptor_new(ph->data_s,
						      IIIMP_IMATTRIBUTE_CLIENT_DESCRIPTOR,
						      0, cdesc);
    if (!pimattr) goto error;

    pmes = iiimp_setimvalues_new(ph->data_s, ph->im_id, pimattr);
    if (!pmes) goto error;

    *ppmes = pmes;

    return IIIMF_STATUS_SUCCESS;

error:
    if (type) iiimp_string_delete(ph->data_s, type);
    if (os_name) iiimp_string_delete(ph->data_s, os_name);
    if (os_arch) iiimp_string_delete(ph->data_s, os_arch);
    if (os_ver) iiimp_string_delete(ph->data_s, os_ver);
    if (X_display_name) iiimp_string_delete(ph->data_s, X_display_name);
    if (X_server_vendor) iiimp_string_delete(ph->data_s, X_server_vendor);
    if (cdesc) iiimp_client_descriptor_delete(ph->data_s, cdesc);
    if (pimattr) iiimp_imattribute_delete(ph->data_s, pimattr);

    return IIIMF_STATUS_MALLOC;
}

/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
