/****************************************************************************/
/*  FILE:   ident.c                                                         */
/*                                                                          */
/*  PURPOSE:    Provides functions that will query a remote ident server    */
/*                                                                          */
/*  NOTE:       This code was shamelessly hasked from PostgreSQL            */
/*              version 6.3.2 source distribution.  PostgreSQL is a leading */
/*              free RDMBS that is under the "Berklay" lincence.  See       */
/*              http://www.postgresql.org for more details.                 */
/*                                                                          */
/****************************************************************************/

/*
 * Parts (most) of the this file are...
 * 
 * Copyright (c) 1994-7 Regents of the University of California
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose, without fee, and without a written agreement
 * is hereby granted, provided that the above copyright notice and this
 * paragraph and the following two paragraphs appear in all copies.
 * 
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
 * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATIONS TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 * 
 * Where the text in this file differs from the original those modifications
 * are Copyright by Chris Albertson in 1998 and are placed under the GNU GPL.
 * You should have received a copy of the GNU General Public License
 * along with this program; see the file COPYING.  If not, write to 
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA
 * 
 * My guess is that you can do what you'd like with this stuff except sell
 * it for proffet.  In any event if this code breaks you may keep all the
 * little pieces.
 */


/* Used by RCS and ident */
char ident_rcsid[] = 
    "$Id: ident.c,v 1.1 1998/09/28 06:41:51 chris Exp $";


#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <pwd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <ctype.h>

#define IDENT_PORT         113
#define IDENT_USERNAME_MAX 256

#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

/* Function prototypes */
static void
interpret_ident_response(char ident_response[],
                         int *error_p, char ident_username[]);


/****************************************************************************/
/*  ident                                                                   */
/*                                                                          */
/*  PURPOSE:                                                                */
/*      Talk to the ident server on host "remote_ip_addr" and find out who  */
/*      owns the tcp connection from his port "remote_port" to port         */
/*      "local_port_addr" on host "local_ip_addr".  Return the username the */
/*      ident server gives as "ident_username[]".                           */
/*                                                                          */
/*      IP addresses and port numbers are in network byte order.            */
/*                                                                          */
/*      But iff we're unable to get the information from ident, return      */
/*      *ident_failed == TRUE (and ident_username[] undefined).             */
/*                                                                          */
/****************************************************************************/
void
ident(const struct in_addr remote_ip_addr, const struct in_addr local_ip_addr,
      const ushort remote_port, const ushort local_port,
      int *ident_failed, char ident_username[])
{
    int     sock_fd;  /* File descriptor for socket on which we talk to Ident */

    int     rc;       /* Return code from a locally called function */
    char    PQerrormsg[1024];
    

    sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP);
    if (sock_fd == -1)
    {
        sprintf(PQerrormsg,
             "Failed to create socket on which to talk to Ident server. "
                "socket() returned errno = %s (%d)\n",
                strerror(errno), errno);
        fputs(PQerrormsg, stderr);
    }
    else
    {
        struct sockaddr_in ident_server;

        /*
         * Socket address of Ident server on the system from which client
         * is attempting to connect to us.
         */
        ident_server.sin_family = AF_INET;
        ident_server.sin_port   = htons(IDENT_PORT);
        ident_server.sin_addr   = remote_ip_addr;
        rc = connect(sock_fd,
               (struct sockaddr *) & ident_server, sizeof(ident_server));
        if (rc != 0)
        {
            sprintf(PQerrormsg,
                "Unable to connect to Ident server on the host which is "
                    "trying to connect to Postgres "
                    "(IP address %s, Port %d). "
                    "errno = %s (%d)\n",
                    inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno);
            fputs(PQerrormsg, stderr);
            *ident_failed = TRUE;
        }
        else
        {
            char        ident_query[80];

            /* The query we send to the Ident server */
            sprintf(ident_query, "%d,%d\n",
                    ntohs(remote_port), ntohs(local_port));
            rc = send(sock_fd, ident_query, strlen(ident_query), 0);
            if (rc < 0)
            {
                sprintf(PQerrormsg,
                        "Unable to send query to Ident server on the host which is "
                      "trying to connect to Postgres (Host %s, Port %d),"
                        "even though we successfully connected to it.  "
                        "errno = %s (%d)\n",
                        inet_ntoa(remote_ip_addr), IDENT_PORT, strerror(errno), errno);
                fputs(PQerrormsg, stderr);
                *ident_failed = TRUE;
            }
            else
            {
                char        ident_response[80 + IDENT_USERNAME_MAX];

                rc = recv(sock_fd, ident_response, sizeof(ident_response) - 1, 0);
                if (rc < 0)
                {
                    sprintf(PQerrormsg,
                          "Unable to receive response from Ident server "
                            "on the host which is "
                      "trying to connect to Postgres (Host %s, Port %d),"
                    "even though we successfully sent our query to it.  "
                            "errno = %s (%d)\n",
                            inet_ntoa(remote_ip_addr), IDENT_PORT,
                            strerror(errno), errno);
                    fputs(PQerrormsg, stderr);
                    *ident_failed = TRUE;
                }
                else
                {
                    int        error;    /* response from Ident is garbage. */

                    ident_response[rc] = '\0';
                    interpret_ident_response(ident_response, &error, ident_username);
                    *ident_failed = error;
                }
            }
            close(sock_fd);
        }
    }
}


/****************************************************************************/
/*  interpret_ident                                                         */
/*                                                                          */
/*  PURPOSE                                                                 */
/*    Parse the string "ident_response[]" as a response from a query to     */
/*    an Ident server                                                       */
/*    If it's a normal response indicating a username, return               */
/*    *error_p == FALSE and the username as ident_username[].               */
/*    If it's anything else,                                                */
/*    return *error_p == TRUE and ident_username[] undefined.               */
/*                                                                          */
/****************************************************************************/
static void
interpret_ident_response(char ident_response[],
                         int *error_p, char ident_username[])
{
    char       *cursor;            /* Cursor into ident_response[] */

    cursor = &ident_response[0];

    /*
     * Ident's response, in the telnet tradition, should end in crlf
     * (\r\n).
     */
    if (strlen(ident_response) < 2)
        *error_p = TRUE;
    else if (ident_response[strlen(ident_response) - 2] != '\r')
        *error_p = TRUE;
    else
    {
        while (*cursor != ':' && *cursor != '\r')
            cursor++;            /* skip port field */

        if (*cursor != ':')
            *error_p = TRUE;
        else
        {
            /* We're positioned to colon before response type field */
            char        response_type[80];
            int            i;        /* Index into response_type[] */

            cursor++;            /* Go over colon */
            while (isblank(*cursor))
                cursor++;        /* skip blanks */
            i = 0;
            while (*cursor != ':' && *cursor != '\r' && !isblank(*cursor)
                   && i < sizeof(response_type) - 1)
                response_type[i++] = *cursor++;
            response_type[i] = '\0';
            while (isblank(*cursor))
                cursor++;        /* skip blanks */
            if (strcmp(response_type, "USERID") != 0)
                *error_p = TRUE;
            else
            {

                /*
                 * It's a USERID response.  Good.  "cursor" should be
                 * pointing to the colon that precedes the operating
                 * system type.
                 */
                if (*cursor != ':')
                    *error_p = TRUE;
                else
                {
                    cursor++;    /* Go over colon */
                    /* Skip over operating system field. */
                    while (*cursor != ':' && *cursor != '\r')
                        cursor++;
                    if (*cursor != ':')
                        *error_p = TRUE;
                    else
                    {
                        int            i;    /* Index into ident_username[] */

                        cursor++;        /* Go over colon */
                        while (isblank(*cursor))
                            cursor++;    /* skip blanks */
                        /* Rest of line is username.  Copy it over. */
                        i = 0;
                        while (*cursor != '\r' && i < IDENT_USERNAME_MAX)
                            ident_username[i++] = *cursor++;
                        ident_username[i] = '\0';
                        *error_p = FALSE;
                    }
                }
            }
        }
    }
}

