/*
 * Copyright (c) 2009
 *  AVM GmbH, Berlin, Germany
 *
 * License: Free, use with no restriction.
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

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

#include "usermap.h"

enum parse_state { LEADING_SPACES, OPTIONAL_LEADING_QUOTE, IN_NAME, TRAILING_SPACES, COLON, NAME_COMPLETE, PARSE_ERROR };



static void EnumUserMaps(void *ctx, int (*callback)(void *ctx, char *friendly_name, char *linux_name))
{
	FILE *fp = fopen("/var/tmp/users.map", "rt");
	if (!fp) return;

	char *linux_name = 0;

	char buf[128];
	while(buf == fgets(buf, sizeof(buf)-1, fp)) {
		char *start;
		buf[sizeof(buf)-1] = '\0';

		if (buf[0] == '#' || buf[0] == ';') continue; // comment line
		// have to skip ! if used for smbd username map option
		if (buf[0] == '!') start = buf + 1; else
			start = buf;
		char *p = strchr(start, '=');
		if (!p) continue;

		char *p2 = p-1;
		while(p2 > start && isspace(*p2)) p2--;
		if (p2 < start) continue;
		if (linux_name) free(linux_name);
		linux_name = (char *)calloc(1, p2-start+1+1);
		if (!linux_name) continue;
		memcpy(linux_name, start, p2-start+1);
		linux_name[p2-start+1] = '\0';

		p++;
		char *name_start = NULL, *name_end = NULL;
		enum parse_state state = LEADING_SPACES;
		short bQuoted = 0;
		while(state != PARSE_ERROR) {
			if ((state != IN_NAME || bQuoted) && (*p == '\0' || *p == '\r' || *p == '\n')) break; // while
			switch(state) {
			case LEADING_SPACES:
				while(isspace(*p)) p++;
				state = OPTIONAL_LEADING_QUOTE;
				break;
			case OPTIONAL_LEADING_QUOTE:
				if (*p == '\"') { bQuoted = 1;  p++;
					name_start = p;
				} else {
					name_start = p++;
				}
				state = IN_NAME;
				break;
			case IN_NAME:
				if (bQuoted) {
					if (*p == '\"') {
						name_end = p ;
						state = NAME_COMPLETE;
						bQuoted = 0;
					}
					p++;
				} else {
					if (isspace(*p) || *p == ',' || *p == '\0' || *p ==  '\r' ||*p == '\n') {
						name_end = p;
						state = NAME_COMPLETE;
					} else p++;
				}
				break;
			case TRAILING_SPACES:
				while(isspace(*p)) p++;
				state = COLON;
				break;
			case COLON:
				if (*p != ',') state = PARSE_ERROR; // parse_error
				else { p++;
					state = LEADING_SPACES;
				}
				break;
			} // switch

			if (state == NAME_COMPLETE) {
				state = TRAILING_SPACES;
				if (name_end > name_start) {
					char *friendly_name = (char *)calloc(1, name_end - name_start + 1);
					if (friendly_name) {
						memcpy(friendly_name, name_start, name_end - name_start);
						friendly_name[name_end - name_start] = '\0';
						int stop = callback(ctx, friendly_name, linux_name);
						free(friendly_name);
						if (stop) goto end;
					}
				}
			}
		} // while
	} // while

end:
	fclose(fp);
	if (linux_name) free(linux_name);
}

struct mapuser_ctx {
	const char *name;
	char *map_buffer;
	size_t map_size;
	short found;
};

static int mapuser_enum(void *context, char *friendly_name, char *linux_name)
{
	struct mapuser_ctx *ctx = (struct mapuser_ctx *)context;

	// * matches every ctx->name
	if (strcmp(friendly_name, "*")) {
		if (strcasecmp(ctx->name, friendly_name)) return 0;
	}

	strncpy(ctx->map_buffer, linux_name, ctx->map_size);
	ctx->map_buffer[ctx->map_size-1] = '\0';
	ctx->found = 1;
	return 1;
}

const char *usermap_mapuser(const char *name, char *map_buffer, size_t map_size)
{
	struct mapuser_ctx ctx;

	ctx.name = name;
	ctx.map_buffer = map_buffer;
	ctx.map_size = map_size;
	ctx.found = 0;

	EnumUserMaps((void *)&ctx, mapuser_enum);

	if (ctx.found) return map_buffer;
	else return name;

}

struct find_userid_ctx {
	int found;
	unsigned uid;
	char **pusername;
};

static int find_enabled_userid_enum(void *context, char *friendly_name, char *linux_name)
{
	struct find_userid_ctx *ctx = (struct find_userid_ctx *)context;

	char name[20];
	snprintf(name, sizeof(name), "boxusr%u", ctx->uid);

	if (!strcmp(linux_name, name)) {
		if (ctx->pusername) *(ctx->pusername) = strdup(friendly_name);
		ctx->found = 1;
		return 1;
	}
	return 0;
}


int usermap_is_compatibility_mode(void)
{
	struct find_userid_ctx ctx;
	ctx.found = 0;
	ctx.uid = 100;
	ctx.pusername = 0;

	EnumUserMaps((void *)&ctx, find_enabled_userid_enum);
	return ctx.found;
}

int usermap_is_skip_auth_from_homenetwork(char **pskip_username)
{
	struct find_userid_ctx ctx;
	ctx.found = 0;
	ctx.uid = 101;
	ctx.pusername = pskip_username;

	EnumUserMaps((void *)&ctx, find_enabled_userid_enum);
	return ctx.found;
}