3
3
#
4
4
# --- BEGIN_HEADER ---
5
5
#
6
- # grid_dav - DAV server providing access to MiG user homes
6
+ # grid_davs - secure DAV server providing access to MiG user homes
7
7
# Copyright (C) 2014 The MiG Project lead by Brian Vinter
8
8
#
9
9
# This file is part of MiG.
25
25
# -- END_HEADER ---
26
26
#
27
27
28
- """Provide DAV access to MiG user homes"""
28
+ """Provide secure DAV access to MiG user homes"""
29
29
30
30
import BaseHTTPServer
31
- import SimpleHTTPServer
31
+ # import SimpleHTTPServer
32
32
import SocketServer
33
- import base64
34
- import glob
35
- import logging
33
+ # import base64
34
+ # import glob
35
+ # import logging
36
36
import ssl
37
37
import os
38
- import socket
38
+ # import socket
39
39
import shutil
40
40
import sys
41
- import threading
42
- import time
43
- from StringIO import StringIO
41
+ # import threading
42
+ # import time
43
+ # from StringIO import StringIO
44
44
45
- import pywebdav .lib
45
+ # import pywebdav.lib
46
46
from pywebdav .server .fileauth import DAVAuthHandler
47
47
#from pywebdav.server.mysqlauth import MySQLAuthHandler
48
48
from pywebdav .server .fshandler import FilesystemHandler
49
49
#from pywebdav.server.daemonize import startstop
50
50
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
53
53
54
54
55
55
from shared .base import client_dir_id , client_alias , invisible_path
56
56
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
59
60
60
61
61
62
configuration , logger = None , None
@@ -68,38 +69,26 @@ class ThreadedHTTPServer(SocketServer.ThreadingMixIn,
68
69
pass
69
70
70
71
71
- def setupDummyConfig (** kw ):
72
+ def setup_dummy_config (** kw ):
73
+ """DAV config object helper"""
72
74
73
75
class DummyConfigDAV :
76
+ """Dummy DAV config"""
74
77
def __init__ (self , ** kw ):
75
78
self .__dict__ .update (** kw )
76
79
77
80
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" ))
79
84
80
85
class DummyConfig :
86
+ """Dummy config"""
81
87
DAV = DummyConfigDAV (** kw )
82
88
83
89
return DummyConfig ()
84
90
85
91
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
-
103
92
class MiGDAVAuthHandler (DAVAuthHandler ):
104
93
"""
105
94
Provides MiG specific authentication based on parameters. The calling
@@ -110,54 +99,64 @@ class has to inject password and username into this.
110
99
the MiG user DB.
111
100
"""
112
101
113
- # TMP! load from DB
114
- allow_password = True
115
- users = {'jonas' : [User ('jonas' , generate_password_hash ('test1234' ))]}
116
-
117
102
# Do not forget to set IFACE_CLASS by caller
118
103
# ex.: IFACE_CLASS = FilesystemHandler('/tmp', 'http://localhost/')
119
104
verbose = False
105
+ users = None
106
+ authenticated_user = None
120
107
121
108
def _log (self , message ):
109
+ print "in _log"
122
110
if self .verbose :
123
- log .info (message )
111
+ logger .info (message )
124
112
125
- def get_userinfo (self , user , pw , command ):
113
+ def get_userinfo (self , username , password , command ):
126
114
"""authenticate user against user DB"""
127
115
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
129
126
130
127
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 ):
132
130
# list of User login objects for username
133
131
entries = self .users [username ]
134
132
offered = password
135
133
for entry in entries :
136
134
if entry .password is not None :
137
135
allowed = entry .password
138
- logging .debug ("Password check for %s" % username )
136
+ logger .debug ("Password check for %s" % username )
139
137
if check_password_hash (offered , allowed ):
140
- logging .info ("Authenticated %s" % username )
138
+ logger .info ("Authenticated %s" % username )
141
139
self .authenticated_user = username
142
140
return 1
143
141
err_msg = "Password authentication failed for %s" % username
144
- logging .error (err_msg )
142
+ logger .error (err_msg )
145
143
print err_msg
146
144
return 0
147
145
148
146
149
- def run (conf ):
147
+ def run (configuration ):
150
148
"""SSL wrap HTTP server for secure DAV access"""
151
149
152
150
handler = MiGDAVAuthHandler
153
151
154
152
# Pass conf options to DAV handler in required object format
155
153
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
161
160
handler ._config = dav_conf
162
161
163
162
server = ThreadedHTTPServer
@@ -167,22 +166,22 @@ def run(conf):
167
166
directory = directory .rstrip ('/' )
168
167
verbose = dav_conf .DAV .getboolean ('verbose' )
169
168
noauth = dav_conf .DAV .getboolean ('noauth' )
170
- host = conf ['host' ]
169
+ host = dav_conf_dict ['host' ]
171
170
host = host .strip ()
172
- port = conf ['port' ]
171
+ port = dav_conf_dict ['port' ]
173
172
174
173
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 )
176
175
return sys .exit (233 )
177
176
178
177
# basic checks against wrong hosts
179
178
if host .find ('/' ) != - 1 or host .find (':' ) != - 1 :
180
- logging .error ('Malformed host %s' % host )
179
+ logger .error ('Malformed host %s' % host )
181
180
return sys .exit (233 )
182
181
183
182
# no root directory
184
183
if directory == '/' :
185
- logging .error ('Root directory not allowed!' )
184
+ logger .error ('Root directory not allowed!' )
186
185
sys .exit (233 )
187
186
188
187
# dispatch directory and host to the filesystem handler
@@ -193,28 +192,33 @@ def run(conf):
193
192
# put some extra vars
194
193
handler .verbose = verbose
195
194
if noauth :
196
- logging .warning ('Authentication disabled!' )
195
+ logger .warning ('Authentication disabled!' )
197
196
handler .DO_AUTH = False
198
197
199
- logging .info ('Serving data from %s' % directory )
198
+ logger .info ('Serving data from %s' % directory )
200
199
201
200
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' )
203
202
204
203
handler .IFACE_CLASS .mimecheck = True
205
204
if not dav_conf .DAV .getboolean ('mimecheck' ):
206
205
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)' )
208
208
209
209
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' % \
211
211
dav_conf_dict )
212
212
handler .IFACE_CLASS .baseurl = dav_conf_dict ['baseurl' ]
213
213
214
214
# initialize server on specified port
215
215
runner = server ((host , port ), handler )
216
216
# 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 )
218
222
runner .socket = ssl .wrap_socket (runner .socket ,
219
223
certfile = cert_path ,
220
224
server_side = True )
@@ -223,36 +227,14 @@ def run(conf):
223
227
try :
224
228
runner .serve_forever ()
225
229
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
+
245
232
246
233
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 = {
256
238
'verbose' : False ,
257
239
'directory' : '/tmp' ,
258
240
'no_auth' : False ,
@@ -267,6 +249,47 @@ def main(conf):
267
249
'chunked_http_response' : True ,
268
250
'mimecheck' : True ,
269
251
'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