All new accounts created on Gitlab now require administrator approval. If you invite any collaborators, please let Flux staff know so they can approve the accounts.

sslxmlrpc_server.py.in 7.25 KB
Newer Older
1 2 3 4 5 6 7 8
#! /usr/bin/env python
#
# EMULAB-COPYRIGHT
# Copyright (c) 2004 University of Utah and the Flux Group.
# All rights reserved.
#
import sys
import getopt
9 10 11 12 13 14
import os
import traceback
import syslog
import string

# Testbed specific stuff
15
sys.path.append("@prefix@/lib")
16 17
from libdb        import *
from libtestbed   import SENDMAIL, TBOPS
18 19
from emulabserver import *

20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
from SimpleXMLRPCServer import SimpleXMLRPCDispatcher
from SimpleXMLRPCServer import SimpleXMLRPCRequestHandler
from M2Crypto import SSL
from M2Crypto.SSL import SSLError

# 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"

38 39 40 41 42 43 44
#
# 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
45 46 47

# syslog facility
LOGFACIL	= "@TBLOGFACIL@"
48 49

#
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 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 167 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 202 203 204 205 206 207 208 209
# 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
	self.logRequests   = 0
        self.emulabserver  = None;

	ctx = SSL.Context('sslv23')
	ctx.load_cert(server_cert, server_cert)
	ctx.load_verify_info(ca_cert)
        ctx.set_verify(SSL.verify_peer | SSL.verify_fail_if_no_peer_cert, 16)
	ctx.set_allow_unknown_ca(0)
	#ctx.set_info_callback()
	    
        SimpleXMLRPCDispatcher.__init__(self)
        SSL.SSLServer.__init__(self, (ADDR, PORT),
                               SimpleXMLRPCRequestHandler, ctx)
	pass

    def logit(self, msg):
        if debug:
            print msg
            pass
        else:
            syslog.syslog(syslog.LOG_INFO, msg);
            pass
        return
    
    #
    # 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):
        try:
            meth = getattr(self.emulabserver, method);
        except AttributeError:
            raise Exception('method "%s" is not supported' % method)
        else:
            return apply(meth, params);
        pass

    #
    # Get the unix_uid for the user.
    #
    def getuserid(self, uid):
        userQuery = DBQueryFatal("SELECT unix_uid FROM users AS u "
                                 "WHERE u.uid=%s",
                                 (uid,))
    
        if len(userQuery) == 0:
            return 0
    
        return int(userQuery[0][0])
    
    #
    # Get the group list for the user.
    #
    def getusergroups(self, user):
        result = []
        
        res = DBQueryFatal("select distinct g.gid,g.unix_gid "
                           "  from group_membership as m "
                           "left join groups as g on "
                           "  g.pid=m.pid and g.gid=m.gid "
                           "where m.uid=%s "
                           "order by date_approved asc ",
                           (user,))

        
        for group in res:
            result.append(int(group[1]));
            pass
    
        return result
    
    #
    # Flip to the user that is in the certificate.
    #
    def fliptouser(self, request, client):
        subject = request.get_peer_cert().get_subject()
        if self.debug:
            self.logit(str(subject))
            pass
        
        self.user = getattr(subject, "CN");
        #
        # Must be a valid and non-zero unix_uid from the DB.
        #
        self.uid = self.getuserid(self.user)
        
        if self.uid == 0:
            self.logit('No such user: "%s"' % self.user)
            raise Exception('No such user: "%s"' % self.user)

        self.glist = self.getusergroups(self.user);
        if len(self.glist) == 0:
            self.logit('No groups for user: "%s"' % self.user)
            raise Exception('No groups for user: "%s"' % self.user)
       
        self.logit("Connect from %s: %s %s" %
                   (client[0], self.user, str(self.glist)))
        
        try:
            os.setgid(self.glist[0])
            os.setgroups(self.glist)
            os.setuid(self.uid)

            os.environ["USER"]    = self.user;
            os.environ["LOGNAME"] = self.user;
            pass
        except:
            traceback.print_exc()
            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:
                self.active_children = []
            self.active_children.append(pid)
            request.socket.close()
            return
        else:
            # Child process.
            # This must never return, hence os._exit()!
            try:
                self.fliptouser(request, client_address);
                self.emulabserver = EmulabServer(readonly=0)
                self.finish_request(request, client_address)
                self.close_request(request)
                self.logit("request finished");
                os._exit(0)
            except:
                try:
                    self.handle_error(request, client_address)
                finally:
                    os._exit(1)

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

210 211
    pass

212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
if not debug:
    #
    # Connect to syslog. 
    #
    syslog.openlog("sslxmlrpc", syslog.LOG_PID,
                   eval("syslog.LOG_" + string.upper(LOGFACIL)))
    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();
241 242 243
    pass

#
244 245 246
# 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. 
247
# 
248 249 250
server = MyServer(debug);
server.serve_forever()