/*
950123: <mea@nic.funet.fi> Fixed things to be more GENERIC -- now it
	compiles also on OSF/1 Alpha...
21/7/92 Fixed SIGPIPE bug in ident_tcpuser3(). <pen@lysator.liu.se>
2/9/92: identuser 4.0. Public domain.
2/9/92: added bunches of zeroing just in case.
2/9/92: added ident_tcpuser3. uses bsd 4.3 select interface.
2/9/92: added ident_tcpsock, ident_sockuser.
2/9/92: added ident_fd2, ident_tcpuser2, simplified some of the code.
12/27/91: fixed up usercmp to deal with restricted tolower XXX
5/6/91 DJB baseline identuser 3.1. Public domain.
*/

/* Tuned to be part of ZMailer -- #include "zmsignal.h" .. */

#include <stdio.h>
#include "zmsignal.h"
#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <fcntl.h> /*XXX*/
#include <sys/socket.h>
#include <netinet/in.h>
#include <ctype.h>
#include <errno.h>
#ifdef	ISC
#include <net/errno.h>
#endif
extern int errno;
#include "identuser.h"

#ifdef _AIX
#include <sys/types.h>
#include <sys/select.h>
#endif

#ifndef FNDELAY
#define FNDELAY O_NDELAY
#endif

unsigned short ident_tcpport = 113;

#define SIZ 500 /* various buffers */

static clearsa(sa)
struct sockaddr_in *sa;
{
 register char *x;
 for (x = (char *) sa;x < sizeof(*sa) + (char *) sa;++x)
   *x = 0;
}

#if 0
static int usercmp(u,v)
register char *u;
register char *v;
{
 register char uc;
 register char vc;
 register char ucvc;
 /* is it correct to consider Foo and fOo the same user? yes */
 /* but the function of this routine may change later */
 while ((uc = *u) && (vc = *v))
  {
   ucvc = (isupper(uc) ? tolower(uc) : uc) - (isupper(vc) ? tolower(vc) : vc);
   if (ucvc)
     return ucvc;
   else
     ++u,++v;
  }
 return uc || vc;
}

static char identline[SIZ];

char *ident_infoline(user,fd,in)
register char *user; /* the supposed name of the user, NULL if unknown */
register int fd; /* the file descriptor of the connection */
register struct in_addr *in;
{
 unsigned short local;
 unsigned short remote;
 register char *ruser;

 if (ident_fd(fd,in,&local,&remote) == -1)
   return 0;
 ruser = ident_tcpuser(*in,local,remote);
 if (!ruser)
   return 0;
 if (!user)
   user = ruser; /* forces X-Ident-User */
 sprintf(identline,
	 (usercmp(ruser,user) ? "forgery %s" : "identuser %s"),
	 ruser);
 return identline;
}

int ident_fd(fd,in,local,remote)
register int fd;
register struct in_addr *in;
register unsigned short *local;
register unsigned short *remote;
{
 struct in_addr inlocal;
 return ident_fd2(fd,&inlocal,in,local,remote);
}
#endif /* 0 */


int ident_fd2(fd,inlocal,inremote,local,remote)
register int fd;
register struct in_addr *inlocal;
register struct in_addr *inremote;
register unsigned short *local;
register unsigned short *remote;
{
 struct sockaddr_in sa;
 int dummy;

 dummy = sizeof(sa);
 if (getsockname(fd,(struct sockaddr*)&sa,&dummy) == -1)
   return -1;
 if (sa.sin_family != AF_INET)
  {
   errno = EAFNOSUPPORT;
   return -1;
  }
 *local = ntohs(sa.sin_port);
 *inlocal = sa.sin_addr;
 dummy = sizeof(sa);
 if (getpeername(fd,(struct sockaddr*)&sa,&dummy) == -1)
   return -1;
 *remote = ntohs(sa.sin_port);
 *inremote = sa.sin_addr;
 return 0;
}

static char ruser[SIZ];
static char realbuf[SIZ];
static char *buf;

#if 0
char *ident_tcpuser(in,local,remote)
register struct in_addr in;
register unsigned short local;
register unsigned short remote;
{
 return ident_tcpuser2(0,in,local,remote);
}
#endif

#define CLORETS(e) { saveerrno = errno; close(s); errno = saveerrno; return e; }

int ident_tcpsock(inlocal,inremote)
register struct in_addr *inlocal;
register struct in_addr *inremote;
{
 struct sockaddr_in sa;
 register int s;
 register int fl;
 register int saveerrno;

 if ((s = socket(AF_INET,SOCK_STREAM,0)) == -1)
   return -1;
 if (inlocal->s_addr)
  {
   clearsa(&sa);
   sa.sin_family = AF_INET;
   sa.sin_port = 0;
   sa.sin_addr = *inlocal;
   if (bind(s,(struct sockaddr*)&sa,sizeof(sa)) == -1)
     CLORETS(-1)
  }
 if ((fl = fcntl(s,F_GETFL,0)) == -1)
   CLORETS(-1);
 if (fcntl(s,F_SETFL,FNDELAY | fl) == -1)
   CLORETS(-1);
 clearsa(&sa);
 sa.sin_family = AF_INET;
 sa.sin_port = htons(ident_tcpport);
 sa.sin_addr = *inremote;
 if (connect(s,(struct sockaddr*)&sa,sizeof(sa)) == -1)
   if (errno != EINPROGRESS)
     CLORETS(-1)
 return s;
}

char *ident_tcpuser2(inlocal,inremote,local,remote)
	struct in_addr *inlocal;
	struct in_addr *inremote;
	unsigned short local;
	unsigned short remote;
{
 register int s;

 s = ident_tcpsock(inlocal,inremote);
 if (s == -1)
   return 0;
 return ident_sockuser(s,local,remote);
}

char *ident_tcpuser3(inlocal,inremote,local,remote,timeout)
register struct in_addr *inlocal;
register struct in_addr *inremote;
register unsigned short local;
register unsigned short remote;
register int timeout;
{
 register int s;
 struct timeval tv;
 fd_set wfds;
 register int r;
 register int saveerrno;
 void (*old_sig)__((int));
 char *retval;
 

 old_sig = signal(SIGPIPE, SIG_IGN);
 
 s = ident_tcpsock(inlocal,inremote);
 if (s == -1)
 {
   signal(SIGPIPE, old_sig);
   return 0;
 }
 tv.tv_sec = timeout;
 tv.tv_usec = 0;
 FD_ZERO(&wfds);
 FD_SET(s,&wfds);
 r = select(s + 1,(fd_set *) 0,&wfds,(fd_set *) 0,&tv);
 /* XXX: how to handle EINTR? */
 if (r == -1)
 {
   signal(SIGPIPE, old_sig);
   CLORETS(0)
 }
 if (!FD_ISSET(s,&wfds))
  {
   close(s);
   errno = ETIMEDOUT;
   signal(SIGPIPE, old_sig);
   return 0;
  }
 retval = ident_sockuser(s,local,remote);
 signal(SIGPIPE, old_sig);

 return retval;
}

char *ident_sockuser(s,local,remote)
register int s;
register unsigned short local;
register unsigned short remote;
{
 register int buflen;
 register int w;
 register int saveerrno;
 char ch;
 unsigned short rlocal;
 unsigned short rremote;
 register int fl;
 fd_set wfds;
 void (*old_sig)__((int));
 
 old_sig = signal(SIGPIPE, SIG_IGN);
 
 FD_ZERO(&wfds);
 FD_SET(s,&wfds);
 select(s + 1,(fd_set *) 0,&wfds,(fd_set *) 0,(struct timeval *) 0);
   /* now s is writable */
 if ((fl = fcntl(s,F_GETFL,0)) == -1)
 {
   signal(SIGPIPE, old_sig);
   CLORETS(0);
 }
 if (fcntl(s,F_SETFL,~FNDELAY & fl) == -1)
 {
   signal(SIGPIPE, old_sig);
   CLORETS(0);
 }
 buf = realbuf;
 sprintf(buf,"%u , %u\r\n",(unsigned int) remote,(unsigned int) local);
 /* note the reversed order---the example in RFC 931 is misleading */
 buflen = strlen(buf);
 while ((w = write(s,buf,buflen)) < buflen)
   if (w == -1) /* should we worry about 0 as well? */
   {
     signal(SIGPIPE, old_sig);
     CLORETS(0);
   }
   else
    {
     buf += w;
     buflen -= w;
    }
 buf = realbuf;
 while ((w = read(s,&ch,1)) == 1)
  {
   *buf = ch;
   if ((ch != ' ') && (ch != '\t') && (ch != '\r'))
     ++buf;
   if ((buf - realbuf == sizeof(realbuf) - 1) || (ch == '\n'))
     break;
  }
 signal(SIGPIPE, old_sig);
 if (w == -1)
   CLORETS(0)
 *buf = 0;

 if (sscanf(realbuf,"%hd,%hd: USERID :%*[^:]:%s",&rremote,&rlocal,ruser) < 3)
  {
   close(s);
   errno = EIO;
   /* makes sense, right? well, not when USERID failed to match ERROR */
   /* but there's no good error to return in that case */

   return 0;
  }
 if ((remote != rremote) || (local != rlocal))
  {
   close(s);
   errno = EIO;
   return 0;
  }
 /* we're not going to do any backslash processing */
 close(s);
 return ruser;
}
