Package entropy :: Package services :: Module interfaces

Source Code for Module entropy.services.interfaces

   1  # -*- coding: utf-8 -*- 
   2  """ 
   3   
   4      @author: Fabio Erculiani <lxnay@sabayonlinux.org> 
   5      @contact: lxnay@sabayonlinux.org 
   6      @copyright: Fabio Erculiani 
   7      @license: GPL-2 
   8   
   9      B{Entropy Services Base Interfaces}. 
  10   
  11  """ 
  12  import sys 
  13  import os 
  14  import select 
  15  import shutil 
  16  import time 
  17  from entropy.const import etpConst, ETP_LOGLEVEL_NORMAL, ETP_LOGPRI_INFO, \ 
  18      const_setup_perms, const_isstring, const_get_stringtype, \ 
  19      const_convert_to_rawstring, etpUi, const_debug_write 
  20  from entropy.exceptions import * 
  21  from entropy.services.skel import SocketAuthenticator, SocketCommands 
  22  from entropy.i18n import _ 
  23  from entropy.output import blue, red, darkgreen, darkred, darkblue, brown, \ 
  24      purple 
  25  try: 
  26      import SocketServer as socketserver 
  27  except ImportError: # Python 3.x 
  28      import socketserver 
  29   
  30   
31 -class SocketHost:
32 33 import socket 34 from threading import Thread 35
36 - class BasicPamAuthenticator(SocketAuthenticator):
37 38 import entropy.tools as entropyTools 39
40 - def __init__(self, HostInterface, *args, **kwargs):
41 self.valid_auth_types = [ "plain", "shadow", "md5" ] 42 SocketAuthenticator.__init__(self, HostInterface)
43
44 - def docmd_login(self, arguments):
45 46 # filter n00bs 47 if not arguments or (len(arguments) != 3): 48 return False, None, None, 'wrong arguments' 49 50 user = arguments[0] 51 auth_type = arguments[1] 52 auth_string = arguments[2] 53 54 # check auth type validity 55 if auth_type not in self.valid_auth_types: 56 return False, user, None, 'invalid auth type' 57 58 udata = self.__get_user_data(user) 59 if udata == None: 60 return False, user, None, 'invalid user' 61 62 uid = udata[2] 63 # check if user is in the Entropy group 64 if not self.entropyTools.is_user_in_entropy_group(uid): 65 return False, user, uid, 'user not in %s group' % (etpConst['sysgroup'],) 66 67 # now validate password 68 valid = self.__validate_auth(user, auth_type, auth_string) 69 if not valid: 70 return False, user, uid, 'auth failed' 71 72 if not uid: 73 self.HostInterface.sessions[self.session]['admin'] = True 74 else: 75 self.HostInterface.sessions[self.session]['user'] = True 76 return True, user, uid, "ok"
77 78 # it we get here is because user is logged in
79 - def docmd_userdata(self):
80 81 auth_uid = self.HostInterface.sessions[self.session]['auth_uid'] 82 mydata = {} 83 udata = self.__get_uid_data(auth_uid) 84 if udata: 85 mydata['username'] = udata[0] 86 mydata['uid'] = udata[2] 87 mydata['gid'] = udata[3] 88 mydata['references'] = udata[4] 89 mydata['home'] = udata[5] 90 mydata['shell'] = udata[6] 91 return True, mydata, 'ok'
92
93 - def __get_uid_data(self, user_id):
94 import pwd 95 # check user validty 96 try: 97 udata = pwd.getpwuid(user_id) 98 except KeyError: 99 return None 100 return udata
101
102 - def __get_user_data(self, user):
103 import pwd 104 # check user validty 105 try: 106 udata = pwd.getpwnam(user) 107 except KeyError: 108 return None 109 return udata
110
111 - def __validate_auth(self, user, auth_type, auth_string):
112 valid = False 113 if auth_type == "plain": 114 valid = self.__do_auth(user, auth_string) 115 elif auth_type == "shadow": 116 valid = self.__do_auth(user, auth_string, auth_type = "shadow") 117 elif auth_type == "md5": 118 valid = self.__do_auth(user, auth_string, auth_type = "md5") 119 return valid
120
121 - def __do_auth(self, user, password, auth_type = None):
122 import spwd 123 124 try: 125 enc_pass = spwd.getspnam(user)[1] 126 except KeyError: 127 return False 128 129 if auth_type == None: # plain 130 import crypt 131 generated_pass = crypt.crypt(str(password), enc_pass) 132 elif auth_type == "shadow": 133 generated_pass = password 134 elif auth_type == "md5": # md5 135 import hashlib 136 m = hashlib.md5() 137 m.update(enc_pass) 138 enc_pass = m.hexdigest() 139 generated_pass = str(password) 140 else: # haha, fuck! 141 generated_pass = None 142 143 if generated_pass == enc_pass: 144 return True 145 return False
146
147 - def docmd_logout(self, myargs):
148 149 # filter n00bs 150 if (len(myargs) < 1) or (len(myargs) > 1): 151 return False, None, 'wrong arguments' 152 153 user = myargs[0] 154 # filter n00bs 155 if not user or not const_isstring(user): 156 return False, None, "wrong user" 157 158 return True, user, "ok"
159
160 - def hide_login_data(self, args):
161 myargs = args[:] 162 myargs[-1] = 'hidden' 163 return myargs
164
165 - class HostServerMixin(socketserver.ThreadingMixIn, socketserver.TCPServer):
166
167 - class ConnWrapper:
168 ''' 169 Base class for implementing the rest of the wrappers in this module. 170 Operates by taking a connection argument which is used when 'self' doesn't 171 provide the functionality being requested. 172 '''
173 - def __init__(self, connection) :
174 self.connection = connection
175
176 - def __getattr__(self, function) :
177 return getattr(self.connection, function)
178 179 import entropy.tools as entropyTools 180 import socket as socket_mod 181 # This means the main server will not do the equivalent of a 182 # pthread_join() on the new threads. With this set, Ctrl-C will 183 # kill the server reliably. 184 daemon_threads = True 185 186 # By setting this we allow the server to re-bind to the address by 187 # setting SO_REUSEADDR, meaning you don't have to wait for 188 # timeouts when you kill the server and the sockets don't get 189 # closed down correctly. 190 allow_reuse_address = True 191
192 - def __init__(self, server_address, RequestHandlerClass, processor, HostInterface, authorized_clients_only = False):
193 194 self.alive = True 195 self.socket = self.socket_mod 196 self.processor = processor 197 self.server_address = server_address 198 self.HostInterface = HostInterface 199 self.SSL = self.HostInterface.SSL 200 self.real_sock = None 201 self.ssl_authorized_clients_only = authorized_clients_only 202 203 if self.SSL: 204 socketserver.BaseServer.__init__(self, server_address, RequestHandlerClass) 205 self.load_ssl_context() 206 self.make_ssl_connection_alive() 207 else: 208 try: 209 socketserver.TCPServer.__init__(self, server_address, RequestHandlerClass) 210 except self.socket_mod.error as e: 211 if e[0] == 13: 212 raise ConnectionError('ConnectionError: %s' % (_("Cannot bind the service"),)) 213 raise
214
215 - def load_ssl_context(self):
216 # setup an SSL context. 217 self.context = self.SSL['m'].Context(self.SSL['m'].SSLv23_METHOD) 218 self.context.set_verify(self.SSL['m'].VERIFY_PEER, self.verify_ssl_cb) # ask for a certificate 219 self.context.set_options(self.SSL['m'].OP_NO_SSLv2) 220 # load up certificate stuff. 221 self.context.use_privatekey_file(self.SSL['key']) 222 self.context.use_certificate_file(self.SSL['cert']) 223 self.context.load_verify_locations(self.SSL['ca_cert']) 224 self.context.load_client_ca(self.SSL['ca_cert']) 225 self.HostInterface.updateProgress('SSL context loaded, key: %s - cert: %s, CA cert: %s, CA pkey: %s' % ( 226 self.SSL['key'], 227 self.SSL['cert'], 228 self.SSL['ca_cert'], 229 self.SSL['ca_pkey'] 230 ) 231 )
232
233 - def make_ssl_connection_alive(self):
234 self.real_sock = self.socket_mod.socket(self.address_family, self.socket_type) 235 self.socket = self.ConnWrapper(self.SSL['m'].Connection(self.context, self.real_sock)) 236 self.server_bind() 237 self.server_activate()
238 239 # this function should do the authentication checking to see that 240 # the client is who they say they are.
241 - def verify_ssl_cb(self, conn, cert, errnum, depth, ok) :
242 return ok
243
244 - def verify_request(self, request, client_address):
245 246 self.do_ssl = self.HostInterface.SSL 247 if self.do_ssl: self.do_ssl = True 248 else: self.do_ssl = False 249 250 allowed = self.ip_blacklist_check(client_address[0]) 251 if allowed: allowed = self.ip_max_connections_check(client_address[0]) 252 if not allowed: 253 self.HostInterface.updateProgress( 254 '[from: %s | SSL: %s] connection refused, ip blacklisted or maximum connections per IP reached' % ( 255 client_address, 256 self.do_ssl, 257 ) 258 ) 259 return False 260 261 allowed = self.max_connections_check(request) 262 if not allowed: 263 self.HostInterface.updateProgress( 264 '[from: %s | SSL: %s] connection refused (max connections reached: %s)' % ( 265 client_address, 266 self.do_ssl, 267 self.HostInterface.max_connections, 268 ) 269 ) 270 return False 271 272 ### let's go! 273 self.HostInterface.connections += 1 274 self.HostInterface.updateProgress( 275 '[from: %s | SSL: %s] connection established (%s of %s max connections)' % ( 276 client_address, 277 self.do_ssl, 278 self.HostInterface.connections, 279 self.HostInterface.max_connections, 280 ) 281 ) 282 return True
283
284 - def ip_blacklist_check(self, client_addr):
285 if client_addr in self.HostInterface.ip_blacklist: 286 return False 287 return True
288
289 - def ip_max_connections_check(self, ip_address):
290 max_conn_per_ip = self.HostInterface.max_connections_per_host 291 max_conn_per_ip_barrier = self.HostInterface.max_connections_per_host_barrier 292 per_host_connections = self.HostInterface.per_host_connections 293 conn_data = per_host_connections.get(ip_address) 294 if conn_data == None: 295 per_host_connections[ip_address] = 1 296 else: 297 conn_data += 1 298 per_host_connections[ip_address] += 1 299 if conn_data > max_conn_per_ip: 300 self.HostInterface.updateProgress( 301 '[from: %s] ------- :EEK: !! connection closed too many simultaneous connections from host (current: %s | limit: %s) -------' % ( 302 ip_address, 303 conn_data, 304 max_conn_per_ip, 305 ) 306 ) 307 return False 308 elif conn_data > max_conn_per_ip_barrier: 309 times = [5, 6, 7, 8] 310 self.HostInterface.updateProgress( 311 '[from: %s] ------- :EEEK: !! connection warning simultaneous connection barrier reached from host (current: %s | soft limit: %s) -------' % ( 312 ip_address, 313 conn_data, 314 max_conn_per_ip_barrier, 315 ) 316 ) 317 rnd_num = self.entropyTools.get_random_number() 318 time.sleep(times[abs(hash(rnd_num))%len(times)]) 319 320 return True
321
322 - def max_connections_check(self, request):
323 current = self.HostInterface.connections 324 maximum = self.HostInterface.max_connections 325 if current >= maximum: 326 try: 327 self.HostInterface.transmit( 328 request, 329 self.HostInterface.answers['mcr'] 330 ) 331 except: 332 pass 333 return False 334 else: 335 return True
336
337 - def serve_forever(self):
338 while self.alive: 339 #r,w,e = select.select([self.socket], [], [], 1) 340 #if r: 341 self.handle_request()
342 343 # taken from SocketServer.py
344 - def finish_request(self, request, client_address):
345 """Finish one request by instantiating RequestHandlerClass.""" 346 self.RequestHandlerClass(request, client_address, self) 347 348 self.HostInterface.updateProgress( 349 '[from: %s] connection closed (%s of %s max connections)' % ( 350 client_address, 351 self.HostInterface.connections - 1, 352 self.HostInterface.max_connections, 353 ) 354 ) 355 per_host_connections = self.HostInterface.per_host_connections 356 conn_data = per_host_connections.get(client_address[0]) 357 if conn_data != None: 358 if conn_data < 1: 359 del per_host_connections[client_address[0]] 360 else: 361 per_host_connections[client_address[0]] -= 1
362
363 - def close_request(self, request):
364 if self.HostInterface.connections > 0: 365 self.HostInterface.connections -= 1
366
367 - class RequestHandler(socketserver.BaseRequestHandler):
368 369 import select 370 import socket 371 import entropy.tools as entropyTools 372 import gc 373 timed_out = False 374
375 - def __init__(self, request, client_address, server):
376 377 # pre-init attribues 378 self.__DEBUG = False 379 self.__buffered_data = None 380 self.__inst_token = self.entropyTools.get_random_number() 381 self.server = None 382 self.request = None 383 self.client_address = None 384 socketserver.BaseRequestHandler.__init__(self, request, 385 client_address, server) 386 self.__data_counter = None
387
388 - def data_receiver(self):
389 390 if self.timed_out: 391 return True 392 self.timed_out = True 393 try: 394 ready_to_read, ready_to_write, in_error = select.select( 395 [self.request], [], [], self.default_timeout) 396 except KeyboardInterrupt: 397 self.timed_out = True 398 return True 399 400 buf_len = 16384 401 if len(ready_to_read) == 1 and ready_to_read[0] == self.request: 402 403 self.timed_out = False 404 # for ValueError exception trapping: 405 data = None 406 407 if self.__DEBUG: 408 self.server.processor.HostInterface.updateProgress( 409 '[from: %s] request arrived :: counter: %s | buf_data: %s' % ( 410 self.client_address, 411 self.__data_counter, 412 len(self.__buffered_data), 413 ) 414 ) 415 416 try: 417 418 if self.ssl and hasattr(self.request, 'setblocking'): 419 # set SSL socket in blocking mode 420 # this fixes bugs related to data stream flooding 421 # with SSL - pyOpenSSL, probably because handshake 422 # and WantRead/WantWrite bullshit is handled 423 # automatically 424 self.request.setblocking(True) 425 426 data = self.request.recv(buf_len) 427 if self.ssl: 428 while self.request.pending(): 429 data += self.request.recv(buf_len) 430 431 if self.__data_counter is None: 432 if not data: # client wants to close 433 return True 434 elif data == self.server.processor.HostInterface.answers['noop']: 435 return False 436 elif len(data) < len(self.myeos): 437 self.server.processor.HostInterface.updateProgress( 438 'interrupted: %s, reason: %s - from client: %s - data: "%s" - counter: %s' % ( 439 self.server.server_address, 440 "malformed EOS", 441 self.client_address, 442 repr(data), 443 self.__data_counter, 444 ) 445 ) 446 self.__buffered_data = const_convert_to_rawstring('') 447 return True 448 mystrlen = data.split(self.myeos)[0] 449 self.__data_counter = int(mystrlen) 450 data = data[len(mystrlen)+1:] 451 self.__data_counter -= len(data) 452 self.__buffered_data += data 453 454 # command length exceeds our command length limit 455 if self.__data_counter > self.max_command_length: 456 raise InterruptError( 457 'InterruptError: command too long: %s, limit: %s' % ( 458 self.__data_counter, self.max_command_length,)) 459 460 buf_empty_watchdog_count = 50 # * 0.05 = 2,5 seconds 461 while self.__data_counter > 0: 462 data_buf = buf_len 463 if self.__data_counter < buf_len: 464 data_buf = self.__data_counter 465 if self.ssl: 466 x = self.request.recv(data_buf) 467 else: 468 x = self.request.recv(data_buf) 469 xlen = len(x) 470 self.__data_counter -= xlen 471 self.__buffered_data += x 472 # if we did not receive a shit and we still 473 # need some data, trigger the watchdog 474 if (xlen == 0) and (self.__data_counter > 0): 475 buf_empty_watchdog_count -= 1 476 time.sleep(0.05) 477 if buf_empty_watchdog_count < 1: 478 raise ValueError( 479 "buffer counter watchdog trigger") 480 481 self.__data_counter = None 482 except ValueError: 483 tb = self.entropyTools.get_traceback() 484 print(tb) 485 self.server.processor.HostInterface.socketLog.write(tb) 486 self.server.processor.HostInterface.socketLog.write(repr(data)) 487 self.server.processor.HostInterface.socketLog.write(repr(self)) 488 self.server.processor.HostInterface.socketLog.write(repr(self.__inst_token)) 489 self.server.processor.HostInterface.updateProgress( 490 'interrupted: %s, reason: %s - from client: %s' % ( 491 self.server.server_address, 492 "malformed transmission", 493 self.client_address, 494 ) 495 ) 496 return True 497 except self.socket.timeout as e: 498 self.server.processor.HostInterface.updateProgress( 499 'interrupted: %s, reason: %s - from client: %s' % ( 500 self.server.server_address, 501 e, 502 self.client_address, 503 ) 504 ) 505 return True 506 except self.socket.sslerror as e: 507 self.server.processor.HostInterface.updateProgress( 508 'interrupted: %s, SSL socket error reason: %s - from client: %s' % ( 509 self.server.server_address, 510 e, 511 self.client_address, 512 ) 513 ) 514 return True 515 except self.ssl_exceptions['WantX509LookupError']: 516 return False 517 except self.ssl_exceptions['WantReadError']: 518 self.server.processor.HostInterface._ssl_poll( 519 self.request, select.POLLIN, 'read') 520 return False 521 except self.ssl_exceptions['WantWriteError']: 522 self.server.processor.HostInterface._ssl_poll( 523 self.request, select.POLLOUT, 'read') 524 return False 525 except self.ssl_exceptions['ZeroReturnError']: 526 return True 527 except self.ssl_exceptions['Error'] as e: 528 self.server.processor.HostInterface.updateProgress( 529 'interrupted: SSL Error, reason: %s - from client: %s' % ( 530 e, 531 self.client_address, 532 ) 533 ) 534 return True 535 except InterruptError as e: 536 self.server.processor.HostInterface.updateProgress( 537 'interrupted: Command Error, reason: %s - from client: %s' % ( 538 e, 539 self.client_address, 540 ) 541 ) 542 return True 543 544 if not self.__buffered_data: 545 return True 546 547 if etpUi['debug']: 548 const_debug_write(__name__, darkred("=== recv ======== \\")) 549 const_debug_write(__name__, darkred(repr(self.__buffered_data))) 550 const_debug_write(__name__, 551 darkred("=== recv[%s] ======== /" % (len(self.__buffered_data),))) 552 553 cmd = self.server.processor.process(self.__buffered_data, self.request, self.client_address) 554 if cmd == 'close': 555 # send KAPUTT signal JA! 556 self.server.processor.transmit(self.server.processor.HostInterface.answers['cl']) 557 return True 558 self.__buffered_data = const_convert_to_rawstring('') 559 return False
560
561 - def fork_lock_acquire(self):
562 if hasattr(self.server.processor.HostInterface, 'ForkLock'): 563 x = getattr(self.server.processor.HostInterface, 'ForkLock') 564 if hasattr(x, 'acquire') and hasattr(x, 'release') and hasattr(x, 'locked'): 565 x.acquire()
566
567 - def fork_lock_release(self):
568 if hasattr(self.server.processor.HostInterface, 'ForkLock'): 569 x = getattr(self.server.processor.HostInterface, 'ForkLock') 570 if hasattr(x, 'acquire') and hasattr(x, 'release') and hasattr(x, 'locked'): 571 if x.locked(): x.release()
572
573 - def handle(self):
574 # not using spawnFunction because it causes some mess 575 # forking this way avoids having memory leaks 576 if self.server.processor.HostInterface.fork_requests: 577 self.fork_lock_acquire() 578 try: 579 my_timeout = self.server.processor.HostInterface.fork_request_timeout_seconds 580 pid = os.fork() 581 seconds = 0 582 if pid > 0: # parent here 583 # pid killer after timeout 584 passed_away = False 585 while True: 586 time.sleep(1) 587 seconds += 1 588 try: 589 dead = os.waitpid(pid, os.WNOHANG)[0] 590 except OSError as e: 591 if e.errno != 10: 592 raise 593 dead = True 594 if passed_away: 595 break 596 if dead: 597 break 598 if seconds > my_timeout: 599 self.server.processor.HostInterface.updateProgress( 600 'interrupted: forked request timeout: %s,%s from client: %s' % ( 601 seconds, 602 dead, 603 self.client_address, 604 ) 605 ) 606 if not dead: 607 import signal 608 os.kill(pid, signal.SIGKILL) 609 passed_away = True # in this way, the process table should be clean 610 continue 611 break 612 else: 613 self.do_handle() 614 os._exit(0) 615 finally: 616 self.fork_lock_release() 617 else: 618 self.do_handle()
619 #self.entropyTools.spawn_function(self.do_handle) 620
621 - def do_handle(self):
622 623 self.default_timeout = self.server.processor.HostInterface.timeout 624 self.ssl = self.server.processor.HostInterface.SSL 625 self.ssl_exceptions = self.server.processor.HostInterface.SSL_exceptions 626 self.myeos = self.server.processor.HostInterface.answers['eos'] 627 self.max_command_length = self.server.processor.HostInterface.max_command_length 628 629 while True: 630 631 try: 632 if self.__DEBUG: 633 self.server.processor.HostInterface.updateProgress( 634 '[from: %s] calling data_receiver' % ( 635 self.client_address, 636 ) 637 ) 638 dobreak = self.data_receiver() 639 if self.__DEBUG: 640 self.server.processor.HostInterface.updateProgress( 641 '[from: %s] quitting data_receiver :: dobreak: %s' % ( 642 self.client_address, 643 dobreak, 644 ) 645 ) 646 if dobreak: 647 break 648 except Exception as e: 649 self.server.processor.HostInterface.updateProgress( 650 'interrupted: Unhandled exception: %s, error: %s - from client: %s' % ( 651 Exception, 652 e, 653 self.client_address, 654 ) 655 ) 656 # print exception 657 tb = self.entropyTools.get_traceback() 658 print(tb) 659 self.server.processor.HostInterface.socketLog.write(tb) 660 break 661 662 self.request.close()
663
664 - def setup(self):
665 self.__data_counter = None 666 self.__buffered_data = ''
667 668
669 - class CommandProcessor:
670 671 import entropy.tools as entropyTools 672 import socket 673 import gc 674
675 - def __init__(self, HostInterface):
676 self.HostInterface = HostInterface 677 self.channel = None
678
679 - def handle_termination_commands(self, data):
680 if data.strip() in self.HostInterface.termination_commands: 681 self.HostInterface.updateProgress('close: %s' % (self.client_address,)) 682 self.transmit(self.HostInterface.answers['cl']) 683 return "close" 684 685 if not data.strip(): 686 return "ignore"
687
688 - def handle_command_string(self, string):
689 # validate command 690 args = string.strip().split(" ") 691 session = args[0] 692 if (session in self.HostInterface.initialization_commands) or \ 693 (session in self.HostInterface.no_session_commands) or \ 694 len(args) < 2: 695 cmd = args[0] 696 session = None 697 else: 698 cmd = args[1] 699 args = args[1:] # remove session 700 701 stream_enabled = False 702 if (session != None) and session in self.HostInterface.sessions: 703 stream_enabled = self.HostInterface.sessions[session].get('stream_mode') 704 705 if stream_enabled and (cmd not in self.HostInterface.config_commands): 706 session_len = 0 707 if session: session_len = len(session)+1 708 return cmd, [string[session_len+len(cmd)+1:]], session 709 else: 710 myargs = [] 711 if len(args) > 1: 712 myargs = args[1:] 713 714 return cmd, myargs, session
715
716 - def handle_end_answer(self, cmd, whoops, valid_cmd):
717 if not valid_cmd: 718 self.transmit(self.HostInterface.answers['no']) 719 elif whoops: 720 self.transmit(self.HostInterface.answers['er']) 721 elif cmd not in self.HostInterface.no_acked_commands: 722 self.transmit(self.HostInterface.answers['ok'])
723
724 - def validate_command(self, cmd, args, session):
725 726 # answer to invalid commands 727 if (cmd not in self.HostInterface.valid_commands): 728 return False, "not a valid command" 729 730 if session == None: 731 if cmd not in self.HostInterface.no_session_commands: 732 return False, "need a valid session" 733 elif session not in self.HostInterface.sessions: 734 return False, "session is not alive" 735 736 # check if command needs authentication 737 if session != None: 738 auth = self.HostInterface.valid_commands[cmd]['auth'] 739 if auth: 740 # are we? 741 authed = self.HostInterface.sessions[session]['auth_uid'] 742 if authed == None: 743 # nope 744 return False, "not authenticated" 745 746 # keep session alive 747 if session != None: 748 self.HostInterface.set_session_running(session) 749 self.HostInterface.update_session_time(session) 750 751 return True, "all good"
752
753 - def load_authenticator(self):
754 f, args, kwargs = self.HostInterface.AuthenticatorInst 755 myinst = f(*args,**kwargs) 756 return myinst
757
758 - def load_service_interface(self, session):
759 760 uid = None 761 if session != None: 762 uid = self.HostInterface.sessions[session]['auth_uid'] 763 764 intf = self.HostInterface.EntropyInstantiation[0] 765 args = self.HostInterface.EntropyInstantiation[1] 766 kwds = self.HostInterface.EntropyInstantiation[2] 767 return intf(*args, **kwds)
768
769 - def process(self, data, channel, client_address):
770 771 self.channel = channel 772 self.client_address = client_address 773 774 term = self.handle_termination_commands(data) 775 if term: 776 return term 777 778 cmd, args, session = self.handle_command_string(data) 779 valid_cmd, reason = self.validate_command(cmd, args, session) 780 781 # decide if we need to load authenticator or Entropy 782 authenticator = None 783 cmd_data = self.HostInterface.valid_commands.get(cmd) 784 if not isinstance(cmd_data, dict): 785 self.HostInterface.updateProgress( 786 '[from: %s] command error: invalid command: %s' % ( 787 self.client_address, 788 cmd, 789 ) 790 ) 791 return "close" 792 elif (("authenticator" in cmd_data['args']) or (cmd in self.HostInterface.login_pass_commands)): 793 try: 794 authenticator = self.load_authenticator() 795 except ConnectionError as e: 796 self.HostInterface.updateProgress( 797 '[from: %s] authenticator error: cannot load: %s' % ( 798 self.client_address, 799 e, 800 ) 801 ) 802 tb = self.entropyTools.get_traceback() 803 print(tb) 804 self.HostInterface.socketLog.write(tb) 805 return "close" 806 except Exception as e: 807 self.HostInterface.updateProgress( 808 '[from: %s] authenticator error: cannot load: %s - unknown error' % ( 809 self.client_address, 810 e, 811 ) 812 ) 813 tb = self.entropyTools.get_traceback() 814 print(tb) 815 self.HostInterface.socketLog.write(tb) 816 return "close" 817 818 p_args = args 819 if (cmd in self.HostInterface.login_pass_commands) and authenticator != None: 820 p_args = authenticator.hide_login_data(p_args) 821 elif cmd in self.HostInterface.raw_commands: 822 p_args = ['raw data'] 823 data_len = len(data) 824 825 # beautify interface if debug mode is on 826 out_cmd = cmd 827 out_data_len = data_len 828 out_session = session 829 out_valid_cmd = valid_cmd 830 out_reason = reason 831 out_args = p_args 832 if etpUi['debug']: 833 out_cmd = purple(out_cmd) 834 out_data_len = brown(str(out_data_len)) 835 out_session = darkgreen(str(out_session)) 836 out_valid_cmd = darkgreen(str(out_valid_cmd)) 837 out_reason = darkblue(str(out_reason)) 838 out_args = brown(str(out_args)) 839 840 self.HostInterface.updateProgress( 841 '[from: %s] command validation :: called %s: length: %s, args: ' 842 '%s, session: %s, valid: %s, reason: %s' % ( 843 self.client_address, 844 out_cmd, 845 out_data_len, 846 out_args, 847 out_session, 848 out_valid_cmd, 849 out_reason, 850 ) 851 ) 852 853 whoops = False 854 if valid_cmd: 855 856 if authenticator != None: 857 # now set session 858 authenticator.set_session(session) 859 860 Entropy = None 861 if "Entropy" in cmd_data['args']: 862 Entropy = self.load_service_interface(session) 863 try: 864 run_task_out = self.run_task(cmd, args, session, Entropy, authenticator) 865 if etpUi['debug']: 866 self.HostInterface.updateProgress( 867 '[from: %s] command executed: result %s' % ( 868 self.client_address, 869 run_task_out, 870 ) 871 ) 872 except self.socket.timeout: 873 self.HostInterface.updateProgress( 874 '[from: %s] command error: timeout, closing connection' % ( 875 self.client_address, 876 ) 877 ) 878 # close connection 879 del authenticator 880 del Entropy 881 return "close" 882 except self.socket.error as e: 883 self.HostInterface.updateProgress( 884 '[from: %s] command error: socket error: %s' % ( 885 self.client_address, 886 e, 887 ) 888 ) 889 # close connection 890 del authenticator 891 del Entropy 892 return "close" 893 except self.HostInterface.SSL_exceptions['SysCallError'] as e: 894 self.HostInterface.updateProgress( 895 '[from: %s] command error: SSL SysCallError: %s' % ( 896 self.client_address, 897 e, 898 ) 899 ) 900 # close connection 901 del authenticator 902 del Entropy 903 return "close" 904 except Exception as e: 905 # write to self.HostInterface.socketLog 906 tb = self.entropyTools.get_traceback() 907 print(tb) 908 self.HostInterface.socketLog.write(tb) 909 # store error 910 self.HostInterface.updateProgress( 911 '[from: %s] command error: %s, type: %s' % ( 912 self.client_address, 913 e, 914 type(e), 915 ) 916 ) 917 if session != None: 918 self.HostInterface.store_rc(str(e), session) 919 whoops = True 920 921 del Entropy 922 923 if session != None: 924 self.HostInterface.update_session_time(session) 925 self.HostInterface.unset_session_running(session) 926 rcmd = None 927 try: 928 self.handle_end_answer(cmd, whoops, valid_cmd) 929 except (self.socket.error, self.socket.timeout, self.HostInterface.SSL_exceptions['SysCallError'],): 930 rcmd = "close" 931 932 if authenticator != None: 933 authenticator.terminate_instance() 934 del authenticator 935 if not self.HostInterface.fork_requests: 936 self.gc.collect() 937 return rcmd
938
939 - def transmit(self, data):
940 941 if etpUi['debug']: 942 const_debug_write(__name__, darkblue("=== send ======== \\")) 943 const_debug_write(__name__, darkblue(repr(data))) 944 const_debug_write(__name__, 945 darkblue("=== send[%s] ======== /" % (len(data),))) 946 947 self.HostInterface.transmit(self.channel, data)
948
949 - def run_task(self, cmd, args, session, Entropy, authenticator):
950 951 p_args = args 952 if cmd in self.HostInterface.login_pass_commands: 953 p_args = authenticator.hide_login_data(p_args) 954 elif cmd in self.HostInterface.raw_commands: 955 p_args = ['raw data'] 956 self.HostInterface.updateProgress( 957 '[from: %s] run_task :: called %s: args: %s, session: %s' % ( 958 self.client_address, 959 cmd, 960 p_args, 961 session, 962 ) 963 ) 964 965 myargs = args 966 mykwargs = {} 967 if cmd not in self.HostInterface.raw_commands: 968 myargs, mykwargs = self._get_args_kwargs(args) 969 970 rc = self.spawn_function(cmd, myargs, mykwargs, session, Entropy, authenticator) 971 if session != None and session in self.HostInterface.sessions: 972 self.HostInterface.store_rc(rc, session) 973 return rc
974
975 - def _get_args_kwargs(self, args):
976 myargs = [] 977 mykwargs = {} 978 979 def is_int(x): 980 try: 981 int(x) 982 except ValueError: 983 return False 984 return True
985 986 for arg in args: 987 if (arg.find("=") != -1) and not arg.startswith("="): 988 x = arg.split("=") 989 a = x[0] 990 b = ''.join(x[1:]) 991 if (b in ("True", "False",)) or is_int(b): 992 mykwargs[a] = eval(b) 993 else: 994 myargs.append(arg) 995 else: 996 if (arg in ("True", "False",)) or is_int(arg): 997 myargs.append(eval(arg)) 998 else: 999 myargs.append(arg) 1000 return myargs, mykwargs
1001
1002 - def spawn_function(self, cmd, myargs, mykwargs, session, Entropy, authenticator):
1003 1004 p_args = myargs 1005 if cmd in self.HostInterface.login_pass_commands: 1006 p_args = authenticator.hide_login_data(p_args) 1007 elif cmd in self.HostInterface.raw_commands: 1008 p_args = ['raw data'] 1009 self.HostInterface.updateProgress( 1010 '[from: %s] called %s: args: %s, kwargs: %s' % ( 1011 self.client_address, 1012 cmd, 1013 p_args, 1014 mykwargs, 1015 ) 1016 ) 1017 return self.do_spawn(cmd, myargs, mykwargs, session, Entropy, authenticator)
1018
1019 - def do_spawn(self, cmd, myargs, mykwargs, session, Entropy, authenticator):
1020 1021 cmd_data = self.HostInterface.valid_commands.get(cmd) 1022 do_fork = cmd_data['as_user'] 1023 f = cmd_data['cb'] 1024 func_args = [] 1025 for arg in cmd_data['args']: 1026 try: 1027 func_args.append(eval(arg)) 1028 except (NameError, SyntaxError): 1029 func_args.append(str(arg)) 1030 1031 if do_fork: 1032 myfargs = func_args[:] 1033 myfargs.extend(myargs) 1034 return self.fork_task(f, session, authenticator, *myfargs, **mykwargs) 1035 else: 1036 return f(*func_args)
1037
1038 - def fork_task(self, f, session, authenticator, *args, **kwargs):
1039 gid = None 1040 uid = None 1041 if session != None: 1042 logged_in = self.HostInterface.sessions[session]['auth_uid'] 1043 if logged_in != None: 1044 uid = logged_in 1045 gid = etpConst['entropygid'] 1046 return self.entropyTools.spawn_function(self._do_fork, f, authenticator, uid, gid, *args, **kwargs)
1047
1048 - def _do_fork(self, f, authenticator, uid, gid, *args, **kwargs):
1049 authenticator.set_exc_permissions(uid, gid) 1050 rc = f(*args,**kwargs) 1051 return rc
1052
1053 - class BuiltInCommands(SocketCommands):
1054 1055 import entropy.dump as dumpTools 1056 import zlib 1057
1058 - def __init__(self, HostInterface):
1059 1060 SocketCommands.__init__(self, HostInterface, inst_name = "builtin") 1061 1062 self.valid_commands = { 1063 'begin': { 1064 'auth': False, # does it need authentication ? 1065 'built_in': True, # is it built-in ? 1066 'cb': self.docmd_begin, # function to call 1067 'args': ["self.transmit", "self.client_address"], # arguments to be passed before *args and **kwards, in SocketHostInterface.do_spawn() 1068 'as_user': False, # do I have to fork the process and run it as logged user? 1069 # needs auth = True 1070 'desc': "instantiate a session", # description 1071 'syntax': "begin", # syntax 1072 'from': str(self), # from what class 1073 }, 1074 'end': { 1075 'auth': False, 1076 'built_in': True, 1077 'cb': self.docmd_end, 1078 'args': ["self.transmit", "session"], 1079 'as_user': False, 1080 'desc': "end a session", 1081 'syntax': "<SESSION_ID> end", 1082 'from': str(self), 1083 }, 1084 'session_config': { 1085 'auth': False, 1086 'built_in': True, 1087 'cb': self.docmd_session_config, 1088 'args': ["session", "myargs"], 1089 'as_user': False, 1090 'desc': "set session configuration options", 1091 'syntax': "<SESSION_ID> session_config <option> [parameters]", 1092 'from': str(self), 1093 }, 1094 'rc': { 1095 'auth': False, 1096 'built_in': True, 1097 'cb': self.docmd_rc, 1098 'args': ["self.transmit", "session"], 1099 'as_user': False, 1100 'desc': "get data returned by the last valid command (streamed python object)", 1101 'syntax': "<SESSION_ID> rc", 1102 'from': str(self), 1103 }, 1104 'hello': { 1105 'auth': False, 1106 'built_in': True, 1107 'cb': self.docmd_hello, 1108 'args': ["self.transmit"], 1109 'as_user': False, 1110 'desc': "get server status", 1111 'syntax': "hello", 1112 'from': str(self), 1113 }, 1114 'alive': { 1115 'auth': True, 1116 'built_in': True, 1117 'cb': self.docmd_alive, 1118 'args': ["self.transmit", "self.client_address", "myargs"], 1119 'as_user': False, 1120 'desc': "check if a session is still alive", 1121 'syntax': "alive <SESSION_ID>", 1122 'from': str(self), 1123 }, 1124 'login': { 1125 'auth': False, 1126 'built_in': True, 1127 'cb': self.docmd_login, 1128 'args': ["self.transmit", "authenticator", "session", "self.client_address", "myargs"], 1129 'as_user': False, 1130 'desc': "login on the running server (allows running extra commands)", 1131 'syntax': "<SESSION_ID> login <authenticator parameters, default: <user> <auth_type> <password> >", 1132 'from': str(self), 1133 }, 1134 'user_data': { 1135 'auth': True, 1136 'built_in': True, 1137 'cb': self.docmd_userdata, 1138 'args': ["self.transmit", "authenticator", "session"], 1139 'as_user': False, 1140 'desc': "get general user information, user must be logged in", 1141 'syntax': "<SESSION_ID> user_data", 1142 'from': str(self), 1143 }, 1144 'logout': { 1145 'auth': True, 1146 'built_in': True, 1147 'cb': self.docmd_logout, 1148 'args': ["self.transmit", "authenticator", "session", "self.client_address", "myargs"], 1149 'as_user': False, 1150 'desc': "logout on the running server", 1151 'syntax': "<SESSION_ID> logout <USER>", 1152 'from': str(self), 1153 }, 1154 'help': { 1155 'auth': False, 1156 'built_in': True, 1157 'cb': self.docmd_help, 1158 'args': ["self.transmit"], 1159 'as_user': False, 1160 'desc': "this output", 1161 'syntax': "help", 1162 'from': str(self), 1163 }, 1164 'available_commands': { 1165 'auth': False, 1166 'built_in': True, 1167 'cb': self.docmd_available_commands, 1168 'args': ["self.HostInterface"], 1169 'as_user': False, 1170 'desc': "get info about available commands (you must retrieve this using the 'rc' command)", 1171 'syntax': "available_commands", 1172 'from': str(self), 1173 }, 1174 'stream': { 1175 'auth': True, 1176 'built_in': True, 1177 'cb': self.docmd_stream, 1178 'args': ["session", "myargs"], 1179 'as_user': False, 1180 'desc': "send a chunk of data to be saved on the session temp file path (will be removed on session expiration)", 1181 'syntax': "<SESSION_ID> stream <chunk of byte-string to write to file>", 1182 'from': str(self), 1183 }, 1184 } 1185 1186 self.no_acked_commands = ["rc", "begin", "end", "hello", "alive", "login", "logout", "help"] 1187 self.termination_commands = ["quit", "close"] 1188 self.initialization_commands = ["begin"] 1189 self.login_pass_commands = ["login"] 1190 self.no_session_commands = ["begin", "hello", "alive", "help"] 1191 self.raw_commands = ["stream"] 1192 self.config_commands = ["session_config"]
1193
1194 - def docmd_session_config(self, session, myargs):
1195 1196 if not myargs: 1197 return False, "not enough parameters" 1198 1199 option = myargs[0] 1200 myopts = myargs[1:] 1201 1202 if option == "compression": 1203 docomp = True 1204 do_zlib = False 1205 if "zlib" in myopts: 1206 do_zlib = True 1207 if myopts: 1208 if isinstance(myopts[0], bool): 1209 docomp = myopts[0] 1210 else: 1211 try: 1212 docomp = eval(myopts[0]) 1213 except (NameError, TypeError,): 1214 pass 1215 if docomp and do_zlib: 1216 docomp = "zlib" 1217 elif docomp and not do_zlib: 1218 docomp = "gzip" 1219 else: 1220 docomp = None 1221 self.HostInterface.sessions[session]['compression'] = docomp 1222 return True, "compression now: %s" % (docomp,) 1223 elif option == "stream": 1224 dostream = True 1225 if "off" in myopts: 1226 dostream = False 1227 self.HostInterface.sessions[session]['stream_mode'] = dostream 1228 return True, 'stream mode: %s' % (dostream,) 1229 else: 1230 return False, "invalid config option"
1231
1232 - def docmd_available_commands(self, host_interface):
1233 1234 def copy_obj(obj): 1235 if isinstance(obj, set) or isinstance(obj, dict): 1236 return obj.copy() 1237 elif isinstance(obj, list) or isinstance(obj, tuple): 1238 return obj[:] 1239 return obj
1240 1241 def can_be_streamed(obj): 1242 base_objs = (bool, int, float, list, tuple, set, dict) + \ 1243 const_get_stringtype() 1244 if isinstance(obj, base_objs): 1245 return True 1246 return False
1247 1248 mydata = {} 1249 mydata['disabled_commands'] = copy_obj(host_interface.disabled_commands) 1250 valid_cmds = copy_obj(host_interface.valid_commands) 1251 mydata['valid_commands'] = {} 1252 for cmd in valid_cmds: 1253 mydict = {} 1254 for item in valid_cmds[cmd]: 1255 param = valid_cmds[cmd][item] 1256 if not can_be_streamed(param): 1257 continue 1258 mydict[item] = param 1259 mydata['valid_commands'][cmd] = mydict.copy() 1260 1261 return mydata 1262
1263 - def docmd_stream(self, session, myargs):
1264 1265 if not self.HostInterface.sessions[session]['stream_mode']: 1266 return False, 'not in stream mode' 1267 if not myargs: 1268 return False, 'no stream sent' 1269 1270 compression = self.HostInterface.sessions[session]['compression'] 1271 1272 stream = myargs[0] 1273 stream_path = self.HostInterface.sessions[session]['stream_path'] 1274 stream_dir = os.path.dirname(stream_path) 1275 if not os.path.isdir(os.path.dirname(stream_path)): 1276 try: 1277 os.makedirs(stream_dir) 1278 if etpConst['entropygid'] != None: 1279 const_setup_perms(stream_dir, etpConst['entropygid']) 1280 except OSError: 1281 return False, 'cannot initialize stream directory' 1282 1283 f = open(stream_path, 'abw') 1284 if compression: 1285 stream = self.zlib.decompress(stream) 1286 f.write(stream) 1287 f.flush() 1288 f.close() 1289 1290 return True, 'ok'
1291
1292 - def docmd_login(self, transmitter, authenticator, session, client_address, myargs):
1293 1294 # is already auth'd? 1295 auth_uid = self.HostInterface.sessions[session]['auth_uid'] 1296 if auth_uid != None: 1297 return False, "already authenticated" 1298 1299 status, user, uid, reason = authenticator.docmd_login(myargs) 1300 if status: 1301 self.HostInterface.updateProgress( 1302 '[from: %s] user %s logged in successfully, session: %s' % ( 1303 client_address, 1304 user, 1305 session, 1306 ) 1307 ) 1308 self.HostInterface.sessions[session]['auth_uid'] = uid 1309 transmitter(self.HostInterface.answers['ok']) 1310 return True, reason 1311 elif user == None: 1312 self.HostInterface.updateProgress( 1313 '[from: %s] user -not specified- login failed, session: %s, reason: %s' % ( 1314 client_address, 1315 session, 1316 reason, 1317 ) 1318 ) 1319 transmitter(self.HostInterface.answers['no']) 1320 return False, reason 1321 else: 1322 self.HostInterface.updateProgress( 1323 '[from: %s] user %s login failed, session: %s, reason: %s' % ( 1324 client_address, 1325 user, 1326 session, 1327 reason, 1328 ) 1329 ) 1330 transmitter(self.HostInterface.answers['no']) 1331 return False, reason
1332
1333 - def docmd_userdata(self, transmitter, authenticator, session):
1334 1335 auth_uid = self.HostInterface.sessions[session]['auth_uid'] 1336 if auth_uid == None: 1337 return False, None, "not authenticated" 1338 1339 return authenticator.docmd_userdata()
1340
1341 - def docmd_logout(self, transmitter, authenticator, session, client_address, myargs):
1342 status, user, reason = authenticator.docmd_logout(myargs) 1343 if status: 1344 self.HostInterface.updateProgress( 1345 '[from: %s] user %s logged out successfully, session: %s, args: %s ' % ( 1346 client_address, 1347 user, 1348 session, 1349 myargs, 1350 ) 1351 ) 1352 self.HostInterface.sessions[session]['auth_uid'] = None 1353 transmitter(self.HostInterface.answers['ok']) 1354 return True, reason 1355 elif user == None: 1356 self.HostInterface.updateProgress( 1357 '[from: %s] user -not specified- logout failed, session: %s, args: %s, reason: %s' % ( 1358 client_address, 1359 session, 1360 myargs, 1361 reason, 1362 ) 1363 ) 1364 transmitter(self.HostInterface.answers['no']) 1365 return False, reason 1366 else: 1367 self.HostInterface.updateProgress( 1368 '[from: %s] user %s logout failed, session: %s, args: %s, reason: %s' % ( 1369 client_address, 1370 user, 1371 session, 1372 myargs, 1373 reason, 1374 ) 1375 ) 1376 transmitter(self.HostInterface.answers['no']) 1377 return False, reason
1378
1379 - def docmd_alive(self, transmitter, client_address, myargs):
1380 cmd = self.HostInterface.answers['no'] 1381 alive = False 1382 if myargs: 1383 session_data = self.HostInterface.sessions.get(myargs[0]) 1384 if session_data != None: 1385 if client_address[0] == session_data.get('ip_address'): 1386 cmd = self.HostInterface.answers['ok'] 1387 alive = True 1388 transmitter(cmd) 1389 return alive
1390
1391 - def docmd_hello(self, transmitter):
1392 from entropy.tools import getstatusoutput 1393 from entropy.core.settings.base import SystemSettings 1394 sys_settings = SystemSettings() 1395 uname = os.uname() 1396 kern_string = uname[2] 1397 running_host = uname[1] 1398 running_arch = uname[4] 1399 load_stats = getstatusoutput('uptime')[1].split("\n")[0] 1400 text = "Entropy Server %s, connections: %s ~ running on: %s ~ host: %s ~ arch: %s, kernel: %s, stats: %s\n" % ( 1401 etpConst['entropyversion'], 1402 self.HostInterface.connections, 1403 sys_settings['system']['name'], 1404 running_host, 1405 running_arch, 1406 kern_string, 1407 load_stats 1408 ) 1409 transmitter(text)
1410
1411 - def docmd_help(self, transmitter):
1412 text = '\nEntropy Socket Interface Help Menu\n' + \ 1413 'Available Commands:\n\n' 1414 valid_cmds = sorted(self.HostInterface.valid_commands.keys()) 1415 for cmd in valid_cmds: 1416 if 'desc' in self.HostInterface.valid_commands[cmd]: 1417 desc = self.HostInterface.valid_commands[cmd]['desc'] 1418 else: 1419 desc = 'no description available' 1420 1421 if 'syntax' in self.HostInterface.valid_commands[cmd]: 1422 syntax = self.HostInterface.valid_commands[cmd]['syntax'] 1423 else: 1424 syntax = 'no syntax available' 1425 if 'from' in self.HostInterface.valid_commands[cmd]: 1426 myfrom = self.HostInterface.valid_commands[cmd]['from'] 1427 else: 1428 myfrom = 'N/A' 1429 text += "[%s] %s\n %s: %s\n %s: %s\n" % ( 1430 myfrom, 1431 blue(cmd), 1432 red("description"), 1433 desc.strip(), 1434 darkgreen("syntax"), 1435 syntax, 1436 ) 1437 transmitter(text)
1438
1439 - def docmd_end(self, transmitter, session):
1440 rc = self.HostInterface.destroy_session(session) 1441 cmd = self.HostInterface.answers['no'] 1442 if rc: 1443 cmd = self.HostInterface.answers['ok'] 1444 transmitter(cmd) 1445 return rc
1446
1447 - def docmd_begin(self, transmitter, client_address):
1448 session = self.HostInterface.get_new_session(client_address[0]) 1449 transmitter(session) 1450 return session
1451
1452 - def docmd_rc(self, transmitter, session):
1453 rc = self.HostInterface.get_rc(session) 1454 comp = self.HostInterface.sessions[session]['compression'] 1455 myserialized = self.dumpTools.serialize_string(rc) 1456 if comp == "zlib": # new shiny zlib 1457 myserialized = self.zlib.compress(myserialized, 7) # compression level 1-9 1458 elif comp == "gzip": # old and burried 1459 import gzip 1460 try: 1461 import io as stringio 1462 except ImportError: 1463 import io as stringio 1464 f = stringio.StringIO() 1465 self.dumpTools.serialize(rc, f) 1466 myf = stringio.StringIO() 1467 mygz = gzip.GzipFile( 1468 mode = 'wb', 1469 fileobj = myf 1470 ) 1471 f.seek(0) 1472 chunk = f.read(8192) 1473 while chunk: 1474 mygz.write(chunk) 1475 chunk = f.read(8192) 1476 mygz.flush() 1477 mygz.close() 1478 myserialized = myf.getvalue() 1479 f.close() 1480 myf.close() 1481 1482 1483 transmitter(myserialized) 1484 1485 return rc
1486
1487 - def __init__(self, service_interface, *args, **kwds):
1488 1489 import gc 1490 self.gc = gc 1491 import threading 1492 self.threading = threading 1493 import entropy.tools as entropyTools 1494 from entropy.misc import TimeScheduled 1495 self.TimeScheduled = TimeScheduled 1496 self.entropyTools = entropyTools 1497 self.Server = None 1498 self.Gc = None 1499 self.PythonGarbageCollector = None 1500 self.AuthenticatorInst = None 1501 1502 self.args = args 1503 self.kwds = kwds 1504 from entropy.misc import LogFile 1505 self.socketLog = LogFile( 1506 level = etpConst['socketloglevel'], 1507 filename = etpConst['socketlogfile'], 1508 header = "[Socket]" 1509 ) 1510 1511 # settings 1512 from entropy.core.settings.base import SystemSettings 1513 import copy 1514 """ 1515 SystemSettings is a singleton, and we just need to read 1516 socket configuration. we don't want to mess other instances 1517 so we pay attention to not use it more than what is needed. 1518 """ 1519 sys_settings = SystemSettings() 1520 self.__socket_settings = copy.deepcopy(sys_settings['socket_service']) 1521 1522 self.SessionsLock = self.threading.Lock() 1523 self.fork_requests = True # used by the command processor 1524 self.fork_request_timeout_seconds = self.__socket_settings['forked_requests_timeout'] 1525 self.stdout_logging = True 1526 self.timeout = self.__socket_settings['timeout'] 1527 self.hostname = self.__socket_settings['hostname'] 1528 self.session_ttl = self.__socket_settings['session_ttl'] 1529 if self.hostname == "*": self.hostname = '' 1530 self.port = self.__socket_settings['port'] 1531 self.threads = self.__socket_settings['threads'] # maximum number of allowed sessions 1532 self.max_connections = self.__socket_settings['max_connections'] 1533 self.max_connections_per_host = self.__socket_settings['max_connections_per_host'] 1534 self.max_connections_per_host_barrier = self.__socket_settings['max_connections_per_host_barrier'] 1535 self.max_command_length = self.__socket_settings['max_command_length'] 1536 self.disabled_commands = self.__socket_settings['disabled_cmds'] 1537 self.ip_blacklist = self.__socket_settings['ip_blacklist'] 1538 self.answers = self.__socket_settings['answers'] 1539 self.connections = 0 1540 self.per_host_connections = {} 1541 self.sessions = {} 1542 self.__output = None 1543 self.SSL = {} 1544 self.SSL_exceptions = {} 1545 self.SSL_exceptions['WantReadError'] = DumbException 1546 self.SSL_exceptions['WantWriteError'] = DumbException 1547 self.SSL_exceptions['WantX509LookupError'] = DumbException 1548 self.SSL_exceptions['ZeroReturnError'] = DumbException 1549 self.SSL_exceptions['SysCallError'] = DumbException 1550 self.SSL_exceptions['Error'] = [] 1551 self.last_print = '' 1552 self.valid_commands = {} 1553 self.no_acked_commands = [] 1554 self.raw_commands = [] 1555 self.config_commands = [] 1556 self.termination_commands = [] 1557 self.initialization_commands = [] 1558 self.login_pass_commands = [] 1559 self.no_session_commands = [] 1560 self.command_classes = [self.BuiltInCommands] 1561 self.command_instances = [] 1562 self.EntropyInstantiation = (service_interface, self.args, self.kwds) 1563 1564 self.setup_external_command_classes() 1565 self.start_local_output_interface() 1566 self.setup_authenticator() 1567 self.setup_hostname() 1568 self.setup_commands() 1569 self.disable_commands() 1570 self.start_session_garbage_collector() 1571 self.setup_ssl() 1572 self.start_python_garbage_collector()
1573
1574 - def killall(self):
1575 if hasattr(self, 'socketLog'): 1576 self.socketLog.close() 1577 if self.Server != None: 1578 self.Server.alive = False 1579 if self.Gc != None: 1580 self.Gc.kill() 1581 if self.PythonGarbageCollector != None: 1582 self.PythonGarbageCollector.kill()
1583
1584 - def append_eos(self, data):
1585 return const_convert_to_rawstring(len(data)) + \ 1586 self.answers['eos'] + \ 1587 data
1588
1589 - def setup_ssl(self):
1590 1591 do_ssl = False 1592 if 'ssl' in self.kwds: 1593 do_ssl = self.kwds.pop('ssl') 1594 1595 if not do_ssl: 1596 return 1597 1598 try: 1599 from OpenSSL import SSL, crypto 1600 except ImportError as e: 1601 self.updateProgress('Unable to load OpenSSL, error: %s' % (repr(e),)) 1602 return 1603 self.SSL_exceptions['WantReadError'] = SSL.WantReadError 1604 self.SSL_exceptions['Error'] = SSL.Error 1605 self.SSL_exceptions['WantWriteError'] = SSL.WantWriteError 1606 self.SSL_exceptions['WantX509LookupError'] = SSL.WantX509LookupError 1607 self.SSL_exceptions['ZeroReturnError'] = SSL.ZeroReturnError 1608 self.SSL_exceptions['SysCallError'] = SSL.SysCallError 1609 self.SSL['m'] = SSL 1610 self.SSL['crypto'] = crypto 1611 self.SSL['key'] = self.__socket_settings['ssl_key'] 1612 self.SSL['cert'] = self.__socket_settings['ssl_cert'] 1613 self.SSL['ca_cert'] = self.__socket_settings['ssl_ca_cert'] 1614 self.SSL['ca_pkey'] = self.__socket_settings['ssl_ca_pkey'] 1615 # change port 1616 self.port = self.__socket_settings['ssl_port'] 1617 self.SSL['not_before'] = 0 1618 self.SSL['not_after'] = 60*60*24*365*5 # 5 years 1619 self.SSL['serial'] = 0 1620 self.SSL['digest'] = 'md5' 1621 1622 if not (os.path.isfile(self.SSL['ca_cert']) and \ 1623 os.path.isfile(self.SSL['ca_pkey']) and \ 1624 os.path.isfile(self.SSL['key']) and \ 1625 os.path.isfile(self.SSL['cert'])): 1626 self.create_ca_server_certs( 1627 self.SSL['serial'], 1628 self.SSL['digest'], 1629 self.SSL['not_before'], 1630 self.SSL['not_after'], 1631 self.SSL['ca_pkey'], 1632 self.SSL['ca_cert'], 1633 self.SSL['key'], 1634 self.SSL['cert'] 1635 ) 1636 os.chmod(self.SSL['ca_cert'], 0o644) 1637 try: 1638 os.chown(self.SSL['ca_cert'], -1, 0) 1639 except OSError: 1640 pass 1641 os.chmod(self.SSL['ca_pkey'], 0o600) 1642 try: 1643 os.chown(self.SSL['ca_pkey'], -1, 0) 1644 except OSError: 1645 pass 1646 1647 os.chmod(self.SSL['key'], 0o600) 1648 try: 1649 os.chown(self.SSL['key'], -1, 0) 1650 except OSError: 1651 pass 1652 os.chmod(self.SSL['cert'], 0o644) 1653 try: 1654 os.chown(self.SSL['cert'], -1, 0) 1655 except OSError: 1656 pass
1657
1658 - def create_ca_server_certs(self, serial, digest, not_before, not_after, ca_pkey_dest, ca_cert_dest, server_key, server_cert):
1659 1660 mycn = 'Entropy Repository Service' 1661 cakey = self.create_ssl_key_pair(self.SSL['crypto'].TYPE_RSA, 1024) 1662 careq = self.create_ssl_certificate_request(cakey, digest, CN = mycn) 1663 cert = self.SSL['crypto'].X509() 1664 cert.set_serial_number(serial) 1665 cert.gmtime_adj_notBefore(not_before) 1666 cert.gmtime_adj_notAfter(not_after) 1667 cert.set_issuer(careq.get_subject()) 1668 cert.set_subject(careq.get_subject()) 1669 cert.sign(cakey, digest) 1670 1671 # now create server key + cert 1672 s_pkey = self.create_ssl_key_pair(self.SSL['crypto'].TYPE_RSA, 1024) 1673 s_req = self.create_ssl_certificate_request(s_pkey, digest, CN = mycn) 1674 s_cert = self.SSL['crypto'].X509() 1675 s_cert.set_serial_number(serial+1) 1676 s_cert.gmtime_adj_notBefore(not_before) 1677 s_cert.gmtime_adj_notAfter(not_after) 1678 s_cert.set_issuer(cert.get_subject()) 1679 s_cert.set_subject(s_req.get_subject()) 1680 s_cert.set_pubkey(s_req.get_pubkey()) 1681 s_cert.sign(cakey, digest) 1682 1683 # write CA 1684 if os.path.isfile(ca_pkey_dest): 1685 shutil.move(ca_pkey_dest, ca_pkey_dest+".moved") 1686 f = open(ca_pkey_dest, "w") 1687 f.write(self.SSL['crypto'].dump_privatekey(self.SSL['crypto'].FILETYPE_PEM, cakey)) 1688 f.flush() 1689 f.close() 1690 if os.path.isfile(ca_cert_dest): 1691 shutil.move(ca_cert_dest, ca_cert_dest+".moved") 1692 f = open(ca_cert_dest, "w") 1693 f.write(self.SSL['crypto'].dump_certificate(self.SSL['crypto'].FILETYPE_PEM, cert)) 1694 f.flush() 1695 f.close() 1696 1697 if os.path.isfile(server_key): 1698 shutil.move(server_key, server_key+".moved") 1699 # write server 1700 f = open(server_key, "w") 1701 f.write(self.SSL['crypto'].dump_privatekey(self.SSL['crypto'].FILETYPE_PEM, s_pkey)) 1702 f.flush() 1703 f.close() 1704 if os.path.isfile(server_cert): 1705 shutil.move(server_cert, server_cert+".moved") 1706 f = open(server_cert, "w") 1707 f.write(self.SSL['crypto'].dump_certificate(self.SSL['crypto'].FILETYPE_PEM, s_cert)) 1708 f.flush() 1709 f.close()
1710
1711 - def create_ssl_key_pair(self, keytype, bits):
1712 pkey = self.SSL['crypto'].PKey() 1713 pkey.generate_key(keytype, bits) 1714 return pkey
1715
1716 - def create_ssl_certificate_request(self, pkey, digest, **name):
1717 req = self.SSL['crypto'].X509Req() 1718 subj = req.get_subject() 1719 for (key, value) in list(name.items()): 1720 setattr(subj, key, value) 1721 req.set_pubkey(pkey) 1722 req.sign(pkey, digest) 1723 return req
1724
1725 - def setup_external_command_classes(self):
1726 1727 if 'external_cmd_classes' in self.kwds: 1728 ext_commands = self.kwds.pop('external_cmd_classes') 1729 if not isinstance(ext_commands, list): 1730 raise InvalidDataType("InvalidDataType: external_cmd_classes must be a list") 1731 self.command_classes += ext_commands
1732
1733 - def setup_commands(self):
1734 1735 identifiers = set() 1736 for myclass in self.command_classes: 1737 1738 myargs = [] 1739 mykwargs = {} 1740 if isinstance(myclass, tuple) or isinstance(myclass, list): 1741 if len(myclass) > 2: 1742 mykwargs = myclass[2] 1743 if len(myclass) > 1: 1744 myargs = myclass[1] 1745 myclass = myclass[0] 1746 1747 myinst = myclass(self, *myargs, **mykwargs) 1748 if str(myinst) in identifiers: 1749 raise PermissionDenied("PermissionDenied: another command instance is owning this name") 1750 identifiers.add(str(myinst)) 1751 self.command_instances.append(myinst) 1752 # now register 1753 myinst.register( self.valid_commands, 1754 self.no_acked_commands, 1755 self.termination_commands, 1756 self.initialization_commands, 1757 self.login_pass_commands, 1758 self.no_session_commands, 1759 self.raw_commands, 1760 self.config_commands 1761 )
1762
1763 - def disable_commands(self):
1764 for cmd in self.disabled_commands: 1765 1766 if cmd in self.valid_commands: 1767 self.valid_commands.pop(cmd) 1768 1769 if cmd in self.no_acked_commands: 1770 self.no_acked_commands.remove(cmd) 1771 1772 if cmd in self.termination_commands: 1773 self.termination_commands.remove(cmd) 1774 1775 if cmd in self.initialization_commands: 1776 self.initialization_commands.remove(cmd) 1777 1778 if cmd in self.login_pass_commands: 1779 self.login_pass_commands.remove(cmd) 1780 1781 if cmd in self.no_session_commands: 1782 self.no_session_commands.remove(cmd) 1783 1784 if cmd in self.raw_commands: 1785 self.raw_commands.remove(cmd) 1786 1787 if cmd in self.config_commands: 1788 self.config_commands.remove(cmd)
1789
1790 - def start_local_output_interface(self):
1791 if 'sock_output' in self.kwds: 1792 outputIntf = self.kwds.pop('sock_output') 1793 self.__output = outputIntf
1794
1795 - def setup_authenticator(self):
1796 1797 # lock, if perhaps some implementations need it 1798 self.AuthenticatorLock = self.threading.Lock() 1799 auth_inst = (self.BasicPamAuthenticator, [self], {},) # authentication class, args, keywords 1800 # external authenticator 1801 if 'sock_auth' in self.kwds: 1802 authIntf = self.kwds.pop('sock_auth') 1803 if isinstance(authIntf, tuple): 1804 if len(authIntf) == 3: 1805 auth_inst = authIntf[:] 1806 else: 1807 raise IncorrectParameter("IncorrectParameter: wront authentication interface specified") 1808 else: 1809 raise IncorrectParameter("IncorrectParameter: wront authentication interface specified") 1810 # initialize authenticator 1811 self.AuthenticatorInst = (auth_inst[0], [self]+auth_inst[1], auth_inst[2],)
1812
1813 - def start_python_garbage_collector(self):
1814 self.PythonGarbageCollector = self.TimeScheduled(3600, self.python_garbage_collect) 1815 self.PythonGarbageCollector.set_accuracy(False) 1816 self.PythonGarbageCollector.start()
1817
1818 - def start_session_garbage_collector(self):
1819 self.Gc = self.TimeScheduled(5, self.gc_clean) 1820 self.Gc.start()
1821
1822 - def python_garbage_collect(self):
1823 self.gc.collect() 1824 self.gc.collect() 1825 self.gc.collect()
1826
1827 - def gc_clean(self):
1828 if not self.sessions: 1829 return 1830 1831 with self.SessionsLock: 1832 for session_id in list(self.sessions.keys()): 1833 sess_time = self.sessions[session_id]['t'] 1834 is_running = self.sessions[session_id]['running'] 1835 auth_uid = self.sessions[session_id]['auth_uid'] # is kept alive? 1836 if (is_running) or (auth_uid == -1): 1837 if auth_uid == -1: 1838 self.updateProgress('not killing session %s, since it is kept alive by auth_uid=-1' % (session_id,) ) 1839 continue 1840 cur_time = time.time() 1841 ttl = self.session_ttl 1842 check_time = sess_time + ttl 1843 if cur_time > check_time: 1844 self.updateProgress('killing session %s, ttl: %ss: no activity' % (session_id, ttl,) ) 1845 self._destroy_session(session_id)
1846
1847 - def setup_hostname(self):
1848 if self.hostname: 1849 try: 1850 self.hostname = self.get_ip_address(self.hostname) 1851 except IOError: # it isn't a device name 1852 pass
1853
1854 - def get_ip_address(self, ifname):
1855 import fcntl 1856 import struct 1857 mysock = self.socket.socket ( self.socket.AF_INET, self.socket.SOCK_STREAM ) 1858 return self.socket.inet_ntoa(fcntl.ioctl(mysock.fileno(), 0x8915, struct.pack('256s', ifname[:15]))[20:24])
1859
1860 - def get_md5_hash(self, salt):
1861 import hashlib 1862 m = hashlib.md5() 1863 m.update(os.urandom(2)) 1864 m.update(salt) 1865 return m.hexdigest()
1866
1867 - def get_new_session(self, ip_address):
1868 with self.SessionsLock: 1869 if len(self.sessions) > self.threads: 1870 # fuck! 1871 return "0" 1872 rng = self.get_md5_hash(str(ip_address)) 1873 while rng in self.sessions: 1874 rng = self.get_md5_hash(str(ip_address)) 1875 self.sessions[rng] = {} 1876 self.sessions[rng]['running'] = False 1877 self.sessions[rng]['auth_uid'] = None 1878 self.sessions[rng]['admin'] = False 1879 self.sessions[rng]['moderator'] = False 1880 self.sessions[rng]['user'] = False 1881 self.sessions[rng]['developer'] = False 1882 self.sessions[rng]['compression'] = None 1883 self.sessions[rng]['stream_mode'] = False 1884 try: 1885 self.sessions[rng]['stream_path'] = self.entropyTools.get_random_temp_file() 1886 except (IOError, OSError,): 1887 self.sessions[rng]['stream_path'] = '' 1888 self.sessions[rng]['t'] = time.time() 1889 self.sessions[rng]['ip_address'] = ip_address 1890 return rng
1891
1892 - def update_session_time(self, session):
1893 with self.SessionsLock: 1894 if session in self.sessions: 1895 self.sessions[session]['t'] = time.time() 1896 self.updateProgress('session time updated for %s' % (session,) )
1897
1898 - def set_session_running(self, session):
1899 with self.SessionsLock: 1900 if session in self.sessions: 1901 self.sessions[session]['running'] = True
1902
1903 - def unset_session_running(self, session):
1904 with self.SessionsLock: 1905 if session in self.sessions: 1906 self.sessions[session]['running'] = False
1907
1908 - def destroy_session(self, session):
1909 with self.SessionsLock: 1910 return self._destroy_session(session)
1911
1912 - def _destroy_session(self, session):
1913 if session in self.sessions: 1914 stream_path = self.sessions[session]['stream_path'] 1915 del self.sessions[session] 1916 if os.path.isfile(stream_path) and os.access(stream_path, os.W_OK) and not os.path.islink(stream_path): 1917 try: 1918 os.remove(stream_path) 1919 except OSError: 1920 pass 1921 return True 1922 return False
1923
1924 - def go(self):
1925 self.socket.setdefaulttimeout(self.timeout) 1926 while True: 1927 try: 1928 self.Server = self.HostServerMixin( 1929 (self.hostname, self.port), 1930 self.RequestHandler, 1931 self.CommandProcessor(self), 1932 self 1933 ) 1934 break 1935 except self.socket.error as e: 1936 if e[0] == 98: 1937 # Address already in use 1938 self.updateProgress('address already in use (%s, port: %s), waiting 5 seconds...' % (self.hostname, self.port,)) 1939 time.sleep(5) 1940 continue 1941 else: 1942 raise 1943 self.updateProgress('server connected, listening on: %s, port: %s, timeout: %s' % (self.hostname, self.port, self.timeout,)) 1944 self.Server.serve_forever() 1945 self.Gc.kill()
1946
1947 - def store_rc(self, rc, session):
1948 with self.SessionsLock: 1949 if session in self.sessions: 1950 if type(rc) in (list, tuple,): 1951 rc_item = rc[:] 1952 elif type(rc) in (set, frozenset, dict,): 1953 rc_item = rc.copy() 1954 else: 1955 rc_item = rc 1956 self.sessions[session]['rc'] = rc_item
1957
1958 - def get_rc(self, session):
1959 with self.SessionsLock: 1960 if session in self.sessions: 1961 return self.sessions[session].get('rc')
1962
1963 - def _ssl_poll(self, sock_obj, filter_type, caller_name):
1964 poller = select.poll() 1965 poller.register(sock_obj, filter_type) 1966 res = poller.poll(sock_obj.gettimeout() * 1000) 1967 if len(res) != 1: 1968 raise TimeoutError("Connection timed out on %s" % caller_name)
1969
1970 - def transmit(self, channel, data):
1971 if self.SSL: 1972 if sys.hexversion >= 0x3000000: 1973 data = const_convert_to_rawstring(data) 1974 mydata = self.append_eos(data) 1975 encode_done = False 1976 while True: 1977 try: 1978 sent = channel.send(mydata) 1979 if sent == len(mydata): 1980 break 1981 mydata = mydata[sent:] 1982 except self.SSL_exceptions['WantWriteError']: 1983 self._ssl_poll(channel, select.POLLOUT, 'write') 1984 except self.SSL_exceptions['WantReadError']: 1985 self._ssl_poll(channel, select.POLLIN, 'write') 1986 except UnicodeEncodeError: 1987 if encode_done: 1988 raise 1989 mydata = mydata.encode('utf-8') 1990 encode_done = True 1991 continue 1992 else: 1993 channel.sendall(self.append_eos(data))
1994
1995 - def updateProgress(self, *args, **kwargs):
1996 message = args[0] 1997 if message != self.last_print: 1998 self.socketLog.log(ETP_LOGPRI_INFO, ETP_LOGLEVEL_NORMAL, str(args[0])) 1999 if self.__output != None and self.stdout_logging: 2000 self.__output.updateProgress(*args,**kwargs) 2001 self.last_print = message
2002