/*
 * WPA Supplicant / Configuration file parser
 * Copyright (c) 2003-2004, Jouni Malinen <jkmaline@cc.hut.fi>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * Alternatively, this software may be distributed under the terms of BSD
 * license.
 *
 * See README and COPYING for more details.
 */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "common.h"
#include "wpa.h"
#include "config.h"
#include "sha1.h"
#include "wpa_supplicant.h"


static char * wpa_config_get_line(char *s, int size, FILE *stream, int *line)
{
	char *pos, *end, *sstart;

	while (fgets(s, size, stream)) {
		(*line)++;
		s[size - 1] = '\0';
		pos = s;

		while (*pos == ' ' || *pos == '\t')
			pos++;
		if (*pos == '#' || *pos == '\n' || *pos == '\0')
			continue;

		/* Remove # comments unless they are within a double quoted
		 * string. Remove trailing white space. */
		sstart = strchr(pos, '"');
		if (sstart)
			sstart = strchr(sstart + 1, '"');
		if (!sstart)
			sstart = pos;
		end = strchr(sstart, '#');
		if (end)
			*end-- = '\0';
		else
			end = pos + strlen(pos) - 1;
		while (end > pos &&
		       (*end == '\n' || *end == ' ' || *end == '\t')) {
			*end-- = '\0';
		}
		if (*pos == '\0')
			continue;

		return pos;
	}

	return NULL;
}


static int wpa_config_parse_ssid(struct wpa_ssid *ssid, int line,
				 const char *value)
{
	char *pos;
	if (*value == '"') {
		value++;
		pos = strchr(value, '"');
		if (pos == NULL || pos[1] != '\0') {
			value--;
			wpa_printf(MSG_ERROR, "Line %d: invalid SSID '%s'.",
				   line, value);
			return -1;
		}
		*pos = '\0';
		ssid->ssid_len = strlen(value);
		if (ssid->ssid_len > MAX_SSID_LEN) {
			wpa_printf(MSG_ERROR, "Line %d: Too long SSID '%s'.",
				   line, value);
			return -1;
		}
		memcpy(ssid->ssid, value, ssid->ssid_len);
	} else {
		int len = strlen(value);
		if (len > MAX_SSID_LEN * 2 || len % 1 ||
		    hexstr2bin(value, ssid->ssid, len / 2)) {
			wpa_printf(MSG_ERROR, "Line %d: Invalid SSID '%s'.",
				   line, value);
			return -1;
		}
		ssid->ssid_len = len / 2;
	}
	wpa_hexdump(MSG_MSGDUMP, "SSID", ssid->ssid, ssid->ssid_len);
	return 0;
}


static int wpa_config_parse_scan_ssid(struct wpa_ssid *ssid, int line,
				      const char *value)
{
	if (value[0] == '1')
		ssid->scan_ssid = 1;
	else if (value[0] == '0')
		ssid->scan_ssid = 0;
	else {
		wpa_printf(MSG_ERROR, "Line %d: invalid scan_ssid setting '%s'"
			   " (expected 0 or 1)\n", line, value);
		return -1;
	}
	return 0;
}


static int wpa_config_parse_bssid(struct wpa_ssid *ssid, int line,
				  const char *value)
{
	if (hwaddr_aton(value, ssid->bssid)) {
		wpa_printf(MSG_ERROR, "Line %d: Invalid BSSID '%s'.",
			   line, value);
		return -1;
	}
	ssid->bssid_set = 1;
	wpa_hexdump(MSG_MSGDUMP, "BSSID", ssid->bssid, ETH_ALEN);
	return 0;
}


static int wpa_config_parse_psk(struct wpa_ssid *ssid, int line,
				const char *value)
{
	if (*value == '"') {
		char *pos;
		int len;

		value++;
		pos = strchr(value, '"');
		if (pos)
			*pos = '\0';
		len = strlen(value);
		if (len < 8 || len > 63) {
			wpa_printf(MSG_ERROR, "Line %d: Invalid passphrase "
				   "length %d (expected: 8..63) '%s'.",
				   line, len, value);
			return -1;
		}
		ssid->passphrase = strdup(value);
		return ssid->passphrase == NULL ? -1 : 0;
	}

	if (hexstr2bin(value, ssid->psk, PMK_LEN) ||
		   value[PMK_LEN * 2] != '\0') {
		wpa_printf(MSG_ERROR, "Line %d: Invalid PSK '%s'.",
			   line, value);
		return -1;
	}
	ssid->psk_set = 1;
	wpa_hexdump(MSG_MSGDUMP, "PSK", ssid->psk, PMK_LEN);
	return 0;
}


static int wpa_config_parse_key_mgmt(struct wpa_ssid *ssid, int line,
				     const char *value)
{
	int val = 0, last, errors = 0;
	char *start, *end, *buf;

	buf = strdup(value);
	if (buf == NULL)
		return -1;
	start = buf;

	while (start != '\0') {
		while (*start == ' ' || *start == '\t')
			start++;
		if (*start == '\0')
			break;
		end = start;
		while (*end != ' ' && *end != '\t' && *end != '\0')
			end++;
		last = *end == '\0';
		*end = '\0';
		if (strcmp(start, "WPA-PSK") == 0)
			val |= WPA_KEY_MGMT_PSK;
		else if (strcmp(start, "WPA-EAP") == 0)
			val |= WPA_KEY_MGMT_IEEE8021X;
		else if (strcmp(start, "NONE") == 0)
			val |= WPA_KEY_MGMT_NONE;
		else {
			wpa_printf(MSG_ERROR, "Line %d: invalid key_mgmt '%s'",
				   line, start);
			errors++;
		}

		if (last)
			break;
		start = end + 1;
	}

	if (val == 0) {
		wpa_printf(MSG_ERROR,
			   "Line %d: no key_mgmt values configured.", line);
		errors++;
	}

	wpa_printf(MSG_MSGDUMP, "key_mgmt: 0x%x", val);
	ssid->key_mgmt = val;
	return errors ? -1 : 0;
}


static int wpa_config_parse_cipher(struct wpa_ssid *ssid, int line,
				   const char *value)
{
	int val = 0, last;
	char *start, *end, *buf;

	buf = strdup(value);
	if (buf == NULL)
		return -1;
	start = buf;

	while (start != '\0') {
		while (*start == ' ' || *start == '\t')
			start++;
		if (*start == '\0')
			break;
		end = start;
		while (*end != ' ' && *end != '\t' && *end != '\0')
			end++;
		last = *end == '\0';
		*end = '\0';
		if (strcmp(start, "CCMP") == 0)
			val |= WPA_CIPHER_CCMP;
		else if (strcmp(start, "TKIP") == 0)
			val |= WPA_CIPHER_TKIP;
		else if (strcmp(start, "WEP104") == 0)
			val |= WPA_CIPHER_WEP104;
		else if (strcmp(start, "WEP40") == 0)
			val |= WPA_CIPHER_WEP40;
		else if (strcmp(start, "NONE") == 0)
			val |= WPA_CIPHER_NONE;
		else {
			wpa_printf(MSG_ERROR, "Line %d: invalid cipher '%s'.",
				   line, start);
			return -1;
		}

		if (last)
			break;
		start = end + 1;
	}

	if (val == 0) {
		wpa_printf(MSG_ERROR, "Line %d: no cipher values configured.",
			   line);
		return -1;
	}
	return val;
}


static int wpa_config_parse_pairwise(struct wpa_ssid *ssid, int line,
				     const char *value)
{
	int val;
	val = wpa_config_parse_cipher(ssid, line, value);
	if (val == -1)
		return -1;
	if (val & ~(WPA_CIPHER_CCMP | WPA_CIPHER_TKIP | WPA_CIPHER_NONE)) {
		wpa_printf(MSG_ERROR, "Line %d: not allowed pairwise cipher "
			   "(0x%x).", line, val);
		return -1;
	}

	wpa_printf(MSG_MSGDUMP, "pairwise: 0x%x", val);
	ssid->pairwise_cipher = val;
	return 0;
}


static int wpa_config_parse_group(struct wpa_ssid *ssid, int line,
				  const char *value)
{
	int val;
	val = wpa_config_parse_cipher(ssid, line, value);
	if (val == -1)
		return -1;
	if (val & ~(WPA_CIPHER_CCMP | WPA_CIPHER_TKIP | WPA_CIPHER_WEP104 |
		    WPA_CIPHER_WEP40)) {
		wpa_printf(MSG_ERROR, "Line %d: not allowed group cipher "
			   "(0x%x).", line, val);
		return -1;
	}

	wpa_printf(MSG_MSGDUMP, "group: 0x%x", val);
	ssid->group_cipher = val;
	return 0;
}


static struct wpa_ssid_fields {
	char *name;
	int (*parser)(struct wpa_ssid *ssid, int line, const char *value);
} ssid_fields[] = {
	{ "ssid", wpa_config_parse_ssid },
	{ "scan_ssid", wpa_config_parse_scan_ssid },
	{ "bssid", wpa_config_parse_bssid },
	{ "psk", wpa_config_parse_psk },
	{ "key_mgmt", wpa_config_parse_key_mgmt },
	{ "pairwise", wpa_config_parse_pairwise },
	{ "group", wpa_config_parse_group },
};

#define NUM_SSID_FIELDS (sizeof(ssid_fields) / sizeof(ssid_fields[0]))


static struct wpa_ssid * wpa_config_read_network(FILE *f, int *line)
{
	struct wpa_ssid *ssid;
	int errors = 0, i, end = 0;
	char buf[256], *pos, *pos2;

	wpa_printf(MSG_MSGDUMP, "Line: %d - start of a new network block",
		   *line);
	ssid = (struct wpa_ssid *) malloc(sizeof(*ssid));
	if (ssid == NULL)
		return NULL;
	memset(ssid, 0, sizeof(*ssid));

	ssid->pairwise_cipher = WPA_CIPHER_CCMP | WPA_CIPHER_TKIP;
	ssid->group_cipher = WPA_CIPHER_CCMP | WPA_CIPHER_TKIP |
		WPA_CIPHER_WEP104 | WPA_CIPHER_WEP40;
	ssid->key_mgmt = WPA_KEY_MGMT_PSK | WPA_KEY_MGMT_IEEE8021X;

	while ((pos = wpa_config_get_line(buf, sizeof(buf), f, line))) {
		if (strcmp(pos, "}") == 0) {
			end = 1;
			break;
		}

		pos2 = strchr(pos, '=');
		if (pos2 == NULL) {
			wpa_printf(MSG_ERROR, "Line %d: Invalid SSID line "
				   "'%s'.", *line, pos);
			errors++;
			continue;
		}

		*pos2++ = '\0';
		if (*pos2 == '"') {
			if (strchr(pos2 + 1, '"') == NULL) {
				wpa_printf(MSG_ERROR, "Line %d: invalid "
					   "quotation '%s'.", *line, pos2);
				errors++;
				continue;
			}
		}

		for (i = 0; i < NUM_SSID_FIELDS; i++) {
			if (strcmp(pos, ssid_fields[i].name) == 0) {
				if (ssid_fields[i].parser(ssid, *line, pos2)) {
					wpa_printf(MSG_ERROR, "Line %d: failed"
						   " to parse %s '%s'.",
						   *line, pos, pos2);
					errors++;
				}
				break;
			}
		}
		if (i == NUM_SSID_FIELDS) {
			wpa_printf(MSG_ERROR, "Line %d: unknown network field "
				   "'%s'.", *line, pos);
			errors++;
		}
	}

	if (!end) {
		wpa_printf(MSG_ERROR, "Line %d: network block was not "
			   "terminated properly.", *line);
		errors++;
	}

	if (ssid->passphrase) {
		if (ssid->psk_set) {
			wpa_printf(MSG_ERROR, "Line %d: both PSK and "
				   "passphrase configured.", *line);
			errors++;
		}
		pbkdf2_sha1(ssid->passphrase, ssid->ssid, ssid->ssid_len, 4096,
			    ssid->psk, PMK_LEN);
		wpa_hexdump(MSG_MSGDUMP, "PSK (from passphrase)",
			    ssid->psk, PMK_LEN);
		ssid->psk_set = 1;
	}

	if ((ssid->key_mgmt & WPA_KEY_MGMT_PSK) && !ssid->psk_set) {
		wpa_printf(MSG_ERROR, "Line %d: WPA-PSK accepted for key "
			   "management, but no PSK configured.", *line);
		errors++;
	}

	if (errors) {
		free(ssid);
		ssid = NULL;
	}

	return ssid;
}


struct wpa_ssid * wpa_config_read(const char *ssid_file)
{
	FILE *f;
	char buf[256], *pos;
	int errors = 0, line = 0;
	struct wpa_ssid *ssid, *tail = NULL, *head = NULL;

	wpa_printf(MSG_DEBUG, "Reading configuration file '%s'", ssid_file);
	f = fopen(ssid_file, "r");
	if (f == NULL)
		return NULL;

	while ((pos = wpa_config_get_line(buf, sizeof(buf), f, &line))) {
		if (strcmp(pos, "network={") == 0) {
			ssid = wpa_config_read_network(f, &line);
			if (ssid == NULL) {
				wpa_printf(MSG_ERROR, "Line %d: failed to "
					   "parse network block.", line);
				errors++;
				continue;
			}
			if (head == NULL) {
				head = tail = ssid;
			} else {
				tail->next = ssid;
				tail = ssid;
			}
		} else {
			wpa_printf(MSG_ERROR, "Line %d: Invalid configuration "
				   "line '%s'.", line, pos);
			errors++;
			continue;
		}
	}

	fclose(f);

	if (errors) {
		wpa_config_free(head);
		head = NULL;
	}

	return head;
}


void wpa_config_free(struct wpa_ssid *ssid)
{
	struct wpa_ssid *prev = NULL;
	while (ssid) {
		prev = ssid;
		ssid = ssid->next;
		free(prev->passphrase);
		free(prev);
	}
}
