#include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef DEFAULT_PORT #define DEFAULT_PORT 5555 #endif #define set_lasterr( fmt, args... ) \ snprintf( lasterr, MAX_LASTERR, fmt, ##args ) #define reset_lasterr() \ set_lasterr( "Success!\n" ) #define set_lasterr_nullpointer( varname ) \ set_lasterr( "%s:%d: %s == NULL !\n", \ __FILE__, __LINE__, varname ) #define check_nullpointer( var, varname ) \ do { \ if( ! var ) { \ set_lasterr_nullpointer( varname ); \ return -1; \ } } while( 0 ); /* * Color Options * * L = Local * R = Remote * I = Input * F = Frame * T = Title * E = Error * U = User name window * MI = Info Message * ME = Error Message * MW = Warning Message */ /** Foreground **/ #define C_FG_L COLOR_MAGENTA #define C_FG_R COLOR_CYAN #define C_FG_I COLOR_WHITE #define C_FG_F COLOR_WHITE #define C_FG_T COLOR_YELLOW #define C_FG_E COLOR_WHITE #define C_FG_U COLOR_WHITE #define C_FG_MI COLOR_BLUE #define C_FG_ME COLOR_RED #define C_FG_MW COLOR_YELLOW /** Background **/ #define C_BG_L COLOR_BLACK #define C_BG_R COLOR_BLACK #define C_BG_I COLOR_BLUE #define C_BG_F COLOR_BLACK #define C_BG_T COLOR_BLACK #define C_BG_E COLOR_RED #define C_BG_U COLOR_BLUE #define C_BG_MI COLOR_BLACK #define C_BG_ME COLOR_BLACK #define C_BG_MW COLOR_BLACK /* Color Pairs */ #define CP_L 1 #define CP_R 2 #define CP_I 3 #define CP_U 4 #define CP_T 63 #define CP_F 62 #define CP_E 61 #define CP_MI 60 #define CP_ME 59 #define CP_MW 58 typedef struct { int h; int w; int x; int y; char *title; WINDOW *win; WINDOW *frame; } window_t; #define MAX_USER 64 #define MAX_NAME 64 typedef struct { struct sockaddr_in sa; int socket; window_t *win; char user[ MAX_USER + 1 ]; char name[ MAX_NAME + 1 ]; } host_t; /*** Global Variables ********************************************************/ int curses_mode=0; /* state var */ host_t h_local; host_t h_remote; int sockfd = -1; window_t w_remote = { 0, 0, 0, 0, NULL, NULL, NULL }; window_t w_local = { 0, 0, 0, 0, NULL, NULL, NULL }; window_t w_input = { 0, 0, 0, 0, NULL, NULL, NULL }; #define MAX_LASTERR 1024 char lasterr[ MAX_LASTERR + 1 ]; #define MAX_BUFFER 1024 char buffer[ 1024 + 1 ]; /****************************************************************************** * Function Declarations * *****************************************************************************/ void init_curses(); void quit_curses(); int destroy_window( window_t *win ); int window_set_title( window_t *win, const char *title ); int setup_window( window_t *win, int w, int h, int x, int y, int frame, const char* title, short color_pair_number ); void setup_windows(); void destroy_windows(); int init_net(); void quit_net(); int net_lookupaddr( const char *remote_name, struct sockaddr_in *remote_addr ); int net_connect( const char *remote_name, int port ); int net_server_init( int port ); int net_server_accept(); int net_senduser( host_t *host ); int delete_line_backwards( WINDOW *win ); int copy_line_buffer( WINDOW *win, char *buffer, int buffer_size ); int msg( window_t *win, short color_pair, char *prefix, char *fmt, ... ); int vmsg( window_t *win, short color_pair, char *prefix, char *fmt, va_list ap ); int msg_info( window_t *win, char *fmt, ... ); int msg_warn( window_t *win, char *fmt, ... ); int msg_error( window_t *win, char *fmt, ... ); int set_user( host_t *host, char *name ); int error_display( const char *msg ); void change_user(); int handle_remote_ch( int c ); int handle_local_ch( int c ); int loop(); void sighandler_winch( int i ); /*** Window Functions ********************************************************/ /** * Initialize and setup Curses */ void init_curses() { initscr(); /* Start curses mode */ cbreak(); /* Line buffering disabled, Pass on everything */ start_color(); /* try color mode */ noecho(); nonl(); curses_mode = 1; } /** * Quit curses mode */ void quit_curses() { curses_mode = 0; endwin(); } /** * Destroy window, freeing resources. This does NOT free the window * itself (in the case it was dynamicaly allocated). * * \param win pointer to window to be destroyed */ int destroy_window( window_t *win ) { reset_lasterr(); check_nullpointer( win, "win" ); if ( win->title ) { free( win->title ); win->title = NULL; } delwin( win->win ); win->win = NULL; delwin( win->frame ); win->frame = NULL; return 0; } /** * Set title and draw frame. Titles just work with windows with frames. * * \param win window to act * \param title new title * * \return 0 on success, error code otherwise */ int window_set_title( window_t *win, const char *title ) { int l = -1; char *s = NULL; attr_t attrs; short color_pair; reset_lasterr(); check_nullpointer( win, "win" ); check_nullpointer( title, "title" ); check_nullpointer( win->frame, "win->frame" ); { int x1, y1; int x2, y2; getmaxyx( win->frame, y1, x1 ); getmaxyx( win->win, y2, x2 ); if ( y1 == y2 ) { set_lasterr( "Titles just apply to windows with frames.\n" ); return -2; /* No frames? */ } } wattr_get( win->frame, &attrs, &color_pair, NULL ); /* Frame */ if ( has_colors() ) wcolor_set( win->frame, CP_F, NULL ); wborder( win->frame, 0, 0, 0, 0, 0, 0, 0, 0 ); /* window_set_title( win, win->title ) fails without this */ l = strlen( title ); s = malloc( l + 1 ); strcpy( s, title ); if ( win->title ) { free( win->title ); win->title = NULL; } win->title = s; if ( l + 4 >= win->w ) l = win->w - 4; s = malloc( l + 1 ); strncpy( s, win->title, l ); s[ l ] = '\0'; wattron( win->frame, A_BOLD ); if ( has_colors() ) wcolor_set( win->frame, CP_T, NULL ); mvwprintw( win->frame, 0, 2, "%s", s ); wattr_set( win->frame, attrs, color_pair, NULL ); wrefresh( win->frame ); free( s ); return 0; } /** * Setup window * * \param win pointer to window to be setup * \param w window width * \param h window height * \param x position in columns * \param y position in lines * \param frame if to use frames or not * \param title title to use (just when using frame) * \param color_pair_number color pair number to be assigned to this window * * \return 0 on success. */ int setup_window( window_t *win, int w, int h, int x, int y, int frame, const char* title, short color_pair_number ) { reset_lasterr(); check_nullpointer( win, "win" ); if ( ( w > 0 ) && ( w <= COLS ) ) win->w = w; else return -2; if ( ( h > 0 ) && ( h <= LINES ) ) win->h = h; else return -3; if ( ( x > -1 ) && ( x + w <= COLS ) ) win->x = x; else return -4; if ( ( y > -1 ) && ( y + h <= LINES ) ) win->y = y; else return -5; win->frame = newwin( h, w, y, x ); if ( frame ) win->win = subwin( win->frame, h - 2, w - 2, y + 1, x + 1 ); else win->win = subwin( win->frame, h, w, y, x ); if ( has_colors() ) { wcolor_set( win->frame, color_pair_number, NULL ); wcolor_set( win->win, color_pair_number, NULL ); wbkgdset( win->frame, ' ' | COLOR_PAIR( color_pair_number ) ); wbkgdset( win->win, ' ' | COLOR_PAIR( color_pair_number ) ); } win->title = NULL; window_set_title( win, title ); idlok( win->win, 1 ); scrollok( win->win, 1 ); touchwin( win->frame ); wrefresh( win->frame ); wclear( win->win ); wrefresh( win->win ); return 0 ; } /** * Setup windows */ void setup_windows() { int i=0, j=0; if ( has_colors() ) { init_pair( CP_L, C_FG_L, C_BG_L ); init_pair( CP_R, C_FG_R, C_BG_R ); init_pair( CP_I, C_FG_I, C_BG_I ); init_pair( CP_U, C_FG_U, C_BG_U ); init_pair( CP_F, C_FG_F, C_BG_F ); init_pair( CP_T, C_FG_T, C_BG_T ); init_pair( CP_E, C_FG_E, C_BG_E ); init_pair( CP_MI, C_FG_MI, C_BG_MI ); init_pair( CP_MW, C_FG_MW, C_BG_MW ); init_pair( CP_ME, C_FG_ME, C_BG_ME ); } i = ( LINES / 2 ); setup_window( &w_remote, COLS, i, 0, 0, 1, " Remote: ", CP_R ); if ( i * 2 < LINES ) j = i; else j = i - 1; setup_window( &w_local, COLS, j, 0, i, 1, " Local: ", CP_L ); setup_window( &w_input, COLS, 1, 0, LINES-1, 0, NULL, CP_I ); keypad( w_input.win, 1 ); idlok( w_input.win, 0 ); scrollok( w_input.win, 0 ); } /** * Destroy windows */ void destroy_windows() { destroy_window( &w_input ); destroy_window( &w_local ); destroy_window( &w_remote ); } /*** Network Functions *******************************************************/ /** * Initialize network connection * * \return 0 on success or the error code and set lasterr. */ int init_net() { h_local.socket = 0; strncpy( h_local.name, "Local", MAX_NAME ); strncpy( h_local.user, "", MAX_NAME ); h_local.win = NULL; h_remote.socket = 0; strncpy( h_remote.name, "Remote", MAX_NAME ); strncpy( h_remote.user, "", MAX_NAME ); h_remote.win = NULL; if ( ( h_local.socket = socket( AF_INET, SOCK_STREAM, 0 ) ) == -1 ) { set_lasterr( "socket: %s", strerror( errno ) ); return errno; } return 0; } /** * End network connection */ void quit_net() { if ( h_remote.socket ) close( h_remote.socket ); close( h_local.socket ); } /** * Lookup Address * * \param remote_name name to be resolved * \param remote_addr where to store results * * \return 0 on success, error code and lasterr set. */ int net_lookupaddr( const char *remote_name, struct sockaddr_in *remote_addr ) { int err; struct addrinfo hints, *res; reset_lasterr(); check_nullpointer( remote_name, "remote_name" ); check_nullpointer( remote_addr, "remote_addr" ); /* Set hits */ memset( &hints, 0, sizeof( hints ) ); hints.ai_family = PF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_flags |= AI_CANONNAME; /* Look up address */ if ( ( err = getaddrinfo( remote_name, NULL, &hints, &res ) ) != 0 ) { set_lasterr( "getaddrinfo: %s\n", gai_strerror( err ) ); return err; } else { memcpy( &(remote_addr->sin_addr), &((struct sockaddr_in *) res->ai_addr)->sin_addr, sizeof( struct in_addr ) ); freeaddrinfo( res ); } return 0; } /** * Connect to remote host * * \param remote_name where to connect * * \return 0 on success, error code and set lasterr otherwise */ int net_connect( const char *remote_name, int port ) { int err; reset_lasterr(); check_nullpointer( remote_name, "remote_name" ); /* setup remote socket address */ h_remote.sa.sin_family = AF_INET; h_remote.sa.sin_port = htons( port ); memset( &(h_remote.sa.sin_zero), '\0', 8 ); if ( ( err = net_lookupaddr( remote_name, &(h_remote.sa) ) ) != 0 ) return err; if ( connect( h_local.socket, (struct sockaddr *) &(h_remote.sa), sizeof(struct sockaddr) ) == -1 ) { set_lasterr( "connect: could not connect to %s:%d. %s\n", inet_ntoa( h_remote.sa.sin_addr ), ntohs( h_remote.sa.sin_port ), strerror( errno ) ); return errno; } sockfd = h_local.socket; return 0; } /** * Start server. * * \param port listen to this port * * \return 0 on success, error code and set lasterr otherwise */ int net_server_init( int port ) { int opt; h_local.sa.sin_family = AF_INET; h_local.sa.sin_port = htons( port ); h_local.sa.sin_addr.s_addr = INADDR_ANY; memset( &(h_local.sa.sin_zero), '\0', 8 ); opt = 1; if ( setsockopt( h_local.socket , SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int)) == -1 ) { set_lasterr( "setsockopt: %s", strerror( errno ) ); return errno; } if ( bind( h_local.socket, (struct sockaddr *) &(h_local.sa), sizeof(struct sockaddr)) == -1 ) { set_lasterr( "bind: %s", strerror( errno ) ); return errno; } if ( listen( h_local.socket, 10 ) == -1 ) { set_lasterr( "listen: %s", strerror( errno ) ); return errno; } return 0; } /** * Accept connections. Must be in server mode. * * \return 0 on success, error code and set lasterr otherwise */ int net_server_accept() { socklen_t sin_size = sizeof( struct sockaddr_in ); if ( ( h_remote.socket = accept( h_local.socket, (struct sockaddr *) &(h_remote.sa), &sin_size ) ) == -1 ) { set_lasterr( "accept: %s", strerror( errno ) ); return errno; } sockfd = h_remote.socket; return 0; } /** * Send username. * TCP message: * \0x1 NEW_NAME \n * * \param host host to get username. * * \param 0 on success */ int net_senduser( host_t *host ) { char *u; int l; reset_lasterr(); check_nullpointer( host, "host" ); check_nullpointer( host->user, "host->user" ); l = strlen( host->user ); u = malloc( l + 3 ); strcpy( u + 1, host->user ); u[ 0 ] = 0x1; u[ l + 1 ] = '\n'; u[ l + 2 ] = '\0'; if ( sockfd <= 0 ) { set_lasterr( "not connected.\n" ); return -1; } if ( send( sockfd, u, strlen( u ), 0 ) == -1 ) { set_lasterr( "send: %s\n", strerror( errno ) ); return errno; } return 0; } /*** Loop Functions **********************************************************/ /** * Delete a line from current cursor position to the begining * * \param win pointer to window where to act * * \return 0 on success */ int delete_line_backwards( WINDOW *win ) { int x, y; reset_lasterr(); check_nullpointer( win, "win" ); getyx( win, y, x ); while ( x >= 0 ) mvwdelch( win, y, x-- ); wrefresh( win ); return 0; } /** * Copy a line from window from the begining until the current cursor * position. A newline (\n) is added at the end. * * \param win pointer to window where to act * \param buffer where to store data * \param buffer_size buffer size * * \return 0 on success, error code otherwise. */ int copy_line_buffer( WINDOW *win, char *buffer, int buffer_size ) { int x, y, i; reset_lasterr(); check_nullpointer( win, "win" ); check_nullpointer( buffer, "buffer" ); getyx( win, y, x ); for ( i=0; i <= x; i++ ) buffer[ i % buffer_size ] = mvwinch( win, y, i ); wmove( win, y, x ); if ( i >= buffer_size ) i = buffer_size - 1; buffer[ i++ ] = '\n'; buffer[ i ] = '\0'; return 0; } /*** Helper Functions ********************************************************/ /** * Output message in window prefixed with [prefix], if provided * * \param win window to print message to * \param color_pair_number color pair number * \param prefix if provided, will be printed out in bold within [] * \param fmt format * \param ... arguments * * \return 0 on success */ int msg( window_t *win, short color_pair_number, char *prefix, char *fmt, ... ) { va_list ap; va_start( ap, fmt ); return vmsg( win, CP_MI, prefix, fmt, ap ); } int vmsg( window_t *win, short color_pair_number, char *prefix, char *fmt, va_list ap ) { attr_t attrs; short cp; if ( ! win || ! fmt ) return -1; wattr_get( win->win, &attrs, &cp, NULL ); wcolor_set( win->win, color_pair_number, NULL ); if ( prefix ) { wattron( win->win, A_BOLD ); wprintw( win->win, "[%s] ", prefix ); wattroff( win->win, A_BOLD ); } vwprintw( win->win, fmt, ap ); wattr_set( win->win, attrs, cp, NULL ); wrefresh( win->win ); return 0; } int msg_info( window_t *win, char *fmt, ... ) { va_list ap; va_start( ap, fmt ); return vmsg( win, CP_MI, "INFO", fmt, ap ); } int msg_warn( window_t *win, char *fmt, ... ) { va_list ap; va_start( ap, fmt ); return vmsg( win, CP_MW, "WARN", fmt, ap ); } int msg_error( window_t *win, char *fmt, ... ) { va_list ap; va_start( ap, fmt ); return vmsg( win, CP_ME, "ERROR", fmt, ap ); } /** * Set user name * * \param host peer to use * \param name user name * * \return 0 on success or the error code and set lasterr. */ int set_user( host_t *host, char *name ) { char m[ 256 ]; reset_lasterr(); check_nullpointer( host, "host" ); check_nullpointer( name, "name" ); check_nullpointer( host->win, "host->win" ); strncpy( host->user, name, MAX_USER ); snprintf( m, 256, " %s: %s@%s:%d ", host->name, host->user, inet_ntoa( host->sa.sin_addr ), ntohs( host->sa.sin_port ) ); if ( window_set_title( host->win, m ) ) return errno; msg_info( host->win, "%s changed user name to: `%s'\n", host->name, host->user ); return 0; } /** * Display error * * \return 0 on success */ int error_display( const char *msg ) { char *om=NULL; if ( ! msg ) return -1; if ( curses_mode ) { int w, h, x, y; window_t win; char lm[] = "Press any key to continue..."; int l = strlen( lm ); w = ( COLS < 20 ) ? COLS : ( COLS - 10 ); x = ( COLS - w ) / 2; h = ( LINES < 4 ) ? LINES : ( LINES - 4 ); y = ( LINES - h ) / 2; om = malloc( strlen( msg ) + 1 ); strcpy( om, msg ); /* setup_window alter lasterr */ if ( ( errno = setup_window( &win, w, h, x, y, 1, " Error: ", CP_E ) ) != 0 ) { free( om ); return errno; } wprintw( win.win, "%s", om ); free( om ); wattron( win.win, A_BOLD ); mvwprintw( win.win, h - 3, ( w - l ) / 2, lm ); wattroff( win.win, A_BOLD ); wrefresh( win.frame ); wgetch( win.win); destroy_window( &win ); touchwin( w_local.frame ); touchwin( w_remote.frame ); touchwin( w_input.frame ); wrefresh( w_local.frame ); wrefresh( w_remote.frame ); wrefresh( w_input.frame ); } else fprintf( stderr, "%s", msg ); return 0; } void change_user() { char user[ MAX_USER + 1 ] = ""; window_t win; int w, x, c; w = ( 20 > COLS ) ? COLS : 20; x = ( COLS - w ) / 2; setup_window( &win, w, 3, x, ( LINES - 3 ) / 2 , 1, " Enter new user name: ", CP_U ); keypad( win.win, 1 ); while( 1 ) { c = wgetch( win.win ); if ( c == 13 ) /* Enter */ break; else if ( c == 263 ) /* Backspace */ { waddch( win.win, '\b' ); wdelch( win.win ); wrefresh( win.win ); } else { waddch( win.win, c ); wrefresh( win.win ); } } copy_line_buffer( win.win, user, MAX_BUFFER ); user[ strlen( user ) - 1 ] = '\0'; if ( user[ 0 ] != '\0' ) { set_user( &h_local, user ); net_senduser( &h_local ); } destroy_window( &win ); touchwin( w_local.frame ); touchwin( w_remote.frame ); touchwin( w_input.frame ); wrefresh( w_local.frame ); wrefresh( w_remote.frame ); wrefresh( w_input.frame ); } /** * Handle incoming remote char and take action * * \param c char from the net * * \return 1 on success, 0 to stop processing, -1 to send the line */ int handle_remote_ch( int c ) { static char user[ MAX_USER + 1 ] = ""; static int capture_user = 0, capture_pos = 0; switch ( c ) { case 0x1: capture_user = 1; capture_pos = 0; break; case '\b': if ( capture_user ) break; waddch( w_remote.win, c ); wdelch( w_remote.win ); wrefresh( w_remote.win ); break; case '\n': if ( capture_user ) { user[ capture_pos ] = '\0'; set_user( &h_remote, user ); capture_user = 0; break; } default: if ( ! capture_user ) { waddch( w_remote.win, c ); wrefresh( w_remote.win ); } else if ( capture_pos < MAX_USER ) user[ capture_pos++ ] = c; } return 1; } /** * Handle incoming local (keyboard) char and take action * * \param c char from keyboard * * \return 1 on success, 0 to stop processing, -1 to send the line */ int handle_local_ch( int c ) { switch ( c ) { case 4: /* ^D */ return 0; case 13: /* Enter */ wclear( w_input.win ); wrefresh( w_input.win ); return handle_local_ch( '\n' ); case 21: /* ^U */ delete_line_backwards( w_input.win ); break; case 263: /* Backspace */ c = '\b'; waddch( w_input.win, c ); wdelch( w_input.win ); wrefresh( w_input.win ); waddch( w_local.win, c ); wdelch( w_local.win ); wrefresh( w_local.win ); return c; case 266: /* F2 */ change_user(); break; default: waddch( w_input.win, c ); wrefresh( w_input.win ); waddch( w_local.win, c ); wrefresh( w_local.win ); return c; } return -1; } /** * Main loop. Multiplex I/O and present the results * * \return 0 on success, error code and set lasterr otherwise */ int loop() { int c; char byte; int sr; fd_set readfds, rds; reset_lasterr(); if ( sockfd <= 0 ) { set_lasterr( "not connected.\n" ); return -1; } FD_ZERO( &readfds ); FD_SET( 0, &readfds ); FD_SET( sockfd, &readfds ); while ( 1 ) { rds = readfds; sr = select( sockfd + 1, &rds, NULL, NULL, NULL ); if ( sr < 0 ) { set_lasterr( "select: %s", strerror( errno ) ); return errno; } if ( FD_ISSET( sockfd, &rds ) ) { if ( ( sr = recv( sockfd, &byte, 1, 0 ) ) == -1 ) { set_lasterr( "recv: %s\n", strerror( errno ) ); return errno; } else if ( sr == 0 ) { set_lasterr( "Connection closed by peer.\n" ); return errno; } else { handle_remote_ch( byte ); } } if ( FD_ISSET( 0, &rds ) ) { c = wgetch( w_input.win ); c = handle_local_ch( c ); if ( c == 0 ) return 0; /* finish connection */ else if ( c > 0 ) { byte = c; /* send */ if ( sr == send( sockfd, &byte, 1, 0 ) == -1 ) { set_lasterr( "send: %s\n", strerror( errno ) ); return errno; } } } } return 0; } int main( int argc, char *argv[] ) { int err = 0, connect_mode=0, stop=0; int port = DEFAULT_PORT; char *user = NULL; if ( argc < 2 ) { printf( "Usage:\n\t%s [port]\n", argv[ 0 ] ); return -1; } /* if user name in format raXXXXXX, * use 3000 + XXXXXX as port */ user = getenv( "USER" ); if ( user && ( strlen( user ) == 8 ) && ( strncmp( user, "ra", 2 ) == 0 ) ) port = 3000 + atoi( user + 2 ) % 10000; /* use port from cmdline if provided */ if ( argc == 3 ) { int p = atoi( argv[ 2 ] ); if ( p ) port = p; } init_curses(); setup_windows(); init_net(); h_local.win = &w_local; h_remote.win = &w_remote; /* * try to connect, if not possible, be the server */ if ( ( connect_mode = net_connect( argv[ 1 ], port ) ) != 0 ) { /* impossible to connect */ msg_warn( &w_local, lasterr ); msg_info( &w_local, "Switching to server mode.\n" ); /* be the server */ if ( net_server_init( port ) ) { error_display( lasterr ); stop = 1; } } strncpy( h_local.user, user, MAX_USER ); if ( ! stop ) do { if ( connect_mode ) /* server, wait for clients */ { while ( net_server_accept() ); msg_info( &w_remote, "Connection from %s:%d.\n", inet_ntoa( h_remote.sa.sin_addr ), ntohs( h_remote.sa.sin_port ) ); } else msg_info( &w_remote, "Connected to %s:%d.\n", inet_ntoa( h_remote.sa.sin_addr ), ntohs( h_remote.sa.sin_port ) ); /* Reassign user and update IP/Port */ if ( set_user( &h_local, h_local.user ) ) { error_display( lasterr ); stop = 1; } net_senduser( &h_local ); /* Main loop */ err = loop(); close( sockfd ); if ( connect_mode == 0 ) { /* Client mode. Display error and stop */ if ( err ) error_display( lasterr ); else { msg_warn( &w_local, "Closed connection to %s@%s:%d.\n", h_remote.user, inet_ntoa( h_remote.sa.sin_addr ), ntohs( h_remote.sa.sin_port ) ); msg_info( &w_local, "Press any key to exit...\n" ); wgetch( w_local.win ); } break; } else { /* Server mode. Display warning and continue */ msg_warn( &w_remote, "Closed connection to %s@%s:%d.\n", h_remote.user, inet_ntoa( h_remote.sa.sin_addr ), ntohs( h_remote.sa.sin_port ) ); } /* reset connection resources */ sockfd = -1; strncpy( h_remote.user, "", MAX_NAME ); h_remote.socket = 0; } while ( connect_mode ); /* Just loop in server mode */ quit_net(); quit_curses(); return err; } /* author: Gustavo Sverzut Barbieri (http://www.gustavobarbieri.com.br) */