sslxmlrpc_server.py.in 23.9 KB
Newer Older
1
#!/usr/local/bin/python
2
#
3
# Copyright (c) 2005-2017 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 B. Stoller's avatar
Leigh B. 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         = {};
Leigh B Stoller's avatar
Leigh B Stoller committed
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
        
Mike Hibler's avatar
Mike Hibler committed
272 273 274 275 276
        dargs = (self,)
        if sys.version_info[0] >= 2 and sys.version_info[1] >= 5:
            dargs = (self,False,None)
            pass
        SimpleXMLRPCDispatcher.__init__(*dargs)
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291
        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
292

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

    ##
    # 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 B. Stoller's avatar
Leigh B. Stoller committed
315
    def set_path(self, path, client_address):
316 317
        if not self.emulabserver:
            if path not in ALLOWED_PATHS:
318
                self.logit("Disallowed path: %s" % path,facility=syslog.LOG_ERR)
319 320 321
                raise Exception("Path not allowed: %s" % path)
            path = os.path.join(path, "lib")
            if not os.access(path, os.X_OK):
322
                self.logit("Path not accessible by user: %s" % path,facility=syslog.LOG_ERR)
323 324 325 326 327 328
                raise Exception("Permission denied: %s" % path)

            if path not in sys.path:
                sys.path.append(path)
                pass
            from emulabserver import EmulabServer
329 330 331

            self.emulabserver = EmulabServer(self.uid, self.uid_idx,
                                             readonly=0,
332 333
                                             clientip=client_address[0],
                                             debug=self.debug)
334
            self.logit("imported EmulabServer")
335 336
            pass
        return
337 338 339 340 341 342 343 344
    
    #
    # 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):
345 346
        self.fliptouser(params)
        
347 348 349 350 351
        try:
            meth = getattr(self.emulabserver, method);
        except AttributeError:
            raise Exception('method "%s" is not supported' % method)
        else:
352
            self.logit("Calling method '" + method + "'");
353 354 355 356
            return apply(meth, params);
        pass

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

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

        if len(res) == 0:
            return 0

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

        return len(res)
    
396 397 398
    #
    # Get the group list for the user.
    #
399
    def getusergroups(self, uid_idx):
400
        res = DBQueryFatal("select distinct g.pid,g.unix_gid,date_approved "
401 402
                           "  from group_membership as m "
                           "left join groups as g on "
403 404
                           "  g.pid_idx=m.pid_idx and g.gid_idx=m.gid_idx "
                           "where m.uid_idx=%s "
405
                           "order by date_approved asc ",
406
                           (str(uid_idx),))
407 408
        
        for group in res:
409 410 411 412 413 414
            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]))
415
            pass
416
        pass
417

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

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

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

463 464
        self.logit("Connect from %s: %s,%d" %
                   (client[0], self.uid, self.uid_idx))
465
        
466 467 468 469 470 471 472
        #
        # 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()
        
473
        if self.checkcert(self.uid_idx, serial) == 0:
474
            self.logit('No such cert with serial "%s"' % serial,facility=syslog.LOG_ERR)
475
            raise Exception('No such cert with serial "%s"' % serial)
476

477 478 479 480
        #
        # We have to make sure the exports are done, since the user might
        # not be using the web interface at all.
        #
481
        if WITHZFS and ZFS_NOEXPORT and int(exports_active) > 0:
482 483 484 485 486
            limit = ((int(exports_active) * 24) - 12) * 3600
                
            res = DBQueryFatal("select UNIX_TIMESTAMP(last_activity) "
                               "  from user_stats "
		               "where uid_idx=%s",
487
                               (str(self.uid_idx),))
488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504
            
            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))
                
505 506 507 508
                if os.system(TBDIR + "/sbin/exports_setup"):
                    raise Exception("exports_setup failed")
                pass
            pass
509 510 511 512 513 514
        pass

    #
    # Flip to the user that is in the certificate.
    #
    def fliptouser(self, params):
Leigh B Stoller's avatar
Leigh B Stoller committed
515 516 517 518 519
        if self.flipped:
            return;

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

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

552
            os.environ["HOME"]    = pwddb[5];
553 554
            os.environ["USER"]    = self.uid;
            os.environ["LOGNAME"] = self.uid;
555 556
            pass
        except:
557
            self.logit(traceback.format_exc(),facility=syslog.LOG_ERR)
558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578
            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:
579
                if sys.version_info.major == 2:
580 581 582
                    if (sys.version_info.minor < 7 or
                        (sys.version_info.minor == 7 and
                         sys.version_info.micro < 8)):
583 584 585 586 587 588 589 590 591 592 593 594 595
                        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)
596 597 598 599 600 601
            request.socket.close()
            return
        else:
            # Child process.
            # This must never return, hence os._exit()!
            try:
602
                self.setupuser(request, client_address);
603

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

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

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

627 628 629 630 631 632 633 634 635
    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]

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

644 645
    pass

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

#
# Daemonize when not running in debug mode.
#
656 657
if not debug:
    #
658
    # Connect to syslog.
659 660
    #
    syslog.openlog("sslxmlrpc", syslog.LOG_PID,
661
                   getattr(syslog, "LOG_" + string.upper(LOGFACIL)))
662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
    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();
685 686 687
    pass

#
688 689 690
# 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. 
691
# 
692 693 694
server = MyServer(debug)
while 1:
    server.handle_request()