sslxmlrpc_server.py.in 24 KB
Newer Older
1
#!/usr/local/bin/python
2
#
3
# Copyright (c) 2005-2018 University of Utah and the Flux Group.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
# 
# {{{EMULAB-LICENSE
# 
# This file is part of the Emulab network testbed software.
# 
# This file is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or (at
# your option) any later version.
# 
# This file 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 Affero General Public
# License for more details.
# 
# You should have received a copy of the GNU Affero General Public License
# along with this file.  If not, see <http://www.gnu.org/licenses/>.
# 
# }}}
23 24 25
#
import sys
import getopt
26
import os, os.path
27
import time
28
import pwd
29 30 31
import traceback
import syslog
import string
32
import socket
33 34 35 36
import BaseHTTPServer

from SimpleXMLRPCServer import SimpleXMLRPCDispatcher

37
# Testbed specific stuff
38
TBDIR = "@prefix@"
39 40 41 42 43
TBPATH = "@prefix@/lib"
if TBPATH not in sys.path:
    sys.path.append(TBPATH)
    pass

44
from libdb        import *
45

46 47 48 49 50 51 52
try:
    from M2Crypto import SSL
    from M2Crypto.SSL import SSLError
except ImportError, e:
    sys.stderr.write("error: The py-m2crypto port is not installed\n")
    sys.exit(1)
    pass
53 54 55 56 57 58 59 60 61 62 63 64 65 66

# When debugging, runs in foreground printing to stdout instead of syslog
debug           = 0

# The port to listen on. We should get this from configure.
PORT            = 3069

# The local address. Using INADDY_ANY for now.
ADDR            = "0.0.0.0"

# The server certificate and the server CS.
server_cert     = "@prefix@/etc/server.pem"
ca_cert         = "@prefix@/etc/emulab.pem"

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
#
# This is an optional feature, off by default except at the MAINSITE, to
# ensure the SSL part of accept() does not block the main thread.  Our
# server is single-threaded, and thus if a client such as a
# connection-based scanner connects and sends no data, that connection
# will block the server from accept()ing other incoming connections
# until socket timeout, probably around 30 seconds.
#
# However, this feature depends on certain m2crypto internals (and
# cannot be done more cleanly, at least on 0.21.1, given m2crypto's
# model).  Thus, it is off by default.
#
LIMIT_SSL_ACCEPT_TIME = False
TBMAINSITE = "@TBMAINSITE@"
if TBMAINSITE == "1":
    LIMIT_SSL_ACCEPT_TIME = True
83 84 85
# Set a timeout for the SSL_accept phase of the client session setup.
# This allows us to not block the main thread indefinitely if a non-SSL
# or malicious client connects to us and says nothing.
86
SSL_CLIENT_ACCEPT_TIMEOUT = 3
87
# Set a timeout that is used for the client socket *after* SSL accept.
88
SSL_CLIENT_REQUEST_TIMEOUT = -1.0
89

90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
have_ssl_timeout_error = False
try:
    from M2Crypto.SSL import SSLTimeoutError
    have_ssl_timeout_error = True
except:
    sys.stderr.write(
        "warning: old m2crypto, cannot warn about ssl timeouts")
    have_ssl_timeout_error = False
    pass
if LIMIT_SSL_ACCEPT_TIME:
    try:
        import SocketServer
        from M2Crypto.SSL import Checker
    except:
        sys.stderr.write(
            "error: you must disable LIMIT_SSL_ACCEPT_TIME; necessary"
            " M2Crypto modules not found!")
        sys.exit(1)
    pass

110 111 112 113 114 115 116
#
# By default, run a wrapper class that includes all off the modules.
# The client can invoke methods of the form experiment.swapexp when
# the server is invoked in this manner.
# 
DEFAULT_MODULE = "EmulabServer"
module         = DEFAULT_MODULE
117

118 119 120 121 122 123 124 125 126 127 128 129 130 131
#
# "Standard" paths for the real and development versions of the software.
#
STD_PATH       = "/usr/testbed"
STD_DEVEL_PATH = "/usr/testbed/devel"

#
# The set of paths that the user is allowed to specify in their request.  The
# path specifies where the 'emulabserver' module will be loaded from.  In
# reality, the path only has an effect on the first request in a persistent
# connection, any subsequent requests will reuse the same module.
#
ALLOWED_PATHS  = [ STD_PATH, "@prefix@" ]

132 133
# syslog facility
LOGFACIL	= "@TBLOGFACIL@"
134

135 136 137 138
# See below.
WITHZFS            = @WITHZFS@
ZFS_NOEXPORT       = @ZFS_NOEXPORT@

139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
##
# Taken from the SimpleXMLRPCServer module in the python installation and
# modified to support persistent connections.
#
class MyXMLRPCRequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
    """Simple XML-RPC request handler class.

    Handles all HTTP POST requests and attempts to decode them as
    XML-RPC requests.
    """

    ##
    # Change the default protocol so that persistent connections are the norm.
    #
    protocol_version = "HTTP/1.1"

    ##
    # Handle a POST request from the user.  This method was changed from the
    # standard version to not close the 
    #
    def do_POST(self):
        """Handles the HTTP POST request.

        Attempts to interpret all HTTP POST requests as XML-RPC calls,
        which are forwarded to the server's _dispatch method for handling.
        """

        # Update PYTHONPATH with the user's requested path.
Leigh Stoller's avatar
Leigh Stoller committed
167
        self.server.set_path(self.path, self.client_address)
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201

        try:
            # get arguments
            data = self.rfile.read(int(self.headers["content-length"]))
            # In previous versions of SimpleXMLRPCServer, _dispatch
            # could be overridden in this class, instead of in
            # SimpleXMLRPCDispatcher. To maintain backwards compatibility,
            # check to see if a subclass implements _dispatch and dispatch
            # using that method if present.
            response = self.server._marshaled_dispatch(
                    data, getattr(self, '_dispatch', None)
                )
        except: # This should only happen if the module is buggy
            # internal error, report as HTTP server error
            self.send_response(500)
            self.end_headers()
            self.wfile.flush()
        else:
            # got a valid XML RPC response
            self.send_response(200)
            self.send_header("Content-type", "text/xml")
            self.send_header("Content-length", str(len(response)))
            self.end_headers()
            self.wfile.write(response)
            self.wfile.flush()
            pass
        return

    def log_request(self, code='-', size='-'):
        """Selectively log an accepted request."""

        if self.server.logRequests:
            BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, code, size)

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
class MyConnection(SSL.Connection):
    """
    A simple subclassing of the native SSL.Connection, because that
    class does not give you the ability to set a timeout for the ssl
    acceptance of a new client socket, once it has been accept()ed.
    """

    def accept(self):
        """
        (NB: this comes nearly directly from M2Crypto.SSL.Connection; it
        is only slightly modified to allow a timeout during the SSL part
        of the accept, once the socket accept has finished.  Once that
        succeeds, we restore a sane timeout for the client socket,
        because we use a forking mixin, so all further work will happen
        in the child.
        
        Accept an SSL connection. The return value is a pair (ssl,
        addr) where ssl is a new SSL connection object and addr is the
        address bound to the other end of the SSL connection.
        """
        sock, addr = self.socket.accept()
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250
        try:
            # Use our subclass, not the default SSL.connection
            ssl = MyConnection(self.ctx, sock)
            # Set non-blocking so that the M2Crypto openssl wrapper glue
            # will honor the _timeout we're about to set; but only if the
            # timeout is not None.
            if SSL_CLIENT_ACCEPT_TIMEOUT is not None:
                sock.setblocking(False)
            # Set a timeout that gets used in the below call of ssl.accept_ssl()
            ssl._timeout = SSL_CLIENT_ACCEPT_TIMEOUT
            ssl.addr = addr
            ssl.setup_ssl()
            ssl.set_accept_state()
            ssl.accept_ssl()
            check = getattr(self, 'postConnectionCheck',
                            self.serverPostConnectionCheck)
            if check is not None:
                if not check(ssl.get_peer_cert(), ssl.addr[0]):
                    raise Checker.SSLVerificationError(
                        'post connection check failed')
            # Make the socket blocking again.
            sock.setblocking(True)
            # ... and undo our timeout.
            ssl._timeout = SSL_CLIENT_REQUEST_TIMEOUT
            return ssl, addr
        except Exception, exc:
            exc.__setattr__('client_address',addr)
            raise
251

252
#
253 254 255 256 257 258
# A simple server based on the forking version SSLServer. We fork cause
# we want to change our uid/gid to that of the person on the other end.
# 
class MyServer(SSL.ForkingSSLServer, SimpleXMLRPCDispatcher):
    def __init__(self, debug):
        self.debug         = debug
259
        self.logRequests   = 0
260
        self.emulabserver  = None;
261 262
        self.glist         = [];
        self.plist         = {};
263 264
        self.flipped       = 0;
        
265 266 267
        ctx = SSL.Context('sslv23')
        ctx.load_cert(server_cert, server_cert)
        ctx.load_verify_info(ca_cert)
268
        ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 16)
269 270
        ctx.set_allow_unknown_ca(0)
        #ctx.set_info_callback()
271 272 273

        # Oh, so glad this is built into TCPServer.
        self.allow_reuse_address = True
274
        
275 276 277 278 279
        dargs = (self,)
        if sys.version_info[0] >= 2 and sys.version_info[1] >= 5:
            dargs = (self,False,None)
            pass
        SimpleXMLRPCDispatcher.__init__(*dargs)
280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
        if LIMIT_SSL_ACCEPT_TIME:
            #
            # Ugh -- duplicate the SSL.SSLServer constructor body, other
            # than to use our little MyConnection wrapper instead of
            # SSLConnection.
            SocketServer.BaseServer.__init__(self, (ADDR,PORT),
                                             MyXMLRPCRequestHandler)
            self.ssl_ctx = ctx
            self.socket = MyConnection(self.ssl_ctx)
            self.server_bind()
            self.server_activate()
        else:
            SSL.SSLServer.__init__(self, (ADDR, PORT),
                                   MyXMLRPCRequestHandler, ctx)
        pass
295

296 297 298 299 300
    ##
    # Log a message to stdout, if in debug mode, otherwise write to syslog.
    #
    # @param msg The message to log.
    #
301
    def logit(self, msg, facility=syslog.LOG_INFO):
302 303 304 305
        if debug:
            print msg
            pass
        else:
306
            syslog.syslog(facility, msg);
307 308
            pass
        return
309 310 311 312 313 314 315 316 317

    ##
    # Updates PYTHONPATH and imports the 'emulabserver' module on its first
    # invocation.  The specified path must be in the ALLOWED_PATHS list and
    # readable by the user, otherwise the request will fail.
    #
    # @param path The path from the POST request, should not include "lib" on
    # the end (e.g. "/usr/testbed")
    #
Leigh Stoller's avatar
Leigh Stoller committed
318
    def set_path(self, path, client_address):
319 320
        if not self.emulabserver:
            if path not in ALLOWED_PATHS:
321
                self.logit("Disallowed path: %s" % path,facility=syslog.LOG_ERR)
322 323 324
                raise Exception("Path not allowed: %s" % path)
            path = os.path.join(path, "lib")
            if not os.access(path, os.X_OK):
325
                self.logit("Path not accessible by user: %s" % path,facility=syslog.LOG_ERR)
326 327 328 329 330 331
                raise Exception("Permission denied: %s" % path)

            if path not in sys.path:
                sys.path.append(path)
                pass
            from emulabserver import EmulabServer
332 333 334

            self.emulabserver = EmulabServer(self.uid, self.uid_idx,
                                             readonly=0,
335 336
                                             clientip=client_address[0],
                                             debug=self.debug)
337
            self.logit("imported EmulabServer")
338 339
            pass
        return
340 341 342 343 344 345 346 347
    
    #
    # There might be a better arrangement, but the problem is that we
    # do not want to create the server instance until we get a chance
    # to look at the certificate and determine the priv level. See
    # below in process_request(). 
    #
    def _dispatch(self, method, params):
348 349
        self.fliptouser(params)
        
350 351 352 353 354
        try:
            meth = getattr(self.emulabserver, method);
        except AttributeError:
            raise Exception('method "%s" is not supported' % method)
        else:
355
            self.logit("Calling method '" + method + "'");
356 357 358 359
            return apply(meth, params);
        pass

    #
360
    # Get the unix_uid for the user. User must be active. 
361
    #
362 363 364
    def getuserid(self, uuid):
        userQuery = DBQueryFatal("select uid,uid_idx,unix_uid,status "
                                 "  from users "
365 366
                                 "where (uid_uuid=%s or uid=%s) and "
                                 "       status='active'",
367
                                 (uuid, uuid))
368
        
369
        if len(userQuery) == 0:
370
            return (None, None, 0);
371
        
372 373
        if (userQuery[0][3] != "active"):
            return (None, None, -1);
374
        
375
        return (userQuery[0][0], int(userQuery[0][1]), int(userQuery[0][2]))
376 377

    #
378
    # Check if the user is an stud.
379
    #
380 381 382
    def isstuduser(self, uid_idx):
        res = DBQueryFatal("select stud from users where uid_idx=%s",
                           (str(uid_idx),))
383 384 385 386 387

        if len(res) == 0:
            return 0

        return res[0][0]
388
    
389 390 391
    #
    # Check the certificate serial number. 
    #
392
    def checkcert(self, uid_idx, serial):
393
        res = DBQueryFatal("select idx from user_sslcerts "
394
                           "where uid_idx=%s and idx=%s and revoked is null ",
395
                           (str(uid_idx), serial))
396 397 398

        return len(res)
    
399 400 401
    #
    # Get the group list for the user.
    #
402
    def getusergroups(self, uid_idx):
403
        res = DBQueryFatal("select distinct g.pid,g.unix_gid,date_approved "
404 405
                           "  from group_membership as m "
                           "left join groups as g on "
406 407
                           "  g.pid_idx=m.pid_idx and g.gid_idx=m.gid_idx "
                           "where m.uid_idx=%s "
408
                           "order by date_approved asc ",
409
                           (str(uid_idx),))
410 411
        
        for group in res:
412 413 414 415 416 417
            self.glist.append(int(group[1]))
            # List of all projects, with a list of gids per project.
            if not self.plist.has_key(group[0]):
                self.plist[group[0]] = []
                pass
            self.plist[group[0]].append(int(group[1]))
418
            pass
419
        pass
420

421
    def setupuser(self, request, client):
422 423
        exports_active = TBGetSiteVar("general/export_active");
        
424 425 426 427
        subject = request.get_peer_cert().get_subject()
        if self.debug:
            self.logit(str(subject))
            pass
428 429 430 431 432 433

        #
        # The CN might look like UUID,serial so split it up.
        #
        cnwords = getattr(subject, "CN").split(",")
        self.uuid = cnwords[0]
434 435 436 437
        
        #
        # Must be a valid and non-zero unix_uid from the DB.
        #
438
        (self.uid,self.uid_idx,self.unix_uid) = self.getuserid(self.uuid)
439
        
440
        if self.unix_uid == 0:
441
            self.logit('No such user: "%s"' % self.uuid,facility=syslog.LOG_ERR)
442
            raise Exception('No such user: "%s"' % self.uuid)
443
        
444
        if self.unix_uid == -1:
445
            self.logit('User "%s,%d" is not active' % (self.uid,self.uid_idx),facility=syslog.LOG_ERR)
446 447
            raise Exception('User "%s,%d" is not active' %
                            (self.uid,self.uid_idx))
448

449
        self.stud = self.isstuduser(self.uid_idx)
450
        if self.stud:
451 452 453 454 455 456 457
            try:
                ALLOWED_PATHS.extend(map(lambda x:
                                         os.path.join(STD_DEVEL_PATH, x),
                                         os.listdir(STD_DEVEL_PATH)))
                pass
            except OSError:
                pass
458
            pass
459
        
460
        self.getusergroups(self.uid_idx);
461
        if len(self.glist) == 0:
462
            self.logit('No groups for user: "%s,%d"' % (self.uid,self.uid_idx),facility=syslog.LOG_ERR)
463 464
            raise Exception('No groups for user: "%s,%d"' %
                            (self.uid,self.uid_idx))
465

466 467
        self.logit("Connect from %s: %s,%d" %
                   (client[0], self.uid, self.uid_idx))
468
        
469 470 471 472 473 474 475
        #
        # Check the certificate serial number. At the moment, the serial
        # must match a certificate that is in the DB for that user. This
        # is my crude method of certificate revocation. 
        #
        serial = request.get_peer_cert().get_serial_number()
        
476
        if self.checkcert(self.uid_idx, serial) == 0:
477
            self.logit('No such cert with serial "%s"' % serial,facility=syslog.LOG_ERR)
478
            raise Exception('No such cert with serial "%s"' % serial)
479

480 481 482 483
        #
        # We have to make sure the exports are done, since the user might
        # not be using the web interface at all.
        #
484
        if WITHZFS and ZFS_NOEXPORT and int(exports_active) > 0:
485 486 487 488 489
            limit = ((int(exports_active) * 24) - 12) * 3600
                
            res = DBQueryFatal("select UNIX_TIMESTAMP(last_activity) "
                               "  from user_stats "
		               "where uid_idx=%s",
490
                               (str(self.uid_idx),))
491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507
            
            last_activity = int(res[0][0])

            self.logit("%s: limit,last_activity for %s,%d: %d,%d,%d" %
                           (client[0], self.uid, self.uid_idx,
                            limit, last_activity, int(time.time())))
            
            # Always update weblogin_last so exports_setup will do something,
            # and to mark activity to keep mount active.
            DBQueryFatal("update user_stats set last_activity=now() "
		         "where uid_idx=%s",
                         (str(self.uid_idx),))

            if time.time() - last_activity > limit:
                self.logit("%s: calling exports_setup for %s,%d" %
                           (client[0], self.uid, self.uid_idx))
                
508 509 510 511
                if os.system(TBDIR + "/sbin/exports_setup"):
                    raise Exception("exports_setup failed")
                pass
            pass
512 513 514 515 516 517
        pass

    #
    # Flip to the user that is in the certificate.
    #
    def fliptouser(self, params):
518 519 520 521 522
        if self.flipped:
            return;

        self.flipped = 1;
        
523 524 525
        #
        # BSD 16 group limit stupidity. This is barely a solution.
        #
Leigh Stoller's avatar
Leigh Stoller committed
526
        if len(self.glist) > 15:
527 528 529 530 531 532 533 534
            argdict = params[1]
            project = None

            if argdict.has_key("pid"):
                project = argdict["pid"]
            elif argdict.has_key("proj"):
                project = argdict["proj"]
            else:
535
                self.logit('Too many groups and no project given as an arg',facility=syslog.LOG_ERR)
536 537 538 539 540
                pass
            
            if project:
                if self.plist.has_key(project):
                    self.glist = self.plist[project]
541
                    self.logit("Setting groups from project %s" % project,facility=syslog.LOG_ERR)
542 543
                else:
                    self.logit('Too many groups but not a member of "%s"' %
544
                               project,facility=syslog.LOG_ERR)
545 546 547 548
                    pass
                pass
            pass
        self.logit("Setting groups: %s" % str(self.glist))
549 550 551
        try:
            os.setgid(self.glist[0])
            os.setgroups(self.glist)
552 553
            os.setuid(self.unix_uid)
            pwddb = pwd.getpwuid(self.unix_uid);
554

555
            os.environ["HOME"]    = pwddb[5];
556 557
            os.environ["USER"]    = self.uid;
            os.environ["LOGNAME"] = self.uid;
558 559
            pass
        except:
560
            self.logit(traceback.format_exc(),facility=syslog.LOG_ERR)
561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581
            os._exit(1)
            pass
        pass

    #
    # XXX - The builtin process_request() method for ForkingMixIn is
    # broken; it closes the "request" in the parent which shuts down
    # the ssl connection. So, I have moved the close_request into the
    # child where it should be, and in the parent I close the socket
    # by reaching into the Connection() class.
    # 
    # In any event, I need to do some other stuff in the child before we
    # actually handle the request. 
    # 
    def process_request(self, request, client_address):
        """Fork a new subprocess to process the request."""
        self.collect_children()
        pid = os.fork()
        if pid:
            # Parent process
            if self.active_children is None:
582
                if sys.version_info.major == 2:
583 584 585
                    if (sys.version_info.minor < 7 or
                        (sys.version_info.minor == 7 and
                         sys.version_info.micro < 8)):
586 587 588 589 590 591 592 593 594 595 596 597 598
                        self.active_children = []
                    else:
                        self.active_children = set()
                        pass
                else:
                    print "Only python version 2"
                    sys.exit(1);
                    pass
                pass
            if type(self.active_children) is list:
                self.active_children.append(pid)
            else:
                self.active_children.add(pid)
599 600 601 602 603 604
            request.socket.close()
            return
        else:
            # Child process.
            # This must never return, hence os._exit()!
            try:
605
                self.setupuser(request, client_address);
606

607 608 609 610 611 612 613
                #
                # New stateful firewall kills long term connections, as
                # for state waiting.
                #
                request.socket.setsockopt(socket.SOL_SOCKET,
                                          socket.SO_KEEPALIVE, 1);

614 615 616
                # Remove the old path since the user can request a different
                # one.
                sys.path.remove(TBPATH)
617 618
                self.finish_request(request, client_address)
                self.close_request(request)
619
                self.logit("request from %s finished" % (client_address[0]));
620 621 622 623 624 625 626 627 628 629
                os._exit(0)
            except:
                try:
                    self.handle_error(request, client_address)
                finally:
                    os._exit(1)

    def verify_request(self, request, client_address):
        return True

630 631 632 633 634 635 636 637 638
    def handle_error(self, request, client_address):
        caddr = "UNKNOWN"
        if client_address is None:
            (ext,exv,extb) = sys.exc_info()
            if exv is not None and hasattr(exv,'client_address'):
                caddr = exv.client_address[0]
        if client_address is not None:
            caddr = client_address[0]

639 640
        if exv is not None \
          and have_ssl_timeout_error and isinstance(exv,SSLTimeoutError):
641 642 643 644 645 646
            self.logit("SSLTimeoutError from %s" % (str(caddr)))
        else:
            self.logit(
                "error from %s: %s" % (str(caddr),traceback.format_exc()),
                facility=syslog.LOG_ERR)

647 648
    pass

649 650 651 652 653 654 655 656 657 658
#
# Check for debug flag.
# 
if len(sys.argv) > 1 and sys.argv[1] == "-d":
    debug = 1
    pass

#
# Daemonize when not running in debug mode.
#
659 660
if not debug:
    #
661
    # Connect to syslog.
662 663
    #
    syslog.openlog("sslxmlrpc", syslog.LOG_PID,
664
                   getattr(syslog, "LOG_" + string.upper(LOGFACIL)))
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687
    syslog.syslog(syslog.LOG_INFO, "SSL XMLRPC server starting up");

    #
    # Daemonize. We redirect our output into a log file cause I have no
    # idea what is going to use plain print. 
    #
    try:
        fp = open("@prefix@/log/sslxmlrpc_server.log", "a");
        sys.stdout = fp
        sys.stderr = fp
        sys.stdin.close();
        pass
    except:
        print "Could not open log file for append"
        sys.exit(1);
        pass

    pid = os.fork()
    if pid:
        os.system("echo " + str(pid) + " > /var/run/sslxmlrpc_server.pid")
        sys.exit(0)
        pass
    os.setsid();
688 689 690
    pass

#
691 692 693
# Create the server and serve forever. We register the instance above
# when we process the request cause we want to look at the cert before
# we decide on the priv level. 
694
# 
695 696 697
server = MyServer(debug)
while 1:
    server.handle_request()