diff --git a/cli/koji b/cli/koji index c247d842..8da52001 100755 --- a/cli/koji +++ b/cli/koji @@ -79,7 +79,7 @@ def get_options(): parser.disable_interspersed_args() parser.add_option("-c", "--config", dest="configFile", help=_("use alternate configuration file"), metavar="FILE", - default="/etc/koji.conf") + default="~/.koji/config") parser.add_option("--keytab", help=_("specify a Kerberos keytab to use")) parser.add_option("--principal", help=_("specify a Kerberos principal to use")) parser.add_option("--runas", help=_("run as the specified user (requires special privileges)")) @@ -131,23 +131,27 @@ def get_options(): 'server' : 'http://localhost/kojihub', 'web_url' : 'http://localhost/koji', 'topdir' : '/mnt/koji', + 'cert': '~/.koji/client.crt', + 'ca': '~/.koji/clientca.crt', + 'serverca': '~/.koji/serverca.crt' } - if os.access(options.configFile, os.F_OK): - f = open(options.configFile) - config = ConfigParser.ConfigParser() - config.readfp(f) - f.close() - #XXX - really need a more robust config file setup, but this will have - # to do for now - if config.has_section('koji'): - for name, value in config.items('koji'): - #note the defaults dictionary also serves to indicate which - #options *can* be set via the config file. Such options should - #not have a default value set in the option parser. - if defaults.has_key(name): - defaults[name] = value + # grab settings from /etc/koji.conf first, and allow them to be + # overridden by user config + for configFile in ('/etc/koji.conf', options.configFile): + if os.access(configFile, os.F_OK): + f = open(configFile) + config = ConfigParser.ConfigParser() + config.readfp(f) + f.close() + if config.has_section('koji'): + for name, value in config.items('koji'): + #note the defaults dictionary also serves to indicate which + #options *can* be set via the config file. Such options should + #not have a default value set in the option parser. + if defaults.has_key(name): + defaults[name] = value for name, value in defaults.iteritems(): - if getattr(options, name) is None: + if getattr(options, name, None) is None: setattr(options, name, value) return options, cmd, args[1:] @@ -3060,8 +3064,8 @@ def activate_session(session): if options.noauth: #skip authentication pass - elif options.user: - #authenticate using user/password + elif os.path.isfile(os.path.expanduser(options.cert)) or options.user: + # authenticate using SSL client cert or user/password session.login() elif sys.modules.has_key('krbV'): try: @@ -3084,7 +3088,7 @@ if __name__ == "__main__": options, command, args = get_options() session_opts = {} - for k in ('user', 'password', 'debug_xmlrpc', 'debug'): + for k in ('cert', 'ca', 'serverca', 'user', 'password', 'debug_xmlrpc', 'debug'): session_opts[k] = getattr(options,k) session = koji.ClientSession(options.server,session_opts) rv = 0 diff --git a/cli/koji.conf b/cli/koji.conf index de4b4f4d..123648cb 100644 --- a/cli/koji.conf +++ b/cli/koji.conf @@ -11,3 +11,13 @@ ;path to the koji top directory ;topdir = /mnt/koji +;configuration for SSL athentication + +;client certificate +;cert = ~/.koji/client.crt + +;certificate of the CA that issued the client certificate +;ca = ~/.koji/clientca.crt + +;certificate of the CA that issued the HTTP server certificate +;serverca = ~/.koji/serverca.crt diff --git a/hub/httpd.conf b/hub/httpd.conf index 08d9242b..d60a07dc 100644 --- a/hub/httpd.conf +++ b/hub/httpd.conf @@ -15,6 +15,7 @@ Alias /koji-hub "/usr/share/koji-hub/XMLRPC" PythonOption AuthPrincipal kojihub@EXAMPLE.COM PythonOption AuthKeytab /etc/koji.keytab PythonOption ProxyPrincipals kojihub@EXAMPLE.COM + PythonOption LoginCreatesUser On PythonOption KojiWebURL http://kojiweb.example.com/koji #format string for host principals (%s = hostname) PythonOption HostPrincipalFormat %s@EXAMPLE.COM @@ -26,3 +27,10 @@ Alias /koji-hub "/usr/share/koji-hub/XMLRPC" #autoreload is mostly useless to us (it would only reload kojixmlrpc.py) +# uncomment this to enable authentication via SSL client certificates +# +# SSLVerifyClient require +# SSLVerifyDepth 10 +# SSLUserName SSL_CLIENT_S_DN_CN +# SSLOptions +StdEnvVars +ExportCertData +# diff --git a/hub/kojixmlrpc.py b/hub/kojixmlrpc.py index 89348707..1c10e863 100644 --- a/hub/kojixmlrpc.py +++ b/hub/kojixmlrpc.py @@ -274,6 +274,7 @@ def handler(req, profiling=False): h.register_module(hostFunctions,"host") h.register_function(koji.auth.login) h.register_function(koji.auth.krbLogin) + h.register_function(koji.auth.sslLogin) h.register_function(koji.auth.logout) h.register_function(koji.auth.subsession) h.register_function(koji.auth.logoutChild) diff --git a/koji/__init__.py b/koji/__init__.py index 9066d493..99e5f02f 100644 --- a/koji/__init__.py +++ b/koji/__init__.py @@ -47,6 +47,7 @@ import urllib import urlparse import xmlrpclib from xmlrpclib import loads, Fault +import ssl.XMLRPCServerProxy def _(args): """Stub function for translation""" @@ -147,6 +148,7 @@ USER_STATUS = Enum(( # normal == username/password AUTHTYPE_NORMAL = 0 AUTHTYPE_KERB = 1 +AUTHTYPE_SSL = 2 #dependency types DEP_REQUIRE = 0 @@ -1049,6 +1051,7 @@ class ClientSession(object): if opts == None: opts = {} self.opts = opts + self.proxyClass = xmlrpclib.ServerProxy self.proxyOpts = {'allow_none':1} if opts.get('debug_xmlrpc',False): self.proxyOpts['verbose']=1 @@ -1071,14 +1074,22 @@ class ClientSession(object): self.callnum = 0 url = "%s?%s" %(self.baseurl,urllib.urlencode(sinfo)) self.sinfo = sinfo - self.proxy = xmlrpclib.ServerProxy(url,**self.proxyOpts) + self.proxy = self.proxyClass(url,**self.proxyOpts) def login(self,opts=None): - sinfo = self.callMethod('login',self.opts['user'], self.opts['password'],opts) - if not sinfo: - return False - self.setSession(sinfo) - return True + if self.opts.get('cert') and \ + os.path.isfile(os.path.expanduser(self.opts['cert'])): + return self.ssl_login(os.path.expanduser(self.opts['cert']), + os.path.expanduser(self.opts['ca']), + os.path.expanduser(self.opts['serverca'])) + elif self.opts.get('user') and self.opts.get('password'): + sinfo = self.callMethod('login',self.opts['user'], self.opts['password'],opts) + if not sinfo: + return False + self.setSession(sinfo) + return True + else: + raise AuthError, 'no credentials provided' def subsession(self): "Create a subsession" @@ -1108,7 +1119,7 @@ class ClientSession(object): ccache.init(cprinc) ccache.init_creds_keytab(principal=cprinc, keytab=keytab) else: - raise GenericError, 'cannot specify a principal without a keytab' + raise AuthError, 'cannot specify a principal without a keytab' else: # We're trying to log ourself in. Connect using existing credentials. cprinc = ccache.principal() @@ -1167,6 +1178,24 @@ class ClientSession(object): return 'host/%s@%s' % (servername, domain) + def ssl_login(self, cert, ca, serverca): + if not self.baseurl.startswith('https:'): + raise AuthError, '%s is not a SSL server URL, and you have configured SSL authentication' % self.baseurl + + certs = {} + certs['key_and_cert'] = os.path.expanduser(cert) + certs['ca_cert'] = os.path.expanduser(ca) + certs['peer_ca_cert'] = os.path.expanduser(serverca) + # only use a timeout during login + self.proxy = ssl.XMLRPCServerProxy.PlgXMLRPCServerProxy(self.baseurl, certs, timeout=60) + sinfo = self.callMethod('sslLogin') + if not sinfo: + return False + self.proxyClass = ssl.XMLRPCServerProxy.PlgXMLRPCServerProxy + self.proxyOpts['certs'] = certs + self.setSession(sinfo) + return True + def logout(self): if not self.logged_in: return @@ -1213,7 +1242,7 @@ class ClientSession(object): sinfo['callnum'] = self.callnum self.callnum += 1 url = "%s?%s" %(self.baseurl,urllib.urlencode(sinfo)) - proxy = xmlrpclib.ServerProxy(url,**self.proxyOpts) + proxy = self.proxyClass(url,**self.proxyOpts) else: proxy = self.proxy tries = 0 diff --git a/koji/auth.py b/koji/auth.py index 69b5de52..15cf36bb 100644 --- a/koji/auth.py +++ b/koji/auth.py @@ -276,7 +276,10 @@ class Session(object): login_principal = cprinc.name user_id = self.getUserIdFromKerberos(login_principal) if not user_id: - user_id = self.createUserFromKerberos(login_principal) + if context.opts.get('LoginCreatesUser', '').lower() in ('yes', 'on', 'true', '1'): + user_id = self.createUserFromKerberos(login_principal) + else: + raise koji.AuthError, 'Unknown Kerberos principal: %s' % login_principal hostip = context.req.connection.remote_ip if hostip == '127.0.0.1': @@ -317,6 +320,44 @@ class Session(object): return (local_ip, local_port, remote_ip, remote_port) + def sslLogin(self): + if self.logged_in: + raise koji.AuthError, "Already logged in" + + # populate standard CGI variables + context.req.add_common_vars() + env = context.req.subprocess_env + + if not (env.has_key('HTTPS') and + env['HTTPS'] == 'on'): + raise koji.AuthError, 'cannot call sslLogin() via a non-https connection' + + subject = context.req.user + if not subject: + raise koji.AuthError, 'could not determine the subject of the client certificate' + + # assume that the certificate subject is their Koji username + cursor = context.cnx.cursor() + query = """SELECT id, status FROM users + WHERE name = %(subject)s""" + cursor.execute(query, locals()) + result = cursor.fetchone() + if result: + user_id, status = result + else: + if context.opts.get('LoginCreatesUser', '').lower() in ('yes', 'on', 'true', '1'): + user_id = self.createUser(subject, koji.USERTYPES['NORMAL'], '') + status = None + else: + raise koji.AuthError, 'Unknown user: %s' % subject + + hostip = context.req.connection.remote_ip + if hostip == '127.0.0.1': + hostip = socket.gethostbyname(socket.gethostname()) + + sinfo = self.createSession(user_id, hostip, koji.AUTHTYPE_SSL) + return sinfo + def makeExclusive(self,force=False): """Make this session exclusive""" c = context.cnx.cursor() @@ -488,6 +529,23 @@ class Session(object): else: return None + def createUser(self, name, usertype, krb_principal): + """ + Create a new user, using the provided values. + Return the user_id of the newly-created user. + """ + cursor = context.cnx.cursor() + select = """SELECT nextval('users_id_seq')""" + cursor.execute(select, locals()) + user_id = cursor.fetchone()[0] + + insert = """INSERT INTO users (id, name, usertype, krb_principal) + VALUES (%(user_id)i, %(name)s, %(usertype)i, %(krb_principal)s)""" + cursor.execute(insert, locals()) + context.cnx.commit() + + return user_id + def createUserFromKerberos(self, krb_principal): """Create a new user, based on the Kerberos principal. Their username will be everything before the "@" in the principal. @@ -498,17 +556,7 @@ class Session(object): user_name = krb_principal[:atidx] user_type = koji.USERTYPES['NORMAL'] - c = context.cnx.cursor() - select = """SELECT nextval('users_id_seq')""" - c.execute(select, locals()) - user_id = c.fetchone()[0] - - insert = """INSERT INTO users (id, name, password, usertype, krb_principal) - VALUES (%(user_id)i, %(user_name)s, null, %(user_type)i, %(krb_principal)s)""" - c.execute(insert, locals()) - context.cnx.commit() - - return user_id + return self.createUser(user_name, user_type, krb_principal) def get_user_groups(user_id): """Get user groups @@ -549,6 +597,9 @@ def login(*args,**opts): def krbLogin(*args, **opts): return context.session.krbLogin(*args, **opts) +def sslLogin(*args, **opts): + return context.session.sslLogin(*args, **opts) + def logout(): return context.session.logout() diff --git a/koji/ssl/SSLCommon.py b/koji/ssl/SSLCommon.py new file mode 100644 index 00000000..68c20c69 --- /dev/null +++ b/koji/ssl/SSLCommon.py @@ -0,0 +1,127 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# Copyright 2005 Dan Williams and Red Hat, Inc. + +import os, sys +from OpenSSL import SSL +import SSLConnection +import httplib +import socket +import SocketServer + +def our_verify(connection, x509, errNum, errDepth, preverifyOK): + # print "Verify: errNum = %s, errDepth = %s, preverifyOK = %s" % (errNum, errDepth, preverifyOK) + + # preverifyOK should tell us whether or not the client's certificate + # correctly authenticates against the CA chain + return preverifyOK + + +def CreateSSLContext(certs): + key_and_cert = certs['key_and_cert'] + ca_cert = certs['ca_cert'] + peer_ca_cert = certs['peer_ca_cert'] + for f in key_and_cert, ca_cert, peer_ca_cert: + if f and not os.access(f, os.R_OK): + print "%s does not exist or is not readable." % f + os._exit(1) + + ctx = SSL.Context(SSL.SSLv3_METHOD) # SSLv3 only + ctx.use_certificate_file(key_and_cert) + ctx.use_privatekey_file(key_and_cert) + ctx.load_client_ca(ca_cert) + ctx.load_verify_locations(peer_ca_cert) + verify = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT + ctx.set_verify(verify, our_verify) + ctx.set_verify_depth(10) + ctx.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_TLSv1) + return ctx + + + +class PlgBaseServer(SocketServer.ThreadingTCPServer): + allow_reuse_address = 1 + + def __init__(self, server_addr, req_handler): + self._quit = False + self.allow_reuse_address = 1 + SocketServer.ThreadingTCPServer.__init__(self, server_addr, req_handler) + + def stop(self): + self._quit = True + + def serve_forever(self): + while not self._quit: + self.handle_request() + self.server_close() + + +class PlgBaseSSLServer(PlgBaseServer): + """ SSL-enabled variant """ + + def __init__(self, server_address, req_handler, certs, timeout=None): + self._timeout = timeout + self.ssl_ctx = CreateSSLContext(certs) + + PlgBaseServer.__init__(self, server_address, req_handler) + + sock = socket.socket(self.address_family, self.socket_type) + con = SSL.Connection(self.ssl_ctx, sock) + self.socket = SSLConnection.SSLConnection(con) + if sys.version_info[:3] >= (2, 3, 0): + self.socket.settimeout(self._timeout) + self.server_bind() + self.server_activate() + + host, port = self.socket.getsockname()[:2] + self.server_name = socket.getfqdn(host) + self.server_port = port + + +class PlgHTTPSConnection(httplib.HTTPConnection): + "This class allows communication via SSL." + + response_class = httplib.HTTPResponse + + def __init__(self, host, port=None, ssl_context=None, strict=None, timeout=None): + httplib.HTTPConnection.__init__(self, host, port, strict) + self.ssl_ctx = ssl_context + self._timeout = timeout + + def connect(self): + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + con = SSL.Connection(self.ssl_ctx, sock) + self.sock = SSLConnection.SSLConnection(con) + if sys.version_info[:3] >= (2, 3, 0): + self.sock.settimeout(self._timeout) + self.sock.connect((self.host, self.port)) + + +class PlgHTTPS(httplib.HTTP): + """Compatibility with 1.5 httplib interface + + Python 1.5.2 did not have an HTTPS class, but it defined an + interface for sending http requests that is also useful for + https. + """ + + _http_vsn = 11 + _http_vsn_str = 'HTTP/1.1' + + _connection_class = PlgHTTPSConnection + + def __init__(self, host='', port=None, ssl_context=None, strict=None, timeout=None): + self._setup(self._connection_class(host, port, ssl_context, strict, timeout)) + diff --git a/koji/ssl/SSLConnection.py b/koji/ssl/SSLConnection.py new file mode 100644 index 00000000..072320e4 --- /dev/null +++ b/koji/ssl/SSLConnection.py @@ -0,0 +1,161 @@ +#!/usr/bin/python +# +# Higher-level SSL objects used by rpclib +# +# Copyright (c) 2002 Red Hat, Inc. +# +# Author: Mihai Ibanescu +# Modifications by Dan Williams + + +from OpenSSL import SSL, crypto +import os, string, time, socket, select + + +class SSLConnection: + """ + This whole class exists just to filter out a parameter + passed in to the shutdown() method in SimpleXMLRPC.doPOST() + """ + + DEFAULT_TIMEOUT = 20 + + def __init__(self, conn): + """ + Connection is not yet a new-style class, + so I'm making a proxy instead of subclassing. + """ + self.__dict__["conn"] = conn + self.__dict__["close_refcount"] = 1 + self.__dict__["closed"] = False + self.__dict__["timeout"] = self.DEFAULT_TIMEOUT + + def __del__(self): + self.__dict__["conn"].close() + + def __getattr__(self,name): + return getattr(self.__dict__["conn"], name) + + def __setattr__(self,name, value): + setattr(self.__dict__["conn"], name, value) + + def settimeout(self, timeout): + if timeout == None: + self.__dict__["timeout"] = self.DEFAULT_TIMEOUT + else: + self.__dict__["timeout"] = timeout + self.__dict__["conn"].settimeout(timeout) + + def shutdown(self, how=1): + """ + SimpleXMLRpcServer.doPOST calls shutdown(1), + and Connection.shutdown() doesn't take + an argument. So we just discard the argument. + """ + self.__dict__["conn"].shutdown() + + def accept(self): + """ + This is the other part of the shutdown() workaround. + Since servers create new sockets, we have to infect + them with our magic. :) + """ + c, a = self.__dict__["conn"].accept() + return (SSLConnection(c), a) + + def makefile(self, mode, bufsize): + """ + We need to use socket._fileobject Because SSL.Connection + doesn't have a 'dup'. Not exactly sure WHY this is, but + this is backed up by comments in socket.py and SSL/connection.c + + Since httplib.HTTPSResponse/HTTPConnection depend on the + socket being duplicated when they close it, we refcount the + socket object and don't actually close until its count is 0. + """ + self.__dict__["close_refcount"] = self.__dict__["close_refcount"] + 1 + return PlgFileObject(self, mode, bufsize) + + def close(self): + if self.__dict__["closed"]: + return + self.__dict__["close_refcount"] = self.__dict__["close_refcount"] - 1 + if self.__dict__["close_refcount"] == 0: + self.shutdown() + self.__dict__["conn"].close() + self.__dict__["closed"] = True + + def sendall(self, data, flags=0): + """ + - Use select() to simulate a socket timeout without setting the socket + to non-blocking mode. + - Don't use pyOpenSSL's sendall() either, since it just loops on WantRead + or WantWrite, consuming 100% CPU, and never times out. + """ + timeout = self.__dict__["timeout"] + con = self.__dict__["conn"] + (read, write, excpt) = select.select([], [con], [], timeout) + if not con in write: + raise socket.timeout((110, "Operation timed out.")) + + starttime = time.time() + origlen = len(data) + sent = -1 + while len(data): + curtime = time.time() + if curtime - starttime > timeout: + raise socket.timeout((110, "Operation timed out.")) + + try: + sent = con.send(data, flags) + except SSL.SysCallError, e: + if e[0] == 32: # Broken Pipe + self.close() + sent = 0 + else: + raise socket.error(e) + except (SSL.WantWriteError, SSL.WantReadError): + time.sleep(0.2) + continue + + data = data[sent:] + return origlen - len(data) + + def recv(self, bufsize, flags=0): + """ + Use select() to simulate a socket timeout without setting the socket + to non-blocking mode + """ + timeout = self.__dict__["timeout"] + con = self.__dict__["conn"] + (read, write, excpt) = select.select([con], [], [], timeout) + if not con in read: + raise socket.timeout((110, "Operation timed out.")) + + starttime = time.time() + while True: + curtime = time.time() + if curtime - starttime > timeout: + raise socket.timeout((110, "Operation timed out.")) + + try: + return con.recv(bufsize, flags) + except SSL.ZeroReturnError: + return None + except SSL.WantReadError: + time.sleep(0.2) + return None + +class PlgFileObject(socket._fileobject): + def close(self): + """ + socket._fileobject doesn't actually _close_ the socket, + which we want it to do, so we have to override. + """ + try: + if self._sock: + self.flush() + self._sock.close() + finally: + self._sock = None + diff --git a/koji/ssl/XMLRPCServerProxy.py b/koji/ssl/XMLRPCServerProxy.py new file mode 100644 index 00000000..71bcee74 --- /dev/null +++ b/koji/ssl/XMLRPCServerProxy.py @@ -0,0 +1,175 @@ +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Library General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +# +# Modified by Dan Williams +# Further modified by Mike Bonnet + +import os, sys +import SSLCommon +import urllib +import xmlrpclib + +__version__='0.12' + +class PlgSSL_Transport(xmlrpclib.Transport): + + user_agent = "pyOpenSSL_XMLRPC/%s - %s" % (__version__, xmlrpclib.Transport.user_agent) + + def __init__(self, ssl_context, timeout=None, use_datetime=0): + if sys.version_info[:3] >= (2, 5, 0): + xmlrpclib.Transport.__init__(self, use_datetime) + self.ssl_ctx=ssl_context + self._timeout = timeout + self._https = None + + def make_connection(self, host): + # Handle username and password. + try: + host, extra_headers, x509 = self.get_host_info(host) + except AttributeError: + # Yay for Python 2.2 + pass + _host, _port = urllib.splitport(host) + self._https = SSLCommon.PlgHTTPS(_host, (_port and int(_port) or 443), ssl_context=self.ssl_ctx, timeout=self._timeout) + return self._https + + def close(self): + if self._https: + self._https.close() + self._https = None + + +class Plg_ClosableTransport(xmlrpclib.Transport): + """Override make_connection so we can close it.""" + def __init__(self): + self._http = None + + def make_connection(self, host): + # create a HTTP connection object from a host descriptor + import httplib + host, extra_headers, x509 = self.get_host_info(host) + self._http = httplib.HTTP(host) + return self._http + + def close(self): + if self._http: + self._http.close() + self._http = None + + +class PlgXMLRPCServerProxy(xmlrpclib.ServerProxy): + def __init__(self, uri, certs, timeout=None, verbose=0, allow_none=0): + if certs and len(certs) > 0: + self.ctx = SSLCommon.CreateSSLContext(certs) + self._transport = PlgSSL_Transport(ssl_context=self.ctx, timeout=timeout) + else: + self._transport = Plg_ClosableTransport() + xmlrpclib.ServerProxy.__init__(self, uri, transport=self._transport, + verbose=verbose, allow_none=allow_none) + + def cancel(self): + self._transport.close() + + +########################################################### +# Testing stuff +########################################################### + + +import threading +import time +import random +import OpenSSL +import socket + +client_start = False + +threadlist_lock = threading.Lock() +threadlist = {} + +class TestClient(threading.Thread): + def __init__(self, certs, num, tm): + self.server = PlgXMLRPCServerProxy("https://127.0.0.1:8886", certs, timeout=20) + self.num = i + self.tm = tm + threading.Thread.__init__(self) + + def run(self): + while not client_start: + time.sleep(0.05) + i = 0 + while i < 5: + reply = None + try: + reply = self.server.ping(self.num, i) + except OpenSSL.SSL.Error, e: + reply = "OpenSSL Error (%s)" % e + except socket.timeout, e: + reply = "Socket timeout (%s)" % e + threadlist_lock.acquire() + self.tm.inc() + threadlist_lock.release() + print "TRY(%d / %d): %s" % (self.num, i, reply) + time.sleep(0.05) + i = i + 1 + threadlist_lock.acquire() + del threadlist[self] + threadlist_lock.release() + +class TimeoutCounter: + def __init__(self): + self._timedout = 0 + self._lock = threading.Lock(); + + def inc(self): + self._lock.acquire() + self._timedout = self._timedout + 1 + self._lock.release() + + def get(self): + return self._timedout + +if __name__ == '__main__': + if len(sys.argv) < 4: + print "Usage: python XMLRPCServerProxy.py key_and_cert ca_cert peer_ca_cert" + sys.exit(1) + + certs = {} + certs['key_and_cert'] = sys.argv[1] + certs['ca_cert'] = sys.argv[2] + certs['peer_ca_cert'] = sys.argv[3] + + tm = TimeoutCounter() + i = 100 + while i > 0: + t = TestClient(certs, i, tm) + threadlist[t] = None + print "Created thread %d." % i + t.start() + i = i - 1 + + time.sleep(3) + print "Unleashing threads." + client_start = True + while True: + try: + time.sleep(0.25) + threadlist_lock.acquire() + if len(threadlist) == 0: + break + threadlist_lock.release() + except KeyboardInterrupt: + os._exit(0) + print "All done. (%d timed out)" % tm.get() + diff --git a/koji/ssl/__init__.py b/koji/ssl/__init__.py new file mode 100644 index 00000000..180fed66 --- /dev/null +++ b/koji/ssl/__init__.py @@ -0,0 +1 @@ +# identify this as the ssl module