/*
 * Amanda, The Advanced Maryland Automatic Network Disk Archiver
 * Copyright (c) 2013-2016 Carbonite, Inc.  All Rights Reserved.
 * All Rights Reserved.
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of U.M. not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  U.M. makes no representations about the
 * suitability of this software for any purpose.  It is provided "as is"
 * without express or implied warranty.
 *
 */
/*
 */
#include "amanda.h"
#include "amutil.h"
#include "ammessage.h"
#include "security-file.h"

#define LINE_SIZE 1024

static
message_t *
open_security_file(FILE **file)
{
    message_t *message;
    if ((message = check_security_file_permission_message())) {
	return message;
    }

    *file = fopen(DEFAULT_SECURITY_FILE, "r");
    if (!*file) {
	return build_message(
		AMANDA_FILE, __LINE__, 3600095, MSG_ERROR, 2,
		"security_file", DEFAULT_SECURITY_FILE,
		"errno"        , errno);
    }

    return NULL;
}

static
message_t *
security_file_check_path(
    char *prefix,
    char *path)
{
    FILE *sec_file;
    char *iprefix;
    char *p, *l;
    char line[LINE_SIZE];
    gboolean found = FALSE;
    message_t *message;

    if (!prefix) {
	return build_message(
		AMANDA_FILE, __LINE__, 3600093, MSG_ERROR, 0);
    }
    if (!path) {
	return build_message(
		AMANDA_FILE, __LINE__, 3600094, MSG_ERROR, 0);
    }

    message = open_security_file(&sec_file);
    if (message) {
	return message;
    }


    iprefix = g_strdup(prefix);
    for (p = iprefix; *p; ++p) *p = tolower(*p);

    while (fgets(line, LINE_SIZE, sec_file)) {
	char *p = strchr(line, '=');
	int len = strlen(line);
	if (len == 0) continue;
	if (*line == '#') continue;
	if (line[len-1] == '\n')
	    line[len-1] = '\0';
	if (p) {
	    *p = '\0';
	    p++;
	    for (l = line; *l; ++l) *l = tolower(*l);
	    if (g_str_equal(iprefix, line)) {
		found = TRUE;
		if (g_str_equal(path, p)) {
		    g_free(iprefix);
		    fclose(sec_file);
		    return NULL;
		}
	    }
	}
    }

    if (!found) { /* accept the configured path */
	if ((strcmp(iprefix,"amgtar:gnutar_path") == 0 &&
	     strcmp(path,GNUTAR) == 0) ||
	    (strcmp(iprefix,"ambsdtar:bsdtar_path") == 0 &&
	     strcmp(path,BSDTAR) == 0) ||
	    (strcmp(iprefix,"amstar:star_path") == 0 &&
	     strcmp(path,STAR) == 0) ||
	    (strcmp(iprefix,"runtar:gnutar_path") == 0 &&
	     strcmp(path,GNUTAR) == 0)) {
	    g_free(iprefix);
	    fclose(sec_file);
	    return NULL;
	}
    }

    message = build_message(
		AMANDA_FILE, __LINE__, 3600096, MSG_ERROR, 3,
		"security_file", DEFAULT_SECURITY_FILE,
		"prefix", iprefix,
		"path"  , path);
    g_free(iprefix);
    fclose(sec_file);
    return message;
}

static gboolean
security_file_get_boolean(
    char *name)
{
    FILE *sec_file;
    char *iname;
    char *n, *l;
    char line[LINE_SIZE];
    char oline[LINE_SIZE];
    message_t *message;

    message = open_security_file(&sec_file);
    if (message) {
	fprintf(stderr, "%s\n", get_message(message));
	return FALSE;
    }

    if (!sec_file) {
	fprintf(stderr, "No sec_file\n");
	return FALSE;
    }

    iname = g_strdup(name);
    for (n = iname; *n; ++n) *n = tolower(*n);

    while (fgets(line, LINE_SIZE, sec_file)) {
	char *p;
	int len = strlen(line);
	if (len == 0) continue;
	if (*line == '#') continue;
	if (line[len-1] == '\n')
	    line[len-1] = '\0';
	strcpy(oline, line);
	p = strchr(line, '=');
	if (p) {
	    *p = '\0';
	    p++;
	    for (l = line; *l; ++l) *l = tolower(*l);
	    if (g_str_equal(iname, line)) {
		if (g_str_equal(p, "YES") ||
		    g_str_equal(p, "yes")) {
		    g_free(iname);
		    fclose(sec_file);
		    return TRUE;
		}
		if (g_str_equal(p, "NO") ||
		    g_str_equal(p, "no")) {
		    g_free(iname);
		    fclose(sec_file);
		    return FALSE;
		}
		error("BOGUS line '%s' in " DEFAULT_SECURITY_FILE " file", oline);
	    }
	}
    }

    g_free(iname);
    fclose(sec_file);
    return FALSE;
}

static gboolean
security_file_get_portrange(
    char *name, int *plow, int *phigh)
{
    FILE *sec_file;
    char *iname;
    char *n, *l;
    char line[LINE_SIZE];
    char oline[LINE_SIZE];
    message_t *message;

    *plow = -1;
    *phigh = -1;
    message = open_security_file(&sec_file);
    if (message) {
	fprintf(stderr, "%s\n", get_message(message));
	return FALSE;
    }

    if (!sec_file) {
	fprintf(stderr, "No sec_file\n");
	return FALSE;
    }

    iname = g_strdup(name);
    for (n = iname; *n; ++n) *n = tolower(*n);

    while (fgets(line, LINE_SIZE, sec_file)) {
	char *p;
	int len = strlen(line);
	if (len == 0) continue;
	if (*line == '#') continue;
	if (line[len-1] == '\n')
	    line[len-1] = '\0';
	strcpy(oline, line);
	p = strchr(line, '=');
	if (p) {
	    *p = '\0';
	    p++;
	    for (l = line; *l; ++l) *l = tolower(*l);
	    if (g_str_equal(iname, line)) {
		char *shigh = strchr(p, ',');
		if (shigh) {
		    shigh++;
		    *plow = atoi(p);
		    *phigh = atoi(shigh);
		    g_free(iname);
		    fclose(sec_file);
		    return TRUE;
		}
		error("BOGUS line '%s' in " DEFAULT_SECURITY_FILE " file", oline);
	    }
	}
    }

    g_free(iname);
    fclose(sec_file);
    return FALSE;
}

static message_t * check_security_file_permission_message_recursive(
     char *security_real_path, char *security_orig);

message_t *
check_security_file_permission_message(void)
{
    char  security_real_path[PATH_MAX];
    char *sec_real_path;

#ifdef SINGLE_USERID
    uid_t ruid = getuid();
    uid_t euid = geteuid();

    if (ruid != 0 && euid != 0 && ruid == euid) {
	return NULL;
    }
#endif

    sec_real_path = realpath(DEFAULT_SECURITY_FILE, security_real_path);
    if (!sec_real_path) {
	return build_message(
		AMANDA_FILE, __LINE__, 3600097, MSG_ERROR, 2,
		"errno", errno,
		"security_file", DEFAULT_SECURITY_FILE);
    }

    if (EUIDACCESS(security_real_path, R_OK) == -1) {
	char  ruid_str[NUM_STR_SIZE];
	char  euid_str[NUM_STR_SIZE];

	g_snprintf(ruid_str, sizeof(ruid_str), "%d", (int)getuid());
	g_snprintf(euid_str, sizeof(euid_str), "%d", (int)geteuid());

	return build_message(
		AMANDA_FILE, __LINE__, 3600063, MSG_ERROR, 5,
		"errno", errno,
		"noun", "access",
		"filename", security_real_path,
		"ruid", ruid_str,
		"euid", euid_str);
    }
    return check_security_file_permission_message_recursive(security_real_path, DEFAULT_SECURITY_FILE);
}

static
message_t *
check_security_file_permission_message_recursive(
    char *security_real_path,
    char *security_orig)
{
    struct stat stat_buf;
    char *s;

    if (!stat(security_real_path, &stat_buf)) {
        if (stat_buf.st_uid != 0 ) {
	    return build_message(
		AMANDA_FILE, __LINE__, 3600088, MSG_ERROR, 2,
		"filename", security_real_path,
		"security_orig", security_orig);
        }
        if (stat_buf.st_mode & S_IWOTH) {
	    return build_message(
		AMANDA_FILE, __LINE__, 3600089, MSG_ERROR, 2,
		"filename", security_real_path,
		"security_orig", security_orig);
        }
        if (stat_buf.st_mode & S_IWGRP) {
	    return build_message(
		AMANDA_FILE, __LINE__, 3600090, MSG_ERROR, 2,
		"filename", security_real_path,
		"security_orig", security_orig);
        }
    }
    else {
	return build_message(
		AMANDA_FILE, __LINE__, 3600098, MSG_ERROR, 3,
		"errno", errno,
		"filename", security_real_path,
		"security_orig", security_orig);
    }

    if ((s = strrchr(security_real_path, '/'))) {
	*s = '\0';
	if (*security_real_path) {
	    return check_security_file_permission_message_recursive(security_real_path, security_orig);
	}
    }
    return NULL;
}

message_t *
security_allow_program_as_root(
    char *name,
    char *path)
{
    message_t *message;
    char *prefix;

    prefix = g_strdup_printf("%s:%s", get_pname(), name);

    message = security_file_check_path(prefix, path);
    g_free(prefix);
    return message;
}

gboolean
security_allow_to_restore(void)
{
    uid_t ruid = getuid();
    uid_t euid = geteuid();
    struct passwd *pw;

    /* non-root can do restore as non-root */
    if (ruid != 0 && euid != 0 && ruid == euid) {
	return TRUE;
    }

    /* root can do a restore */
    if (ruid == 0 && euid == 0)
	return TRUE;

    if ((pw = getpwnam(CLIENT_LOGIN)) == NULL) {
	return FALSE;
    }
    if (euid == pw->pw_uid) {
	return security_file_get_boolean("restore_by_amanda_user");
    } else {
	return FALSE;
    }
}

gboolean
security_allow_bind(
    int s,
    sockaddr_union *addr)
{

    int port;
    int type;
    char *proto;
    socklen_t_equiv length = sizeof(type);
    struct servent *result;

    port = SU_GET_PORT(addr);
    if (getsockopt(s, SOL_SOCKET, SO_TYPE, &type, &length) == -1) {
	fprintf(stderr, "getsockopt failed: %s", strerror(errno));
	return FALSE;
    }

    if (type == SOCK_STREAM) {
	proto = "tcp";
    } else if (type == SOCK_DGRAM) {
	proto = "udp";
    } else {
	fprintf(stderr, "Wrong socket type: %d\n", type);
	return FALSE;
    }
    result = getservbyport((int)htons(port), proto);
    if (result && !strstr(result->s_name, AMANDA_SERVICE_NAME)) {
	fprintf(stderr, "port %d is owned by %s", port, result->s_name);
	return FALSE;
    }

    if (type == SOCK_STREAM) {
	int low, high;
	if (security_file_get_portrange("tcp_port_range", &low, &high)) {
	    if (low <= port && port <= high) {
		return TRUE;
	    } else {
		fprintf(stderr, "tcp port out of range (%d <= %d <= %d)\n", low, port, high);
		return FALSE;
	    }
	} else {
	    // use LOW_TCPPORTRANGE
#if defined LOW_TCPPORTRANGE && defined LOW_TCPPORTRANGE_MIN && defined LOW_TCPPORTRANGE_MAX
	    low = LOW_TCPPORTRANGE_MIN;
	    high = LOW_TCPPORTRANGE_MAX;
	    if (low <= port && port <= high) {
		return TRUE;
	    } else {
		fprintf(stderr, "tcp port out of range (%d <= %d <= %d)\n", low, port, high);
		return FALSE;
	    }
#else
	    fprintf(stderr, "No defined tcp_port_range in '%s'\n", DEFAULT_SECURITY_FILE);
	    return FALSE;
#endif
	}
    } else  { //(type == SOCK_DGRAM)
	int low, high;
	if (security_file_get_portrange("udp_port_range", &low, &high)) {
	    if (low <= port && port <= high) {
		return TRUE;
	    } else {
		fprintf(stderr, "udp port out of range (%d <= %d <= %d)\n", low, port, high);
		return FALSE;
	    }
	} else {
	    // use UDPPORTRANGE
#if defined UDPPORTRANGE && defined UDPPORTRANGE_MIN && defined UDPPORTRANGE_MAX
	    low = UDPPORTRANGE_MIN;
	    high = UDPPORTRANGE_MAX;
	    if (low <= port && port <= high) {
		return TRUE;
	    } else {
		fprintf(stderr, "udp port out of range (%d <= %d <= %d)\n", low, port, high);
		return FALSE;
	    }
#else
	    fprintf(stderr, "No defined udp_port_range in '%s'\n", DEFAULT_SECURITY_FILE);
	    return FALSE;
#endif
	}
    }
}

