/* * ident_server.c * Ident server for rfc1413 lookups * --------------------------------------------------------------------------- * * The majority of the code contained herein is Copyright 1995/1996 by * Neil Peter Charley. * Portions of the code (notably that for Solaris 2.x and BSD compatibility) * are Copyright 1996 James William Hawtin. Thanks also goes to James * for pointing out ways to make the code more robust. * ALL code contained herein is covered by one or the other of the above * Copyright's. * * Distributed as part of Playground+ package with permission. */ char identserverc_rscid[] = "$Revision: 1.6 $"; #if defined(__STRICT_ANSI__) #include <features.h> #define __USE_POSIX #define __USE_BSD #define __USE_MISC #endif /* __STRICT_ANSI__ */ #include <unistd.h> #if defined(HAVE_FILIOH) #include <sys/filio.h> #endif /* HAVE_FILIOH */ #include <stdlib.h> #include <stdio.h> #include <stdarg.h> #include <string.h> #include <errno.h> #include <sys/time.h> #include <signal.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/resource.h> #include "include/config.h" #include "include/player.h" #include "include/fix.h" #include "include/proto.h" #include "include/ident.h" /* Extern Function Prototypes */ /* Extern Variables */ #ifndef errno extern int errno; #endif #if !defined(OSF) #if !defined(NetBSD) && defined(BSD) /* Check for NetBSD; prevent compilation error -- blimey */ /* Missing BSD PROTOTYPES */ extern int setrlimit(int, struct rlimit *); extern int getrlimit(int, struct rlimit *); extern int setitimer(int, struct itimerval *, struct itimerval *); extern int fclose(); extern int select(); extern time_t time(time_t *); extern int connect(); extern int shutdown(int, int); extern int socket(int, int, int); extern int strftime(char *, int, char *, struct tm *); extern void perror(char *); extern int fflush(); extern int ioctl(); extern int fprintf(); extern void bzero(void *, int); extern void bcopy(char *, char *, int); extern int vfprintf(FILE *stream, char *format, ...); #endif /* BSD */ #else /* OSF */ extern int ioctl(int d, unsigned long request, void * arg); #endif /* !OSF */ #if !defined(MALLOC) #define MALLOC(c) malloc(c) #endif /* MALLOC */ #if !defined(FREE) #define FREE(c) free(c) #endif /* FREE */ /* Local Function Prototypes */ void catch_sigterm(SIGTYPE); void check_connections(void); void check_requests(void); void closedown_request(int slot); void do_request(ident_request *id_req); void logid(char *,...); void process_request(int slot); void process_result(int slot); void queue_request(ident_identifier id, struct sockaddr_in *local_sockadd, struct sockaddr_in *sockadd); void take_off_queue(int freeslot); void write_request(ident_request *id_req); /* Local Variables */ #define BUFFER_SIZE 1024 char scratch[4096]; char req_buf[BUFFER_SIZE]; ident_request idents_in_progress[MAX_IDENTS_IN_PROGRESS]; ident_request *first_inq = NULL; ident_request *last_inq = NULL; int debug = 0; int status = 0; int beats_per_second; int req_size; #define IDENT_STATUS_RUNNING 1 #define IDENT_STATUS_SHUTDOWN 2 int main(int argc, char *argv[]) { struct rlimit rlp; #if defined(USE_SIGACTION) struct sigaction sigact; #endif struct sockaddr_in sadd; fd_set fds_write; fd_set fds_read; int i; struct timeval timeout; time_t now; getrlimit(RLIMIT_NOFILE, &rlp); rlp.rlim_cur = rlp.rlim_max; setrlimit(RLIMIT_NOFILE, &rlp); /* First things first, let the client know we're alive */ write(1, IDENT_SERVER_CONNECT_MSG, strlen(IDENT_SERVER_CONNECT_MSG)); /* Need this to keep in sync with the client side * * <start><ident_id><local_port><sin_family><s_addr><sin_port> */ req_size = sizeof(char) + sizeof(ident_identifier) + sizeof(sadd.sin_addr.s_addr) + sizeof(sadd.sin_port) + sizeof(sadd.sin_family) + sizeof(sadd.sin_addr.s_addr) + sizeof(sadd.sin_port); #if defined(DEBUG_IDENT) logid("ident", "Ident message size = %d", req_size); #endif /* Set up signal handling */ #if defined(USE_SIGACTION) #if defined(HAVE_SIGEMPTYSET) sigemptyset(&(sigact.sa_mask)); #else sigact.sa_mask = 0; #endif /* HAVE_SIGEMPTYSET */ #if defined(__linux__) sigact.sa_restorer = (void *)NULL; #endif sigact.sa_handler = catch_sigterm; sigaction(SIGTERM, &sigact, (struct sigaction *)NULL); sigact.sa_handler = SIG_IGN; sigaction(SIGPIPE, &sigact, (struct sigaction *)NULL); #else /* !USE_SIGACTION */ signal(SIGTERM, catch_sigterm); signal(SIGPIPE, SIG_IGN); #endif /* USE_SIGACTION */ #if defined(HAVE_BZERO) bzero(&idents_in_progress[0], MAX_IDENTS_IN_PROGRESS * sizeof(ident_request)); #else /* !HAVE_BZERO */ memset(&idents_in_progress[0], 0, MAX_IDENTS_IN_PROGRESS * sizeof(ident_request)); #endif /* HAVE_BZERO */ for (i = 0 ; i < MAX_IDENTS_IN_PROGRESS ; i++) { idents_in_progress[i].fd = -1; } /* Now enter the main loop */ status = IDENT_STATUS_RUNNING; while (status != IDENT_STATUS_SHUTDOWN) { if (1 == getppid()) { /* If our parent is now PID 1 (init) the talker must have died without * killing us, so we have no business still being here * * time to die... */ exit(0); } FD_ZERO(&fds_write); /* These are for connection being established */ FD_ZERO(&fds_read); /* These are for a reply being ready */ FD_SET(0, &fds_read); for (i = 0 ; i < MAX_IDENTS_IN_PROGRESS ; i++) { if (idents_in_progress[i].fd > 0) { if (idents_in_progress[i].flags & IDENT_CONNREFUSED) { process_result(i); } else if (!(idents_in_progress[i].flags & IDENT_CONNECTED)) { FD_SET(idents_in_progress[i].fd, &fds_write); } else { FD_SET(idents_in_progress[i].fd, &fds_read); } } else { /* Free slot, so lets try to fill it */ take_off_queue(i); } } timeout.tv_sec = 1; timeout.tv_usec = 0; i = select(FD_SETSIZE, &fds_read, &fds_write, 0, &timeout); switch (i) { case -1: #if defined(DEBUG_IDENT_TOO) logid("ident", "ident: select failed: %s\n", strerror(errno)); #endif /* DEBUG_IDENT_TOO */ break; case 0: break; default: for (i = 0 ; i < MAX_IDENTS_IN_PROGRESS ; i++) { if (idents_in_progress[i].fd == -1) { continue; } if (FD_ISSET(idents_in_progress[i].fd, &fds_write)) { /* Has now connected, so send request */ idents_in_progress[i].flags |= IDENT_CONNECTED; write_request(&idents_in_progress[i]); } else if (FD_ISSET(idents_in_progress[i].fd, &fds_read)) { /* Reply is ready, so process it */ idents_in_progress[i].flags |= IDENT_REPLY_READY; process_result(i); } } if (FD_ISSET(0, &fds_read)) { check_requests(); } } now = time(NULL); for (i = 0 ; i < MAX_IDENTS_IN_PROGRESS ; i++) { if (idents_in_progress[i].fd > 0) { if (now > (idents_in_progress[i].request_time + IDENT_TIMEOUT)) { /* Request has timed out, whether on connect or reply */ idents_in_progress[i].flags |= IDENT_TIMEDOUT; process_result(i); } } } } return 0; } /* Catch a SIGTERM, this means to shutdown */ void catch_sigterm(SIGTYPE) { status = IDENT_STATUS_SHUTDOWN; } /* Check for requests from the client */ void check_requests(void) { char msgbuf[129]; int toread; int bread; static int bufpos = 0; int i; ioctl(0, FIONREAD, &toread); if (toread <= 0) { return; } if (toread > 128) { toread = 128; } bread = read(0, msgbuf, toread); for (i = 0 ; i < bread ;) { req_buf[bufpos++] = msgbuf[i++]; if (bufpos == req_size) { process_request(bufpos); bufpos = 0; } } } void process_request(int toread) { int i; struct sockaddr_in sockadd; struct sockaddr_in local_sockadd; ident_identifier ident_id; for (i = 0 ; i < toread ;) { switch (req_buf[i++]) { case IDENT_CLIENT_SEND_REQUEST: memcpy(&ident_id, &req_buf[i], sizeof(ident_id)); i += sizeof(ident_id); memcpy(&(local_sockadd.sin_addr.s_addr), &req_buf[i], sizeof(local_sockadd.sin_addr.s_addr)); i += sizeof(local_sockadd.sin_addr.s_addr); memcpy(&(local_sockadd.sin_port), &req_buf[i], sizeof(local_sockadd.sin_port)); i += sizeof(local_sockadd.sin_port); memcpy(&(sockadd.sin_family), &req_buf[i], sizeof(sockadd.sin_family)); i += sizeof(sockadd.sin_family); memcpy(&(sockadd.sin_addr.s_addr), &req_buf[i], sizeof(sockadd.sin_addr.s_addr)); i += sizeof(sockadd.sin_addr.s_addr); memcpy(&(sockadd.sin_port), &req_buf[i], sizeof(sockadd.sin_port)); i += sizeof(sockadd.sin_port); #if defined(DEBUG_IDENT_TOO) logid("ident", "Server: Id = %d\n" " Address = %08X:%d\n" " To = %08X:%d\n" "Bytes needed %d, Bytes to recieve %d\n", ident_id, (int) ntohl(sockadd.sin_addr.s_addr), ntohs(sockadd.sin_port), (int) ntohl(local_sockadd.sin_addr.s_addr), ntohs(local_sockadd.sin_port), i, toread); #endif /* DEBUG_IDENT_TOO */ queue_request(ident_id, &local_sockadd, &sockadd); break; case IDENT_CLIENT_CANCEL_REQUEST: memcpy(&ident_id, &req_buf[i], sizeof(ident_id)); i += sizeof(ident_id); break; default: #if defined(DEBUG_IDENT_TOO) logid("ident", "Ident_Server: Unknown message from client:" " '%d'\n", req_buf[i]); #endif /* DEBUG_IDENT_TOO */ break; } } } /* Queue up a request from the client and/or send it off */ void queue_request(ident_identifier id, struct sockaddr_in *local_sockadd, struct sockaddr_in *sockadd) { ident_request *new_id_req; new_id_req = (ident_request *) MALLOC(sizeof(ident_request)); memset(new_id_req, 0, sizeof(ident_request)); new_id_req->ident_id = id; memcpy(&(new_id_req->local_sockaddr), local_sockadd, sizeof(struct sockaddr_in)); new_id_req->next = NULL; memcpy(&(new_id_req->sock_addr), sockadd, sizeof(struct sockaddr_in)); if (!first_inq) { first_inq = new_id_req; } else if (!last_inq) { last_inq = new_id_req; first_inq->next = last_inq; } else { last_inq->next = new_id_req; last_inq = new_id_req; } } void take_off_queue(int freeslot) { ident_request *old; if (first_inq) { memcpy(&idents_in_progress[freeslot], first_inq, sizeof(ident_request)); old = first_inq; first_inq = first_inq->next; FREE(old); do_request(&idents_in_progress[freeslot]); } } void do_request(ident_request *id_req) { int dummy; int ret; struct sockaddr_in sa; #if defined(DEBUG_IDENT_TOO) logid("ident", "Server: Doing request - %d\n", id_req->ident_id); #endif /* DEBUG_IDENT_TOO */ if (-1 == (id_req->fd = socket(PF_INET, SOCK_STREAM, 0))) { #if defined(DEBUG_IDENT_TOO) logid("ident", "Couldn't get new fd for request"); #endif /* DEBUG_IDENT_TOO */ /* Erk, put on pending queue */ queue_request(id_req->ident_id, &(id_req->local_sockaddr), &(id_req->sock_addr)); return; } if (ioctl(id_req->fd, FIONBIO, (caddr_t)&dummy) < 0) { #if defined(DEBUG_IDENT_TOO) logid("ident", "Can't set non-blocking on request sock"); #endif /* DEBUG_IDENT_TOO */ /* Do without? */ } id_req->request_time = time(NULL); /* First bind to correct local address */ #if defined(__FreeBSD__) sa.sin_len = sizeof(sa); #endif /* __FreeBSD__ */ sa.sin_family = id_req->sock_addr.sin_family; sa.sin_port = htons(0); sa.sin_addr.s_addr = id_req->local_sockaddr.sin_addr.s_addr; ret = bind(id_req->fd, (struct sockaddr *)&sa, sizeof(sa)); if (ret != 0) { #if defined(DEBUG_IDENT_TOO) logid("ident", "do_Request:bind() error \"%s\"\nAddress was: %s, errno = %d\n", strerror(errno), inet_ntoa(sa.sin_addr), errno); #endif /* DEBUG_IDENT_TOO */ } /* Now try and connect to remote address */ sa.sin_addr.s_addr = id_req->sock_addr.sin_addr.s_addr; sa.sin_port = htons(IDENT_PORT); ret = connect(id_req->fd, (struct sockaddr *) &sa, sizeof(sa)); if (ret != 0 #if defined(EINPROGRESS) && errno != EINPROGRESS #endif /* EINPROGRESS */ ) { if (errno == ECONNREFUSED) { #if defined(DEBUG_IDENT_TOO) logid("ident", "ID %d, CONNREFUSED\n", id_req->ident_id); #endif /* DEBUG_IDENT_TOO */ id_req->flags |= IDENT_CONNREFUSED; } else { #if defined(DEBUG_IDENT_TOO) logid("ident", "Error on connect, NOT CONNREFUSED, errno = %d", errno); #endif /* DEBUG_IDENT_TOO */ } #if defined(EINPROGRESS) } else if (errno != EINPROGRESS) #else /* !EINPROGRESS */ } else #endif /* EINPROGRESS */ { id_req->flags |= IDENT_CONNECTED; write_request(id_req); } } void write_request(ident_request *id_req) { #if defined(DEBUG_IDENT_TOO) logid("ident", "Server: write_request()...\n"); #endif sprintf(scratch, "%d, %d\n", ntohs(id_req->sock_addr.sin_port), ntohs(id_req->local_sockaddr.sin_port)); if (-1 == write(id_req->fd, scratch, strlen(scratch))) { #if defined(DEBUG_IDENT_TOO) logid("ident", "Server: write() failed\n"); #endif id_req->flags |= IDENT_CONNREFUSED; } else { id_req->flags |= IDENT_WRITTEN; } #if defined(DEBUG_IDENT_TOO) logid("ident", "Server: Sending request '%s'\n", scratch); #endif /* DEBUG_IDENT_TOO */ } /* Get a result from an identd and send it to the client in the * form: * * <SERVER_SEND_REPLY><ident_id><reply text>'\n' */ void process_result(int slot) { char reply[BUFFER_SIZE]; int bread; char *s; char *t = NULL; char *reply_text = NULL; s = scratch; *s++ = IDENT_SERVER_SEND_REPLY; memcpy(s, &(idents_in_progress[slot].ident_id), sizeof(ident_identifier)); s += sizeof(ident_identifier); strcpy(s, "<ERROR>"); reply_text = s; #if defined(DEBUG_IDENT_TOO) logid("ident", "Server: Processing result...\n"); #endif /* DEBUG_IDENT_TOO */ if (idents_in_progress[slot].flags & IDENT_CONNREFUSED) { /* Connection was refused */ strcpy(s, "<CONN-REFUSED>"); t = strchr(s, '\0'); } else if (!(idents_in_progress[slot].flags & IDENT_CONNECTED)) { /* No connection was established */ strcpy(s, "<CONN-FAILED>"); t = strchr(s, '\0'); #if defined(DEBUG_IDENT_TOO) logid("ident", " Not Connected\n"); #endif /* DEBUG_IDENT_TOO */ } else if (!(idents_in_progress[slot].flags & IDENT_WRITTEN)) { /* Connection made but no message written */ strcpy(s, "<DIDN'T-SEND>"); t = strchr(s, '\0'); #if defined(DEBUG_IDENT_TOO) logid("ident", " Not Written\n"); #endif /* DEBUG_IDENT_TOO */ } else if (!(idents_in_progress[slot].flags & IDENT_REPLY_READY)) { /* Request written but didn't get reply before timeout */ strcpy(s, "<TIMED-OUT>"); t = strchr(s, '\0'); #if defined(DEBUG_IDENT_TOO) logid("ident", " Not Ready for Read\n"); #endif /* DEBUG_IDENT_TOO */ } else { /* Got a reply, phew! */ /* BUFFER_SIZE - 20 (== 1004 atm) should be plenty as RFC1413 * specifies that a USERID reply should not have a user id field * of more than 512 octets. * Additionally RFC1413 specifies that: * "Clients should feel free to abort the connection if they * receive 1000 characters without receiving an <EOL>" * * NB: <EOL> ::= "015 012" ; CR-LF End of Line Indicator * I assume that under C this will be converted to '\n' */ #if defined(HAVE_BZERO) bzero(reply, BUFFER_SIZE); #else /* !HAVE_BZERO */ memset(reply, 0, BUFFER_SIZE); #endif /* HAVE_BZERO */ bread = read(idents_in_progress[slot].fd, reply, BUFFER_SIZE - 20); reply[bread] = '\0'; /* Make sure the reply is '\n' terminated */ t = strchr(reply, '\n'); if (t) { t++; #if defined(HAVE_BZERO) bzero(t, &reply[BUFFER_SIZE] - t - 1); #else memset(t, 0, &reply[BUFFER_SIZE] - t - 1); #endif /* HAVE_BZERO */ } else { reply[1001] = '\n'; #if defined(HAVE_BZERO) bzero(&reply[1002], BUFFER_SIZE - 1002 - 1); #else memset(&reply[1002], 0, BUFFER_SIZE - 1002 - 1); #endif /* HAVE_BZERO */ } #if defined(DEBUG_IDENT_TOO) logid("ident", "Server: Got reply '%s'\n", reply); #endif /* DEBUG_IDENT_TOO */ if (!(t = strstr(reply, "USERID"))) { /* In this case the reply MUST be in the form: * * <port-pair> : ERROR : <error-type> * * (see RFC1413, if a later one exists for the ident protocol * then this code should be updated) * * We will reply to our client with the '<error-type>' text * */ #if defined(DEBUG_IDENT) logid("ident_ids", "SERVER: slot %d, ident_id %d, ACTUAL reply '%s'", slot, idents_in_progress[slot].ident_id, reply); #endif /* DEBUG_IDENT */ if (!(t = strstr(reply, "ERROR"))) { /* Reply doesn't contain 'USERID' *or* 'ERROR' */ strcpy(s, "<ERROR>"); } else { #if defined(DEBUG_IDENT_TOO) logid("ident", "Reply: '%s'", t); #endif /* DEBUG_IDENT_TOO */ t = strchr(t, ':'); /* * <port-pair> : ERROR : <error-type> * ^ * t-------| */ if (!t) { /* Couldn't find the colon after 'ERROR' */ strcpy(s, "<ERROR>"); } else { *s++ = '<'; } } } else { /* Got a reply of the form: * * <port-pair> : USERID : <opsys-field> : <user-id> * ^ * t-----| * * We will reply to our client with the '<user-id>' text * */ #if defined(DEBUG_IDENT) logid("ident_ids", "SERVER: slot %d, ident_id %d, ACTUAL reply '%s'", slot, idents_in_progress[slot].ident_id, reply); logid("ident", "Reply: '%s'", t); #endif /* DEBUG_IDENT */ t = strchr(t, ':'); if (!t) { /* Couldn't find the : after USERID * * <port-pair> : USERID : <opsys-field> : <user-id> * ^ * t----------| */ strcpy(s, "<ERROR>"); } else { t++; t = strchr(t, ':'); if (!t) { /* Couldn't find the : after <opsys-field> * * <port-pair> : USERID : <opsys-field> : <user-id> * ^ * t----------| */ strcpy(s, "<ERROR>"); } } } if (t) { /* t is now at the : before the field we want to return */ /* Skip the : */ t++; /* Skip any white space */ while ((*t == ' ' || *t == '\t') && *t != '\0') { t++; } if (*t != '\0') { /* Found start of the desired text */ sprintf(s, "%s", t); t = strchr(s, '\0'); t--; /* The reply SHOULD be '\n' terminated (RFC1413) */ while (*t == ' ' || *t == '\t' || *t == '\n' || *t == '\r') { t--; } /* t now at end of text we want */ /* Move forward to next char */ t++; /* If char before s is a '<' we put it there ourselves to wrap * an 'ERROR' return in <>. So we need to put the > on the * end */ if (*(s - 1) == '<') { *t++ = '>'; } } else { strcpy(s, "<ERROR>"); t = 0; } } } /* Make sure t is pointing to the NULL terminator of the string */ if (!t) { t = strchr(s, '\0'); } /* A newline terminates the message */ *t++ = '\n'; *t = '\0'; /* Now actually send the reply to the client */ write(1, scratch, (t - scratch)); #if defined(DEBUG_IDENT) logid("ident_ids", "SERVER: slot %d, ident_id %d, reply '%s'", slot, idents_in_progress[slot].ident_id, reply_text); #endif /* DEBUG_IDENT */ #if defined(DEBUG_IDENT_TOO) *(t + 1) = '\0'; logid("ident", " Sending reply '%s' of %d bytes\n", s, (t - scratch)); #endif /* DEBUG_IDENT_TOO */ closedown_request(slot); } void closedown_request(int slot) { shutdown(idents_in_progress[slot].fd, 2); close(idents_in_progress[slot].fd); idents_in_progress[slot].fd = -1; } /* log using stdarg variable length arguments */ void logid(char *str, ...) { va_list args; FILE *fp; char *fmt; struct tm *tim; char the_time[256]; char fname[21]; time_t current_time; va_start(args, str); current_time = time(0); tim = localtime(¤t_time); strftime((char *)&the_time, 255, "%H:%M:%S %Z - %d/%m/%Y - ", tim); sprintf(fname, "logs/%s.log", str); fp = fopen(fname, "a"); if (!fp) { fprintf(stderr, "Eek! Can't open file to log: %s\n", fname); return; } fprintf(fp, "%s", the_time); fmt = va_arg(args, char *); vfprintf(fp, fmt, args); va_end(args); fprintf(fp, "\n"); fclose(fp); }