diff --git a/CHANGELOG.md b/CHANGELOG.md index f309ae7..4c16912 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +### py-kms_2020-10-01 +- Sql database path customizable. +- Sql database file keeps different AppId. +- Support for multi-address connection. +- Added timeout send / receive. + ### py-kms_2020-07-01 - py-kms Gui: now matches all cli options, added modes onlyserver / onlyclient, added some animations. diff --git a/docs/Usage.md b/docs/Usage.md index efd2130..bb4c78e 100644 --- a/docs/Usage.md +++ b/docs/Usage.md @@ -53,14 +53,17 @@ e.g. because it could not reach the server. The default is 120 minutes (2 hours) -r or --renewal-interval > Instructs clients to renew activation every _RENEWALINTERVAL_ minutes. The default is 10080 minutes (7 days). - -s or --sqlite + -s or --sqlite [] > Use this option to store request information from unique clients in an SQLite database. Deactivated by default. If enabled the default database file is _pykms_database.db_. You can also provide a specific location. - -t0 or --timeout-idle + -t0 or --timeout-idle > Maximum inactivity time (in seconds) after which the connection with the client is closed. Default setting is serve forever (no timeout). + -t1 or --timeout-sndrcv +> Set the maximum time (in seconds) to wait for sending / receiving a request / response. Default is no timeout. + -y or --async-msg > With high levels of logging (e.g hundreds of log statements), in a traditional synchronous log model, the overhead involved becomes more expensive, so using this option you enable printing (pretty / logging) messages @@ -137,6 +140,109 @@ You can also enable other suboptions of `-F` doing what is reported in the follo -S or --logsize > Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default. + -n or --listen <'IP,PORT'> +> Use this subparser `connect` option to add multiple listening ip address - port couples. Note the format with the comma between the ip address and the port number. You can use this option more than once. + + -b or --backlog +> Use this subparser `connect` option to specify the maximum length of the queue of pending connections, referred to a ip address - port couple. +If placed just after `connect` refers to the main address and all additive couples without `-b` or `-u` options. Default is 5. + + -u or --no-reuse +> Use this subparser `connect` option not to allow binding / listening to the same ip address - port couple specified with `-n`. +If placed just after `connect` refers to the main address and all additive couples without `-b` or `-u` options. Reusing port is activated by default. + + -d or --dual +> Use this subparser `connect` option to allow listening to an IPv6 address also accepting connections via IPv4. +If used it refers to all addresses (main and additional). Deactivated by default. + +examples (with fictitious addresses and ports): + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
commandaddress (main)backlog (main)reuse port (main)address (listen)backlog (listen)reuse port (listen)dualstack (main / listen)
python3 pykms_Server.py connect -b 12
('0.0.0.0', 1688)12True[][][]False
python3 pykms_Server.py :: connect -b 12 -u -d
('::', 1688)12False[][][]True
python3 pykms_Server.py connect -n 1.1.1.1,1699 -b 10
('0.0.0.0', 1688)5True[('1.1.1.1', 1699)][10][True]False
python3 pykms_Server.py :: 1655 connect -n 2001:db8:0:200::7,1699 -d -b 10 -n 2.2.2.2,1677 -u
('::', 1655)5True[('2001:db8:0:200::7', 1699), ('2.2.2.2', 1677)][10, 5][True, False]True
python3 pykms_Server.py connect -b 12 -u -n 1.1.1.1,1699 -b 10 -n 2.2.2.2,1677 -b 15
('0.0.0.0', 1688)12False[('1.1.1.1', 1699), ('2.2.2.2', 1677)][10, 15][True, True]False
python3 pykms_Server.py connect -d -u -b 8 -n 1.1.1.1,1699 -n 2.2.2.2,1677 -b 12
('0.0.0.0', 1688)8False[('1.1.1.1', 1699), ('2.2.2.2', 1677)][8, 12][False, True]True
python3 pykms_Server.py connect -b 11 -u -n ::,1699 -n 2.2.2.2,1677
('0.0.0.0', 1688)11False[('::', 1699), ('2.2.2.2', 1677)][11, 11][False, False]False
+ ### pykms_Client.py If _py-kms_ server doesn't works correctly, you can test it with the KMS client `pykms_Client.py`, running on the same machine where you started `pykms_Server.py`. @@ -176,6 +282,12 @@ activate regardless of CMID being unique for a subset of specific machines or no -n or --name > Use this flag to manually specify an ASCII _MACHINENAME_ to use. If no _MACHINENAME_ is specified a random one will be generated. + -t0 or --timeout-idle +> Set the maximum time (in seconds) to wait for a connection attempt to KMS server to succeed. Default is no timeout. + + -t1 or --timeout-sndrcv +> Set the maximum time (in seconds) to wait for sending / receiving a request / response. Default is no timeout. + -y or --async-msg > Prints pretty / logging messages asynchronously. Deactivated by default. diff --git a/py-kms/pykms_Format.py b/py-kms/pykms_Format.py index ef8cd6c..3c08ebd 100644 --- a/py-kms/pykms_Format.py +++ b/py-kms/pykms_Format.py @@ -176,7 +176,7 @@ queue_print = Queue.Queue() class ShellMessage(object): viewsrv, viewclt = (True for _ in range(2)) asyncmsgsrv, asyncmsgclt = (False for _ in range(2)) - indx, count, remain, numlist = (0, 0, 0, []) + indx, count, remain, numlist, dummy = (0, 0, 0, [], False) loggersrv_pty = logging.getLogger('logsrvpty') loggerclt_pty = logging.getLogger('logcltpty') @@ -254,12 +254,12 @@ class ShellMessage(object): with open(self.path_clean_nl, 'r') as file: some = file.read() if num == 21: - ShellMessage.count, ShellMessage.remain, ShellMessage.numlist = (0, 0, []) + ShellMessage.count, ShellMessage.remain, ShellMessage.numlist, ShellMessage.dummy = (0, 0, [], False) os.remove(self.path_nl) os.remove(self.path_clean_nl) except: if num == 19: - ShellMessage.count, ShellMessage.remain, ShellMessage.numlist = (0, 0, []) + ShellMessage.count, ShellMessage.remain, ShellMessage.numlist, ShellMessage.dummy = (0, 0, [], False) os.remove(self.path_nl) def putter(self, aqueue, toput): @@ -366,11 +366,14 @@ class ShellMessage(object): for msg in self.put_text: ShellMessage.count += msg.count('\n') # Append a dummy element. - ShellMessage.numlist.append('put') + if ShellMessage.dummy: + ShellMessage.numlist.append('put') self.formatter(msg) print(self.msgfrmt, end = '\n', flush = True) else: for num in self.nshell: + if num == 0: + ShellMessage.dummy = True self.newlines_count(num) self.formatter(MsgMap[num]) print(self.msgfrmt, end = '\n', flush = True) diff --git a/py-kms/pykms_GuiBase.py b/py-kms/pykms_GuiBase.py index 0b6752e..d6498c9 100644 --- a/py-kms/pykms_GuiBase.py +++ b/py-kms/pykms_GuiBase.py @@ -412,24 +412,38 @@ class KmsGui(tk.Tk): ## Create widgets (optsrvwin:Srv:PageWin:PageEnd)------------------------------------------------------------------------------------------- # Timeout connection. - timeout0lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.optfont) - self.timeout0 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.optfont) - self.timeout0.insert('end', str(srv_options['time0']['def'])) - ToolTip(self.timeout0, text = srv_options['time0']['help'], wraplength = self.wraplength) + srvtimeout0lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.optfont) + self.srvtimeout0 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.optfont) + self.srvtimeout0.insert('end', str(srv_options['time0']['def'])) + ToolTip(self.srvtimeout0, text = srv_options['time0']['help'], wraplength = self.wraplength) + # Timeout send/recv. + srvtimeout1lbl = tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Timeout send-recv: ', font = self.optfont) + self.srvtimeout1 = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.optfont) + self.srvtimeout1.insert('end', str(srv_options['time1']['def'])) + ToolTip(self.srvtimeout1, text = srv_options['time1']['help'], wraplength = self.wraplength) # Sqlite database. self.chkvalsql = tk.BooleanVar() self.chkvalsql.set(srv_options['sql']['def']) + self.chkfilesql = tk.Entry(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 16, font = self.optfont) + self.chkfilesql.insert('end', srv_options['sql']['file']) + self.chkfilesql.xview_moveto(1) + self.chkfilesql.configure(state = 'disabled') + chksql = tk.Checkbutton(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], text = 'Create Sqlite\nDatabase', - font = self.optfontredux, var = self.chkvalsql, relief = 'groove') + font = self.optfontredux, var = self.chkvalsql, relief = 'groove', + command = lambda: self.sql_status()) ToolTip(chksql, text = srv_options['sql']['help'], wraplength = self.wraplength) ## Layout widgets (optsrvwin:Srv:PageWin:PageEnd) # a label for vertical aligning with PageStart tk.Label(self.pagewidgets["Srv"]["PageWin"]["PageEnd"], width = 0, height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw') - timeout0lbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') - self.timeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w') - chksql.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'w') + srvtimeout0lbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') + self.srvtimeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w') + srvtimeout1lbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e') + self.srvtimeout1.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'w') + chksql.grid(row = 3, column = 0, padx = 5, pady = 5, sticky = 'e') + self.chkfilesql.grid(row = 3, column = 1, padx = 5, pady = 5, sticky = 'w') # Store server-side widgets. self.storewidgets_srv = self.gui_store(side = "Srv", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton']) @@ -440,6 +454,12 @@ class KmsGui(tk.Tk): relief = 'ridge', font = self.msgfont) self.textboxsrv.put() + def sql_status(self): + if self.chkvalsql.get(): + self.chkfilesql.configure(state = 'normal') + else: + self.chkfilesql.configure(state = 'disabled') + def always_centered(self, geo, centered, refs): x = (self.winfo_screenwidth() // 2) - (self.winfo_width() // 2) y = (self.winfo_screenheight() // 2) - (self.winfo_height() // 2) @@ -485,7 +505,7 @@ class KmsGui(tk.Tk): return x, y, w, h def gui_clt(self): - self.count_clear = 0 + self.count_clear, self.keep_clear = (0, '0.0') self.optcltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') self.msgcltwin = tk.Frame(self.masterwin, background = self.customcolors['black'], relief = 'ridge', width = 300, height = 200) self.btncltwin = tk.Canvas(self.masterwin, background = self.customcolors['white'], borderwidth = 3, relief = 'ridge') @@ -613,11 +633,25 @@ class KmsGui(tk.Tk): padx = 35, pady = 54, sticky = 'e') ## Create widgets (optcltwin:Clt:PageWin:PageEnd) ------------------------------------------------------------------------------------------- + # Timeout connection. + clttimeout0lbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], text = 'Timeout connection: ', font = self.optfont) + self.clttimeout0 = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 16, font = self.optfont) + self.clttimeout0.insert('end', str(clt_options['time0']['def'])) + ToolTip(self.clttimeout0, text = clt_options['time0']['help'], wraplength = self.wraplength) + # Timeout send/recv. + clttimeout1lbl = tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], text = 'Timeout send-recv: ', font = self.optfont) + self.clttimeout1 = tk.Entry(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 16, font = self.optfont) + self.clttimeout1.insert('end', str(clt_options['time1']['def'])) + ToolTip(self.clttimeout1, text = clt_options['time1']['help'], wraplength = self.wraplength) ## Layout widgets (optcltwin:Clt:PageWin:PageEnd) # a label for vertical aligning with PageStart tk.Label(self.pagewidgets["Clt"]["PageWin"]["PageEnd"], width = 0, height = 0, bg = self.customcolors['lavender']).grid(row = 0, column = 0, padx = 5, pady = 5, sticky = 'nw') + clttimeout0lbl.grid(row = 1, column = 0, padx = 5, pady = 5, sticky = 'e') + self.clttimeout0.grid(row = 1, column = 1, padx = 5, pady = 5, sticky = 'w') + clttimeout1lbl.grid(row = 2, column = 0, padx = 5, pady = 5, sticky = 'e') + self.clttimeout1.grid(row = 2, column = 1, padx = 5, pady = 5, sticky = 'w') ## Store client-side widgets. self.storewidgets_clt = self.gui_store(side = "Clt", typewidgets = ['Button', 'Entry', 'TCombobox', 'Checkbutton']) @@ -714,13 +748,13 @@ class KmsGui(tk.Tk): srv_config[srv_options['llevel']['des']] = self.srvlevel.get() srv_config[srv_options['lsize']['des']] = self.prep_option(self.srvsize.get()) - srv_config[srv_options['time0']['des']] = self.prep_option(self.timeout0.get()) - srv_config[srv_options['sql']['des']] = self.chkvalsql.get() + srv_config[srv_options['time0']['des']] = self.prep_option(self.srvtimeout0.get()) + srv_config[srv_options['time1']['des']] = self.prep_option(self.srvtimeout1.get()) + srv_config[srv_options['sql']['des']] = (self.chkfilesql.get() if self.chkvalsql.get() else self.chkvalsql.get()) ## Redirect stdout. gui_redirector('stdout', redirect_to = TextRedirect.Log, redirect_conditio = (srv_config[srv_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT'])) - gui_redirector_setup() serverqueue.put('start') def srv_actions_stop(self): @@ -733,6 +767,7 @@ class KmsGui(tk.Tk): else: serverthread.is_running_server = False self.srv_toggle_all(on_start = False) + self.count_clear, self.keep_clear = (0, '0.0') def srv_toggle_all(self, on_start = True): self.srv_toggle_state() @@ -786,10 +821,12 @@ class KmsGui(tk.Tk): clt_config[clt_options['llevel']['des']] = self.cltlevel.get() clt_config[clt_options['lsize']['des']] = self.prep_option(self.cltsize.get()) + clt_config[clt_options['time0']['des']] = self.prep_option(self.clttimeout0.get()) + clt_config[clt_options['time1']['des']] = self.prep_option(self.clttimeout1.get()) + ## Redirect stdout. gui_redirector('stdout', redirect_to = TextRedirect.Log, redirect_conditio = (clt_config[clt_options['lfile']['des']] in ['STDOUT', 'FILESTDOUT'])) - gui_redirector_setup() # run client (in a thread). self.clientthread = client_thread(name = "Thread-Clt") @@ -824,20 +861,16 @@ class KmsGui(tk.Tk): def on_clear_setup(self): if any(opt in ['STDOUT', 'FILESTDOUT'] for opt in srv_config[srv_options['lfile']['des']]): + add_newline = True if self.count_clear == 0: - self.ini = txsrv.index('end') - add_newline = False - else: - if self.count_clear == 1: - self.ini = '%s.0' %(int(self.ini[0]) - 1) - else: - self.ini = '%s.0' %(int(self.ini[0])) - add_newline = True - rng = [self.ini, 'end'] - self.count_clear += 1 + self.keep_clear = txsrv.index('end-1c') else: - rng, add_newline = None, False - self.count_clear = 0 + add_newline = False + if self.count_clear == 0: + self.keep_clear = txsrv.index('end') + + rng = [self.keep_clear, 'end'] + self.count_clear += 1 return rng, add_newline diff --git a/py-kms/pykms_Misc.py b/py-kms/pykms_Misc.py index 7dbcabd..f790d3b 100644 --- a/py-kms/pykms_Misc.py +++ b/py-kms/pykms_Misc.py @@ -452,8 +452,8 @@ def kms_parser_check_connect(config, options, userarg, zeroarg, onearg): raise KmsParserException("optional connect arguments missing") rng = range(lung - 1) - config['backlog_primary'] = options['backlog']['def'] - config['reuse_primary'] = options['reuse']['def'] + config['backlog_main'] = options['backlog']['def'] + config['reuse_main'] = options['reuse']['def'] def assign(arguments, index, options, config, default, islast = False): if all(opt not in arguments for opt in options): @@ -464,18 +464,18 @@ def kms_parser_check_connect(config, options, userarg, zeroarg, onearg): else: config.append(default) - def assign_primary(arguments, config): + def assign_main(arguments, config): if any(opt in arguments for opt in ['-b', '--backlog']): - config['backlog_primary'] = config['backlog'][0] + config['backlog_main'] = config['backlog'][0] config['backlog'].pop(0) if any(opt in arguments for opt in ['-u', '--no-reuse']): - config['reuse_primary'] = config['reuse'][0] + config['reuse_main'] = config['reuse'][0] config['reuse'].pop(0) if config['listen']: # check before. pos = userarg.index(config['listen'][0]) - assign_primary(userarg[1 : pos - 1], config) + assign_main(userarg[1 : pos - 1], config) # check middle. for indx in rng: @@ -487,8 +487,8 @@ def kms_parser_check_connect(config, options, userarg, zeroarg, onearg): assign(arguments, indx, ['-u', '--no-reuse'], config['reuse'], options['reuse']['def']) if not arguments: - config['backlog'][indx] = config['backlog_primary'] - config['reuse'][indx] = config['reuse_primary'] + config['backlog'][indx] = config['backlog_main'] + config['reuse'][indx] = config['reuse_main'] # check after. if lung == 1: @@ -501,11 +501,11 @@ def kms_parser_check_connect(config, options, userarg, zeroarg, onearg): assign(arguments, None, ['-u', '--no-reuse'], config['reuse'], options['reuse']['def'], islast = True) if not arguments: - config['backlog'][indx + 1] = config['backlog_primary'] - config['reuse'][indx + 1] = config['reuse_primary'] + config['backlog'][indx + 1] = config['backlog_main'] + config['reuse'][indx + 1] = config['reuse_main'] else: - assign_primary(userarg[1:], config) + assign_main(userarg[1:], config) #------------------------------------------------------------------------------------------------------------------------------------------------------------ diff --git a/py-kms/pykms_Server.py b/py-kms/pykms_Server.py index 8f30fb6..7db3e37 100755 --- a/py-kms/pykms_Server.py +++ b/py-kms/pykms_Server.py @@ -25,7 +25,7 @@ from pykms_Format import enco, deco, pretty_printer, justify from Etrigan import Etrigan, Etrigan_parser, Etrigan_check, Etrigan_job from pykms_Connect import MultipleListener -srv_version = "py-kms_2020-07-01" +srv_version = "py-kms_2020-10-01" __license__ = "The Unlicense" __author__ = u"Matteo ℱan " __url__ = "https://github.com/SystemRage/py-kms" @@ -196,7 +196,8 @@ for server OSes and Office >=5', 'def' : None, 'des' : "clientcount"}, 'renewal' : {'help' : 'Use this option to specify the renewal interval (in minutes). Default is \"10080\" minutes (7 days).', 'def' : 1440 * 7, 'des' : "renewal"}, 'sql' : {'help' : 'Use this option to store request information from unique clients in an SQLite database. Deactivated by default. \ -If enabled the default .db file is \"pykms_database.db\". You can also provide a specific location.', 'def' : False, 'des' : "sqlite"}, +If enabled the default .db file is \"pykms_database.db\". You can also provide a specific location.', 'def' : False, + 'file': os.path.join('.', 'pykms_database.db'), 'des' : "sqlite"}, 'hwid' : {'help' : 'Use this option to specify a HWID. The HWID must be an 16-character string of hex characters. \ The default is \"364F463A8863D35F\" or type \"RANDOM\" to auto generate the HWID.', 'def' : "364F463A8863D35F", 'des' : "hwid"}, @@ -213,10 +214,11 @@ Type \"STDOUT\" to view log info on stdout. Type \"FILESTDOUT\" to combine previ Use \"STDOUTOFF\" to disable stdout messages. Use \"FILEOFF\" if you not want to create logfile.', 'def' : os.path.join('.', 'pykms_logserver.log'), 'des' : "logfile"}, 'lsize' : {'help' : 'Use this flag to set a maximum size (in MB) to the output log file. Deactivated by default.', 'def' : 0, 'des': "logsize"}, - 'listen' : {'help' : 'Adds multiple listening address / port couples.', 'des': "listen"}, + 'listen' : {'help' : 'Adds multiple listening ip address - port couples.', 'des': "listen"}, 'backlog' : {'help' : 'Specifies the maximum length of the queue of pending connections. Default is \"5\".', 'def' : 5, 'des': "backlog"}, - 'reuse' : {'help' : 'Allows binding / listening to the same address and port. Activated by default.', 'def' : True, 'des': "reuse"}, - 'dual' : {'help' : 'Allows binding / listening to an IPv6 address also accepting connections via IPv4. Deactivated by default.', + 'reuse' : {'help' : 'Do not allows binding / listening to the same address and port. Reusing port is activated by default.', 'def' : True, + 'des': "reuse"}, + 'dual' : {'help' : 'Allows listening to an IPv6 address also accepting connections via IPv4. Deactivated by default.', 'def' : False, 'des': "dual"} } @@ -274,7 +276,7 @@ def server_options(): connect_parser.add_argument("-u", "--no-reuse", action = "append_const", dest = srv_options['reuse']['des'], const = False, default = [], help = srv_options['reuse']['help']) connect_parser.add_argument("-d", "--dual", action = "store_true", dest = srv_options['dual']['des'], default = srv_options['dual']['def'], - help = srv_options['reuse']['help']) + help = srv_options['dual']['help']) try: userarg = sys.argv[1:] @@ -472,7 +474,7 @@ def server_check(): if isinstance(srv_config['sqlite'], str): check_dir(srv_config['sqlite'], 'srv', log_obj = loggersrv.error, argument = '-s/--sqlite', typefile = '.db') elif srv_config['sqlite'] is True: - srv_config['sqlite'] = os.path.join('.', 'pykms_database.db') + srv_config['sqlite'] = srv_options['sql']['file'] try: import sqlite3 @@ -516,8 +518,8 @@ def server_create(): # Create address list. all_address = [( srv_config['ip'], srv_config['port'], - (srv_config['backlog_primary'] if 'backlog_primary' in srv_config else srv_options['backlog']['def']), - (srv_config['reuse_primary'] if 'reuse_primary' in srv_config else srv_options['reuse']['def']) + (srv_config['backlog_main'] if 'backlog_main' in srv_config else srv_options['backlog']['def']), + (srv_config['reuse_main'] if 'reuse_main' in srv_config else srv_options['reuse']['def']) )] log_address = "TCP server listening at %s on port %d" %(srv_config['ip'], srv_config['port'])