/**
 * Copyright (C) 2006 International Business Machines Corp.
 *   Author(s): Michael A. Halcrow <mahalcro@us.ibm.com>
 *              Trevor Highland <trevor.highland@gmail.com>
 *
 * 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; either version 2 of the
 * License, or (at your option) any later version.
 *
 * 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., 59 Temple Place - Suite 330, Boston, MA
 * 02111-1307, USA.
 */

#ifndef S_SPLINT_S
#include <sys/types.h>
#include <sys/stat.h>
#endif
#include <sys/mman.h>
#include <fcntl.h>
#include <string.h>
#ifndef S_SPLINT_S
#include <stdio.h>
#include <syslog.h>
#endif /* S_SPLINT_S */
#include <errno.h>
#include <stdlib.h>
#include <pwd.h>
#include "../include/ecryptfs.h"

#define MAX_TOK_LEN 128
#define MAX_FILE_SIZE 0xa000

int print_nvp_list(struct ecryptfs_name_val_pair *dst)
{
	syslog(LOG_ERR, "Printing nvp list\n");
	while (dst) {
		syslog(LOG_ERR, "name=%s\n", dst->name);
		syslog(LOG_ERR, "val=%s\n", dst->value);
		dst = dst->next;
	}
	return 0;
}

/**
 * For each name in dst that is also in src, set the value in dst to
 * that which is in src.
 *
 * For each name in src that is not in dst, append a copy of the node
 * onto dst.
 */
int ecryptfs_nvp_list_union(struct ecryptfs_name_val_pair *dst,
			    struct ecryptfs_name_val_pair *src)
{
	int rc = 0;
	struct ecryptfs_name_val_pair *dst_cursor;
	struct ecryptfs_name_val_pair *src_cursor;

	src_cursor = src->next;
	while (src_cursor) {
		int found_match;
		struct ecryptfs_name_val_pair *prev_dst_cursor;

		if (!src_cursor->name)
			goto next_src_cursor;
		found_match = 0;
		prev_dst_cursor = dst;
		dst_cursor = dst->next;
		while (dst_cursor) {
			if (!dst_cursor->name)
				goto next_dst_cursor;
			if (strcmp(src_cursor->name, dst_cursor->name) == 0) {
				found_match = 1;
				free(dst_cursor->value);
				rc = asprintf(&dst_cursor->value, "%s",
					      src_cursor->value);
				if (rc == -1) {
					rc = -ENOMEM;
					goto out;
				}
			}
next_dst_cursor:
			prev_dst_cursor = dst_cursor;
			dst_cursor = dst_cursor->next;
		}
		if (!found_match) {
			prev_dst_cursor->next = dst_cursor =
				malloc(sizeof(struct ecryptfs_name_val_pair));
			if (!dst_cursor) {
				rc = -ENOMEM;
				goto out;
			}
			dst_cursor->next = NULL;
			dst_cursor->flags = src_cursor->flags;
			rc = asprintf(&dst_cursor->name, "%s",
				      src_cursor->name);
			if (rc == -1) {
				rc = -ENOMEM;
				goto out;
			}
			rc = asprintf(&dst_cursor->value, "%s",
				      src_cursor->value);
			if (rc == -1) {
				rc = -ENOMEM;
				goto out;
			}
		}
next_src_cursor:
		src_cursor = src_cursor->next;
	}
out:
	return rc;
}

int
ecryptfs_parse_rc_file_fullpath(struct ecryptfs_name_val_pair *nvp_list_head,
				char *fullpath)
{
	int rc;
	int fd;

	fd = open(fullpath, O_RDONLY);
	if (fd == -1) {
		rc = -EIO;
		goto out;
	}
	rc = parse_options_file(fd, nvp_list_head);
	close(fd);
out:
	return rc;
}

int ecryptfs_parse_rc_file(struct ecryptfs_name_val_pair *nvp_list_head)
{
	char *home;
	uid_t uid;
	struct passwd *pw;
	char *rcfile_fullpath;
	int rc;

	uid = getuid();
	pw = getpwuid(uid);
	home = pw->pw_dir;
	rc = asprintf(&rcfile_fullpath, "%s/.ecryptfsrc", home);
	if (rc == -1) {
		rc = -ENOMEM;
		goto out;
	}
	rc = ecryptfs_parse_rc_file_fullpath(nvp_list_head, rcfile_fullpath);
	free(rcfile_fullpath);
out:
	return rc;
}

int process_comma_tok(struct ecryptfs_name_val_pair **current, char *tok,
		      /*@null@*/ char *prefix)
{
	int tok_len = (int)strlen(tok);
	char new_prefix[MAX_TOK_LEN];
	char sub_token[MAX_TOK_LEN];
	char *name = NULL;
	char *value = NULL;
	int i, j, st_len;
	int rc = 0;

	if(tok && tok[0] == '\0') {
		goto out;
	}
	if (tok_len > MAX_TOK_LEN) {
		rc = -EINVAL;
		goto out;
	}
	if (tok[0] == '=' || tok[0] == ':') {
		rc = -EINVAL;
		goto out;
	}
	j = 0;
	if (tok_len > 4 && !memcmp(tok, "key=", 4))
		for (i = 4; i < tok_len; i++) {
			if (tok[i] == ':')
				goto process_colon_list;
		}
	goto process_nv_pair;
process_colon_list:
	new_prefix[j] = '\0';
	i = 0;
	j = 0;
	while (i < tok_len) {
		if (tok[i] == ':') {
			sub_token[j] = '\0';
			if ((rc = process_comma_tok(current, sub_token, NULL)))
				goto out;
			j = 0;
		} else
			sub_token[j++] = tok[i];
		i++;
	}
	sub_token[j] = '\0';
	rc = process_comma_tok(current, sub_token, new_prefix);
	goto out;
process_nv_pair:
	st_len = snprintf(sub_token, MAX_TOK_LEN, "%s%s",
			  (prefix ? prefix : ""), tok);
	j = 0;
	for (i = 0; i < st_len; i++)
		if (sub_token[i] == '=') {
			if (!(name = malloc(i + 1))) {
				rc = -ENOMEM;
				goto out;
			}
			memcpy(name, sub_token, i);
			name[i] = '\0';
			j = i;
		}
	if (!name) {
		if (!(name = malloc(i))) {
			rc = -ENOMEM;
			goto out;
		}
		memcpy(name, sub_token, i);
		name[i] = '\0';
	} else {
		if((i-j) > 1) {
			if (!(value = malloc(i - j + 1))) {
				rc = -ENOMEM;
				goto out;
			}
			memcpy(value, &sub_token[j+1], (i - j));
			value[(i - j)] = '\0';
		}
	}
	if (!((*current)->next = malloc(sizeof(**current)))) {
		rc = -ENOMEM;
		goto out;
	}
	if (strlen(name) == 0) {
		free(name);
		free(value);
	} else {
		*current = (*current)->next;
		(*current)->name = name;
		(*current)->value = value;
		(*current)->next = NULL;
	}
out:
	return rc;
}

/**
 * name=val,key=PKI:name1=val1:name2=val2,name=val...
 */
int generate_nv_list(struct ecryptfs_name_val_pair *head, char *buf)
{
	struct ecryptfs_name_val_pair *current = head;
	char tok_str[MAX_TOK_LEN];
	int buf_len = strlen(buf);
	int i, j = 0;
	int rc = 0;

	for (i = 0; i < buf_len; i++) {
		if (buf[i] == ',' || buf[i] == '\n') {
			tok_str[j] = '\0';
			if ((rc = process_comma_tok(&current, tok_str, NULL)))
				goto out;
			j = 0;
		} else
			tok_str[j++] = buf[i];
		if (j == MAX_TOK_LEN)
			goto out;
	}
	tok_str[j] = '\0';
	if ((rc = process_comma_tok(&current, tok_str, NULL)))
		goto out;
out:
	return rc;
}

int ecryptfs_parse_options(char *opts, struct ecryptfs_name_val_pair *head)
{
	return generate_nv_list(head, opts);
}

int parse_options_file(int fd, struct ecryptfs_name_val_pair *head)
{
	int rc = 0;
	int pagesize;
	char *data;
	off_t file_size;
	struct stat filestat;

	rc = fstat(fd, &filestat);
	if (rc)
		goto out;
	pagesize = getpagesize();
	file_size = filestat.st_size;
	if (file_size > MAX_FILE_SIZE) {
		syslog(LOG_ERR, "File size too large\n");
		rc = -1;
		goto out;
	}
	if (file_size % pagesize) {
		file_size -= file_size % pagesize;
		file_size += pagesize;
	}
	data = mmap((caddr_t)0, file_size, PROT_READ, MAP_PRIVATE, fd, 0);
	if (data == MAP_FAILED)	{
		rc = errno;
		goto out;
	}
	rc = generate_nv_list(head, data);
	munmap(data, file_size);
out:
	return rc;
}
