#!/usr/bin/python ################################################################## # python script to create sockets to receive files for archiving # ################################################################## # # This script 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; either # version 2 of the License, or (at your option) any later version. # # This script 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. # # You should have received a copy of the GNU General Public # License along with this script; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA # or check http://www.gnu.org/licenses/ for more information. # # NAME # get_archives.py # # USAGE # get_archives [-h] [-d] ServicePort PortRangeStart PortRangeEnd # # DESCRIPTION # This service listens on ServicePort for incoming connections with # a filename and answers with a port number to which the caller can # then send the files. The port number come from the PortRange # # REVISION HISTORY # 0.1 Initial creation # # AUTHOR # Hans-Georg Bork, hgb@users.sourceforge.net # # Copyright (c) 2017 Hans-Georg Bork # import sys import socket import threading import argparse import signal import subprocess import ConfigParser from time import strftime, localtime HOST = '0.0.0.0' # Hostname to bind (all IP's) free_ports = [] used_ports = [] threads = [] config = {} # dict containing config from files old_stdout = sys.stdout debug = False # parse and apply config def set_config(): global config, free_ports config = { "serviceport": 49010, "portrangestart": 49011, "portrangeend": 50010, "logfile": "/tmp/get_archives.log", "configfile": "/usr/local/etc/get_archives.conf" } # default config iconfig = config # Parsing config files c = ConfigParser.SafeConfigParser( allow_no_value = True ) try: c.readfp( open( config[ "configfile" ] )) except: print "ERROR : Cannot read initial config from", config[ "configfile" ], ". Cancelling..." sys.exit( 66 ) c.read([ "/etc/get_archives.conf" ]) # store config from files into config dict for o in c.sections(): for t in c.items( o ): config[ t[ 0 ]] = t[ 1 ] # Parsing command line description = "Service to listen for filenames on ServicePort and then get the files on other ports from range. Service expect requests in the format \" \"" epilog = "Default config can be found in /usr/local/etc/get_archives.conf or /etc/get_archives.conf. Last read option is valid and command line option supersedes config" parser = argparse.ArgumentParser( description = description, epilog = epilog ) ex_group = parser.add_mutually_exclusive_group() parser.add_argument( "--ServicePort", "--serviceport", type=int, dest="serviceport", default=config["serviceport"], help="Main port to use for service") parser.add_argument( "--PortRangeStart", "--portrangestart", type=int, dest="portrangestart", default=config["portrangestart"], help="Start of range of ports to use for data transfer") parser.add_argument( "--PortRangeEnd", "--portrangeend", type=int, dest="portrangeend", default=config["portrangeend"], help="End of range of ports to use for data transfer") ex_group.add_argument( "--LogFile", "--logfile", type=str, dest="logfile", default=config["logfile"], help="Set a different log file (absolute path required)" ) ex_group.add_argument( "--stdout", action="store_true", help="Send output to standard output instead into log file" ) parser.add_argument( "-d", "--debug", action="store_true", help="Show debug messages in (default) log " + config[ "logfile" ] ) args = parser.parse_args() if args.portrangestart > args.portrangeend: print "ERROR : PortRangeStart cannot be ahead of PortRangeEnd. Cancelling..." sys.exit( 66 ) # DEBUG output : store config from files into config dict if args.debug: log( "DEBUG : Initial config : ", str( iconfig.items() ) ) for o in c.sections(): for t in c.items( o ): if args.debug: log( "DEBUG : New config set : ", t ) # add or replace config from command line options config.update( vars( args )) if config[ 'debug' ]: log( "DEBUG : Current config : ", str( config.items() ) ) # Apply config if not config['stdout']: try: config[ "logfilehandler" ] = open( config['logfile'], 'a', 0 ) except: print "ERROR : Cannot open log file", config['logfile'], "\nINFO : Logs are written to stdout" free_ports = range( config['portrangestart'], config['portrangeend'] + 1 ) def log( *msg ): "Function log creates log messages by concatenating args with no spaces" log_text = strftime("%Y-%m-%d %H:%M:%S", localtime()) + " : " for m in msg: log_text += str( m ) try: config[ "logfilehandler" ].write( log_text + "\n" ) except: print log_text def cleanup(): global threads for t in threads[:]: print "Waiting for remaining threads to finish " + t.name t.join try: config[ "logfilehandler" ].close() except: pass def hard_exit( signum, frame ): sys.exit( 99 ) def trap_signal( signum, frame ): log( "Signal ", signum, " received. Cleaning up. Send signal again to stop cleaning as well" ) signal.signal( signal.SIGINT, hard_exit ) signal.signal( signal.SIGTERM, hard_exit ) cleanup() sys.exit( 99 ) def set_server_port(): if not port_available( config['serviceport'] ): print "\nFATAL ERROR : Cannot use ServicePort", config['serviceport'], "for main service\n" sys.exit( 1 ) return 1 log( "Starting service on port ", config['serviceport'], " with port range [", config['portrangestart'], ":", config['portrangeend'], "]" ) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) s.bind(( HOST, config['serviceport'] )) return s class connThread( threading.Thread ): def __init__( self, port, filename ): threading.Thread.__init__( self ) self.port = port self.filename = filename def run( self ): log( "Starting connection on ", self.port, " for ", self.filename ) open_new_conn( self.port, self.filename ) log( "Exiting connection on ", self.port, " for ", self.filename ) def del_from_list( list, val ): for l in list[:]: if l == val: list.remove( l ) break def open_new_conn (port, file): global free_ports global used_ports ns = socket.socket( socket.AF_INET, socket.SOCK_STREAM ) ns.setsockopt( socket.SOL_SOCKET, socket.SO_REUSEADDR, 1 ) ns.bind(( HOST, port )) ns.listen( 0 ) conn, addr = ns.accept() out_file = open( file, "w" ) while True: data = conn.recv(1024) if not data: break out_file.write( data ) conn.close() out_file.close() del_from_list( used_ports, port ) free_ports.append( port ) def port_available( port ): ret = 0 try: # connect on port if config[ 'debug' ]: log( "DEBUG : Check availability of port ", port ) socket.socket( socket.AF_INET, socket.SOCK_STREAM ).connect(( HOST, port )) except socket.error as e: # if port is not in use ret = 1 return ret def main(): global free_ports global used_ports global threads try: server_socket = set_server_port() except: sys.exit( 1 ) while True: # main loop # cleanup threads if any to clear for t in threads[:]: if not t.isAlive(): threads.remove( t ) server_socket.listen( 5 ) conn, conn_addr = server_socket.accept() log( "New connection from host [", conn_addr[0], "] with source port [", conn_addr[1], "]" ) port_to_use = 0 data = conn.recv(1024) if data[:-1] == "EXIT_THIS": log( "Got EXIT command" ) conn.close() break elif data[:-1] == "STATUS": log( "Got STATUS command" ) conn.send( "Free ports to use : " + str( len( free_ports )) + "\n" ) conn.send( "Ports currently in use : " + str( len( used_ports )) + "\n" ) conn.send( "Threads in use : " + str( len( threads )) + "\n" ) conn.send( "Current config : " + str( config.items() ) + "\n" ) conn.close() continue elif data[:-1] == "RELOAD": log( "Got RELOAD command" ) set_config() try: conn.close() del server_socket server_socket = set_server_port() except: conn.close() log( "Cannot reload config. Exiting ...\n" ) cleanup() sys.exit( 1 ) else: log( "Configuration reloaded\n" ) elif data[:7] == "REINDEX": log( "Got REINDEX command" ) try: comm, dir = data[:-1].split() except: if len( data ) > 0: conn.send( "Target cannot be empty. Connection refused\n" ) conn.send( "Send \"REINDEX \"\n" ) conn.close() continue try: target_dir = config[ dir ] except: if config[ 'debug' ]: log( "DEBUG : Received target : ", dir ) conn.send( "Invalid target " + dir + ". Connection refused\n" ) conn.close() continue try: subprocess.call( [ "/bin/bash", "/usr/local/bin/mk-index-html.sh", target_dir + "/.." ] ) except: log( "subprocess.call( [ \"/bin/bash\", \"/usr/local/bin/mk-index-html.sh\", \"" + target_dir + "\" ] ) : FAILED" ) conn.send( "ERROR : REINDEX using \"/usr/local/bin/mk-index-html.sh" + target_dir + "\" FAILED\n\n" ) elif len( free_ports ) > 0: try: dir, fn = data[:-1].split() except: if config[ 'debug' ]: log( "DEBUG : Received data : ", data ) if len( data ) > 0: conn.send( "Filename cannot be empty. Connection refused\n" ) conn.send( "Send \" \"\n" ) conn.close() continue try: target_dir = config[ dir ] except: if config[ 'debug' ]: log( "DEBUG : Received target : ", dir ) conn.send( "Invalid target " + dir + ". Connection refused\n" ) conn.close() continue for p in free_ports[:]: if port_available( p ): port_to_use = p if config[ 'debug' ]: log( "DEBUG : Next port in use ", port_to_use ) free_ports.remove( p ) used_ports.append( p ) break else: if config[ 'debug' ]: log( "DEBUG : Port already in use ", p ) free_ports.remove( p ) used_ports.append( p ) if port_to_use > 0: file = target_dir + "/" + fn + ".tar.gz" if config[ 'debug' ]: log( "DEBUG : Filename to store data : ", file ) threads.append( connThread( port_to_use, file )) threads[len( threads ) - 1].start() if config[ 'debug' ]: log( "DEBUG : Amount threads in use : ", len( threads ) ) conn.send( str( port_to_use )) else: if config[ 'debug' ]: log( "DEBUG : Invalid port_to_use : ", port_to_use ) log( "No available ports left. Connection refused" ) conn.send( "No available ports left. Connection refused\n" ) else: log( "No available ports left. Connection refused" ) conn.send( "No available ports left. Connection refused\n" ) # close connection, so that client stops with result conn.close() # Cleanup used_ports (if not done) #for p in used_ports: # if p == port_to_use: continue # if port_available( p ): # used_ports.remove( p ) # free_ports_append( p ) # End main loop if __name__ == "__main__": signal.signal( signal.SIGINT, trap_signal ) signal.signal( signal.SIGTERM, trap_signal ) set_config() main() cleanup()