/* automatic handling of confread struct arguments
 * Copyright (C) 2006 Andreas Steffen
 * Hochschule fuer Technik Rapperswil, Switzerland
 *
 * 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.  See <http://www.fsf.org/copyleft/gpl.txt>.
 *
 * 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.
 */

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

#include <library.h>
#include <utils/debug.h>

#include "keywords.h"
#include "confread.h"
#include "args.h"

/* argument types */

typedef enum {
	ARG_NONE,
	ARG_ENUM,
	ARG_UINT,
	ARG_TIME,
	ARG_ULNG,
	ARG_ULLI,
	ARG_UBIN,
	ARG_PCNT,
	ARG_STR,
	ARG_LST,
	ARG_MISC
} arg_t;

/* various keyword lists */

static const char *LST_bool[] = {
	"no",
	"yes",
	 NULL
};

static const char *LST_sendcert[] = {
	"always",
	"ifasked",
	"never",
	"yes",
	"no",
	 NULL
};

static const char *LST_unique[] = {
	"no",
	"yes",
	"replace",
	"keep",
	"never",
	 NULL
};

static const char *LST_strict[] = {
	"no",
	"yes",
	"ifuri",
	 NULL
};
static const char *LST_dpd_action[] = {
	"none",
	"clear",
	"hold",
	"restart",
	 NULL
};

static const char *LST_startup[] = {
	"ignore",
	"add",
	"route",
	"start",
	 NULL
};

static const char *LST_keyexchange[] = {
	"ike",
	"ikev1",
	"ikev2",
	 NULL
};

static const char *LST_authby[] = {
	"psk",
	"secret",
	"pubkey",
	"rsa",
	"rsasig",
	"ecdsa",
	"ecdsasig",
	"xauthpsk",
	"xauthrsasig",
	"never",
	 NULL
};

static const char *LST_fragmentation[] = {
	"no",
	"yes",
	"force",
	 NULL
};

typedef struct {
	arg_t       type;
	size_t      offset;
	const char  **list;
} token_info_t;

static const token_info_t token_info[] =
{
	/* config setup keywords */
	{ ARG_STR,  offsetof(starter_config_t, setup.charondebug),  NULL               },
	{ ARG_ENUM, offsetof(starter_config_t, setup.uniqueids), LST_unique            },
	{ ARG_ENUM, offsetof(starter_config_t, setup.cachecrls), LST_bool              },
	{ ARG_ENUM, offsetof(starter_config_t, setup.strictcrlpolicy), LST_strict      },
	{ ARG_MISC, 0, NULL  /* KW_PKCS11_DEPRECATED */                                },
	{ ARG_MISC, 0, NULL  /* KW_SETUP_DEPRECATED */                                 },

	/* conn section keywords */
	{ ARG_STR,  offsetof(starter_conn_t, name), NULL                               },
	{ ARG_ENUM, offsetof(starter_conn_t, startup), LST_startup                     },
	{ ARG_ENUM, offsetof(starter_conn_t, keyexchange), LST_keyexchange             },
	{ ARG_MISC, 0, NULL  /* KW_TYPE */                                             },
	{ ARG_MISC, 0, NULL  /* KW_COMPRESS */                                         },
	{ ARG_ENUM, offsetof(starter_conn_t, install_policy), LST_bool                 },
	{ ARG_ENUM, offsetof(starter_conn_t, aggressive), LST_bool                     },
	{ ARG_STR,  offsetof(starter_conn_t, authby), LST_authby                       },
	{ ARG_STR,  offsetof(starter_conn_t, eap_identity), NULL                       },
	{ ARG_STR,  offsetof(starter_conn_t, aaa_identity), NULL                       },
	{ ARG_MISC, 0, NULL  /* KW_MOBIKE */                                           },
	{ ARG_MISC, 0, NULL  /* KW_FORCEENCAPS */                                      },
	{ ARG_ENUM, offsetof(starter_conn_t, fragmentation), LST_fragmentation         },
	{ ARG_UBIN, offsetof(starter_conn_t, ikedscp), NULL                            },
	{ ARG_TIME, offsetof(starter_conn_t, sa_ike_life_seconds), NULL                },
	{ ARG_TIME, offsetof(starter_conn_t, sa_ipsec_life_seconds), NULL              },
	{ ARG_TIME, offsetof(starter_conn_t, sa_rekey_margin), NULL                    },
	{ ARG_ULLI, offsetof(starter_conn_t, sa_ipsec_life_bytes), NULL                },
	{ ARG_ULLI, offsetof(starter_conn_t, sa_ipsec_margin_bytes), NULL              },
	{ ARG_ULLI, offsetof(starter_conn_t, sa_ipsec_life_packets), NULL              },
	{ ARG_ULLI, offsetof(starter_conn_t, sa_ipsec_margin_packets), NULL            },
	{ ARG_MISC, 0, NULL  /* KW_KEYINGTRIES */                                      },
	{ ARG_PCNT, offsetof(starter_conn_t, sa_rekey_fuzz), NULL                      },
	{ ARG_MISC, 0, NULL  /* KW_REKEY */                                            },
	{ ARG_MISC, 0, NULL  /* KW_REAUTH */                                           },
	{ ARG_STR,  offsetof(starter_conn_t, ike), NULL                                },
	{ ARG_STR,  offsetof(starter_conn_t, esp), NULL                                },
	{ ARG_STR,  offsetof(starter_conn_t, ah), NULL                                 },
	{ ARG_TIME, offsetof(starter_conn_t, dpd_delay), NULL                          },
	{ ARG_TIME, offsetof(starter_conn_t, dpd_timeout), NULL                        },
	{ ARG_ENUM, offsetof(starter_conn_t, dpd_action), LST_dpd_action               },
	{ ARG_ENUM, offsetof(starter_conn_t, close_action), LST_dpd_action             },
	{ ARG_TIME, offsetof(starter_conn_t, inactivity), NULL                         },
	{ ARG_MISC, 0, NULL  /* KW_MODECONFIG */                                       },
	{ ARG_MISC, 0, NULL  /* KW_XAUTH */                                            },
	{ ARG_STR,  offsetof(starter_conn_t, xauth_identity), NULL                     },
	{ ARG_ENUM, offsetof(starter_conn_t, me_mediation), LST_bool                   },
	{ ARG_STR,  offsetof(starter_conn_t, me_mediated_by), NULL                     },
	{ ARG_STR,  offsetof(starter_conn_t, me_peerid), NULL                          },
	{ ARG_UINT, offsetof(starter_conn_t, reqid), NULL                              },
	{ ARG_MISC, 0, NULL  /* KW_MARK */                                             },
	{ ARG_MISC, 0, NULL  /* KW_MARK_IN */                                          },
	{ ARG_MISC, 0, NULL  /* KW_MARK_OUT */                                         },
	{ ARG_MISC, 0, NULL  /* KW_TFC */                                              },
	{ ARG_MISC, 0, NULL  /* KW_PFS_DEPRECATED */                                   },
	{ ARG_MISC, 0, NULL  /* KW_CONN_DEPRECATED */                                  },

	/* ca section keywords */
	{ ARG_STR,  offsetof(starter_ca_t, name), NULL                                 },
	{ ARG_ENUM, offsetof(starter_ca_t, startup), LST_startup                       },
	{ ARG_STR,  offsetof(starter_ca_t, cacert), NULL                               },
	{ ARG_STR,  offsetof(starter_ca_t, crluri), NULL                               },
	{ ARG_STR,  offsetof(starter_ca_t, crluri2), NULL                              },
	{ ARG_STR,  offsetof(starter_ca_t, ocspuri), NULL                              },
	{ ARG_STR,  offsetof(starter_ca_t, ocspuri2), NULL                             },
	{ ARG_STR,  offsetof(starter_ca_t, certuribase), NULL                          },
	{ ARG_MISC, 0, NULL  /* KW_CA_DEPRECATED */                                    },

	/* end keywords */
	{ ARG_STR,  offsetof(starter_end_t, host), NULL                                },
	{ ARG_UINT, offsetof(starter_end_t, ikeport), NULL                             },
	{ ARG_STR,  offsetof(starter_end_t, subnet), NULL                              },
	{ ARG_MISC, 0, NULL  /* KW_PROTOPORT */                                        },
	{ ARG_STR,  offsetof(starter_end_t, sourceip), NULL                            },
	{ ARG_STR,  offsetof(starter_end_t, dns), NULL                                 },
	{ ARG_ENUM, offsetof(starter_end_t, firewall), LST_bool                        },
	{ ARG_ENUM, offsetof(starter_end_t, hostaccess), LST_bool                      },
	{ ARG_ENUM, offsetof(starter_end_t, allow_any), LST_bool                       },
	{ ARG_STR,  offsetof(starter_end_t, updown), NULL                              },
	{ ARG_STR,  offsetof(starter_end_t, auth), NULL                                },
	{ ARG_STR,  offsetof(starter_end_t, auth2), NULL                               },
	{ ARG_STR,  offsetof(starter_end_t, id), NULL                                  },
	{ ARG_STR,  offsetof(starter_end_t, id2), NULL                                 },
	{ ARG_STR,  offsetof(starter_end_t, rsakey), NULL                              },
	{ ARG_STR,  offsetof(starter_end_t, cert), NULL                                },
	{ ARG_STR,  offsetof(starter_end_t, cert2), NULL                               },
	{ ARG_STR,  offsetof(starter_end_t, cert_policy), NULL                         },
	{ ARG_ENUM, offsetof(starter_end_t, sendcert), LST_sendcert                    },
	{ ARG_STR,  offsetof(starter_end_t, ca), NULL                                  },
	{ ARG_STR,  offsetof(starter_end_t, ca2), NULL                                 },
	{ ARG_STR,  offsetof(starter_end_t, groups), NULL                              },
	{ ARG_STR,  offsetof(starter_end_t, groups2), NULL                             },
	{ ARG_MISC, 0, NULL  /* KW_END_DEPRECATED */                                   },
};

static void free_list(char **list)
{
	char **s;

	for (s = list; *s; s++)
	{
		free(*s);
	}
	free(list);
}

char** new_list(char *value)
{
	char *val, *b, *e, *end, **ret;
	int count;

	val = strdupnull(value);
	if (!val)
	{
		return NULL;
	}
	end = val + strlen(val);
	for (b = val, count = 0; b < end;)
	{
		for (e = b; ((*e != ' ') && (*e != '\0')); e++);
		*e = '\0';
		if (e != b)
		{
			count++;
		}
		b = e + 1;
	}
	if (count == 0)
	{
		free(val);
		return NULL;
	}
	ret = (char **)malloc((count+1) * sizeof(char *));

	for (b = val, count = 0; b < end; )
	{
		for (e = b; (*e != '\0'); e++);
		if (e != b)
		{
			ret[count++] = strdupnull(b);
		}
		b = e + 1;
	}
	ret[count] = NULL;
	free(val);
	return ret;
}


/*
 * assigns an argument value to a struct field
 */
bool assign_arg(kw_token_t token, kw_token_t first, kw_list_t *kw, char *base,
				bool *assigned)
{
	char *p = base + token_info[token].offset;
	const char **list = token_info[token].list;

	int index = -1;  /* used for enumeration arguments */

	seen_t *seen = (seen_t*)base; /* seen flags are at the top of the struct */

	*assigned = FALSE;

	DBG3(DBG_APP, "  %s=%s", kw->entry->name, kw->value);

	if (*seen & SEEN_KW(token, first))
	{
		DBG1(DBG_APP, "# duplicate '%s' option", kw->entry->name);
		return FALSE;
	}

	if (token == KW_ESP || token == KW_AH)
	{
		if (*seen & (SEEN_KW(KW_ESP, first) | SEEN_KW(KW_AH, first)))
		{
			DBG1(DBG_APP, "# can't have both 'ah' and 'esp' options");
			return FALSE;
		}
	}

	/* set flag that this argument has been seen */
	*seen |= SEEN_KW(token, first);

	/* is there a keyword list? */
	if (list != NULL && token_info[token].type != ARG_LST)
	{
		bool match = FALSE;

		while (*list != NULL && !match)
		{
			index++;
			match = streq(kw->value, *list++);
		}
		if (!match)
		{
			DBG1(DBG_APP, "# bad value: %s=%s", kw->entry->name, kw->value);
			return FALSE;
		}
	}

	switch (token_info[token].type)
	{
	case ARG_NONE:
		DBG1(DBG_APP, "# option '%s' not supported yet", kw->entry->name);
		return FALSE;
	case ARG_ENUM:
		{
			if (index < 0)
			{
				DBG1(DBG_APP, "# bad enumeration value: %s=%s (%d)",
					 kw->entry->name, kw->value, index);
				return FALSE;
			}

			if (token_info[token].list == LST_bool)
			{
				bool *b = (bool *)p;
				*b = (index > 0);
			}
			else
			{
				int *i = (int *)p;
				*i = index;
			}
		}
		break;

	case ARG_UINT:
		{
			char *endptr;
			u_int *u = (u_int *)p;

			*u = strtoul(kw->value, &endptr, 10);

			if (*endptr != '\0')
			{
				DBG1(DBG_APP, "# bad integer value: %s=%s", kw->entry->name,
					 kw->value);
				return FALSE;
			}
		}
		break;
	case ARG_ULNG:
	case ARG_PCNT:
		{
			char *endptr;
			unsigned long *l = (unsigned long *)p;

			*l = strtoul(kw->value, &endptr, 10);

			if (token_info[token].type == ARG_ULNG)
			{
				if (*endptr != '\0')
				{
					DBG1(DBG_APP, "# bad integer value: %s=%s", kw->entry->name,
						 kw->value);
					return FALSE;
				}
			}
			else
			{
				if ((*endptr != '%') || (endptr[1] != '\0') || endptr == kw->value)
				{
					DBG1(DBG_APP, "# bad percent value: %s=%s", kw->entry->name,
						 kw->value);
					return FALSE;
				}
			}

		}
		break;
	case ARG_ULLI:
		{
			char *endptr;
			unsigned long long *ll = (unsigned long long *)p;

			*ll = strtoull(kw->value, &endptr, 10);

			if (*endptr != '\0')
			{
				DBG1(DBG_APP, "# bad integer value: %s=%s", kw->entry->name,
					 kw->value);
				return FALSE;
			}
		}
		break;
	case ARG_UBIN:
		{
			char *endptr;
			u_int *u = (u_int *)p;

			*u = strtoul(kw->value, &endptr, 2);

			if (*endptr != '\0')
			{
				DBG1(DBG_APP, "# bad binary value: %s=%s", kw->entry->name,
					 kw->value);
				return FALSE;
			}
		}
		break;
	case ARG_TIME:
		{
			char *endptr;
			time_t *t = (time_t *)p;

			*t = strtoul(kw->value, &endptr, 10);

			/* time in seconds? */
			if (*endptr == '\0' || (*endptr == 's' && endptr[1] == '\0'))
			{
				break;
			}
			if (endptr[1] == '\0')
			{
				if (*endptr == 'm')  /* time in minutes? */
				{
					*t *= 60;
					break;
				}
				if (*endptr == 'h')  /* time in hours? */
				{
					*t *= 3600;
					break;
				}
				if (*endptr == 'd')  /* time in days? */
				{
					*t *= 3600*24;
					break;
				}
			}
			DBG1(DBG_APP, "# bad duration value: %s=%s", kw->entry->name,
				 kw->value);
			return FALSE;
		}
	case ARG_STR:
		{
			char **cp = (char **)p;

			/* free any existing string */
			free(*cp);

			/* assign the new string */
			*cp = strdupnull(kw->value);
		}
		break;
	case ARG_LST:
		{
			char ***listp = (char ***)p;

			/* free any existing list */
			if (*listp != NULL)
			{
				free_list(*listp);
			}
			/* create a new list and assign values */
			*listp = new_list(kw->value);

			/* is there a keyword list? */
			if (list != NULL)
			{
				char ** lst;

				for (lst = *listp; lst && *lst; lst++)
				{
					bool match = FALSE;

					list = token_info[token].list;

					while (*list != NULL && !match)
					{
						match = streq(*lst, *list++);
					}
					if (!match)
					{
						DBG1(DBG_APP, "# bad value: %s=%s",
							 kw->entry->name, *lst);
						return FALSE;
					}
				}
			}
		}
		/* fall through */
	default:
		return TRUE;
	}

	*assigned = TRUE;
	return TRUE;
}

/*
 *  frees all dynamically allocated arguments in a struct
 */
void free_args(kw_token_t first, kw_token_t last, char *base)
{
	kw_token_t token;

	for (token = first; token <= last; token++)
	{
		char *p = base + token_info[token].offset;

		switch (token_info[token].type)
		{
		case ARG_STR:
			{
				char **cp = (char **)p;

				free(*cp);
				*cp = NULL;
			}
			break;
		case ARG_LST:
			{
				char ***listp = (char ***)p;

				if (*listp != NULL)
				{
					free_list(*listp);
					*listp = NULL;
				 }
			}
			break;
		default:
			break;
		}
	}
}

/*
 *  clone all dynamically allocated arguments in a struct
 */
void clone_args(kw_token_t first, kw_token_t last, char *base1, char *base2)
{
	kw_token_t token;

	for (token = first; token <= last; token++)
	{
		if (token_info[token].type == ARG_STR)
		{
			char **cp1 = (char **)(base1 + token_info[token].offset);
			char **cp2 = (char **)(base2 + token_info[token].offset);

			*cp1 = strdupnull(*cp2);
		}
	}
}

static bool cmp_list(char **list1, char **list2)
{
	if ((list1 == NULL) && (list2 == NULL))
	{
		return TRUE;
	}
	if ((list1 == NULL) || (list2 == NULL))
	{
		return FALSE;
	}

	for ( ; *list1 && *list2; list1++, list2++)
	{
		if (strcmp(*list1,*list2) != 0)
		{
			return FALSE;
		}
	}

	if ((*list1 != NULL) || (*list2 != NULL))
	{
		return FALSE;
	}

	return TRUE;
}

/*
 *  compare all arguments in a struct
 */
bool cmp_args(kw_token_t first, kw_token_t last, char *base1, char *base2)
{
	kw_token_t token;

	for (token = first; token <= last; token++)
	{
		char *p1 = base1 + token_info[token].offset;
		char *p2 = base2 + token_info[token].offset;

		switch (token_info[token].type)
		{
		case ARG_ENUM:
			if (token_info[token].list == LST_bool)
			{
				bool *b1 = (bool *)p1;
				bool *b2 = (bool *)p2;

				if (*b1 != *b2)
				{
					return FALSE;
				}
			}
			else
			{
				int *i1 = (int *)p1;
				int *i2 = (int *)p2;

				if (*i1 != *i2)
				{
					return FALSE;
				}
			}
			break;
		case ARG_UINT:
			{
				u_int *u1 = (u_int *)p1;
				u_int *u2 = (u_int *)p2;

				if (*u1 != *u2)
				{
					return FALSE;
				}
			}
			break;
		case ARG_ULNG:
		case ARG_PCNT:
			{
				unsigned long *l1 = (unsigned long *)p1;
				unsigned long *l2 = (unsigned long *)p2;

				if (*l1 != *l2)
				{
					return FALSE;
				}
			}
			break;
		case ARG_ULLI:
			{
				unsigned long long *ll1 = (unsigned long long *)p1;
				unsigned long long *ll2 = (unsigned long long *)p2;

				if (*ll1 != *ll2)
				{
					return FALSE;
				}
			}
			break;
		case ARG_TIME:
			{
				time_t *t1 = (time_t *)p1;
				time_t *t2 = (time_t *)p2;

				if (*t1 != *t2)
				{
					return FALSE;
				}
			}
			break;
		case ARG_STR:
			{
				char **cp1 = (char **)p1;
				char **cp2 = (char **)p2;

				if (*cp1 == NULL && *cp2 == NULL)
				{
					break;
				}
				if (*cp1 == NULL || *cp2 == NULL || strcmp(*cp1, *cp2) != 0)
				{
					return FALSE;
				}
			}
			break;
		case ARG_LST:
			{
				char ***listp1 = (char ***)p1;
				char ***listp2 = (char ***)p2;

				if (!cmp_list(*listp1, *listp2))
				{
					return FALSE;
				}
			}
			break;
		default:
			break;
		}
	}
	return TRUE;
}