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

#include <iiimp.h>

#include "input-method.h"
#include "role-data-client.h"


#define NODE_DEFAULT	"localhost"
#define NODE_KEY	"node="
#define NODE_KEY_LEN	(5)
#define SERVICE_DEFAULT	"9010"
#define SERVICE_KEY	"service="
#define SERVICE_KEY_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"


const char *
iiimf_role_data_client_user_name(IIIMF_im * im)
{
    return im->role_data.client->user_name;
}


const char *
iiimf_role_data_client_home_dir(IIIMF_im * im)
{
    return im->role_data.client->home_dir;
}


const char *
iiimf_role_data_client_node(IIIMF_im * im)
{
    return im->role_data.client->node;
}


const char *
iiimf_role_data_client_server_node(IIIMF_im * im)
{
    return im->role_data.client->server_node;
}


const char *
iiimf_role_data_client_service(IIIMF_im * im)
{
    return im->role_data.client->service;
}


#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 IIIMF_status
iiimf_role_data_client_file_server(IIIMF_role_data_client * client)
{
    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;
    char *		service;

    home_dir_len = strlen(client->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, client->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;

    POSITION_HEAD(p, rest);
    if (rest < NODE_KEY_LEN) {
	(void)munmap(pa, st.st_size);
	return IIIMF_STATUS_CONFIG;
    }

    status = get_param(&p, &rest, NODE_KEY, NODE_KEY_LEN, &node);
    if (IIIMF_STATUS_SUCCESS != status) {
	(void)munmap(pa, st.st_size);
	return status;
    }

    client->server_node = node;

    POSITION_HEAD(p, rest);
    if (rest < SERVICE_KEY_LEN) {
	(void)munmap(pa, st.st_size);
	return IIIMF_STATUS_SUCCESS;
    }

    status = get_param(&p, &rest, SERVICE_KEY, SERVICE_KEY_LEN, &service);

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

    client->service = service;

    return IIIMF_STATUS_SUCCESS;
}


static IIIMF_status
iiimf_role_data_client_file_compat_server(IIIMF_role_data_client * client)
{
    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;
    char *		service;

    home_dir_len = strlen(client->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, client->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) {
	    client->server_node = node;
	    p += 1;
	    rest -= 1;
	    break;
	}
    }
    if (NULL == client->server_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) {
	client->service = service;
    }

    return status;
}


static IIIMF_status
iiimf_role_data_client_environ_server(IIIMF_role_data_client * client)
{
    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;
    }

    client->server_node = node;

    POSITION_HEAD(p, rest);

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

    client->service = service;

    return IIIMF_STATUS_SUCCESS;
}


IIIMF_status
iiimf_im_client_type_set(
    IIIMF_im *		im,
    const char *	type)
{
    char *	p;
    p = strdup(type);
    if (NULL == p) return IIIMF_STATUS_MALLOC;
    free(im->role_data.client->type);
    im->role_data.client->type = p;
    return IIIMF_STATUS_SUCCESS;
}

IIIMF_status
iiimf_im_user_set(
    IIIMF_im *		im,
    const char *	username,
    const char *	password)
{
    IIIMF_role_data_client *	client;

    client = im->role_data.client;
    if (username) {
	if (client->user_name) free(client->user_name);
	client->user_name = strdup(username);
    }
    if (password) {
	if (client->password) free(client->password);
	client->password = strdup(password);
    }

    return IIIMF_STATUS_SUCCESS;
}


IIIMF_status
iiimf_role_data_client_new(
    const char *		server_node,
    const char *		service,
    IIIMF_role_data_client **	client_ret)
{
    IIIMF_role_data_client *	client;
    struct passwd *		pwd;
    IIIMF_status		status;
    struct utsname		name;

    status = IIIMF_STATUS_CONFIG;

    client = (IIIMF_role_data_client *)malloc(sizeof (IIIMF_role_data_client));
    if (NULL == client) return IIIMF_STATUS_MALLOC;

    client->user_name = NULL;
    client->home_dir = NULL;
    client->node = NULL;

    client->server_node = ((NULL == server_node) ? NULL : strdup(server_node));
    client->service = ((NULL == service) ? NULL : strdup(service));

    client->type = strdup(CLIENT_TYPE);

    client->os_name = NULL;
    client->os_arch = NULL;
    client->os_version = NULL;
    client->X_display_name = NULL;
    client->X_server_vendor = NULL;

    pwd = getpwuid(geteuid());
    if (NULL == pwd) {
	iiimf_role_data_client_delete(client);
	return IIIMF_STATUS_CONFIG;
    }

    client->user_name = strdup(pwd->pw_name);
    client->home_dir = strdup(pwd->pw_dir);

    endpwent();

    if ((NULL == client->user_name) ||
	(NULL == client->home_dir) ||
	((NULL != server_node) && (NULL == client->server_node)) ||
	((NULL != service) && (NULL == client->service))) {
	iiimf_role_data_client_delete(client);
	return IIIMF_STATUS_MALLOC;
    }

    if (-1 == uname(&name)) {
	iiimf_role_data_client_delete(client);
	return IIIMF_STATUS_CONFIG;
    }

    client->node = strdup(name.nodename);

    client->os_name = strdup(name.sysname);
    client->os_version = strdup(name.release);

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

    if ((NULL == server_node) && (NULL == service)) {
	status = iiimf_role_data_client_environ_server(client);
	if (IIIMF_STATUS_SUCCESS != status) {
	    status = iiimf_role_data_client_file_server(client);
	}
	if (IIIMF_STATUS_SUCCESS != status) {
	    status = iiimf_role_data_client_file_compat_server(client);
	}
    }

    if (NULL == client->server_node) {
	client->server_node = strdup(NODE_DEFAULT);
    }
    if (NULL == client->service) {
	client->service = strdup(SERVICE_DEFAULT);
    }
    if ((NULL == client->node) || (NULL == client->service)) {
	iiimf_role_data_client_delete(client);
	return IIIMF_STATUS_MALLOC;
    }

    *client_ret = client;

    return IIIMF_STATUS_SUCCESS;
}


void
iiimf_role_data_client_delete(IIIMF_role_data_client * client)
{
    if (client->user_name) free(client->user_name);
    if (client->password) free(client->password);
    if (client->home_dir) free(client->home_dir);
    if (client->node) free(client->node);
    if (client->server_node) free(client->server_node);
    if (client->service) free(client->service);
    if (client->type) free(client->type);
    if (client->os_name) free(client->os_name);
    if (client->os_arch) free(client->os_arch);
    if (client->os_version) free(client->os_version);
    if (client->X_display_name) free(client->X_display_name);
    if (client->X_server_vendor) free(client->X_server_vendor);

    free(client);
}


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


IIIMF_status
iiimf_role_data_client_auth_password(IIIMF_im * im, char ** password_ret)
{

#if 0
    char *	password;
    int		fd;
    int		len;
    int		i;
    char	pwd_buf[AUTH_PASSWORD_LEN + 1];

    if (im->role_data.client->password) {
	*password_ret = strdup(im->role_data.client->password);
	if (!(*password_ret)) return IIIMF_STATUS_MALLOC;
    } else {
	password = NULL;
	fd = -1;

	fd = auth_password_file_open(im->role_data.client->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';

	*password_ret = password;
    }

#endif
    *password_ret = strdup("");
    return IIIMF_STATUS_SUCCESS;
}


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