/************************************************************************ Realms of Aurealis James Rhone aka Vall of RoA rclient.c RoAClient GAMMA 0.9. Original project started 5/5/97. RoA Specific client capable of receiving server sent binaries (including sound files) and manipulating them via user defined techniques (sound players). ******** 100% Completely Original Code ******** *** BE AWARE OF ALL RIGHTS AND RESERVATIONS *** ******** 100% Completely Original Code ******** All rights reserved henceforth. Please note that no guarantees are associated with any code from Realms of Aurealis. All code which has been released to the general public has been done so with an 'as is' pretense. RoAClient is a completely original program with the exception of the readline suite mentioned below. Some of the original design was derived from Tintin++, though no code was directly utilized. Credit is given where credit is due. *** Read, Learn, Understand, Improve *** *************************************************************************/ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/wait.h> #include <sys/time.h> #include <unistd.h> #include <fcntl.h> #include <string.h> #include <signal.h> #include <netdb.h> #include <netinet/in.h> #include "../defines.h" #include "../structures.h" #include "../utils.h" #include "../clicomm.h" #include "rclient.h" // Global variables char *cversion = "RoAClient v1.0 GAMMA"; int chook = -1; /* global client<->mud socket */ int mudport; /* port number to connect to*/ BOOL hide_text = 0; /* echo or no echo? global */ char trans[MAX_STRING_LENGTH]; int sound_pid = -1; /* child playing sound file's pid */ int file_dl_pid = -1; /* large download child pid */ char ipaddy[MAX_INPUT_LENGTH]; char ipport[MAX_INPUT_LENGTH]; // prototypes... void rclient_login(char *name, char *pword); int doio(int sok); int main(int argc, char **argv) { char buf[MAX_STRING_LENGTH]; char name[MAX_INPUT_LENGTH]; char pword[MAX_INPUT_LENGTH]; if (argc != 2 && argc != 3) { printf("Usage: %s <RoAServer IP [port]> \n", argv[0]); exit(-1); } csay(cversion); // bounce them over... strcpy(ipaddy, argv[1]); if (argc == 3) mudport = atoi(argv[2]); else mudport = 4000; sprintf(buf, "Attempting %s, port %d.", ipaddy, mudport); csay(buf); // we need the username and password now... printf("Username: "); fgets(name, 80, stdin); name[strlen(name) - 1] = '\0'; printf("Password: "); fgets(pword, 80, stdin); pword[strlen(pword) - 1] = '\0'; signal(SIGINT, (void *) goodnight); signal(SIGTERM, (void *) goodnight); signal(SIGCHLD, (void *) reaper); // ok, get a client connection... chook = get_roa_socket(ipaddy, mudport+1); if (chook >= 0) csay("Connected to RoAServer."); else { csay("Unable to connect to RoAServer. Exiting."); exit(-1); } // ok, we're connected, send name and passwd... rclient_login(name, pword); // we'll sit in here forever, until it's over... doio(chook); client_exit(); } // write it over the wire... int send_dblock(dblock *blk) { int cnt, size, bufsize, retval = -1, nbytes = 0; char *sndbuf; void (*func)(); #ifdef DEBUG switch(blk->type) { case LOGIN_REQUEST: csay("Sending login request."); break; case SOUND_NOGO: csay("Sending sound nogo."); break; case SOUND_GO: csay("Sending sound go."); break; case SOUND_STOP: csay("Sending sound stop."); break; case PIC_NOGO: csay("Sending pic nogo."); break; case PIC_GO: csay("Sending pic go."); break; case PIC_STOP: csay("Sending pic stop."); break; case EDIT_REPLY: csay("Sending edit reply."); break; } #endif func = signal(SIGPIPE, SIG_IGN); size = sizeof(dblock) + blk->streamlen; bufsize = size + sizeof(int); CREATE(sndbuf, char, bufsize); if (!sndbuf) { csay("OUT OF MEMORY!!"); client_exit(); } // assemble the three pieces... header, block, stream memcpy(sndbuf, (void *) &size, sizeof(int)); memcpy(&(sndbuf[sizeof(int)]), blk, sizeof(dblock)); memcpy(&(sndbuf[sizeof(int)+sizeof(dblock)]), blk->stream, blk->streamlen); nbytes = mywrite(chook, sndbuf, bufsize); if (nbytes != bufsize) { FREENULL(sndbuf); printf("ERROR: %d bytes out of %d write on chook: %d\n", nbytes, bufsize, chook); csay("Write error: send_dblock"); perror("send_dblock:"); client_exit(); } else retval = bufsize; FREENULL(sndbuf); signal(SIGPIPE, func); return retval; } int myread(int sok, char *trans, int length) { int cnt, sofar; sofar = 0; while (sofar < length) { cnt = read(sok, trans + sofar, length - sofar); if (cnt < 0) { if (errno == EAGAIN) { printf("\r%d out of %d bytes read...", sofar, length); continue; } else return cnt; } else if (cnt == 0) { csay("Connection closed by server...leaving."); client_exit(); } sofar += cnt; printf("\r%d out of %d bytes read...", sofar, length); } printf("\n"); return sofar; } int mywrite(int sok, char *trans, int length) { int cnt, sofar; sofar = 0; while (sofar < length) { cnt = write(sok, trans + sofar, length - sofar); if (cnt < 0) { if (errno == EAGAIN) { printf("\r%d out of %d bytes wrote...", sofar, length); continue; } else return cnt; } if (cnt == 0) { csay("Connection closed by server...leaving."); client_exit(); } sofar += cnt; printf("\r%d out of %d bytes wrote...", sofar, length); } printf("\n"); return sofar; } // read in a data block from this descriptor // this allocs memory for the data block, BTW int read_dblock(void **blk_ptr) { int size; int nbytes; char *msg; void (*func)(); func = signal(SIGPIPE, SIG_IGN); errno = 0; // error check here... nbytes = myread(chook, (char *)&size, sizeof(int)); if (nbytes != sizeof(int)) { printf("ERROR: %d bytes out of %d read on chook: %d\n", nbytes, sizeof(int), chook); perror("hmm:"); csay("Client communication error, exiting."); csay("Client communication error, exiting."); csay("Client communication error, exiting."); client_exit(); } printf("Allocating for %d size...\n", size); msg = (char *)malloc(size); if (!msg) { csay("Read error in read_dblock...out of memory."); return -1; } nbytes = myread(chook, msg, size); if (nbytes != size) { printf("ERROR: %d bytes out of %d read on chook: %d\n", nbytes, size, chook); perror("hmm:"); csay("Client communication error, exiting."); csay("Client communication error, exiting."); csay("Client communication error, exiting."); client_exit(); } // else... we read the right size... *blk_ptr = msg; signal(SIGPIPE, func); return nbytes + sizeof(int);; } // send our name and passwd... void rclient_login(char *name, char *pword) { dblock blk; blk.type = LOGIN_REQUEST; blk.version = 2; strcpy(blk.str1, name); strcpy(blk.str2, pword); blk.streamlen = 0; blk.stream = NULL; send_dblock(&blk); } int reaper() { int status; int pid; pid = wait3(&status, WNOHANG, (struct rusage *)0); #ifdef DEBUG printf("Child %d exited.\n\r", pid); #endif signal(SIGCHLD, (void *) reaper); } int goodnight() { csay("Signal received. Closing connection.\n"); client_exit(); } void client_exit(void) { csay("RoAClient exiting."); /* don't check for errors, we don't care right now */ if (sound_pid > 0) kill(sound_pid, SIGKILL); close(chook); audi(); } void audi() { system("stty sane"); exit(1); } void csay(char *txt) { printf("RoAC**> %s\n\r",txt); fflush(stdout); } // open up a client connection ... int get_roa_socket(char *ipname, int port) { struct sockaddr_in srvadr_in; struct hostent *sp; int addrlen,sok,cnt,opt; char buf[MAX_STRING_LENGTH]; addrlen = sizeof(struct sockaddr_in); memset((char *)&srvadr_in, 0, addrlen); srvadr_in.sin_family = AF_INET; sp = gethostbyname(ipname); if (!sp) { csay("Unable to gethostbyname."); exit(1); } srvadr_in.sin_addr.s_addr = ((struct in_addr *)(sp->h_addr))->s_addr; srvadr_in.sin_port = htons(port); sok = socket(PF_INET, SOCK_STREAM, 0); if (sok < 0) { perror("Init-mud-socket"); exit(0); } #if defined(SO_SNDBUF) opt = MAX_STRING_LENGTH + 128; if (setsockopt(sok, SOL_SOCKET, SO_SNDBUF, (char *) &opt, sizeof(opt)) < 0) { perror("setsockopt SNDBUF"); exit(1); } #endif opt = 1; if (setsockopt(sok, SOL_SOCKET, SO_REUSEADDR, (char *) & opt, sizeof (opt)) < 0) { perror ("setsockopt REUSEADDR"); exit (0); } #if defined(SO_LINGER) { struct linger ld; ld.l_onoff = 0; ld.l_linger = 0; if (setsockopt(sok, SOL_SOCKET, SO_LINGER, (char *) &ld, sizeof(ld)) < 0) { perror("setsockopt LINGER"); exit(1); } } #endif if (connect(sok, (struct sockaddr *)&srvadr_in, addrlen) < 0) { sprintf(buf, "Unable to connect to port %d.",port); csay(buf); return -1; } // nonblock(sok); return sok; } /* display header info from TYPE_TEXT_MESSAGE from server */ void display_server_text_message(char *mesg) { csay("Message from server:"); csay(mesg); } int send_sound_GO(char *fname) { dblock blk; blk.type = SOUND_GO; blk.version = 2; strcpy(blk.str1, fname); strcpy(blk.str2, ""); blk.streamlen = 0; blk.stream = NULL; send_dblock(&blk); return 1; } int send_sound_NOGO(char *fname) { dblock blk; blk.type = SOUND_NOGO; blk.version = 2; strcpy(blk.str1, fname); strcpy(blk.str2, ""); blk.streamlen = 0; blk.stream = NULL; send_dblock(&blk); return 1; } /* read incoming message, decode it, and process as required */ /* call appropriate functions depending on message type */ int read_socket(int sok) { int cnt, size, i, sofar = 0; int type, length, togo, chunk; char fname[MAX_INPUT_LENGTH]; char *str; FILE *fp; char buf[MAX_STRING_LENGTH]; char trans[MAX_STRING_LENGTH]; void *sfile; dblock *blk; if (read_dblock((void*)&blk) < 0) { close(sok); csay("Unable to read_dblock in read_socket, leaving..."); client_exit(); return -1; } // if we have a stream, offset the pointer correctly... if (blk->streamlen > 0) blk->stream = (void *) &blk[1]; #ifdef DEBUG sprintf(buf, "Client received header version %d.", blk->version); csay(buf); #endif /* act on type */ switch (blk->type) { /* read message length, then read length bytes and send */ case TEXT_MESG: display_server_text_message(blk->str1); FREENULL(blk); break; case TEXT_FILE: /* check for fname here, see if have to dl or not */ /* right now assume we have to dl */ sprintf(trans, "text/%s", blk->str1); if (!(fp = fopen(trans, "wt"))) { sprintf(buf, "Unable to open %s for write.", trans); csay(buf); FREENULL(blk); return -1; } fprintf(fp, "%s", (char *)blk->stream); fclose(fp); sprintf(buf, "Text file: %s written successfully.", blk->str1); csay(buf); FREENULL(blk); break; case SOUND_QUERY: csay("Query received."); sprintf(trans, "binary/%s", blk->str1); if ((fp = fopen(trans, "r"))) { fclose(fp); send_sound_NOGO(blk->str1); sprintf(buf, "(%d) Playing sound file.", getpid()); csay(buf); play_sound(trans); } else send_sound_GO(blk->str1); FREENULL(blk); break; case SOUND_FILE: csay("Sound_File received."); sprintf(trans, "binary/%s", blk->str1); if (!(fp = fopen(trans, "wb"))) { sprintf(buf, "Unable to open %s for write.", trans); csay(buf); FREENULL(blk); return 1; } if (fwrite(blk->stream, 1, blk->streamlen, fp) < blk->streamlen) { csay("TYPE BINARY SOUNDFILE: sound fwrite error."); } fclose(fp); FREENULL(blk); sprintf(buf, "Sound file: %s written successfully.", trans); csay(buf); sprintf(buf, "(%d) Playing sound file.", getpid()); csay(buf); play_sound(trans); break; case EDIT_REQUEST: csay("Edit request received....sleeping (debug)."); sleep(300); blk->type = EDIT_REPLY; send_dblock(blk); FREENULL(blk); break; default: break; } return 1; } /* the main I/O loop -roa */ int doio(int sok) { fd_set readfds; int rv, maxdesc; while (TRUE) { FD_ZERO(&readfds); FD_SET(sok, &readfds); /* mud connection */ // select forever rv = select(sok +1, &readfds, NULL, NULL, 0); if (rv == 0) continue; else if (rv < 0) { if (errno != EINTR) csay("select error"); continue; } /* if sok has something to say, lets grab it */ if (FD_ISSET(sok, &readfds)) if (read_socket(sok) < 0) { perror("read error..."); csay("READ ERROR"); close(sok); audi(); } } } /* stop the socket from blocking */ void nonblock(int s) { int flags; flags = fcntl(s, F_GETFL); flags |= O_NONBLOCK; if (fcntl(s, F_SETFL, flags) < 0) { perror("Fatal error executing nonblock (comm.c)"); exit(1); } } /* is it a number, ripped from circle */ int is_number(char *str) { int look_at; if (*str == '\0') return(0); for (look_at = 0; *(str + look_at) != '\0'; look_at++) if ((*(str + look_at) < '0') || (*(str + look_at) > '9')) return(0); return(1); } /* this function forks off a child, closes its stdin/out and execs the sound player with fname as an argument parent simply returns 1 */ int play_sound(char *fname) { int midi = FALSE; int mod = FALSE; int au = FALSE; int wav = FALSE; char buf[512]; /* if already playing, killit and play this */ /* don't check for errors, we don't care right now */ if (sound_pid > 0) if (kill(sound_pid, SIGKILL) < 0) csay("Error killing prior sound pid."); if (strstr(fname, ".mid") || strstr(fname, ".MID")) midi = TRUE; else if (strstr(fname, ".wav") || strstr(fname, ".WAV")) wav = TRUE; else if (strstr(fname, ".au") || strstr(fname, ".AU")) au = TRUE; else if (strstr(fname, ".mod") || strstr(fname, ".MOD")) mod = TRUE; fflush(NULL); sound_pid = fork(); if (!sound_pid) /* child mon */ { close(0); close(1); /* reroute these to syslog at some point */ /* close up the chook descriptor */ close(chook); if (midi) execl(MIDIPLAYER, "rclient_midi", fname, "-g", (char*)0); if (mod) execl(MODPLAYER, "rclient_mod", fname, (char*)0); if (au) execl(AUPLAYER, "rclient_au", fname, (char*)0); if (wav) { // execl(WAVPLAYER, "rclient_wav", fname, "-t", ".wav", (char*)0); sprintf(buf, "%s %s -t .wav /dev/audio", WAVPLAYER, fname); system(buf); exit(1); } csay("EXEC ERROR."); client_exit(); } else if (sound_pid < 0) /* error mon */ { csay("Forking error play_sound."); client_exit(); } /* parent, just return */ return 1; }