mooproxy-1.0.0/0000755000175000017500000000000011525336516013235 5ustar marcelmmarcelmmooproxy-1.0.0/mooproxy.c0000644000175000017500000002563211525336516015305 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include #include #include #include #include "global.h" #include "daemon.h" #include "config.h" #include "world.h" #include "network.h" #include "timer.h" #include "log.h" #include "mcp.h" #include "misc.h" #include "command.h" #include "resolve.h" #include "crypt.h" #include "line.h" static void die( World*, char * ); static void mainloop( World * ); static void handle_flags( World * ); static void print_help_text( void ); static void print_version_text( void ); static void print_license_text( void ); int main( int argc, char **argv ) { Config config; World *world = NULL; char *err = NULL, *warn = NULL; pid_t pid; int i; setlinebuf( stdout ); /* Parse commandline options */ parse_command_line_options( argc, argv, &config ); /* Ok, what is it the user wants us to do? */ switch( config.action ) { case PARSEOPTS_ERROR: die( world, config.error ); exit( EXIT_FAILURE ); case PARSEOPTS_HELP: print_help_text(); exit( EXIT_SUCCESS ); case PARSEOPTS_VERSION: print_version_text(); exit( EXIT_SUCCESS ); case PARSEOPTS_LICENSE: print_license_text(); exit( EXIT_SUCCESS ); case PARSEOPTS_MD5CRYPT: prompt_to_md5hash(); exit( EXIT_SUCCESS ); } /* Create the configuration dir hierarchy. */ if( create_configdirs( &err ) != 0 ) die( world, err ); /* Check if we received a world name. */ if( config.worldname == NULL || config.worldname[0] == '\0' ) die( world, xstrdup( "You must supply a world name." ) ); /* Announce that we are starting up. */ printf( "Starting mooproxy " VERSIONSTR " at %s.\n", time_fullstr( time( NULL ) ) ); /* Register startup time. */ uptime_started_now(); /* Register the signal handlers (duh). */ set_up_signal_handlers(); /* Create the uninitialized world now. */ world = world_create( config.worldname ); world_configfile_from_name( world ); /* Check the permissions on the configuration dirs/files, and * warn the user if they're too weak. */ if( check_configdir_perms( &warn, &err ) != 0 ) die( world, err ); if( warn ) { printf( "%s", warn ); free( warn ); } /* Make sure this world isn't open yet. */ if( world_acquire_lock_file( world, &err ) ) die( world, err ); /* Load the world's configuration. */ printf( "Opening world %s.\n", config.worldname ); if( world_load_config( world, &err ) != 0 ) die( world, err ); /* Refuse to start if the authentication string is absent. */ if( world->auth_hash == NULL ) die( world, xstrdup( "No authentication string given in " "configuration file. Refusing to start." ) ); /* Bind to network port */ printf( "Binding to port.\n" ); world_bind_port( world, world->listenport ); /* Fatal error, abort. */ if( world->bindresult->fatal ) die( world, xstrdup( world->bindresult->fatal ) ); /* Print the result for each address family. */ for( i = 0; i < world->bindresult->af_count; i++ ) printf( " %s %s\n", world->bindresult->af_success[i] ? " " : "!", world->bindresult->af_msg[i] ); /* We need success on at least one address family. */ if( world->bindresult->af_success_count == 0 ) die( world, xstrdup( world->bindresult->conclusion ) ); printf( "%s\n", world->bindresult->conclusion ); /* Move the listening FDs to the world. */ world->listen_fds = world->bindresult->listen_fds; world->bindresult->listen_fds = NULL; /* Stay on foreground, or daemonize. */ if( config.no_daemon ) { pid = getpid(); world_write_pid_to_file( world, pid ); printf( "Mooproxy successfully started. Staying in " "foreground (pid %li).\n", (long) pid ); } else { pid = daemonize( &err ); /* Handle errors. */ if( pid == -1 ) die( world, err ); /* If pid > 0, we're the parent. Say goodbye to the user! */ if( pid > 0 ) { world_write_pid_to_file( world, pid ); printf( "Mooproxy successfully started in PID %li.\n", (long) pid ); launch_parent_exit( EXIT_SUCCESS ); } /* If pid == 0, we continue as the child. */ } /* Initialization done, enter the main loop. */ mainloop( world ); /* Clean up a bit. */ world_remove_lockfile( world ); world_sync_logdata( world ); world_log_link_remove( world ); world_destroy( world ); exit( EXIT_SUCCESS ); } /* If wld is not NULL, destroy it. Print err. Terminate with failure. */ static void die( World *wld, char *err ) { if( wld != NULL && wld->lockfile != NULL ) world_remove_lockfile( wld ); if( wld != NULL ) world_destroy( wld ); fprintf( stderr, "%s\n", err ); free( err ); exit( EXIT_FAILURE ); } /* The main loop which ties everything together. */ static void mainloop( World *wld ) { time_t last_checked = time( NULL ), ltime; Line *line; /* Initialize the time administration. */ world_timer_init( wld, last_checked ); set_current_time( last_checked ); /* Log the fact that we started. */ line = world_msg_client( wld, "Started mooproxy v" VERSIONSTR "." ); line->flags = LINE_LOGONLY; /* Loop forever. */ for(;;) { /* Wait for input from network to be placed in rx queues. */ wait_for_network( wld ); /* See if another second elapsed */ ltime = time( NULL ); if( ltime != last_checked ) world_timer_tick( wld, ltime ); last_checked = ltime; /* Dispatch lines from the server */ while( ( line = linequeue_pop( wld->server_rxqueue ) ) ) { if( world_is_mcp( line->str ) ) { /* MCP */ world_do_mcp_server( wld, line ); } else { /* Regular */ linequeue_append( wld->client_toqueue, line ); if( wld->easteregg_version ) world_easteregg_server( wld, line ); } } /* Dispatch lines from the client */ while( ( line = linequeue_pop( wld->client_rxqueue ) ) ) { if( world_do_command( wld, line->str ) ) { /* Command (those are activating). */ wld->flags |= WLD_ACTIVATED; line_destroy( line ); } else if( world_is_mcp( line->str ) ) { /* MCP. */ world_do_mcp_client( wld, line ); } else { /* Regular lines are always activating. */ wld->flags |= WLD_ACTIVATED; linequeue_append( wld->server_toqueue, line ); } } /* Process server toqueue to txqueue */ linequeue_merge( wld->server_txqueue, wld->server_toqueue ); /* Process client toqueue to txqueue */ while( ( line = linequeue_pop( wld->client_toqueue ) ) ) { /* Log if logging is enabled, and the line is loggable */ if( wld->logging && !( line->flags & LINE_DONTLOG ) ) world_log_line( wld, line ); /* If the same line makes it back here again, it should not * be logged again */ line->flags |= LINE_DONTLOG; /* Only process the line further if it's not LOGONLY. */ if( line->flags & LINE_LOGONLY ) line_destroy( line ); else linequeue_append( wld->client_txqueue, line ); } /* If we aren't connected to the client, move queued lines to the * buffer. */ if( wld->client_status != ST_CONNECTED ) { while( ( line = linequeue_pop( wld->client_txqueue ) ) ) if( line->flags & LINE_DONTBUF ) line_destroy( line ); else linequeue_append( wld->buffered_lines, line ); } /* Flush the send buffers */ world_flush_client_logqueue( wld ); world_flush_server_txbuf( wld ); world_flush_client_txbuf( wld ); /* Trim the dynamic buffers if they're too long */ world_trim_dynamic_queues( wld ); handle_flags( wld ); if( wld->flags & WLD_SHUTDOWN ) return; } } /* Handle wld->flags. Clear each flag which has been handled. */ static void handle_flags( World *wld ) { if( wld->flags & WLD_ACTIVATED ) { wld->flags &= ~WLD_ACTIVATED; world_inactive_to_history( wld ); } if( current_time() < wld->client_last_notconnmsg + NOTCONN_MSGINTERVAL ) wld->flags &= ~WLD_NOTCONNECTED; if( wld->flags & WLD_NOTCONNECTED ) { wld->flags &= ~WLD_NOTCONNECTED; if( wld->server_status == ST_RESOLVING || wld->server_status == ST_CONNECTING ) world_msg_client( wld, "Connection attempt (to %s) " "still in progress!", wld->server_host ); else world_msg_client( wld, "Not connected to server!" ); wld->client_last_notconnmsg = current_time(); } if( wld->flags & WLD_CLIENTQUIT ) { wld->flags &= ~WLD_CLIENTQUIT; world_disconnect_client( wld ); } if( wld->flags & WLD_SERVERQUIT ) { wld->flags &= ~WLD_SERVERQUIT; world_disconnect_server( wld ); } if( wld->flags & WLD_RECONNECT ) { wld->flags &= ~WLD_RECONNECT; world_schedule_reconnect( wld ); } if( wld->flags & WLD_SERVERRESOLVE ) { wld->flags &= ~WLD_SERVERRESOLVE; world_start_server_resolve( wld ); } if( wld->flags & WLD_SERVERCONNECT ) { wld->flags &= ~WLD_SERVERCONNECT; world_start_server_connect( wld ); } if( wld->flags & WLD_LOGLINKUPDATE ) { wld->flags &= ~WLD_LOGLINKUPDATE; world_log_link_update( wld ); } if( wld->flags & WLD_REBINDPORT ) { wld->flags &= ~WLD_REBINDPORT; world_rebind_port( wld ); } } /* Print the help output (-h, --help) */ static void print_help_text( void ) { printf( "Mooproxy - a buffering proxy for MOO connections\n" "Copyright (C) %s Marcel L. Moreaux \n" "\n" "Usage: mooproxy [options]\n" "\n" " -h, --help shows this help screen and exits\n" " -V, --version shows version information and exits\n" " -L, --license shows licensing information and exits\n" " -w, --world world to load\n" " -d, --no-daemon forces mooproxy to stay in the foreground\n" " -m, --md5crypt prompts for a string to create an md5 hash of\n" "\n" "Released under the GPL v2, report bugs to \n" "Mooproxy comes with ABSOLUTELY NO WARRANTY; " "for details run mooproxy --license\n", COPYYEARS ); } /* Print the version output (-V, --version) */ static void print_version_text( void ) { printf( "Mooproxy version %s (released on %s).\n" "Copyright (C) %s Marcel L Moreaux\n", VERSIONSTR, RELEASEDATE, COPYYEARS ); } /* Print the license output (-L, --license) */ static void print_license_text( void ) { printf( "Mooproxy - a buffering proxy for MOO connections\n" "Copyright (C) %s Marcel L. Moreaux \n" "\n" "This program is free software; you can redistribute it and/or modify\n" "it under the terms of the GNU General Public License as published by\n" "the Free Software Foundation; version 2 dated June, 1991.\n" "\n" "This program is distributed in the hope that it will be useful,\n" "but WITHOUT ANY WARRANTY; without even the implied warranty of\n" "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n" "GNU General Public License for more details.\n", COPYYEARS ); } mooproxy-1.0.0/line.c0000644000175000017500000000753511525336516014342 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include #include "line.h" #include "misc.h" /* The estimated memory cost of a line object: the size of the object itself, * and some random guess at memory management overhead */ #define LINE_BYTE_COST ( sizeof( Line ) + sizeof( void * ) * 2 + 8 ) extern Line *line_create( char *str, long len ) { Line *line; line = xmalloc( sizeof( Line ) ); line->str = str; line->len = ( len == -1 ) ? strlen( str ) : len; line->flags = LINE_REGULAR; line->prev = NULL; line->next = NULL; line->time = current_time(); line->day = current_day(); return line; } extern void line_destroy( Line *line ) { if( line ) free( line->str ); free( line ); } extern Line *line_dup( Line *line ) { Line *newline; newline = xmalloc( sizeof( Line ) ); newline->str = xmalloc( line->len + 1 ); strcpy( newline->str, line->str ); newline->len = line->len; newline->flags = line->flags; newline->time = line->time; newline->day = line->day; newline->prev = NULL; newline->next = NULL; return newline; } extern Linequeue *linequeue_create( void ) { Linequeue *queue; queue = xmalloc( sizeof( Linequeue ) ); queue->head= NULL; queue->tail = NULL; queue->count = 0; queue->size = 0; return queue; } extern void linequeue_destroy( Linequeue *queue ) { linequeue_clear( queue ); free( queue ); } extern void linequeue_clear( Linequeue *queue ) { Line *line, *next; for( line = queue->head; line != NULL; line = next ) { next = line->next; line_destroy( line ); } queue->head = NULL; queue->tail = NULL; queue->count = 0; queue->size = 0; } extern void linequeue_append( Linequeue *queue, Line *line ) { line->next = NULL; if( !queue->head ) { /* Only line in queue, there is no previous, and head should * point to this line, too. */ line->prev = NULL; queue->head = line; } else { /* There are other lines, last line's next should point * to the new line, and new line's prev should point to * previous tail. */ queue->tail->next = line; line->prev = queue->tail; } queue->tail = line; queue->count++; queue->size += line->len + LINE_BYTE_COST; } extern Line* linequeue_pop( Linequeue *queue ) { return linequeue_remove( queue, queue->head ); } extern Line* linequeue_popend( Linequeue *queue ) { return linequeue_remove( queue, queue->tail ); } extern Line *linequeue_remove( Linequeue *queue, Line *line ) { if( !line ) return line; if( line->next ) line->next->prev = line->prev; else queue->tail = line->prev; if( line->prev ) line->prev->next = line->next; else queue->head = line->next; queue->count--; queue->size -= line->len + LINE_BYTE_COST; line->prev = NULL; line->next = NULL; return line; } extern void linequeue_merge( Linequeue *one, Linequeue *two ) { /* If the second list is empty, nop. */ if( two->head == NULL || two->tail == NULL ) return; /* If the first list is empty, simply copy the references. */ if( one->head == NULL || one->tail == NULL ) { one->head = two->head; one->tail = two->tail; } else { one->tail->next = two->head; two->head->prev = one->tail; one->tail = two->tail; } one->count += two->count; one->size += two->size; /* Two is now empty. */ two->head = NULL; two->tail = NULL; two->count = 0; two->size = 0; } mooproxy-1.0.0/mcp.h0000644000175000017500000000250111525336516014163 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__MCP #define MOOPROXY__HEADER__MCP #include "world.h" #include "line.h" /* Checks if line is a MCP command. Returns 1 if it is, 0 if not. */ extern int world_is_mcp( char *line ); /* Handle a MCP line from the client. line is consumed. */ extern void world_do_mcp_client( World *wld, Line *line ); /* Handle a MCP line from the server. line is consumed. */ extern void world_do_mcp_server( World *wld, Line *line ); /* Call when we just connected to the server, to handle MCP administration. */ extern void world_mcp_server_connect( World *wld ); /* Call when the client connected, to handle MCP administration */ extern void world_mcp_client_connect( World *wld ); #endif /* ifndef MOOPROXY__HEADER__MCP */ mooproxy-1.0.0/crypt.c0000644000175000017500000001301311525336516014540 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #define _XOPEN_SOURCE /* For crypt(). */ #include #include #include #include #include #include #include #include #include #include "crypt.h" #include "misc.h" static char *prompt_for_password( char *msg ); /* Used to encode 6-bit integers into human-readable ASCII characters */ static const char *seedchars = "./0123456789ABCDEFGHIJKLMNOPQRST" "UVWXYZabcdefghijklmnopqrstuvwxyz"; extern void prompt_to_md5hash( void ) { struct timeval tv1, tv2; char salt[] = "$1$........"; char *password, *password2; gettimeofday( &tv1, NULL ); password = prompt_for_password( "Authentication string: " ); password2 = prompt_for_password( "Re-type authentication string: " ); gettimeofday( &tv2, NULL ); printf( "\n" ); /* No typos allowed! */ if( strcmp( password, password2 ) ) { printf( "The supplied strings are not equal, aborting.\n" ); free( password ); free( password2 ); return; } /* FIXME: I should really use /dev/random or something for this. */ /* Construct the salt. * Bits 47..36: lowest 12 bits of mooproxy's PID. */ salt[ 3] = seedchars[( getpid() / 64 ) % 64]; salt[ 4] = seedchars[getpid() % 64]; /* Bits 35..24: lowest 12 bits of time(). */ salt[ 5] = seedchars[( time( NULL ) / 64 ) % 64 ]; salt[ 6] = seedchars[time( NULL ) % 64]; /* bits 23..12: lowest 12 bits of microseconds in gettimeofday() * _before_ prompting for strings. */ salt[ 7] = seedchars[( tv1.tv_usec / 64 ) % 64]; salt[ 8] = seedchars[tv1.tv_usec % 64]; /* bits 11..0: lowest 12 bits of microseconds in gettimeofday() * _after_ prompting for strings. */ salt[ 9] = seedchars[( tv2.tv_usec / 64 ) % 64]; salt[10] = seedchars[tv2.tv_usec % 64]; printf( "MD5 hash: %s\n", crypt( password, salt ) ); if( !strcmp( password, "" ) ) printf( "Note: this is a hash of an empty string; " "it will be refused by mooproxy.\n" ); free( password ); free( password2 ); return; } /* Print msg to stdout, disable terminal input echo, and read password from * stdin. */ char *prompt_for_password( char *msg ) { struct termios tio_orig, tio_new; int n; char *password = malloc( NET_MAXAUTHLEN + 1 ); /* Check if we're connected to a terminal. */ if( !isatty( 0 ) ) { fprintf( stderr, "Password prompting requires a terminal.\n" ); exit( EXIT_FAILURE ); } /* Get terminal settings. */ if( tcgetattr( 0, &tio_orig ) != 0 ) { fprintf( stderr, "Failed to disable terminal echo " "(tcgetattr said %s).", strerror( errno ) ); exit( EXIT_FAILURE ); } /* Disable terminal input echo. */ memcpy( &tio_new, &tio_orig, sizeof( struct termios ) ); tio_new.c_lflag &= ~ECHO; if( tcsetattr( 0, TCSAFLUSH, &tio_new ) != 0 ) { fprintf( stderr, "Failed to disable terminal echo " "(tcsetattr said %s).", strerror( errno ) ); exit( EXIT_FAILURE ); } /* Write the prompt. */ write( 0, msg, strlen( msg ) ); /* Read the password (and bail out if something fails). */ n = read( 0, password, NET_MAXAUTHLEN ); if( n < 0 ) { fprintf( stderr, "Failed to read password from terminal " "(read said %s).", strerror( errno ) ); exit( EXIT_FAILURE ); } /* nul-terminate the read password. */ password[n] = '\0'; /* Strip off any trailing \n. */ if( n >= 1 && password[n - 1] == '\n' ) password[n - 1] = '\0'; /* Strip off any trailing \r. */ if( n >= 2 && password[n - 2] == '\r' ) password[n - 2] = '\0'; /* Restore terminal settings. */ tcsetattr( 0, TCSAFLUSH, &tio_orig ); /* Write a newline to the terminal. */ write( 0, "\n", sizeof( "\n" ) - 1 ); return password; } /* An MD5 hash looks like $1$ssssssss$hhhhhhhhhhhhhhhhhhhhhh, * where 's' are salt characters, and 'h' hash characters. */ extern int looks_like_md5hash( char *str ) { int i; /* A hash should be 34 characters long. */ if( strlen( str ) != 34 ) return 0; /* A hash should start with "$1$", and its 12th char should be '$' */ if( str[0] != '$' || str[1] != '1' || str[2] != '$' || str[11] != '$' ) return 0; /* The salt should contain only valid characters. */ for( i = 3; i < 11; i++ ) if( strchr( seedchars, str[i] ) == NULL ) return 0; /* The hash should contain only valid characters. */ for( i = 12; i < 34; i++ ) if( strchr( seedchars, str[i] ) == NULL ) return 0; /* All tests passed, it's probably ok. */ return 1; } extern int world_match_authentication( World *wld, const char *str ) { /* A literal exists. Check against that, it's way faster. */ if( wld->auth_literal != NULL ) return !strcmp( wld->auth_literal, str ); /* Match against the MD5 hash */ if( !match_string_md5hash( str, wld->auth_hash ) ) return 0; /* If we got here, str is valid. Cache the literal for later. */ wld->auth_literal = xstrdup( str ); return 1; } extern int match_string_md5hash( const char *str, const char *md5hash ) { char *strhash; strhash = crypt( str, md5hash ); return !strcmp( md5hash, strhash ); } mooproxy-1.0.0/accessor.c0000644000175000017500000002423311525336516015207 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include #include #include "accessor.h" #include "world.h" #include "log.h" #include "misc.h" #include "crypt.h" static int set_string( char *, char **, char ** ); static int set_long( char *, long *, char ** ); static int set_long_ranged( char *, long *, char **, long, long, char * ); static int set_bool( char *, int *, char ** ); static int get_string( char *, char ** ); static int get_long( long, char ** ); static int get_bool( int, char ** ); extern int aset_listenport( World *wld, char *key, char *value, int src, char **err ) { int ret; /* We're called from user. Set the requested port, flag for rebind, * and return silently if everything went ok. */ if( src == ASRC_USER ) { ret = set_long_ranged( value, &wld->requestedlistenport, err, 1, 65535, "Port numbers" ); /* Something went wrong, just return that. */ if( ret != SET_KEY_OK ) return ret; /* We got SET_KEY_OK, try and bind on this new port. */ wld->flags |= WLD_REBINDPORT; return SET_KEY_OKSILENT; } /* We're called from configfile, just set the thing as usual. */ return set_long_ranged( value, &wld->listenport, err, 1, 65535, "Port numbers" ); } extern int aset_auth_hash( World *wld, char *key, char *value, int src, char **err ) { char *val, *origval, *newhash, *oldliteral = NULL; int ret; /* Get the first word (the new hash) in newhash. * Also, get the rest of the string (the old literal if coming from * the user, empty if coming from file) in oldliteral */ origval = val = xstrdup( value ); newhash = get_one_word( &val ); newhash = xstrdup( ( newhash != NULL ) ? newhash : "" ); val = trim_whitespace( val ); val = remove_enclosing_quotes( val ); oldliteral = xstrdup( val ); free( origval ); if( !looks_like_md5hash( newhash ) ) { *err = xstrdup( "That doesn't look like a valid MD5 hash." ); free( newhash ); free( oldliteral ); return SET_KEY_BAD; } if( match_string_md5hash( "", newhash ) ) { *err = xstrdup( "The authentication string may not be empty." ); free( newhash ); free( oldliteral ); return SET_KEY_BAD; } if( src == ASRC_USER ) { if( !strcmp( oldliteral, "" ) ) { *err = xstrdup( "The new hash must be followed by the " "old literal authentication string." ); free( newhash ); free( oldliteral ); return SET_KEY_BAD; } if( !match_string_md5hash( oldliteral, wld->auth_hash ) ) { *err = xstrdup( "The old literal authentication string" " was not correct." ); free( newhash ); free( oldliteral ); return SET_KEY_BAD; } } free( wld->auth_literal ); wld->auth_literal = NULL; ret = set_string( newhash, &wld->auth_hash, err ); free( newhash ); free( oldliteral ); return ret; } extern int aset_dest_host( World *wld, char *key, char *value, int src, char **err ) { return set_string( value, &wld->dest_host, err ); } extern int aset_dest_port( World *wld, char *key, char *value, int src, char **err ) { return set_long_ranged( value, &wld->dest_port, err, 1, 65535, "Port numbers" ); } extern int aset_autologin( World *wld, char *key, char *value, int src, char **err ) { return set_bool( value, &wld->autologin, err ); } extern int aset_autoreconnect( World *wld, char *key, char *value, int src, char **err ) { int ret; ret = set_bool( value, &wld->autoreconnect, err ); /* The reconnect functions actually look at reconnect_enabled, which * gets set in other ways. But if the user sets autoreconnect, we want * that to take effect immediately. */ if( ret == SET_KEY_OK ) wld->reconnect_enabled = wld->autoreconnect; return ret; } extern int aset_commandstring( World *wld, char *key, char *value, int src, char **err ) { return set_string( value, &wld->commandstring, err ); } extern int aset_strict_commands( World *wld, char *key, char *value, int src, char **err ) { return set_bool( value, &wld->strict_commands, err ); } extern int aset_infostring( World *wld, char *key, char *value, int src, char **err ) { free( wld->infostring_parsed ); wld->infostring_parsed = parse_ansi_tags( value ); return set_string( value, &wld->infostring, err ); } extern int aset_newinfostring( World *wld, char *key, char *value, int src, char **err ) { free( wld->newinfostring_parsed ); wld->newinfostring_parsed = parse_ansi_tags( value ); return set_string( value, &wld->newinfostring, err ); } extern int aset_context_lines( World *wld, char *key, char *value, int src, char **err ) { return set_long_ranged( value, &wld->context_lines, err, 0, LONG_MAX / 1024, "Context on connect" ); } extern int aset_buffer_size( World *wld, char *key, char *value, int src, char **err ) { return set_long_ranged( value, &wld->buffer_size, err, 0, LONG_MAX / 1024 - 2, "Max buffer size" ); } extern int aset_logbuffer_size( World *wld, char *key, char *value, int src, char **err ) { return set_long_ranged( value, &wld->logbuffer_size, err, 0, LONG_MAX / 1024, "Max logbuffer size" ); } extern int aset_logging( World *wld, char *key, char *value, int src, char **err ) { wld->flags |= WLD_LOGLINKUPDATE; return set_bool( value, &wld->logging, err ); } extern int aset_log_timestamps( World *wld, char *key, char *value, int src, char **err ) { return set_bool( value, &wld->log_timestamps, err ); } extern int aset_easteregg_version( World *wld, char *key, char *value, int src, char **err ) { return set_bool( value, &wld->easteregg_version, err ); } /* ----------------------------- getters -------------------------------- */ extern int aget_listenport( World *wld, char *key, char **value, int src ) { return get_long( wld->listenport, value ); } extern int aget_auth_hash( World *wld, char *key, char **value, int src ) { /* For security reasons, the user may not view the MD5 hash. */ if( src == ASRC_USER ) return GET_KEY_PERM; return get_string( wld->auth_hash, value ); } extern int aget_dest_host( World *wld, char *key, char **value, int src ) { return get_string( wld->dest_host, value ); } extern int aget_dest_port( World *wld, char *key, char **value, int src ) { return get_long( wld->dest_port, value ); } extern int aget_autologin( World *wld, char *key, char **value, int src ) { return get_bool( wld->autologin, value ); } extern int aget_autoreconnect( World *wld, char *key, char **value, int src ) { return get_bool( wld->autoreconnect, value ); } extern int aget_commandstring( World *wld, char *key, char **value, int src ) { return get_string( wld->commandstring, value ); } extern int aget_strict_commands( World *wld, char *key, char **value, int src ) { return get_bool( wld->strict_commands, value ); } extern int aget_infostring( World *wld, char *key, char **value, int src ) { return get_string( wld->infostring, value ); } extern int aget_newinfostring( World *wld, char *key, char **value, int src ) { return get_string( wld->newinfostring, value ); } extern int aget_context_lines( World *wld, char *key, char **value, int src ) { return get_long( wld->context_lines, value ); } extern int aget_buffer_size( World *wld, char *key, char **value, int src ) { return get_long( wld->buffer_size, value ); } extern int aget_logbuffer_size( World *wld, char *key, char **value, int src ) { return get_long( wld->logbuffer_size, value ); } extern int aget_logging( World *wld, char *key, char **value, int src ) { return get_bool( wld->logging, value ); } extern int aget_log_timestamps( World *wld, char *key, char **value, int src ) { return get_bool( wld->log_timestamps, value ); } extern int aget_easteregg_version( World *wld, char *key, char **value, int src ) { return get_bool( wld->easteregg_version, value ); } /* ------------------------- helper functions --------------------------- */ /* The setters return SET_KEY_OK or SET_KEY_BAD. * When returning SET_KEY_BAD, err will be set. Err should be free()d. * The setters do not consume the src string. * * The getters always return GET_KEY_OK. * The string they place in dest should be free()d. */ static int set_string( char *src, char **dest, char **err ) { free( *dest ); *dest = xstrdup( src ); return SET_KEY_OK; } static int set_long( char *src, long *dest, char **err ) { char *endptr; long val; val = strtol( src, &endptr, 0 ); /* Yay! String is non-empty and valid number. */ if( *src != '\0' && *endptr == '\0' ) { *dest = val; return SET_KEY_OK; } *err = xstrdup( "Integers must be simple decimal or hexadecimal " "numbers." ); return SET_KEY_BAD; } /* Like set_long, but the allowed value is limited in range. */ static int set_long_ranged( char *src, long *dest, char **err, long low, long high, char *name ) { long val; if( set_long( src, &val, err ) == SET_KEY_BAD ) return SET_KEY_BAD; if( val >= low && val <= high ) { *dest = val; return SET_KEY_OK; } if( low == 0 && high == LONG_MAX ) xasprintf( err, "%s must be a positive number.", name ); else xasprintf( err, "%s must be between %li and %li inclusive.", name, low, high ); return SET_KEY_BAD; } static int set_bool( char *src, int *dest, char **err ) { int val; val = true_or_false( src ); if( val != -1 ) { *dest = val; return SET_KEY_OK; } *err = xstrdup( "Booleans must be one of true, yes, on, " "false, no, or off." ); return SET_KEY_BAD; } static int get_string( char *src, char **dest ) { xasprintf( dest, "\"%s\"", src ); return GET_KEY_OK; } static int get_long( long src, char **dest ) { xasprintf( dest, "%li", src ); return GET_KEY_OK; } static int get_bool( int src, char **dest ) { *dest = xstrdup( src ? "true" : "false" ); return GET_KEY_OK; } mooproxy-1.0.0/log.h0000644000175000017500000000251311525336516014170 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__LOG #define MOOPROXY__HEADER__LOG #include "world.h" /* Submit line for logging. * line is not consumed, and can be processed further. */ extern void world_log_line( World *wld, Line *line ); /* Attempt to flush all accumulated lines to the logfile(s). * Should be called regularly. */ extern void world_flush_client_logqueue( World *wld ); /* Attempt to ensure all written logdata actually ends up on disk. */ extern void world_sync_logdata( World *wld ); /* Remove the 'today' and 'yesterday' logfile symlinks. */ extern void world_log_link_remove( World *wld ); /* Update the 'today' and 'yesterday' logfile symlinks. */ extern void world_log_link_update( World *wld ); #endif /* ifndef MOOPROXY__HEADER__LOG */ mooproxy-1.0.0/world.c0000644000175000017500000006664211525336516014546 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include #include #include #include #include #include #include #include "global.h" #include "world.h" #include "misc.h" #include "line.h" #include "panic.h" #include "network.h" #define EASTEREGG_TRIGGER " vraagt aan je, \"Welke mooproxy versie draai je?\"" static int worlds_count = 0; static World **worlds_list = NULL; static void register_world( World *wld ); static void unregister_world( World *wld ); static Line *message_client( World *wld, char *prefix, char *str ); extern World *world_create( char *wldname ) { int i; World *wld; wld = xmalloc( sizeof( World ) ); /* Essentials */ wld->name = wldname; wld->configfile = NULL; wld->flags = 0; wld->lockfile = NULL; wld->lockfile_fd = -1; /* Destination */ wld->dest_host = NULL; wld->dest_port = -1; /* Listening connection */ wld->listenport = -1; wld->requestedlistenport = -1; wld->listen_fds = NULL; wld->bindresult = xmalloc( sizeof( BindResult ) ); world_bindresult_init( wld->bindresult ); /* Authentication related stuff */ wld->auth_hash = NULL; wld->auth_literal = NULL; wld->auth_tokenbucket = NET_AUTH_BUCKETSIZE; wld->auth_connections = 0; for( i = 0; i < NET_MAXAUTHCONN; i++ ) { wld->auth_buf[i] = xmalloc( NET_MAXAUTHLEN ); wld->auth_address[i] = NULL; wld->auth_fd[i] = -1; } wld->auth_privaddrs = linequeue_create(); /* Data related to the server connection */ wld->server_status = ST_DISCONNECTED; wld->server_fd = -1; wld->server_port = NULL; wld->server_host = NULL; wld->server_address = NULL; wld->server_resolver_pid = -1; wld->server_resolver_fd = -1; wld->server_addresslist = NULL; wld->server_connecting_fd = -1; wld->reconnect_enabled = 0; wld->reconnect_delay = 0; wld->reconnect_at = 0; wld->server_rxqueue = linequeue_create(); wld->server_toqueue = linequeue_create(); wld->server_txqueue = linequeue_create(); wld->server_rxbuffer = xmalloc( NET_BBUFFER_ALLOC ); /* See (1) */ wld->server_rxfull = 0; wld->server_txbuffer = xmalloc( NET_BBUFFER_ALLOC ); /* See (1) */ wld->server_txfull = 0; /* Data related to the client connection */ wld->client_status = ST_DISCONNECTED; wld->client_fd = -1; wld->client_address = NULL; wld->client_prev_address = NULL; wld->client_last_connected = 0; wld->client_login_failures = 0; wld->client_last_failaddr = NULL; wld->client_last_failtime = 0; wld->client_last_notconnmsg = 0; wld->client_rxqueue = linequeue_create(); wld->client_toqueue = linequeue_create(); wld->client_txqueue = linequeue_create(); wld->client_rxbuffer = xmalloc( NET_BBUFFER_ALLOC ); /* See (1) */ wld->client_rxfull = 0; wld->client_txbuffer = xmalloc( NET_BBUFFER_ALLOC ); /* See (1) */ wld->client_txfull = 0; /* Miscellaneous */ wld->buffered_lines = linequeue_create(); wld->inactive_lines = linequeue_create(); wld->history_lines = linequeue_create(); wld->dropped_inactive_lines = 0; wld->dropped_buffered_lines = 0; wld->easteregg_last = 0; /* Timer stuff */ wld->timer_prev_sec = -1; wld->timer_prev_min = -1; wld->timer_prev_hour = -1; wld->timer_prev_day = -1; wld->timer_prev_mon = -1; wld->timer_prev_year = -1; /* Logging */ wld->log_queue = linequeue_create(); wld->log_current = linequeue_create(); wld->dropped_loggable_lines = 0; wld->log_currentday = 0; wld->log_currenttime = 0; wld->log_currenttimestr = NULL; wld->log_fd = -1; wld->log_buffer = xmalloc( NET_BBUFFER_ALLOC ); /* See (1) */ wld->log_bfull = 0; wld->log_lasterror = NULL; wld->log_lasterrtime = 0; /* MCP stuff */ wld->mcp_negotiated = 0; wld->mcp_key = NULL; wld->mcp_initmsg = NULL; /* Ansi Client Emulation (ACE) */ wld->ace_enabled = 0; wld->ace_prestr = NULL; wld->ace_poststr = NULL; /* Options */ wld->autologin = DEFAULT_AUTOLOGIN; wld->autoreconnect = DEFAULT_AUTORECONNECT; wld->commandstring = xstrdup( DEFAULT_CMDSTRING ); wld->strict_commands = DEFAULT_STRICTCMDS; wld->infostring = xstrdup( DEFAULT_INFOSTRING ); wld->infostring_parsed = parse_ansi_tags( wld->infostring ); wld->newinfostring = xstrdup( DEFAULT_NEWINFOSTRING ); wld->newinfostring_parsed = parse_ansi_tags( wld->newinfostring ); wld->context_lines = DEFAULT_CONTEXTLINES; wld->buffer_size = DEFAULT_BUFFERSIZE; wld->logbuffer_size = DEFAULT_LOGBUFFERSIZE; wld->logging = DEFAULT_LOGGING; wld->log_timestamps = DEFAULT_LOGTIMESTAMPS; wld->easteregg_version = DEFAULT_EASTEREGGS; /* Add to the list of worlds */ register_world( wld ); return wld; /* (1) comment for allocations of several buffers: * * We allocate using NET_BBUFFER_ALLOC rather than NET_BUFFER_LEN, * because the buffers need to be longer than their "pretend length", * so we can fit in some additional things even when the buffer is * completely full with lines. * * Some things that we need to fit in there: * * - For the log buffer, a \n after the last line. * - For the TX buffers, a \r\n and possible ansi sequences for * client emulation * - For the RX buffers, a sentinel for the linear search */ } extern void world_destroy( World *wld ) { int i; /* Remove the world from the worldlist. */ unregister_world( wld ); /* Essentials */ free( wld->name ); free( wld->configfile ); free( wld->lockfile ); /* Destination */ free( wld->dest_host ); /* Listening connection */ if( wld->listen_fds ) for( i = 0; wld->listen_fds[i] > -1; i++ ) close( wld->listen_fds[i] ); free( wld->listen_fds ); world_bindresult_free( wld->bindresult ); free( wld->bindresult ); /* Authentication related stuff */ free( wld->auth_hash ); free( wld->auth_literal ); for( i = 0; i < NET_MAXAUTHCONN; i++ ) { free( wld->auth_buf[i] ); free( wld->auth_address[i] ); if( wld->auth_fd[i] > -1 ) close( wld->auth_fd[i] ); } linequeue_destroy( wld->auth_privaddrs ); /* Data related to server connection */ if( wld->server_fd > -1 ) close( wld->server_fd ); free( wld->server_host ); free( wld->server_port ); free( wld->server_address ); if( wld->server_resolver_fd > -1 ) close( wld->server_resolver_fd ); free( wld->server_addresslist ); if( wld->server_connecting_fd > -1 ) close( wld->server_connecting_fd ); linequeue_destroy( wld->server_rxqueue ); linequeue_destroy( wld->server_toqueue ); linequeue_destroy( wld->server_txqueue ); free( wld->server_rxbuffer ); free( wld->server_txbuffer ); /* Data related to client connection */ if( wld->client_fd > -1 ) close( wld->client_fd ); free( wld->client_address ); free( wld->client_prev_address ); linequeue_destroy( wld->client_rxqueue ); linequeue_destroy( wld->client_toqueue ); linequeue_destroy( wld->client_txqueue ); free( wld->client_rxbuffer ); free( wld->client_txbuffer ); /* Miscellaneous */ linequeue_destroy( wld->buffered_lines ); linequeue_destroy( wld->inactive_lines ); linequeue_destroy( wld->history_lines ); /* Logging */ linequeue_destroy( wld->log_queue ); linequeue_destroy( wld->log_current ); free( wld->log_currenttimestr ); if( wld->log_fd > -1 ) close( wld->log_fd ); free( wld->log_buffer ); free( wld->log_lasterror ); /* MCP stuff */ free( wld->mcp_key ); free( wld->mcp_initmsg ); /* Ansi Client Emulation (ACE) */ free( wld->ace_prestr ); free( wld->ace_poststr ); /* Options */ free( wld->commandstring ); free( wld->infostring ); free( wld->infostring_parsed ); free( wld->newinfostring ); free( wld->newinfostring_parsed ); /* The world itself */ free( wld ); } extern void world_get_list( int *count, World ***wldlist ) { *count = worlds_count; *wldlist = worlds_list; } /* Add a world to the global list of worlds. */ static void register_world( World *wld ) { worlds_list = xrealloc( worlds_list, ( worlds_count + 1 ) * sizeof( World * ) ); worlds_list[worlds_count] = wld; worlds_count++; } /* Remove a world from the global list of worlds. */ static void unregister_world( World *wld ) { int i = 0; /* Search for the world in the list */ while( i < worlds_count && worlds_list[i] != wld ) i++; /* Is the world not in the list? */ if( i == worlds_count ) /* FIXME: this shouldn't happen. Report? */ return; /* At this point, worlds_list[i] is our world */ for( i++; i < worlds_count; i++ ) worlds_list[i - 1] = worlds_list[i]; worlds_list = xrealloc( worlds_list, ( worlds_count - 1 ) * sizeof( World * ) ); worlds_count--; } extern void world_bindresult_init( BindResult *bindresult ) { bindresult->fatal = NULL; bindresult->af_count = 0; bindresult->af_success_count = 0; bindresult->af_success = NULL; bindresult->af_msg = NULL; bindresult->listen_fds = NULL; bindresult->conclusion = NULL; } extern void world_bindresult_free( BindResult *bindresult ) { int i; free( bindresult->fatal ); free( bindresult->af_success ); for( i = 0; i < bindresult->af_count; i++ ) free( bindresult->af_msg[i] ); free( bindresult->af_msg ); if( bindresult->listen_fds ) for( i = 0; bindresult->listen_fds[i] > -1; i++ ) close( bindresult->listen_fds[i] ); free( bindresult->listen_fds ); free( bindresult->conclusion ); } extern void world_configfile_from_name( World *wld ) { /* Configuration file = ~/CONFIGDIR/WORLDSDIR/worldname */ xasprintf( &wld->configfile, "%s/%s/%s/%s", get_homedir(), CONFIGDIR, WORLDSDIR, wld->name ); } extern Line *world_msg_client( World *wld, const char *fmt, ... ) { va_list argp; char *str; /* Parse the arguments into a string */ va_start( argp, fmt ); xvasprintf( &str, fmt, argp ); va_end( argp ); /* Send the message with the infostring prefix. */ return message_client( wld, wld->infostring_parsed, str ); } extern Line *world_newmsg_client( World *wld, const char *fmt, ... ) { va_list argp; char *str; /* Parse the arguments into a string */ va_start( argp, fmt ); xvasprintf( &str, fmt, argp ); va_end( argp ); /* Send the message with the newinfostring prefix. */ return message_client( wld, wld->newinfostring_parsed, str ); } /* Send prefix + str + to the client. * str is consumed, prefix is not. * If prefix is NULL or "", use wld->infostring_parsed. * Returns the line object that has been enqueued to the client. */ static Line *message_client( World *wld, char *prefix, char *str ) { Line *line; char *msg; if( prefix == NULL || *prefix == '\0' ) prefix = wld->infostring_parsed; /* Construct the message. The 5 is for the ansi reset and \0. */ msg = xmalloc( strlen( str ) + strlen( prefix ) + 5 ); strcpy( msg, prefix ); strcat( msg, str ); strcat( msg, "\x1B[0m" ); free( str ); /* And append it to the client queue */ line = line_create( msg, -1 ); line->flags = LINE_MESSAGE; linequeue_append( wld->client_toqueue, line ); return line; } extern void world_trim_dynamic_queues( World *wld ) { unsigned long *bll = &wld->buffered_lines->size; unsigned long *ill = &wld->inactive_lines->size; unsigned long *hll = &wld->history_lines->size; unsigned long limit; /* Trim the normal buffers. The data is distributed over * buffered_lines, inactive_lines and history_lines. We will trim * them in reverse order until everything is small enough. */ limit = wld->buffer_size * 1024; /* First, the history lines. Remove oldest lines. */ while( *bll + *ill + *hll > limit && wld->history_lines->head != NULL ) line_destroy( linequeue_pop( wld->history_lines ) ); /* Next, the inactive lines. These are important, so we count * the number of dropped lines. Remove oldest lines. */ while( *bll + *ill > limit && wld->inactive_lines->head != NULL ) { line_destroy( linequeue_pop( wld->inactive_lines ) ); wld->dropped_inactive_lines++; } /* Finally, the buffered lines. These are important, so we count * the number of dropped lines. Remove oldest lines. */ while( *bll > limit && wld->buffered_lines->head != NULL ) { line_destroy( linequeue_pop( wld->buffered_lines ) ); wld->dropped_buffered_lines++; } /* Trim the logbuffers. The data is distributed over log_current and * log_queue. We will trim them in reverse order until everything is * small enough. */ limit = wld->logbuffer_size * 1024; /* First log_queue. These are very important, so we count the number * of dropped lines. Remove newest lines. */ while( wld->log_queue->size + wld->log_current->size > limit && wld->log_queue->head != NULL ) { line_destroy( linequeue_popend( wld->log_queue ) ); wld->dropped_loggable_lines++; } /* Next, log_current. These are very important, so we count the number * of dropped lines. Remove newest lines. */ while( wld->log_current->size > limit && wld->log_current->head != NULL ) { line_destroy( linequeue_popend( wld->log_current ) ); wld->dropped_loggable_lines++; } } extern void world_recall_and_pass( World *wld ) { Linequeue *il = wld->inactive_lines; Linequeue *bl = wld->buffered_lines; Linequeue *queue; Line *line, *recalled; int nocontext = 0, noposnew = 0, nocernew = 0; if( wld->context_lines > 0 ) { /* Recall history lines. */ queue = world_recall_history( wld, wld->context_lines ); /* Inform the client of the context lines */ if( queue->count > 0 ) { world_newmsg_client( wld, "%lu line%s of context " "history.", queue->count, ( queue->count == 1 ) ? "" : "s" ); /* Move the lines from local queue to client. */ linequeue_merge( wld->client_toqueue, queue ); } else /* Flag no context lines for later reporting. */ nocontext = 1; /* We don't need this anymore. */ linequeue_destroy( queue ); } /* Possibly new lines. */ if( il->count > 0 ) { /* Inform the user about them. */ world_newmsg_client( wld, "%s%lu possibly new line%s " "(%.1f%% of buffer).", ( nocontext == 0 ) ? "" : "No context lines. ", il->count, ( il->count == 1 ) ? "" : "s", il->size / 10.24 / wld->buffer_size ); /* If nocontext was 1, we reported on it now. Clear it. */ nocontext = 0; /* Pass the lines. These are already in the history path, so * they need to be duplicated and be NOHISTed. */ line = wld->inactive_lines->head; while( line != NULL ) { recalled = line_dup( line ); recalled->flags = LINE_RECALLED; linequeue_append( wld->client_toqueue, recalled ); line = line->next; } } else /* Flag no possibly new lines for later reporting. */ noposnew = 1; /* Certainly new lines. */ if( bl->count > 0 ) { /* Inform the user about them. */ world_newmsg_client( wld, "%s%s%lu certainly new line%s " "(%.1f%% of buffer).", ( nocontext == 0 ) ? "" : "No context lines. ", ( noposnew == 0 ) ? "" : "No possibly new lines. ", bl->count, ( bl->count == 1 ) ? "" : "s", bl->size / 10.24 / wld->buffer_size ); /* If nocontext/noposnew were 1, we reported. Clear them. */ nocontext = 0; noposnew = 0; /* Pass the lines. Since these are just waiting to be passed * to a client, and never have been in history, they don't * need to be fiddled with and can be passed in one go. */ linequeue_merge( wld->client_toqueue, wld->buffered_lines ); } else /* Flag no possibly new lines for later reporting. */ nocernew = 1; /* Flag end of new lines, with some special cases... */ if( noposnew == 1 && nocernew == 1 ) world_newmsg_client( wld, "%sNo new lines.", ( nocontext == 0 ) ? "" : "No context lines. " ); else world_newmsg_client( wld, "%sEnd of new lines.", ( nocernew == 0 ) ? "" : "No certainly new lines. " ); /* Inform the user about dropped possibly new lines, if any. */ if( wld->dropped_inactive_lines > 0 ) { world_newmsg_client( wld, "WARNING: Due to low buffer space, " "%lu possibly new line%s %s been dropped!", wld->dropped_inactive_lines, ( wld->dropped_inactive_lines == 1 ) ? "" : "s", ( wld->dropped_inactive_lines == 1 ) ? "has" : "have" ); wld->dropped_inactive_lines = 0; } /* Inform the user about dropped certainly new lines, if any. */ if( wld->dropped_buffered_lines > 0 ) { world_newmsg_client( wld, "WARNING: Due to low buffer space, " "%lu certainly new line%s %s been dropped!", wld->dropped_buffered_lines, ( wld->dropped_buffered_lines == 1 ) ? "" : "s", ( wld->dropped_buffered_lines == 1 ) ? "has" : "have" ); wld->dropped_buffered_lines = 0; } } extern void world_login_server( World *wld, int override ) { /* Only log in if autologin is enabled or override is in effect */ if( !wld->autologin && !override ) return; /* Of course, we need to be connected */ if( wld->server_status != ST_CONNECTED ) { world_msg_client( wld, "Not connected to server, " "cannot login." ); return; } /* We can't login without wld->auth_literal */ if( wld->auth_literal == NULL ) { world_msg_client( wld, "Cannot login to server, literal " "authentication string not known." ); world_msg_client( wld, "You need to re-authenticate to moo" "proxy (i.e. reconnect) for this to work." ); return; } /* Say if we're logging in automatically or manually. */ if( override ) world_msg_client( wld, "Logging in on world %s.", wld->name ); else world_msg_client( wld, "Automatically logging in on world %s.", wld->name ); linequeue_append( wld->server_toqueue, line_create( xstrdup( wld->auth_literal ), -1 ) ); } extern void world_inactive_to_history( World *wld ) { linequeue_merge( wld->history_lines, wld->inactive_lines ); } extern Linequeue *world_recall_history( World *wld, long count ) { Linequeue *queue; Line *line, *recalled; char *str; long len; /* Create our queue. */ queue = linequeue_create(); /* If we have no lines or count <= 0, return the empty queue. */ line = wld->history_lines->tail; if( line == NULL || count < 1 ) return queue; /* Seek back from the end of the history, to the first line we * should display. */ for( ; line->prev != NULL && count > 1; count-- ) line = line->prev; /* Copy the lines to our local queue. */ while( line != NULL ) { /* Copy the string, without ASCII BELLs. */ str = xmalloc( line->len + 1 ); len = strcpy_nobell( str, line->str ); /* Resize it, if necessary. */ if( len != line->len ) str = xrealloc( str, len + 1 ); /* Make it into a proper line object... */ recalled = line_create( str, len ); recalled->flags = LINE_RECALLED; recalled->time = line->time; recalled->day = line->day; /* And put it in our queue. */ linequeue_append( queue, recalled ); line = line->next; } /* All done, return the queue. */ return queue; } extern void world_rebind_port( World *wld ) { BindResult *result = wld->bindresult; int i; /* Binding to the port we're already bound to makes no sense. */ if( wld->requestedlistenport == wld->listenport ) { world_msg_client( wld, "Already bound to port %li.", wld->requestedlistenport ); return; } /* Announce what we're gonna do. */ world_msg_client( wld, "Binding to new port." ); /* Actually try and bind to the new port. */ world_bind_port( wld, wld->requestedlistenport ); /* Fatal error, inform the user and abort. */ if( result->fatal ) { world_msg_client( wld, "%s", result->fatal ); world_msg_client( wld, "The option listenport has not been " "changed." ); return; } /* Print the result for each address family. */ for( i = 0; i < result->af_count; i++ ) world_msg_client( wld, " %s %s", result->af_success[i] ? " " : "!", result->af_msg[i] ); /* Report the conclusion. */ world_msg_client( wld, "%s", result->conclusion ); /* We need success on at least one af. */ if( result->af_success_count == 0 ) { world_msg_client( wld, "The option listenport has not been " "changed." ); return; } /* Yay, success! Get rid of old file descriptors. */ if( wld->listen_fds != NULL ) for( i = 0; wld->listen_fds[i] != -1; i++ ) close( wld->listen_fds[i] ); /* Install the new ones. */ free( wld->listen_fds ); wld->listen_fds = result->listen_fds; result->listen_fds = NULL; /* Update the listenport. */ wld->listenport = wld->requestedlistenport; /* Announce success. */ world_msg_client( wld, "The option listenport has been changed " "to %li.", wld->listenport ); } extern void world_schedule_reconnect( World *wld ) { long delay = ( wld->reconnect_delay + 500 ) / 1000; char *whenstr; /* This is only valid if we're currently not connected. */ if( wld->server_status != ST_DISCONNECTED ) return; /* And we're not gonna do this if we're not supposed to. */ if( !wld->reconnect_enabled ) return; /* Indicate we're waiting, and for when. */ wld->server_status = ST_RECONNECTWAIT; wld->reconnect_at = current_time() + delay; /* Construct a nice message for the user. */ if( delay == 0 ) whenstr = xstrdup( "immediately" ); else if( delay < 60 ) xasprintf( &whenstr, "in %li second%s", delay, ( delay == 1 ) ? "" : "s" ); else xasprintf( &whenstr, "in %li minute%s and %i second%s", delay / 60, ( delay / 60 == 1 ) ? "" : "s", delay % 60, ( delay % 60 == 1 ) ? "" : "s" ); /* And send it off. */ world_msg_client( wld, "Will reconnect %s (at %s).", whenstr, time_string( wld->reconnect_at, "%T" ) ); free( whenstr ); /* Increase the delay. */ wld->reconnect_delay *= AUTORECONNECT_BACKOFF_FACTOR; wld->reconnect_delay += 3000 + rand() / (RAND_MAX / 4000); if( wld->reconnect_delay > AUTORECONNECT_MAX_DELAY * 1000 ) wld->reconnect_delay = AUTORECONNECT_MAX_DELAY * 1000; /* If we have to reconnect immediately, actually do it immediately. */ if( delay == 0 ) world_do_reconnect( wld ); } extern void world_do_reconnect( World *wld ) { /* We'll only do a reconnect if we're waiting for reconnecting. */ if( wld->server_status != ST_RECONNECTWAIT ) return; /* Start the connecting. */ wld->flags |= WLD_SERVERRESOLVE; /* And announce what we did. */ world_msg_client( wld, "" ); world_msg_client( wld, "Reconnecting now (at %s).", time_fullstr( current_time() ) ); } extern void world_decrease_reconnect_delay( World *wld ) { /* Decrease the delay. */ wld->reconnect_delay = wld->reconnect_delay / 2 - 1; if( wld->reconnect_delay < 0 ) wld->reconnect_delay = 0; } extern int world_enable_ace( World *wld ) { Linequeue *queue; char *tmp, *status; int cols = wld->ace_cols, rows = wld->ace_rows; /* Create the "statusbar". */ status = xmalloc( cols + strlen( wld->name ) + 20 ); sprintf( status, "---- mooproxy - %s ", wld->name ); memset( status + strlen( wld->name ) + 17, '-', cols ); status[cols] = '\0'; /* Construct the string to send to the client: * - reset terminal * - move cursor to the statusbar location * - print statusbar * - set scroll area to "input window" * - move cursor to the very last line. */ xasprintf( &tmp, "\x1B" "c\x1B[%i;1H%s\x1B[%i;%ir\x1B[%i;1H", rows - 3, status, rows - 2, rows, rows ); free( status ); /* We fail if the string doesn't fit into the buffer. */ if( wld->client_txfull + strlen( tmp ) > NET_BBUFFER_ALLOC ) { free( tmp ); return 0; } /* Append the string to the buffer. */ strcpy( wld->client_txbuffer + wld->client_txfull, tmp ); wld->client_txfull += strlen( tmp ); free( tmp ); /* Construct the string that will be prepended to each line: * - save cursor position * - set scroll area to "output window" * - set cursor to the last line of the "output window" */ free( wld->ace_prestr ); xasprintf( &wld->ace_prestr, "\x1B[s\x1B[1;%ir\x1B[%i;1H", rows - 4, rows - 4 ); /* Construct the string that will be appended to each line: * - set scroll area to "input window" * - restore cursor position * - reset attribute mode */ free( wld->ace_poststr ); xasprintf( &wld->ace_poststr, "\x1B[%i;%ir\x1B[u\x1B[0m", rows - 2, rows ); /* Of course, ACE is enabled now. */ wld->ace_enabled = 1; /* We want to include the inactive lines in our recall as well, so * we move them to the history right now. */ world_inactive_to_history( wld ); /* Recall enough lines to fill the "output window". */ queue = world_recall_history( wld, rows - 4 ); linequeue_merge( wld->client_toqueue, queue ); linequeue_destroy( queue ); return 1; } extern void world_disable_ace( World *wld ) { /* Send "reset terminal" to the client, so the clients terminal is * not left in a messed up state. * ACE deactivation should always succeed, so if the ansi sequence * doesn't fit in the buffer, we'll still do all the other stuff. */ if( wld->client_txfull + 2 < NET_BBUFFER_ALLOC ) { wld->client_txbuffer[wld->client_txfull++] = '\x1B'; wld->client_txbuffer[wld->client_txfull++] = 'c'; } free( wld->ace_prestr ); wld->ace_prestr = NULL; free( wld->ace_poststr ); wld->ace_poststr = NULL; wld->ace_enabled = 0; } /* A little easter egg. If someone asks us (in Dutch, in a specific format) * what Mooproxy version we're running, we respond. */ extern void world_easteregg_server( World *wld, Line *line ) { const static char *verbs[] = {"an", "bl", "fl", "gie", "gl", "gn", "gr", "gri", "grm", "ju", "ko", "mo", "mu", "pe", "sn", "ve", "zu", "zwi"}; const char *verb; char *name, *response, *p; /* Only once per minute, please. */ if( wld->easteregg_last > current_time() - 60 ) return; /* If the line is too short, it can't be the trigger. */ if( line->len < sizeof( EASTEREGG_TRIGGER ) - 1 + 1 ) return; /* Trigger? */ if( strcmp( line->str + line->len - sizeof( EASTEREGG_TRIGGER ) + 1, EASTEREGG_TRIGGER ) ) return; /* Isolate the name of the player triggering the easter egg. */ name = xmalloc( line->len + 1 ); strcpy_noansi( name, line->str ); for( p = name; *p; p++ ) if( isspace( *p ) ) break; *p = '\0'; if( name[0] == '[' || name[0] == '<' || name[0] == '{' ) { /* Looks like a channel, let's ignore it. */ free( name ); return; } /* Pick a random verb. */ verb = verbs[rand() % ( sizeof( verbs ) / sizeof( char * ) )]; if( name[0] == '>' && name[1] == '>' ) /* Response to a page. */ xasprintf( &response, "> %s Ik draai mooproxy %s", verb, VERSIONSTR ); else /* Response to a local player. */ xasprintf( &response, "%s -%s Ik draai mooproxy %s", verb, name, VERSIONSTR ); /* Send the message and clean up. */ linequeue_append( wld->server_toqueue, line_create( response, -1 ) ); wld->easteregg_last = current_time(); free( name ); } extern int world_start_shutdown( World *wld, int force, int fromsig ) { Line *line; /* Unlogged data? Refuse if not forced. */ if( !force && wld->log_queue->size + wld->log_current->size + wld->log_bfull > 0 ) { long unlogged_lines = 1 + wld->log_queue->count + wld->log_current->count + wld->log_bfull / 80; long unlogged_kib = ( wld->log_bfull + wld->log_queue->size + wld->log_current->size + 512 ) / 1024; world_msg_client( wld, "There are approximately %li lines " "(%liKiB) not yet logged to disk. ", unlogged_lines, unlogged_kib ); world_msg_client( wld, "Refusing to shut down. " "Use %s to override.", fromsig ? "SIGQUIT" : "shutdown -f" ); return 0; } /* We try to leave their terminal in a nice state. */ if( wld->ace_enabled ) world_disable_ace( wld ); /* We're good, proceed. */ line = world_msg_client( wld, "Shutting down%s (reason: %s).", force ? " forcibly" : "", fromsig ? "signal" : "client request" ); line->flags = LINE_CHECKPOINT; wld->flags |= WLD_SHUTDOWN; return 1; } mooproxy-1.0.0/BUGS0000644000175000017500000000101011525336516013710 0ustar marcelmmarcelm * Resolver slave can fail after libc upgrade. The forked child will inherit the libc of the parent process, but libnss_*.so are loaded dynamically, so their versions might not match the libc version mooproxy was started with. (reported: Bram Senders) * MCP can get into an inconsistent state (no known mcp key, no initmsg) I was able to get mooproxy in this state by reconnecting several times (some of which through daisy-chained mooproxy). Not sure how that caused the state to be messed up. mooproxy-1.0.0/dataflow.pdf0000644000175000017500000001530511525336516015535 0ustar marcelmmarcelm%PDF-1.4 %쏢 5 0 obj <> stream x[KWћd:|?N9-}4jd[4Jz7>&lF1vqSbY7D/;va!M/>/dEtVGmGk[G6H 5:!)+jW]#p1+7 ֝v{[{%tR }QZ)n}7*Hl>A@4uFypjrwv~=6ZݽYB*67jo~4HoQuoW7{-U݃7OW_~7&XnJ?d@s0) יY*}OVVL`d퓼&N:IUNO\n֫cf2(\bވS&+LkTol5 Ct'&$7x4R;-Q&7?'?;uw;Hq')4w6y=Hz!KS|֗!Ћ E`O%4(! )}az:LqPP ^5w(:0.\@-:0)L^ëUR^<;ҋo* "C{@izIDhY?N2ZwSX_C鄾uƅku<^rL s4Su2=*q%Srzʟ靀F?b)ZMG/{ 6DBI1" E0ptk,@!@ &4M>#K"8/oHl:p4}_Ls1F eo/a-W1a?e|.Twl@F= {=ܨ?]0¸f]I0 &0;WP[U3a-m%\6+n*L&t3_<:| \;|gCCs.!(CT1n,8¡={*k1<=*n8ͅi 5XAlfB5o*&\!X 0Ŋ:Aa  L4DIP7=&nX.Da -}#¬ @CI\Wa"LLZʳ5i(ʸSSo.ap)_*~'zJo5 t[(x Ґ>"~b(PxB7l4 Kfy#LKLHw!e2->z NiC(׬x>oi U3ߍE*CՌ3LV6l3y%9zqqFBf0C.A 4`A-9dUKF`2&!y=핬0iq*\%4403X*<@Thy ,eYk@L[*i r7ϪǶ!Zy}t '`3;]c)!P;K@Nw CZ%U*c1^g%7htpX~P$I&ǤӄN_jbGk)r)6#Q?) +JסZP!Z)#%_P0˚Q> !57(TBepDzQ4=SUP$>&v"n}|~:e L#5V"Llsasa2$rjLmjDUF-%B !}&Lm&%BJi5r.p|sa^IT6u&g6z'KGF&0KfqZiiHJLm9LNPCnmi $|Q]jJ@wt#zJX.z`W%S#tMD]ʆB3FOea:j&TUgJ}v:\%cfw4RDqA ܈!%0! 3Z \2v1!j=vA<HJOiV (l/7 sHg&#)eL #3%^>w==mv{u3^nJs~|0Au}WThYx""~z>|\-֫ݧCm4ѧiٜt@ru8ߏ^/̈́[]<cѦRCK$m!+0A^ %j z4k=]!>.?|ܭ=v׫e9vӡ &NB($qWp\??׻<] 1(s]FgZcuٜ Nٴ GTu9 Diz>,.@8qy4.?fhJ32aЬ RB1[.bd> u,Ž6&S"~٢ 1*v;t!j(jfX}vP'cۀ`0'jGb.a3*nklz'\̳'373kyvC4|AI(# 6eWϻ&X(bPz'!G;]3R<> ~7B3Xp:$wRԦ7Y MڧZ. gK'n )x#A?dƳłs)-DФ-KTqՀ f0c|dAiaxV4ep0#/@ETeom F%M=P 0hU#R$<.> 7BK5wƈa$]'zˊ^I3JW/&x :odR\Ep'~mڨ)CB34l'5:2Søi^aHP1:Jކj'6 q( ^7&SC({U{2 z>[f )yyHdF8{?ɑa}Y/NQhC4+ˮ5yiTX 9M,KyI@3<`榧WB>O`4V,yçvqI6Y8"}|f.#DaC} #Y m'9q6dƒ .2WeY2&zzu6t Qq~IR.go1ǐRxZMZ:kcz_ǮOge2W-Now^y SjSEQ0"7P&8>u1kC"y1ٺ/gB҈R&T3?6Ձp(),M i$&86-q^B:8YK =C4mǑ\E, dbS*Zg[[#̲KAQ/ {UzR,eհܭp2~M¥jl084I@C?P1I CѝRYz>Q)|> /Contents 5 0 R >> endobj 3 0 obj << /Type /Pages /Kids [ 4 0 R ] /Count 1 >> endobj 1 0 obj <> endobj 7 0 obj <>endobj 10 0 obj <> endobj 11 0 obj <> endobj 8 0 obj <> endobj 9 0 obj <> endobj 12 0 obj <>stream 2009-07-29T02:09:08+02:00 2009-07-29T02:09:08+02:00 Dia v0.96.1 /home/marcelm/projects/mooproxy/dataflow.diamarcelm endstream endobj 2 0 obj <>endobj xref 0 13 0000000000 65535 f 0000004424 00000 n 0000006225 00000 n 0000004365 00000 n 0000004214 00000 n 0000000015 00000 n 0000004194 00000 n 0000004489 00000 n 0000004599 00000 n 0000004663 00000 n 0000004530 00000 n 0000004560 00000 n 0000004732 00000 n trailer << /Size 13 /Root 1 0 R /Info 2 0 R /ID [<4D6262B242EFAE66F37087F5AC331E11><4D6262B242EFAE66F37087F5AC331E11>] >> startxref 6440 %%EOF mooproxy-1.0.0/COPYING0000644000175000017500000003005211525336516014270 0ustar marcelmmarcelm GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS mooproxy-1.0.0/config.h0000644000175000017500000001047711525336516014664 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__CONFIG #define MOOPROXY__HEADER__CONFIG #include "world.h" /* Collection of settings from the commandline. */ typedef struct Config Config; struct Config { int action; char *worldname; int no_daemon; char *error; }; /* Return values for parse_command_line_options. * Indicates what action to take. */ #define PARSEOPTS_START 0 #define PARSEOPTS_HELP 1 #define PARSEOPTS_VERSION 2 #define PARSEOPTS_LICENSE 3 #define PARSEOPTS_MD5CRYPT 4 #define PARSEOPTS_ERROR 5 /* Return values for attempting to set a key. */ #define SET_KEY_OK 1 #define SET_KEY_OKSILENT 2 #define SET_KEY_NF 3 #define SET_KEY_PERM 4 #define SET_KEY_BAD 5 /* Return values for attempting to query a key. */ #define GET_KEY_OK 1 #define GET_KEY_NF 2 #define GET_KEY_PERM 3 /* Parse the commandline arguments, extracting options and actions. * argc, argv: Arguments from main(). * config: Config struct. All members of this struct will be set. * If config->action is PARSEOPTS_ERROR, config->error * will contain an error. config and its members should be * freed. */ extern void parse_command_line_options( int argc, char **argv, Config *config ); /* Attempts to load the configuration file for this world. * On success, returns 0. * On failure, returns non-zero and sets err to point to an error string. * The error string should be free()d. */ extern int world_load_config( World *wld, char **err ); /* Place an array of key names (except the hidden ones) in list (a pointer to * an array of strings). The array should be freed, the key names should not. * Returns the number of keys in the list. */ extern int world_get_key_list( World *wld, char ***list ); /* Searches the option database for the key name, and attempts to set it * to the new value. * Arguments: * key: The name of the key to set. (string not consumed) * value: String representation of the new value. (string not consumed) * err: On SET_KEY_BAD, contains error msg. The string should be free()d. * Return values: * SET_KEY_OK: Everything ok, the key was set. * SET_KEY_NF: Key doesn't exist (or is hidden). * SET_KEY_PERM: Key may not be written. * SET_KEY_BAD: New value was bad. See err. */ extern int world_set_key( World *wld, char *key, char *value, char **err ); /* Searches the option database for the key name, and attempts to query * its value. * Arguments: * key: The name of the key to query. (string not consumed) * value: On GET_KEY_OK, is set to the string representation of the * value. The string should be free()d. * Return values: * GET_KEY_OK: Everything ok, the value is read. * GET_KEY_NF: Key doesn't exist (or is hidden). * GET_KEY_PERM: Key may not be read. */ extern int world_get_key( World *wld, char *key, char **value ); /* Like world_get_key(), but gets descriptions of this setting. * This function won't return GET_KEY_PERM. * *shortdesc and *longdesc should not be free()d. * *longdesc will be NULL if there is no long description available. */ extern int world_desc_key( World *wld, char *key, char **shortdesc, char **longdesc ); /* This function attempts to create the configuration dirs for mooproxy. * On failure, it returns non-zero and places the error in err (the string * should be free()d). */ extern int create_configdirs( char **err ); /* Check the permissions on the configuration directory. On error, returns * non-zero and places the error in err (the string should be free()d). * If the permissions are not satisfactory, a warning string is placed in * warn (which should be free()d). Otherwise, warn is set to NULL. */ extern int check_configdir_perms( char **warn, char **err ); #endif /* ifndef MOOPROXY__HEADER__CONFIG */ mooproxy-1.0.0/network.c0000644000175000017500000007532011525336516015101 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "global.h" #include "network.h" #include "misc.h" #include "mcp.h" #include "log.h" #include "timer.h" #include "resolve.h" #include "crypt.h" #include "panic.h" /* Maximum length of queue of pending kernel connections. */ #define NET_BACKLOG 4 /* Authentication messages */ #define NET_AUTHSTRING "Welcome, this is mooproxy. Please authenticate." #define NET_AUTHFAIL "Authentication failed, goodbye." static int create_fdset( World *, fd_set *, fd_set * ); static void handle_connecting_fd( World * ); static void server_connect_error( World *, int, const char * ); static void handle_listen_fd( World *, int ); static void handle_auth_fd( World *, int ); static void remove_auth_connection( World *, int, int ); static void verify_authentication( World *, int ); static void promote_auth_connection( World *, int ); static void handle_client_fd( World * ); static void handle_server_fd( World * ); static void privileged_add( World *, char * ); static void privileged_del( World *, char * ); static int is_privileged( World *, char * ); extern void wait_for_network( World *wld ) { struct timeval tv; fd_set rset, wset; int high, i, r; /* Check the auth connections for ones that need to be promoted to * client, and for ones that need verification. */ for( i = 0; i < wld->auth_connections; i++ ) { switch( wld->auth_status[i] ) { case AUTH_ST_CORRECT: promote_auth_connection( wld, i ); return; case AUTH_ST_VERIFY: if( wld->auth_tokenbucket > 0 || wld->auth_ispriv[i] ) { verify_authentication( wld, i ); return; } break; } } /* If there are unprocessed lines in the *_toqueue queues, return. * The main loop will move these lines to their respective transmit * queues, after which we'll be called again. We will handle the * transmit queues. */ if( wld->server_toqueue->count + wld->client_toqueue->count > 0 ) return; /* Prepare for select() */ high = create_fdset( wld, &rset, &wset ); tv.tv_sec = 1; tv.tv_usec = 0; r = select( high + 1, &rset, &wset, NULL, &tv ); if( r < 0 ) { /* EINTR is ok, other errors are not. */ if( errno == EINTR ) return; else panic( PANIC_SELECT, errno, 0 ); } /* Check all of the monitored FD's, and handle them appropriately. */ if( wld->server_resolver_fd != -1 && FD_ISSET( wld->server_resolver_fd, &rset ) ) world_handle_resolver_fd( wld ); if( wld->server_fd != -1 && FD_ISSET( wld->server_fd, &rset ) ) handle_server_fd( wld ); if( wld->client_fd != -1 && FD_ISSET( wld->client_fd, &rset ) ) handle_client_fd( wld ); for( i = 0; wld->listen_fds[i] != -1; i++ ) if( FD_ISSET( wld->listen_fds[i], &rset ) ) handle_listen_fd( wld, i ); for( i = wld->auth_connections - 1; i >= 0; i-- ) if( FD_ISSET( wld->auth_fd[i], &rset ) ) handle_auth_fd( wld, i ); if( wld->server_connecting_fd != -1 ) if( FD_ISSET( wld->server_connecting_fd, &wset ) ) handle_connecting_fd( wld ); } /* Construct the fd_set's for the select() call. * Put all the FD's that are watched for reading in rset, and all those that * are watched for writing in wset. Return the highest FD. */ static int create_fdset( World *wld, fd_set *rset, fd_set *wset ) { int high = 0, i; /* -------- Readable FD's -------- */ FD_ZERO( rset ); /* Add resolver slave FD */ if( wld->server_resolver_fd != -1 ) FD_SET( wld->server_resolver_fd, rset ); if( wld->server_resolver_fd > high ) high = wld->server_resolver_fd; /* Add server FD */ if( wld->server_fd != -1 ) FD_SET( wld->server_fd, rset ); if( wld->server_fd > high ) high = wld->server_fd; /* Add listening FDs */ for( i = 0; wld->listen_fds[i] != -1; i++ ) { FD_SET( wld->listen_fds[i], rset ); if( wld->listen_fds[i] > high ) high = wld->listen_fds[i]; } /* Add the authenticating FDs */ for( i = wld->auth_connections - 1; i >= 0; i-- ) { FD_SET( wld->auth_fd[i], rset ); if( wld->auth_fd[i] > high ) high = wld->auth_fd[i]; } /* Add client FD */ if( wld->client_fd != -1 ) FD_SET( wld->client_fd, rset ); if( wld->client_fd > high ) high = wld->client_fd; /* -------- Writable FD's -------- */ FD_ZERO( wset ); /* Add the non-blocking connecting FD */ if( wld->server_connecting_fd != -1 ) FD_SET( wld->server_connecting_fd, wset ); if( wld->server_connecting_fd > high ) high = wld->server_connecting_fd; /* If there is data to be written to the server, we want to * know if the FD is writable. */ if( wld->server_txqueue->count > 0 || wld->server_txfull > 0 ) { if( wld->server_fd != -1 ) FD_SET( wld->server_fd, wset ); if( wld->server_fd > high ) high = wld->server_fd; } /* If there is data to be written to the client, we want to * know if the FD is writable. */ if( wld->client_txqueue->count > 0 || wld->client_txfull > 0 ) { if( wld->client_fd != -1 ) FD_SET( wld->client_fd, wset ); if( wld->client_fd > high ) high = wld->client_fd; } return high; } extern void world_bind_port( World *wld, long port ) { struct addrinfo hints, *ailist, *ai; int current = -1, successful = 0, fd, yes = 1, ret; char hostbuf[NI_MAXHOST + 1], portstr[NI_MAXSERV + 1], **currentmsg; BindResult *result = wld->bindresult; /* Clean up any previous BindResult */ world_bindresult_free( result ); world_bindresult_init( result ); /* We start with a list of one FD (the -1 terminator) */ result->listen_fds = xmalloc( sizeof( int ) ); result->listen_fds[0] = -1; if( port == -1 ) { xasprintf( &result->fatal, "No port defined to listen on." ); return; } /* Specify the socket addresses we want. Any AF the system supports, * STREAM socket type, TCP protocol, listening socket, and our port. */ memset( &hints, '\0', sizeof( hints ) ); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; snprintf( portstr, NI_MAXSERV, "%li", port ); /* Get the socket addresses */ ret = getaddrinfo( NULL, portstr, &hints, &ailist ); if( ret != 0 ) { xasprintf( &result->fatal, "Getting address information " "failed: %s", gai_strerror( ret ) ); return; } /* Loop over the socket addresses, to count them. */ for( ai = ailist; ai != NULL; ai = ai->ai_next ) result->af_count++; /* Allocate result arrays */ result->af_success = xmalloc( result->af_count * sizeof( int ) ); result->af_msg = xmalloc( result->af_count * sizeof( char * ) ); /* Loop over the socket addresses, and attempt to create, bind(), * and listen() them all. */ for( ai = ailist; ai != NULL; ai = ai->ai_next ) { current++; currentmsg = &result->af_msg[current]; result->af_success[current] = 0; /* Get the numeric representation of this address * (e.g. "0.0.0.0" or "::") */ ret = getnameinfo( ai->ai_addr, ai->ai_addrlen, hostbuf, NI_MAXHOST, NULL, 0, NI_NUMERICHOST ); if( ret != 0 ) { xasprintf( currentmsg, "Getting name information " "failed: %s", gai_strerror( ret ) ); continue; } /* Create the socket */ fd = socket( ai->ai_family, ai->ai_socktype, ai->ai_protocol ); if( fd < 0 ) goto fail_this_af; /* Set the socket to non-blocking */ if( fcntl( fd, F_SETFL, O_NONBLOCK ) < 0 ) goto fail_this_af; /* Reuse socket address */ if( setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof( int ) ) < 0 ) goto fail_this_af; /* Try IPV6_V6ONLY, so IPv4 won't get mapped onto IPv6 */ #if defined( AF_INET6 ) && defined( IPV6_V6ONLY ) if( ai->ai_family == AF_INET6 && setsockopt( fd, IPPROTO_IPV6, IPV6_V6ONLY, &yes, sizeof( yes ) ) < 0 ) goto fail_this_af; #endif /* Bind to and listen on the port */ if( bind( fd, ai->ai_addr, ai->ai_addrlen ) < 0 ) goto fail_this_af; if( listen( fd, NET_BACKLOG ) < 0 ) goto fail_this_af; /* Report success, and add the FD to the list */ result->af_success[current] = 1; xasprintf( currentmsg, "Listening on %s port %s.", hostbuf, portstr ); /* Add the FD to the list */ result->listen_fds = xrealloc( result->listen_fds, sizeof( int ) * ( successful + 2 ) ); result->listen_fds[successful++] = fd; result->listen_fds[successful] = -1; continue; /* Single error handling and cleanup point */ fail_this_af: xasprintf( currentmsg, "Listening on %s port %s failed: %s", hostbuf, portstr, strerror( errno ) ); if( fd > -1 ) close( fd ); } freeaddrinfo( ailist ); /* Write conclusion to BindResult */ if( successful > 0 ) xasprintf( &result->conclusion, "Listening on port %s " "for %i protocol famil%s.", portstr, successful, ( successful == 1 ) ? "y" : "ies" ); else xasprintf( &result->conclusion, "Listening on port %s " "failed for all protocol families.", portstr ); result->af_success_count = successful; return; } extern void world_start_server_connect( World *wld ) { struct addrinfo hints, *ai = NULL; char *tmp; int len, ret, fd = -1; /* Clean up any previous address */ free( wld->server_address ); wld->server_address = NULL; /* This shouldn't happen */ if( wld->server_addresslist == NULL ) return; /* If we reached the end of the list of addresses, then none of the * addresses in the list worked. Bail out with error. */ if( wld->server_addresslist[0] == '\0' ) { free( wld->server_addresslist ); wld->server_addresslist = NULL; world_msg_client( wld, "Connecting failed, giving up." ); wld->server_status = ST_DISCONNECTED; wld->flags |= WLD_RECONNECT; return; } /* wld->server_addresslist is a C string containing a list of * \n-separated addresses. Isolate the first address, put it in * wld->server_address, and remove it from the list. */ tmp = wld->server_addresslist; len = strlen( tmp ); while( *tmp != '\n' && *tmp != '\0' ) tmp++; *tmp = '\0'; wld->server_address = xstrdup( wld->server_addresslist ); memmove( wld->server_addresslist, tmp + 1, len - strlen( wld->server_addresslist ) ); /* Bravely announce our attempt at the next address... */ world_msg_client( wld, " Trying %s", wld->server_address ); /* Specify the socket address we want. Any AF that fits the address, * STREAM socket type, TCP protocol, only numeric addresses. */ memset( &hints, '\0', sizeof( hints ) ); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_NUMERICHOST; /* Get the socket addresses */ ret = getaddrinfo( wld->server_address, wld->server_port, &hints, &ai ); if( ret != 0 ) { server_connect_error( wld, fd, gai_strerror( ret ) ); return; } /* Since we fed getaddrinfo() a numeric address string (e.g. "1.2.3.4") * we should get only one socket address (namely for the AF that * goes with the particular address string format). * Therefore, we only use the first of the returned list. */ fd = socket( ai->ai_family, ai->ai_socktype, ai->ai_protocol ); if( fd < 0 ) { freeaddrinfo( ai ); server_connect_error( wld, fd, strerror( errno ) ); return; } /* Non-blocking connect, please */ if( fcntl( fd, F_SETFL, O_NONBLOCK ) < 0 ) { freeaddrinfo( ai ); server_connect_error( wld, fd, strerror( errno ) ); return; } /* Connect. Failure with EINPROGRESS is ok (and even expected) */ ret = connect( fd, ai->ai_addr, ai->ai_addrlen ); if( ret < 0 && errno != EINPROGRESS ) { freeaddrinfo( ai ); server_connect_error( wld, fd, strerror( errno ) ); return; } /* Connection in progress! */ wld->server_connecting_fd = fd; wld->server_status = ST_CONNECTING; freeaddrinfo( ai ); } /* Handle activity on the non-blocking connecting FD. */ static void handle_connecting_fd( World *wld ) { int so_err, fd = wld->server_connecting_fd; socklen_t optlen = sizeof( so_err ); Line *line; /* Get the SO_ERROR option from the socket. This option holds the * error code for the connect() in the case of connection failure. * Also, check for other getsockopt() errors. */ if( getsockopt( fd, SOL_SOCKET, SO_ERROR, &so_err, &optlen ) < 0 ) { server_connect_error( wld, fd, strerror( errno ) ); return; } if( optlen != sizeof( so_err ) ) { server_connect_error( wld, fd, "getsockopt weirdness" ); return; } if( so_err != 0 ) { server_connect_error( wld, fd, strerror( so_err ) ); return; } /* Connecting successful, we have a FD. Now make it non-blocking. */ if( fcntl( fd, F_SETFL, O_NONBLOCK ) < 0 ) { server_connect_error( wld, fd, strerror( errno ) ); return; } /* Clean up addresslist */ free( wld->server_addresslist ); wld->server_addresslist = NULL; /* Transfer the FD */ wld->server_connecting_fd = -1; wld->server_fd = fd; /* Flag and announce connectedness. */ wld->server_status = ST_CONNECTED; world_msg_client( wld, " Success." ); line = world_msg_client( wld, "Now connected to world %s (%s:%s).", wld->name, wld->server_host, wld->server_port ); line->flags = LINE_CHECKPOINT; /* Inform the MCP layer that we just connected to the server. */ world_mcp_server_connect( wld ); /* We got a successful connection now. If autoreconnect is enabled, * we want to reconnect from now on. */ wld->reconnect_enabled = wld->autoreconnect; /* Auto-login */ world_login_server( wld, 0 ); } /* This function gets called from world_start_server_connect() and * handle_connecting_fd() on error. It displays a warning to the user, * cleans stuff up, and flags the world for the next connection attempt * (so that the next address in wld->server_addresslist is attempted) */ static void server_connect_error( World *wld, int fd, const char *err ) { world_msg_client( wld, " Failure: %s", err ); if( fd != -1 ) close( fd ); wld->server_connecting_fd = -1; free( wld->server_address ); wld->server_address = NULL; wld->flags |= WLD_SERVERCONNECT; } extern void world_cancel_server_connect( World *wld ) { /* We aren't connecting, abort */ if( wld->server_connecting_fd == -1 ) return; close( wld->server_connecting_fd ); wld->server_connecting_fd = -1; free( wld->server_address ); wld->server_address = NULL; free( wld->server_addresslist ); wld->server_addresslist = NULL; wld->server_status = ST_DISCONNECTED; } extern void world_disconnect_server( World *wld ) { if( wld->server_fd > -1 ) close( wld->server_fd ); wld->server_fd = -1; wld->server_txfull = 0; wld->server_rxfull = 0; free( wld->server_address ); wld->server_address = NULL; wld->server_status = ST_DISCONNECTED; } extern void world_disconnect_client( World *wld ) { /* We don't want to mess up the next client with undesired ansi * stuff, so we always disable ace on disconnect. */ if( wld->ace_enabled ) world_disable_ace( wld ); if( wld->client_fd != -1 ) close( wld->client_fd ); free( wld->client_prev_address ); wld->client_prev_address = wld->client_address; wld->client_address = NULL; wld->client_last_connected = current_time(); wld->client_fd = -1; wld->client_txfull = 0; wld->client_rxfull = 0; wld->client_status = ST_DISCONNECTED; } /* Accepts a new connection on the listening FD, and places the new connection * in the list of authentication connections. */ static void handle_listen_fd( World *wld, int which ) { struct sockaddr_storage sa; socklen_t sal = sizeof( sa ); int newfd, i, ret; char hostbuf[NI_MAXHOST + 1]; /* Accept the new connection */ newfd = accept( wld->listen_fds[which], (struct sockaddr *) &sa, &sal ); if( newfd == -1 && errno == ECONNABORTED ) /* No connection after all? Ok. */ return; if( newfd == -1 ) /* Other accept() errors shouldn't happen */ panic( PANIC_ACCEPT, errno, 0 ); /* Who's connecting to us? If getting the address fails, abort */ ret = getnameinfo( (struct sockaddr *) &sa, sal, hostbuf, NI_MAXHOST, NULL, 0, NI_NUMERICHOST ); if( ret != 0 ) { /* FIXME: This shouldn't happen. Should this be reported? */ close( newfd ); return; } /* Make the new fd nonblocking */ if( fcntl( newfd, F_SETFL, O_NONBLOCK ) == -1 ) { /* FIXME: This shouldn't happen. Should this be reported? */ close( newfd ); return; } /* FIXME: Should this be logged somewhere? */ /* printf( "Accepted connection from %s.\n", hostbuf ); */ /* If all auth slots are full, we need to kick one out. */ if( wld->auth_connections == NET_MAXAUTHCONN ) { int privcount = 0, remove = 0; for( i = 0; i < NET_MAXAUTHCONN; i++ ) privcount += wld->auth_ispriv[i]; /* If there are many privconns, just select the oldest conn. */ if( privcount > NET_AUTH_PRIVRES ) remove = 0; /* If there are few privconns, select the oldest non-priv. */ else for( i = 0 ; i < NET_MAXAUTHCONN; i++ ) if( !wld->auth_ispriv[i] ) { remove = i; break; } /* Remove the selected connection. */ remove_auth_connection( wld, remove, 0 ); } /* Use the next available slot */ i = wld->auth_connections++; /* Initialize carefully. auth_status is especially important. */ wld->auth_fd[i] = newfd; wld->auth_status[i] = AUTH_ST_WAITNET; wld->auth_read[i] = 0; wld->auth_address[i] = xstrdup( hostbuf ); wld->auth_ispriv[i] = is_privileged( wld, wld->auth_address[i] ); /* Zero the buffer, just to be sure. * Now an erroneous compare won't check against garbage (or worse, * a correct authentication string) still in the buffer. */ memset( wld->auth_buf[i], 0, NET_MAXAUTHLEN ); /* "Hey you!" */ write( newfd, NET_AUTHSTRING "\r\n", sizeof( NET_AUTHSTRING "\r\n" ) - 1); } /* Handles activity on an authenticating FD. Reads any data from the FD and * place it in auth buffer. If the auth buffer is full, or a \n is read from * the FD, verify the authentication attempt. */ static void handle_auth_fd( World *wld, int wa ) { int i, n; /* Only proceed if this connection is actually waiting for more data */ if( wld->auth_status[wa] != AUTH_ST_WAITNET ) return; /* Read into the buffer */ n = read( wld->auth_fd[wa], wld->auth_buf[wa] + wld->auth_read[wa], NET_MAXAUTHLEN - wld->auth_read[wa] ); if( n == -1 && ( errno == EINTR || errno == EAGAIN ) ) return; /* Connection closed, or error */ if( n <= 0 ) { remove_auth_connection( wld, wa, 0 ); return; } /* Search for the first newline in the newly read part. */ for( i = wld->auth_read[wa]; i < wld->auth_read[wa] + n; i++ ) if( wld->auth_buf[wa][i] == '\n' ) break; /* If we didn't encounter a newline, and we didn't read * too many characters, return */ if( i == wld->auth_read[wa] + n && i < NET_MAXAUTHLEN ) { wld->auth_read[wa] += n; return; } /* We encountered a newline or read too many characters, verify the * authentication attempt */ wld->auth_read[wa] += n; wld->auth_status[wa] = AUTH_ST_VERIFY; } /* Close and remove one authentication connection from the array, shift * the rest to make the array consecutive again, and decrease the counter.*/ static void remove_auth_connection( World *wld, int wa, int donack ) { char *buf; int i; /* Tell the peer the authentication string was wrong? */ if( donack ) write( wld->auth_fd[wa], NET_AUTHFAIL "\r\n", sizeof( NET_AUTHFAIL "\r\n" ) - 1); /* Connections that have been actively denied are counted towards * failed login attempts, so we record some information. */ if( donack ) { if( wld->client_login_failures < LONG_MAX ) wld->client_login_failures++; wld->client_last_failtime = current_time(); free( wld->client_last_failaddr ); wld->client_last_failaddr = wld->auth_address[wa]; wld->auth_address[wa] = NULL; } /* Free allocated resources */ free( wld->auth_address[wa] ); if( wld->auth_fd[wa] != -1 ) close( wld->auth_fd[wa] ); /* Remember the buffer address of the kicked connection for later */ buf = wld->auth_buf[wa]; /* Shift the later part of the array */ for( i = wa; i < wld->auth_connections - 1; i++ ) { wld->auth_fd[i] = wld->auth_fd[i + 1]; wld->auth_read[i] = wld->auth_read[i + 1]; wld->auth_buf[i] = wld->auth_buf[i + 1]; wld->auth_address[i] = wld->auth_address[i + 1]; wld->auth_status[i] = wld->auth_status[i + 1]; wld->auth_ispriv[i] = wld->auth_ispriv[i + 1]; } wld->auth_connections--; /* Initialise the newly freed auth connection */ wld->auth_read[wld->auth_connections] = 0; wld->auth_fd[wld->auth_connections] = -1; wld->auth_buf[wld->auth_connections] = buf; wld->auth_address[wld->auth_connections] = NULL; wld->auth_status[wld->auth_connections] = AUTH_ST_WAITNET; wld->auth_ispriv[wld->auth_connections] = 0; } /* Check the received buffer against the authstring. * If they match, the auth connection is flagged as authenticated, and * the client connection is flagged for disconnection. * Otherwise, the auth connection is torn down. */ static void verify_authentication( World *wld, int wa ) { char *buffer = wld->auth_buf[wa]; int alen, maxlen, buflen = wld->auth_read[wa]; Line *line; /* If the authentication string is nonexistent, reject everything */ if( wld->auth_hash == NULL ) { remove_auth_connection( wld, wa, 1 ); return; } /* Each authentication attempt uses up one token. */ if( wld->auth_tokenbucket > 0 ) wld->auth_tokenbucket--; /* Determine the maximum number of characters to scan */ maxlen = buflen + 2; if( maxlen > NET_MAXAUTHLEN ) maxlen = NET_MAXAUTHLEN; /* Search for the first newline */ for( alen = 0; alen < maxlen - 1; alen++ ) if( buffer[alen] == '\n' ) break; /* If we didn't find a newline, trash it. */ if( buffer[alen] != '\n' ) { if( wld->auth_ispriv[wa] ) privileged_del( wld, wld->auth_address[wa] ); remove_auth_connection( wld, wa, 1 ); return; } /* Replace the newline by a NUL, so the part of the buffer before * the newline becomes a C string. */ buffer[alen] = '\0'; /* If there was a \r before the newline, replace that too. */ if( alen > 0 && buffer[alen - 1] == '\r' ) buffer[alen - 1] = '\0'; /* Now, check if the string before the newline is correct. */ if( !world_match_authentication( wld, buffer ) ) { if( wld->auth_ispriv[wa] ) privileged_del( wld, wld->auth_address[wa] ); remove_auth_connection( wld, wa, 1 ); return; } /* Alen contains the length of the authentication string including * the newline character(s), minus 1. Thus, buffer[alen] used to be * \n and is now \0. We increase alen, so that buffer[alen] now * is the first character after the authentication string including * newline. */ alen++; /* Move anything after the (correct) authentication string to the * start of the buffer. */ memmove( buffer, buffer + alen, buflen - alen ); wld->auth_read[wa] -= alen; /* Tell the client (if any) that the connection is taken over, * and flag the client connection for disconnection. */ if( wld->client_status == ST_CONNECTED ) { /* We don't want to mess up the next client with undesired * stuff, so we disable ace now. */ if( wld->ace_enabled ) world_disable_ace( wld ); line = world_msg_client( wld, "Connection taken over by %s.", wld->auth_address[wa] ); line->flags &= ~LINE_DONTLOG; wld->flags |= WLD_CLIENTQUIT; } else { /* Record this connection for the log. */ line = world_msg_client( wld, "Client connected from %s.", wld->auth_address[wa] ); line->flags = LINE_LOGONLY; } /* Finally, flag this connection as correctly authenticated. */ wld->auth_status[wa] = AUTH_ST_CORRECT; } /* Promote the (correct) authconn to the client connection. * Clean up old auth stuff, transfer anything left in buffer to * client buffer. */ static void promote_auth_connection( World *wld, int wa ) { /* If the world is not correctly authenticated, ignore it. */ if( wld->auth_status[wa] != AUTH_ST_CORRECT ) return; /* The client shouln't be connected. * If it is, flag for disconnection and let 'em come back to us. */ if( wld->client_status != ST_DISCONNECTED ) { wld->flags |= WLD_CLIENTQUIT; return; } wld->client_status = ST_CONNECTED; /* Transfer connection */ wld->client_fd = wld->auth_fd[wa]; wld->auth_fd[wa] = -1; wld->client_address = wld->auth_address[wa]; wld->auth_address[wa] = NULL; wld->client_connected_since = current_time(); /* Add this address to the list of privileged addresses. */ privileged_add( wld, wld->client_address ); /* In order to copy anything left in the authbuffer to the rxbuffer, * the rxbuffer must be large enough. */ #if (NET_BBUFFER_LEN < NET_MAXAUTHLEN) #error NET_BBUFFER_LEN is smaller than NET_MAXAUTHLEN #endif /* Copy anything left in the authbuf to client buffer, and process */ memcpy( wld->client_rxbuffer, wld->auth_buf[wa], wld->auth_read[wa] ); wld->client_rxfull = buffer_to_lines( wld->client_rxbuffer, 0, wld->auth_read[wa], wld->client_rxqueue ); /* Clean up stuff left of the auth connection */ remove_auth_connection( wld, wa, 0 ); /* Introduce ourselves, and offer help. We're polite! */ world_msg_client( wld, "This is mooproxy %s. Get help with: %shelp.", VERSIONSTR, wld->commandstring ); /* If a client was previously connected, say when and from where. */ if( wld->client_prev_address != NULL ) world_msg_client( wld, "Last connected at %s, from %s.", time_fullstr( wld->client_last_connected ), wld->client_prev_address ); /* If there were unsuccessful login attempts since the last login, * report on that. */ if( wld->client_login_failures > 0 ) { world_msg_client( wld, "" ); world_msg_client( wld, "%i failed login attempt%s since %s.", wld->client_login_failures, wld->client_login_failures > 1 ? "s" : "", wld->client_prev_address != NULL ? "your last login" : "mooproxy started" ); world_msg_client( wld, "Last failed attempt at %s, from %s.", time_fullstr( wld->client_last_failtime ), wld->client_last_failaddr ); world_msg_client( wld, "" ); } /* Show context history and pass buffered text */ world_recall_and_pass( wld ); /* If there were a lot of failed login attempts, warn the user. */ if( wld->client_login_failures >= NET_TOOMANY_LOGIN_FAILURES ) world_msg_client( wld, "POSSIBLE BREAK-IN ATTEMPT! Many login" " failures, scroll up for more info." ); /* Reset the unsuccessful login counter. */ wld->client_login_failures = 0; /* Inform the MCP layer that a client just connected. */ if( wld->server_fd != -1 ) world_mcp_client_connect( wld ); } /* Handles any waiting data on the client FD. Read data, parse in to lines, * append to RX queue. If the connection died, close FD. */ static void handle_client_fd( World *wld ) { int n, offset = wld->client_rxfull; char *buffer = wld->client_rxbuffer; Line *line; /* Read into the buffer */ n = read( wld->client_fd, buffer + offset, NET_BBUFFER_LEN - offset ); /* Failure with EINTR or EAGAIN is acceptable. Just let it go. */ if( n == -1 && ( errno == EINTR || errno == EAGAIN ) ) return; /* The connection died, record a message. */ if( n < 1 ) { if( n < 0 ) line = world_msg_client( wld, "Connection to client " "lost (%s).", strerror( errno ) ); else line = world_msg_client( wld, "Client closed connection." ); world_disconnect_client( wld ); line->flags = LINE_LOGONLY; return; } /* Parse to lines, and place in queue */ wld->client_rxfull = buffer_to_lines( buffer, offset, n, wld->client_rxqueue ); } /* Handles any waiting data on the client FD. Read data, parse in to lines, * append to RX queue. If the connection died, close FD. */ static void handle_server_fd( World *wld ) { int n, offset = wld->server_rxfull; char *buffer = wld->server_rxbuffer; Line *line; /* Read into buffer */ n = read( wld->server_fd, buffer + offset, NET_BBUFFER_LEN - offset ); /* Failure with EINTR or EAGAIN is acceptable. Just let it go. */ if( n == -1 && ( errno == EINTR || errno == EAGAIN ) ) return; /* The connection died, notify the client */ if( n < 1 ) { if( n < 0 ) line = world_msg_client( wld, "Connection to server " "lost (%s).", strerror( errno ) ); else line = world_msg_client( wld, "The server closed the connection." ); world_disconnect_server( wld ); line->flags = LINE_CHECKPOINT; wld->flags |= WLD_RECONNECT; return; } /* Parse to lines, and place in queue */ wld->server_rxfull = buffer_to_lines( buffer, offset, n, wld->server_rxqueue ); } extern void world_flush_client_txbuf( World *wld ) { /* If we're not connected, do nothing */ if( wld->client_fd == -1 ) return; flush_buffer( wld->client_fd, wld->client_txbuffer, &wld->client_txfull, wld->client_txqueue, wld->inactive_lines, 1, wld->ace_prestr, wld->ace_poststr, NULL ); } extern void world_flush_server_txbuf( World *wld ) { /* If there is nothing to send, do nothing */ if( wld->server_txqueue->count == 0 && wld->server_txfull == 0 ) return; /* If we're not connected, discard and notify client */ if( wld->server_fd == -1 ) { linequeue_clear( wld->server_txqueue ); wld->flags |= WLD_NOTCONNECTED; return; } flush_buffer( wld->server_fd, wld->server_txbuffer, &wld->server_txfull, wld->server_txqueue, NULL, 1, NULL, NULL, NULL ); } extern void world_auth_add_bucket( World *wld ) { wld->auth_tokenbucket += NET_AUTH_TOKENSPERSEC; if( wld->auth_tokenbucket > NET_AUTH_BUCKETSIZE ) wld->auth_tokenbucket = NET_AUTH_BUCKETSIZE; } /* Add addr to the list of privileged addresses. Make room if needed. * Ensure that the new addr is at the bottom, so the list is LRU-ordered. */ static void privileged_add( World *wld, char *addr ) { /* If the address is already in the list, remove it. * This way, addr will be the last entry in the list, so the list * maintains a LRU ordering. */ privileged_del( wld, addr ); /* Make room if the list is full. */ if( wld->auth_privaddrs->count >= NET_MAX_PRIVADDRS ) line_destroy( linequeue_pop( wld->auth_privaddrs ) ); /* Add it. */ linequeue_append( wld->auth_privaddrs, line_create( xstrdup( addr ), -1 ) ); } /* Remove addr from the privileged addresses list, if present. */ static void privileged_del( World *wld, char *addr ) { Line *line; for( line = wld->auth_privaddrs->head; line; line = line->next ) if( !strcmp( addr, line->str ) ) { linequeue_remove( wld->auth_privaddrs, line ); line_destroy( line ); return; } } /* Check if addr is in the privileged address list. Returns 1 if so, else 0. */ static int is_privileged( World *wld, char *addr ) { Line *line; for( line = wld->auth_privaddrs->head; line; line = line->next ) if( !strcmp( addr, line->str ) ) return 1; return 0; } mooproxy-1.0.0/command.c0000644000175000017500000006404711525336516015032 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include #include #include #include #include "world.h" #include "command.h" #include "misc.h" #include "network.h" #include "config.h" #include "log.h" #include "daemon.h" #include "resolve.h" #include "recall.h" static int try_command( World *wld, char *cmd, char *args ); static int try_getoption( World *wld, char *key, char *args ); static int try_setoption( World *wld, char *key, char *args ); static int command_index( char *cmd ); static int refuse_arguments( World *wld, char *cmd, char *args ); static void show_longdesc( World *wld, char *desc ); static void show_help( World *wld ); static int show_command_help( World *wld, char *cmd ); static int show_setting_help( World *wld, char *key ); static void command_help( World *wld, char *cmd, char *args ); static void command_quit( World *wld, char *cmd, char *args ); static void command_shutdown( World *wld, char *cmd, char *args ); static void command_connect( World *wld, char *cmd, char *args ); static void command_disconnect( World *wld, char *cmd, char *args ); static void command_settings( World *wld, char *cmd, char *args ); static void command_recall( World *wld, char *cmd, char *args ); static void command_ace( World *wld, char *cmd, char *args ); static void command_version( World *wld, char *cmd, char *args ); static void command_date( World *wld, char *cmd, char *args ); static void command_uptime( World *wld, char *cmd, char *args ); static void command_world( World *wld, char *cmd, char *args ); static void command_forget( World *wld, char *cmd, char *args ); static void command_authinfo( World *wld, char *cmd, char *args ); static const struct { char *cmd; void (*func)( World *, char *, char * ); char *args; char *shortdesc; char *longdesc; } cmd_db[] = { { "help", command_help, "[]", "Helps you.", "Without argument, displays a summary of commands and settings.\n" "When the name of a command or setting is provided, displays \n" "detailed help for that command or setting." }, { "quit", command_quit, "", "Disconnects your client from mooproxy.", NULL }, { "shutdown", command_shutdown, "[-f]", "Shuts down mooproxy.", "Shuts down mooproxy.\n\n" "Under some circumstances, mooproxy may refuse to shut down\n" "(for example if not all loggable lines have been written to\n" "disk). -f may be used to force shutdown. Sending mooproxy SIGTERM\n" "is equivalent to /shutdown, SIGQUIT to /shutdown -f." }, { "connect", command_connect, "[ []]", "Connects to the server.", "Connects to the server.\n\n" "The arguments, and , are optional. If they're not\n" "provided, mooproxy will use the settings \"host\" and \"port\"." }, { "disconnect", command_disconnect, "", "Disconnects from the server.", NULL }, { "settings", command_settings, "", "Lists the available settings.", "Lists all available settings, and their current values." }, { "recall", command_recall, "<...>", "Recalls some lines from history.", " recall \n" "\n" "Will recall the last lines.\n" "\n" " recall [from ] [to ] [search ]\n" "\n" "Will recall the lines from to that match the\n" "search , where is a case-insensitive primitive regexp\n" "(supporting . and .*), and is one or more of:\n" "\n" " now\n" " today\n" " yesterday\n" " last/next mon[day]/tue[sday]/...\n" " [YY/]MM/DD\n" " HH:MM[:SS]\n" " -/+ s[econds]/m[inutes]/h[ours]/d[ays]\n" " -/+ l[ines] (only for the to timespec, and only alone)\n" "\n" "Examples:\n" "\n" " recall from 10:00 to 11:00 search gandalf.*morning\n" " recall from yesterday 16:00 to +20 lines\n" " recall from -30m search joke\n" " recall from last monday to next wednesday search weather\n" " recall from 04/22 next wed 11:35 to +1 hour\n" "\n" "For more details, see the README file.\n" }, { "ace", command_ace, "[x | off]", "En/disables ANSI client emulation.", "Enables or disables ANSI client emulation (ACE).\n" "\n" "ACE is intended to be used when the client is a text terminal\n" "(such as telnet or netcat running in a terminal window) rather\n" "than a proper MOO client.\n" "ACE makes mooproxy send ANSI escape sequences that will emulate\n" "the behaviour of a primitive MOO client on a text terminal.\n" "\n" "Because mooproxy cannot know the size of your terminal, you have\n" "to supply the size of your terminal to the ace command as COLxROW,\n" "where COL is the number of columns, and ROW is the number of rows.\n" "\n" "If you invoke the command without arguments, and ACE was not yet\n" "enabled, ACE will be enabled with a default size of 80x24.\n" "If you invoke the command without arguments, and ACE was already\n" "enabled, ACE will re-initialize with the last known size.\n" "\n" "When invoked with \"off\", ACE will be disabled.\n" "\n" "ACE will be automatically disabled when you disconnect or reconnect.\n" "\n" "ACE is known to work properly with the Linux console, xterm, rxvt,\n" "gnome-terminal, putty, and Windows XP telnet.\n" }, { "version", command_version, "", "Shows the mooproxy version.", NULL }, { "date", command_date, "", "Shows the current date and time.", NULL }, { "uptime", command_uptime, "", "Shows mooproxy's starting time and uptime.", NULL }, { "world", command_world, "", "Shows the name of the current world.", NULL }, { "forget", command_forget, "", "Forgets all history lines.", NULL }, { "authinfo", command_authinfo, "", "Shows some authentication information.", NULL }, { NULL, NULL, NULL, NULL, NULL } }; extern int world_do_command( World *wld, char *line ) { char *args, *cmd = line, *cstr = wld->commandstring; /* Ignore the equal parts of commandstr and the line. */ while( *cstr && *cstr == *cmd ) { cstr++; cmd++; } /* If there's still something left in cstr, it's not a command. */ if( *cstr ) return 0; /* Now separate the command from the args. */ args = cmd; while( *args && !isspace( *args ) ) args++; /* Extract the command and the arguments. */ cmd = xstrndup( cmd, args - cmd ); args = trim_whitespace( xstrdup( args ) ); if( ! *args ) { free( args ); args = NULL; } /* Try parsing cmd + args as a command, an option query, and an * option update, in that order. * The try_* functions may modify cmd and args _only_ if they * successfully parse the command. They may never free them. * args will be NULL if there were no arguments. */ if( try_command( wld, cmd, args ) || try_getoption( wld, cmd, args ) || try_setoption( wld, cmd, args ) ) { free( cmd ); free( args ); return 1; } /* Ok, it's not something we understand. */ /* If strictcmd is off, the line should be processed as regular. */ if( !wld->strict_commands ) { free( cmd ); free( args ); return 0; } /* Invalid command, complain */ world_msg_client( wld, "No such command or option: %s.", cmd ); free( cmd ); free( args ); return 1; } /* Try if cmd is a valid command. * If it is, call the proper function and return 1. Otherwise, return 0. */ static int try_command( World *wld, char *cmd, char *args ) { int idx; /* See if it's a command, and if so, call the proper function. */ idx = command_index( cmd ); if( idx > -1 ) { (*cmd_db[idx].func)( wld, cmd, args ); return 1; } return 0; } /* Try if key is a valid key and args is empty. * If it is, query the option value and return 1. Otherwise, return 0. */ static int try_getoption( World *wld, char *key, char *args ) { char *val, *kend; /* If we have arguments, the option is not being queried. */ if( args ) return 0; /* Make kend point to the last char of key, or \0 if key is empty. */ kend = key + strlen( key ); if( kend != key ) kend--; switch( world_get_key( wld, key, &val ) ) { case GET_KEY_NF: /* Key not found, tell the caller to keep trying. */ return 0; break; case GET_KEY_OK: world_msg_client( wld, "The option %s is %s.", key, val ); free( val ); break; case GET_KEY_PERM: world_msg_client( wld, "The option %s may not be read.", key ); break; } return 1; } /* Try if key is a valid key and args is non-empty. * If it is, update the option value and return 1. Otherwise, return 0. */ static int try_setoption( World *wld, char *key, char *args ) { char *val, *tmp, *err; /* If we have no arguments, the option is not being set. */ if( !args ) return 0; /* Remove any enclosing quotes. * We strdup, because we may not modify args yet. */ val = remove_enclosing_quotes( xstrdup( args ) ); switch( world_set_key( wld, key, val, &err ) ) { case SET_KEY_NF: /* Key not found, tell the caller to keep trying. */ free( val ); return 0; break; case SET_KEY_PERM: world_msg_client( wld, "The option %s may not be set.", key ); break; case SET_KEY_BAD: world_msg_client( wld, "%s", err ); free( err ); break; case SET_KEY_OKSILENT: break; case SET_KEY_OK: /* Query the option we just set, so we get a normalized * representation of the value. */ if( world_get_key( wld, key, &tmp ) == GET_KEY_OK ) { world_msg_client( wld, "The option %s is now %s.", key, tmp ); free( tmp ); break; } /* The key was successfully set, but we could not query the * new value. Maybe the option is hidden or it may not be * read. Just say it's been changed. */ world_msg_client( wld, "The option %s has been changed.", key ); break; } free( val ); return 1; } /* Returns the index of the command in the db, or -1 if not found. */ static int command_index( char *cmd ) { int i; for( i = 0; cmd_db[i].cmd; i++ ) if( !strcmp( cmd, cmd_db[i].cmd ) ) return i; return -1; } /* If the args contains anything else than whitespace, complain to the * client and return 1. Otherwise, NOP and return 0. */ static int refuse_arguments( World *wld, char *cmd, char *args ) { /* If args is NULL, we're ok. */ if( !args ) return 0; /* Otherwise, we complain. */ world_msg_client( wld, "The command %s does not take arguments.", cmd ); return 1; } /* Shows a \n-separated string of lines to the client, line by line. * Each line is indented by two spaces. */ static void show_longdesc( World *wld, char *desc ) { char *s, *t; s = xmalloc( strlen( desc ) + 1 ); while( *desc ) { /* s is our temporary storage. Copy the current line of desc * to s, and advance desc to point to the next line. */ t = s; while( *desc != '\0' && *desc != '\n' ) *t++ = *desc++; *t = '\0'; if( *desc == '\n' ) desc++; /* And send the copied line off to the client. */ world_msg_client( wld, " %s", s ); } free( s ); } /* Display the "generic" help, listing all commands and settings. */ static void show_help( World *wld ) { int i, numkeys, longest = 0; char **keylist, *desc, *tmp; world_msg_client( wld, "" ); world_msg_client( wld, "Use %shelp or %shelp " "to get more detailed help.", wld->commandstring, wld->commandstring ); world_msg_client( wld, "" ); /* Determine the longest command (for layout). */ for( i = 0; cmd_db[i].cmd; i++ ) { /* We're displaying the command name plus its arguments, so * we need to determine the length of both. */ int l = strlen( cmd_db[i].cmd ) + strlen( cmd_db[i].args ) + 1; if( l > longest ) longest = l; } /* Get the list of settings, and determine the longest cmd/setting. */ numkeys = world_get_key_list( wld, &keylist ); for( i = 0; i < numkeys; i++ ) if( strlen( keylist[i] ) > longest ) longest = strlen( keylist[i] ); /* Display the commands. */ world_msg_client( wld, "Commands" ); for( i = 0; cmd_db[i].cmd; i++ ) { /* Join the command and its arguments, we need to feed them * to printf as one string. */ xasprintf( &tmp, "%s %s", cmd_db[i].cmd, cmd_db[i].args ); world_msg_client( wld, " %*s %s", -longest, tmp, cmd_db[i].shortdesc ); free( tmp ); } /* Display the settings. */ world_msg_client( wld, "" ); world_msg_client( wld, "Settings" ); for( i = 0; i < numkeys; i++ ) { char *key = keylist[i]; desc = ""; /* In case world_desc_key() fails. */ world_desc_key( wld, key, &desc, &tmp); world_msg_client( wld, " %*s %s", -longest, key, desc ); } free( keylist ); } /* Displays the help for one command. * Returns 1 if it was able to show the help for cmd, 0 otherwise. */ static int show_command_help( World *wld, char *cmd ) { int idx; idx = command_index( cmd ); if( idx < 0 ) return 0; world_msg_client( wld, "" ); world_msg_client( wld, "%s%s %s", wld->commandstring, cmd_db[idx].cmd, cmd_db[idx].args ); world_msg_client( wld, "" ); if( cmd_db[idx].longdesc ) show_longdesc( wld, cmd_db[idx].longdesc ); else show_longdesc( wld, cmd_db[idx].shortdesc ); return 1; } /* Displays the help for one setting. * Returns 1 if it was able to show the help for key, 0 otherwise. */ static int show_setting_help( World *wld, char *key ) { char *shortdesc, *longdesc; if( world_desc_key( wld, key, &shortdesc, &longdesc ) != GET_KEY_OK ) return 0; world_msg_client( wld, "" ); world_msg_client( wld, "%s", key ); world_msg_client( wld, "" ); if( longdesc ) show_longdesc( wld, longdesc ); else show_longdesc( wld, shortdesc ); return 1; } /* Give help on a command or setting. If the command or setting is not * recognized, show some generic help. */ static void command_help( World *wld, char *cmd, char *args ) { /* Generic help. */ if( !args ) { show_help( wld ); return; } /* Help for one command. */ if( show_command_help( wld, args ) ) return; /* Help for one setting. */ if( show_setting_help( wld, args ) ) return; /* And if all else fails: generic help. */ show_help( wld ); } /* Disconnect the client. No arguments. */ static void command_quit( World *wld, char *cmd, char *args ) { Line *line; if( refuse_arguments( wld, cmd, args ) ) return; /* We try to leave their terminal in a nice state when they leave. */ if( wld->ace_enabled ) world_disable_ace( wld ); world_msg_client( wld, "Closing connection." ); wld->flags |= WLD_CLIENTQUIT; /* And one for the log... */ line = world_msg_client( wld, "Client /quit." ); line->flags = LINE_LOGONLY; } /* Shut down or restart mooproxy (forcibly on -f). Arguments: [-f] */ static void command_shutdown( World *wld, char *cmd, char *args ) { int force = 0; /* Are we being forced? */ if( args && !strcmp( args, "-f" ) ) force = 1; /* We don't recognize any other arguments. */ if( args && strcmp( args, "-f" ) ) { world_msg_client( wld, "Unrecognised argument: `%s'", args ); return; } world_start_shutdown( wld, force, 0 ); } /* Connect to the server. Arguments: [host [port]] * The function sets wld->server_host and wld->server_port and then * flags the world for server resolve start. */ static void command_connect( World *wld, char *cmd, char *args ) { char *tmp; long port; /* Are we already connected? */ if( wld->server_status == ST_CONNECTED ) { world_msg_client( wld, "Already connected to %s.", wld->server_host ); return; } /* Or in the process of connecting? */ if( wld->server_status == ST_RESOLVING || wld->server_status == ST_CONNECTING ) { world_msg_client( wld, "Connection attempt to %s already " "in progress.", wld->server_host ); return; } /* Or perhaps we're waiting to reconnect? */ if( wld->server_status == ST_RECONNECTWAIT ) { world_msg_client( wld, "Resetting autoreconnect delay and " "reconnecting immediately." ); wld->reconnect_delay = 0; wld->reconnect_at = current_time(); world_do_reconnect( wld ); return; } /* First, clean up existing target. */ free( wld->server_host ); wld->server_host = NULL; free( wld->server_port ); wld->server_port = NULL; /* If there is an argument, use the first word as hostname */ if( args && ( tmp = get_one_word( &args ) ) != NULL ) wld->server_host = xstrdup( tmp ); /* If there's another argument, use that as the port */ if( args && ( tmp = get_one_word( &args ) ) != NULL ) { port = strtol( tmp, NULL, 0 ); /* See that the argument port is valid. */ if( port < 1 || port > 65535 ) { world_msg_client( wld, "`%s' is not a valid port " "number.", tmp ); return; } xasprintf( &wld->server_port, "%li", port ); } /* If there's no server hostname at all, complain. */ if( wld->server_host == NULL && wld->dest_host == NULL ) { world_msg_client( wld, "No server host to connect to." ); return; } /* If there's no argument hostname, use the configured one. */ if( wld->server_host == NULL ) wld->server_host = xstrdup( wld->dest_host ); /* If there's no server port at all, complain. */ if( wld->server_port == NULL && wld->dest_port == -1 ) { world_msg_client( wld, "No server port to connect to." ); return; } /* If there's no argument port, use the configured one. */ if( wld->server_port == NULL ) xasprintf( &wld->server_port, "%li", wld->dest_port ); /* Inform the client */ world_msg_client( wld, "Connecting to %s, port %s", wld->server_host, wld->server_port ); /* We don't reconnect if a new connection attempt fails. */ wld->reconnect_enabled = 0; /* Start the resolving */ wld->flags |= WLD_SERVERRESOLVE; } /* Disconnects from the server. No arguments. */ static void command_disconnect( World *wld, char *cmd, char *args ) { Line *line; if( refuse_arguments( wld, cmd, args ) ) return; switch ( wld->server_status ) { case ST_DISCONNECTED: world_msg_client( wld, "Not connected." ); break; case ST_RESOLVING: world_cancel_server_resolve( wld ); world_msg_client( wld, "Connection attempt aborted." ); break; case ST_CONNECTING: world_cancel_server_connect( wld ); world_msg_client( wld, "Connection attempt aborted." ); break; case ST_CONNECTED: wld->flags |= WLD_SERVERQUIT; line = world_msg_client( wld, "Disconnected from server." ); line->flags = LINE_CHECKPOINT; break; case ST_RECONNECTWAIT: wld->server_status = ST_DISCONNECTED; wld->reconnect_delay = 0; world_msg_client( wld, "Canceled reconnect." ); } } /* Prints a list of options and their values. No arguments. */ static void command_settings( World *wld, char *cmd, char *args ) { char **list, *key, *val; int i, num, longest = 0; if( refuse_arguments( wld, cmd, args ) ) return; world_msg_client( wld, "Settings" ); num = world_get_key_list( wld, &list ); /* Calculate the longest key name, so we can line out the table. */ for( i = 0; i < num; i++ ) if( strlen( list[i] ) > longest ) longest = strlen( list[i] ); for( i = 0; i < num; i++ ) { key = list[i]; if( world_get_key( wld, key, &val ) != GET_KEY_OK ) val = xstrdup( "-" ); world_msg_client( wld, " %*s %s", -longest, key, val ); free( val ); } free( list ); } /* Recalls lines. */ static void command_recall( World *wld, char *cmd, char *args ) { Linequeue *queue; long count; char *str; /* If there are no arguments, print terse usage. */ if( !args ) { world_msg_client( wld, "Use: recall [from ] [to ]" " [search ]" ); world_msg_client( wld, "Or: recall last " " [search ]" ); world_msg_client( wld, "Or: recall " ); world_msg_client( wld, "See the README file for details." ); return; } /* We want to include the inactive lines in our recall as well, so * we move them to the history right now. */ world_inactive_to_history( wld ); /* Check if there are any lines to recall. */ if( wld->history_lines->count == 0 ) { world_msg_client( wld, "There are no lines to recall." ); return; } /* Check if the arg string is all digits. */ for( str = args; isdigit( *str ); str++ ) continue; /* It isn't, pass it on for more sophisticated processing. */ if( *str != '\0' ) { world_recall_command( wld, args ); return; } /* Ok, it's all digits, do 'classic' recall. */ count = atol( args ); if( count <= 0 ) { world_msg_client( wld, "Number of lines to recall should be " "greater than zero." ); return; } /* Get the recalled lines. */ queue = world_recall_history( wld, count ); /* Announce the recall. */ world_msg_client( wld, "Recalling %lu line%s.", queue->count, ( queue->count == 1 ) ? "" : "s" ); /* Append the recalled lines, and clean up. */ linequeue_merge( wld->client_toqueue, queue ); linequeue_destroy( queue ); } /* Enable/disable ACE. Arguments: ROWSxCOLUMNS, or 'off'. */ static void command_ace( World *wld, char *cmd, char *args ) { int cols, rows; /* If /ace off, and ACE wasn't enabled, complain and be done. */ if( args && !strcmp( args, "off" ) && !wld->ace_enabled ) { world_msg_client( wld, "ACE is not enabled." ); return; } /* If /ace off, and ACE was enabled, disable it and be done. */ if( args && !strcmp( args, "off" ) && wld->ace_enabled ) { world_msg_client( wld, "Disabled ACE." ); world_disable_ace( wld ); return; } /* If we have arguments, parse them. */ if( args ) { if( sscanf( args, "%ix%i", &cols, &rows ) != 2 ) { /* Parsing failed, complain and be done. */ world_msg_client( wld, "Usage: ace x" ); return; } /* Parsing succesful, the dimensions are in cols/rows. */ } /* No arguments. */ if( !args ) { if( !wld->ace_enabled ) { /* ACE wasn't enabled, initialize to 80x24. */ cols = 80; rows = 24; } else { /* Ace was already enabled, use the previous dims. */ cols = wld->ace_cols; rows = wld->ace_rows; } } /* If the number of columns is too small or too large, complain. */ if( cols < 20 || cols > 2000 ) { world_msg_client( wld, "The number of columns should be " "between 20 and 2000." ); return; } /* If the number of rows is too small or too large, complain. */ if( rows < 10 || rows > 1000 ) { world_msg_client( wld, "The number of rows should be " "between 10 and 1000." ); return; } wld->ace_cols = cols; wld->ace_rows = rows; if( world_enable_ace( wld ) ) world_msg_client( wld, "Enabled ACE for %ix%i terminal.", wld->ace_cols, wld->ace_rows ); else world_msg_client( wld, "Failed to enable ACE." ); } /* Print the version. No arguments. */ static void command_version( World *wld, char *cmd, char *args ) { if( refuse_arguments( wld, cmd, args ) ) return; world_msg_client( wld, "Mooproxy version %s (released on %s).", VERSIONSTR, RELEASEDATE ); } /* Print the current (full) date. No arguments. */ static void command_date( World *wld, char *cmd, char *args ) { if( refuse_arguments( wld, cmd, args ) ) return; world_msg_client( wld, "The current date is %s.", time_fullstr( current_time() ) ); } /* Print mooproxies starting date/time and uptime. No arguments. */ static void command_uptime( World *wld, char *cmd, char *args ) { time_t tme = uptime_started_at(); long utme = (long) current_time() - tme; if( refuse_arguments( wld, cmd, args ) ) return; world_msg_client( wld, "Started %s. Uptime is %li days, %.2li:%.2li:" "%.2li.", time_fullstr( tme ), utme / 86400, utme % 86400 / 3600, utme % 3600 / 60, utme % 60 ); } /* Print the name of the current world. No arguments. */ static void command_world( World *wld, char *cmd, char *args ) { char *status; if( refuse_arguments( wld, cmd, args ) ) return; switch( wld->server_status ) { case ST_DISCONNECTED: status = "not connected"; break; case ST_RESOLVING: status = "resolving hostname"; break; case ST_CONNECTING: status = "attempting to connect"; break; case ST_CONNECTED: status = "connected"; break; case ST_RECONNECTWAIT: status = "waiting before reconnect"; break; default: status = "unknown status"; break; } world_msg_client( wld, "The world is %s (%s).", wld->name, status ); } /* Clear all history lines from memory. No arguments. */ static void command_forget( World *wld, char *cmd, char *args ) { if( refuse_arguments( wld, cmd, args ) ) return; world_inactive_to_history( wld ); linequeue_clear( wld->history_lines ); world_msg_client( wld, "All history lines have been forgotten." ); } /* Print some authentication information. No arguments. */ static void command_authinfo( World *wld, char *cmd, char *args ) { Line *line; if( refuse_arguments( wld, cmd, args ) ) return; /* Header. */ world_msg_client( wld, "Authentication information:" ); world_msg_client( wld, "" ); /* Current connection. */ world_msg_client( wld, " Current connection from %s since %s.", wld->client_address, time_fullstr( wld->client_connected_since ) ); /* Previous connection. */ if( wld->client_prev_address ) world_msg_client( wld, " Previous connection from %s until " "%s.", wld->client_prev_address, time_fullstr( wld->client_last_connected ) ); else world_msg_client( wld, " No previous connection." ); world_msg_client( wld, "" ); /* Failed attempts. */ world_msg_client( wld, " %i failed login attempts since you logged " "in.", wld->client_login_failures ); /* Last failed attempt. */ if( wld->client_last_failaddr ) world_msg_client( wld, " Last failed from %s at %s.", wld->client_last_failaddr, time_fullstr( wld->client_last_failtime ) ); world_msg_client( wld, "" ); /* Privileged addresses. */ world_msg_client( wld, " Privileged addresses (%i/%i):", wld->auth_privaddrs->count, NET_MAX_PRIVADDRS ); for( line = wld->auth_privaddrs->tail; line; line = line->prev) world_msg_client( wld, " - %s", line->str ); world_msg_client( wld, "" ); /* Authentication slots/bucket. */ world_msg_client( wld, " Authentication slots: %i/%i used.", wld->auth_connections, NET_MAXAUTHCONN ); world_msg_client( wld, " Authentication token bucket is %i/%i full. " "Refill rate: %i/sec.", wld->auth_tokenbucket, NET_AUTH_BUCKETSIZE, NET_AUTH_TOKENSPERSEC ); } mooproxy-1.0.0/timer.h0000644000175000017500000000234611525336516014533 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__TIMER #define MOOPROXY__HEADER__TIMER #include /* Update the current time administration to the supplied time, but do not * run periodic or scheduled events. This function should be called at least * once before calling world_timer_tick(). */ extern void world_timer_init( World *wld, time_t t ); /* Update current time administration, and execute periodic or scheduled * events. This function should be called regularly (preferably once each * second). t should be the current time as reported by time() */ extern void world_timer_tick( World *wld, time_t t ); #endif /* ifndef MOOPROXY__HEADER__TIMER */ mooproxy-1.0.0/daemon.h0000644000175000017500000000377111525336516014661 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__DAEMON #define MOOPROXY__HEADER__DAEMON #include #include #include "world.h" /* Set up the signal handlers. Duh :) */ extern void set_up_signal_handlers( void ); /* Set the time at which mooproxy started, to determine the uptime. * This function also seeds the PRNG using the useconds from gettimeofday(). */ extern void uptime_started_now( void ); /* Get the time at which mooproxy started. */ extern time_t uptime_started_at( void ); /* Split off daemon, so we can run in the background. * In summary: fork, setsid, fork again, chdir, and redirect stdin, stdout * and stderr to /dev/zero. */ extern pid_t daemonize( char **err ); /* The exit function for the launching parent, after the daemonize step * is complete. */ extern void launch_parent_exit( int exitval ); /* Attempt to lock this worlds lockfile (creating it if it doesn't exist). * If the file could not be created or locked, err will contain an error * message, and non-zero is returned. On success, the lockfile FD is placed * in world->lockfile_fd, and zero is returned. */ extern int world_acquire_lock_file( World *wld, char **err ); /* Write the PID to the pidfile (best effort, won't throw error) */ extern void world_write_pid_to_file( World *wld, pid_t pid ); /* Closes the lockfile, and remove it. */ extern void world_remove_lockfile( World *wld ); #endif /* ifndef MOOPROXY__HEADER__DAEMON */ mooproxy-1.0.0/config.c0000644000175000017500000004143211525336516014652 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include #include #include #include #include #include #include #include #include "global.h" #include "config.h" #include "accessor.h" #include "misc.h" #include "world.h" /* The separator for keys and values in the configfile. */ #define SEPARATOR_CHAR '=' #define COMMENT_CHAR '#' static int read_configfile( char *file, char **contents, char **err ); static int set_key_value( World *wld, char *key, char *value, char **err ); static int set_key_internal( World *wld, char *key, char *value, int src, char **err ); static int get_key_internal( World *wld, char *key, char **value, int src ); /* The list of settable/gettable options. The hidden flag indicates if the * option is visible to the user. The setters and getters are functions * that actually set or query the option, and are located in accessor.c. */ static const struct { int hidden; char *keyname; int (*setter)( World *, char *, char *, int, char ** ); int (*getter)( World *, char *, char **, int ); char *shortdesc; char *longdesc; } key_db[] = { { 0, "listenport", aset_listenport, aget_listenport, "The network port mooproxy listens on.", "The network port mooproxy listens on for client connections." }, { 0, "auth_hash", aset_auth_hash, aget_auth_hash, "The \"password\" a client needs to connect.", "Clients connecting to mooproxy have to provide a string\n" "(such as \"connect \") to authenticate\n" "themselves. This setting contains a hash of this string.\n" "\n" "To change auth_hash, you need to specify the new hash as\n" "well as the old literal authentication string, like so:\n" "\n" "/auth_hash \"\"\n" "\n" "You can generate a new hash by running mooproxy --md5crypt.\n" "See the README for more details on authentication." }, { 0, "host", aset_dest_host, aget_dest_host, "The hostname of the server to connect to.", NULL }, { 0, "port", aset_dest_port, aget_dest_port, "The port to connect to on the server.", NULL }, { 0, "autologin", aset_autologin, aget_autologin, "Log in automatically after connecting.", "If true, mooproxy will try to log you in after connecting to\n" "the server. For this, it will use the client authentication\n" "string (see auth_hash)." }, { 0, "autoreconnect", aset_autoreconnect, aget_autoreconnect, "Reconnect when the server disconnects us.", "If true, mooproxy will attempt to reconnect to the server\n" "whenever the connection to the server is lost or fails to be\n" "established. It uses exponential back-off.\n" "\n" "Mooproxy will not reconnect if establishing a connection\n" "fails directly after the /connect command has been issued.\n" "\n" "Autoreconnect usually only makes sense if autologin is on." }, { 0, "commandstring", aset_commandstring, aget_commandstring, "How mooproxy recognizes commands.", "Lines from the client starting with this string are\n" "interpreted as commands (but see also: strict_commands).\n" "This string does not need to be one character, it can be any\n" "length (even empty)." }, { 0, "strict_commands", aset_strict_commands, aget_strict_commands, "Ignore invalid commands.", "If mooproxy receives a string from the client that starts\n" "with the commandstring, but is not a valid command, this\n" "setting determines what mooproxy will do.\n" "\n" "If true, mooproxy will complain that the command is invalid.\n" "If false, mooproxy will pass the line through to the server\n" "as if it was a regular line." }, { 0, "infostring", aset_infostring, aget_infostring, "Prefix for all messages from mooproxy.", "Informational messages from mooproxy to the user are\n" "prefixed by this string.\n" "\n" "Use the following sequences to get colours:\n" " %b -> blue %g -> green %m -> magenta %w -> white\n" " %c -> cyan %k -> black %r -> red %y -> yellow\n" "Use uppercase to get the bold/bright variant of that colour.\n" "Use %% to get a literal %." }, { 0, "newinfostring", aset_newinfostring, aget_newinfostring, "Prefix for history/new lines messages.", "Mooproxy prefixes this string to the \"context history\",\n" "\"possibly new lines\", \"certainly new lines\" and \"end of new\n" "lines\" messages. Setting this to something colourful might\n" "make it easier to spot these messages.\n" "\n" "This string accepts the same colour sequences as infostring.\n" "If this string is set to the empty string (\"\"), mooproxy will\n" "use the regular infostring for these messages." }, { 0, "context_lines", aset_context_lines, aget_context_lines, "Context lines to provide when you connect.", "When a client connects, mooproxy can reproduce lines from\n" "history to the client, in order to provide the user with\n" "context. This setting sets the number of reproduced lines." }, { 0, "buffer_size", aset_buffer_size, aget_buffer_size, "Max memory to spend on new/history lines.", "The maximum amount of memory in KiB used to hold history\n" "lines (lines you have already read) and new lines (lines you\n" "have not yet read).\n" "\n" "If the amount of lines mooproxy receives from the server\n" "while the client is not connected exceeds this amount of\n" "memory, mooproxy will have to drop unread lines (but it will\n" "still attempt to log those lines, see logbuffer_size)." }, { 0, "logbuffer_size", aset_logbuffer_size, aget_logbuffer_size, "Max memory to spend on unlogged lines.", "The maximum amount of memory in KiB used to hold loggable\n" "lines that have not yet been written to disk.\n" "\n" "If your disk space runs out, and the amount of unlogged\n" "lines exceeds this amount of memory, new lines will NOT be\n" "logged." }, { 0, "logging", aset_logging, aget_logging, "Log everything from the server.", "If true, mooproxy will log all lines from the server (and a\n" "select few messages from mooproxy) to file." }, { 0, "log_timestamps", aset_log_timestamps, aget_log_timestamps, "Prefix every logged line by a timestamp.", "If true, all logged lines are prefixed with a [HH:MM:SS]\n" "timestamp." }, { 1, "easteregg_version", aset_easteregg_version, aget_easteregg_version, NULL, NULL }, { 0, NULL, NULL, NULL, NULL, NULL } }; /* Command line options. */ static const char short_opts[] = ":hVLw:md"; static const struct option long_opts[] = { { "help", 0, NULL, 'h' }, { "version", 0, NULL, 'V' }, { "license", 0, NULL, 'L' }, { "world", 1, NULL, 'w' }, { "md5crypt", 0, NULL, 'm' }, { "no-daemon", 0, NULL, 'd' }, { NULL, 0, NULL, 0 } }; extern void parse_command_line_options( int argc, char **argv, Config *config ) { int result; config->action = 0; config->worldname = NULL; config->no_daemon = 0; config->error = NULL; opterr = 0; while( ( result = getopt_long( argc, argv, short_opts, long_opts, NULL ) ) != -1 ) { switch( result ) { /* -h, --help */ case 'h': config->action = PARSEOPTS_HELP; return; /* -L, --license */ case 'L': config->action = PARSEOPTS_LICENSE; return; /* -V, --version */ case 'V': config->action = PARSEOPTS_VERSION; return; /* -m, --md5crypt */ case 'm': config->action = PARSEOPTS_MD5CRYPT; return; /* -w, --world */ case 'w': free( config->worldname ); config->worldname = xstrdup( optarg ); break; /* -d, --no-daemon */ case 'd': config->no_daemon = 1; break; /* Unrecognised */ case '?': /* On unrecognised short option, optopt is the unrecognized * char. On unrecognised long option, optopt is 0 and optind * contains the number of the unrecognized argument. * Sigh... */ if( optopt == 0 ) xasprintf( &config->error, "Unrecognized option: `%s'" ". Use --help for help.", argv[optind - 1] ); else xasprintf( &config->error, "Unrecognized option: `-%c'" ". Use --help for help.", optopt ); config->action = PARSEOPTS_ERROR; return; /* Option without required argument. */ case ':': xasprintf( &config->error, "Option `%s' expects an argument.", argv[optind - 1] ); config->action = PARSEOPTS_ERROR; return; /* We shouldn't get here */ default: break; } } /* Any non-options left? */ if( optind < argc ) { xasprintf( &config->error, "Unexpected argument: `%s'. " "Use --help for help.", argv[optind] ); config->action = PARSEOPTS_ERROR; return; } } extern int world_load_config( World *wld, char **err ) { char *config, *cfg, *line, *sep, *key, *value, *seterr; long lineno = 0; if( read_configfile( wld->configfile, &config, err ) ) return 1; /* We use and modify cfg. Config is used to free the block later. */ cfg = config; for(;;) { /* We read a line */ line = read_one_line( &cfg ); lineno++; /* No more lines? Done */ if( !line ) break; /* Is the line only whitespace or comment? Skip it */ line = trim_whitespace( line ); if( line[0] == '\0' || line[0] == COMMENT_CHAR ) { free( line ); continue; } /* Search for the separator character */ sep = strchr( line, SEPARATOR_CHAR ); if( sep == NULL ) { /* No separator character? We don't understand */ xasprintf( err, "Error in %s, line %li,\n " "parse error: `%s'", wld->configfile, lineno, line ); free( line ); free( config ); return 1; } /* Split the line in two strings, and extract key and value */ *sep = '\0'; key = xstrdup( line ); value = xstrdup( sep + 1 ); /* Clean up key and value */ key = trim_whitespace( key ); value = trim_whitespace( value ); value = remove_enclosing_quotes( value ); /* We use err to test if an error occured. */ *err = NULL; /* Try and set the key */ switch( set_key_value( wld, key, value, &seterr ) ) { case SET_KEY_NF: xasprintf( err, "Error in %s, line %li,\n " "unknown key `%s'", wld->configfile, lineno, key ); break; case SET_KEY_PERM: xasprintf( err, "Error in %s, line %li,\n " "setting key `%s' not allowed.", wld->configfile, lineno, key ); break; case SET_KEY_BAD: xasprintf( err, "Error in %s, line %li,\n " "key `%s': %s", wld->configfile, lineno, key, seterr ); free( seterr ); break; case SET_KEY_OK: break; default: xasprintf( err, "Huh? set_key_value( %s ) returned " "weird value...", key ); break; } /* Clean up */ free( key ); free( value ); free( line ); /* If there was an error, abort */ if( *err ) { free( config ); return 1; } } /* We're done! */ free( config ); return 0; } /* Read file (with max length CONFIG_MAXLENGTH KiB) in a block of memory, * and put the address of this block in contents. The block should be freed. * On success, return 0. * On error, no block is places in contents, an error is placed in err, and * 1 is returned. */ static int read_configfile( char *file, char **contentp, char **err ) { int fd, r; off_t size; char *contents; if( file == NULL ) { *err = xstrdup( "No configuration file defined." ); return 1; } /* Open the file, and bail out on error */ fd = open( file, O_RDONLY ); if( fd == -1 ) { xasprintf( err, "Error opening `%s':\n %s", file, strerror( errno ) ); return 1; } /* Check the length of the file */ size = lseek( fd, 0, SEEK_END ); if( size > CONFIG_MAXLENGTH * 1024 ) { xasprintf( err, "Error: `%s' is larger than %lu kilobytes.", file, CONFIG_MAXLENGTH ); close( fd ); return 1; } /* Reset the offset to the start of file */ lseek( fd, 0, SEEK_SET ); /* Allocate memory, and read file contents */ contents = xmalloc( size + 1 ); r = read( fd, contents, size ); if( r == -1 ) { xasprintf( err, "Error reading from `%s':\n %s", file, strerror( errno ) ); free( contents ); close( fd ); return 1; } /* Nul-terminate the string */ contents[size] = '\0'; /* Clean up, and done */ close( fd ); *contentp = contents; return 0; } extern int world_get_key_list( World *wld, char ***list ) { int i, num = 0; *list = NULL; for( i = 0; key_db[i].keyname; i++ ) { /* Hidden? Skip. */ if( key_db[i].hidden ) continue; *list = xrealloc( *list, ++num * sizeof( char * ) ); ( *list )[num - 1] = key_db[i].keyname; } return num; } extern int world_set_key( World *wld, char *key, char *value, char **err ) { return set_key_internal( wld, key, value, ASRC_USER, err ); } extern int world_get_key( World *wld, char *key, char **value ) { return get_key_internal( wld, key, value, ASRC_USER ); } extern int world_desc_key( World *wld, char *key, char **shortdesc, char **longdesc ) { int i; for( i = 0; key_db[i].keyname; i++ ) if( !strcmp_under( key, key_db[i].keyname ) ) { if( key_db[i].hidden ) return GET_KEY_NF; *shortdesc = key_db[i].shortdesc; *longdesc = key_db[i].longdesc; return GET_KEY_OK; } return GET_KEY_NF; } /* This function is like world_set_key(), but internal to this file. * It's for setting from file, not from the user. */ static int set_key_value( World *wld, char *key, char *value, char **err ) { return set_key_internal( wld, key, value, ASRC_FILE, err ); } /* Attempts to set a single key to a new value. * For the interface, see world_set_key(). * Additional arguments: * src: One of ASRC_FILE, ASRC_USER. Indicates if the set request * is coming from the config file or from the user. */ static int set_key_internal( World *wld, char *key, char *value, int src, char **err ) { int i; for( i = 0; key_db[i].keyname; i++ ) if( !strcmp_under( key, key_db[i].keyname ) ) return (*key_db[i].setter)( wld, key, value, src, err ); return SET_KEY_NF; } /* Queries a single key. For the interface, see world_get_key(). * Additional arguments: * src: One of ASRC_FILE, ASRC_USER. Indicates if the request * is coming from the config file or from the user. */ static int get_key_internal( World *wld, char *key, char **value, int src ) { int i; for( i = 0; key_db[i].keyname; i++ ) if( !strcmp_under( key, key_db[i].keyname ) ) return (*key_db[i].getter)( wld, key, value, src ); return GET_KEY_NF; } extern int create_configdirs( char **err ) { char *path, *errstr = NULL; *err = NULL; xasprintf( &path, "%s/%s", get_homedir(), CONFIGDIR ); if( attempt_createdir( path, &errstr ) ) goto create_failed; free( path ); xasprintf( &path, "%s/%s/%s", get_homedir(), CONFIGDIR, WORLDSDIR ); if( attempt_createdir( path, &errstr ) ) goto create_failed; free( path ); xasprintf( &path, "%s/%s/%s", get_homedir(), CONFIGDIR, LOGSDIR ); if( attempt_createdir( path, &errstr ) ) goto create_failed; free( path ); xasprintf( &path, "%s/%s/%s", get_homedir(), CONFIGDIR, LOCKSDIR ); if( attempt_createdir( path, &errstr ) ) goto create_failed; free( path ); return 0; create_failed: xasprintf( err, "Error creating `%s': %s", path, errstr ); free( path ); free( errstr ); return 1; } extern int check_configdir_perms( char **warn, char **err ) { char *path; struct stat fileinfo; *warn = NULL; /* Construct the path */ xasprintf( &path, "%s/%s", get_homedir(), CONFIGDIR ); /* Get the information. */ if( stat( path, &fileinfo ) == -1 ) { xasprintf( err, "Could not stat `%s': %s", path, strerror( errno ) ); free( path ); return 1; } /* Are the permissions ok? */ if( ( fileinfo.st_mode & ( S_IRWXG | S_IRWXO ) ) == 0 ) { free( path ); return 0; } /* The permissions are not ok, construct a message. * OMG! It's the xasprintf from hell! */ xasprintf( warn, "\n" "----------------------------------------------------------" "--------------------\nWARNING! The mooproxy configuration " "directory has weak permissions:\n" "\n %s%s%s%s%s%s%s%s%s %s\n\n" "This means other users on your system may be able to read " "your mooproxy files.\nIf this is intentional, make sure the " "permissions on the files and directories\nit contains are " "sufficiently strict.\n-------------------------------------" "-----------------------------------------\n\n", (fileinfo.st_mode & S_IRUSR ) ? "r" : "-", (fileinfo.st_mode & S_IWUSR ) ? "w" : "-", (fileinfo.st_mode & S_IXUSR ) ? "x" : "-", (fileinfo.st_mode & S_IRGRP ) ? "R" : "-", (fileinfo.st_mode & S_IWGRP ) ? "W" : "-", (fileinfo.st_mode & S_IXGRP ) ? "X" : "-", (fileinfo.st_mode & S_IROTH ) ? "R" : "-", (fileinfo.st_mode & S_IWOTH ) ? "W" : "-", (fileinfo.st_mode & S_IXOTH ) ? "X" : "-", path ); free( path ); return 0; } mooproxy-1.0.0/resolve.c0000644000175000017500000001346111525336516015065 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include "resolve.h" #include "world.h" #include "misc.h" #include "network.h" #define RESOLVE_SUCCESS 'a' #define RESOLVE_ERROR 'b' static void slave_main( World *, int ); static void slave_msg_to_parent( char *, int ); extern void world_start_server_resolve( World *wld ) { int filedes[2]; pid_t pid; /* We'll only start resolving if we're not busy. */ if( wld->server_status != ST_DISCONNECTED && wld->server_status != ST_RECONNECTWAIT ) return; /* Attempt to create the comminucation pipe */ if( pipe( filedes ) < 0 ) { world_msg_client( wld, "Could not create pipe: %s", strerror( errno ) ); return; } /* Fork off the slave */ pid = fork(); if( pid == -1 ) { world_msg_client( wld, "Could not create resolver slave: %s", strerror( errno ) ); return; } /* We're the slave, go do our stuff */ if( pid == 0 ) { close( filedes[0] ); slave_main( wld, filedes[1] ); _exit( 0 ); } /* We're the parent. */ close( filedes[1] ); wld->server_resolver_fd = filedes[0]; wld->server_resolver_pid = pid; wld->server_status = ST_RESOLVING; } extern void world_cancel_server_resolve( World *wld ) { /* If we aren't resolving, ignore the request */ if( wld->server_status != ST_RESOLVING ) return; /* Just kill the slave and clean up. */ kill( wld->server_resolver_pid, SIGKILL ); waitpid( wld->server_resolver_pid, NULL, 0 ); close( wld->server_resolver_fd ); wld->server_resolver_pid = -1; wld->server_resolver_fd = -1; wld->server_status = ST_DISCONNECTED; } extern void world_handle_resolver_fd( World *wld ) { char *addresses = NULL; int len = 0, i = 1; /* Read from the pipe. We should be able to read everything quickly. * wld->server_resolver_fd is blocking, so when there's more data * than fitted in the kernel socket buffers, the read() will block, * but the slave's write() becomes unblocked, so the rest of the * data gets transferred. * If the resolver slave should write something to the socket but * not close it, we'll hang here indefinetely. * FIXME: make this more robust. */ while( i > 0 ) { addresses = xrealloc( addresses, len + 1024 ); i = read( wld->server_resolver_fd, addresses + len, 1024 ); if( i > 0 ) len += i; } /* Make it a NUL-terminated string */ addresses = xrealloc( addresses, len + 1 ); addresses[len++] = '\0'; /* The first character should say whether the lookup was successful * or not. */ switch( addresses[0] ) { case RESOLVE_SUCCESS: wld->server_addresslist = xstrdup( addresses + 2 ); wld->flags |= WLD_SERVERCONNECT; break; case RESOLVE_ERROR: world_msg_client( wld, "%s", addresses + 2 ); wld->flags |= WLD_RECONNECT; break; /* This shouldn't happen */ default: world_msg_client( wld, "Resolver slave went wacko." ); break; } /* Clean up, kill resolver slave. */ free( addresses ); close( wld->server_resolver_fd ); kill( wld->server_resolver_pid, SIGKILL ); waitpid( wld->server_resolver_pid, NULL, 0 ); wld->server_resolver_pid = -1; wld->server_resolver_fd = -1; /* We're still not connected, but not in the process of connecting * either. The wld->flags |= WLD_SERVERCONNECT will take care of * setting up the connection. */ wld->server_status = ST_DISCONNECTED; } /* The resolver slave code. Do the DNS lookup, format the data in a nice * string, and send that string back to the parent. */ static void slave_main( World* wld, int commfd ) { struct addrinfo hints, *ailist, *ai; char *msg, hostbuf[NI_MAXHOST + 1]; int ret, msglen, len; /* Specify the socket addresses we want. Any AF the system supports, * STREAM socket type, TCP protocol. */ memset( &hints, '\0', sizeof( hints ) ); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; /* Get the socket addresses */ ret = getaddrinfo( wld->server_host, NULL, &hints, &ailist ); if( ret != 0 ) { xasprintf( &msg, "%c\nResolving failed: %s", RESOLVE_ERROR, gai_strerror( ret ) ); slave_msg_to_parent( msg, commfd ); return; } msglen = 3; msg = xstrdup( " \n" ); msg[0] = RESOLVE_SUCCESS; /* Loop through the socket addresses, convert them to numeric * address representations, and append those to the msg string */ for( ai = ailist; ai != NULL; ai = ai->ai_next ) { if( getnameinfo( ai->ai_addr, ai->ai_addrlen, hostbuf, NI_MAXHOST, NULL, 0, NI_NUMERICHOST ) != 0 ) continue; len = strlen( hostbuf ); msg = xrealloc( msg, msglen + len + 1 ); strcat( msg, hostbuf ); strcat( msg, "\n" ); msglen += len + 1; } freeaddrinfo( ailist ); slave_msg_to_parent( msg, commfd ); } /* Write str over fd to the parent. */ static void slave_msg_to_parent( char *str, int fd ) { int i, len = strlen( str ) + 1; /* fd is (or should be) non-blocking. When there's more data to send * than fits in the kernel socket buffers, write() will block, but * the parent will become unblocked and will read from the FD. Then * our write becomes unblocked. */ while( len > 0 ) { i = write( fd, str, len ); if( i > 0 ) len -= i; else break; } free( str ); } mooproxy-1.0.0/README0000644000175000017500000003505111525336516014121 0ustar marcelmmarcelm Mooproxy - a buffering proxy for MOO connections Copyright (C) 2001-2011 Marcel L. Moreaux 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; version 2 dated June, 1991. 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. 1. About 2. Manual compiling and installing 3. Installing the Debian package 4. Creating a Debian package 5. Using 6. Authentication 7. MCP 8. /recall 9. Logfiles change from 0.1.1 to 0.1.2 10. Portability 11. Authors and thanks 1. About -------- Mooproxy is a layer between your MOO client and a MOO server, as shown below: MOO client <----> Mooproxy <----> MOO server The intention is that the proxy remains connected to the server, even if you disconnect your MOO client, for example, if you shutdown your computer, take your laptop to a different place, or have network problems. Mooproxy also provides centralised logging, which is useful if you use multiple MOO clients. Mooproxy is still under development; bug reports and other feedback is appreciated. If you really want to help, run it in valgrind (with --leak-check=full --show-reachable=yes), and report any crashes or memleaks you get (Please report the full valgrind output, and the version of mooproxy you're using). gdb backtraces of crashes are also helpful. 2. Manual compiling and installing ---------------------------------- First, you can edit the Makefile to change some options (like installation path and compiler flags), if you want to. Next, to compile mooproxy, simply run make in the source directory. You need a working C compiler, and the development libraries for libc. Mooproxy also uses some functionality that may not be present on all Unices; see Portability. You can install by running make install This will install the mooproxy binary (by default in /usr/local/bin), and the manpage (by default in /usr/local/share/man/man1). Alternatively, you can install by simply copying the mooproxy executable to a suitable place, such as /usr/local/bin or ~/bin. Mooproxy requires no other files (except configuration files) to run. 3. Installing the Debian package -------------------------------- Currently, an i386 Debian package is provided. You can install it with dpkg -i mooproxy_.deb 4. Creating a Debian package ---------------------------- You can create your own Debian package as well (for another architecture, for example). Follow the following steps: - Download mooproxy-debsrc-.tar.gz - Unpack it somewhere - cd into mooproxy- - Unpack mooproxy_.orig.tar.gz - Run zcat mooproxy_.diff.gz | patch -p0 - cd into mooproxy- - Run dpkg-buildpackage -us -uc -rfakeroot The Debian package (and some other files) will be generated in the top-level mooproxy- directory. 5. Using -------- Running mooproxy is simple, just invoke it like this: mooproxy -w where is the name of the world. Mooproxy will use the configuration file ~/.mooproxy/worlds/ . See the file ExampleConfig for an example configuration file. The ExampleConfig file contains all configurable options, and the settings in ExampleConfig are exactly the same as the builtin defaults in mooproxy. Mooproxy accepts commands to change a lot of its behaviour run-time. When connected to mooproxy, do /help for a brief list of commands. 6. Authentication ----------------- There are two situations in mooproxy that require authentication; - A MOO client connects to mooproxy - Mooproxy connects to a MOO server Mooproxy uses the same authentication string for both. In the configuration file for the world, you should put a MD5 hash of the authentication string. When the client connects to mooproxy, the client or the user supplies the authentication string to mooproxy. Mooproxy hashes the authentication string from the client, compares it to the stored MD5 hash, and if they match, the client is accepted. At this point, mooproxy stores the _literal_ authentication string received from the client in its memory. When mooproxy logs in to the server (for example, because autologin is enabled), mooproxy uses this literal authentication string to authenticate itself to the MOO server. The MD5 hash can be created with the following command: mooproxy --md5crypt An example scenario. Assume: The MOO server expects: connect Your MOO client generates: connect Gandalf Shire Now, run the following command: mooproxy --md5crypt When mooproxy asks for your authentication string (it will ask twice, to save you from typos), you should enter: connect Gandalf Shire Mooproxy will print a MD5 hash of the string, e.g.: $1$DWuIlrlu$vjiUiV7i23jNMCpSkDzDz. You put this string in your configuration file, like so: auth_hash = "$1$DWuIlrlu$vjiUiV7i23jNMCpSkDzDz." Now, when your client connects to mooproxy, it is prompted for the authentication string. If your client responds with: connect Gandalf Shire Mooproxy will accept the client, and store "connect Gandalf Shire" in its memory. Now, when mooproxy connects to the MOO server, and autologin is enabled, mooproxy sends the saved string, namely connect Gandalf Shire to the MOO server, logging you in, just like your MOO client would without mooproxy in the middle. To change the authentication string from within mooproxy, you must also provide the old literal authentication string, like this: /set auth_hash So, continuing the example scenario above, if you change your password to "Mirkwood", you must run mooproxy --md5crypt to generate a MD5 hash of the new authentication string: $ mooproxy --md5crypt Authentication string: connect Gandalf Mirkwood MD5 hash: $1$PzGmXA.I$qmZomnNK2gBW6tR8iTJLA/ You can then set the new hash like this: /set auth_hash $1$PzGmXA.I$qmZomnNK2gBW6tR8iTJLA/ "connect Gandalf Shire" The MD5 hash should _not_ be enclosed in quotes. It is recommended to enclose the old literal in quotes, to remove ambiguity. After changing auth_hash from within mooproxy, mooproxy no longer has a literal authentication string. Therefore, autologin will not work until you re-authenticate to mooproxy (for example, by disconnecting your client, and connecting again). 7. MCP ------ Moo servers and clients may use the MOO Client Protocol (MCP) to exchange some information out-of-band. Common uses include periodically showing a tip or something else in a status bar, or maintaining a userlist on the client. For more information, see http://www.moo.mud.org/mcp/index.html . MCP is a stateful protocol; it has to be initialised and maintained properly. When a MOO client connects to the MOO server, it expects to setup a MCP session. Consider the following scenario: - Mooproxy is started - The client connects to mooproxy - Mooproxy connects to the MOO server - The MOO server sends MCP handshake. The client replies, and a MCP session is set up. Mooprox relays all MCP messages. - The client disconnects - The client connects again Now, from the perspective of the MOO server, a MCP session still exists, but the client cannot continue this old session (due to a lack of information on the client). So the MCP session needs to be torn down and rebuilt. Because Standard MCP doesn't provide a mechanism to re-initialise the MCP session, a MCP package has been created for this purpose. The package is called dns-nl-icecrew-mcpreset. More information can be found in mcpreset.html . Mooproxy uses the server MCP reset. When a client connects to mooproxy, and mooproxy is already connected to a MOO server, mooproxy sends a MCP reset command to the MOO server: #$#dns-nl-icecrew-mcpreset-reset When the MOO server receives this message, it should terminate the current MCP session for the client, and start a new one by sending the MCP handshake. Mooproxy then relays this handshake to the newly connected client, which will respond, and a new MCP session will be set up. If working MCP is desired, mooproxy can only be used with MOO servers that support dns-nl-icecrew-mcpreset-reset. 8. /recall ---------- In mooproxy 0.1.3, the recall command has received a significant update. If you invoke the recall command with just a number, it will recall that number of lines in their full glory, just like before: /recall 50 But now recall supports the following syntax as well: /recall [from ] [to ] [search ] This will recall any lines matching in the period from to . A timespec is one or more of the following: now Sets the time to the current date and time. today Sets the time to 0:00 today. yesterday Sets the time to 0:00 yesterday. last/next monday/tuesday/... Next sets the time to 0:00 of the first from today. Last sets it to 0:00 of the last before today. [YY/]MM/DD Set the time to 0:00 of the given day. If no year is specified, use MM/DD within the current year. HH:MM[:SS] Set the time to HH:MM within the current day. If seconds are not specified, set them to 00. -/+ seconds/minutes/hours/days Change the time to be earlier or later. -/+ lines Search in lines before/after . Notes: * Lines are recalled with their colours stripped. * -/+ lines is special. It can only be used together with 'to', and when used, it must be the only timespec. * If both from and to are used, from must appear before to. * If search is used, it must be the last keyword. * The search keyword is case insensitive. It also treats as a very primitive regular expression, which _only_ supports . (matches any character) and .* (matches zero or more any characters). * Weekdays support the usual abbreviations (mon, tue, wed, ...). * The units in relative timespecs can be abbreviated too (down to individual characters). +2 minutes, +2 mins, +2 min, +2 m are all equivalent. * The spacing in -/+ is optional. + 2 min, +2 min, +2min are all equivalent. Examples. Assume the current date and time are Wed Aug 22, 20:42:11. /recall from 10:00 to 11:00 Recall all lines between 10:00:00 and 11:00:00 today. /recall from yesterday 10:00 to 11:00 Recall all lines from 10:00 to 11:00 yesterday. /recall from -30 mins search yes.*play Search the last half hour for any lines containing "yes" before "play". /recall from 10:00 to +50 lines Recall 50 lines after 10:00:00 today. /recall to -80 lines Recall the last 80 lines. /recall from last monday to today. Recall everything from Mon 20/08 00:00 to Wed 22/08 00:00. /recall from last saturday to next monday Recall everything from Sat 18/08 00:00 to Mon 20/08 00:00. /recall from 04/22 next wed 11:35 to +1 hour Recall from 11:35 to 12:35 the wednesday after 22/04 (a sunday) When you invoke recall, it will first report what it is going to recall, so you can check if recall will actually inspect the lines you intended it to inspect: % Recalling from Wed 2007/04/25 11:35:00 to Wed 2007/04/25 12:35:00. Then it will produce the matching lines, with all colours stripped. Finally, it will report how many lines it considered: % Recall end (X / Y / Z). Here, X is the number of lines mooproxy has in its history. Y is the number of lines that matched the time criteria (i.e. from and to). Finally, Z is the number of lines actually recalled (i.e. that matched the time criteria and search criteria). 9. Logfiles change from 0.1.1 to 0.1.2 -------------------------------------- In mooproxy 0.1.2, the logging was changed to log into a nested hierarchy of directories, instead of all files in a single directory. Mooproxy versions older than 0.1.2 log like this: logs |-- world one - 2007-01-01.log |-- world one - 2007-02-01.log |-- world two - 2007-02-01.log |-- world two - 2007-03-01.log |-- world two - 2007-03-02.log `-- world two - 2007-03-03.log Mooproxy 0.1.2 and later log like this: logs |-- world one | |-- 2007-01 | | `-- world one - 2007-01-01.log | `-- 2007-02 | `-- world one - 2007-02-01.log `-- world two |-- 2007-02 | `-- world two - 2007-02-01.log `-- 2007-03 |-- world two - 2007-03-01.log |-- world two - 2007-03-02.log `-- world two - 2007-03-03.log To move any existing logs from the old layout to the new layout, use the update-logs.sh that's provided with mooproxy 0.1.2 (in the Debian package, the shell script resides in /usr/share/doc/mooproxy). Please make a backup of your current logfiles before running this script. 10. Portability --------------- Mooproxy is designed to run on modern Unices, most notably GNU/Linux and the open source BSD variants. Mooproxy is written in ANSI C, using mostly POSIX-defined systemcalls. This section documents the additional requirements for mooproxy. As of this writing, mooproxy is tested on Debian Unstable with Linux 2.6. Portability pitfalls: * Mooproxy uses vasprintf(). This is a GNU extension that is also implemented in the BSDs. * Mooproxy uses getopt_long(). This is a GNU extension that is also implemented in the BSDs. * Mooproxy uses getaddrinfo() and getnameinfo() for address family independent networking. These functions are defined in POSIX, but they're fairly recent additions, which could lead to problems on older OSes. * On systems with IPv6, mooproxy works best if the (pretty recent) IPV6_V6ONLY socket option is available, but this is not required. * Mooproxy uses crypt(), and expects it to support MD5 hashing. crypt() is defined in POSIX, but MD5 hashing is a GNU extension that is also implemented in the BSDs. * Mooproxy uses the S_ISLNK() macro, which is mandated in POSIX POSIX.1-2001 but not in earlier versions. 11. Authors and thanks ---------------------- The author of mooproxy is: Marcel L. Moreaux Thanks (for testing, reporting bugs, and giving suggestions) to: Paul van Tilburg Wouter Lueks Admar Schoonen Bram Senders Christian Luijten (end of document) mooproxy-1.0.0/resolve.h0000644000175000017500000000257311525336516015074 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__RESOLVE #define MOOPROXY__HEADER__RESOLVE #include "world.h" /* Start the process of resolving wld->server_host. Create a comminucation pipe, * fork the resolver slave, and set wld->server_status to ST_RESOLVING. */ extern void world_start_server_resolve( World *wld ); /* Abort the current resolving attempt. The resolver slave is killed. */ extern void world_cancel_server_resolve( World *wld ); /* When the resolver slave is done, it'll write its data to * wld->server_resolver_fd. Activity on this FD should be handled by this * function. It will read data from the FD, construct the list of addresses * or report an error, and terminate the resolver slave. */ extern void world_handle_resolver_fd( World *wld ); #endif /* ifndef MOOPROXY__HEADER__RESOLVE */ mooproxy-1.0.0/world.h0000644000175000017500000002136411525336516014543 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__WORLD #define MOOPROXY__HEADER__WORLD #include #include #include "global.h" #include "line.h" /* World flags */ #define WLD_ACTIVATED 0x00000001 #define WLD_NOTCONNECTED 0x00000002 #define WLD_CLIENTQUIT 0x00000004 #define WLD_SERVERQUIT 0x00000008 #define WLD_RECONNECT 0x00000010 #define WLD_SERVERRESOLVE 0x00000020 #define WLD_SERVERCONNECT 0x00000040 #define WLD_LOGLINKUPDATE 0x00000080 #define WLD_REBINDPORT 0x00000100 #define WLD_SHUTDOWN 0x00000200 /* Server/client statuses */ #define ST_DISCONNECTED 0x01 #define ST_RESOLVING 0x02 #define ST_CONNECTING 0x03 #define ST_CONNECTED 0x04 #define ST_RECONNECTWAIT 0x05 /* Authentication connection statuses */ #define AUTH_ST_WAITNET 0x01 #define AUTH_ST_VERIFY 0x02 #define AUTH_ST_CORRECT 0x03 /* BindResult struct. Contains the result of the attempt to bind on a port. */ typedef struct BindResult BindResult; struct BindResult { /* Contains error msg if a fatal init error occurred. Otherwise NULL. */ char *fatal; /* Number of address families we tried to bind for */ int af_count; /* Number of address families we successfully bound for */ int af_success_count; /* Array of booleans indicating each AF's success or failure */ int *af_success; /* Human-readable message for each AF */ char **af_msg; /* Array of af_success_count + 1 filedescriptors. The last one is -1. */ int *listen_fds; /* Human-readable conclusion */ char *conclusion; }; /* The World struct. Contains all configuration and state information for a * world. */ typedef struct World World; struct World { /* Essentials */ char *name; char *configfile; unsigned long flags; char *lockfile; int lockfile_fd; /* Destination */ char *dest_host; long dest_port; /* Listening connection */ long listenport; long requestedlistenport; int *listen_fds; BindResult *bindresult; /* Authentication related stuff */ char *auth_hash; char *auth_literal; int auth_tokenbucket; int auth_connections; int auth_status[NET_MAXAUTHCONN]; int auth_ispriv[NET_MAXAUTHCONN]; int auth_fd[NET_MAXAUTHCONN]; int auth_read[NET_MAXAUTHCONN]; char *auth_buf[NET_MAXAUTHCONN]; char *auth_address[NET_MAXAUTHCONN]; Linequeue *auth_privaddrs; /* Data related to the server connection */ int server_status; int server_fd; char *server_port; char *server_host; char *server_address; pid_t server_resolver_pid; int server_resolver_fd; char *server_addresslist; int server_connecting_fd; int reconnect_enabled; int reconnect_delay; time_t reconnect_at; Linequeue *server_rxqueue; Linequeue *server_toqueue; Linequeue *server_txqueue; char *server_rxbuffer; long server_rxfull; char *server_txbuffer; long server_txfull; /* Data related to the client connection */ int client_status; int client_fd; char *client_address; char *client_prev_address; time_t client_connected_since; time_t client_last_connected; long client_login_failures; char *client_last_failaddr; time_t client_last_failtime; time_t client_last_notconnmsg; Linequeue *client_rxqueue; Linequeue *client_toqueue; Linequeue *client_txqueue; char *client_rxbuffer; long client_rxfull; char *client_txbuffer; long client_txfull; /* Miscellaneous */ Linequeue *buffered_lines; Linequeue *inactive_lines; Linequeue *history_lines; long dropped_inactive_lines; long dropped_buffered_lines; time_t easteregg_last; /* Timer stuff */ int timer_prev_sec; int timer_prev_min; int timer_prev_hour; long timer_prev_day; int timer_prev_mon; int timer_prev_year; /* Logging */ Linequeue *log_queue; Linequeue *log_current; long dropped_loggable_lines; long log_currentday; time_t log_currenttime; char *log_currenttimestr; int log_fd; char *log_buffer; long log_bfull; char *log_lasterror; time_t log_lasterrtime; /* MCP stuff */ int mcp_negotiated; char *mcp_key; char *mcp_initmsg; /* Ansi Client Emulation (ACE) */ int ace_enabled; int ace_cols; int ace_rows; char *ace_prestr; char *ace_poststr; /* Options */ int autologin; int autoreconnect; char *commandstring; int strict_commands; char *infostring; char *infostring_parsed; char *newinfostring; char *newinfostring_parsed; long context_lines; long buffer_size; long logbuffer_size; int logging; int log_timestamps; int easteregg_version; }; /* Create a world struct, and initialise it with default values. The world's * name will be set to wldname */ extern World *world_create( char *wldname ); /* Free all memory allocated for this world, close all FD's, and then free * the world itself. */ extern void world_destroy( World *wld ); /* Get a list of all World objects. The number of worlds will be placed in * count, and an array of pointers to Worlds in wldlist. * Neither the array nor the World objects should be freed. */ extern void world_get_list( int *count, World ***wldlist ); /* Initialize a BindResult object to empty/zeroes. */ extern void world_bindresult_init( BindResult *bindresult ); /* Free all allocated resources in a BindResult object. */ extern void world_bindresult_free( BindResult *bindresult ); /* Construct the path of the configuration file, based on wld->name, and put * it in wld->configfile. */ extern void world_configfile_from_name( World *wld ); /* Queue a line for transmission to the client, and prefix the line with * the infostring, to indicate it's a message from mooproxy. * The fmt and ... arguments are like printf(). Return the line object, * so the caller may frobble with line->flags or some such. */ extern Line *world_msg_client( World *wld, const char *fmt, ... ); /* Like world_msg_client(), but uses newinfostring as a prefix. */ extern Line *world_newmsg_client( World *wld, const char *fmt, ... ); /* Schedule the next reconnect attempt. */ extern void world_schedule_reconnect( World *wld ); /* Attempt to reconnect to the server. */ extern void world_do_reconnect( World *wld ); /* Decrease the delay between reconnect attempts. */ extern void world_decrease_reconnect_delay( World *wld ); /* Measure the length of dynamic queues such as buffered and history, * and remove the oldest lines until they are small enough. * This puts a limit on the amount of memory these queues will occupy. */ extern void world_trim_dynamic_queues( World *wld ); /* Replicate the configured number of history lines to provide context for * the newly connected client, and then pass all buffered lines. */ extern void world_recall_and_pass( World *wld ); /* (Auto-)login to the server. If autologin is enabled, log in. * If autologin is disabled, only log in if override is non-zero. */ extern void world_login_server( World *wld, int override ); /* Appends the lines in the inactive queue to the history queue, effectively * remove the 'possibly new' status from these lines. */ extern void world_inactive_to_history( World *wld ); /* Recall (at most) count lines from wld->history_lines. * Return a newly created Linequeue object with copies of the recalled lines. * The lines have their flags set to LINE_RECALLED, and their strings * have ASCII BELLs stripped out. */ extern Linequeue *world_recall_history( World *wld, long count ); /* Attempt to bind to wld->requestedlistenport. If successful, close old * listen fds and install new ones. If unsuccessful, retain old fds. */ extern void world_rebind_port( World *wld ); /* Enable Ansi Client Emulation for this world. * Sends ansi sequences to initialize the screen, sets wld->ace_enabled to * true, and initalizes the other wld->ace_* variables. * Returns true if initialization was successful, false otherwise. */ extern int world_enable_ace( World *wld ); /* Disable Ansi Client Emulation for this world. * Sends ansi sequences to reset the screen and cleans up wld->ace_* */ extern void world_disable_ace( World *wld ); /* Check for, and respond to, easter eggs. */ extern void world_easteregg_server( World *wld, Line *line ); /* Signal shutdown if there are no unlogged lines (or force = 1). * Returns 0 if shutdown is refused, 1 otherwise. * This function messages the client. * fromsig should be one when called from SIGTERM/QUIT, 0 otherwise. */ extern int world_start_shutdown( World *wld, int force, int fromsig ); #endif /* ifndef MOOPROXY__HEADER__WORLD */ mooproxy-1.0.0/network.h0000644000175000017500000000467011525336516015106 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__NETWORK #define MOOPROXY__HEADER__NETWORK #include "world.h" /* Wait for data from the network or timeout. * Any data received from the network is parsed into lines and put in the * respective input queues, so they can be processed further. */ extern void wait_for_network( World *wld ); /* Open wld->listenport on the local machine, and start listening on it. * The returned BindResult does not need to be free'd. */ extern void world_bind_port( World *wld, long port ); /* Take the first address in wld->server_addresslist, start a non-blocking * connect to that address, and sets wld->server_status to ST_CONNECTING. */ extern void world_start_server_connect( World *wld ); /* If mooproxy is in the process of connecting to the server, abort all that * and clean up. Note that this does not abort _resolving_ attempts. */ extern void world_cancel_server_connect( World *wld ); /* Disconnect mooproxy from the server. */ extern void world_disconnect_server( World *wld ); /* Disconnect the client from mooproxy. */ extern void world_disconnect_client( World *wld ); /* Try to flush the queued lines into the send buffer, and the send buffer * to the client. If this fails, leave the contents of the buffer in the * queued lines, mark the fd as write-blocked, and signal an FD change. */ extern void world_flush_client_txbuf( World *wld ); /* Try to flush the queued lines into the send buffer, and the send buffer * to the server. If this fails, leave the contents of the buffer in the * queued lines, mark the fd as write-blocked, and signal an FD change. * If the socket is closed, discard contents, and announce * disconnectedness. */ extern void world_flush_server_txbuf( World *wld ); /* Add some tokens to the auth token bucket. */ extern void world_auth_add_bucket( World *wld ); #endif /* ifndef MOOPROXY__HEADER__NETWORK */ mooproxy-1.0.0/misc.h0000644000175000017500000001566211525336516014353 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__MISC #define MOOPROXY__HEADER__MISC #include #include #include #include "line.h" /* Maximum length of the string to be returned by time_string() */ #define TIMESTR_MAXLEN 256 /* Maximum number of time_string() results that may be used simultaneously. * Additional calls to time_string() will overwrite older returned strings. */ #define TIMESTR_MAXSIMULT 4 /* Wrappers for malloc(), realloc(), strdup(), asprintf(), and vasprintf() * These wrappers check for and abort on memory allocation failure. * Otherwise, they behave identical to the original functions. */ extern void *xmalloc( size_t size ); extern void *xrealloc( void *ptr, size_t size ); extern char *xstrdup( const char *s ); extern char *xstrndup( const char *s, size_t n ); extern int xasprintf( char **strp, const char *fmt, ... ); extern int xvasprintf( char **strp, const char *fmt, va_list argp ); /* Set the current time which can later be queried by current_time(). * The time is used e.g. to timestamp newly created lines. */ extern void set_current_time( time_t t ); /* Get the current time, as set by set_current_time(). */ extern time_t current_time( void ); /* Set the current day which can later be queried by current_day(). * The time is used to timestamp lines for logging. */ extern void set_current_day( long day ); /* Get the current day, as set by set_current_day(). */ extern long current_day( void ); /* Returns a string containing the users homedir. * The string should not be freed or manipulated. */ extern char *get_homedir( void ); /* Extract the first line from str. * Returns a pointer to a copy of the first line of str, excluding the * newline character. The copy should be free()d. * If there are no lines left in str, the function returns NULL. * str is modified to point to the next line. */ extern char *read_one_line( char **str ); /* Extract the first word from str. * Returns a pointer to the first word of str, excluding any whitespace * characters. The pointer points to the word inside the (mangled) str, * it should not be free()d separately. * If there are no words left in str, the function returns NULL. * str is modified to point to the first character after the first whitespace * character after the first word. */ extern char *get_one_word( char **str ); /* Extract the first word from str. * Returns the first word in freshly allocated memory, which should be free()d. * If there are no words left in str, the function returns NULL. * str is not modified. */ extern char *peek_one_word( char *str ); /* Strips all whitespace from the start and end of line. * Returns line. Modifies line in place. */ extern char *trim_whitespace( char *line ); /* If str starts and ends with ", or starts and ends with ', modify str * to remove leading and trailing quote. Returns str. */ extern char *remove_enclosing_quotes( char *str ); /* Determines if str says true or false (case insensitive). * If the string is "true", "yes", "on", or "1", return 1. * If the string is "false", "no", "off", or "0", return 0. * If the string is not recognized, return -1. */ extern int true_or_false( const char *str ); /* Convert the time in t to a formatted string in local time. * For the format, see strftime (3). * Returns a pointer to a statically allocated buffer. * The buffer should not be free()d, and will be overwritten by subsequent * later calls to time_string(). * See the TIMESTR_ defines earlier in this file. */ extern char *time_string( time_t t, const char *fmt ); /* Like time_string(), but with a specific "full" time format. * Shares its buffer with time_string(). */ extern char *time_fullstr( time_t t ); /* Process buffer into lines. Start scanning at offset, scan only read bytes. * The salvaged lines are appended to q. Return the new offset. */ extern int buffer_to_lines( char *buffer, int offset, int read, Linequeue *q ); /* Process the given buffer/queue, and write it to the given fd. * Arguments: * fd: FD to write to. * buffer: Buffer to use for writing to the FD. * bffl: Indicates how full the buffer is. Will be updated. * queue: Queue of lines to be written. * tohist: Queue to append written lines to. * If queue is NULL, written lines are discarded. * network_nl: If true: appends network newlines to written lines. * If false: appends UNIX newlines to written lines. * prestr: Prepended to each line, if not NULL. * poststr: Appended to every line, if not NULL. * errnum: Modified to contain error code on error. * Return value: * 0 on success (everything in buffer and queue was written without error) * 1 on congestion (the FD could take no more, not everything was written) * 2 on error (errnum is set) */ extern int flush_buffer( int fd, char *buffer, long *bffl, Linequeue *queue, Linequeue *tohist, int network_nl, char *prestr, char *poststr, int *errnum ); /* Return a copy of str (which must be freed manually) in which all occurrences * of color tags (like %R) are replaced by their corresponding ANSI sequence * (like ^[[1,31m). */ extern char *parse_ansi_tags( char *str ); /* Attempt to create dirname (but not parent dirs). * On success, return 0. * On failure, return non-zero, and put error in err (err should be freed). * Err will contain just a brief error message (often strerror output), * more formatting should be done by the caller. */ extern int attempt_createdir( char *dirname, char **err ); /* Copy a character string from src to dest, like strcpy(), but skip any * ANSI sequences (such as color, bold, etc) and other unprintable characters * (control characters, ASCII BELL, etc). * dest must point to a large enough buffer. * Returns the length of the new string (excluding terminating \0). */ extern long strcpy_noansi( char *dest, char *src ); /* Copy a character string from src to dest, like strcpy(), but skip any * ASCII BELL (0x07) characters. dest must point to a large enough buffer. * Returns the length of the new string (excluding terminating \0). */ extern long strcpy_nobell( char *dest, char *src ); /* Like strcmp(), but ignores underscores. Strings which can be transformed * to eachother solely by the insertion and/or deletion of underscores are * considered to be equal. */ extern int strcmp_under( char *s, char *t ); #endif /* ifndef MOOPROXY__HEADER__MISC */ mooproxy-1.0.0/daemon.c0000644000175000017500000001303011525336516014641 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include "daemon.h" #include "misc.h" #include "panic.h" /* Can hold a 128 bit number in decimal. Should be enough for pids. */ #define MAX_PIDDIGITS 42 static time_t program_start_time = 0; static char *pid_from_pidfile( int fd ); extern void sighandler_sigterm( int sig ); extern void set_up_signal_handlers( void ) { /* This signal might be sent if a connection breaks down. * We handle that with select() and read(). */ signal( SIGPIPE, SIG_IGN ); /* This signal might be sent if someone sends us OOB TCP messages. * We're not interested. */ signal( SIGURG, SIG_IGN ); /* This signal might be sent if the controlling terminal exits. * We want to continue running in that case. */ signal( SIGHUP, SIG_IGN ); /* We're not interested in user signals. */ signal( SIGUSR1, SIG_IGN ); signal( SIGUSR2, SIG_IGN ); /* 'Bad' signals, indicating a mooproxy bug. Panic. */ signal( SIGSEGV, &sighandler_panic ); signal( SIGILL, &sighandler_panic ); signal( SIGFPE, &sighandler_panic ); signal( SIGBUS, &sighandler_panic ); /* Handle SIGTERM (shutdown) and SIGQUIT (shutdown -f). */ signal( SIGTERM, &sighandler_sigterm ); signal( SIGQUIT, &sighandler_sigterm ); } extern void uptime_started_now( void ) { struct timeval tv; program_start_time = time( NULL ); gettimeofday( &tv, NULL ); srand( tv.tv_usec ); } extern time_t uptime_started_at( void ) { return program_start_time; } extern pid_t daemonize( char **err ) { pid_t pid; int devnull; /* We need /dev/null, so we can redirect stdio to it. * Open it now, so we can complain before the child is created. */ devnull = open( "/dev/null", O_RDWR ); if( devnull == -1 ) { xasprintf( err, "Opening /dev/null failed: %s", strerror( errno ) ); return -1; } /* Fork the child... */ pid = fork(); if( pid == -1 ) { xasprintf( err, "Fork failed: %s", strerror( errno ) ); return -1; } /* If we're the parent, we're done now. */ if( pid > 0 ) return pid; /* Become a session leader. */ setsid(); /* Don't tie up the path we're launched from. */ chdir( "/" ); /* Redirect stdio to /dev/null. */ dup2( devnull, 0 ); dup2( devnull, 1 ); dup2( devnull, 2 ); close( devnull ); return 0; } extern void launch_parent_exit( int exitval ) { exit( exitval ); } extern int world_acquire_lock_file( World *wld, char **err ) { char *lockfile, *pidstr; int fd, ret; /* Lockfile = ~/CONFIGDIR/LOCKSDIR/worldname */ xasprintf( &lockfile, "%s/%s/%s/%s", get_homedir(), CONFIGDIR, LOCKSDIR, wld->name ); /* Open lockfile. Should be non-blocking; read and write actions * are best-effort. */ fd = open( lockfile, O_RDWR | O_CREAT | O_NONBLOCK, S_IRUSR | S_IWUSR ); if( fd < 0 ) { xasprintf( err, "Error opening lockfile %s: %s", lockfile, strerror( errno ) ); free( lockfile ); return 1; } /* Acquire a lock on the file. */ ret = flock( fd, LOCK_EX | LOCK_NB ); /* Some other process already has a lock. We assume it's another * instance of mooproxy. */ if( ret < 0 && errno == EWOULDBLOCK ) { pidstr = pid_from_pidfile( fd ); xasprintf( err, "Mooproxy instance for world %s already " "running (PID %s).", wld->name, pidstr ); free( lockfile ); free( pidstr ); close( fd ); return 1; } /* Some other error. */ if( ret < 0 ) { xasprintf( err, "Error locking lockfile %s: %s", lockfile, strerror( errno ) ); free( lockfile ); close( fd ); return 1; } /* Everything appears OK. Store info, return. */ wld->lockfile = lockfile; wld->lockfile_fd = fd; return 0; } static char *pid_from_pidfile( int fd ) { char *pidstr; int len, i; pidstr = xmalloc( MAX_PIDDIGITS + 1 ); len = read( fd, pidstr, MAX_PIDDIGITS + 1 ); /* If len < 0, there was an error. If len == 0, the file was empty. */ if( len < 1 || len > MAX_PIDDIGITS ) { free( pidstr ); return xstrdup( "unknown" ); } /* NUL-terminate the string. */ pidstr[len] = '\0'; /* Strip any trailing \n */ if( pidstr[len - 1] == '\n' ) pidstr[--len] = '\0'; /* Any non-digits in the string? Bail out. */ for( i = 0; i < len; i++ ) if( !isdigit( pidstr[i] ) ) { free( pidstr ); return xstrdup( "unknown" ); } return pidstr; } extern void world_write_pid_to_file( World *wld, pid_t pid ) { char *pidstr; xasprintf( &pidstr, "%li\n", (long) pid ); ftruncate( wld->lockfile_fd, 0 ); write( wld->lockfile_fd, pidstr, strlen( pidstr ) ); free( pidstr ); } extern void world_remove_lockfile( World *wld ) { close( wld->lockfile_fd ); wld->lockfile_fd = -1; unlink( wld->lockfile ); free( wld->lockfile ); wld->lockfile = NULL; } extern void sighandler_sigterm( int signum ) { World **wldlist; int wldcount; world_get_list( &wldcount, &wldlist ); world_start_shutdown( wldlist[0], signum == SIGQUIT, 1 ); } mooproxy-1.0.0/panic.h0000644000175000017500000000241611525336516014503 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__PANIC #define MOOPROXY__HEADER__PANIC #define PANIC_SIGNAL 1 #define PANIC_MALLOC 2 #define PANIC_REALLOC 3 #define PANIC_STRDUP 4 #define PANIC_STRNDUP 5 #define PANIC_VASPRINTF 6 #define PANIC_SELECT 7 #define PANIC_ACCEPT 8 /* Signal handler. Basically calls panic(). */ extern void sighandler_panic( int sig ); /* Panic. Try to write a helpful message to stderr, crash-file, and any * connected client. After that, terminate. * Reason holds the reason, (u)extra holds extra information specific to * the panic cause specified in reason. */ extern void panic( int reason, long extra, unsigned long uextra ); #endif /* ifndef MOOPROXY__HEADER__PANIC */ mooproxy-1.0.0/mcp.c0000644000175000017500000002063211525336516014163 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ /* * For some more information about MCP, see the README. */ #include #include #include #include "mcp.h" #include "misc.h" #include "line.h" static int mcp_is_init( Line *line ); static int mcp_is_negotiate_can( Line *line ); static char *extract_mcpkey( Line *line ); static char *skip_mcp_value( char *str ); extern int world_is_mcp( char *line ) { return line[0] == '#' && line[1] == '$' && line[2] == '#'; } extern void world_do_mcp_client( World *wld, Line *line ) { char *mcpkey = NULL, *tmp; Line *msg; /* First of all, pass the line on to the server. */ line->flags = LINE_MCP; linequeue_append( wld->server_toqueue, line ); /* If we're already negotiated, we're done. */ if( wld->mcp_negotiated ) return; /* Check if it's the "mcp" message. */ if( mcp_is_init( line ) ) { mcpkey = extract_mcpkey( line ); /* If the returned key is NULL, don't kill any existing key. */ if( mcpkey != NULL ) { free( wld->mcp_key ); wld->mcp_key = mcpkey; } } /* Check if it's the mcp-negotiate-can message. */ if( mcp_is_negotiate_can( line ) && wld->mcp_key != NULL ) { wld->mcp_negotiated = 1; /* Insert a client->server and server->client * mcp-negotiate-can message. This is necessary to get the * server and the client listening for mcpreset commands. */ xasprintf( &tmp, "#$#mcp-negotiate-can %s package: " "dns-nl-icecrew-mcpreset min-version: 1.0 " "max-version: 1.0", wld->mcp_key ); msg = line_create( tmp, -1 ); msg->flags = LINE_MCP; linequeue_append( wld->server_toqueue, msg ); linequeue_append( wld->client_toqueue, line_dup( msg ) ); } } extern void world_do_mcp_server( World *wld, Line *line ) { /* Flag the line as MCP, and pass it on. */ line->flags = LINE_MCP; linequeue_append( wld->client_toqueue, line ); /* Now, if we don't have an initmsg yet, and this is the "mcp" * message, save this line for later. */ if( wld->mcp_initmsg == NULL && mcp_is_init( line ) ) wld->mcp_initmsg = xstrdup( line->str ); } extern void world_mcp_server_connect( World *wld ) { char *str; Line *line; /* If we don't have a key, MCP is simply uninitialized. The server * should greet us with the MCP handshake, and the client should * reply, getting everything going. */ /* If we have a key, but we're not negotiated, negotiate now. */ if( wld->mcp_key != NULL && !wld->mcp_negotiated ) { xasprintf( &str, "#$#mcp-negotiate-can %s package: " "dns-nl-icecrew-mcpreset min-version: 1.0 " "max-version: 1.0", wld->mcp_key ); line = line_create( str, -1 ); line->flags = LINE_MCP; linequeue_append( wld->client_toqueue, line ); xasprintf( &str, "#$#mcp-negotiate-end %s", wld->mcp_key ); line = line_create( str, -1 ); line->flags = LINE_MCP; linequeue_append( wld->client_toqueue, line ); wld->mcp_negotiated = 1; } /* If we have a key and we're negotiated, do the mcp reset. */ if( wld->mcp_key != NULL && wld->mcp_negotiated ) { /* Send the mcp reset. */ xasprintf( &str, "#$#dns-nl-icecrew-mcpreset-reset %s", wld->mcp_key ); line = line_create( str, -1 ); line->flags = LINE_MCP; linequeue_append( wld->client_toqueue, line ); } /* Erase all MCP administration. */ wld->mcp_negotiated = 0; free( wld->mcp_key ); wld->mcp_key = NULL; free( wld->mcp_initmsg ); wld->mcp_initmsg = NULL; } extern void world_mcp_client_connect( World *wld ) { char *str; Line *line; /* If we don't have an MCP key, the best we can do is relay the MCP * handshake from the server to the client. The client should then * react (if it supports MCP) to get the whole thing going. */ if( wld->mcp_key == NULL ) { /* If we have not captured a handshake msg from the server, * either the server does not support MCP, or something's * screwed. */ if( wld->mcp_initmsg != NULL ) { line = line_create( xstrdup( wld->mcp_initmsg ), -1 ); line->flags = LINE_MCP; linequeue_append( wld->client_toqueue, line ); } } /* If we have a key, but we're not negotiated, negotiate now. */ if( wld->mcp_key != NULL && !wld->mcp_negotiated ) { xasprintf( &str, "#$#mcp-negotiate-can %s package: " "dns-nl-icecrew-mcpreset min-version: 1.0 " "max-version: 1.0", wld->mcp_key ); line = line_create( str, -1 ); line->flags = LINE_MCP; linequeue_append( wld->server_toqueue, line ); xasprintf( &str, "#$#mcp-negotiate-end %s", wld->mcp_key ); line = line_create( str, -1 ); line->flags = LINE_MCP; linequeue_append( wld->server_toqueue, line ); wld->mcp_negotiated = 1; } /* If we have a key and we're negotiated, do the mcp reset. */ if( wld->mcp_key != NULL && wld->mcp_negotiated ) { /* Send the mcp reset. */ xasprintf( &str, "#$#dns-nl-icecrew-mcpreset-reset %s", wld->mcp_key ); line = line_create( str, -1 ); line->flags = LINE_MCP; linequeue_append( wld->server_toqueue, line ); /* We're not negotiated anymore. */ wld->mcp_negotiated = 0; /* Any old key is now invalid. */ free( wld->mcp_key ); wld->mcp_key = NULL; /* We'll receive a new "mcp" message. */ free( wld->mcp_initmsg ); wld->mcp_initmsg = NULL; } } /* Check if this is the "mcp" message. Returns a boolean. */ static int mcp_is_init( Line *line ) { return !strncasecmp( line->str, "#$#mcp ", 7 ); } /* Check if this is the "mcp-negotiate-can" message. Returns a boolean. */ static int mcp_is_negotiate_can( Line *line ) { /* Note that we do _not_ check the MCP key on this message. * We only check the mcp-negotiate-can from the client, which is * assumed to be trusted. */ return !strncasecmp( line->str, "#$#mcp-negotiate-can ", 21 ); } /* Attempts to extract the value associated with the "authentication-key" * key from this message. The message should be a "mcp" message. * Returns a copy of the authentication key (which should be freed), or * NULL on failure. */ static char *extract_mcpkey( Line *line ) { char *str = line->str, *end, *mcpkey; int foundkey; /* If this is not a "mcp" message, we can't parse it. */ if( strncasecmp( str, "#$#mcp ", 7 ) ) return NULL; /* Skip past the first part of the string, we parsed that. */ str += 6; for(;;) { /* Skip whitespace */ while( *str == ' ' ) str++; /* End of string, we fail. */ if( *str == '\0' ) return NULL; /* See if the current key is "authentication-key", and * remember that for later. */ foundkey = !strncasecmp( str, "authentication-key: ", 20 ); /* Skip over the key. */ while( *str != ':' && *str != ' ' && *str != '\0' ) str++; /* A key should really be followed by ':'. */ if( *str != ':' ) return NULL; str++; /* Skip whitespace. */ while( *str == ' ' ) str++; /* If the key was not the one we're looking for, just skip * over the value and continue. */ if( !foundkey ) { str = skip_mcp_value( str ); /* Fail on error. */ if( str == NULL ) return NULL; continue; } /* The key was the one we're looking for! * First, find the end of the value. */ end = str; while( *end != ' ' && *end != '\0' ) end++; /* Duplicate it, and return it (stripping off quotes). */ mcpkey = xmalloc( end - str + 1 ); strncpy( mcpkey, str, end - str ); mcpkey[end - str] = '\0'; return remove_enclosing_quotes( mcpkey ); } } static char *skip_mcp_value( char *str ) { /* If the value does not start with ", it's a simple value. * Just skip until the first space. */ if( *str != '"' ) { while( *str != ' ' && *str != '\0' ) str++; return str; } /* It starts with ", so it's a quoted value. First, skip over the ". */ str++; /* Scan until a " or end of string. */ while( *str != '"' && *str != '\0' ) { /* If we encounter a \, the following character is escaped, * so we skip the next character. */ if( *str == '\\' && str[1] != '\0' ) str++; str++; } /* It's a parse error if we hit the end of the string. */ if( *str != '"' ) return NULL; return str + 1; } mooproxy-1.0.0/global.h0000644000175000017500000000627311525336516014656 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__GLOBAL #define MOOPROXY__HEADER__GLOBAL /* Mooproxy version */ #define VERSIONSTR "1.0.0" #define RELEASEDATE "2011-02-11" #define COPYYEARS "2001-2011" /* Configuration dirnames */ #define CONFIGDIR ".mooproxy" #define WORLDSDIR "worlds" #define LOGSDIR "logs" #define LOCKSDIR "locks" /* Some default option values */ #define DEFAULT_AUTOLOGIN 0 #define DEFAULT_AUTORECONNECT 0 #define DEFAULT_CMDSTRING "/" #define DEFAULT_INFOSTRING "%c%% " #define DEFAULT_NEWINFOSTRING "%C%% " #define DEFAULT_LOGGING 1 #define DEFAULT_CONTEXTLINES 100 #define DEFAULT_BUFFERSIZE 4096 #define DEFAULT_LOGBUFFERSIZE 4096 #define DEFAULT_STRICTCMDS 1 #define DEFAULT_LOGTIMESTAMPS 1 #define DEFAULT_EASTEREGGS 1 /* Parameters for the token bucket controlling authentication attempts. */ #define NET_AUTH_BUCKETSIZE 5 #define NET_AUTH_TOKENSPERSEC 1 /* The number of failed login attempts that triggers the warning. */ #define NET_TOOMANY_LOGIN_FAILURES 20 /* Maximum number of privileged addresses. */ #define NET_MAX_PRIVADDRS 8 /* Maximum number of authenticating connections */ #define NET_MAXAUTHCONN 8 /* Number of authentication slots reserved for privileged addresses. */ #define NET_AUTH_PRIVRES 2 /* Maximum number of characters accepted from an authenticating client. * This effectively also limits the authentication string length */ #define NET_MAXAUTHLEN 128 /* Size of blocks-to-lines buffer in bytes. * Note that this limits the maximum line length. */ #define NET_BBUFFER_LEN 65536 /* The actual size (rather than "pretend size") of the buffers. * See (1) in world.c for details. */ #define NET_BBUFFER_ALLOC ( NET_BBUFFER_LEN + 512 ) /* The maximum time in seconds to delay between two autoreconnects. */ #define AUTORECONNECT_MAX_DELAY 1800 /* The exponential backoff in autoreconnecting. */ #define AUTORECONNECT_BACKOFF_FACTOR 1.5 /* The minimum number of seconds between two identical complaints about * the logfiles */ #define LOG_MSGINTERVAL 600 /* The minimum number of seconds between two "not connected" messages.*/ #define NOTCONN_MSGINTERVAL 3 /* The strftime() format, and string length of the log timestamp. */ #define LOG_TIMESTAMP_FORMAT "[%H:%M:%S] " #define LOG_TIMESTAMP_LENGTH 11 /* When malloc() fails, mooproxy will sleep for a bit and then try again. * This setting determines how often mooproxy will try before giving up. */ #define XMALLOC_OOM_RETRIES 4 /* The name of the panic file, which will be placed in ~. */ #define PANIC_FILE "mooproxy.panic" /* The maximum allowed length of the config file, in KiB. */ #define CONFIG_MAXLENGTH 128UL #endif /* ifndef MOOPROXY__HEADER__GLOBAL */ mooproxy-1.0.0/Changelog0000644000175000017500000002525011525336516015053 0ustar marcelmmarcelm11-02-2011 - 1.0.0 -------------------- * Greatly improved online /help. * Renamed a bunch of settings (yeah, again) auth_md5hash -> auth_hash context_on_connect -> context_lines logging_enabled -> logging timestamped_logs -> log_timestamps max_buffer_size -> buffer_size max_logbuffer_size -> logbuffer_size * /get and /set have been removed, and replaced by / * Setting names are now "underscore equivalent", so you can use arbitrary underscores in setting names (authhash is equivalent to auth_hash). * /listopts has been renamed to /settings, and now prints values as well. * Added Ansi Client Emulation (ACE), to make life bearable when using telnet. * Authentication attemts are now rate-limited at 1 attempt/sec/IP, except for "known" IP addresses, and added the /authinfo command. * Several messages (such as "now connected to", "connection lost", client connect, mooproxy startup, mooproxy shutdown) are now logged too. * Perform a clean shutdown on SIGTERM, and a forced shutdown on SIGQUIT. * Changing the autoreconnect setting now takes effect immediately (it used to take effect after the next connect). * Added /forget command to clear all history lines. 01-09-2007 - 0.1.3 -------------------- * /recall now allows users to search for specific lines by time and contents. See the README for details. * New option; autoreconnect will cause mooproxy to reconnect when it loses the connection to the server. * New option; newinfostring allows users to use a custom prefix for "New lines" and all related messages. This should make it easier to spot those lines when the client connects. * Setting the listenport option from within mooproxy is now allowed, and actually attempts to rebind to the new port. * Context lines produced when connecting now have the ASCII BELL character filtered from them, so you won't hear a sound from old lines. * Tab characters are now included in the logs instead of being discarded. * Improved logging performance. * Fixed a file descriptor leak on server hostname resolve. * Fixed a small bug that sometimes caused the timestamps in the logfiles to be off by one second. * Fixed a small bug where mooproxy would sometimes say "No new lines" instead of "End of new lines". 05-03-2007 - 0.1.2 -------------------- * Context/history/new lines got a makeover. On connect, the user now gets context lines, possibly new lines, and certainly new lines. * max_buffered_size and max_history_size are replaced by max_buffer_size. * Logfiles are now stored in a hierarchy; logs/$worldname/YYYY-MM. Use the update-logs.sh script to update your logfiles' locations. Users of the Debian package can find the script in /usr/share/doc/mooproxy. * Mooproxy now requires colors in the infostring to be specified using mooproxy color sequences (see ExampleConfig for details). * Added timestamped_logs option, which prepends a timestamp to logged lines. * Added max_logbuffer_size option, to limit the amount of unlogged lines. * Mooproxy now creates 'today' and 'yesterday' logfile symlinks for each world. * Logfiles are now synced to disk every minute. * The logging error-checking has been improved. * Mooproxy now sends mcpreset to the client on server reconnect. * Mooproxy now checks ~/.mooproxy permissions and warns if they're weak. * The "Not connected to server" message is now throttled to once every 4 sec. 04-08-2006 - 0.1.1 -------------------- * Fixed a bug where mooproxy won't start if ~/.mooproxy/locks/ doesn't exist. * Fixed a bug where mooproxy will crash if there's no auth_md5hash defined in the configuration file or the user does /set auth_md5hash "". * Fixed a bug where mooproxy did not save its PID in the pidfile if it was run in the foreground (instead of as a daemon). * Configuration loading code rewritten. * MCP code rewritten (less intrusive for non MCP capable clients now). * The maximum linelength has been increased from 16kb to 64kb * Improved the crashlog layout, and included a bit more information. * The README now contains a bit of information about MCP. * Cleaned up several parts of code, adding comments and improving efficiency or robustness in a few places. 16-02-2006 - 0.1.0 -------------------- * Mooproxy is now also packaged as a Debian package * Mooproxy has become an actual daemon, backgrounding itself * New commandline option; -d forces mooproxy to stay on the foreground * Create and check for lockfiles, so only one instance can run for each world * SIGSEGV, SIGFPE, SIGILL, SIGBUS are now caught * On crash, mooproxy creates a crash notice (~/mooproxy.crashed) * Mooproxy sends this crash notice to the client and to stderr as well * SIGUSR1 and SIGUSR2 are now ignored * /shutdown now refuses to shut down if not all loggable data could be written to disk. -f overrides this 12-12-2005 - 0.0.7.1 ---------------------- * Fixed a bug where recalled lines would go in the history again, leading to duplicated lines in the history. Thanks to Admar Schoonen for reporting. 02-09-2005 - 0.0.7 -------------------- * From now on, mooproxy is licensed under GPL 2, instead of "GPL 2 or later" * The README has gotten a major upgrade and now contains much more useful info * Server side now supports IPv6 (actually AF independent) * Client side now supports IPv6 (actually AF independent) * The authentication string is now MD5-hashed in the config file * Mooproxy accepts the --md5crypt option to generate a MD5 hash * /getopt and /setopt have been renamed to /get and /set * New option; autologin. Determines if the proxy logs in after connect * Logging has been improved further. On error conditions (such as lack of disk space), mooproxy now buffers loggable lines in memory, and writes them to log as soon as the error condition is resolved. The amount of memory mooproxy may use for this is not yet limited. * Multiple lines are now merged in one write(), improving throughput * Mooproxy now always emits network newlines (\r\n) * On connect, mooproxy now prints when and where from you were last connected * On connection takeover, mooproxy now reports the IP address of the overtaking client to the overtaken client. * The maximum authstring length has been reduced from 1024 to 128 bytes * The maximum linelength has been increased from 8kb to 16kb * Resolve of and connect to server are now nonblocking * /disconnect now aborts any resolving or connecting in progress * Mooproxy now checks every memory allocation, and aborts if one fails. * Mooproxy now returns EXIT_FAILURE or EXIT_SUCCESS, instead of the (undocumented) list of specific exit codes * Mooproxy now ignores SIGURG (used in OOB TCP, which we don't use anyway) * Cleaned up code comments, and added lots of new ones 19-07-2005 - 0.0.6.1 ---------------------- * Fixed SEGV on connect with no buffered lines waiting. Thanks to Bram Senders for reporting. 19-07-2005 - 0.0.6 -------------------- * The infrastructure and main loop architecture have gotten a major overhaul * Regression; the output "resolving" and "connecting" steps of the /connect command now only get reported to the user simultaneously, after the process has completely succeeded or completely failed. * Mooproxy now automatically recalls and passes on connect * Removed command; /pass * New option; strict_commands. Determines what to do with invalid commands * New option; context_on_connect. The number of context lines you get * New option; max_buffered_size. The number of KB dedicated to buffer * New option; max_history_size. The number of KB dedicated to history * New option; logging_enabled. Determines if mooproxy logs. * Option parsing/management has been rewritten * Mooproxy now displays how full the buffer was in percent on connect * New command; /world. Prints the name of the current world * New command; /uptime. Reports starting date and running time * The output from /listopts is now limited to roughly 72 chars * The maximum linelength has been decreased from 100kb to 8kb * Mooproxy now ignores SIGHUP (so it continues running if parent dies) * MCP reset is now silent * Config dirs are now created with mode 700 instead of 750 * Fixed bug in logging code. Extended ASCII chars are now logged too. 29-04-2004 - 0.0.5 -------------------- * Forget MCP session on connect-to-server, so that MCP reset works properly if you disconnect and reconnect to the server. * Authentication code cleanup/rewrite. More efficient now. * If all authentication connections are full and there's a new connection, the oldest authentication connection gets kicked. * Removed commands; /time, /block, /options, /showopts * New commands for option management; /listopts, /getopt, /setopt * /connect now takes optional hostname and port * Improved /help * Day rollover notification (not generic, should be improved) * Logging (always on, output only, always without ansi, only 1 logfile/day. Functional, but generally hackish, rewrite) 19-04-2004 - 0.0.4 -------------------- * Improved both read() and write() code. Much more robust now. The tradeoff is that simultaneously received lines are no longer merged into one write(). * Fixed SEGV on resolve failure. * Fixed SEGV on authentication if no authstring is given in config. * Mooproxy now refuses to start if authstring is "" or absent. * MCP reset is now sent automatically on client connect. The /mcpreset command is removed. * History implemented. Use /recall to recall num lines of history. Currently, mooproxy always keeps 1024 lines of history. * Added /version, which prints the mooproxy version. * Added /date, which prints the date/time. /time is an alias for /date. * Improved MCP parsing code. Much cleaner, and more robust. 15-04-2004 - 0.0.3 -------------------- * Oops, 0.0.2 SEGVs if the client sends multilines to the server. That's fixed (well, workarounded... Improve the MCP code is on the Todo) now. 15-04-2004 - 0.0.2 -------------------- * Prelimary MCP support. Mooproxy understands enough MCP to inject messages to the server stating it can do MCP resets. * New command; /mcpreset. Attempts to reset the MCP session. Use directly after your client connected to mooproxy (if mooproxy is connected to the server). * "disconnected" now goes in buffer and direct, meaning you will get the message twice (eventually). 01-04-2004 - 0.0.1 -------------------- * First release; Basic functionality is there, but many features are lacking. You can connect to mooproxy, connect to the server, disconnect, reconnect, etc. 29-01-2004 - None ------------------- * Resumed coding :) 27-03-2001 - None ------------------- * started coding mooproxy-1.0.0/mooproxy.10000644000175000017500000000433711525336516015222 0ustar marcelmmarcelm.\" Hey, EMACS: -*- nroff -*- .\" First parameter, NAME, should be all caps .\" Second parameter, SECTION, should be 1-8, maybe w/ subsection .\" other parameters are allowed: see man(7), man(1) .TH MOOPROXY 1 "September 1, 2011" .\" Please adjust this date whenever revising the manpage. .\" .\" Some roff macros, for reference: .\" .nh disable hyphenation .\" .hy enable hyphenation .\" .ad l left justify .\" .ad b justify to both left and right margins .\" .nf disable filling .\" .fi enable filling .\" .br insert line break .\" .sp insert n+1 empty lines .\" for manpage-specific macros, see man(7) .SH NAME mooproxy \- a buffering proxy for MOO connections .SH SYNOPSIS .B mooproxy .RI [ options ] .SH DESCRIPTION .PP .B mooproxy is a buffering proxy for MOOs (a class of text-based RPGs, descending from MUDs). The MOO client connects to mooproxy, and mooproxy connects to the MOO server. .PP Mooproxy keeps you connected to the MOO server if the client disconnects (voluntarily, because of network problems, because the client crashes, because of a power outage, etc). .PP Mooproxy also provides centralised logging, buffers text when the client isn't connected, and provides context when the client does connect. .PP .SH OPTIONS This program follows the usual GNU command line syntax, with long options starting with two dashes (`-'). A summary of options is included below. .TP .B \-h, \-\-help Show summary of options and exit. .TP .B \-V, \-\-version Show version information and exit. .TP .B \-L, \-\-license Show licensing information and exit. .TP .B \-w, \-\-world \fIworldname\fR Specify the world file to load. .TP .B \-d, \-\-no-daemon Do not daemonize; stay in the foreground instead. .TP .B \-m, \-\-md5crypt Prompt for a string, and create and show an MD5 hash of this string. .SH COPYRIGHT Mooproxy is copyright (C) 2001-2011 Marcel Moreaux .SH SEE ALSO .PP Mooproxy offers online help for all of its commands and settings, accessible using the /help command. .PP More comprehensive information about mooproxy can be found in /usr/share/doc/mooproxy/README. .PP See /usr/share/doc/mooproxy/ExampleConfig for an example configuration file. mooproxy-1.0.0/log.c0000644000175000017500000002734311525336516014173 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include #include #include #include #include #include #include #include "global.h" #include "world.h" #include "log.h" #include "misc.h" #include "line.h" static void update_one_link( World *wld, char *link, time_t timestamp ); static void log_init( World *, time_t ); static void log_deinit( World * ); static void log_write( World * ); static void nag_client_error( World *, char *, char *, char * ); extern void world_log_line( World *wld, Line *line ) { Line *newline; char *str; long len; /* Do we prepend a timestamp? */ if( wld->log_timestamps ) { /* First, make sure the timestamp is up to date. */ if( wld->log_currenttime != line->time ) { free( wld->log_currenttimestr ); wld->log_currenttimestr = xstrdup( time_string( line->time, LOG_TIMESTAMP_FORMAT ) ); wld->log_currenttime = line->time; } /* Duplicate the line, but with ANSI stripped, * prepending the timestamp. */ str = xmalloc( line->len + 1 + LOG_TIMESTAMP_LENGTH ); strcpy( str, wld->log_currenttimestr ); len = strcpy_noansi( str + LOG_TIMESTAMP_LENGTH, line->str ); len += LOG_TIMESTAMP_LENGTH; } else { /* Duplicate the line, but with ANSI stripped. */ str = xmalloc( line->len + 1 ); len = strcpy_noansi( str, line->str ); } /* Process the string into a logline. */ str = xrealloc( str, len + 1 ); newline = line_create( str, len ); newline->flags = line->flags; newline->time = line->time; newline->day = line->day; /* Put the new line in the to-be-logged queue. */ linequeue_append( wld->log_queue, newline ); } extern void world_flush_client_logqueue( World *wld ) { /* See if there's anything to log at all */ if( wld->log_queue->count + wld->log_current->count + wld->log_bfull == 0 ) { /* If: * - There's nothing to log, and * - We have a log file open, and * - Either: * - Logging is disabled, or * - It's a different day now * we should close the log file. */ if( wld->log_fd > -1 && ( !wld->logging || wld->log_currentday != current_day() ) ) log_deinit( wld ); /* There's nothing to log. If there was a log error situation, * that must be over now, report on that. */ if( wld->log_lasterror ) { Line *line; line = world_msg_client( wld, "Logging resumed success" "fully. %li lines have been irretrievably " "lost.", wld->dropped_loggable_lines ); /* Make this line a checkpoint message, but if no * lines were dropped, don't log it. */ line->flags = LINE_CHECKPOINT; if( wld->dropped_loggable_lines == 0 ) line->flags |= LINE_DONTLOG; wld->dropped_loggable_lines = 0; free( wld->log_lasterror ); wld->log_lasterror = NULL; } /* Nothing to log, we're done. */ return; } /* Ok, we have something to log. */ if( wld->log_queue->count > 0 ) { /* If the current day queue is empty, and there are lines * from a different day waiting, the current day is done. */ if( wld->log_current->count + wld->log_bfull == 0 && wld->log_queue->head->day != wld->log_currentday ) { /* Close the current logfile. * A new one will be opened automatically. */ log_deinit( wld ); wld->log_currentday = wld->log_queue->head->day; return; } /* Move lines that were logged in the current day from * log_queue to log_currentday */ while( wld->log_queue->count > 0 && wld->log_queue->head->day == wld->log_currentday ) linequeue_append( wld->log_current, linequeue_pop( wld->log_queue ) ); } /* Lines waiting and no log file open? Open one! */ if( wld->log_fd == -1 && wld->log_current->count > 0 ) log_init( wld, wld->log_current->head->time ); /* Actually try and write lines from the current day. */ log_write( wld ); } extern void world_sync_logdata( World *wld ) { if( wld->log_fd == -1 ) return; /* Sync all data written to the logfile FD to disk. * This is best effort, we don't check errors atm. */ fdatasync( wld->log_fd ); } extern void world_log_link_remove( World *wld ) { int logging = wld->logging; wld->logging = 0; world_log_link_update( wld ); wld->logging = logging; } extern void world_log_link_update( World *wld ) { time_t timestamp = current_time(); int today; /* Update the 'today' link. */ update_one_link( wld, "today", timestamp ); /* Obtain a unix timestamp somewhere in the previous day. * Just subtracting 24*60*60 seconds is not guaranteed to work on * summertime switchover days, because days with a summertime -> * wintertime switch have 25*60*60 seconds. On the other hand, * subtracting 25*60*60 seconds might put us _two_ days before the * current day. * Converting to localtime first and then decrementing the day field * does not help, because the struct tm also contains flags for the * daylight saving time. That means that on switchover days we carry * over the wrong DST flag to the previous day which again leads to * problems. * Finally, renaming 'today' to 'yesterday' at midnight only works * at midnight, not when starting mooproxy. * So we just subtract hours from the current unix timestamp until we * end up in the previous day. Ugly, but it works. */ today = localtime( ×tamp )->tm_mday; while( localtime( ×tamp )->tm_mday == today ) timestamp -= 3600; update_one_link( wld, "yesterday", timestamp ); } static void update_one_link( World *wld, char *linkname, time_t timestamp ) { char *link, *target, *fulltarget; struct stat statinfo; int ret; /* Construct the link path+filename. */ xasprintf( &link, "%s/%s/%s/%s/%s", get_homedir(), CONFIGDIR, LOGSDIR, wld->name, linkname ); ret = lstat( link, &statinfo ); /* We give up if stat() fails with anything besides 'file not found'. */ if( ret == -1 && errno != ENOENT ) { free( link ); return; } /* Also, if the file exists but isn't a symlink, we leave it alone. */ if( ret > -1 && !S_ISLNK( statinfo.st_mode ) ) { free( link ); return; } /* Old link, begone. */ unlink( link ); /* If logging is disabled, we won't create any new symlinks. * Therefore, we're done. */ if( !wld->logging ) { free( link ); return; } /* Create the absolute path+filename of the target file. */ xasprintf( &fulltarget, "%s/%s/%s/%s/%s/%s - %s.log", get_homedir(), CONFIGDIR, LOGSDIR, wld->name, time_string( timestamp, "%Y-%m" ), wld->name, time_string( timestamp, "%F" ) ); ret = lstat( fulltarget, &statinfo ); /* If we can't get to the file, there's no sense in linking to it, * so we bail out. */ if( ret == -1 ) { free( link ); free( fulltarget ); return; } /* Create the relative path+filename of the target file. */ xasprintf( &target, "%s/%s - %s.log", time_string( timestamp, "%Y-%m" ), wld->name, time_string( timestamp, "%F" ) ); symlink( target, link ); free( link ); free( target ); free( fulltarget ); } static void log_init( World *wld, time_t timestamp ) { char *file = NULL, *errstr = NULL, *prev = NULL; int fd; /* Close the current log first. */ if( wld->log_fd > -1 ) log_deinit( wld ); /* Refuse if we have a logfile open. */ if( wld->log_fd > -1 ) return; /* Try to create ~/CONFIGDIR */ xasprintf( &file, "%s/%s", get_homedir(), CONFIGDIR ); if( attempt_createdir( file, &errstr ) ) goto createdir_failed; /* Try to create .../LOGSDIR */ prev = file; xasprintf( &file, "%s/%s", prev, LOGSDIR ); if( attempt_createdir( file, &errstr ) ) goto createdir_failed; free( prev ); /* Try to create .../$world */ prev = file; xasprintf( &file, "%s/%s", prev, wld->name ); if( attempt_createdir( file, &errstr ) ) goto createdir_failed; free( prev ); /* Try to create .../YYYY-MM */ prev = file; xasprintf( &file, "%s/%s", prev, time_string( timestamp, "%Y-%m" ) ); if( attempt_createdir( file, &errstr ) ) goto createdir_failed; free( prev ); /* Filename: .../$world - YYYY-MM-DD.log */ prev = file; xasprintf( &file, "%s/%s - %s.log", prev, wld->name, time_string( timestamp, "%F" ) ); free( prev ); /* Try and open the logfile. */ fd = open( file, O_WRONLY | O_CREAT | O_APPEND | O_NONBLOCK, S_IRUSR | S_IWUSR ); /* Error opening logfile? Complain. */ if( fd == -1 ) { nag_client_error( wld, "Could not open", file, strerror( errno ) ); free( file ); return; } wld->log_fd = fd; free( file ); /* Update the log symlinks later. */ wld->flags |= WLD_LOGLINKUPDATE; return; createdir_failed: nag_client_error( wld, "Could not create", file, errstr ); free( file ); /* Prevent double free()s ;) */ if( file != prev ) free( prev ); free( errstr ); } static void log_deinit( World *wld ) { /* Refuse if there's something left in the buffer. */ if( wld->log_bfull > 0 ) return; /* We're closing the logfile, it'd be nice if stuff ended up on * disk now. */ world_sync_logdata( wld ); if( wld->log_fd != -1 ) close( wld->log_fd ); wld->log_fd = -1; /* Update the log symlinks later. */ wld->flags |= WLD_LOGLINKUPDATE; } static void log_write( World *wld ) { int ret, errnum; /* No logfile, no writes */ if( wld->log_fd == -1 ) return; ret = flush_buffer( wld->log_fd, wld->log_buffer, &wld->log_bfull, wld->log_current, NULL, 0, NULL, NULL, &errnum ); if( ret == 1 ) nag_client_error( wld, "Could not write to logfile", NULL, "file descriptor is congested" ); if( ret == 2 ) nag_client_error( wld, "Could not write to logfile", NULL, strerror( errnum ) ); } static void nag_client_error( World *wld, char *msg, char *file, char *err ) { Line *line; char *str; int buffer_line = 0; /* Construct the message */ if( file == NULL ) xasprintf( &str, "%s: %s!", msg, err ); else xasprintf( &str, "%s `%s': %s!", msg, file, err ); /* Either the error should be different, or LOG_MSGINTERVAL seconds * should have passed. Otherwise, be silent. */ if( current_time() - wld->log_lasterrtime <= LOG_MSGINTERVAL && wld->log_lasterror && !strcmp( wld->log_lasterror, str ) ) { free( str ); return; } /* If the new error is the same as the old error, we're just repeating * ourselves. In that case, flag the line as DONTBUF, so the user * doesn't get spammed on connect. * If however, the new error is different, let the line be buffered. */ if( wld->log_lasterror && !strcmp( wld->log_lasterror, str ) ) buffer_line = LINE_DONTBUF; /* If we have dropped loggable lines, yell about that first. */ if( wld->dropped_loggable_lines > 0 ) { line = world_msg_client( wld, "LOGGABLE LINES DROPPED! %li " "lines have been irretrievably lost!", wld->dropped_loggable_lines ); line->flags |= buffer_line; } /* Next, inform the user that logging failed and how much logbuffer * space is left. */ line = world_msg_client( wld, "LOGGING FAILED! Approximately %lu lines" " not yet logged (%.1f%% of logbuffer).", wld->log_bfull / 80 + wld->log_queue->count + wld->log_current->count + 1, ( wld->log_queue->size + wld->log_current->size ) / 10.24 / wld->logbuffer_size ); line->flags |= buffer_line; /* Finally, report the reason of the failure. */ line = world_msg_client( wld, "%s", str ); line->flags |= buffer_line; /* Replace the old error by the new error. */ wld->log_lasterrtime = current_time(); free( wld->log_lasterror ); wld->log_lasterror = str; } mooproxy-1.0.0/line.h0000644000175000017500000000722211525336516014340 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__LINE #define MOOPROXY__HEADER__LINE #include /* Line flags */ #define LINE_DONTLOG 0x00000001 /* Don't log the line. */ #define LINE_DONTBUF 0x00000002 /* Don't buffer the line. */ #define LINE_NOHIST 0x00000004 /* Don't put the line in history. */ #define LINE_LOGONLY 0x00000008 /* Only send to the log. */ /* Regular server->client or client->server lines. */ #define LINE_REGULAR ( 0 ) /* MCP lines. */ #define LINE_MCP ( LINE_DONTLOG | LINE_DONTBUF | LINE_NOHIST ) /* Mooproxy checkpoint message. * These are mooproxy messages that are so important that they * deserve to be buffered and logged (e.g. day rollover, shutdown). */ #define LINE_CHECKPOINT ( 0 ) /* Mooproxy normal message (e.g. /settings output). */ #define LINE_MESSAGE ( LINE_DONTLOG | LINE_NOHIST ) /* Recalled lines, like context and possibly-new. */ #define LINE_RECALLED ( LINE_DONTLOG | LINE_DONTBUF | LINE_NOHIST ) /* Line type */ typedef struct Line Line; struct Line { char *str; Line *next; Line *prev; long len; long day; /* Day of the line's creation. Used in logging. */ time_t time; /* Time of the line's creation. */ int flags; }; /* Linequeue type */ typedef struct Linequeue Linequeue; struct Linequeue { Line *head; Line *tail; /* Number of lines in this queue. */ unsigned long count; /* Total number of bytes this queue occupies. */ unsigned long size; }; /* Create a line, set its flags to LINE_REGULAR, and prev/next to NULL. * Time and day are set to current time/day. * len should contain the length of str (excluding \0), or -1, in which case * line_create() will calculate the length itself. * Str is consumed. Returns the new line. */ extern Line *line_create( char *str, long len ); /* Destroy line, freeing its resources. */ extern void line_destroy( Line *line ); /* Duplicate line (and its string). All fields are copied, except for * prev and next, which are set to NULL. Returns the new line. */ extern Line *line_dup( Line *line ); /* Allocate and initialize a line queue. The queue is empty. * Return value: the new queue. */ extern Linequeue *linequeue_create( void ); /* Destroy queue, freeing its resources. * Any strings left in the queue are destroyed too. */ extern void linequeue_destroy( Linequeue *queue ); /* Destroy all lines in queue. */ extern void linequeue_clear( Linequeue *queue ); /* Append line to the end of queue */ extern void linequeue_append( Linequeue *queue, Line *line ); /* Remove the line at the start of the queue, and return it (NULL if empty). */ extern Line* linequeue_pop( Linequeue *queue ); /* Remove the line at the end of the queue, and return it (NULL if empty). */ extern Line* linequeue_popend( Linequeue *queue ); /* Remove line from queue, regardless of its position in queue. * Line MUST be in queue. Returns line. */ extern Line *linequeue_remove( Linequeue *queue, Line *line ); /* Merge two queues. The contents of queue two is appended to queue one, * leaving queue two empty. */ extern void linequeue_merge( Linequeue *one, Linequeue *two ); #endif /* ifndef MOOPROXY__HEADER__LINE */ mooproxy-1.0.0/panic.c0000644000175000017500000001114211525336516014472 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include #include #include #include #include #include #include #include #include "panic.h" #include "world.h" #include "misc.h" #include "global.h" static void panicreason_to_string( char *str, int reason, long extra, unsigned long uextra ); static void worldlist_to_string( char *str, int wldcount, World **worlds ); extern void sighandler_panic( int sig ) { panic( PANIC_SIGNAL, sig, 0 ); } extern void panic( int reason, long extra, unsigned long uextra) { char *timestamp, panicstr[1024], panicfile[4096]; int panicfd, wldcount, i; World **worlds; /* First of all, set the signal handlers to default actions. * We don't want to cause an endless loop by SEGVing in here. */ signal( SIGSEGV, SIG_DFL ); signal( SIGILL, SIG_DFL ); signal( SIGFPE, SIG_DFL ); signal( SIGBUS, SIG_DFL ); /* Get list of worlds */ world_get_list( &wldcount, &worlds ); /* Create the panicfile */ strcpy( panicfile, getenv( "HOME" ) ); strcat( panicfile, "/" PANIC_FILE ); panicfd = open( panicfile, O_WRONLY | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR ); /* Create the timestamp and panic message */ timestamp = time_string( time( NULL ), "%F %T" ); sprintf( panicstr, "%s mooproxy (PID %li) crashed!\n", timestamp, (long) getpid() ); /* Write it to all destinations */ write( STDERR_FILENO, panicstr, strlen( panicstr ) ); write( panicfd, panicstr, strlen( panicstr ) ); for( i = 0; i < wldcount; i++ ) { if( worlds[i]->client_fd < 0 ) continue; write( worlds[i]->client_fd, "\r\n", sizeof( "\r\n" ) - 1 ); write( worlds[i]->client_fd, panicstr, strlen( panicstr ) ); } /* Formulate the reason of the crash */ panicreason_to_string( panicstr, reason, extra, uextra ); /* And write it to all destinations */ write( STDERR_FILENO, panicstr, strlen( panicstr ) ); write( panicfd, panicstr, strlen( panicstr ) ); for( i = 0; i < wldcount; i++ ) { if( worlds[i]->client_fd < 0 ) continue; write( worlds[i]->client_fd, panicstr, strlen( panicstr ) ); } /* Formulate the list of worlds */ worldlist_to_string( panicstr, wldcount, worlds ); /* And write it to all destinations */ write( STDERR_FILENO, panicstr, strlen( panicstr ) ); write( panicfd, panicstr, strlen( panicstr ) ); for( i = 0; i < wldcount; i++ ) { if( worlds[i]->client_fd < 0 ) continue; write( worlds[i]->client_fd, panicstr, strlen( panicstr ) ); } /* Bail out */ _exit( EXIT_FAILURE ); } static void panicreason_to_string( char *str, int reason, long extra, unsigned long uextra ) { /* Make the string begin with two spaces */ strcpy( str, " " ); str += 2; switch( reason ) { case PANIC_SIGNAL: switch( extra ) { case SIGSEGV: strcpy( str, "Segmentation fault" ); break; case SIGILL: strcpy( str, "Illegal instruction" ); break; case SIGFPE: strcpy( str, "Floating point exception" ); break; case SIGBUS: strcpy( str, "Bus error" ); break; default: strcpy( str, "Caught unknown signal" ); break; } break; case PANIC_MALLOC: sprintf( str, "Failed to malloc() %lu bytes", uextra ); break; case PANIC_REALLOC: sprintf( str, "Failed to realloc() %lu bytes", uextra ); break; case PANIC_STRDUP: sprintf( str, "Failed to strdup() %lu bytes", uextra ); break; case PANIC_STRNDUP: sprintf( str, "Failed to strndup() %lu bytes", uextra ); break; case PANIC_VASPRINTF: strcpy( str, "vasprintf() failed (lack of memory?)" ); break; case PANIC_SELECT: sprintf( str, "select() failed: %s", strerror( extra ) ); break; case PANIC_ACCEPT: sprintf( str, "accept() failed: %s", strerror( extra ) ); break; default: strcpy( str, "Unknown error" ); break; } strcat( str, "\n" ); } static void worldlist_to_string( char *str, int wldcount, World **worlds ) { int i; strcpy( str, " open worlds:" ); for( i = 0; i < wldcount; i++ ) { strcat( str, " [" ); strcat( str, worlds[i]->name ); strcat( str, "]" ); } strcat( str, "\n" ); } mooproxy-1.0.0/command.h0000644000175000017500000000174011525336516015026 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__COMMAND #define MOOPROXY__HEADER__COMMAND /* Try to execute a command. * If the line is recognised as a command, execute it and return 1. * If the line is not recognised and should be processed further, return 0. * line will not be modified or consumed. */ extern int world_do_command( World *wld, char *line ); #endif /* ifndef MOOPROXY__HEADER__COMMAND */ mooproxy-1.0.0/mcpreset.html0000644000175000017500000001173111525336516015750 0ustar marcelmmarcelm MCP Package specification :: dns-nl-icecrew-mcpreset

dns-nl-icecrew-mcpreset

Package for resetting and reinitializing the mcp session

Messages

 
Client to Server
dns-nl-icecrew-mcpreset-reset ( )
 
Server to Client
dns-nl-icecrew-mcpreset-reset ( )

Description

This package provides a way to reset and then reinitialize the mcp session. This should probably be provided by the MCP specification itself, but since it isn't this package will have to do.

There is only one message which as a slightly different meaning for the server than for the client, but the difference is only small.

When the client receives the 'reset' message it should finalize the current mcp session and be ready for a new initialisation by the server (this is done by resending #$#mcp version: 2.1 to: 2.1).

On the server side the 'reset' message also will finalize the mcp session. After it is finalized the server should send the initialisation of a new session (as stated above).

Caution: the client MUST be ready to accept the new initialisation and therefor MUST be properly finalised. Make sure that the client finalizes first!


Messages:

dns-nl-icecrew-mcpreset-reset

This will reset the current MCP session.

On the client side the session should be finalised and the client should be ready to accept a new initialisation of a MCP session.

On the server side the current session should be finalised and a new initialisation should be started.

Single line

dns-nl-icecrew-mcpreset-reset ( )

Example

#$#dns-nl-icecrew-mcpreset-reset 3453



Downloaded from http://www.icecrew.nl/files/koemoe/mcp/p_mcpreset.xhtml on 2008-12-06.

mooproxy-1.0.0/TODO0000644000175000017500000000557111525336516013735 0ustar marcelmmarcelmProbably's: * Tune Latency with TCP_NODELAY ? * Implement some MCP userlist in mooproxy? * More features for /recall (non-re, case sensitive, count, limit, etc) * Better RE support for /recall * Throttle client read() when server_txqueue is too full. (and vice versa) * /lock command to disallow changing options / connecting to strange servers? * Improve setting of auth_hash. * Change authstring to just mean the password instead of the entire connection string. May require additional options... * Allow specific options to be overrided from commandline (mooproxy -o listenport=1111) Maybe's: * Config file reloading on the fly? [from: cipri] * Support to listen on specific interfaces or addresses? [from: cipri] * Remove OOM detection in xmalloc() etc ? [Under Linux, the process seems to get SIGKILLed anyway] * Add logging framework which logs to file if mooproxy is running as daemon ? * Fix panic.c not to use sprintf()? * Print date/time on mooproxy messages? Maybe define a %-sequence for date/time? * Add SSL support (both ways)? * Add option to /connect to enable autoreconnect from the start? * Put mooproxy version in pre-authentication welcome message? * Make mooproxy send MCP to the server setting the linelength to 80 if the connecting client is not MCP capable [from: bram] * Have ACE send MCP client-size messages? * Improve the timer code, so mooproxy doesn't tick every second. [ugh, maybe... lots of hassle for little gain] * Fix /recall to -10 lines [completely forgot what's wrong with it...] * A command to send non-activating input to the MOO server. * Rewrite "about" section of README. * /copyright command? * /idle command (shows how long since you've last sent something to the moo) * Access / recall logs from within mooproxy? [from: paul] * Option for verbose MCP, to ease MCP debugging? * Provide more and better documentation. * Make mooproxy not remember authstring if autologin is off. ? * errno=EINTR is probably ok for most/all system calls, but is handled as an error in most places. Gotta fix...? * Live upgrades...? Probably not's: * Disallow the proxy to connect to itself (how?)? - Seems impossible to do in a sane way. * Have mooproxy convert received text to the right charset for logging? - Too complex; mooproxy can't know the charsets. * The accessor functions are mostly just dumb wrappers. Can't this be done using less code? - Lots of effort, virtually no gain. * Commandstring escaping (to be able to send something starting with the cmdstring to the MOO)? - Little enthusiasm from users. * Add option to use different dir (instead of ~/.mooproxy) for configuration ? - Little point. * Add option to ignore lockfile ? - Little point. The lockfile can always be removed if necessary. * Fix the "wacko resolver slave after libc upgrade" bug. - Only seems to be triggered by dist-upgrades, which require a reboot anyway. mooproxy-1.0.0/recall.c0000644000175000017500000005325411525336516014654 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ /* Todo: Check increasing precision in adjacent timespecs? (ie, barf on 01/02 01/02) Keywords to add: limit, count, context, before, after, timestamp, highlight, noansi WHEN -> ABSOLUTE [WHEN] | RELATIVE [WHEN] ABSOLUTE -> 'now' | 'today' | 'yesterday' | {last,next} 'mon' | 'monday' | 'tue' | 'tuesday' | ... | [YY/]MM/DD | HH:MM[:SS] RELATIVE -> '-/+' MODIFIER MODIFIER -> 'lines' | 'seconds' | 'minutes' | 'hours' | 'days' */ #include #include #include #include #include #include "recall.h" #include "world.h" #include "misc.h" typedef struct Params Params; struct Params { /* The argument string we're parsing. */ char *argstr; /* Error messages go here. */ char *error; /* A copy of the word in argstr we're examining now. * Must be freed. */ char *word; /* Indexes to the current word in argstr. */ long word_start; long word_end; /* Bitfield indicating which keywords we've seen so far. */ long keywords_seen; /* This time is manipulated by the time parsing functions. */ time_t when; /* The actual recall options. The parameters set (some of) these. */ time_t from; time_t to; long lines; char *search_str; /* Statistics about the recalled lines. */ long lines_inperiod; long lines_matched; }; static int parse_arguments( World *wld, Params *params ); static int parse_keyword_index( char *keyword ); static void parse_nextword( Params *params ); static int parse_keyword_from( World *wld, Params *params ); static int parse_keyword_to( World *wld, Params *params ); static int parse_keyword_search( World *wld, Params *params ); static int parse_when( World *wld, Params *params, int lma ); static int parse_when_relative( World *wld, Params *params, int lma ); static int parse_when_absolute( World *wld, Params *params ); static int parse_when_absdate( World *wld, Params *params ); static int parse_when_abstime( World *wld, Params *params ); static int parse_when_rchk( Params *params, int v, int l, int u, char *name ); static void recall_search_and_recall( World *wld, Params *params ); static void recall_match_one_line( World *wld, Params *params, Line *line ); static int recall_match( char *line, char *re ); static const char *weekday[] = { "sun", "sunday", "mon", "monday", "tue", "tuesday", "wed", "wednesday", "thu", "thursday", "fri", "friday", "sat", "saturday", NULL }; static const struct { char *keyword; int (*func)( World *, Params * ); } keyword_db[] = { { "from", parse_keyword_from }, { "to", parse_keyword_to }, { "search", parse_keyword_search }, { NULL, NULL } }; extern void world_recall_command( World *wld, char *argstr ) { Params params; params.argstr = argstr; /* Default recall: from oldest line to now. */ params.from = current_time(); if( wld->history_lines->tail ) params.from = wld->history_lines->head->time; params.to = current_time(); params.lines = 0; params.search_str = NULL; /* Parse the command arguments. */ if( parse_arguments( wld, ¶ms ) ) goto out; /* Make sure from < to. */ if( params.from > params.to ) { time_t t = params.from; params.from = params.to; params.to = t; } /* Print the recall header. */ if( params.lines == 0 ) { world_msg_client( wld, "Recalling from %s to %s.", time_string( params.from, "%a %Y/%m/%d %T" ), time_string( params.to, "%a %Y/%m/%d %T" ) ); } else { world_msg_client( wld, "Recalling %li lines %s %s.", abs( params.lines ), ( params.lines > 0 ) ? "after" : "before", ( params.from == current_time() ) ? "now" : time_string( params.from, "%a %Y/%m/%d %T" ) ); } /* Recall matching lines. */ recall_search_and_recall( wld, ¶ms ); /* Print the recall footer. */ world_msg_client( wld, "Recall end (%i / %i / %i).", wld->history_lines->count, params.lines_inperiod,\ params.lines_matched ); out: free( params.word ); free( params.error ); free( params.search_str ); } /* Loop over all argument words, trying to make sense of them. * All parameters encountered are altered in params. * On error, return true. On successful parse, return false. */ static int parse_arguments( World *wld, Params *params ) { int idx = 0; /* Initialization... */ params->error = NULL; params->word = NULL; params->word_start = 0; params->word_end = 0; params->keywords_seen = 0; parse_nextword( params ); while( params->word[0] != '\0' ) { /* Ok, which keyword are we dealing with? */ idx = parse_keyword_index( params->word ); /* Invalid keyword? */ if( idx == -1 ) { world_msg_client( wld, "Unrecognized keyword `%s'.", params->word ); return 1; } /* Keyword we've already seen? */ if( params->keywords_seen & ( 1 << idx ) ) { world_msg_client( wld, "Keyword `%s' may appear " "only once.", keyword_db[idx].keyword ); return 1; } params->keywords_seen |= 1 << idx; /* Parse the keywords options, if any. Throw error if the * keyword option parse function complains. */ if( (*keyword_db[idx].func)( wld, params ) ) { world_msg_client( wld, "%s", params->error ); return 1; } } /* The entire argument string has been parsed! */ return 0; } /* Loop up a keyword, return its index or -1 if unknown. * Search is case insensitive. */ static int parse_keyword_index( char *keyword ) { int idx; for( idx = 0; keyword_db[idx].keyword; idx++ ) if( !strcasecmp( keyword, keyword_db[idx].keyword ) ) return idx; return -1; } /* Scan for the next word in the argument string, copy it into the word * variable, and update some pointers. */ static void parse_nextword( Params *params ) { int wlen = 0; char *str; free( params->word ); params->word = NULL; /* Find the start of the next word. */ while( isspace( params->argstr[params->word_end] ) ) params->word_end++; /* Mark the start of the word. */ str = params->argstr + params->word_end; params->word_start = params->word_end; /* Find the end of the word. */ if( *str != '\0' ) while( *str && !isspace( *str ) ) { str++; wlen++; } /* Copy the word. */ params->word_end = params->word_start + wlen; params->word = xmalloc( wlen + 1 ); memcpy( params->word, params->argstr + params->word_start, wlen ); params->word[wlen] = '\0'; } /* Parse the options to the 'from' keyword. */ static int parse_keyword_from( World *wld, Params *params ) { int i; /* From may not come after to! */ if( params->keywords_seen & ( 1 << parse_keyword_index( "to" ) ) ) { xasprintf( ¶ms->error, "The `from' keyword may not " "appear after the `to' keyword." ); return 1; } parse_nextword( params ); /* When defaults to now, so that relative times are relative to now * instead of to the oldest line. */ params->when = current_time(); /* We're really expecting a timespec now. */ if( params->word[0] == '\0' ) { xasprintf( ¶ms->error, "Missing timespec after `from' " "keyword." ); return 1; } /* Scan next words for timespecs. */ for( i = 0; ; i++ ) { if( parse_when( wld, params, 0 ) ) { /* Parsing failed. If it's the first option, it's an * invalid timespec. If it's not the first option, we * claim success and leave the unparseable word to be * parsed higher layers. */ /* If it's the first option, it's invalid. Create * an error message if there isn't one already. */ if( i == 0 && !params->error ) xasprintf( ¶ms->error, "Invalid timespec: " "%s.", params->word ); /* Only fail if we actually have an error. */ return params->error != NULL; } params->from = params->when; } } /* Parse the options to the 'to' keyword. */ static int parse_keyword_to( World *wld, Params *params ) { int i; /* If from has been used, default when to from, so that relative times * are relative to the previously specified from. */ if( params->keywords_seen & ( 1 << parse_keyword_index( "from" ) ) ) params->when = params->from; else params->when = current_time(); parse_nextword( params ); /* We're really expecting a timespec now. */ if( params->word[0] == '\0' ) { xasprintf( ¶ms->error, "Missing timespec after `to' " "keyword." ); return 1; } /* Scan next words for timespecs. */ for( i = 0; ; i++ ) { /* lma = i == 0; 'lines' is only allowed if it's the first * option to this keyword. */ if( parse_when( wld, params, i == 0 ) ) { /* Parsing failed. If it's the first option, it's an * invalid timespec. If it's not the first option, we * claim success and leave the unparseable word to be * parsed higher layers. */ /* If it's the first option, it's invalid. Create * an error message if there isn't one already. */ if( i == 0 && !params->error ) xasprintf( ¶ms->error, "Invalid timespec: " "%s.", params->word ); /* Only fail if we actually have an error. */ return params->error != NULL; } /* If the lines parameter has been set, stop parsing. */ if( params->lines != 0 ) return 0; params->to = params->when; } } /* Parse the options to the 'search' keyword. */ static int parse_keyword_search( World *wld, Params *params ) { parse_nextword( params ); if( params->word[0] == '\0' ) { xasprintf( ¶ms->error, "Missing search string after " "`search' keyword." ); return 1; } /* The entire rest of the argument string becomes the search string. */ params->search_str = xstrdup( params->argstr + params->word_start ); /* Eat any remaining words. */ while( params->word[0] != '\0' ) parse_nextword( params ); return 0; } /* Parse a timespec. Returns true on error, false on success. * On error, params->error may or may not be set. * On success, params->when or params->lines will be modified. */ static int parse_when( World *wld, Params *params, int lma ) { if( params->word[0] == '-' || params->word[0] == '+' ) return parse_when_relative( wld, params, lma ); else return parse_when_absolute( wld, params ); } /* Parse a relative timespec. Returns true on error, false on success. * On error, params->error may or may not be set. * On success, params->when or params->lines will be modified. * lma idicates whether or not the 'lines' modifier is allowed now. */ static int parse_when_relative( World *wld, Params *params, int lma ) { char *str = params->word; long n = 0; int dir = 0; /* A relative timespec should start with - or +. */ if( *str != '-' && *str != '+' ) return 1; /* Get the direction, as determined by the sign. */ dir = ( *str == '+' ) ? 1 : -1; /* Eliminate the sign character. */ str++; /* If there's nothing left, continue parsing at the next word. */ if( *str == '\0' ) { parse_nextword( params ); str = params->word; } /* Need a digit now. */ if( !isdigit( *str ) ) { xasprintf( ¶ms->error, "Invalid relative timespec: %s.", params->word ); return 1; } /* Construct the number. */ while( isdigit( *str ) ) n = n * 10 + ( *str++ - '0' ); /* Zero not allowed. */ if( n == 0 ) { xasprintf( ¶ms->error, "Number should be non-zero: %s.", params->word ); return 1; } /* If there's nothing left, continue parsing at the next word. */ if( *str == '\0' ) { parse_nextword( params ); str = params->word; } /* We do want a modifier now. */ if( *str == '\0' ) { xasprintf( ¶ms->error, "Missing modifier to relative " "timespec." ); return 1; } /* FIXME: go through localtime() / mktime() ? * This might be better (or worse) for daylight saving. */ /* Parse the modifier word, and adjust when/lines accordingly. */ if( !strncasecmp( str, "seconds", strlen( str ) ) ) params->when += dir * n; else if( !strncasecmp( str, "secs", strlen( str ) ) ) params->when += dir * n; else if( !strncasecmp( str, "minutes", strlen( str ) ) ) params->when += dir * n * 60; else if( !strncasecmp( str, "mins", strlen( str ) ) ) params->when += dir * n * 60; else if( !strncasecmp( str, "hours", strlen( str ) ) ) params->when += dir * n * 60 * 60; else if( !strncasecmp( str, "hrs", strlen( str ) ) ) params->when += dir * n * 60 * 60; else if( !strncasecmp( str, "days", strlen( str ) ) ) params->when += dir * n * 60 * 60 * 24; else if( !strncasecmp( str, "lines", strlen( str ) ) ) params->lines = dir * n; else { xasprintf( ¶ms->error, "Invalid modifier to relative " "timespec: %s.", str ); return 1; } /* Was lines set while this was not allowed? */ if( params->lines != 0 && !lma ) { xasprintf( ¶ms->error, "The `lines' modifier may " "only be used alone with the `to' keyword." ); return 1; } parse_nextword( params ); return 0; } /* Parse an absolute timespec. Returns true on error, false on success. * On error, params->error may or may not be set. * On success, params->when or params->lines will be modified. */ static int parse_when_absolute( World *wld, Params *params ) { struct tm *tm; time_t t; int i, prevnext = 0; /* Now. */ if( !strcasecmp( params->word, "now" ) ) { params->when = current_time(); parse_nextword( params ); return 0; } /* Today. */ if( !strcasecmp( params->word, "today" ) ) { t = current_time(); tm = localtime( &t ); tm->tm_sec = 0; tm->tm_min = 0; tm->tm_hour = 0; params->when = mktime( tm ); parse_nextword( params ); return 0; } /* Yesterday. */ if( !strcasecmp( params->word, "yesterday" ) ) { t = current_time(); tm = localtime( &t ); tm->tm_sec = 0; tm->tm_min = 0; tm->tm_hour = 0; tm->tm_mday -= 1; params->when = mktime( tm ); parse_nextword( params ); return 0; } /* If we encounter 'next' or 'prev', note that in prevnext. * We'll use it in parsing weekdays. * 1 means next, -1 means last. */ if( !strcasecmp( params->word, "next" ) ) prevnext = 1; if( !strcasecmp( params->word, "last" ) ) prevnext = -1; if( prevnext != 0 ) { parse_nextword( params ); /* Try if we got a weekday next. */ for( i = 0; weekday[i]; i++ ) { if( strcasecmp( params->word, weekday[i] ) ) continue; tm = localtime( ¶ms->when ); tm->tm_sec = 0; tm->tm_min = 0; tm->tm_hour = 0; /* Seek forward if we encountered a 'last'. * Otherwise, seek backward. */ if( prevnext == 1 ) tm->tm_mday += ( i / 2 - tm->tm_wday + 6 ) % 7 + 1; else tm->tm_mday -= ( tm->tm_wday - i / 2 + 6 ) % 7 + 1; params->when = mktime( tm ); parse_nextword( params ); return 0; } /* If we got here, we didn't get a valid weekday. */ xasprintf( ¶ms->error, "Expecting a week day after `%s'.", prevnext == -1 ? "last" : "next" ); return 1; } /* Try parsing for [YY/]MM/DD. */ if( !parse_when_absdate( wld, params ) ) return 0; /* Try parsing for HH:MM[:SS]. */ if( !parse_when_abstime( wld, params ) ) return 0; return 1; } /* Parse an absolute date, [YY/]MM/DD. * Return true on error, false on success. * On success, params->when is modified. */ static int parse_when_absdate( World *wld, Params *params ) { struct tm *tm; int r, l, n1 = 0, n2 = 0, n3 = 0; /* Break down the current when. */ tm = localtime( ¶ms->when ); /* Scan the timespec string. */ r = sscanf( params->word, "%d/%d%n/%d%n", &n1, &n2, &l, &n3, &l ); /* If sscanf() didn't read 2 or 3 variables, or didn't consume the * entire string, something was wrong. */ if( ( r != 2 && r != 3 ) || l != strlen( params->word ) ) return 1; /* Is it MM/DD? */ if( r == 2 ) { tm->tm_mday = n2; tm->tm_mon = n1 - 1; } /* Or YY/MM/DD? */ if( r == 3 ) { tm->tm_mday = n3; tm->tm_mon = n2 - 1; /* Years are interpreted as a 0-99 number meaning a year * in the 1970 - 2069 range. */ tm->tm_year = ( n1 + 30 ) % 100 + 70; } /* Months are 1-12, days are 1-31. */ if( parse_when_rchk( params, tm->tm_mon + 1, 1, 12, "Months" ) ) return 1; if( parse_when_rchk( params, tm->tm_mday, 1, 31, "Days" ) ) return 1; /* Set the time to 00:00:00, and flag unknown daylight saving time. */ tm->tm_sec = 0; tm->tm_min = 0; tm->tm_hour = 0; tm->tm_isdst = -1; params->when = mktime( tm ); parse_nextword( params ); return 0; } /* Parse an absolute time, HH:MM[:SS]. * Return true on error, false on success. * On success, params->when is modified. */ static int parse_when_abstime( World *wld, Params *params ) { struct tm *tm; int r, l, n1 = 0, n2 = 0, n3 = 0; /* Break down the current when. */ tm = localtime( ¶ms->when ); /* Scan the timespec string. */ r = sscanf( params->word, "%d:%d%n:%d%n", &n1, &n2, &l, &n3, &l ); /* If sscanf() didn't read 2 or 3 variables, or didn't consume the * entire string, something was wrong. */ if( ( r != 2 && r != 3 ) || l != strlen( params->word ) ) return 1; tm->tm_hour = n1; tm->tm_min = n2; /* If HH:MM:SS, set the seconds. Otherwise, set seconds to 00. */ if( r == 3 ) tm->tm_sec = n3; else tm->tm_sec = 0; /* Hours are 00-23, minutes and seconds are 0-59. */ if( parse_when_rchk( params, tm->tm_hour, 0, 23, "Hours" ) ) return 1; if( parse_when_rchk( params, tm->tm_min, 0, 59, "Minutes" ) ) return 1; if( parse_when_rchk( params, tm->tm_sec, 0, 59, "Seconds" ) ) return 1; tm->tm_isdst = -1; params->when = mktime( tm ); parse_nextword( params ); return 0; } /* Check if l <= v <= u. * If the check succeeds, return false. * If the check fails, return true and set params->error. */ static int parse_when_rchk( Params *params, int v, int l, int u, char *name ) { if( v >= l && v <= u ) return 0; xasprintf( ¶ms->error, "%s should be in the range %i to %i.", name, l, u ); return 1; } /* Search the history for any lines that match our search criteria, * and recall those lines. */ static void recall_search_and_recall( World *wld, Params *params ) { Line *line; char *str; int count = 0, lines = 0; /* Initialize statistics. */ params->lines_inperiod = 0; params->lines_matched = 0; /* Transform the search-string, in-place, into a \0 separated list * of individually searchable strings so our "RE engine" can use it. * Yes, this is utterly horrible. */ if( params->search_str != NULL ) { params->search_str = xrealloc( params->search_str, strlen( params->search_str ) + 2 ); str = params->search_str; while( *str != '\0' ) *str = tolower( *str ), str++; str = params->search_str; while( *str != '\0' ) { if( *str++ != '.' ) continue; if( *str != '*' ) continue; *(str - 1) = '\0'; memmove( str, str + 1, strlen( str ) ); } str++; *str = '\0'; } /* Search all lines that satisfy from <= time <= to. */ if( params->lines == 0 ) { /* Just loop from oldest line to newest, and only do further * matching on those that satisfy the time requirements. */ for( line = wld->history_lines->head; line; line = line->next ) { if( line->time < params->from ) continue; if( line->time > params->to ) continue; recall_match_one_line( wld, params, line ); } } /* Search params->lines after from. */ if( params->lines > 0 ) { /* Just loop from oldest line to newest, and only do further * matching on the first X lines that are new enough. */ for( line = wld->history_lines->head; line; line = line->next ) { if( line->time < params->from ) continue; if( ++count > params->lines ) break; recall_match_one_line( wld, params, line ); } } /* Search -params->lines before from. */ if( params->lines < 0 ) { /* First, loop from newest line to oldest, stopping when we've * encountered X lines that are old enough. */ for( line = wld->history_lines->tail; line; line = line->prev ) { if( line->time > params->from ) continue; if( ++lines >= -params->lines ) break; } /* Don't run off the head of the queue. */ if( !line ) line = wld->history_lines->head; /* Now, loop from the last line found back forward in time, * inspecting X lines. */ for( ; line; line = line->next ) { if( line->time > params->from ) continue; if( ++count > lines ) break; recall_match_one_line( wld, params, line ); } } } /* Inspect a line that already matches the time criteria further. * If it matches the string criteria as well, recall it. */ static void recall_match_one_line( World *wld, Params *params, Line *line ) { Line *recalled; char *str; /* It got here, so it matched the time criteria. */ params->lines_inperiod++; /* Get the string without ANSI stuff. */ str = xmalloc( line->len + 1 ); strcpy_noansi( str, line->str ); /* If we have a search string, and it doesn't match, dump the line. */ if( params->search_str != NULL && !recall_match( str, params->search_str ) ) { free( str ); return; } /* We're good, recall it! */ recalled = line_create( str, -1 ); recalled->flags = LINE_MESSAGE; recalled->time = line->time; linequeue_append( wld->client_toqueue, recalled ); params->lines_matched++; } /* Match the (prepared) re against the string. * Return true on successful match, false otherwise. */ static int recall_match( char *line, char *re ) { char *l, *r; /* A continuation of the horrible re stuff. * Please just pretend it doesn't exist. */ while( *re != '\0' && *line != '\0' ) { l = line; r = re; while( ( *r == '.' || tolower( *l ) == *r ) && *l != '\0' ) l++, r++; if( *r != '\0' ) line++; else { re = r + 1; line = l; } } return *re == '\0'; } mooproxy-1.0.0/update-logs.sh0000644000175000017500000000430011525336516016012 0ustar marcelmmarcelm#! /bin/bash echo "=======================================================================" echo "Log update script for mooproxy" echo echo "In mooproxy 0.1.2, the location of logfiles has changed. This script is" echo "intended for users upgrading from mooproxy 0.1.1 (or older) to 0.1.2." echo "It will move the old logfiles to their new destinations." echo echo "Mooproxy < 0.1.2 logs files like this:" echo " logs/worldname - YYYY-MM-DD.log" echo echo "Mooproxy >= 0.1.2 logs files to a nested hierarchy, like this:" echo " logs/worldname/YYYY-MM/worldname - YYYY-MM-DD.log" echo echo "This script will create the appropriate directories, and move any" echo "existing logfiles to their correct location." echo echo "This script assumes your logs are in ~/.mooproxy/logs/." echo "Also, just in case:" echo echo " >> MAKE BACKUPS OF YOUR LOGS BEFORE RUNNING THIS SCRIPT. <<" echo "=======================================================================" echo echo "To continue, please type the phrase 'Yes, move my logs.'" echo -n "> " read CONFIRMATION if [ "${CONFIRMATION}" != "Yes, move my logs." ] then echo "Aborting." exit 0 fi echo echo "Moving logs..." echo echo cd ~/.mooproxy/logs || (echo "cd ~/.mooproxy/logs failed!"; exit 1) NOTMOVED="" for I in *.log do WORLD=$(echo "${I}" | sed 's/ - ....-..-..\.log$//') MONTH=$(echo "${I}" | sed 's/-..\.log$//' | grep -oe '....-..$') if [ ! -d "${WORLD}" ] then echo "mkdir ${WORLD}/" mkdir "${WORLD}" || (echo "mkdir ${WORLD} failed!"; exit 1) fi if [ ! -d "${WORLD}/${MONTH}" ] then echo "mkdir ${WORLD}/${MONTH}/" mkdir "${WORLD}/${MONTH}" || (echo "mkdir ${WORLD}/${MONTH} failed!"; exit 1) fi if [ -e "${WORLD}/${MONTH}/${I}" ] then echo "! ${WORLD}/${MONTH}/${I} already exists, not moving." NOTMOVED="true" else echo -n " " mv -iv "${I}" "${WORLD}/${MONTH}/" || (echo "mv -iv ${I} ${WORLD}/${MONTH}/ failed!"; exit 1) fi done echo echo "=========" echo "All done." if [ -n "${NOTMOVED}" ] then echo echo "The following filenames already existed in their new location," echo "and were therefore not moved:" echo ls -1 *.log echo echo "Please merge these files manually." else echo "All files were moved." fi mooproxy-1.0.0/dataflow.dia0000644000175000017500000001116611525336516015522 0ustar marcelmmarcelm]KsȑW04;A^({{:b( Zl|BE,DK#BY?1|2ɳ'q6I|?O_4O?"zwde뇪zO^RFU^xiGi}҃>]L_`UQo*eO1|}~[2_7VfyQ{̧S8Ҿ~)5OqѾS^&zH5:wcVJ=([|-~V׍NR=F"ɶ&mBJ~Bʗ\grtwnKNW4Q1ʩTR .)o*ڞ.8ʚYbE^b=1IU{$_X.d#z̫o*ktIܥq'Y5_ƹ|1{v\ a,bqygJlɏokC[-}[)_/)i,.}w׾r=i_nbʔ:l8ogym[cc.oTƶH"~Cq&=&ˋy\nbrExC|avHL|ڷE4Oe#+0k!y#S{l~ـg#y! Y^A=/Dx#pS]PH4HΡO|=o0o6"n_8ӭUjy4tφ8l=]- rKeSӛFJky=YY/ǭ%fz3o ݺC4=DEˮtB*jv5Bs`aVI,93S4ozc!U{֘'aN6ƵYʸ*-geY5o}/J4o8W,ziMUXRzSa7SN}4YdqǹfRr, -(P2Ȁpl -edM 8fnP:w@za&WU(CBpL–2_ZB内5!0\$'4_\!8&4p\X \޸ \0\ fˢЀG6?7@(p \{» `^8BkO jN ̸ʐ9r҂CZPևʵy -+p.6Wi%YuA7z@p.,U9 'H9:THPN' 'HNxH*/^QBJOG`L e$ "hz$$ ,Tϝ,`B0|LOH9lruC2D;Ey$r!֙E&py! %qO NAzn>9Mqo@baBb81xz9{=Ff@UҜ? 58g&m@H 4 55 5G ɷN`MX!p54}p}e:~}xTnL *|U Ư (&%4~t!"x/^ YDA7lkzWC/%CoJG s-"NMh$ˆqK{_</+ ]o[( $ߋC//?L)14d%衷рP19*`XY#/y~Yl|< Xh=Mh S E >["vKQ'Bu2 Sa6 Hh-Ngr\aWDtxB/!ݔȾ`i4S91 œBX8Dq 4š Na-NiX _$ 'N %Qg9Bط1ɉ lCVJՇqRW. ZcM)mdRa2AB E}3Q>턍oWgutJ dlךPP]bl؏#Ad60XucJj*MzRqq[3Y'04!^|SM;NYeV>ų>eQ=DN,TUnPW֙"]s&F4F m # #:[?ppgjv/xYv7΅礚=g6T&Qmhij*7$*Yoh5Am4vh|ZQ7 Waĸpr8- (*!TBuQqT5N6=gV:j=7XzuQ|qr9]pb JFD^:$o]`[B5/ /Ѐm;w;ueiX:n\k*d]|"9A8}gmfp7\Vtɶ8&K i\#$9WOF/L!S e t=+!fQ}iӗEu>OL Cؔ{Sޯ}c1QK;;k~I]ܶm-;W}qiP 2NdKdOQ\/1:wоjA6fA {O[ ~0T#CgA.:3#ԗt9Ss5ӝ >עGؘ=g"P7{ZZ<*DZQF)]8iB 6Š/l܋2l5>l>5ģ@y%E\qѥJʴ*$M4U/pOeh5HMdâ\Š{&wX{͉[Z۱ P|BeDZ9\-<ŪI3+SKO@׆+R !ē(i~ 9,.(җI?O$ߝW 4ь;2+.<%f/KS&DI髖(:ce:GFiB$UGģ3QIY 'WIzX9^U(/W1iKgmooproxy-1.0.0/recall.h0000644000175000017500000000157511525336516014660 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__RECALL #define MOOPROXY__HEADER__RECALL #include "world.h" /* The recall command. Parse argstr, and recall lines (or print an error * to the user). */ extern void world_recall_command( World *wld, char *argstr ); #endif /* ifndef MOOPROXY__HEADER__RECALL */ mooproxy-1.0.0/accessor.h0000644000175000017500000001072411525336516015214 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__ACCESSOR #define MOOPROXY__HEADER__ACCESSOR #include "config.h" #include "world.h" #define ASRC_FILE 0 /* Originator: configuration file */ #define ASRC_USER 1 /* Originator: user (/get and /set commands) */ /* The setter functions. These functions attempt to set a single key * to the given value, and report back errors if this fails. * * Arguments: World *wld, char *key, char *value, int src, char **err * wld: the world to change this key in * key: the name of the key to set * value: string-representation of the new value * src: indicate the originator of the request. One of ASRC_FILE, ASRC_USER * err: points to a char *, which will be set to an error string on * SET_KEY_BAD. If err is set, it should be free()d. * * Return values: * SET_KEY_OK: Everything OK, new value stored * SET_KEY_NF: The key was not found * SET_KEY_PERM: This key may not be written (by the given originator) * SET_KEY_BAD: The given value was not acceptable. err has been set. */ extern int aset_listenport( World *, char *, char *, int, char ** ); extern int aset_auth_hash( World *, char *, char *, int, char ** ); extern int aset_dest_host( World *, char *, char *, int, char ** ); extern int aset_dest_port( World *, char *, char *, int, char ** ); extern int aset_autologin( World *, char *, char *, int, char ** ); extern int aset_autoreconnect( World *, char *, char *, int, char ** ); extern int aset_commandstring( World *, char *, char *, int, char ** ); extern int aset_strict_commands( World *, char *, char *, int, char ** ); extern int aset_infostring( World *, char *, char *, int, char ** ); extern int aset_newinfostring( World *, char *, char *, int, char ** ); extern int aset_context_lines( World *, char *, char *, int, char ** ); extern int aset_buffer_size( World *, char *, char *, int, char ** ); extern int aset_logbuffer_size( World *, char *, char *, int, char ** ); extern int aset_logging( World *, char *, char *, int, char ** ); extern int aset_log_timestamps( World *, char *, char *, int, char ** ); extern int aset_easteregg_version( World *, char *, char *, int, char ** ); /* The getter functions. These functions get the value of a single given key, * and report back errors if this fails. * * Arguments: World *wld, char *key, char **value, int src * wld: the world to get this key from * key: the name of the key to get * value: pointer to the location to store the string representation of the * requested value. This will only be set on GET_KEY_OK. * The string should be free()d. * src: indicate the originator of the request. One of ASRC_FILE, ASRC_USER * * Return values: * GET_KEY_OK: Everything OK, * GET_KEY_NF: The key was not found * GET_KEY_PERM: This key may not be read (by the given originator) */ extern int aget_listenport( World *, char *, char **, int ); extern int aget_auth_hash( World *, char *, char **, int ); extern int aget_dest_host( World *, char *, char **, int ); extern int aget_dest_port( World *, char *, char **, int ); extern int aget_autologin( World *, char *, char **, int ); extern int aget_autoreconnect( World *, char *, char **, int ); extern int aget_commandstring( World *, char *, char **, int ); extern int aget_strict_commands( World *, char *, char **, int ); extern int aget_infostring( World *, char *, char **, int ); extern int aget_newinfostring( World *, char *, char **, int ); extern int aget_context_lines( World *, char *, char **, int ); extern int aget_buffer_size( World *, char *, char **, int ); extern int aget_logbuffer_size( World *, char *, char **, int ); extern int aget_logging( World *, char *, char **, int ); extern int aget_log_timestamps( World *, char *, char **, int ); extern int aget_easteregg_version( World *, char *, char **, int ); #endif /* ifndef MOOPROXY__HEADER__ACCESSOR */ mooproxy-1.0.0/ExampleConfig0000644000175000017500000000750211525336516015705 0ustar marcelmmarcelm# This is an example configuration file for mooproxy. # It contains all configurable options, all set to the same defaults that are # hardcoded into mooproxy. # # You need to change at least listenport and auth_hash before you can # use mooproxy. # The network port mooproxy listens on for client connections. listenport = -1 # Clients connecting to mooproxy have to provide a string # (such as "connect ") to authenticate # themselves. This setting contains a hash of this string. # # To change auth_hash, you need to specify the new hash as # well as the old literal authentication string, like so: # # /auth_hash "" # # You can generate a new hash by running mooproxy --md5crypt. # See the README for more details on authentication. auth_hash = "" # The hostname of the server to connect to. host = "" # The port to connect to on the server. port = -1 # If true, mooproxy will try to log you in after connecting to # the server. For this, it will use the client authentication # string (see auth_hash). autologin = false # If true, mooproxy will attempt to reconnect to the server # whenever the connection to the server is lost or fails to be # established. It uses exponential back-off. # # Mooproxy will not reconnect if establishing a connection # fails directly after the /connect command has been issued. # # Autoreconnect usually only makes sense if autologin is on. autoreconnect = false # Lines from the client starting with this string are # interpreted as commands (but see also: strict_commands). # This string does not need to be one character, it can be any # length (even empty). commandstring = "/" # If mooproxy receives a string from the client that starts # with the commandstring, but is not a valid command, this # setting determines what mooproxy will do. # # If true, mooproxy will complain that the command is invalid. # If false, mooproxy will pass the line through to the server # as if it was a regular line. strict_commands = true # Informational messages from mooproxy to the user are # prefixed by this string. # # Use the following sequences to get colours: # %b -> blue %g -> green %m -> magenta %w -> white # %c -> cyan %k -> black %r -> red %y -> yellow # Use uppercase to get the bold/bright variant of that colour. # Use %% to get a literal %. infostring = "%c%% " # Mooproxy prefixes this string to the "context history", # "possibly new lines", "certainly new lines" and "end of new # lines" messages. Setting this to something colourful might # make it easier to spot these messages. # # This string accepts the same colour sequences as infostring. # If this string is set to the empty string (""), mooproxy will # use the regular infostring for these messages. newinfostring = "%C%% " # When a client connects, mooproxy can reproduce lines from # history to the client, in order to provide the user with # context. This setting sets the number of reproduced lines. context_lines = 100 # The maximum amount of memory in KiB used to hold history # lines (lines you have already read) and new lines (lines you # have not yet read). # # If the amount of lines mooproxy receives from the server # while the client is not connected exceeds this amount of # memory, mooproxy will have to drop unread lines (but it will # still attempt to log those lines, see logbuffer_size). buffer_size = 4096 # The maximum amount of memory in KiB used to hold loggable # lines that have not yet been written to disk. # # If your disk space runs out, and the amount of unlogged # lines exceeds this amount of memory, new lines will NOT be # logged. logbuffer_size = 4096 # If true, mooproxy will log all lines from the server (and a # select few messages from mooproxy) to file. logging = true # If true, all logged lines are prefixed with a [HH:MM:SS] # timestamp. log_timestamps = true mooproxy-1.0.0/timer.c0000644000175000017500000000710311525336516014522 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #include #include "world.h" #include "timer.h" #include "log.h" #include "misc.h" #include "network.h" static void tick_second( World *, time_t ); static void tick_minute( World *, time_t ); static void tick_hour( World *, time_t ); static void tick_day( World *, time_t, long ); static void tick_month( World *, time_t ); static void tick_year( World *, time_t ); extern void world_timer_init( World *wld, time_t t ) { struct tm *ts; ts = localtime( &t ); wld->timer_prev_sec = ts->tm_sec; wld->timer_prev_min = ts->tm_min; wld->timer_prev_hour = ts->tm_hour; wld->timer_prev_day = ts->tm_year * 366 + ts->tm_yday; wld->timer_prev_mon = ts->tm_mon; wld->timer_prev_year = ts->tm_year; } extern void world_timer_tick( World *wld, time_t t ) { struct tm *ts; ts = localtime( &t ); /* Seconds */ if( wld->timer_prev_sec != ts->tm_sec ) tick_second( wld, t ); /* Minutes */ if( wld->timer_prev_min != ts->tm_min ) tick_minute( wld, t ); /* Hours */ if( wld->timer_prev_hour != ts->tm_hour ) tick_hour( wld, t ); /* Days */ if( wld->timer_prev_day != ts->tm_year * 366 + ts->tm_yday ) tick_day( wld, t, ts->tm_year * 366 + ts->tm_yday ); /* Months */ if( wld->timer_prev_mon != ts->tm_mon ) tick_month( wld, t ); /* Years */ if( wld->timer_prev_year != ts->tm_year ) tick_year( wld, t ); wld->timer_prev_sec = ts->tm_sec; wld->timer_prev_min = ts->tm_min; wld->timer_prev_hour = ts->tm_hour; wld->timer_prev_day = ts->tm_year * 366 + ts->tm_yday; wld->timer_prev_mon = ts->tm_mon; wld->timer_prev_year = ts->tm_year; } /* Called each time a second elapses. */ static void tick_second( World *wld, time_t t ) { set_current_time( t ); /* Add some tokens to the auth token bucket. */ world_auth_add_bucket( wld ); /* If we're waiting for a reconnect, and now is the time, do it. */ if( wld->server_status == ST_RECONNECTWAIT && wld->reconnect_at <= t ) world_do_reconnect( wld ); } /* Called each time a minute elapses. */ static void tick_minute( World *wld, time_t t ) { /* Try and sync written logdata to disk. The sooner it hits the * actual disk, the better. */ world_sync_logdata( wld ); /* If we're connected, decrease the reconnect delay every minute. */ if( wld->reconnect_delay != 0 && wld->server_status == ST_CONNECTED ) world_decrease_reconnect_delay( wld ); } /* Called each time an hour elapses. */ static void tick_hour( World *wld, time_t t ) { } /* Called each time a day elapses. */ static void tick_day( World *wld, time_t t, long day ) { Line *line; set_current_day( day ); line = world_msg_client( wld, "%s", time_string( t, "Day changed to %A %d %b %Y." ) ); line->flags = LINE_CHECKPOINT; wld->flags |= WLD_LOGLINKUPDATE; } /* Called each time a month elapses. */ static void tick_month( World *wld, time_t t ) { } /* Called each time a year elapses. */ static void tick_year( World *wld, time_t t ) { Line *line; line = world_msg_client( wld, "%s", time_string( t, "Happy %Y!" ) ); line->flags = LINE_CHECKPOINT; } mooproxy-1.0.0/Makefile0000644000175000017500000000134011525336516014673 0ustar marcelmmarcelmCFLAGS += -Wall -g LFLAGS = -Wall -lcrypt BINDIR = /usr/local/bin MANDIR = /usr/local/share/man/man1 OBJS = mooproxy.o misc.o config.o daemon.o world.o network.o command.o \ mcp.o log.o accessor.o timer.o resolve.o crypt.o line.o panic.o \ recall.o all: mooproxy mooproxy: $(OBJS) $(CC) $(LFLAGS) $(OBJS) -o mooproxy # strip mooproxy # If a header file changed, maybe some data formats changed, and all object # files using it must be recompiled. # Rather than mapping the actual header-usage relations, we just recompile # all object files when any header file changed. *.o: *.h clean: rm -f *.o core *.core mooproxy install: mooproxy install -d $(BINDIR) $(MANDIR) install mooproxy $(BINDIR) install mooproxy.1 $(MANDIR) mooproxy-1.0.0/crypt.h0000644000175000017500000000330311525336516014546 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #ifndef MOOPROXY__HEADER__CRYPT #define MOOPROXY__HEADER__CRYPT #include "world.h" /* Prompt the user for an authentication string, twice. Compare the strings, * and if they're not equal, complain. Otherwise, create an MD5 hash (with * random salt) from the string, and print that. */ extern void prompt_to_md5hash( void ); /* Check if str looks like an MD5 hash. This is not foolproof; it is intended * to protect from simple mistakes. * Returns 1 if str looks like a MD5 hash, and 0 if not. */ extern int looks_like_md5hash( char *str ); /* Checks if str is the correct authentication string for wld. * If wld->auth_literal exists, str is checked against this. * Otherwise, it's hashed and checked against wld->auth_hash. * If str matches, and wld->auth_literal does not exist, it is created. * Returns 1 if str matches, 0 if it doesn't. */ extern int world_match_authentication( World *wld, const char *str ); /* Matches str against md5hash. * Returns 1 if str matches, 0 if it doesn't. */ extern int match_string_md5hash( const char *str, const char *md5hash ); #endif /* ifndef MOOPROXY__HEADER__CRYPT */ mooproxy-1.0.0/misc.c0000644000175000017500000003446711525336516014352 0ustar marcelmmarcelm/* * * mooproxy - a buffering proxy for MOO connections * Copyright (C) 2001-2011 Marcel L. Moreaux * * 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; version 2 dated June, 1991. * * 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. * */ #define _GNU_SOURCE /* For vasprintf(). */ #include #include #include #include #include #include #include #include #include #include #include "global.h" #include "misc.h" #include "line.h" #include "panic.h" static time_t current_second = 0; static long current_daynum = 0; static char *empty_homedir = ""; /* Lookup table for the translation of codes like %W to ANSI sequences. */ static const char *ansi_sequences[] = { "", "\x1B[1;34m", "\x1B[1;36m", "", "", "", "\x1B[1;32m", "", "", "", "\x1B[1;30m", "", "\x1B[1;35m", "", "", "", "", "\x1B[1;31m", "", "", "", "", "\x1B[1;37m", "", "\x1B[1;33m", "", "", "", "", "", "", "", "", "\x1B[0;34m", "\x1B[0;36m", "", "", "", "\x1B[0;32m", "", "", "", "\x1B[0;30m", "", "\x1B[0;35m", "", "", "", "", "\x1B[0;31m", "", "", "", "", "\x1B[0;37m", "", "\x1B[0;33m", "" }; extern void *xmalloc( size_t size ) { void *ptr; int i; ptr = malloc( size ); if( ptr != NULL ) return ptr; /* Allocation failed. Retry a few times. */ for( i = 0; i < XMALLOC_OOM_RETRIES; i++ ) { sleep( 1 ); ptr = malloc( size ); if( ptr != NULL ) return ptr; } /* Still failed, give up. */ panic( PANIC_MALLOC, 0, (unsigned long) size ); return NULL; } extern void *xrealloc( void *ptr, size_t size ) { int i; ptr = realloc( ptr, size ); if( ptr != NULL || size == 0 ) return ptr; /* Reallocation failed. Retry a few times. */ for( i = 0; i < XMALLOC_OOM_RETRIES; i++ ) { sleep( 1 ); ptr = realloc( ptr, size ); if( ptr != NULL ) return ptr; } /* Still failed, give up. */ panic( PANIC_REALLOC, 0, (unsigned long) size ); return NULL; } extern char *xstrdup( const char *s ) { char *str; int i; str = strdup( s ); if( str != NULL ) return str; /* Strdup failed. Retry a few times. */ for( i = 0; i < XMALLOC_OOM_RETRIES; i++ ) { sleep( 1 ); str = strdup( s ); if( str != NULL ) return str; } /* Still failed, give up. */ panic( PANIC_STRDUP, 0, (unsigned long) strlen( s ) ); return NULL; } extern char *xstrndup( const char *s, size_t n ) { char *str; int i; str = strndup( s, n ); if( str != NULL ) return str; /* Strdup failed. Retry a few times. */ for( i = 0; i < XMALLOC_OOM_RETRIES; i++ ) { sleep( 1 ); str = strndup( s, n ); if( str != NULL ) return str; } /* Still failed, give up. */ panic( PANIC_STRDUP, 0, (unsigned long) n ); return NULL; } extern int xasprintf( char **strp, const char *fmt, ... ) { va_list argp; int i; va_start( argp, fmt ); i = xvasprintf( strp, fmt, argp ); va_end( argp ); return i; } extern int xvasprintf( char **strp, const char *fmt, va_list argp ) { int i, r; r = vasprintf( strp, fmt, argp ); if( r >= 0 ) return r; /* Vasprintf failed. Retry a few times. */ for( i = 0; i < XMALLOC_OOM_RETRIES; i++ ) { sleep( 1 ); r = vasprintf( strp, fmt, argp ); if( r >= 0 ) return r; } /* Still failed, give up. */ panic( PANIC_VASPRINTF, 0, 0 ); return 0; } extern void set_current_time( time_t t ) { current_second = t; } extern time_t current_time( void ) { return current_second; } extern void set_current_day( long day ) { current_daynum = day; } extern long current_day( void ) { return current_daynum; } extern char *get_homedir( void ) { char *tmp; tmp = getenv( "HOME" ); if( tmp ) return tmp; return empty_homedir; } extern char *read_one_line( char **str ) { char *s, b, *line; /* Nothing left? */ if( **str == '\0' ) return NULL; /* Search for the first \n or \0 */ s = *str; while( *s != '\n' && *s != '\0' ) s++; /* Back up the \n or \0, and replace it with \0 */ b = *s; *s = '\0'; /* Duplicate the first line */ line = xstrdup( *str ); /* Restore the backup */ *s = b; /* If it was a newline, advance str one further, so it points to * the next line. But it should not point beyond a \0. */ if( *s == '\n' ) s++; *str = s; return line; } extern char *get_one_word( char **str ) { char *s = *str, *word; /* Find the first non-whitespace character */ while( isspace( *s ) ) s++; /* If that's \0, we're out of words. */ if( *s == '\0' ) return NULL; word = s; /* Look for the first whitespace character or \0 */ while( !isspace( *s ) && *s != '\0' ) s++; /* If we have a whitespace character, replace it with \0 */ if( *s != '\0' ) *s++ = '\0'; *str = s; return word; } extern char *peek_one_word( char *str ) { char *s = str, *t, *word; /* Find the first non-whitespace character */ while( isspace( *s ) ) s++; /* If that's \0, we're out of words. */ if( *s == '\0' ) return NULL; t = s; /* Look for the first whitespace character or \0 */ while( !isspace( *s ) && *s != '\0' ) s++; word = xmalloc( s - t + 1 ); memcpy( word, t, s - t ); word[s - t] = '\0'; return word; } extern char *trim_whitespace( char *line ) { size_t r, s, t = strlen( line ); /* Find the first non-whitespace character */ for( s = 0; s <= t; s++ ) if( !isspace( line[s] ) ) break; /* Move everything from the first non-whitespace character to the * start of the string. */ for( r = s; r <= t; r++ ) line[r - s] = line[r]; /* Subtract number of skipped ws chars from the length */ t -= s; if( t ) { /* Search for the last non-whitespace character. */ for( t--; t >= 0; t-- ) if( !isspace( line[t] ) ) break; /* Found it, make t point to the (whitespace) character * after the last non-whitespace character. */ t++; } /* Chop off trailing whitespace. */ line[t] = '\0'; return line; } extern char *remove_enclosing_quotes( char *str ) { size_t i, len = strlen( str ); /* If the length is < 2, it can't contain two quotes. */ if( len < 2 ) return str; /* If the first character is not ' or ", we're done. */ if( str[0] != '"' && str[0] != '\'' ) return str; /* If the last character is not ' or ", we're done. */ if( str[len - 1] != '"' && str[len - 1] != '\'' ) return str; /* If the first and last characters are not equal, we're done. */ if( str[0] != str[len - 1] ) return str; /* Shift string one character forward, eliminating leading quote. */ for( i = 0; i < len - 2; i++ ) str[i] = str[i + 1]; /* Chop off trailing quote. */ str[len - 2] = '\0'; return str; } extern int true_or_false( const char *str ) { if( !strcasecmp( str, "true" ) ) return 1; if( !strcasecmp( str, "false" ) ) return 0; if( !strcasecmp( str, "yes" ) ) return 1; if( !strcasecmp( str, "no" ) ) return 0; if( !strcasecmp( str, "on" ) ) return 1; if( !strcasecmp( str, "off" ) ) return 0; if( !strcasecmp( str, "1" ) ) return 1; if( !strcasecmp( str, "0" ) ) return 0; return -1; } extern char *time_string( time_t t, const char *fmt ) { static char timestr_buf[TIMESTR_MAXSIMULT][TIMESTR_MAXLEN]; static int current_buf = TIMESTR_MAXSIMULT; struct tm *tms; current_buf++; if( current_buf >= TIMESTR_MAXSIMULT ) current_buf = 0; timestr_buf[current_buf][0] = '\0'; tms = localtime( &t ); strftime( timestr_buf[current_buf], TIMESTR_MAXLEN, fmt, tms ); return timestr_buf[current_buf]; } extern char *time_fullstr( time_t t ) { return time_string( t, "%A %d %b %Y, %T" ); } extern int buffer_to_lines( char *buffer, int offset, int read, Linequeue *q ) { char *eob = buffer + offset + read, *new; char *start = buffer, *end = buffer + offset; size_t len; /* eob: end of buffer. Points _beyond_ the last char of the buffer * new: Used temporarily to hold the copy of the current line * start: start of the current line * end: end of the current line */ /* Place the sentinel \n at the end of the buffer. */ *eob = '\n'; while( end < eob ) { /* Search for \n using linear search with sentinel. */ while( *end != '\n' ) end++; /* We reached the end of buffer without hitting a newline, * and there is more room in the buffer. Abort, and let * more data accumulate in the buffer. */ if( end == eob && end < buffer + NET_BBUFFER_LEN ) break; /* Reached end of buffer, and there's no space left in the * buffer. But, we consumed some lines from the buffer, * so we should shift the unconsumed data to the start * of buffer, and let more data accumulate in the buffer. */ if( end == eob && start != buffer ) break; /* If we got here, either we hit a \n, or the buffer is * filled entirely with one big line. Either way: process it */ /* Chop leading \r */ if( *start == '\r' ) start++; len = end - start; /* If the last character before \n is a \r (and it's not * before the start of string), chop it. */ if( end > start && *( end - 1 ) == '\r' ) len--; /* Copy the line out of the buffer, NUL-terminate it, * create a line, and queue it. */ new = xmalloc( len + 1 ); memcpy( new, start, len ); new[len] = '\0'; linequeue_append( q, line_create( new, len ) ); /* If the end-of-line is before end-of-buffer, advance end * so it points beyond the \n. */ if( end < eob ) end++; start = end; } /* If the start of the first line left in the buffer is not at the * start of the buffer, move it. */ if( start != buffer ) memmove( buffer, start, offset + read - ( start - buffer ) ); return offset + read - ( start - buffer ); } extern int flush_buffer( int fd, char *buffer, long *bffl, Linequeue *queue, Linequeue *tohist, int network_nl, char *prestr, char *poststr, int *errnum ) { long bfull = *bffl, len; Line *line; int wr; while( bfull > 0 || queue->count > 0 ) { /* Add any queued lines into the buffer. */ while( queue->count > 0 ) { /* Get the length of the next line. */ len = queue->head->len; /* If it's too large, truncate (or it'll never fit) */ if( len > NET_BBUFFER_LEN ) len = NET_BBUFFER_LEN; /* If the line doesn't fit, bail out. */ if( bfull + len > NET_BBUFFER_LEN ) break; /* First, write the prepend-string, if present. */ if( prestr ) { strcpy( buffer + bfull, prestr ); bfull += strlen( prestr ); } /* Now, get the line itself, and write to the buffer. */ line = linequeue_pop( queue ); memcpy( buffer + bfull, line->str, len ); bfull += len; /* Next up, the newline. */ if( network_nl ) buffer[bfull++] = '\r'; buffer[bfull++] = '\n'; /* And finally the append-string, if present. */ if( poststr ) { strcpy( buffer + bfull, poststr ); bfull += strlen( poststr ); } /* Move the line to a history queue, or destroy it. */ if( tohist == NULL || line->flags & LINE_NOHIST ) line_destroy( line ); else linequeue_append( tohist, line ); } /* Now we try to write as much of the buffer as possible */ wr = write( fd, buffer, bfull ); /* If there was an error, or if the socket can't take any * more data, abort. The higher layers will handle * errors or congestion. */ if( wr == -1 || wr == 0 ) { *bffl = bfull; if( errnum != NULL ) *errnum = errno; if( wr == -1 && errno != EAGAIN ) return 2; else return 1; } /* If only part of the buffer was written, move the unwritten * part of the data to the start of the buffer. */ if( wr < bfull ) memmove( buffer, buffer + wr, bfull - wr ); bfull -= wr; } *bffl = bfull; return 0; } extern char *parse_ansi_tags( char *str ) { char *parsed, *parsedstart; if( str == NULL ) return NULL; /* We allocate plenty, to make sure the parsed string fits. */ parsed = xmalloc( strlen( str ) * 8 + 8 ); parsedstart = parsed; while( *str != '\0' ) { if( *str != '%' ) { /* Don't copy control chars. */ if( *str < 32 ) str++; else *parsed++ = *str++; continue; } /* We got a %, advance to the next character. */ str++; if( *str == '\0' ) break; if( *str == '%' ) { *parsed++ = *str++; continue; } if( *str >= 'A' && *str <= 'z' ) { strcpy( parsed, ansi_sequences[*str - 'A'] ); parsed += strlen( ansi_sequences[*str - 'A'] ); str++; continue; } str++; } *parsed = '\0'; parsed = xrealloc( parsedstart, strlen( parsedstart ) + 1 ); return parsed; } extern int attempt_createdir( char *dirname, char **err ) { struct stat fileinfo; if( mkdir( dirname, S_IRUSR | S_IWUSR | S_IXUSR ) == 0 ) return 0; /* If there was an error other than "already exists", complain */ if( errno != EEXIST ) { *err = xstrdup( strerror( errno ) ); return 1; } /* The directory already existed. But is it really a dir? */ if( stat( dirname, &fileinfo ) == -1 ) { if( errno == ENOTDIR ) *err = xstrdup( "File exists" ); else *err = xstrdup( strerror( errno ) ); return 1; } /* Is it? */ if( !S_ISDIR( fileinfo.st_mode ) ) { *err = xstrdup( "File exists" ); return 1; } return 0; } extern long strcpy_noansi( char *dest, char *src ) { char *origdest = dest; for(;;) { /* Normal character, copy and continue. */ if( (unsigned char) *src >= ' ' || *src == '\t' ) { *dest++ = *src++; continue; } /* Escape char, skip ANSI sequence. */ if( *src == '\x1B' ) { src++; if( *src == '[' ) while( *src != '\0' && !isalpha( *src ) ) src++; if( *src != '\0' ) src++; continue; } /* Nul, terminate. */ if( *src == '\0' ) break; /* A character we don't deal with, skip over it. */ src++; } *dest = '\0'; return dest - origdest; } extern long strcpy_nobell( char *dest, char *src ) { char *origdest = dest; /* Copy the string, skipping over ASCII BELL. */ while( *src ) if( *src != 0x07 ) *dest++ = *src++; else src++; /* Nul-terminate. */ *dest = '\0'; return dest - origdest; } extern int strcmp_under( char *s, char *t ) { for(;;) { while( *s == '_' ) s++; while( *t == '_' ) t++; while( *s && *s == *t ) s++, t++; if( *s == '\0' && *t == '\0' ) return 0; if( *s != '_' && *t != '_' ) return *s - *t; } }