116
116
117
117
configuration , logger = None , None
118
118
119
- _read_ops = ['stat' , 'lstat' , ' list_folder' , 'check' , 'read' , 'readlink' ,
119
+ _read_ops = ['list_folder' , 'check' , 'read' , 'readlink' ,
120
120
'open+read' ]
121
121
_write_ops = ['mkdir' , 'rmdir' , 'remove' , 'rename' , 'chmod' , 'chown' ,
122
- 'chattr' , 'write' , 'truncate' , 'open+write' ]
122
+ 'write' , 'truncate' , 'open+write' ]
123
+ _filter_ops = ['chattr' , 'lstat' , 'stat' ]
123
124
_other_ops = ['close' ]
124
125
126
+ def list_attr_changes (configuration , path , attr ):
127
+ """Decipher actual attribute changes requested in attr on path"""
128
+ _logger = configuration .logger
129
+ _logger .debug ("check %s attr changes: %s" % (path , attr ))
130
+ changes = []
131
+ for field in ('st_size' , 'st_uid' , 'st_gid' , 'st_mode' , 'st_atime' ,
132
+ 'st_mtime' ):
133
+ #_logger.debug("checking path %s attr %s" % (path, field))
134
+ val = getattr (attr , field , None )
135
+ if val is not None :
136
+ _logger .debug ("found %s attr %s value %s" % (path , field , val ))
137
+ changes .append (field )
138
+ return changes
139
+
140
+ def filter_data_access (configuration , path , access_mode , operation , args = []):
141
+ """Filter access to what operation is allowed to do based on path, args
142
+ and access_mode.
143
+ In read-write mode all operations are allowed to proceed path checks.
144
+ In write-only mode the potentially changing operations are always allowed
145
+ and specific stat/lstat is allowed as well to make most clients behave
146
+ without conusing errors. It doesn't reveal any directory or file contents
147
+ apart from metadata about paths the user already knows about.
148
+ In read-only mode stat is always allowed while the potentially changing
149
+ operations are rejected if they would actually modify anything.
150
+ """
151
+ _logger = configuration .logger
152
+ _logger .debug ("filter %s operation on %s with %s args in %s session" %
153
+ (operation , path , args , access_mode ))
154
+ if access_mode == READ_WRITE_ACCESS :
155
+ return True
156
+ if access_mode == READ_ONLY_ACCESS :
157
+ _logger .debug ("checking %s on %s with %s in %s session" %
158
+ (operation , path , args , access_mode ))
159
+ if operation in ['lstat' , 'stat' ]:
160
+ return True
161
+ # NOTE: allow non-changing chattr
162
+ # TODO: allow non-changing chown and chmod, too?
163
+ if operation == 'chattr' and args :
164
+ if not list_attr_changes (configuration , path , args [0 ]):
165
+ return True
166
+
167
+ _logger .warning ("invalid %s operation on %s in %s session" %
168
+ (operation , path , access_mode ))
169
+ return False
170
+ if access_mode == WRITE_ONLY_ACCESS :
171
+ if operation in ['chattr' ]:
172
+ return True
173
+ # NOTE: allow specific lstat/stat for write-only shares
174
+ if operation in ['lstat' , 'stat' ]:
175
+ return True
176
+ _logger .warning ("invalid %s operation on %s in %s session" %
177
+ (operation , path , access_mode ))
178
+ return False
125
179
126
- def check_data_access (configuration , user_id , path , access_mode , operation ):
180
+ def check_data_access (configuration , user_id , path , access_mode , operation ,
181
+ args = []):
127
182
"""A simple helper to check if user_id has access to execute operation on path
128
183
under the constraints set by access_mode. Namely, check if operation
129
184
could potentially modify data in a read-only session or lookup/read data
@@ -140,6 +195,11 @@ def check_data_access(configuration, user_id, path, access_mode, operation):
140
195
_logger .warning ("invalid %s operation for %s in %s session" %
141
196
(operation , user_id , access_mode ))
142
197
return False
198
+ elif operation in _filter_ops :
199
+ _logger .debug ("filter %s operation on %s with %s args for %r in %s session" %
200
+ (operation , path , args , user_id , access_mode ))
201
+ return filter_data_access (configuration , path , access_mode , operation ,
202
+ args )
143
203
elif operation in _other_ops :
144
204
_logger .debug ("ignoring %s operation for %r in %s session" %
145
205
(operation , user_id , access_mode ))
@@ -345,7 +405,7 @@ def _impl(self, *method_args, **method_kwargs):
345
405
% endpos \
346
406
+ " file endpos: %s, '%s'" % \
347
407
(file_endpos , self .sftpserver ._get_fs_path (
348
- path , operation = method .__name__ ))
408
+ path , operation = method .__name__ , args = [] ))
349
409
logger .warning (msg )
350
410
351
411
# close
@@ -793,13 +853,13 @@ def __gdp_log(self, operation, path, dst_path=None, flags=None,
793
853
794
854
# Use shared daemon fs helper functions
795
855
796
- def _get_fs_path (self , sftp_path , operation = None ):
856
+ def _get_fs_path (self , sftp_path , operation = None , args = [] ):
797
857
"""Wrap helper"""
798
858
# self.logger.debug("get_fs_path: check access to %s" % [sftp_path])
799
859
if not check_data_access (configuration , self .user_name , sftp_path ,
800
- self .access , operation ):
801
- self .logger .warning ("get_fs_path: refused %s %s access to %r" %
802
- (self .user_name , operation , sftp_path ))
860
+ self .access , operation , args ):
861
+ self .logger .warning ("get_fs_path: refused %s %s (%s) access to %r" %
862
+ (self .user_name , operation , args , sftp_path ))
803
863
raise AccessError ("%r not allowed to %s %r in this %s session" %
804
864
(self .user_name , operation , sftp_path ,
805
865
self .access ))
@@ -846,11 +906,14 @@ def _chattr(self, path, attr, sftphandle=None):
846
906
# path = force_utf8(path)
847
907
# Paramiko seems to handle *native* strings correctly across py version
848
908
path = force_native_str (path )
849
- # self.logger.debug("_chattr %s " % [ path] )
909
+ self .logger .debug ("_chattr %r %r " % ( path , attr ) )
850
910
try :
851
- real_path = self ._get_fs_path (path , operation = 'chattr' )
852
- except ValueError as err :
853
- self .logger .warning ('chattr %s: %s' % ([path ], [err ]))
911
+ real_path = self ._get_fs_path (path , operation = 'chattr' , args = [attr ])
912
+ except AccessError as aer :
913
+ self .logger .warning ('chattr %s: %s' % ([path ], [aer ]))
914
+ return paramiko .SFTP_PERMISSION_DENIED
915
+ except ValueError as ver :
916
+ self .logger .warning ('chattr %s: %s' % ([path ], [ver ]))
854
917
return paramiko .SFTP_PERMISSION_DENIED
855
918
if not os .path .exists (real_path ):
856
919
self .logger .warning ("chattr on missing path %s :: %s" %
@@ -928,7 +991,7 @@ def _chmod(self, path, mode, sftphandle=None):
928
991
# path = force_utf8(path)
929
992
# self.logger.debug("_chmod %s" % path)
930
993
try :
931
- real_path = self ._get_fs_path (path , operation = 'chmod' )
994
+ real_path = self ._get_fs_path (path , operation = 'chmod' , args = [ mode ] )
932
995
except ValueError as err :
933
996
self .logger .warning ('chmod %s: %s' % (path , err ))
934
997
return paramiko .SFTP_PERMISSION_DENIED
@@ -985,7 +1048,7 @@ def open(self, path, flags, attr):
985
1048
open_flavor = 'open+read'
986
1049
987
1050
try :
988
- real_path = self ._get_fs_path (path , operation = open_flavor )
1051
+ real_path = self ._get_fs_path (path , operation = open_flavor , args = [ flags ] )
989
1052
except ValueError as err :
990
1053
self .logger .warning ('open %s: %s' % ([path ], [err ]))
991
1054
return paramiko .SFTP_PERMISSION_DENIED
@@ -1059,7 +1122,7 @@ def list_folder(self, path):
1059
1122
"""Handle operations of same name"""
1060
1123
# self.logger.debug('list_folder %s' % [path])
1061
1124
try :
1062
- real_path = self ._get_fs_path (path , operation = 'list_folder' )
1125
+ real_path = self ._get_fs_path (path , operation = 'list_folder' , args = [] )
1063
1126
except ValueError as err :
1064
1127
self .logger .warning ('list_folder %s: %s' % ([path ], [err ]))
1065
1128
return paramiko .SFTP_PERMISSION_DENIED
@@ -1107,7 +1170,7 @@ def stat(self, path):
1107
1170
# path = force_utf8(path)
1108
1171
# self.logger.debug('stat %s' % path)
1109
1172
try :
1110
- real_path = self ._get_fs_path (path , operation = 'stat' )
1173
+ real_path = self ._get_fs_path (path , operation = 'stat' , args = [] )
1111
1174
except ValueError as err :
1112
1175
self .logger .warning ('stat %s: %s' % (path , err ))
1113
1176
return paramiko .SFTP_PERMISSION_DENIED
@@ -1135,7 +1198,7 @@ def lstat(self, path):
1135
1198
# path = force_utf8(path)
1136
1199
# self.logger.debug('lstat %s' % path)
1137
1200
try :
1138
- real_path = self ._get_fs_path (path , operation = 'lstat' )
1201
+ real_path = self ._get_fs_path (path , operation = 'lstat' , args = [] )
1139
1202
except ValueError as err :
1140
1203
self .logger .warning ('lstat %s: %s' % (path , err ))
1141
1204
return paramiko .SFTP_PERMISSION_DENIED
@@ -1159,7 +1222,7 @@ def remove(self, path):
1159
1222
# path = force_utf8(path)
1160
1223
# self.logger.debug("remove %s" % path)
1161
1224
try :
1162
- real_path = self ._get_fs_path (path , operation = 'remove' )
1225
+ real_path = self ._get_fs_path (path , operation = 'remove' , args = [] )
1163
1226
except ValueError as err :
1164
1227
self .logger .warning ('remove %s: %s' % (path , err ))
1165
1228
return paramiko .SFTP_PERMISSION_DENIED
@@ -1205,7 +1268,7 @@ def rename(self, oldpath, newpath):
1205
1268
# newpath = force_utf8(newpath)
1206
1269
# self.logger.debug("rename %s %s" % (oldpath, newpath))
1207
1270
try :
1208
- real_oldpath = self ._get_fs_path (oldpath , operation = 'rename' )
1271
+ real_oldpath = self ._get_fs_path (oldpath , operation = 'rename' , args = [] )
1209
1272
except ValueError as err :
1210
1273
self .logger .warning ('rename %s %s: %s' % (oldpath , newpath , err ))
1211
1274
return paramiko .SFTP_PERMISSION_DENIED
@@ -1231,7 +1294,7 @@ def rename(self, oldpath, newpath):
1231
1294
self .logger .warning ('move on read-only old path %s :: %s' %
1232
1295
(oldpath , real_oldpath ))
1233
1296
return paramiko .SFTP_PERMISSION_DENIED
1234
- real_newpath = self ._get_fs_path (newpath , operation = 'rename' )
1297
+ real_newpath = self ._get_fs_path (newpath , operation = 'rename' , args = [] )
1235
1298
if not check_write_access (real_newpath , parent_dir = True ):
1236
1299
self .logger .warning ('move on read-only new path %s :: %s' %
1237
1300
(newpath , real_newpath ))
@@ -1258,7 +1321,7 @@ def mkdir(self, path, mode):
1258
1321
# path = force_utf8(path)
1259
1322
# self.logger.debug("mkdir %s" % path)
1260
1323
try :
1261
- real_path = self ._get_fs_path (path , operation = 'mkdir' )
1324
+ real_path = self ._get_fs_path (path , operation = 'mkdir' , args = [ mode ] )
1262
1325
except ValueError as err :
1263
1326
self .logger .warning ('mkdir %s: %s' % (path , err ))
1264
1327
return paramiko .SFTP_PERMISSION_DENIED
@@ -1289,7 +1352,7 @@ def rmdir(self, path):
1289
1352
# path = force_utf8(path)
1290
1353
# self.logger.debug("rmdir %s" % path)
1291
1354
try :
1292
- real_path = self ._get_fs_path (path , operation = 'rmdir' )
1355
+ real_path = self ._get_fs_path (path , operation = 'rmdir' , args = [] )
1293
1356
except ValueError as err :
1294
1357
self .logger .warning ('rmdir %s: %s' % (path , err ))
1295
1358
return paramiko .SFTP_PERMISSION_DENIED
@@ -1342,7 +1405,7 @@ def readlink(self, path):
1342
1405
# path = force_utf8(path)
1343
1406
# self.logger.debug("readlink %s" % path)
1344
1407
try :
1345
- real_path = self ._get_fs_path (path , operation = 'readlink' )
1408
+ real_path = self ._get_fs_path (path , operation = 'readlink' , args = [] )
1346
1409
except ValueError as err :
1347
1410
self .logger .warning ('readlink %s: %s' % (path , err ))
1348
1411
return paramiko .SFTP_PERMISSION_DENIED
0 commit comments