Package entropy :: Package services :: Module interfaces

Source Code for Module entropy.services.interfaces

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