| 432 | | class NonBlockingHttpBOSH(NonBlockingTcp): |
| 433 | | ''' |
| 434 | | Socket wrapper that makes HTTP message out of send data and peels-off |
| 435 | | HTTP headers from incoming messages |
| 436 | | ''' |
| 437 | | |
| 438 | | def __init__(self, bosh_uri, bosh_port, on_disconnect): |
| 439 | | self.bosh_protocol, self.bosh_host, self.bosh_path = self.urisplit(bosh_uri) |
| 440 | | if self.bosh_protocol is None: |
| 441 | | self.bosh_protocol = 'http' |
| 442 | | if self.bosh_path == '': |
| 443 | | bosh_path = '/' |
| 444 | | self.bosh_port = bosh_port |
| 445 | | |
| 446 | | def send(self, raw_data, now=False): |
| 447 | | |
| 448 | | NonBlockingTcp.send( |
| 449 | | self, |
| 450 | | self.build_http_message(raw_data), |
| 451 | | now) |
| 452 | | |
| 453 | | def _on_receive(self,data): |
| 454 | | '''Preceeds pass of received data to Client class. Gets rid of HTTP headers |
| 455 | | and checks them.''' |
| 456 | | statusline, headers, httpbody = self.parse_http_message(data) |
| 457 | | if statusline[1] != '200': |
| 458 | | log.error('HTTP Error: %s %s' % (statusline[1], statusline[2])) |
| 459 | | self.disconnect() |
| 460 | | self.on_receive(httpbody) |
| 461 | | |
| 462 | | |
| 463 | | def build_http_message(self, httpbody): |
| 464 | | ''' |
| 465 | | Builds bosh http message with given body. |
| 466 | | Values for headers and status line fields are taken from class variables. |
| 467 | | ) |
| 468 | | ''' |
| 469 | | headers = ['POST %s HTTP/1.1' % self.bosh_path, |
| 470 | | 'Host: %s:%s' % (self.bosh_host, self.bosh_port), |
| 471 | | 'Content-Type: text/xml; charset=utf-8', |
| 472 | | 'Content-Length: %s' % len(str(httpbody)), |
| 473 | | '\r\n'] |
| 474 | | headers = '\r\n'.join(headers) |
| 475 | | return('%s%s\r\n' % (headers, httpbody)) |
| 476 | | |
| 477 | | def parse_http_message(self, message): |
| 478 | | ''' |
| 479 | | splits http message to tuple ( |
| 480 | | statusline - list of e.g. ['HTTP/1.1', '200', 'OK'], |
| 481 | | headers - dictionary of headers e.g. {'Content-Length': '604', |
| 482 | | 'Content-Type': 'text/xml; charset=utf-8'}, |
| 483 | | httpbody - string with http body |
| 484 | | ) |
| 485 | | ''' |
| 486 | | message = message.replace('\r','') |
| 487 | | (header, httpbody) = message.split('\n\n') |
| 488 | | header = header.split('\n') |
| 489 | | statusline = header[0].split(' ') |
| 490 | | header = header[1:] |
| 491 | | headers = {} |
| 492 | | for dummy in header: |
| 493 | | row = dummy.split(' ',1) |
| 494 | | headers[row[0][:-1]] = row[1] |
| 495 | | return (statusline, headers, httpbody) |
| 496 | | |
| 497 | | |
| 498 | | USE_PYOPENSSL = False |
| 499 | | |
| 500 | | try: |
| 501 | | #raise ImportError("Manually disabled PyOpenSSL") |
| 502 | | import OpenSSL.SSL |
| 503 | | import OpenSSL.crypto |
| 504 | | USE_PYOPENSSL = True |
| 505 | | log.info("PyOpenSSL loaded") |
| 506 | | except ImportError: |
| 507 | | log.debug("Import of PyOpenSSL failed:", exc_info=True) |
| 508 | | |
| 509 | | # FIXME: Remove these prints before release, replace with a warning dialog. |
| 510 | | print >> sys.stderr, "=" * 79 |
| 511 | | print >> sys.stderr, "PyOpenSSL not found, falling back to Python builtin SSL objects (insecure)." |
| 512 | | print >> sys.stderr, "=" * 79 |
| 513 | | |
| 514 | | |
| 515 | | def torf(cond, tv, fv): |
| 516 | | if cond: return tv |
| 517 | | return fv |
| 518 | | |
| 519 | | def gattr(obj, attr, default=None): |
| 520 | | try: |
| 521 | | return getattr(obj, attr) |
| 522 | | except: |
| 523 | | return default |
| 524 | | |
| 525 | | class SSLWrapper: |
| 526 | | class Error(IOError): |
| 527 | | def __init__(self, sock=None, exc=None, errno=None, strerror=None, peer=None): |
| 528 | | self.parent = IOError |
| 529 | | |
| 530 | | errno = errno or gattr(exc, 'errno') |
| 531 | | strerror = strerror or gattr(exc, 'strerror') or gattr(exc, 'args') |
| 532 | | if not isinstance(strerror, basestring): strerror = repr(strerror) |
| 533 | | |
| 534 | | self.sock = sock |
| 535 | | self.exc = exc |
| 536 | | self.peer = peer |
| 537 | | self.exc_name = None |
| 538 | | self.exc_args = None |
| 539 | | self.exc_str = None |
| 540 | | self.exc_repr = None |
| 541 | | |
| 542 | | if self.exc is not None: |
| 543 | | self.exc_name = str(self.exc.__class__) |
| 544 | | self.exc_args = gattr(self.exc, 'args') |
| 545 | | self.exc_str = str(self.exc) |
| 546 | | self.exc_repr = repr(self.exc) |
| 547 | | if not errno: |
| 548 | | try: |
| 549 | | if isinstance(exc, OpenSSL.SSL.SysCallError): |
| 550 | | if self.exc_args[0] > 0: |
| 551 | | errno = self.exc_args[0] |
| 552 | | strerror = self.exc_args[1] |
| 553 | | except: pass |
| 554 | | |
| 555 | | self.parent.__init__(self, errno, strerror) |
| 556 | | |
| 557 | | if self.peer is None and sock is not None: |
| 558 | | try: |
| 559 | | ppeer = self.sock.getpeername() |
| 560 | | if len(ppeer) == 2 and isinstance(ppeer[0], basestring) \ |
| 561 | | and isinstance(ppeer[1], int): |
| 562 | | self.peer = ppeer |
| 563 | | except: pass |
| 564 | | |
| 565 | | def __str__(self): |
| 566 | | s = str(self.__class__) |
| 567 | | if self.peer: s += " for %s:%d" % self.peer |
| 568 | | if self.errno is not None: s += ": [Errno: %d]" % self.errno |
| 569 | | if self.strerror: s += " (%s)" % self.strerror |
| 570 | | if self.exc_name: |
| 571 | | s += ", Caused by %s" % self.exc_name |
| 572 | | if self.exc_str: |
| 573 | | if self.strerror: s += "(%s)" % self.exc_str |
| 574 | | else: s += "(%s)" % str(self.exc_args) |
| 575 | | return s |
| 576 | | |
| 577 | | def __init__(self, sslobj, sock=None): |
| 578 | | self.sslobj = sslobj |
| 579 | | self.sock = sock |
| 580 | | log.debug("%s.__init__ called with %s", self.__class__, sslobj) |
| 581 | | |
| 582 | | def recv(self, data, flags=None): |
| 583 | | """ Receive wrapper for SSL object |
| 584 | | |
| 585 | | We can return None out of this function to signal that no data is |
| 586 | | available right now. Better than an exception, which differs |
| 587 | | depending on which SSL lib we're using. Unfortunately returning '' |
| 588 | | can indicate that the socket has been closed, so to be sure, we avoid |
| 589 | | this by returning None. """ |
| 590 | | |
| 591 | | raise NotImplementedException() |
| 592 | | |
| 593 | | def send(self, data, flags=None, now = False): |
| 594 | | raise NotImplementedException() |
| 595 | | |
| 596 | | class PyOpenSSLWrapper(SSLWrapper): |
| 597 | | '''Wrapper class for PyOpenSSL's recv() and send() methods''' |
| 598 | | |
| 599 | | def __init__(self, *args): |
| 600 | | self.parent = SSLWrapper |
| 601 | | self.parent.__init__(self, *args) |
| 602 | | |
| 603 | | def is_numtoolarge(self, e): |
| 604 | | t = ('asn1 encoding routines', 'a2d_ASN1_OBJECT', 'first num too large') |
| 605 | | return isinstance(e.args, (list, tuple)) and len(e.args) == 1 and \ |
| 606 | | isinstance(e.args[0], (list, tuple)) and len(e.args[0]) == 2 and \ |
| 607 | | e.args[0][0] == e.args[0][1] == t |
| 608 | | |
| 609 | | def recv(self, bufsize, flags=None): |
| 610 | | retval = None |
| 611 | | try: |
| 612 | | if flags is None: retval = self.sslobj.recv(bufsize) |
| 613 | | else: retval = self.sslobj.recv(bufsize, flags) |
| 614 | | except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: |
| 615 | | pass |
| 616 | | # log.debug("Recv: " + repr(e)) |
| 617 | | except OpenSSL.SSL.SysCallError, e: |
| 618 | | log.debug("Recv: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True) |
| 619 | | #traceback.print_exc() |
| 620 | | raise SSLWrapper.Error(self.sock or self.sslobj, e) |
| 621 | | except OpenSSL.SSL.Error, e: |
| 622 | | if self.is_numtoolarge(e): |
| 623 | | # warn, but ignore this exception |
| 624 | | log.warning("Recv: OpenSSL: asn1enc: first num too large (ignored)") |
| 625 | | else: |
| 626 | | log.debug("Recv: Caught OpenSSL.SSL.Error:", exc_info=True) |
| 627 | | #traceback.print_exc() |
| 628 | | #print "Current Stack:" |
| 629 | | #traceback.print_stack() |
| 630 | | raise SSLWrapper.Error(self.sock or self.sslobj, e) |
| 631 | | return retval |
| 632 | | |
| 633 | | def send(self, data, flags=None, now = False): |
| 634 | | try: |
| 635 | | if flags is None: return self.sslobj.send(data) |
| 636 | | else: return self.sslobj.send(data, flags) |
| 637 | | except (OpenSSL.SSL.WantReadError, OpenSSL.SSL.WantWriteError), e: |
| 638 | | #log.debug("Send: " + repr(e)) |
| 639 | | time.sleep(0.1) # prevent 100% CPU usage |
| 640 | | except OpenSSL.SSL.SysCallError, e: |
| 641 | | log.error("Send: Got OpenSSL.SSL.SysCallError: " + repr(e), exc_info=True) |
| 642 | | #traceback.print_exc() |
| 643 | | raise SSLWrapper.Error(self.sock or self.sslobj, e) |
| 644 | | except OpenSSL.SSL.Error, e: |
| 645 | | if self.is_numtoolarge(e): |
| 646 | | # warn, but ignore this exception |
| 647 | | log.warning("Send: OpenSSL: asn1enc: first num too large (ignored)") |
| 648 | | else: |
| 649 | | log.error("Send: Caught OpenSSL.SSL.Error:", exc_info=True) |
| 650 | | #traceback.print_exc() |
| 651 | | #print "Current Stack:" |
| 652 | | #traceback.print_stack() |
| 653 | | raise SSLWrapper.Error(self.sock or self.sslobj, e) |
| 654 | | return 0 |
| 655 | | |
| 656 | | class StdlibSSLWrapper(SSLWrapper): |
| 657 | | '''Wrapper class for Python's socket.ssl read() and write() methods''' |
| 658 | | |
| 659 | | def __init__(self, *args): |
| 660 | | self.parent = SSLWrapper |
| 661 | | self.parent.__init__(self, *args) |
| 662 | | |
| 663 | | def recv(self, bufsize, flags=None): |
| 664 | | # we simply ignore flags since ssl object doesn't support it |
| 665 | | try: |
| 666 | | return self.sslobj.read(bufsize) |
| 667 | | except socket.sslerror, e: |
| 668 | | #log.debug("Recv: Caught socket.sslerror:", exc_info=True) |
| 669 | | #traceback.print_exc() |
| 670 | | if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): |
| 671 | | raise SSLWrapper.Error(self.sock or self.sslobj, e) |
| 672 | | return None |
| 673 | | |
| 674 | | def send(self, data, flags=None, now = False): |
| 675 | | # we simply ignore flags since ssl object doesn't support it |
| 676 | | try: |
| 677 | | return self.sslobj.write(data) |
| 678 | | except socket.sslerror, e: |
| 679 | | #log.debug("Send: Caught socket.sslerror:", exc_info=True) |
| 680 | | #traceback.print_exc() |
| 681 | | if e.args[0] not in (socket.SSL_ERROR_WANT_READ, socket.SSL_ERROR_WANT_WRITE): |
| 682 | | raise SSLWrapper.Error(self.sock or self.sslobj, e) |
| 683 | | return 0 |
| 684 | | |
| 685 | | |
| 686 | | class NonBlockingTLS(PlugIn): |
| 687 | | ''' TLS connection used to encrypts already estabilished tcp connection.''' |
| 688 | | |
| 689 | | # from ssl.h (partial extract) |
| 690 | | ssl_h_bits = { "SSL_ST_CONNECT": 0x1000, "SSL_ST_ACCEPT": 0x2000, |
| 691 | | "SSL_CB_LOOP": 0x01, "SSL_CB_EXIT": 0x02, |
| 692 | | "SSL_CB_READ": 0x04, "SSL_CB_WRITE": 0x08, |
| 693 | | "SSL_CB_ALERT": 0x4000, |
| 694 | | "SSL_CB_HANDSHAKE_START": 0x10, "SSL_CB_HANDSHAKE_DONE": 0x20} |
| 695 | | |
| 696 | | def PlugIn(self, owner, on_tls_success, on_tls_failure, now=0): |
| 697 | | ''' If the 'now' argument is true then starts using encryption immidiatedly. |
| 698 | | If 'now' in false then starts encryption as soon as TLS feature is |
| 699 | | declared by the server (if it were already declared - it is ok). |
| 700 | | ''' |
| 701 | | if owner.__dict__.has_key('NonBlockingTLS'): |
| 702 | | return # Already enabled. |
| 703 | | PlugIn.PlugIn(self, owner) |
| 704 | | DBG_LINE='NonBlockingTLS' |
| 705 | | self.on_tls_success = on_tls_success |
| 706 | | self.on_tls_faliure = on_tls_failure |
| 707 | | if now: |
| 708 | | try: |
| 709 | | res = self._startSSL() |
| 710 | | except Exception, e: |
| 711 | | log.error("PlugIn: while trying _startSSL():", exc_info=True) |
| 712 | | #traceback.print_exc() |
| 713 | | self._owner.socket.pollend() |
| 714 | | return |
| 715 | | on_tls_success() |
| 716 | | return res |
| 717 | | if self._owner.Dispatcher.Stream.features: |
| 718 | | try: |
| 719 | | self.FeaturesHandler(self._owner.Dispatcher, self._owner.Dispatcher.Stream.features) |
| 720 | | except NodeProcessed: |
| 721 | | pass |
| 722 | | else: |
| 723 | | self._owner.RegisterHandlerOnce('features',self.FeaturesHandler, xmlns=NS_STREAMS) |
| 724 | | self.starttls = None |
| 725 | | |
| 726 | | def plugout(self,now=0): |
| 727 | | ''' Unregisters TLS handler's from owner's dispatcher. Take note that encription |
| 728 | | can not be stopped once started. You can only break the connection and start over.''' |
| 729 | | # if dispatcher is not plugged we cannot (un)register handlers |
| 730 | | if self._owner.__dict__.has_key('Dispatcher'): |
| 731 | | self._owner.UnregisterHandler('features', self.FeaturesHandler,xmlns=NS_STREAMS) |
| 732 | | self._owner.Dispatcher.PlugOut() |
| 733 | | self._owner = None |
| 734 | | |
| 735 | | def FeaturesHandler(self, conn, feats): |
| 736 | | ''' Used to analyse server <features/> tag for TLS support. |
| 737 | | If TLS is supported starts the encryption negotiation. Used internally ''' |
| 738 | | if not feats.getTag('starttls', namespace=NS_TLS): |
| 739 | | self.DEBUG("TLS unsupported by remote server.", 'warn') |
| 740 | | self.on_tls_failure("TLS unsupported by remote server.") |
| 741 | | return |
| 742 | | self.DEBUG("TLS supported by remote server. Requesting TLS start.", 'ok') |
| 743 | | self._owner.RegisterHandlerOnce('proceed', self.StartTLSHandler, xmlns=NS_TLS) |
| 744 | | self._owner.RegisterHandlerOnce('failure', self.StartTLSHandler, xmlns=NS_TLS) |
| 745 | | self._owner.send('<starttls xmlns="%s"/>' % NS_TLS) |
| 746 | | raise NodeProcessed |
| 747 | | |
| 748 | | def _dumpX509(self, cert, stream=sys.stderr): |
| 749 | | print >> stream, "Digest (SHA-1):", cert.digest("sha1") |
| 750 | | print >> stream, "Digest (MD5):", cert.digest("md5") |
| 751 | | print >> stream, "Serial #:", cert.get_serial_number() |
| 752 | | print >> stream, "Version:", cert.get_version() |
| 753 | | print >> stream, "Expired:", torf(cert.has_expired(), "Yes", "No") |
| 754 | | print >> stream, "Subject:" |
| 755 | | self._dumpX509Name(cert.get_subject(), stream) |
| 756 | | print >> stream, "Issuer:" |
| 757 | | self._dumpX509Name(cert.get_issuer(), stream) |
| 758 | | self._dumpPKey(cert.get_pubkey(), stream) |
| 759 | | |
| 760 | | def _dumpX509Name(self, name, stream=sys.stderr): |
| 761 | | print >> stream, "X509Name:", str(name) |
| 762 | | |
| 763 | | def _dumpPKey(self, pkey, stream=sys.stderr): |
| 764 | | typedict = {OpenSSL.crypto.TYPE_RSA: "RSA", OpenSSL.crypto.TYPE_DSA: "DSA"} |
| 765 | | print >> stream, "PKey bits:", pkey.bits() |
| 766 | | print >> stream, "PKey type: %s (%d)" % (typedict.get(pkey.type(), "Unknown"), pkey.type()) |
| 767 | | |
| 768 | | def _startSSL(self): |
| 769 | | ''' Immidiatedly switch socket to TLS mode. Used internally.''' |
| 770 | | log.debug("_startSSL called") |
| 771 | | if USE_PYOPENSSL: return self._startSSL_pyOpenSSL() |
| 772 | | return self._startSSL_stdlib() |
| 773 | | |
| 774 | | def _startSSL_pyOpenSSL(self): |
| 775 | | #log.debug("_startSSL_pyOpenSSL called, thread id: %s", str(thread.get_ident())) |
| 776 | | log.debug("_startSSL_pyOpenSSL called") |
| 777 | | tcpsock = self._owner.Connection |
| 778 | | # FIXME: should method be configurable? |
| 779 | | tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.TLSv1_METHOD) |
| 780 | | #tcpsock._sslContext = OpenSSL.SSL.Context(OpenSSL.SSL.SSLv23_METHOD) |
| 781 | | tcpsock.ssl_errnum = 0 |
| 782 | | tcpsock._sslContext.set_verify(OpenSSL.SSL.VERIFY_PEER, self._ssl_verify_callback) |
| 783 | | cacerts = os.path.join('../data', 'other', 'cacerts.pem') |
| 784 | | try: |
| 785 | | tcpsock._sslContext.load_verify_locations(cacerts) |
| 786 | | except: |
| 787 | | log.warning('Unable to load SSL certificats from file %s' % \ |
| 788 | | os.path.abspath(cacerts)) |
| 789 | | # load users certs |
| 790 | | if os.path.isfile('%s/.gajim/cacerts.pem' % os.environ['HOME']): |
| 791 | | store = tcpsock._sslContext.get_cert_store() |
| 792 | | f = open('%s/.gajim/cacerts.pem' % os.environ['HOME']) |
| 793 | | lines = f.readlines() |
| 794 | | i = 0 |
| 795 | | begin = -1 |
| 796 | | for line in lines: |
| 797 | | if 'BEGIN CERTIFICATE' in line: |
| 798 | | begin = i |
| 799 | | elif 'END CERTIFICATE' in line and begin > -1: |
| 800 | | cert = ''.join(lines[begin:i+2]) |
| 801 | | try: |
| 802 | | X509cert = OpenSSL.crypto.load_certificate( |
| 803 | | OpenSSL.crypto.FILETYPE_PEM, cert) |
| 804 | | store.add_cert(X509cert) |
| 805 | | except OpenSSL.crypto.Error, exception_obj: |
| 806 | | log.warning('Unable to load a certificate from file %s: %s' %\ |
| 807 | | ('%s/.gajim/cacerts.pem' % os.environ['HOME'], exception_obj.args[0][0][2])) |
| 808 | | except: |
| 809 | | log.warning( |
| 810 | | 'Unknown error while loading certificate from file %s' % \ |
| 811 | | '%s/.gajim/cacerts.pem' % os.environ['HOME']) |
| 812 | | begin = -1 |
| 813 | | i += 1 |
| 814 | | tcpsock._sslObj = OpenSSL.SSL.Connection(tcpsock._sslContext, tcpsock._sock) |
| 815 | | tcpsock._sslObj.set_connect_state() # set to client mode |
| 816 | | |
| 817 | | wrapper = PyOpenSSLWrapper(tcpsock._sslObj) |
| 818 | | tcpsock._recv = wrapper.recv |
| 819 | | tcpsock._send = wrapper.send |
| 820 | | |
| 821 | | log.debug("Initiating handshake...") |
| 822 | | # FIXME: Figure out why _connect_success is called before the |
| 823 | | # SSL handshake is completed in STARTTLS mode. See #2838. |
| 824 | | tcpsock._sslObj.setblocking(True) |
| 825 | | try: |
| 826 | | self.starttls='in progress' |
| 827 | | tcpsock._sslObj.do_handshake() |
| 828 | | except: |
| 829 | | log.error('Error while TLS handshake: ', exc_info=True) |
| 830 | | self.on_tls_failure('Error while TLS Handshake') |
| 831 | | return |
| 832 | | tcpsock._sslObj.setblocking(False) |
| 833 | | log.debug("Synchronous handshake completed") |
| 834 | | #log.debug("Async handshake started...") |
| 835 | | |
| 836 | | # fake it, for now |
| 837 | | self.starttls='success' |
| 838 | | |
| 839 | | def _startSSL_stdlib(self): |
| 840 | | log.debug("_startSSL_stdlib called") |
| 841 | | tcpsock=self._owner.Connection |
| 842 | | tcpsock._sock.setblocking(True) |
| 843 | | tcpsock._sslObj = socket.ssl(tcpsock._sock, None, None) |
| 844 | | tcpsock._sock.setblocking(False) |
| 845 | | tcpsock._sslIssuer = tcpsock._sslObj.issuer() |
| 846 | | tcpsock._sslServer = tcpsock._sslObj.server() |
| 847 | | wrapper = StdlibSSLWrapper(tcpsock._sslObj, tcpsock._sock) |
| 848 | | tcpsock._recv = wrapper.recv |
| 849 | | tcpsock._send = wrapper.send |
| 850 | | self.starttls='success' |
| 851 | | |
| 852 | | def _ssl_verify_callback(self, sslconn, cert, errnum, depth, ok): |
| 853 | | # Exceptions can't propagate up through this callback, so print them here. |
| 854 | | try: |
| 855 | | self._owner.Connection.ssl_fingerprint_sha1 = cert.digest('sha1') |
| 856 | | if errnum == 0: |
| 857 | | return True |
| 858 | | self._owner.Connection.ssl_errnum = errnum |
| 859 | | self._owner.Connection.ssl_cert_pem = OpenSSL.crypto.dump_certificate( |
| 860 | | OpenSSL.crypto.FILETYPE_PEM, cert) |
| 861 | | return True |
| 862 | | except: |
| 863 | | log.error("Exception caught in _ssl_info_callback:", exc_info=True) |
| 864 | | traceback.print_exc() # Make sure something is printed, even if log is disabled. |
| 865 | | |
| 866 | | def StartTLSHandler(self, conn, starttls): |
| 867 | | ''' Handle server reply if TLS is allowed to process. Behaves accordingly. |
| 868 | | Used internally.''' |
| 869 | | if starttls.getNamespace() <> NS_TLS: |
| 87 |