/****************************************************************************
* [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame | \\._.// *
* -----------------------------------------------------------| (0...0) *
* SMAUG 1.4 (C) 1994, 1995, 1996, 1998 by Derek Snider | ).:.( *
* -----------------------------------------------------------| {o o} *
* SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus, | / ' ' \ *
* Scryn, Rennard, Swordbearer, Gorog, Grishnakh, Nivek, |~'~.VxvxV.~'~*
* Tricops and Fireblade | *
*------------------------------------------------------------------------ *
* Ident *
****************************************************************************/
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#ifdef WIN32
#include <io.h>
#define EWOULDBLOCK WSAEWOULDBLOCK
#else
#include <netinet/in.h>
#include <netdb.h>
#include <sys/socket.h>
#define closesocket close
#endif
#include "mud.h"
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
typedef enum { AS_TOOPEN, AS_TOSEND, AS_TOREAD } AUTH_STATE;
typedef struct auth_data
{
struct auth_data *next, *prev;
DESCRIPTOR_DATA *d;
int afd;
int state;
int times;
struct sockaddr_in us, them;
} AUTH_DATA;
AUTH_DATA *first_auth, *last_auth;
bool auth_read(AUTH_DATA *a, CHAR_DATA *ch);
bool auth_write(AUTH_DATA *a, CHAR_DATA *ch);
bool auth_open(AUTH_DATA *a, CHAR_DATA *ch);
void auth_maxdesc(int *md, fd_set *ins, fd_set *outs, fd_set *excs)
{
AUTH_DATA *a;
for (a = first_auth; a; a = a->next)
if (a->state != AS_TOOPEN)
{
*md = UMAX(*md, a->afd);
FD_SET(a->afd, ins);
FD_SET(a->afd, outs);
FD_SET(a->afd, excs);
}
return;
}
void auth_check(fd_set *ins, fd_set *outs, fd_set *excs)
{
AUTH_DATA *a, *a_next;
CHAR_DATA *ch;
bool ferr;
for (a = first_auth; a; a = a_next)
{
a_next = a->next;
ferr = FALSE;
ch = (a->d->original ? a->d->original : a->d->character);
if (a->state == AS_TOOPEN)
{
if (!auth_open(a, ch))
ferr = TRUE;
}
else if (FD_ISSET(a->afd, excs))
{
FD_CLR(a->afd, ins);
FD_CLR(a->afd, outs);
bug("Auth_check: exception found for %s@%s.",
(ch ? ch->name : "(unknown)"), a->d->host);
STRFREE(a->d->user);
a->d->user = STRALLOC("Exception");
ferr = TRUE;
}
else if (FD_ISSET(a->afd, ins) && a->state == AS_TOREAD)
{
if (!auth_read(a, ch))
{
FD_CLR(a->afd, outs);
ferr = TRUE;
}
}
else if (FD_ISSET(a->afd, outs) && a->state == AS_TOSEND)
{
if (!auth_write(a, ch))
ferr = TRUE;
}
if (ferr)
{
if (a->state != AS_TOOPEN)
closesocket(a->afd);
UNLINK(a, first_auth, last_auth, next, prev);
DISPOSE(a);
}
}
return;
}
char *break_arg(char **s, char end)
{
char *ret, *ws;
while (isspace(**s))
++*s;
ret = *s;
while (**s && **s != end)
++*s;
for (ws = (*s)-1; isspace(*ws); --ws)
*ws = '\0';
if (**s != '\0')
{
**s = '\0';
++*s;
}
/* bug("break_arg: broke '%s', left '%s'", ret, *s);*/
return ret;
}
#define KILLRET(bs, us) \
do { \
bug(bs, (ch ? ch->name : "(unknown)"), a->d->host); \
STRFREE(a->d->user); \
a->d->user = STRALLOC(us); \
return FALSE; \
} while(0)
bool auth_read(AUTH_DATA *a, CHAR_DATA *ch)
{
char readbuf[MAX_STRING_LENGTH];
char system[MAX_INPUT_LENGTH], user[MAX_INPUT_LENGTH];
char *s = readbuf;
int n;
n = recv(a->afd, readbuf, sizeof(readbuf)-10, 0);
if (n < 0)
{
perror("auth_read: read");
KILLRET("Auth_read: Error on read for %s@%s.", "(Error on read)");
}
if (!n)
return TRUE;
/* KILLRET("Auth_read: EOF on read for %s@%s.", "(EOF on read)");*/
readbuf[n] = '\0';
while (isspace(*s))
++s;
if (!*s)
KILLRET("Auth_read: blank auth for %s@%s.", "(blank auth)");
/* That 1024 check should actually be against the port the user is
logged into, but in SMAUG theres like 4 possible, and I don't
feel like checking every single one.. -- Alty */
/* bug("ident = '%s'.", s);*/
if (!atoi(break_arg(&s, ',')) || atoi(break_arg(&s, ':')) < 1024)
KILLRET("Auth_read: Invalid ident reply for %s@%s.", "(invalid ident)");
break_arg(&s, ':');
sprintf(system, "%.*s", (int)sizeof(system)-1, break_arg(&s, ':'));
/* if (!str_cmp(system, "OTHER"))
KILLRET("Auth_read: invalid system for %s@%s.", "(invalid system)");*/
sprintf(user, "%.*s", (int)sizeof(user)-1, break_arg(&s, ' '));
if (!*user)
KILLRET("Auth_read: no username for %s@%s.", "(no username)");
sprintf(log_buf, "Auth reply ok. Incoming user [%s@%s] for %s.",
user, a->d->host, (ch ? ch->name : "(unknown)"));
STRFREE(a->d->user);
a->d->user = STRALLOC(user);
return FALSE; /* FALSE actually removes the AUTH_DATA, which is good. */
}
bool auth_write(AUTH_DATA *a, CHAR_DATA *ch)
{
char authbuf[32];
int n;
sprintf(authbuf, "%u , %u\r\n", (unsigned int)ntohs(a->them.sin_port),
(unsigned int)ntohs(a->us.sin_port));
n = send(a->afd, authbuf, strlen(authbuf), 0);
if (n != strlen(authbuf))
{
if (!--a->times)
{
/* This used to be the KILLRET you see below, but too many imms complained
about log being spammed.. yeah.. like it aint spammed anyways..
-- Alty */
STRFREE(a->d->user);
a->d->user = STRALLOC("(broken pipe)");
return FALSE;
}
/* KILLRET("Auth_write: broken pipe for %s@%s.", "(broken pipe)");*/
closesocket(a->afd);
a->state = AS_TOOPEN;
if (a->times == sysdata.ident_retries-1)
{
STRFREE(a->d->user);
a->d->user = STRALLOC("(pipe breaking)");
}
return TRUE; /* Dont kill AUTH_DATA */
}
a->state = AS_TOREAD;
return TRUE;
}
bool auth_open(AUTH_DATA *a, CHAR_DATA *ch)
{
struct sockaddr_in sock;
struct servent *serv;
int err;
#ifdef WIN32
unsigned int arg = 1;
#endif
a->afd = socket(AF_INET, SOCK_STREAM, 0);
err = errno;
if (a->afd < 0)
{
perror("auth_open: socket");
if (err == EAGAIN)
{
bug("Auth_open: can't allocate filedesc for %s@%s.",
(ch ? ch->name : "(unknown)"), a->d->host);
--a->times;
return TRUE;
}
KILLRET("Auth_open: unknown socket error", "(socket error)");
}
#ifndef FNDELAY
#define FNDELAY O_NDELAY
#endif
#ifdef WIN32
if (ioctlsocket(a->afd, FIONBIO, &arg) == -1 )
#else
if (fcntl(a->afd, F_SETFL, FNDELAY) < 0)
#endif
{
perror("auth_open: fcntl");
KILLRET("Auth_open: unknown fcntl error", "(fcntl error)");
}
sock = a->them;
serv = getservbyname("ident", "tcp");
if (!serv)
sock.sin_port = htons(113);
else
sock.sin_port = serv->s_port;
sock.sin_family = AF_INET;
if (connect(a->afd, (struct sockaddr *)&sock, sizeof(sock)) < 0
#ifdef WIN32
&& errno != WSAEINPROGRESS )
#else
&& errno != EINPROGRESS )
#endif
{
#ifndef WIN32
if (errno != ECONNREFUSED)
{
perror("auth_open: connect");
KILLRET("Auth_open: unknown connect error", "(connect error)");
}
#endif
KILLRET("Auth_open: connection refused", "(connect refused)");
}
a->state = AS_TOSEND;
return TRUE;
}
#undef KILLRET
#define ENDRET(bs, us) \
do { \
bug(bs, (ch ? ch->name : "(unknown)"), d->host); \
STRFREE(d->user); \
d->user = STRALLOC(us); \
return; \
} while(0)
void set_auth(DESCRIPTOR_DATA *d)
{
CHAR_DATA *ch = (d->original ? d->original : d->character);
AUTH_DATA *a;
struct sockaddr_in us, them;
int ulen = sizeof(us), tlen = sizeof(them);
/* To stop an uninitialized memory read --Shaddai */
us.sin_port = 0;
them.sin_port = 0;
if (sysdata.ident_retries <= 0)
{
STRFREE(d->user);
d->user = STRALLOC("(ident not active)");
return;
}
if (getsockname(d->descriptor, (struct sockaddr *)&us, &ulen) < 0)
{
perror("set_auth: getsockname");
ENDRET("Set_auth: getsockname error for %s@%s.", "(getsockname error)");
}
if (getpeername(d->descriptor, (struct sockaddr *)&them, &tlen) < 0)
{
perror("set_auth: getpeername");
ENDRET("Set_auth: getpeername error for %s@%s.", "(getpeername error)");
}
CREATE(a, AUTH_DATA, 1);
a->d = d;
a->state = AS_TOOPEN;
a->times = sysdata.ident_retries;
a->us = us;
a->them = them;
LINK(a, first_auth, last_auth, next, prev);
STRFREE(d->user);
d->user = STRALLOC("(in progress)");
return;
}
void kill_auth(DESCRIPTOR_DATA *d)
{
AUTH_DATA *a;
for (a = first_auth; a; a = a->next)
if (a->d == d)
{
if (a->state != AS_TOOPEN)
closesocket(a->afd);
UNLINK(a, first_auth, last_auth, next, prev);
DISPOSE(a);
STRFREE(d->user);
d->user = STRALLOC("(killed)");
return;
}
return;
}