/************************************************************************
 *   IRC - Internet Relay Chat, src/s_auth.c
 *   Copyright (C) 1992 Darren Reed
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/* $Id: s_auth.c,v 1.2 2006/01/07 22:13:26 trystanscott Exp $ */

#include "struct.h"
#include "common.h"
#include "sys.h"
#include "res.h"
#include "numeric.h"
#include "patchlevel.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#if defined(__hpux)
#include "inet.h"
#endif
#include <fcntl.h>
#include "sock.h"    /* If FD_ZERO isn't define up to this point */
/* define it (BSD4.2 needs this) */
#include "h.h"
#include "fdlist.h"
#include "fds.h"

static void authsenderr(aClient *);

/*
 * start_auth
 * 
 * Flag the client to show that an attempt to contact the ident server on
 * the client's host.  The connect and subsequently the socket are all
 * put into 'non-blocking' mode.  Should the connect or any later phase
 * of the identifing process fail, it is aborted and the user is given
 * a username of "unknown".
 */
void start_auth(aClient *cptr)
{
    struct sockaddr_in sock;
    struct sockaddr_in localaddr;
    socklen_t locallen;

    Debug((DEBUG_NOTICE, "start_auth(%x) fd %d status %d",
	   cptr, cptr->fd, cptr->status));
    if ((cptr->authfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
    {
#ifdef	USE_SYSLOG
	syslog(LOG_ERR, "Unable to create auth socket for %s:%m",
	       get_client_name(cptr, TRUE));
#endif
	    ircstp->is_abad++;
	    return;
    }
    if (cptr->authfd >= MAXCONNECTIONS)
    {
	sendto_realops_lev(DEBUG_LEV,"Can't allocate fd for auth on %s",
		   get_client_name(cptr, (IsServer(cptr) ? HIDEME : TRUE)));
	close(cptr->authfd);
        cptr->authfd = -1;
	return;
    }
#ifdef SHOW_HEADERS
    sendto_one(cptr, REPORT_DO_ID);
#endif
    set_non_blocking(cptr->authfd, cptr);
    /*
     * get the local address of the client and bind to that to make the
     * auth request.  This used to be done only for ifdef VIRTTUAL_HOST,
     * but needs to be done for all clients since the ident request must
     * originate from that same address-- and machines with multiple IP
     * addresses are common now
     */
    locallen = sizeof(struct sockaddr_in);

    memset(&localaddr, '\0', locallen);
    getsockname(cptr->fd, (struct sockaddr *) &localaddr, &locallen);
    localaddr.sin_port = htons(0);

    if (bind(cptr->authfd, (struct sockaddr *) &localaddr,
	     sizeof(localaddr)) == -1) 
    {
	report_error("binding auth stream socket %s:%s", cptr);
	close(cptr->authfd);
        cptr->authfd = -1;
	return;
    }
    
    memcpy((char *) &sock.sin_addr, (char *) &cptr->ip,
	   sizeof(struct in_addr));

    sock.sin_port = htons(113);
    sock.sin_family = AF_INET;

    if (connect(cptr->authfd, (struct sockaddr *) &sock,
		sizeof(sock)) == -1 && errno != EINPROGRESS)
    {
	ircstp->is_abad++;
	/* No error report from this... */
	close(cptr->authfd);
	cptr->authfd = -1;
#ifdef SHOW_HEADERS
	sendto_one(cptr, REPORT_FAIL_ID);
#endif
	return;
    }

    cptr->flags |= (FLAGS_WRAUTH | FLAGS_AUTH);
    if (cptr->authfd > highest_fd)
	highest_fd = cptr->authfd;

    add_fd(cptr->authfd, FDT_AUTH, cptr);
    return;
}

/*
 * send_authports
 * 
 * Send the ident server a query giving "theirport , ourport". The write
 * is only attempted *once* so it is deemed to be a fail if the entire
 * write doesn't write all the data given.  This shouldnt be a problem
 * since the socket should have a write buffer far greater than this
 * message to store it in should problems arise. -avalon
 */
void send_authports(aClient *cptr)
{
    struct sockaddr_in us, them;
    char        authbuf[32];
    socklen_t   ulen, tlen;

    Debug((DEBUG_NOTICE, "write_authports(%x) fd %d authfd %d stat %d",
	   cptr, cptr->fd, cptr->authfd, cptr->status));
    tlen = ulen = sizeof(us);

    if (getsockname(cptr->fd, (struct sockaddr *) &us, &ulen) ||
	getpeername(cptr->fd, (struct sockaddr *) &them, &tlen))
    {
#ifdef	USE_SYSLOG
	syslog(LOG_DEBUG, "auth get{sock,peer}name error for %s:%m",
	       get_client_name(cptr, TRUE));
#endif
	authsenderr(cptr);
	return;
    }

    (void) ircsprintf(authbuf, "%u , %u\r\n",
		      (unsigned int) ntohs(them.sin_port),
		      (unsigned int) ntohs(us.sin_port));
    
    Debug((DEBUG_SEND, "sending [%s] to auth port %s.113",
	   authbuf, inetntoa((char *) &them.sin_addr)));

    if (send(cptr->authfd, authbuf, strlen(authbuf), 0) != strlen(authbuf)) {
	authsenderr(cptr);
	return;
    }
    
    cptr->flags &= ~FLAGS_WRAUTH;
    
    return;
}

/*
 * authsenderr() *  
 * input - pointer to aClient output
 */
static void authsenderr(aClient *cptr)
{
    ircstp->is_abad++;

    del_fd(cptr->authfd);

    close(cptr->authfd);
    if (cptr->authfd == highest_fd)
	while (!local[highest_fd])
	    highest_fd--;
    cptr->authfd = -1;
    cptr->flags &= ~(FLAGS_AUTH | FLAGS_WRAUTH);
#ifdef SHOW_HEADERS
    sendto_one(cptr, REPORT_FAIL_ID);
#endif

    return;
}

/*
 * read_authports
 *
 * read the reply (if any) from the ident server we connected to. The
 * actual read processing here is pretty weak - no handling of the
 * reply if it is fragmented by IP.
 *
 * Whoever wrote this code should be shot.
 * Looks like it's trouncing on memory it shouldn't be.
 * Rewriting, some credit goes to wd for saving me time with his code.
 * - lucas
 */

#define AUTHBUFLEN 128

void read_authports(aClient *cptr)
{
   char buf[AUTHBUFLEN], usern[USERLEN + 1];
   int len, userncnt;
   char *userid = "", *s, *reply, *os, *tmp;

   len = recv(cptr->authfd, buf, AUTHBUFLEN, 0);

   if(len > 0)
   {
      do
      {
         if(buf[len - 1] != '\n')
            break;

         buf[--len] = '\0';

         if(len == 0)
            break;

         if(buf[len - 1] == '\r')
            buf[--len] = '\0';

         if(len == 0)
            break;

         s = strchr(buf, ':');
         if(!s)
            break;
         s++;

         while(IsSpace(*s))
            s++;

         reply = s;
         if(strncmp(reply, "USERID", 6))
            break;

         s = strchr(reply, ':');
         if(!s)
            break;
         s++;

         while(IsSpace(*s))
            s++;

         os = s;

         s = strchr(os, ':');
         if(!s)
            break;
         s++;

         while(IsSpace(*s))
            s++;

         userid = tmp = usern;
         /* s is the pointer to the beginning of the userid field */
         for(userncnt = USERLEN; *s && userncnt; s++)
         {
            if(*s == '@')
               break;

            if(!IsSpace(*s) && *s != ':')
            {
               *tmp++ = *s;
               userncnt--;
            }
         }
         *tmp = '\0';

      } while(0);
   }

   del_fd(cptr->authfd);
   close(cptr->authfd);
   if (cptr->authfd == highest_fd)
      while (!local[highest_fd])
         highest_fd--;
   cptr->authfd = -1;
   ClearAuth(cptr);

   if (!*userid)
   {
      ircstp->is_abad++;
      strcpy(cptr->username, "unknown");
#ifdef SHOW_HEADERS
      sendto_one(cptr, REPORT_FAIL_ID);
#endif
      return;
   }
#ifdef SHOW_HEADERS
   else
      sendto_one(cptr, REPORT_FIN_ID);
#endif

   ircstp->is_asuc++;
   strncpyzt(cptr->username, userid, USERLEN + 1);
   cptr->flags |= FLAGS_GOTID;
   return;
}



syntax highlighted by Code2HTML, v. 0.9.1