/*
 * tcpwrap.c
 * Copyright (C) 2000  Shugo Maeda <shugo@ruby-lang.org>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "ruby.h"
#include "rubyio.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <tcpd.h>
#ifdef HAVE_IDENT_H
#include <ident.h>
#endif
#if defined(HAVE_UNISTD_H)
# include <unistd.h>
#endif 
#if defined(HAVE_SYS_PARAM_H)
# include <sys/param.h>
#else
# define MAXPATHLEN 1024
#endif

#define DEFAULT_RFC1413_QUERY_TIMEOUT 30

int allow_severity = 0;
int deny_severity = 0;

static VALUE cTCPServer;
static VALUE eSocket;
static VALUE cTCPWrapper;
static VALUE eTCPWrapError;

typedef struct tcp_wrapper {
    VALUE daemon;
    VALUE server;
    int ident_lookup;
    int ident_timeout;
} tcp_wrapper_t;

static void mark_tcpd(tcp_wrapper_t *tcpd)
{
    rb_gc_mark(tcpd->daemon);
    rb_gc_mark(tcpd->server);
}

static void free_tcpd(tcp_wrapper_t *tcpd)
{
    free(tcpd);
}

static VALUE tcpd_s_new(int argc, VALUE *argv, VALUE self)
{
    VALUE obj, daemon, server, ident_lookup, ident_timeout;
    int to;
    tcp_wrapper_t *tcpd;

    rb_scan_args(argc, argv, "22", &daemon, &server, &ident_lookup, &ident_timeout);
    if (argc == 4) {
	to = NUM2INT(ident_timeout);
    }
    else {
	to = DEFAULT_RFC1413_QUERY_TIMEOUT;
    }
    Check_Type(daemon, T_STRING);
    if (!rb_obj_is_kind_of(server, cTCPServer))
	rb_raise(rb_eTypeError, "expecting TCPServer");
    obj = Data_Make_Struct(cTCPWrapper, tcp_wrapper_t, mark_tcpd, free_tcpd, tcpd);
    tcpd->daemon = daemon;
    tcpd->server = server;
    tcpd->ident_lookup = RTEST(ident_lookup);
    tcpd->ident_timeout = to;
    return obj;
}

static VALUE tcpd_accept(VALUE self)
{
    tcp_wrapper_t *tcpd;
    VALUE sock;
    int sockfd;
    OpenFile *fptr;
    struct sockaddr_storage addr;
    char client_name[NI_MAXHOST] = STRING_UNKNOWN;
    char client_addr[NI_MAXHOST] = STRING_UNKNOWN;
    char *client_user = NULL;
    int len = sizeof(addr);
    int error;

    Data_Get_Struct(self, tcp_wrapper_t, tcpd);
 again:
    sock = rb_funcall(tcpd->server, rb_intern("accept"), 0);
    GetOpenFile(sock, fptr);
    sockfd = fileno(fptr->f);
    if (getpeername(sockfd, (struct sockaddr*) &addr, &len) < 0)
	rb_sys_fail("getpeername(2)");
    error = getnameinfo((struct sockaddr*) &addr, len,
			client_addr, sizeof(client_addr),
			NULL, 0, NI_NUMERICHOST);
    if (error) {
	rb_raise(eSocket, "getnameinfo: %s", gai_strerror(error));
    }
    error = getnameinfo((struct sockaddr*) &addr, len,
			client_name, sizeof(client_name),
			NULL, 0, NI_NAMEREQD);
    if (error) {
	rb_raise(eSocket, "getnameinfo: %s", gai_strerror(error));
    }
#ifdef HAVE_IDENT_ID
    if (tcpd->ident_lookup)
	client_user = ident_id(sockfd, tcpd->ident_timeout);
#endif
    if (!hosts_ctl(RSTRING(tcpd->daemon)->ptr,
		   client_name,
		   client_addr,
		   (client_user == NULL) ? STRING_UNKNOWN : client_user)) {
	rb_funcall(sock, rb_intern("shutdown"), 0);
	rb_funcall(sock, rb_intern("close"), 0);
#ifdef HAVE_IDENT_ID
	if (client_user)
	    free(client_user);
#endif
	goto again;
    }
#ifdef HAVE_IDENT_ID
	if (client_user)
	    free(client_user);
#endif
    return sock;
}

static VALUE
tcpd_s_set_hosts_allow(VALUE self, VALUE s)
{
    static char hosts_allow[MAXPATHLEN];

    Check_SafeStr(s);
    snprintf(hosts_allow, sizeof(hosts_allow), "%s", RSTRING(s)->ptr);
    if(access(hosts_allow, R_OK) < 0)
	rb_warning("cannot read %s", hosts_allow);
    hosts_allow_table = hosts_allow;

    return s;
}

static VALUE
tcpd_s_get_hosts_allow(VALUE self)
{
    return rb_str_new2(hosts_allow_table);
}

static VALUE
tcpd_s_set_hosts_deny(VALUE self, VALUE s)
{
    static char hosts_deny[MAXPATHLEN];

    Check_SafeStr(s);
    snprintf(hosts_deny, sizeof(hosts_deny), "%s", RSTRING(s)->ptr);
    if(access(hosts_deny, R_OK) < 0)
	rb_warning("cannot read %s", hosts_deny);
    hosts_deny_table = hosts_deny;

    return s;
}

static VALUE
tcpd_s_get_hosts_deny(VALUE self)
{
    return rb_str_new2(hosts_deny_table);
}

static char*
str_to_ctlstr(VALUE s)
{
    if(NIL_P(s)) return STRING_UNKNOWN;
    Check_Type(s, T_STRING);
    return RSTRING(s)->ptr;
}

static VALUE
tcpd_s_hosts_ctl(VALUE self, VALUE daemon, VALUE name, VALUE addr, VALUE user)
{
    if (!hosts_ctl(str_to_ctlstr(daemon), str_to_ctlstr(name),
		   str_to_ctlstr(addr), str_to_ctlstr(user))){
	rb_raise(eTCPWrapError, "access denied.");
    }

    return Qnil;
}

void Init_tcpwrap()
{
    rb_require("socket");
    cTCPServer = rb_const_get(rb_cObject, rb_intern("TCPServer"));
    eSocket = rb_const_get(rb_cObject, rb_intern("SocketError"));
    cTCPWrapper = rb_define_class("TCPWrapper", rb_cObject);
    eTCPWrapError = rb_define_class("TCPWrapError", rb_eStandardError);
    rb_define_singleton_method(cTCPWrapper, "new", tcpd_s_new, -1);
    rb_define_method(cTCPWrapper, "accept", tcpd_accept, 0);
    rb_define_singleton_method(cTCPWrapper, "hosts_allow=", tcpd_s_set_hosts_allow, 1);
    rb_define_singleton_method(cTCPWrapper, "hosts_allow", tcpd_s_get_hosts_allow, 0);
    rb_define_singleton_method(cTCPWrapper, "hosts_deny=", tcpd_s_set_hosts_deny, 1);
    rb_define_singleton_method(cTCPWrapper, "hosts_deny", tcpd_s_get_hosts_deny, 0);
    rb_define_singleton_method(cTCPWrapper, "hosts_ctl", tcpd_s_hosts_ctl, 4);
}
