%% options copyright owner = Dirk Krause copyright year = 2012-2014 license = bsd %% header #include "dk3all.h" #include "dkt-version.h" #include "dk3sock.h" /** Job structue for dknet. */ typedef struct { dk3_app_t *app; /**< Application structure. */ dkChar const * const *msg; /**< Localized message texts. */ dk3_sto_t *ssess; /**< Storage for sessions. */ dk3_sto_it_t *isess; /**< Iterator through sessions storage. */ dk3_sto_t *sacl; /**< Storage for acceptable clients. */ dk3_sto_it_t *iacl; /**< Iterator for acceptable clients storage. */ dkChar const *hn; /**< Host name to connect to. */ dkChar const *fn; /**< File name to send or receive. */ dkChar const *rfn; /**< Real file name (after expansion). */ FILE *fipo; /**< Input file. */ unsigned long flush; /**< Number of network reads per file flush. */ int cmd; /**< Command to execute, see @ref dknetcmd. */ int exval; /**< Exit status code. */ int clmax; /**< Maximum number of clients. */ int clcur; /**< Current number of connected clients. */ int stt; /**< Flag: Start transfer. */ int blog; /**< Listen backlog. */ int llerr; /**< Log level for stderr. */ unsigned short nport; /**< Port number to connect to. */ } dknet_job; /** One session connection. */ typedef struct { dk3_socket_t ss; /**< Session socket. */ int ec; /**< Error code (0 on success). */ } dknet_session; /** @defgroup dknetcmd Command to execute during dknet job. */ /**@{*/ /** Operation: Receive data. */ #define DKNET_CMD_RECEIVE 1 /** Operation: Send data. */ #define DKNET_CMD_SEND 2 /** Operation: Show help text. */ #define DKNET_CMD_HELP 4 /** Operation: Show version number. */ #define DKNET_CMD_VERSION 8 /** Operation: Show license terms. */ #define DKNET_CMD_LICENSE 16 /**@}*/ /** @defgroup dknetexitcodes Exit codes for dknet. */ /**@{*/ /** Exit status code: No error. */ #define DKNET_EXIT_OK 0 /** Exit status code: Error occured, no further information. */ #define DKNET_EXIT_ERROR_ANY 1 /** Exit status code: Not enough memory. */ #define DKNET_EXIT_ERROR_MEMORY 2 /** Exit status code: Error in command line arguments. */ #define DKNET_EXIT_ERROR_OPTIONS 3 /** Exit status code: Failed to open/read/write input or output file. */ #define DKNET_EXIT_ERROR_IO 4 /** Exit status code: Failed to initialize WinSock. */ #define DKNET_EXIT_ERROR_WINSOCK 5 /** Exit status code: Error in network operation. */ #define DKNET_EXIT_ERROR_NETWORK 6 /**@}*/ %% module #include "dknet.h" $!trace-include #if DK3_HAVE_SELECT || DK3_ON_WINDOWS /** Version number. */ static dkChar const dknet_version[] = { DKT_VERSION }; /** Keywords used by the program, not localized. */ static dkChar const * const dknet_noloc[] = { $!string-table macro=dkT # # 0: Program group name # dkt-3 # # 1: String table name # dknet.str # # 2: Help file name. # dknet.txt # # 3: Program name. # dknet # # 4: Space # # # 5: Keyword "unlimited" # unlimited $!end }; /** Message texts used by the module, localized. */ static dkChar const * const dknet_loc[] = { $!string-table file=dknet.str,macro=dkT # # 0 ERROR: Just one file name allowed! # Just one file name allowed! # # 1 2 ERROR: Not a number # Not a number: " "! # # 3 4 ERROR: Range overflow! # Range overflow (not in 1...65535): " "! # # 5 ERROR: Remote port must not be 0! # Remote port must not be 0! # # 6 ERROR: Port number already set! # Port number already set! # # 7 ERROR: Host name already set! # Host name already set! # # 8 ERROR: Number of clients must not be negative! # Number of clients must not be negative! # # 9 10 ERROR: Unknown option: "..."! # Unknown option: " "! # # 11 ERROR: Incompatible actions! # Incompatible actions! # # 12 # Option "-r" requires two arguments (Host name and port number)! # # 13 ERROR: Option "..." requires an argument! # Option "-s" requires an argument (port number)! # # 14 ERROR: Option "..." requires an argument! # Option "-n" requires an argument (maximum number of clients)! # # 15 ERROR: Option "..." requires an argument! # Option "-a" requires an argument (allowed client)! # # 16 ERROR: Can not combine send/receive with help/version/license! # Can not combine send/receive with help/version/license! # # 17 ERROR: Can not send and receive at same time! # Can not send and receive at same time! # # 18 ERROR: No action was choosen! # No action was choosen! # # 19 ERROR: Use dknet --help for help! # Use "dknet --help" for help! # # 20 PROGRESS: Creating listener socket set. # Creating listener socket set. # # 21 PROGRESS: Finished creating listener socket set. # Finished creating listener socket set. # # 22 PROGRESS: Waiting for client connections. # Waiting for client connections. # # 23 # Finished waiting for client connections, closing listener socket set. # # 24 # Listener socket set closed. # # 25 # Sending data. # # 26 # Finished sending data. # # 27 # Receiving data. # # 28 # Finished receiving data. # # 29 # Failed to read initial byte from client! # # 30 31 # Error while sending to socket , disconnected! $!end }; /** Help text to show if no help file is found. */ dkChar const * const dknet_help_text[] = { $!text file=dknet.txt,macro=dkT NAME dknet - Dirk Krause's network tool SYNOPSIS dknet -s [-n ] [-a
[/]] [] dknet -r [-t] [-f ] [] dknet -h dknet -v dknet -L DESCRIPTION The dknet program transfers data over a network. One sender process can send data to multiple recipient processes, each input data block is sent to each recipient sequently (no broadcast/multicast involved). The first of the commands above runs the program as server and provides the specified file to the recipients. If no file is specified, data from standard input is used. The -s option configures the port to listen for connection attempts. The -n option specifies the maximum number of clients (default: 1). 0 or the "unlimited" keyword indicates an unlimited number of clients, the last client connecting has to use the -t option to start the transfer. The -a option can be used to restrict clients to IP addresses, this option can be used multiple times. The second command runs the program as recipient and connects to the specified host and port to receive data. Data is saved to the specified file. If no file is specified, data is written to standard output. The -t option can be used at the last client connecting to start the transfer immediately. The -f option can be used to enforce an output file flush after each n-th block. OPTIONS -s runs the program as sender. -n specifies the maximum number of clients. -a
[/] restricts clients to IP addresses. -r runs the program as recipient. -t tells the sender to start the transfer immediately. -f flushes output buffers after each n-th block. -h shows the help text. -v shows the version number. -L shows the license conditions. RETURN VALUE Exit status code 0 indicates success. Positive exit status codes indicate errors. NOTES Previous versions of this program used "-r host:port". This version uses "-r host port" (the -r option takes two arguments). This change was necessary as the colon is used in IPv6 addresses so we can no longer use it to separate address and port number. AUTHOR Dirk Krause COPYRIGHT AND LICENSE Please run dknet -L to see the license conditions. SEE ALSO http://dktools.sourceforge.net $!end }; /** License conditions for dknet. */ dkChar const * const dknet_license_text[] = { $!text macro=dkT License conditions ================== Copyright (c) 2012-2013, Dirk Krause All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder(s) nor the names of contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. $!end }; /** Long options keywords. */ static dkChar const * const dknet_long_options[] = { $!string-table macro=dkT r$eceive se$nd h$elp v$ersion l$icense-terms recv c$lients a$llow st$art $!end }; /** Initialize job structure. @param job Job structure to initialize. */ static void dknet_job_init(dknet_job *job) { job->app = NULL; job->msg = NULL; job->ssess = NULL; job->isess = NULL; job->sacl = NULL; job->iacl = NULL; job->hn = NULL; job->fn = NULL; job->rfn = NULL; job->fipo = NULL; job->cmd = 0; /* Default: Read data from network. */ job->exval = 1; /* Set to 0 later on success. */ job->flush = 0UL; job->clmax = 1; job->clcur = 0; job->stt = 0; job->blog = 5; job->llerr = 0; job->nport = 0; } /** Job cleanup. @param job Job structure to clean up. */ static void dknet_job_cleanup(dknet_job *job) { } /** Clean up data structures set up during and after command line processing. */ static void dknet_job_cleanup_command_line_processing(dknet_job *job) { void *pp; /* Release all acceptable clients. */ if(job->sacl) { if(job->iacl) { dk3sto_it_reset(job->iacl); while(NULL != (pp = dk3sto_it_next(job->iacl))) { dk3_delete(pp); } dk3sto_it_close(job->iacl); job->iacl = NULL; } dk3sto_close(job->sacl); job->sacl = NULL; } /* Release host and file name. */ dk3_release(job->hn); dk3_release(job->fn); } /** Save file name in job. @param job Job structure. @param fn File name to store. @return 1 on success, 0 on error. */ static int dknet_save_filename(dknet_job *job, dkChar const *fn) { int back = 0; if(!(job->fn)) { job->fn = dk3str_dup_app(fn, job->app); if(job->fn) { back = 1; } else { job->exval = DKNET_EXIT_ERROR_MEMORY; } } else { /* ERROR: Already have file name! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 0); job->exval = DKNET_EXIT_ERROR_OPTIONS; } return back; } /** Save port number to send data to or receive data from. @param job Job structure. @param ptr Port number as text. @return 1 on success, 0 on error. */ static int dknet_save_portnumber(dknet_job *job, dkChar const *ptr) { int back = 0; unsigned u; unsigned short us; $? "+ dknet_save_portnumber \"%s\"", ptr if(1 == dk3sf_sscanf3(ptr, dkT("%u"), &u)) { us = (unsigned short)u; #if VERSION_BEFORE_20140809 if(u == (unsigned)us) #else if(u <= (unsigned)(DK3_US_MAX)) #endif { if(us > 0) { if(0 == job->nport) { job->nport = us; back = 1; } else { $? "! already defined" /* ERROR: Port number already set! */ job->exval = DKNET_EXIT_ERROR_OPTIONS; dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 6); } } else { $? "! 0" /* ERROR: Remote port must not be 0! */ job->exval = DKNET_EXIT_ERROR_OPTIONS; dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 5); } } else { $? "! overflow" /* ERROR: Range overflow! */ dk3app_log_3(job->app, DK3_LL_ERROR, job->msg, 3, 4, ptr); job->exval = DKNET_EXIT_ERROR_OPTIONS; } } else { $? "! not a number" /* ERROR: Not a number! */ dk3app_log_3(job->app, DK3_LL_ERROR, job->msg, 1, 2, ptr); job->exval = DKNET_EXIT_ERROR_OPTIONS; } $? "- dknet_save_portnumber %d", back return back; } /** Save host name and port number to receive data from. @param job Job structure. @param p1 Host name. @param p2 Port number. @return 1 on success, 0 on error. */ static int dknet_save_host_and_port(dknet_job *job, dkChar const *p1, dkChar const *p2) { int back = 0; $? "+ dknet_save_host_and_port \"%s\" \"%s\"", p1, p2 if(!(job->hn)) { job->hn = dk3str_dup_app(p1, job->app); if(job->hn) { back = dknet_save_portnumber(job, p2); } else { $? "! memory" job->exval = DKNET_EXIT_ERROR_MEMORY; } } else { $? "! already set" /* ERROR: Host name already defined! */ job->exval = DKNET_EXIT_ERROR_OPTIONS; dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 7); } $? "- dknet_save_host_and_port %d", back return back; } /** Save maximum number of clients. @param job Job structure. @param ptr Text containing the number. @return 1 on success, 0 on error. */ static int dknet_save_max_clients(dknet_job *job, dkChar const *ptr) { int back = 0; int mx = 0; $? "+ dknet_save_max_clients \"%s\"", TR_STR(ptr) if(1 == dk3sf_sscanf3(ptr, dkT("%d"), &mx)) { if(mx >= 0) { job->clmax = mx; back = 1; } else { $? "! negative number" /* ERROR: Negative number! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 8); } } else { $? ". strcmp() != 0" if(0 == dk3str_cmp(dknet_noloc[5], ptr)) { job->clmax = 0; back = 1; } else { $? "! not unlimited" /* ERROR: Not a number! */ dk3app_log_3(job->app, DK3_LL_ERROR, job->msg, 1, 2, ptr); } } $? "- dknet_save_max_clients %d", back return back; } /** Save an allowed client. @param job Job structure. @param ptr Text containing address/mask. @return 1 on success, 0 on error. */ static int dknet_save_allowed_client(dknet_job *job, dkChar const *ptr) { dk3_peer_allowed_t allowed_client; dk3_peer_allowed_t *np; int back = 0; $? "+ dknet_save_allowed_client \"%s\"", TR_STR(ptr) if(!(job->sacl)) { job->sacl = dk3sto_open_app(job->app); if(job->sacl) { dk3sto_set_comp(job->sacl, dk3socket_compare_peer, 0); } } if(!(job->iacl)) { if(job->sacl) { job->iacl = dk3sto_it_open(job->sacl); } } if((job->sacl) && (job->iacl)) { if(dk3socket_dkchar_set_peer(&allowed_client, ptr, NULL, job->app)) { np = dk3_new_app(dk3_peer_allowed_t,1,job->app); if(np) { dk3mem_cpy( (void *)np, (void *)(&allowed_client), sizeof(dk3_peer_allowed_t) ); if(dk3sto_add(job->sacl, (void *)np)) { back = 1; } else { $? "! memory" dk3_delete(np); job->exval = DKNET_EXIT_ERROR_MEMORY; dk3app_log_i1(job->app, DK3_LL_ERROR, 9); } } else { $? "! memory" job->exval = DKNET_EXIT_ERROR_MEMORY; dk3app_log_i1(job->app, DK3_LL_ERROR, 9); } } else { $? "! not a peer description" job->exval = DKNET_EXIT_ERROR_OPTIONS; } } else { $? "! memory" /* ERROR: Memory! */ dk3app_log_i1(job->app, DK3_LL_ERROR, 9); job->exval = DKNET_EXIT_ERROR_MEMORY; } $? "- dknet_save_allowed_client %d", back return back; } /** Process command line arguments. As -r takes two arguments and -a can occur multiple times we have to do this manually. @param job Job structure. @return 1 on success, 0 on error. */ static int dknet_process_command_line_arguments(dknet_job *job) { dkChar const * const *xargv; /* Command line arguments array. */ dkChar const * const *lfdptr; /* Current command line argument. */ dkChar const *ptr; /* Current command line argument. */ dkChar const *p1; /* First argument to -r . */ dkChar const *p2; /* Second argument to -r . */ unsigned long ul; /* Temporary scan result. */ int xargc; /* Number of command line arguments. */ int i; /* Current cmd line argument index. */ int act; /* Action to take. */ int cmdhvl; /* Help, version, license. */ int cmdrun; /* Send, receive. */ int back = 1; $? "+ dknet_process_command_line_arguments" job->exval = DKNET_EXIT_ERROR_OPTIONS; cmdhvl = DKNET_CMD_HELP | DKNET_CMD_VERSION | DKNET_CMD_LICENSE; cmdrun = DKNET_CMD_SEND | DKNET_CMD_RECEIVE; xargc = dk3app_get_argc(job->app); xargv = dk3app_get_argv(job->app); lfdptr = xargv; lfdptr++; i = 1; while(i < xargc) { act = 0; ptr = *lfdptr; $? ". process %d \"%s\"", i, ptr if(dkT('-') == *ptr) { $? ". option" ptr++; switch(*ptr) { case dkT('-'): { $? ". long option" ptr++; act = dk3str_array_abbr(dknet_long_options, ptr, dkT('$'), 0); if(-1 < act) { act += 1; if(6 == act) act = 1; } else { back = 0; /* ERROR: Unknown option! */ job->exval = DKNET_EXIT_ERROR_OPTIONS; ptr--; ptr--; dk3app_log_3(job->app, DK3_LL_ERROR, job->msg, 9, 10, ptr); } ptr = NULL; } break; case dkT('r'): { $? ". receive" act = 1; ptr++; if(!(*ptr)) ptr = NULL; } break; case dkT('s'): { $? ". send" act = 2; ptr++; if(!(*ptr)) ptr = NULL; } break; case dkT('h'): { $? ". help" act = 3; } break; case dkT('v'): { $? ". version" act = 4; } break; case dkT('L'): { $? ". license" act = 5; } break; case dkT('n'): { $? ". max clients" act = 7; ptr++; if(!(*ptr)) ptr = NULL; } break; case dkT('a'): { $? ". allowed client" act = 8; ptr++; if(!(*ptr)) ptr = NULL; } break; case dkT('t'): { $? ". transfer" act = 9; } break; case dkT('f'): { act = 10; ptr++; if(!(*ptr)) ptr = NULL; } break; default: { $? "! unknown option" back = 0; /* ERROR: Unknown option! */ job->exval = DKNET_EXIT_ERROR_OPTIONS; ptr--; dk3app_log_3(job->app, DK3_LL_ERROR, job->msg, 9, 10, ptr); } break; } } else { $? ". file name" if(!dknet_save_filename(job, ptr)) { back = 0; } } switch(act) { case 1: { /* receive */ if(0 != job->cmd) { /* ERROR: Incompatible actions! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 11); back = 0; job->exval = DKNET_EXIT_ERROR_OPTIONS; } p1 = NULL; p2 = NULL; job->cmd |= DKNET_CMD_RECEIVE; if(!(ptr)) { i++; lfdptr++; if(i < xargc) { ptr = *lfdptr; } } if(ptr) { p1 = ptr; i++; lfdptr++; if(i < xargc) { p2 = *lfdptr; if(!dknet_save_host_and_port(job, p1, p2)) back = 0; } else { $? "! second argument missing" back = 0; /* ERROR: Two arguments required */ dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 12); job->exval = DKNET_EXIT_ERROR_OPTIONS; } } else { $? "! first argument missing" back = 0; /* ERROR: Argument expected! */ job->exval = DKNET_EXIT_ERROR_OPTIONS; dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 12); } } break; case 2: { /* send */ if(0 != job->cmd) { /* ERROR: Incompatible action! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 11); back = 0; job->exval = DKNET_EXIT_ERROR_OPTIONS; } job->cmd |= DKNET_CMD_SEND; if(!(ptr)) { i++; lfdptr++; if(i < xargc) { ptr = *lfdptr; } } if(ptr) { if(!dknet_save_portnumber(job, ptr)) { back = 0; } } else { back = 0; /* ERROR: Argument expected! */ job->exval = DKNET_EXIT_ERROR_OPTIONS; dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 13); } } break; case 3: { /* help */ job->cmd |= DKNET_CMD_HELP; } break; case 4: { /* version */ job->cmd |= DKNET_CMD_VERSION; } break; case 5: { /* license */ job->cmd |= DKNET_CMD_LICENSE; } break; case 7: { $? ". max clients" if(!(ptr)) { i++; lfdptr++; if(i < xargc) { ptr = *lfdptr; } } if(ptr) { if(!dknet_save_max_clients(job, ptr)) back = 0; } else { /* ERROR: Argument expected! */ job->exval = DKNET_EXIT_ERROR_OPTIONS; dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 14); back = 0; } } break; case 8: { $? ". allowed client" if(!(ptr)) { i++; lfdptr++; if(i < xargc) { ptr = *lfdptr; } } if(ptr) { if(!dknet_save_allowed_client(job, ptr)) back = 0; } else { /* ERROR: Argument expected! */ job->exval = DKNET_EXIT_ERROR_OPTIONS; dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 15); back = 0; } } break; case 9: { $? ". transfer immediately" job->stt = 1; } break; case 10: { if(!(ptr)) { i++; lfdptr++; if(i < xargc) { ptr = *lfdptr; } } if(ptr) { ul = 0UL; if(dk3sf_sscanf3(ptr,dkT("%lu"),&ul)) { job->flush = ul; } else { /* ERROR: Not a number. */ back = 0; dk3app_log_i3(job->app, DK3_LL_ERROR, 141, 142, ptr); } } else { job->exval = DKNET_EXIT_ERROR_OPTIONS; dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 15); back = 0; } } break; } i++; lfdptr++; $? ". back = %d", back } if(back) { if((job->cmd & cmdhvl) && (job->cmd & cmdrun)) { $? "! not all" back = 0; /* ERROR: Send/receive can not be combined with help, version */ dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 16); } else { if(cmdrun == (job->cmd & cmdrun)) { $? "! not both" back = 0; /* ERROR: Either send or receive is allowed, not both! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 17); } } } if(0 == job->cmd) { $? "! no command" back = 0; /* ERROR: No action was choosen! */ job->exval = DKNET_EXIT_ERROR_OPTIONS; dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 18); } else { job->exval = DKNET_EXIT_ERROR_ANY; } $? "- dknet_process_command_line_arguments %d", back return back; } /** Accept connection requests from clients. @param job Job structure. @return 1 on success, 0 on error. */ static int dknet_accept_clients(dknet_job *job) { dk3_socket_set_t *lso; /* Listener sockets. */ dknet_session *sp; /* Session pointer. */ dk3_socket_t newsock; /* New socket. */ int mustContinue; /* Flag: Must continue listening. */ int back = 0; char c; $? "+ dknet_accept_clients" if(job->llerr < DK3_LL_PROGRESS) { dk3app_set_stderr_log_level(job->app, DK3_LL_PROGRESS); } /* PROGRESS: Creating listener socket set. */ dk3app_log_1(job->app, DK3_LL_PROGRESS, job->msg, 20); lso = dk3socket_listeners(job->nport, job->blog, 0, NULL, job->app); /* PROGRESS: Finished creating listener socket set. */ dk3app_log_1(job->app, DK3_LL_PROGRESS, job->msg, 21); if(lso) { back = 1; /* PROGRESS: Waiting for clients to connect. */ dk3app_log_1(job->app, DK3_LL_PROGRESS, job->msg, 22); do { mustContinue = 0; c = 0x00; newsock = dk3socket_set_accept( lso, NULL, NULL, job->iacl, 1, 0L, 0L, NULL, job->app ); if(INVALID_SOCKET != newsock) { $? ". newsock" mustContinue = 1; if(1 == dk3socket_recv(newsock,(void *)(&c),1,5L,0L,NULL,job->app)) { job->clcur += 1; $? ". recv" sp = dk3_new_app(dknet_session,1,job->app); if(sp) { $? ". sp" sp->ss = newsock; sp->ec = 0; if(dk3sto_add(job->ssess, (void *)sp)) { $? ". add" if(job->clmax > 0) { $? ". clmax" if(job->clcur >= job->clmax) { mustContinue = 0; } } if(0x01 == c) { $? ". 0x01" mustContinue = 0; } } else { $? "! add" dk3socket_close(newsock, NULL, NULL); sp->ss = 0; sp->ec = 0; dk3_delete(sp); back = 0; mustContinue = 0; job->exval = DKNET_EXIT_ERROR_MEMORY; } } else { $? "! sp" dk3socket_close(newsock, NULL, NULL); back = 0; mustContinue = 0; job->exval = DKNET_EXIT_ERROR_MEMORY; } } else { $? "! recv" dk3socket_close(newsock, NULL, NULL); /* ERROR: Failed to read initial byte from client! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 29); } } else { $? "! newsock" back = 0; } } while((mustContinue) && (back)); /* PROGRESS: Finished waiting for clients to connect, closing set */ dk3app_log_1(job->app, DK3_LL_PROGRESS, job->msg, 23); dk3socket_set_close(lso, NULL, job->app); if(0 == job->clcur) { back = 0; } /* PROGRESS: Listener set closed. */ dk3app_log_1(job->app, DK3_LL_PROGRESS, job->msg, 24); } dk3app_set_stderr_log_level(job->app, job->llerr); $? "- dknet_accept_clients %d", back return back; } /** Do data transfer (send file). @param job Job structure. */ static void dknet_transfer_data(dknet_job *job) { dkChar sobu[64]; /* Buffer for socket number. */ char buffer[1460]; /* Buffer to read data from file. */ dknet_session *sp; /* Current session. */ size_t rb; /* Bytes read from file. */ int wb; if(job->llerr < DK3_LL_PROGRESS) { dk3app_set_stderr_log_level(job->app, DK3_LL_PROGRESS); } /* PROGRESS: Sending data. */ dk3app_log_1(job->app, DK3_LL_PROGRESS, job->msg, 25); job->exval = DKNET_EXIT_OK; do { if((job->rfn) && (job->fipo)) { rb = dk3sf_fread_app((void *)buffer,1,sizeof(buffer),job->fipo,job->app); } else { rb = dk3sf_read_app(0, (void *)buffer, sizeof(buffer), job->app); } if(rb > 0) { dk3sto_it_reset(job->isess); while(NULL != (sp = (dknet_session *)dk3sto_it_next(job->isess))) { if(!(sp->ec)) { wb = dk3socket_send(sp->ss,(void *)buffer,rb,0L,0L,NULL,job->app); if((size_t)wb != rb) { sp->ec = 1; job->exval = DKNET_EXIT_ERROR_NETWORK; /* ERROR While sending data over network! */ if(job->app) { #if VERSION_BEFORE_20140716 dk3sf_sprintf3(sobu,dkT("%d"),((int)(sp->ss))); dk3app_log_3(job->app, DK3_LL_ERROR, job->msg, 30, 31, sobu); #else if ( dk3ma_im_to_string( sobu, DK3_SIZEOF(sobu,dkChar), (dk3_im_t)(sp->ss) ) ) { dk3app_log_3(job->app, DK3_LL_ERROR, job->msg, 30, 31, sobu); } #endif } } } } } } while(rb > 0); /* PROGRESS: Finished sending data. */ dk3app_log_1(job->app, DK3_LL_PROGRESS, job->msg, 26); dk3app_set_stderr_log_level(job->app, job->llerr); } /** Run sender with opened file (either stdin or specified file). @param job Job structure. */ static void dknet_run_sender_with_file(dknet_job *job) { dknet_session *sp; job->exval = DKNET_EXIT_ERROR_MEMORY; job->ssess = dk3sto_open_app(job->app); if(job->ssess) { job->isess = dk3sto_it_open(job->ssess); if(job->isess) { /* Accept clients and transfer data. */ job->exval = DKNET_EXIT_ERROR_NETWORK; if(dknet_accept_clients(job)) { dknet_transfer_data(job); } /* Clean up. */ dk3sto_it_reset(job->isess); while(NULL != (sp = (dknet_session *)dk3sto_it_next(job->isess))) { if(INVALID_SOCKET != sp->ss) { dk3socket_eat_input(sp->ss, job->app); dk3socket_close(sp->ss, NULL, job->app); sp->ss = INVALID_SOCKET; } sp->ec = 0; dk3_delete(sp); } dk3sto_it_close(job->isess); } dk3sto_close(job->ssess); } job->ssess = NULL; job->isess = NULL; } /** Listen for incoming connection requests, send data. @param job Job structure. */ static void dknet_real_sender(dknet_job *job) { #if DK3_ON_WINDOWS int oldmode = 0; #endif $? "+ dknet_real_sender" #if DK3_ON_WINDOWS if(!(job->rfn)) { oldmode = _setmode(0, _O_BINARY); } #endif if(job->rfn) { job->fipo = dk3sf_fopen_app(job->rfn, dk3app_not_localized(36), job->app); if(job->fipo) { dknet_run_sender_with_file(job); fclose(job->fipo); job->fipo = NULL; } else { /* ERROR: Failed to open input file! */ job->exval = DKNET_EXIT_ERROR_IO; } } else { dknet_run_sender_with_file(job); } #if DK3_ON_WINDOWS if(!(job->rfn)) { _setmode(0, oldmode); } #endif $? "- dknet_real_sender" } /** Connect to server, receive data and write to output or file. @param job Job structure. */ static void dknet_real_receiver(dknet_job *job) { char bu[1460]; /* Buffer. */ FILE *fipo = NULL; /* Output file. */ dk3_socket_t sock; /* Network socket. */ unsigned long flushcount = 0UL; /* Blocks found. */ #if DK3_ON_WINDOWS int oldmode = 0; /* Old stdout mode. */ #endif int firstpass = 1; /* Flag: First pass in loop. */ int res; /* Network read result. */ int mustflush; /* Flag: Must flush. */ char chr; /* Character to send. */ $? "+ dknet_real_receiver" #if DK3_ON_WINDOWS if(!(job->rfn)) { oldmode = _setmode(_fileno(stdout), _O_BINARY); } #endif chr = 0x00; if(job->stt) { chr = 0x01; } job->exval = DKNET_EXIT_ERROR_NETWORK; sock = dk3socket_dkchar_open_net_stream_client( job->hn, job->nport, 0, 0L, 0L, NULL, job->app ); if(INVALID_SOCKET != sock) { $? ". socket" res = dk3socket_send(sock, (void *)(&chr), 1, 0L, 0L, NULL, job->app); if(1 == res) { $? ". send" res = dk3socket_shutdown(sock,DK3_TCPIP_SHUTDOWN_WRITE,NULL,job->app); if(res) { $? ". shutdown" job->exval = DKNET_EXIT_OK; if(job->llerr < DK3_LL_PROGRESS) { dk3app_set_stderr_log_level(job->app, DK3_LL_PROGRESS); } /* PROGRESS: Start of transmission. */ dk3app_log_1(job->app, DK3_LL_PROGRESS, job->msg, 27); flushcount = 0UL; do { mustflush = 0; res = dk3socket_recv(sock, bu, sizeof(bu), 0L, 0L, NULL, job->app); if(res > 0) { $? ". have data" if(job->flush) { flushcount++; if(flushcount >= job->flush) { mustflush = 1; flushcount = 0; } } if(firstpass) { firstpass = 0; if(job->rfn) { fipo = dk3sf_fopen_app( job->rfn, dk3app_not_localized(53), job->app ); if(!(fipo)) { /* ERROR: Failed to open output file! */ job->exval = DKNET_EXIT_ERROR_IO; } } } if(job->rfn) { if(fipo) { if(dk3sf_fwrite_app(bu, 1, res, fipo, job->app)) { if(mustflush) { fflush(fipo); } } else { /* ERROR: Failed to write data. */ job->exval = DKNET_EXIT_ERROR_IO; } } } else { if(dk3sf_fwrite_app(bu, 1, res, stdout, job->app)) { if(mustflush) { fflush(stdout); } } else { /* ERROR: Not all bytes written successfully. */ job->exval = DKNET_EXIT_ERROR_IO; } } } else { $? ". no data found" } } while(res > 0); if(job->rfn) { if(job->fipo) { if(!(mustflush)) { fflush(job->fipo); } } } else { if(!(mustflush)) { fflush(stdout); } } if(DKNET_EXIT_OK == job->exval) { /* PROGRESS: End of transmission. */ dk3app_log_1(job->app, DK3_LL_PROGRESS, job->msg, 28); } dk3app_set_stderr_log_level(job->app, job->llerr); if(job->rfn) { if(fipo) { if(!dk3sf_fclose_fn_app(fipo, job->rfn, job->app)) { job->exval = DKNET_EXIT_ERROR_IO; } } } } else { $? "! shutdown" /* ERROR: Socket write shutdown failed! */ job->exval = DKNET_EXIT_ERROR_NETWORK; } } else { $? "! send" /* ERROR Failed to send initial byte! */ job->exval = DKNET_EXIT_ERROR_NETWORK; } } else { $? "! socket" /* ERROR: Failed to open socket! */ job->exval = DKNET_EXIT_ERROR_NETWORK; } #if DK3_ON_WINDOWS if(!(job->rfn)) { _setmode(_fileno(stdout), oldmode); } #endif $? "- dknet_real_receiver" } /** Process one file name. @param job Job structure. @param fsend Flag: Send this file. */ static void dknet_process_filename(dknet_job *job, int fsend) { dkChar bu[DK3_MAX_PATH]; dk3_dir_t *pdir = NULL; $? "+ dknet_process_filename %d", fsend job->exval = DKNET_EXIT_ERROR_OPTIONS; if(dk3str_len(job->fn) < DK3_SIZEOF(bu,dkChar)) { dk3str_cpy_not_overlapped(bu, job->fn); dk3str_correct_filename(bu); if(dk3sf_must_expand(bu)) { $? ". must expand" pdir = dk3dir_fne_open_app(bu, job->app); if(pdir) { $? ". pdir" if(dk3dir_get_number_of_files(pdir) > 0) { if(1 == dk3dir_get_number_of_files(pdir)) { if(dk3dir_get_next_file(pdir)) { job->rfn = dk3dir_get_fullname(pdir); if(job->rfn) { $? ". ok" if(fsend) { dknet_real_sender(job); } else { dknet_real_receiver(job); } } else { $? "! no file name" /* ERROR: Bug, must not happen! */ } } else { $? "! no next file" /* ERROR: Bug, must not happen! */ } } else { $? "! too many files" dk3app_log_i3(job->app, DK3_LL_ERROR, 168, 169, bu); job->exval = DKNET_EXIT_ERROR_OPTIONS; } } else { $? "! no file" dk3app_log_i3(job->app, DK3_LL_ERROR, 215, 216, bu); job->exval = DKNET_EXIT_ERROR_OPTIONS; } dk3dir_close(pdir); } else { $? "! dk3dir_fne_open_app" job->exval = DKNET_EXIT_ERROR_MEMORY; } } else { $? ". no need to expand" job->rfn = bu; if(fsend) { dknet_real_sender(job); } else { dknet_real_receiver(job); } } } else { $? "! file name too long" } $? "- dknet_process_filename" } /** Listen for incoming connection requests, send data. @param job Job structure. */ static void dknet_sender(dknet_job *job) { if(job->fn) { dknet_process_filename(job, 1); } else { dknet_real_sender(job); } } /** Connect to server, receive data and write to output or file. @param job Job structure. */ static void dknet_receiver(dknet_job *job) { if(job->fn) { dknet_process_filename(job, 0); } else { dknet_real_receiver(job); } } /** Continue after initializing application and localized texts. @param job Job structure. */ static void dknet_run_with_app_and_messages(dknet_job *job) { dkChar const * const *ptr; $? "+ dknet_run_with_app_and_messages" if(dknet_process_command_line_arguments(job)) { if((job->cmd) & (DKNET_CMD_HELP | DKNET_CMD_VERSION | DKNET_CMD_LICENSE)) { if((job->cmd) & DKNET_CMD_VERSION) { dk3sf_initialize_stdout(); dk3sf_fputs(dknet_noloc[3], stdout); dk3sf_fputs(dknet_noloc[4], stdout); dk3sf_fputs(dknet_version, stdout); dk3sf_fputc(dkT('\n'), stdout); } if((job->cmd) & DKNET_CMD_LICENSE) { dk3sf_initialize_stdout(); ptr = dknet_license_text; while(*ptr) { dk3sf_fputs(*(ptr++), stdout); dk3sf_fputc(dkT('\n'), stdout); } } if((job->cmd) & DKNET_CMD_HELP) { dk3app_help(job->app, dknet_noloc[2], dknet_help_text); } job->exval = DKNET_EXIT_OK; } else { if(DKNET_CMD_SEND == job->cmd) { dknet_sender(job); } else { if(DKNET_CMD_RECEIVE == job->cmd) { dknet_receiver(job); } else { /* ERROR: Neither sender nor receiver! */ dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 18); } } } } else { /* Show help text */ dk3app_log_1(job->app, DK3_LL_ERROR, job->msg, 19); } dknet_job_cleanup_command_line_processing(job); $? "- dknet_run_with_app_and_messages" } /** Entry point of the program. @param argc Number of command line arguments. @param argv Command line arguments array. @return 0 on success, any other value indicates an error. */ DK3_MAIN { dknet_job job; int exval = 0; /* Exit code. */ $!trace-init dknet.deb $? "+ main" dknet_job_init(&job); job.app = dk3app_open_command( argc, (dkChar const * const *)argv, dknet_noloc[0] ); if(job.app) { job.llerr = dk3app_get_stderr_log_level(job.app); job.msg = dk3app_messages( job.app, dknet_noloc[1], (dkChar const **)dknet_loc ); if(job.msg) { if(dk3socket_up(NULL, job.app)) { dknet_run_with_app_and_messages(&job); dk3socket_down(NULL, job.app); } else { /* ERROR: Failed to initialize WinSock! */ job.exval = DKNET_EXIT_ERROR_WINSOCK; } } else { $? "! Bug, should not happen" } dk3app_close(job.app); job.app = NULL; } else { /* ERROR: Memory */ fputs("dknet: ERROR: Not enough memory!\n", stderr); fflush(stderr); job.exval = DKNET_EXIT_ERROR_MEMORY; } dknet_job_cleanup(&job); exval = job.exval; $? "- main %d", exval $!trace-end fflush(stdout); exit(exval); return exval; } #else /* DK3_HAVE_SELECT || DK3_ON_WINDOWS */ /** Entry point of the program. @param argc Number of command line arguments. @param argv Command line arguments array. @return 0 on success, any other value indicates an error. */ DK3_MAIN { fputs("dknet: ERROR: No select() function available!\n", stderr); fflush(stderr); } #endif /* DK3_HAVE_SELECT */