Skip to content

Commit cd0f89a

Browse files
author
Jonas Bardino
committed
refactor sftp server to use shared griddaemon module where sharing makes sense. begin davs support using the same shared griddaemon module functions. https with login support in dav server is working but still neither mapping to user home or chrooting. separate user side configuration of davs login is also missing, simply reusing ssh login for now.
git-svn-id: svn+ssh://svn.code.sf.net/p/migrid/code/trunk@2144 b75ad72c-e7d7-11dd-a971-7dbc132099af
1 parent 648239b commit cd0f89a

File tree

5 files changed

+453
-334
lines changed

5 files changed

+453
-334
lines changed

certs

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
MiG-certificates

mig/server/grid_dav.py mig/server/grid_davs.py

+116-93
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
#
44
# --- BEGIN_HEADER ---
55
#
6-
# grid_dav - DAV server providing access to MiG user homes
6+
# grid_davs - secure DAV server providing access to MiG user homes
77
# Copyright (C) 2014 The MiG Project lead by Brian Vinter
88
#
99
# This file is part of MiG.
@@ -25,37 +25,38 @@
2525
# -- END_HEADER ---
2626
#
2727

28-
"""Provide DAV access to MiG user homes"""
28+
"""Provide secure DAV access to MiG user homes"""
2929

3030
import BaseHTTPServer
31-
import SimpleHTTPServer
31+
#import SimpleHTTPServer
3232
import SocketServer
33-
import base64
34-
import glob
35-
import logging
33+
#import base64
34+
#import glob
35+
#import logging
3636
import ssl
3737
import os
38-
import socket
38+
#import socket
3939
import shutil
4040
import sys
41-
import threading
42-
import time
43-
from StringIO import StringIO
41+
#import threading
42+
#import time
43+
#from StringIO import StringIO
4444

45-
import pywebdav.lib
45+
#import pywebdav.lib
4646
from pywebdav.server.fileauth import DAVAuthHandler
4747
#from pywebdav.server.mysqlauth import MySQLAuthHandler
4848
from pywebdav.server.fshandler import FilesystemHandler
4949
#from pywebdav.server.daemonize import startstop
5050

51-
from pywebdav.lib.INI_Parse import Configuration
52-
from pywebdav.lib import VERSION, AUTHOR
51+
#from pywebdav.lib.INI_Parse import Configuration
52+
#from pywebdav.lib import VERSION, AUTHOR
5353

5454

5555
from shared.base import client_dir_id, client_alias, invisible_path
5656
from shared.conf import get_configuration_object
57-
from shared.useradm import ssh_authpasswords, get_ssh_authpasswords, \
58-
check_password_hash, extract_field, generate_password_hash
57+
from shared.griddaemons import get_fs_path, strip_root, \
58+
flags_to_mode, acceptable_chmod, refresh_users
59+
from shared.useradm import check_password_hash
5960

6061

6162
configuration, logger = None, None
@@ -68,38 +69,26 @@ class ThreadedHTTPServer(SocketServer.ThreadingMixIn,
6869
pass
6970

7071

71-
def setupDummyConfig(**kw):
72+
def setup_dummy_config(**kw):
73+
"""DAV config object helper"""
7274

7375
class DummyConfigDAV:
76+
"""Dummy DAV config"""
7477
def __init__(self, **kw):
7578
self.__dict__.update(**kw)
7679

7780
def getboolean(self, name):
78-
return (str(getattr(self, name, 0)) in ('1', "yes", "true", "on", "True"))
81+
"""Get boolean value from config"""
82+
return (str(getattr(self, name, 0)) in ('1', "yes", "true", "on",
83+
"True"))
7984

8085
class DummyConfig:
86+
"""Dummy config"""
8187
DAV = DummyConfigDAV(**kw)
8288

8389
return DummyConfig()
8490

8591

86-
class User(object):
87-
"""User login class to hold a single valid login for a user"""
88-
def __init__(self, username, password,
89-
chroot=True, home=None, public_key=None):
90-
self.username = username
91-
self.password = password
92-
self.chroot = chroot
93-
self.public_key = public_key
94-
if type(public_key) in (str, unicode):
95-
# We already checked that key is valid if we got here
96-
self.public_key = parse_pub_key(public_key)
97-
98-
self.home = home
99-
if self.home is None:
100-
self.home = self.username
101-
102-
10392
class MiGDAVAuthHandler(DAVAuthHandler):
10493
"""
10594
Provides MiG specific authentication based on parameters. The calling
@@ -110,54 +99,64 @@ class has to inject password and username into this.
11099
the MiG user DB.
111100
"""
112101

113-
# TMP! load from DB
114-
allow_password = True
115-
users = {'jonas': [User('jonas', generate_password_hash('test1234'))]}
116-
117102
# Do not forget to set IFACE_CLASS by caller
118103
# ex.: IFACE_CLASS = FilesystemHandler('/tmp', 'http://localhost/')
119104
verbose = False
105+
users = None
106+
authenticated_user = None
120107

121108
def _log(self, message):
109+
print "in _log"
122110
if self.verbose:
123-
log.info(message)
111+
logger.info(message)
124112

125-
def get_userinfo(self, user, pw, command):
113+
def get_userinfo(self, username, password, command):
126114
"""authenticate user against user DB"""
127115

128-
username, password = user, pw
116+
refresh_users(configuration)
117+
usermap = {}
118+
for user_obj in self.server_conf.daemon_conf['users']:
119+
if not usermap.has_key(user_obj.username):
120+
usermap[user_obj.username] = []
121+
usermap[user_obj.username].append(user_obj)
122+
self.users = usermap
123+
logger.debug("get_userinfo found users: %s" % self.users)
124+
125+
# TODO: add pubkey support
129126

130127
offered = None
131-
if self.allow_password and self.users.has_key(username):
128+
if 'password' in self.server_conf.user_davs_auth and \
129+
self.users.has_key(username):
132130
# list of User login objects for username
133131
entries = self.users[username]
134132
offered = password
135133
for entry in entries:
136134
if entry.password is not None:
137135
allowed = entry.password
138-
logging.debug("Password check for %s" % username)
136+
logger.debug("Password check for %s" % username)
139137
if check_password_hash(offered, allowed):
140-
logging.info("Authenticated %s" % username)
138+
logger.info("Authenticated %s" % username)
141139
self.authenticated_user = username
142140
return 1
143141
err_msg = "Password authentication failed for %s" % username
144-
logging.error(err_msg)
142+
logger.error(err_msg)
145143
print err_msg
146144
return 0
147145

148146

149-
def run(conf):
147+
def run(configuration):
150148
"""SSL wrap HTTP server for secure DAV access"""
151149

152150
handler = MiGDAVAuthHandler
153151

154152
# Pass conf options to DAV handler in required object format
155153

156-
dav_conf_dict = conf['dav_cfg']
157-
for name in ('host', 'port'):
158-
dav_conf_dict[name] = conf[name]
159-
dav_conf = setupDummyConfig(**dav_conf_dict)
160-
# injecting options
154+
dav_conf_dict = configuration.dav_cfg
155+
dav_conf_dict['host'] = configuration.user_davs_address
156+
dav_conf_dict['port'] = configuration.user_davs_port
157+
dav_conf = setup_dummy_config(**dav_conf_dict)
158+
# inject options
159+
handler.server_conf = configuration
161160
handler._config = dav_conf
162161

163162
server = ThreadedHTTPServer
@@ -167,22 +166,22 @@ def run(conf):
167166
directory = directory.rstrip('/')
168167
verbose = dav_conf.DAV.getboolean('verbose')
169168
noauth = dav_conf.DAV.getboolean('noauth')
170-
host = conf['host']
169+
host = dav_conf_dict['host']
171170
host = host.strip()
172-
port = conf['port']
171+
port = dav_conf_dict['port']
173172

174173
if not os.path.isdir(directory):
175-
logging.error('%s is not a valid directory!' % directory)
174+
logger.error('%s is not a valid directory!' % directory)
176175
return sys.exit(233)
177176

178177
# basic checks against wrong hosts
179178
if host.find('/') != -1 or host.find(':') != -1:
180-
logging.error('Malformed host %s' % host)
179+
logger.error('Malformed host %s' % host)
181180
return sys.exit(233)
182181

183182
# no root directory
184183
if directory == '/':
185-
logging.error('Root directory not allowed!')
184+
logger.error('Root directory not allowed!')
186185
sys.exit(233)
187186

188187
# dispatch directory and host to the filesystem handler
@@ -193,28 +192,33 @@ def run(conf):
193192
# put some extra vars
194193
handler.verbose = verbose
195194
if noauth:
196-
logging.warning('Authentication disabled!')
195+
logger.warning('Authentication disabled!')
197196
handler.DO_AUTH = False
198197

199-
logging.info('Serving data from %s' % directory)
198+
logger.info('Serving data from %s' % directory)
200199

201200
if not dav_conf.DAV.getboolean('lockemulation'):
202-
logging.info('Deactivated LOCK, UNLOCK (WebDAV level 2) support')
201+
logger.info('Deactivated LOCK, UNLOCK (WebDAV level 2) support')
203202

204203
handler.IFACE_CLASS.mimecheck = True
205204
if not dav_conf.DAV.getboolean('mimecheck'):
206205
handler.IFACE_CLASS.mimecheck = False
207-
logging.info('Disabled mimetype sniffing (All files will have type application/octet-stream)')
206+
logger.info('Disabled mimetype sniffing (All files will have type '
207+
'application/octet-stream)')
208208

209209
if dav_conf_dict['baseurl']:
210-
logging.info('Using %(baseurl)s as base url for PROPFIND requests' % \
210+
logger.info('Using %(baseurl)s as base url for PROPFIND requests' % \
211211
dav_conf_dict)
212212
handler.IFACE_CLASS.baseurl = dav_conf_dict['baseurl']
213213

214214
# initialize server on specified port
215215
runner = server((host, port), handler)
216216
# Wrap in SSL
217-
cert_path = os.path.join(conf['cert_base'], conf['cert_file'])
217+
218+
cert_path = configuration.user_davs_key
219+
if not os.path.isfile(cert_path):
220+
logger.error('No such server key: %s' % cert_path)
221+
sys.exit(1)
218222
runner.socket = ssl.wrap_socket(runner.socket,
219223
certfile=cert_path,
220224
server_side=True)
@@ -223,36 +227,14 @@ def run(conf):
223227
try:
224228
runner.serve_forever()
225229
except KeyboardInterrupt:
226-
logging.info('Killed by user')
227-
228-
229-
def main(conf):
230-
"""Run server"""
231-
if conf['log_path']:
232-
logging.basicConfig(path=conf['log_path'], level=conf['log_level'],
233-
format=conf['log_format'])
234-
else:
235-
logging.basicConfig(level=conf['log_level'],
236-
format=conf['log_format'])
237-
logging.info("starting DAV server")
238-
try:
239-
run(conf)
240-
except KeyboardInterrupt:
241-
logging.info("received interrupt - shutting down")
242-
except Exception, exc:
243-
logging.error("exiting on unexpected exception: %s" % exc)
244-
230+
logger.info('Killed by user')
231+
245232

246233
if __name__ == "__main__":
247-
cfg = {'log_level': logging.INFO,
248-
'log_path': None,
249-
'log_format': '%(asctime)s %(levelname)s %(message)s',
250-
'host': 'localhost',
251-
'port': 4443,
252-
'cert_base': '../../MiG-certificates',
253-
'cert_file': 'localhost.pem',
254-
#'configfile': '',
255-
'dav_cfg': {
234+
configuration = get_configuration_object()
235+
logger = configuration.logger
236+
# TODO: dynamically switch to user home directory
237+
configuration.dav_cfg = {
256238
'verbose': False,
257239
'directory': '/tmp',
258240
'no_auth': False,
@@ -267,6 +249,47 @@ def main(conf):
267249
'chunked_http_response': True,
268250
'mimecheck': True,
269251
'baseurl': '',
270-
},
271-
}
272-
main(cfg)
252+
}
253+
254+
logger = configuration.logger
255+
if not configuration.site_enable_davs:
256+
err_msg = "DAVS access to user homes is disabled in configuration!"
257+
logger.error(err_msg)
258+
print err_msg
259+
sys.exit(1)
260+
261+
chroot_exceptions = [os.path.abspath(configuration.vgrid_private_base),
262+
os.path.abspath(configuration.vgrid_public_base),
263+
os.path.abspath(configuration.vgrid_files_home),
264+
os.path.abspath(configuration.resource_home)]
265+
# Don't allow chmod in dirs with CGI access as it introduces arbitrary
266+
# code execution vulnerabilities
267+
chmod_exceptions = [os.path.abspath(configuration.vgrid_private_base),
268+
os.path.abspath(configuration.vgrid_public_base)]
269+
configuration.daemon_conf = {
270+
'address': configuration.user_davs_address,
271+
'port': configuration.user_davs_port,
272+
'root_dir': os.path.abspath(configuration.user_home),
273+
'chmod_exceptions': chmod_exceptions,
274+
'chroot_exceptions': chroot_exceptions,
275+
'allow_password': 'password' in configuration.user_davs_auth,
276+
'allow_publickey': 'publickey' in configuration.user_davs_auth,
277+
'user_alias': configuration.user_davs_alias,
278+
'users': [],
279+
'time_stamp': 0,
280+
'logger': logger,
281+
}
282+
283+
print """
284+
Running grid davs server for user dav access to their MiG homes.
285+
286+
Set the MIG_CONF environment to the server configuration path
287+
unless it is available in mig/server/MiGserver.conf
288+
"""
289+
logger.info("starting DAV server")
290+
try:
291+
run(configuration)
292+
except KeyboardInterrupt:
293+
logger.info("received interrupt - shutting down")
294+
except Exception, exc:
295+
logger.error("exiting on unexpected exception: %s" % exc)

0 commit comments

Comments
 (0)