/*
Copyright 1990-2001 Sun Microsystems, Inc. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions: The above copyright notice and this
permission notice shall be included in all copies or substantial
portions of the Software.


THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE OPEN GROUP OR SUN MICROSYSTEMS, INC. BE LIABLE
FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH
THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE EVEN IF
ADVISED IN ADVANCE OF THE POSSIBILITY OF SUCH DAMAGES.


Except as contained in this notice, the names of The Open Group and/or
Sun Microsystems, Inc. shall not be used in advertising or otherwise to
promote the sale, use or other dealings in this Software without prior
written authorization from The Open Group and/or Sun Microsystems,
Inc., as applicable.


X Window System is a trademark of The Open Group

OSF/1, OSF/Motif and Motif are registered trademarks, and OSF, the OSF
logo, LBX, X Window System, and Xinerama are trademarks of the Open
Group. All other trademarks and registered trademarks mentioned herein
are the property of their respective owners. No right, title or
interest in or to any trademark, service mark, logo or trade name of
Sun Microsystems, Inc. or its licensors is granted.

*/

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

#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <alloca.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>

#ifdef HAVE_GETHOSTNAME
#include <unistd.h>
#endif

#include "IMAuth.hh"
#include "IMLog.hh"

#include "iwrap.h"

const int init_auth_entry_slot_num = 10;
const int addr_elements_maxnum = (32 / 8);
const int addr6_elements_maxnum = (128 / 16);
const char* domainname_symbol_name = "DOMAINNAME";
const char* hostname_symbol_name = "HOSTNAME";

int
compare_hostname(
    const char *h1,
    const char *h2
)
{
    while (*h1 && *h2) {
	if (tolower(*h1) != tolower(*h2)) return 0;
	h1++;
	h2++;
    }
    if ((*h1 == '\0') && (*h2 == '\0'))
	return 1;

    return 0;
}

int
IMAuth::adjust_pae_slot_size(
    int num
)
{
    alloced_ae_num = num;
    pae = (auth_entry*) realloc(pae, sizeof(auth_entry) * alloced_ae_num);
    if (!pae) return 0;
    return 1;
}

IMAuth::pattern_type
IMAuth::check_pattern_type(
    const char *pat
)
{
    const char *p;
    int i, bs;

    i = 1;
    bs = 0;
    for (p = pat; *p; p++) {
	if (isdigit(*p)) continue;
	if (bs == 1) {
	    i = 0;
	    break;
	}
	if (*p == '.') i++;
	else if (*p == '/')  bs = 1;
	else {
	    i = 0;
	    break;
	}
    }
    if (i == 4) return ADDR;

    {
	int f0 = 0;
	i = 1;
	for (p = pat; *p; p++) {
	    if (bs == 1) {
		if (isdigit(*p)) continue;
	    } else {
		if (isalnum(*p))  continue;
		if (*p == ':') {
		    if ((p > pat)
			&& p[-1] == ':') {
			if (f0) break;
			f0 = 1;
		    }
		    i++;
		} else if (*p == '/')  {
		    bs = 1;
		}
	    }
	    i = 0;
	    break;
	}
	if ((i == 16)
	    || (f0 && (i >= 2) && (i <= 16)))
	    return ADDR6;
    }

  return HOSTNAME;
}

int
IMAuth::
addr_element_to_number(
    const char **addr
)
{
    int i, n;
    const char *q;
    char *qn;
    char an[4];

    i = 0;
    q = *addr;
    while (*q) {
	if ((*q == '.') || (*q == '/'))	{
	    q++;
	    break;
	}
	if (i >= 3) {
	    *addr = q;
	    return -1;
	}
	an[i++] = *q;
	q++;
    }
    if (i > 0) {
	an[i] = '\0';
	n = strtol(an, &qn, 10);
	if (*qn != '\0') return -1;
    } else {
	return -1;
    }

    *addr = q;
    return n;
}

int
IMAuth::
store_addr_pattern(
    const char *pattern, 
    int *addr_pattern
)
{
    int i, n;
    int bl;
    const char *p;
    char *pn;

    p = pattern;
    for (i = 0; i < addr_elements_maxnum; i++) {
	n = addr_element_to_number(&p);
	if (n < 0) return -1;
	addr_pattern[i] = n;
    }
    if ((p > pattern)
	&& (p[-1] == '/')) {
	bl = strtol(p, &pn, 10);
	if (*pn != '\0') return -1;
	return bl;
    }

    return i * 8;
}

int
IMAuth::
addr6_element_to_number(
    const char **addr
)
{
    int i, n;
    const char *q;
    char *qn;
    char an[5];

    i = 0;
    q = *addr;
    if (*q == ':') {
	q++;
	if (*q == ':') {
	    *addr = q;
	    return -2;
	}
    }
    while (*q) {
	if (*q == ':') break;
	if (*q == '/') break;
	if (i >= 4) {
	    *addr = q;
	    return -1;
	}
	an[i++] = *q;
	q++;
    }
    if (i > 0) {
	an[i] = '\0';
	n = strtol(an, &qn, 16);
	if (*qn != '\0') return -1;
    } else {
	return -1;
    }

    *addr = q;
    return n;
}

int
IMAuth::
store_addr6_pattern(
    const char *pattern,
    int *addr_pattern
)
{
    int i, n;
    int bl;
    int zidx = -1;
    const char *p;
    char *pn;

    p = pattern;
    for (i = 0; *p; i++) {
	if (*p == '/') break;
	if (i >= addr6_elements_maxnum) return -1;
	n = addr6_element_to_number(&p);
	if (n == -1) return -1;
	if (n == -2) {
	    if (zidx >= 0) return -1;
	    zidx = i;
	    if ((p[1] == '\0') || (p[1] == '/')) {
		p++;
		break;
	    }
	    i--;
	} else {
	    addr_pattern[i] = n;
	}
    }

    if (zidx >= 0) {
	// zero compression
	n = addr6_elements_maxnum - i;
	if (n <= 0) return -1;
	/*
	  |<-----------i------------>|
	  |------------|*************|
	  |------------|00000000|*************|
          |<--zidx---->|<--n--->|             |
          |<======addr6_elements_maxnum======>|
	 */
	memmove(&addr_pattern[zidx + n], &addr_pattern[zidx], (i - zidx) * sizeof(int));
	memset(&addr_pattern[zidx], 0, n * sizeof(int));
	i = addr6_elements_maxnum;
    }
    if (i < addr6_elements_maxnum) return -1;

    if ((p > pattern) && (*p == '/')) {
	// prefix
	bl = strtol(p + 1, &pn, 10);
	if (*pn != '\0') return -1;
	if (bl > i * 16) return -1;
    } else {
	bl = addr6_elements_maxnum * 16;
    }

    return bl;
}

int
IMAuth::
set_entry(
    const char *pattern,
    access_type at
)
{
    auth_entry *pc;

    if (ae_num >= alloced_ae_num) {
	if (!adjust_pae_slot_size(alloced_ae_num * 2))
	    return 0;
    }
    
    pc = &pae[ae_num];
    pc->at = at;
    pc->pt = check_pattern_type(pattern);

    switch(pc->pt) {
      case HOSTNAME:
       pc->addr_pattern = NULL;
       pc->host_pattern = normalize_hostname_pattern(pattern);
       if (!pc->host_pattern) return 0;
       pc->bitlen = 0;
       break;
      case ADDR:
       pc->addr_pattern = (int*) malloc(sizeof(int) * addr_elements_maxnum);
       pc->host_pattern = NULL;
       pc->bitlen = store_addr_pattern(pattern, pc->addr_pattern);
       break;
      case ADDR6:
       pc->addr_pattern = (int*) malloc(sizeof(int) * addr6_elements_maxnum);
       pc->host_pattern = NULL;
       pc->bitlen = store_addr6_pattern(pattern, pc->addr_pattern);
       break;
    }
    if (pc->bitlen < 0) return 0;

    ae_num++;

    return 1;
}

int
IMAuth::
set_default_entry(access_type at)
{
    def_at = at;
    return 1;
}

void
IMAuth::
set_command_name(const char *cmdname)
{
    if (command_name) free(command_name);
    command_name = strdup(cmdname);
}


int
IMAuth::
clear_all_entries()
{
    int i;
    for (i = 0; i < ae_num; i++) {
	if (pae[i].host_pattern) free(pae[i].host_pattern);
	if (pae[i].addr_pattern) free(pae[i].addr_pattern);
    }
    ae_num = 0;
    if (!adjust_pae_slot_size(init_auth_entry_slot_num))
	return 0;

    return 1;
}

int
IMAuth::
match_hostname_entry(
    const char *pat,
    const char *hostname
)
{
    const char *p, *q;

    p = pat;
    q = hostname;
    while (*p && *q) {
	if (*p == '*') {
	    p++;
	    /* psudo longest match principle. */
	    if (strchr(p, '*')) {
		do {
		    if (*q == *p) break;
		}while (*q++);
	    } else {
		do {
		    if (compare_hostname(q, p)) return 1;
		}while (*q++);
	    }
	} else {
	    if (tolower(*p) != tolower(*q)) return 0;
	    p++;
	    q++;
	}
    }
    if (domainname && (*q == '.') && (!*p)) {
	return compare_hostname(domainname, &q[1]);
    }

    if (!*p && !*q) return 1;
    return 0;
}

char*
IMAuth::normalize_hostname_pattern(
    const char *pat
)
{
    int size;
    const char *p, *q;
    char *varname, *val;
    char *pn, *nstr;

  size = 1;
  /* Replace $(VARNAME) */
  for (p = pat; *p;) {
      if (q = strchr(p, '$')) {
	  size += (q - p);
	  q++;
	  if (*q == '$') {
	      size++;
	      p = q + 1;
	  } else if (*q == '(') {
	      q++;
	      p = q;
	      q = strchr(p, ')');
	      if (!q) return NULL;
	      varname = (char*) alloca((q - p + 1) * sizeof(char));
	      if (!varname) return NULL;
	      memcpy(varname, p, (q - p));
	      varname[q - p] = '\0';
	      val = sm.get_symbol_value(varname);
	      if (!val) return NULL;
	      size += strlen(val);
	      p = q + 1;
	  } else {
	      return NULL;
	  }
      } else {
	  size += strlen(p) + 1;
	  break;
      }
  }

  nstr = (char*) malloc(size * sizeof(char));
  /* Replace $(VARNAME) */
  for (p = pat, pn = nstr; *p;) {
      if (q = strchr(p, '$')) {
	  memcpy(pn, p, (q - p));
	  pn += q - p;
	  q++;
	  if (*q == '$') {
	      *pn++ = '$';
	      p = q + 1;
	  } else if (*q == '(') {
	      q++;
	      p = q;
	      q = strchr(p, ')');
	      if (!q) return NULL;
	      varname = (char*) alloca((q - p + 1) * sizeof(char));
	      if (!varname) return NULL;
	      memcpy(varname, p, (q - p));
	      varname[q - p] = '\0';
	      strcpy(pn, sm.get_symbol_value(varname));
	      pn += strlen(pn);
	      p = q + 1;
	  } else {
	      abort();
	  }
      } else {
	  strcpy(pn, p);
	  break;
      }
  }

  return nstr;
}

int
IMAuth::
match_addr46_entry(
    const int *pat,
    int patbitlen,
    const int *addr,
    int addrbitlen
)
{
  int i, mask, bl;

  if (patbitlen > addrbitlen) return 0;
  bl = patbitlen;
  i = 0;
  while (bl > 0) {
      if (bl < 8) {
	  mask = (1 << bl) - 1;
	  if ((pat[i] & mask) != (addr[i] & mask)) return 0;
	  bl = 0;
      } else {
	  if (pat[i] != addr[i]) return 0;
	  bl -= 8;
      }
  }
  return 1;
}

int
IMAuth::match_entry(
    auth_entry *p
)
{
    if (p->pt == HOSTNAME) {
	if (!from_hostname) return 0;
	return match_hostname_entry(p->host_pattern, from_hostname);
    } else if ((p->pt == ADDR) || (p->pt == ADDR6)) {
	if (!from_address) return 0;
	return match_addr46_entry(p->addr_pattern,
				  p->bitlen,
				  from_address,
				  from_address_bitlen);
    }

    return 0;
}

int
IMAuth::set_hostinfo()
{
#ifdef HAVE_GETHOSTNAME
    char hostname[8192];
    char *p;
    int len;

    if (gethostname(hostname, sizeof(hostname)) < 0) goto error;
    sm.set_symbol_value(hostname_symbol_name, hostname);
    if (!(p = strchr(hostname, '.'))) {
	/* If gethostname() dose not return FQDN,
	   we cannot help using resolver. */
	struct hostent *phe;
	char **aliases;
	phe = gethostbyname(hostname);
	if (!phe) return 0;
	p = strchr(phe->h_name, '.');
	for (aliases = phe->h_aliases;(!p && *aliases);aliases++)
	    p = strchr(*aliases, '.');
    }
    if (p) {
	p++;
	len = strlen(p);
	domainname = (char *) malloc(len + 1);
	strcpy(domainname, p);
	sm.set_symbol_value(domainname_symbol_name, domainname);
    } else {
	domainname = NULL;
	sm.set_symbol_value(domainname_symbol_name, "");
    }
    return 1;

 error:
#endif
    sm.set_symbol_value(domainname_symbol_name, "");
    sm.set_symbol_value(hostname_symbol_name, "");
    return 0;
}

int
IMAuth::set_fd_from(
    int fd
)
{
    struct sockaddr_storage ss;
    struct sockaddr *pname = (struct sockaddr*) &ss;
    socklen_t size;

    size = sizeof(ss);

    auth_type = IMAuth::UNDEFINED;

    if (getpeername(fd, pname, &size) < 0) return 0;

    if (pname->sa_family == AF_INET) {
	int i;
	char *addr;
	char *hname;
	struct sockaddr_in *pad;
	struct hostent *phe_rev, *phe;

	auth_type = IMAuth::TCP;
	pad = ((struct sockaddr_in*) pname);
	addr = inet_ntoa(pad->sin_addr);
	if (!addr) return 0;
	if (from_address) free(from_address);
	from_address = (int*) malloc(sizeof(int) * addr_elements_maxnum);
	memset(from_address, 0, sizeof(int) * addr_elements_maxnum);
	from_address_bitlen = store_addr_pattern(addr, from_address);
	if (from_address_bitlen < 0) return 0;

	size = sizeof(struct in_addr);
	phe_rev = gethostbyaddr((char*)(&pad->sin_addr), size, AF_INET);
	if (!phe_rev) return 1;

	hname = (char*) alloca(strlen(phe_rev->h_name) + 1);
	if (!hname) return 0;
	strcpy(hname, phe_rev->h_name);

	phe = gethostbyname(hname);
	if (!phe) return 1;

	if (!compare_hostname(hname, phe->h_name)) return 0;
	for (i = 0;phe->h_addr_list[i];i++)
	    if (memcmp(phe->h_addr_list[i], &(pad->sin_addr), size) == 0) {
		if (from_hostname) free(from_hostname);
		from_hostname = strdup(hname);
		if (!from_hostname) return 0;
		return 1;
	    }
    }
#ifdef AF_INET6
    else if (pname->sa_family == AF_INET6) {
	int i;
	char nihost[NI_MAXHOST], nihost2[NI_MAXHOST];
	char niserv[NI_MAXSERV];
	int from_address2[addr6_elements_maxnum];
	int from_address2_bitlen;

	auth_type = IMAuth::TCP;

	if (getnameinfo(pname, size,
			nihost, sizeof(nihost),
			niserv, sizeof(niserv),
			NI_NUMERICHOST) != 0)
	    return 0;
	if (from_address) free(from_address);
	from_address = (int*) malloc(sizeof(int) * addr6_elements_maxnum);
	memset(from_address, 0, sizeof(int) * addr6_elements_maxnum);
	from_address_bitlen = store_addr6_pattern(nihost, from_address);
	if (from_address_bitlen < 0) return 0;

	if (getnameinfo(pname, size,
			nihost, sizeof(nihost),
			NULL, 0, 0) != 0)
	    return 0;

	struct addrinfo hints, *pst, *pst2;
	memset(&hints, 0, sizeof(hints));
	hints.ai_family = PF_UNSPEC;
	hints.ai_socktype = SOCK_STREAM;
	if (getaddrinfo(nihost, niserv, &hints, &pst) != 0)
	    return 0;
	pst2 = pst;

	for (i = 0; pst; pst = pst->ai_next) {
	    if (getnameinfo(pst->ai_addr, pst->ai_addrlen, nihost2,
			    sizeof(nihost2), NULL, 0, NI_NUMERICHOST) != 0)
		continue;
	    from_address2_bitlen = store_addr6_pattern(nihost2, from_address2);
	    if (from_address2_bitlen < 0) continue;
	    if ((from_address2_bitlen == from_address_bitlen)
		&& match_addr46_entry(from_address, from_address_bitlen,
				      from_address2, from_address2_bitlen)) {
		if (from_hostname) free(from_hostname);
		freeaddrinfo(pst2);
		from_hostname = strdup(nihost);
		if (!from_hostname) return 0;
		return 1;
	    }
	    free(from_address2);
	}
	freeaddrinfo(pst2);
    }
#endif
#if defined(HAVE_UNIX_SOCKET)
    else if (pname->sa_family == AF_UNIX) {
        auth_type = IMAuth::UNIX;
	if (from_hostname) free(from_hostname);
	from_hostname = strdup("localhost");
	return 1;
    }
#endif
    else {
	return 0;
    }

    return 0;
}

IMAuth::access_type
IMAuth::get_access_type(
    int fd
)
{
    int i;
    if (!set_fd_from(fd)) return UNKNOWN;
    for (i = 0;i < ae_num;i++) {
	if (match_entry(&pae[i]))
	    return pae[i].at;
    }

    return def_at;
}

int
IMAuth::fd_ok(
    int fd
)
{
    access_type at;

    at = get_access_type(fd);
    if (at != IMAuth::DENY) {
	if (permit_access(command_name, fd)) {
	    if (at != IMAuth::UNKNOWN) {
		LOG_INFO("Allow the connection from %s.", from_hostname);
	    } else {
		LOG_WARNING("Allow the connection from unknown source.");
	    }
	    return 1;
	}
    }
    if (at != IMAuth::UNKNOWN) {
	LOG_WARNING("Denied the access from %s.", from_hostname);
    } else {
	LOG_WARNING("Denied the access from unknown source.");
    }
    return 0;
}


int
IMAuth::auth_fd(
    int fd,
    const char *user,
    const char *password
)
{
    int flag;

    access_type at = get_access_type(fd);

    switch (at) {
      case PERMIT:
       if (auth_type == IMAuth::UNIX) {
	   flag = check_password(fd, user, password);
	   /* TODO */
	   if (flag == -1) { 
	       /* this system doesn't support unix domain credential. */
	       flag = 1;
	   }
       } else {
           flag = 1;
       }
       break;
      case DENY:
       flag = 0;
       break;
      case CHECKUSER:
       flag = check_password(fd, user, password);
       break;
      case PASSWORD:
       if (!password || !user) {
	   flag = 0;
       } else {
	   flag = check_password(fd, user, password);
       }
       break;
      default:
       flag = 0;
       break;
    }

    if (flag) {
	if (at != IMAuth::UNKNOWN) {
	    LOG_INFO("The access from %s@%s was granted.", user, from_hostname);
	} else {
	    LOG_INFO("The access from %s(unknown source) was granted", user);
	}
    } else {
	if (at != IMAuth::UNKNOWN) {
	    LOG_WARNING("Denied the access from %s@%s.", user, from_hostname);
	} else {
	    LOG_WARNING("Denied the access from %s(unknown source)", user);
	}
    }

    return flag;
}

IMAuth::IMAuth()
{
    ae_num = 0;
    auth_type = IMAuth::UNDEFINED;
    from_hostname = NULL;
    from_address = NULL;
    command_name = domainname = NULL;
    alloced_ae_num = init_auth_entry_slot_num;
    pae = (auth_entry*) malloc(sizeof(auth_entry) * alloced_ae_num);
    if (!pae) alloced_ae_num = 0;
    def_at = DENY;
    set_hostinfo();
}

IMAuth::~IMAuth()
{
    clear_all_entries();
    free(pae);
    if (from_address) free(from_address);
    if (from_hostname) free(from_hostname);
    if (command_name) free(command_name);
    if (domainname) free(domainname);
}

/* Local Variables: */
/* c-file-style: "iiim-project" */
/* End: */
