+++ /dev/null
-import httplib
-import socket
-import uuid
-
-from samba.gensec import Security
-from samba.auth import AuthContext
-
-
-__all__ = ['NTLMAuthHandler']
-
-COOKIE_NAME="ocs-ntlm-auth"
-
-
-class NTLMAuthHandler(object):
- """
- HTTP/1.0 ``NTLM`` authentication middleware
-
- Parameters: application -- the application object that is called only upon
- successful authentication.
-
- """
-
- def __init__(self, application):
- # TODO: client expiration and/or cleanup
- self.client_status = {}
- self.application = application
-
- def _in_progress_response(self, start_response, ntlm_data=None, client_id=None):
- status = "401 %s" % httplib.responses[401]
- content = "More data needed..."
- headers = [("Content-Type", "text/plain"),
- ("Content-Length", "%d" % len(content))]
- if ntlm_data is None:
- www_auth_value = "NTLM"
- else:
- enc_ntlm_data = ntlm_data.encode("base64")
- www_auth_value = ("NTLM %s"
- % enc_ntlm_data.strip().replace("\n", ""))
- if client_id is not None:
- # MUST occur when ntlm_data is None, can still occur otherwise
- headers.append(("Set-Cookie", "%s=%s" % (COOKIE_NAME, client_id)))
-
- headers.append(("WWW-Authenticate", www_auth_value))
- start_response(status, headers)
-
- return [content]
-
- def _failure_response(self, start_response, explanation=None):
- status = "403 %s" % httplib.responses[403]
- content = "Authentication failure"
- if explanation is not None:
- content = content + " (%s)" % explanation
- headers = [("Content-Type", "text/plain"),
- ("Content-Length", "%d" % len(content))]
- start_response(status, headers)
-
- return [content]
-
- def _get_cookies(self, env):
- cookies = {}
- if "HTTP_COOKIE" in env:
- cookie_str = env["HTTP_COOKIE"]
- for pair in cookie_str.split(";"):
- (key, value) = pair.strip().split("=")
- cookies[key] = value
-
- return cookies
-
- def __call__(self, env, start_response):
- cookies = self._get_cookies(env)
- if COOKIE_NAME in cookies:
- client_id = cookies[COOKIE_NAME]
- else:
- client_id = None
-
- # old model that only works with mod_wsgi:
- # if "REMOTE_ADDR" in env and "REMOTE_PORT" in env:
- # client_id = "%(REMOTE_ADDR)s:%(REMOTE_PORT)s".format(env)
-
- if client_id is None or client_id not in self.client_status:
- # first stage
- server = Security.start_server(auth_context=AuthContext())
- server.start_mech_by_name("ntlmssp")
- client_id = str(uuid.uuid4())
-
- if "HTTP_AUTHORIZATION" in env:
- # Outlook may directly have sent a NTLM payload
- auth = env["HTTP_AUTHORIZATION"]
- auth_msg = server.update(auth[5:].decode('base64'))
- response = self._in_progress_response(start_response,
- auth_msg[1],
- client_id)
- self.client_status[client_id] = {"stage": "stage1",
- "server": server}
- else:
- self.client_status[client_id] = {"stage": "stage0",
- "server": server}
- response = self._in_progress_response(start_response, None, client_id)
- else:
- status_stage = self.client_status[client_id]["stage"]
-
- if status_stage == "ok":
- # client authenticated previously
- response = self.application(env, start_response)
- elif status_stage == "stage0":
- # test whether client supports "NTLM"
- if "HTTP_AUTHORIZATION" in env:
- auth = env["HTTP_AUTHORIZATION"]
- server = self.client_status[client_id]["server"]
- auth_msg = server.update(auth[5:].decode('base64'))
- response = self._in_progress_response(start_response,
- auth_msg[1])
- self.client_status[client_id]["stage"] = "stage1"
- else:
- del(self.client_status[client_id])
- response = self._failure_response(start_response,
- "failure at '%s'"
- % status_stage)
- elif status_stage == "stage1":
- if "HTTP_AUTHORIZATION" in env:
- auth = env["HTTP_AUTHORIZATION"]
- server = self.client_status[client_id]["server"]
- try:
- auth_msg = server.update(auth[5:].decode('base64'))
- except RuntimeError: # a bit violent...
- auth_msg = (0,)
-
- if auth_msg[0] == 1:
- # authentication completed
- self.client_status[client_id]["stage"] = "ok"
- del(self.client_status[client_id]["server"])
- response = self.application(env, start_response)
- else:
- # we start over with the whole process
-
- server = Security.start_server(auth_context=AuthContext())
- server.start_mech_by_name("ntlmssp")
- self.client_status[client_id] = {"stage": "stage0",
- "server": server}
- response = self._in_progress_response(start_response)
- else:
- del(self.client_status[client_id])
- response = self._failure_response(start_response,
- "failure at '%s'"
- % status_stage)
- else:
- raise RuntimeError("none shall pass!")
-
- return response
-
-
-middleware = NTLMAuthHandler
from pylons.wsgiapp import PylonsApp
from routes.middleware import RoutesMiddleware
+from openchange.web.auth.NTLMAuthHandler import NTLMAuthHandler
+
from ocsmanager.config.environment import load_environment
# from paste.auth.basic import AuthBasicHandler
# from ocsmanager.model.OCSAuthenticator import *
-from NTLMAuthHandler import NTLMAuthHandler
-
def make_app(global_conf, full_stack=True, static_files=True, **app_conf):
"""Create a Pylons WSGI application and return it
# authenticator = OCSAuthenticator(config)
# app = AuthBasicHandler(app, "OCSManager", authenticator)
- app = NTLMAuthHandler(app)
+ fqdn = "%(hostname)s.%(dnsdomain)s" % config["samba"]
+ app = NTLMAuthHandler(app, samba_host=fqdn)
# Establish the Registry for this application
app = RegistryManager(app)
response_tree = {"Type": "EXPR",
"Server": samba_server_name,
- "SSL": "On",
- "AuthPackage": "Basic"}
+ "SSL": "Off",
+ "AuthPackage": "Ntlm"}
self._append_elements(prot_element, response_tree)
"""
+++ /dev/null
-# RPCProxyApplication.py -- OpenChange RPC-over-HTTP implementation
-#
-# Copyright (C) 2012 Wolfgang Sourdeau <wsourdeau@inverse.ca>
-#
-# 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 3 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 General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program. If not, see <http://www.gnu.org/licenses/>.
-#
-
-import logging
-import sys
-
-from channels import RPCProxyInboundChannelHandler,\
- RPCProxyOutboundChannelHandler
-
-
-class RPCProxyApplication(object):
- def __init__(self):
- print >>sys.stderr, "RPCProxy started"
-
- def __call__(self, environ, start_response):
- if "wsgi.errors" in environ:
- log_stream = environ["wsgi.errors"]
- else:
- log_stream = sys.stderr
-
- logHandler = logging.StreamHandler(log_stream)
- fmter = logging.Formatter("[%(process)d] [%(levelname)s] %(message)s")
- logHandler.setFormatter(fmter)
-
- logger = logging.Logger("rpcproxy")
- logger.setLevel(logging.INFO)
- logger.addHandler(logHandler)
- self.logger = logger
-
- if "REQUEST_METHOD" in environ:
- method = environ["REQUEST_METHOD"]
- method_method = "_do_" + method
- if hasattr(self, method_method):
- method_method_method = getattr(self, method_method)
- response = method_method_method(environ, start_response)
- else:
- response = self._unsupported_method(environ, start_response)
- else:
- response = self._unsupported_method(environ, start_response)
-
- return response
-
- @staticmethod
- def _unsupported_method(environ, start_response):
- msg = "Unsupported method"
- start_response("501 Not Implemented", [("Content-Type", "text/plain"),
- ("Content-length",
- str(len(msg)))])
-
- return [msg]
-
- def _do_RPC_IN_DATA(self, environ, start_response):
- handler = RPCProxyInboundChannelHandler(self.logger)
- return handler.sequence(environ, start_response)
-
- def _do_RPC_OUT_DATA(self, environ, start_response):
- handler = RPCProxyOutboundChannelHandler(self.logger)
- return handler.sequence(environ, start_response)
--- /dev/null
+# RPCProxyApplication.py -- OpenChange RPC-over-HTTP implementation
+#
+# Copyright (C) 2012 Wolfgang Sourdeau <wsourdeau@inverse.ca>
+#
+# 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 3 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import logging
+from errno import EEXIST
+from os import umask, mkdir, rmdir, listdir
+from os.path import join
+from uuid import uuid4
+import sys
+
+
+from channels import RPCProxyInboundChannelHandler,\
+ RPCProxyOutboundChannelHandler
+
+
+class RPCProxyApplication(object):
+ def __init__(self, samba_host, log_level=logging.DEBUG):
+ print >>sys.stderr, "RPCProxy started"
+
+ has_socket_dir = False
+ umask(0077)
+ while not has_socket_dir:
+ leafname = "rpcproxy-%s" % str(uuid4())
+ dirname = "/tmp/%s" % leafname
+ try:
+ mkdir(dirname)
+ has_socket_dir = True
+ self.sockets_dir = dirname
+ except OSError, e:
+ if e.errno != EEXIST:
+ raise
+
+ self.samba_host = samba_host
+ self.log_level = log_level
+
+ def __del__(self):
+ for filename in listdir(self.sockets_dir):
+ print >>sys.stderr, \
+ "RPCProxyApplication: removing stale socket '%s'" % filename
+ unlink(join(self.sockets_dir, filename))
+ rmdir(self.sockets_dir)
+
+ def __call__(self, environ, start_response):
+ if "REQUEST_METHOD" in environ:
+ method = environ["REQUEST_METHOD"]
+ method_method = "_do_" + method
+ if hasattr(self, method_method):
+ if "wsgi.errors" in environ:
+ log_stream = environ["wsgi.errors"]
+ else:
+ log_stream = sys.stderr
+
+ logHandler = logging.StreamHandler(log_stream)
+ fmter = logging.Formatter("[%(process)d:%(name)s] %(levelname)s: %(message)s")
+ logHandler.setFormatter(fmter)
+
+ logger = logging.Logger(method)
+ logger.setLevel(self.log_level)
+ logger.addHandler(logHandler)
+ # logger.set_name(method)
+
+ method_method_method = getattr(self, method_method)
+ response = method_method_method(logger, environ, start_response)
+ else:
+ response = self._unsupported_method(environ, start_response)
+ else:
+ response = self._unsupported_method(environ, start_response)
+
+ return response
+
+ @staticmethod
+ def _unsupported_method(environ, start_response):
+ msg = "Unsupported method"
+ start_response("501 Not Implemented", [("Content-Type", "text/plain"),
+ ("Content-length",
+ str(len(msg)))])
+
+ return [msg]
+
+ def _do_RPC_IN_DATA(self, logger, environ, start_response):
+ handler = RPCProxyInboundChannelHandler(self.sockets_dir, logger)
+ return handler.sequence(environ, start_response)
+
+ def _do_RPC_OUT_DATA(self, logger, environ, start_response):
+ handler = RPCProxyOutboundChannelHandler(self.sockets_dir,
+ self.samba_host,
+ logger)
+ return handler.sequence(environ, start_response)
from uuid import UUID
# from rpcproxy.RPCH import RPCH, RTS_FLAG_ECHO
-from fdunix import send_socket, receive_socket
-from packets import RTS_CMD_CONNECTION_TIMEOUT, RTS_CMD_VERSION, \
- RTS_CMD_RECEIVE_WINDOW_SIZE, RTS_CMD_CONNECTION_TIMEOUT, \
- RTS_FLAG_ECHO, \
- RPCPacket, RPCRTSPacket, RPCRTSOutPacket
+from openchange.utils.fdunix import send_socket, receive_socket
+from openchange.utils.packets import RTS_CMD_CONNECTION_TIMEOUT, \
+ RTS_CMD_VERSION, RTS_CMD_RECEIVE_WINDOW_SIZE, \
+ RTS_CMD_CONNECTION_TIMEOUT, RTS_FLAG_ECHO, RTS_FLAG_OTHER_CMD, \
+ RTS_CMD_DATA_LABELS, RPCPacket, RPCRTSPacket, RPCRTSOutPacket
"""Documentation:
# those id must have the same length
INBOUND_PROXY_ID = "IP"
OUTBOUND_PROXY_ID = "OP"
-SOCKETS_DIR = "/tmp/rpcproxy"
-OC_HOST = "127.0.0.1"
class RPCProxyChannelHandler(object):
- def __init__(self, logger):
+ def __init__(self, sockets_dir, logger):
+ self.sockets_dir = sockets_dir
self.logger = logger
+ self.unix_socket = None
self.client_socket = None # placeholder for wsgi.input
self.bytes_read = 0
self.connection_cookie = None
def handle_echo_request(self, environ, start_response):
- self.logger.info("handling echo request")
+ self.logger.debug("handling echo request")
packet = RPCRTSOutPacket()
packet.flags = RTS_FLAG_ECHO
return [data]
def log_connection_stats(self):
- self.logger.info("request took %f secs; %d bytes received; %d bytes sent"
+ self.logger.debug("channel keep alive during %f secs;"
+ " %d bytes received; %d bytes sent"
% ((time() - self.startup_time),
self.bytes_read, self.bytes_written))
class RPCProxyInboundChannelHandler(RPCProxyChannelHandler):
- def __init__(self, logger):
- RPCProxyChannelHandler.__init__(self, logger)
+ def __init__(self, sockets_dir, logger):
+ RPCProxyChannelHandler.__init__(self, sockets_dir, logger)
self.oc_conn = None
self.window_size = 0
self.conn_timeout = 0
def _receive_conn_b1(self):
# CONN/B1 RTS PDU (TODO: validation)
# receive the cookie
- self.logger.info("IN: receiving CONN/B1")
+ self.logger.debug("receiving CONN/B1")
packet = RPCPacket.from_file(self.client_socket, self.logger)
if not isinstance(packet, RPCRTSPacket):
raise Exception("Unexpected non-rts packet received for CONN/B1")
+ self.logger.debug("packet headers = " + packet.pretty_dump())
+
self.connection_cookie = str(UUID(bytes=packet.commands[1]["Cookie"]))
self.channel_cookie = str(UUID(bytes=packet.commands[2]["Cookie"]))
self.client_keepalive = packet.commands[4]["ClientKeepalive"]
# channel
# connect as a client to the cookie unix socket
- socket_name = os.path.join(SOCKETS_DIR, self.connection_cookie)
- self.logger.info("IN: connecting to OUT via unix socket '%s'"
+ socket_name = os.path.join(self.sockets_dir, self.connection_cookie)
+ self.logger.debug("connecting to OUT via unix socket '%s'"
% socket_name)
- sock = socket(AF_UNIX, SOCK_STREAM)
+ unix_socket = socket(AF_UNIX, SOCK_STREAM)
connected = False
attempt = 0
while not connected:
try:
attempt = attempt + 1
- sock.connect(socket_name)
+ unix_socket.connect(socket_name)
+ self.unix_socket = unix_socket
connected = True
except socket_error:
- self.logger.info("IN: handling socket.error: %s"
+ self.logger.debug("handling socket.error: %s"
% str(sys.exc_info()))
if attempt < 10:
- self.logger.warn("IN: reattempting to connect to OUT"
+ self.logger.warn("CUICUI reattempting to connect to OUT"
" channel... (%d/10)" % attempt)
sleep(1)
if connected:
- self.logger.info("IN: connection succeeded")
- self.logger.info("IN: sending window size and connection timeout")
+ self.logger.debug("connection succeeded")
+ self.logger.debug("sending window size and connection timeout")
# identify ourselves as the IN proxy
- sock.sendall(INBOUND_PROXY_ID)
+ unix_socket.sendall(INBOUND_PROXY_ID)
# send window_size to 256Kib (max size allowed)
# and conn_timeout (in seconds, max size allowed)
- sock.sendall(pack("<ll", (256 * 1024), 14400000))
+ unix_socket.sendall(pack("<ll", (256 * 1024), 14400000))
# recv oc socket
- self.oc_conn = receive_socket(sock)
+ self.oc_conn = receive_socket(unix_socket)
- self.logger.info("IN: oc_conn received (fileno=%d)"
+ self.logger.debug("oc_conn received (fileno=%d)"
% self.oc_conn.fileno())
- sock.close()
else:
self.logger.error("too many failed attempts to establish a"
" connection to OUT channel")
return connected
def _runloop(self):
- self.logger.info("IN: runloop")
+ self.logger.debug("runloop")
status = True
while status:
try:
oc_packet = RPCPacket.from_file(self.client_socket,
self.logger)
+ self.logger.debug("packet headers = "
+ + oc_packet.pretty_dump())
self.bytes_read = self.bytes_read + oc_packet.size
- self.logger.info("IN: packet headers = "
- + oc_packet.pretty_dump())
-
if isinstance(oc_packet, RPCRTSPacket):
- # or oc_packet.header["ptype"] == DCERPC_PKT_AUTH3):
- # we do not forward rts packets
- self.logger.info("IN: ignored RTS packet")
+ labels = [RTS_CMD_DATA_LABELS[command["type"]]
+ for command in oc_packet.commands]
+ self.logger.debug("ignored RTS packet with commands: %s"
+ % ", ".join(labels))
else:
- self.logger.info("IN: sending packet to OC")
+ self.logger.debug("sending packet to OC")
self.oc_conn.sendall(oc_packet.data)
self.bytes_written = self.bytes_written + oc_packet.size
except IOError:
status = False
+ self.logger.debug("handling socket.error: %s"
+ % str(sys.exc_info()))
# exc = sys.exc_info()
- self.logger.error("IN: client connection closed")
- self._notify_OUT_channel()
-
- def _notify_OUT_channel(self):
- self.logger.info("IN: notifying OUT channel of shutdown")
-
- socket_name = os.path.join(SOCKETS_DIR, self.connection_cookie)
- self.logger.info("IN: connecting to OUT via unix socket '%s'"
- % socket_name)
- sock = socket(AF_UNIX, SOCK_STREAM)
- connected = False
- attempt = 0
- while not connected:
- try:
- attempt = attempt + 1
- sock.connect(socket_name)
- connected = True
- except socket_error:
- self.logger.info("IN: handling socket.error: %s"
- % str(sys.exc_info()))
- if attempt < 10:
- self.logger.warn("IN: reattempting to connect to OUT"
- " channel... (%d/10)" % attempt)
- sleep(1)
-
- if connected:
- self.logger.info("IN: connection succeeded")
- try:
- sock.sendall(INBOUND_PROXY_ID + "q")
- sock.close()
- except:
- # UNIX socket might already have been closed by OUT channel
- pass
- else:
- self.logger.error("too many failed attempts to establish a"
- " connection to OUT channel")
-
- def _terminate_oc_socket(self):
- self.oc_conn.close()
+ self.logger.error("client connection closed")
def sequence(self, environ, start_response):
- self.logger.info("IN: processing request")
+ self.logger.debug("processing request")
if "REMOTE_PORT" in environ:
- self.logger.info("IN: remote port = %s" % environ["REMOTE_PORT"])
- # self.logger.info("IN: path: ' + self.path)
+ self.logger.debug("remote port = %s" % environ["REMOTE_PORT"])
+ # self.logger.debug("path: ' + self.path)
content_length = int(environ["CONTENT_LENGTH"])
- self.logger.info("IN: request size is %d" % content_length)
+ self.logger.debug("request size is %d" % content_length)
# echo request
if content_length <= 0x10:
- self.logger.info("IN: Exiting (1) from do_RPC_IN_DATA")
for data in self.handle_echo_request(environ, start_response):
yield data
+ self.logger.debug("exiting from echo request")
elif content_length >= 128:
- self.logger.info("IN: Processing IN channel request")
+ self.logger.debug("processing IN channel request")
self.client_socket = environ["wsgi.input"]
self._receive_conn_b1()
("Content-length", "0")])
self._runloop()
- self._terminate_oc_socket()
+ # shutting down sockets
+ self.logger.debug("notifying OUT channel of shutdown")
+ try:
+ self.unix_socket.sendall(INBOUND_PROXY_ID + "q")
+ self.unix_socket.close()
+ except socket_error:
+ # OUT channel already closed the connection
+ pass
+
+ self.oc_conn.close()
self.log_connection_stats()
- self.logger.info("IN: Exiting (2) from do_RPC_IN_DATA")
+ self.logger.debug("exiting from main sequence")
# TODO: error handling
start_response("200 Success",
# return [msg]
class RPCProxyOutboundChannelHandler(RPCProxyChannelHandler):
- def __init__(self, logger):
- RPCProxyChannelHandler.__init__(self, logger)
- self.unix_socket = None
+ def __init__(self, sockets_dir, samba_host, logger):
+ RPCProxyChannelHandler.__init__(self, sockets_dir, logger)
+ self.samba_host = samba_host
self.oc_conn = None
self.in_window_size = 0
self.in_conn_timeout = 0
def _receive_conn_a1(self):
# receive the cookie
# TODO: validation of CONN/A1
- self.logger.info("OUT: receiving CONN/A1")
+ self.logger.debug("receiving CONN/A1")
packet = RPCPacket.from_file(self.client_socket, self.logger)
if not isinstance(packet, RPCRTSPacket):
raise Exception("Unexpected non-rts packet received for CONN/A1")
+ self.logger.debug("packet headers = " + packet.pretty_dump())
+
self.connection_cookie = str(UUID(bytes=packet.commands[1]["Cookie"]))
self.channel_cookie = str(UUID(bytes=packet.commands[2]["Cookie"]))
def _send_conn_a3(self):
- self.logger.info("OUT: sending CONN/A3 to client")
+ self.logger.debug("sending CONN/A3 to client")
# send the A3 response to the client
packet = RPCRTSOutPacket(self.logger)
# we set the min timeout value allowed, as we would actually need
return packet.make()
def _send_conn_c2(self):
- self.logger.info("OUT: sending CONN/C2 to client")
+ self.logger.debug("sending CONN/C2 to client")
# send the C2 response to the client
packet = RPCRTSOutPacket(self.logger)
# we set the min timeout value allowed, as we would actually need
def _setup_oc_socket(self):
# create IP connection to OpenChange
- self.logger.info("OUT: connecting to OC_HOST:1024")
+ self.logger.debug("connecting to %s:1024" % self.samba_host)
connected = False
while not connected:
try:
oc_conn = socket(AF_INET, SOCK_STREAM)
- oc_conn.connect((OC_HOST, 1024))
+ oc_conn.connect((self.samba_host, 1024))
connected = True
except socket_error:
- self.logger.info("OUT: failure to connect, retrying...")
+ self.logger.debug("failure to connect, retrying...")
sleep(1)
- self.logger.info("OUT: connection to OC succeeeded (fileno=%d)"
+ self.logger.debug("connection to OC succeeeded (fileno=%d)"
% oc_conn.fileno())
self.oc_conn = oc_conn
def _setup_channel_socket(self):
# TODO: add code to create missing socket dir
# create the corresponding unix socket
- socket_name = os.path.join(SOCKETS_DIR, self.connection_cookie)
- self.logger.info("OUT: creating unix socket '%s'" % socket_name)
+
+ if not os.access(self.sockets_dir, os.R_OK | os.W_OK | os.X_OK):
+ raise IOError("Socket directory '%s' does not exist or has the"
+ " wrong permissions" % self.sockets_dir)
+
+ socket_name = os.path.join(self.sockets_dir, self.connection_cookie)
+ self.logger.debug("creating unix socket '%s'" % socket_name)
if os.access(socket_name, os.F_OK):
os.remove(socket_name)
sock = socket(AF_UNIX, SOCK_STREAM)
self.unix_socket = sock
def _wait_IN_channel(self):
- self.logger.info("OUT: waiting for connection from IN")
+ self.logger.debug("waiting for connection from IN")
# wait for the IN channel to connect as a B1 should be occurring
# on the other side
in_sock = self.unix_socket.accept()[0]
raise IOError("connection must be from IN proxy (1): /%s/"
% data)
- self.logger.info("OUT: receiving window size + conn_timeout")
+ self.logger.debug("receiving window size + conn_timeout")
# receive the WindowSize + ConnectionTimeout
(self.in_window_size, self.in_conn_timeout) = \
unpack_from("<ll", in_sock.recv(8, MSG_WAITALL))
# send OC socket
- self.logger.info("OUT: sending OC socket to IN")
+ self.logger.debug("sending OC socket to IN")
send_socket(in_sock, self.oc_conn)
in_sock.close()
def _runloop(self):
- self.logger.info("OUT: runloop")
+ self.logger.debug("runloop")
unix_fd = self.unix_socket.fileno()
oc_fd = self.oc_conn.fileno()
for data in fd_pool.poll(1000):
fd, event_no = data
if fd == oc_fd:
- # self.logger.info("received event '%d' on oc socket"
+ # self.logger.debug("received event '%d' on oc socket"
# % event_no)
if event_no & POLLHUP > 0:
# FIXME: notify IN channel?
- self.logger.info("OUT: connection closed from OC")
+ self.logger.debug("connection closed from OC")
status = False
elif event_no & POLLIN > 0:
oc_packet = RPCPacket.from_file(self.oc_conn,
self.logger)
- self.logger.info("OUT: packet headers = "
+ self.logger.debug("packet headers = "
+ oc_packet.pretty_dump())
if isinstance(oc_packet, RPCRTSPacket):
raise Exception("Unexpected rts packet received")
- self.logger.info("OUT: sending data to client")
+ self.logger.debug("sending data to client")
self.bytes_read = self.bytes_read + oc_packet.size
self.bytes_written = self.bytes_written + oc_packet.size
yield oc_packet.data
# else:
- # self.logger.info("ignored event '%d' on oc socket"
+ # self.logger.debug("ignored event '%d' on oc socket"
# % event_no)
elif fd == unix_fd:
- self.logger.info("OUT: ignored event '%d' on unix socket"
+ self.logger.debug("ignored event '%d' on unix socket"
% event_no)
# FIXME: we should listen to what the IN channel has to say
status = False
raise Exception("invalid poll event: %s" % str(data))
# write(oc_packet.header_data)
# write(oc_packet.data)
- # self.logger.info("OUT: data sent to client")
+ # self.logger.debug("data sent to client")
def _terminate_sockets(self):
- socket_name = os.path.join(SOCKETS_DIR, self.connection_cookie)
- self.logger.info("OUT: removing and closing unix socket '%s'"
+ socket_name = os.path.join(self.sockets_dir, self.connection_cookie)
+ self.logger.debug("removing and closing unix socket '%s'"
% socket_name)
if os.access(socket_name, os.F_OK):
os.remove(socket_name)
self.oc_conn.close()
def sequence(self, environ, start_response):
- self.logger.info("OUT: processing request")
+ self.logger.debug("processing request")
if "REMOTE_PORT" in environ:
- self.logger.info("OUT: remote port = %s" % environ["REMOTE_PORT"])
- # self.logger.info("OUT: path: ' + self.path)
+ self.logger.debug("remote port = %s" % environ["REMOTE_PORT"])
+ # self.logger.debug("path: ' + self.path)
content_length = int(environ["CONTENT_LENGTH"])
- self.logger.info("OUT: request size is %d" % content_length)
+ self.logger.debug("request size is %d" % content_length)
if content_length <= 0x10:
# echo request
for data in self.handle_echo_request(environ, start_response):
yield data
elif content_length == 76:
- self.logger.info("OUT: Processing nonreplacement Out channel"
- "request")
+ self.logger.debug("processing nonreplacement Out channel"
+ "request")
self.client_socket = environ["wsgi.input"]
self._receive_conn_a1()
self._wait_IN_channel()
yield self._send_conn_c2()
- self.logger.info("OUT: total bytes sent yet: %d"
+ self.logger.debug("total bytes sent yet: %d"
% self.bytes_written)
for data in self._runloop():
yield data
raise Exception("This content-length is not handled")
self.log_connection_stats()
- self.logger.info("OUT: Exiting from do_RPC_OUT_DATA")
+ self.logger.debug("exiting from main sequence")
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
+# this is the WSGI starting point for rpcproxy
+
import sys
-sys.path.extend(("/usr/local/samba/lib/python2.7/site-packages",
- "/home/wsourdeau/src/openchange/mapiproxy/services/ocsmanager/rpcproxy"))
+print >>sys.stderr, "path: %s" % ":".join(sys.path)
+
+import logging
+
+from openchange.web.auth.NTLMAuthHandler import *
+from RPCProxyApplication import *
+
-from rpcproxy.NTLMAuthHandler import *
-from rpcproxy.RPCProxyApplication import *
+SOCKETS_DIR = "/tmp/rpcproxy"
+SAMBA_HOST = "127.0.0.1"
+LOG_LEVEL = logging.DEBUG
-application = NTLMAuthHandler(RPCProxyApplication())
-# application = RPCProxyApplication()
+application = NTLMAuthHandler(RPCProxyApplication(samba_host=SAMBA_HOST,
+ log_level=LOG_LEVEL),
+ samba_host=SAMBA_HOST)
self.command_data = None
if self.logger is not None:
- self.logger.info("returning packet: %s" % repr(data))
+ self.logger.debug("returning packet: %s" % repr(data))
return data
#
"""This module provides the NTLMAuthHandler class, a WSGI middleware that
-enables authentication via RPC to Samba
+enables NTLM authentication via RPC to Samba.
+
+It works by proxying the NTLMSSP payload between the client and the samba
+server. Accessorily it could be used against an MS Exchange service, but this
+is untested.
"""
import httplib
-from uuid import uuid4, UUID
from socket import socket, _socketobject, SHUT_RDWR, AF_INET, AF_UNIX, \
SOCK_STREAM, MSG_WAITALL, error as socket_error
from struct import pack, error as struct_error
+import sys
+from uuid import uuid4, UUID
-from packets import *
+from openchange.utils.packets import *
COOKIE_NAME = "ocs-ntlm-auth"
-SAMBA_HOST = "localhost"
SAMBA_PORT = 1024
"""
- def __init__(self, application):
+ def __init__(self, application, samba_host="localhost"):
# TODO: client expiration and/or cleanup
self.client_status = {}
self.application = application
+ self.samba_host = samba_host
def _in_progress_response(self, start_response,
ntlm_data=None, client_id=None):
return cookies
- def _stage0(self, client_id, env, start_response):
+ def _handle_negotiate(self, client_id, env, start_response):
# print >>sys.stderr, "* client auth stage0"
auth = env["HTTP_AUTHORIZATION"]
ntlm_payload = auth[5:].decode("base64")
# print >> sys.stderr, "connecting to host"
- server = socket(AF_INET, SOCK_STREAM)
- server.connect((SAMBA_HOST, SAMBA_PORT))
+ try:
+ server = socket(AF_INET, SOCK_STREAM)
+ server.connect((self.samba_host, SAMBA_PORT))
+ except:
+ print >>sys.stderr, \
+ ("NTLMAuthHandler: caught exception when connecting to samba"
+ " host")
+ raise
+
# print >> sys.stderr, "host: %s" % str(server.getsockname())
# print >> sys.stderr, "building bind packet"
return response
- def _stage1(self, client_id, env, start_response):
+ def _handle_auth(self, client_id, env, start_response):
# print >>sys.stderr, "* client auth stage1"
server = self.client_status[client_id]["server"]
if client_id is None or client_id not in self.client_status:
# stage 0, where the cookie has not been set yet and where we
# know the NTLM payload is a NEGOTIATE message
- response = self._stage0(client_id, env, start_response)
+ response = self._handle_negotiate(client_id,
+ env, start_response)
else:
# stage 1, where the client has already received the challenge
# from the server and is now sending an AUTH message
- response = self._stage1(client_id, env, start_response)
+ response = self._handle_auth(client_id, env, start_response)
else:
if client_id is None or client_id not in self.client_status:
# this client has never been seen