CloudStore.py 11.8 KB
Newer Older
1 2
#!/usr/bin/python

3
# TODO File permission checks
4 5 6 7 8

from bottle import route, run, request, static_file, abort, redirect, app
import json, os, shutil
import uuid
import subprocess
9
import ConfigParser
10 11
from pwd import getpwnam

12
# Get configuration file
13
config = ConfigParser.ConfigParser()
tarokkk committed
14
config.read('/opt/webadmin/cloud/miscellaneous/store-server/store.config')
15 16 17 18 19 20


ROOT_WWW_FOLDER = config.get('store', 'root_www_folder')
ROOT_BIN_FOLDER = config.get('store', 'root_bin_folder')
SITE_URL = config.get('store', 'site_url')
USER_MANAGER = config.get('store', 'user_manager')
21
# Standalone server
22 23
SITE_HOST = config.get('store', 'site_host')
SITE_PORT = config.get('store', 'site_port')
24
# Temporary dir for tar.gz
25
TEMP_DIR = config.get('store', 'temp_dir')
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
#ForceSSL
try:
    FORCE_SSL = config.get('store', 'force_ssl') == "True"
except:
    FORCE_SSL = False


def force_ssl(original_function):
    def new_function(*args, **kwargs):
        ssl = request.environ.get('SSL_CLIENT_VERIFY', 'NONE')
        if ssl != "SUCCESS":
            abort(403, "Forbidden requests. This site need SSL verification! SSL status: "+ssl)
        else:
            return original_function(*args, **kwargs)
    if FORCE_SSL:
        return new_function
    else:
        return original_function(*args, **kwargs)
44 45

@route('/')
46
@force_ssl
47
def index():
48 49
    response = "NONE"
    try:
50
        response = request.environ.get('SSL_CLIENT_VERIFY', 'NONE')
51 52 53
    except:
        pass
    return "It works! SSL: "+response
54

55
# @route('/<neptun:re:[a-zA-Z0-9]{6}>', method='GET')
56
@route('/<neptun>', method='GET')
57
@force_ssl
58 59 60 61 62 63 64 65
def neptun_GET(neptun):
    home_path = '/home/'+neptun+'/home'
    if os.path.exists(home_path) != True:
        abort(401, 'The requested user does not exist!')
    else:
        statistics=getQuotaStatus(neptun)
        return { 'Used' : statistics[0], 'Soft' : statistics[1], 'Hard' : statistics[2]}

66 67
COMMANDS = {}

68
@route('/<neptun>', method='POST')
69
@force_ssl
70
def neptun_POST(neptun):
71
    # Check if user avaiable (home folder ready)
72 73 74 75
    home_path = '/home/'+neptun+'/home'
    if os.path.exists(home_path) != True:
        abort(401, 'The requested user does not exist!')
    else:
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
        try:
            return COMMANDS[request.json['CMD']](request, neptun, home_path)
        except KeyError:
            abort(400, "Command not found!")


# LISTING
def cmd_list(request, neptun, home_path):
    list_path = home_path+request.json['PATH']
    if os.path.exists(list_path) != True:
        abort(404, "Path not found!")
    else:
        return list_directory(home_path, list_path)
COMMANDS['LIST'] = cmd_list

# DOWNLOAD LINK GENERATOR
def cmd_download(request, neptun, home_path):
    dl_path = home_path+'/'+request.json['PATH']
    dl_path = os.path.realpath(dl_path)
    if not dl_path.startswith(home_path):
        abort(400, 'Invalid download path.')
    dl_hash = str(uuid.uuid4())
    if( os.path.isfile(dl_path) ):
        os.symlink(dl_path, ROOT_WWW_FOLDER+'/'+dl_hash)
        # Debug
        # redirect('http://store.cloud.ik.bme.hu:8080/dl/'+dl_hash)
        return json.dumps({'LINK' : SITE_URL+'/dl/'+dl_hash})
    else:
        try:
            os.makedirs(TEMP_DIR+'/'+neptun, 0700)
        except:
            pass
        folder_name = os.path.basename(dl_path)
        temp_path = TEMP_DIR+'/'+neptun+'/'+folder_name+'.zip'
        with open(os.devnull, "w") as fnull:
            # zip -rqDj vmi.zip /home/tarokkk/vpn-ik
            result = subprocess.call(['/usr/bin/zip', '-rqDj', temp_path, dl_path], stdout = fnull, stderr = fnull)
        os.symlink(temp_path, ROOT_WWW_FOLDER+'/'+dl_hash)
        return json.dumps({'LINK' : SITE_URL+'/dl/'+dl_hash})
COMMANDS['DOWNLOAD'] = cmd_download

# UPLOAD
def cmd_upload(request, neptun, home_path):
    up_path = home_path+'/'+request.json['PATH']
    up_path = os.path.realpath(up_path)
    if not up_path.startswith(home_path):
        abort(400, 'Invalid upload path.')
    if os.path.exists(up_path) == True and os.path.isdir(up_path):
        up_hash = str(uuid.uuid4())
        os.symlink(up_path, ROOT_WWW_FOLDER+'/'+up_hash)
        return json.dumps({ 'LINK' : SITE_URL+'/ul/'+up_hash})
    else:
        abort(400, 'Upload directory not exists!')
COMMANDS['UPLOAD'] = cmd_upload

# MOVE
def cmd_move(request, neptun, home_path):
    src_path = home_path+'/'+request.json['SOURCE']
    dst_path = home_path+'/'+request.json['DESTINATION']
    src_path = os.path.realpath(src_path)
    dst_path = os.path.realpath(dst_path)
    if not src_path.startswith(home_path):
        abort(400, 'Invalid source path.')
    if not dst_path.startswith(home_path):
        abort(400, 'Invalid destination path.')
    if os.path.exists(src_path) == True and os.path.exists(dst_path) == True and os.path.isdir(dst_path) == True:
        shutil.move(src_path, dst_path)
        return
    else:
    # TODO
        abort(400, "Can not move the file.")
COMMANDS['MOVE'] = cmd_move

# RENAME
def cmd_rename(request, neptun, home_path):
    src_path = home_path+'/'+request.json['PATH']
    src_path = os.path.realpath(src_path)
    if not src_path.startswith(home_path):
        abort(400, 'Invalid source path.')
    dst_path = os.path.dirname(src_path)+'/'+request.json['NEW_NAME']
    if os.path.exists(src_path) == True:
        os.rename(src_path, dst_path)
    else:
        abort(404, "File or Folder not found!")
COMMANDS['RENAME'] = cmd_rename

# NEW FOLDER
def cmd_new_folder(request, neptun, home_path):
    dir_path = home_path+'/'+request.json['PATH']
    dir_path = os.path.realpath(dir_path)
    if not dir_path.startswith(home_path):
        abort(400, 'Invalid directory path.')
    if os.path.exists(dir_path) == True:
        abort(400, "Directory already exist!")
    else:
        os.mkdir(dir_path, 0755)
COMMANDS['NEW_FOLDER'] = cmd_new_folder

# REMOVE
def cmd_remove(request, neptun, home_path):
    remove_path = home_path+'/'+request.json['PATH']
    remove_path = os.path.realpath(remove_path)
    if not remove_path.startswith(home_path):
        abort(400, 'Invalid path.')
    if os.path.exists(remove_path) != True:
        abort(404, "Path not found!")
    else:
        if os.path.isdir(remove_path) == True:
            shutil.rmtree(remove_path)
185 186
            return
        else:
187 188 189
            os.remove(remove_path)
            return
COMMANDS['REMOVE'] = cmd_remove
190

191 192 193 194 195 196 197 198 199 200 201
def cmd_toplist(request, neptun, home_path):
    d = []
    try:
        top_dir = os.path.normpath(os.path.join(home_path, "../.top"))
        d = [file_dict(os.readlink(os.path.join(top_dir, f)), home_path)
                for f in os.listdir(top_dir)]
    except:
        pass
    return json.dumps(sorted(d, key=lambda f: f['MTIME']))
COMMANDS['TOPLIST'] = cmd_toplist

202
@route('/set/<neptun>', method='POST')
203
@force_ssl
204 205 206 207 208 209 210 211 212
def set_keys(neptun):
    key_list = []
    smb_password = ''
    try:
        smbpasswd = request.json['SMBPASSWD']
        for key in request.json['KEYS']:
            key_list.append(key)
    except:
        abort(400, 'Wrong syntax!')
213
    result = subprocess.call([ROOT_BIN_FOLDER+'/'+USER_MANAGER, 'set', neptun, smbpasswd])
214
    if result == 0:
215
        updateSSHAuthorizedKeys(neptun, key_list)
216 217 218 219 220 221
        return
    elif result == 2:
        abort(403, 'User does not exist!')


@route('/new/<neptun>', method='POST')
222
@force_ssl
223 224 225
def new_user(neptun):
    key_list = []
    smbpasswd=''
226
    try:
227 228 229
        smbpasswd = request.json['SMBPASSWD']
    except:
        abort(400, 'Invalid syntax')
230 231
    # Call user creator script
    result = subprocess.call([ROOT_BIN_FOLDER+'/'+USER_MANAGER, 'add', neptun, smbpasswd])
232 233 234 235
    if result == 0:
        try:
            for key in request.json['KEYS']:
                key_list.append(key)
236
            updateSSHAuthorizedKeys(neptun, key_list)
237
        except:
238
            abort(400, 'SSH')
239 240 241 242 243 244 245
        return
    elif result == 2:
        abort(403, 'User already exist!')
    else:
        abort(400, 'An error occured!')


246 247

# Static file
248 249
@route('/dl/<hash_num>', method='GET')
def dl_hash(hash_num):
250
    hash_path = ROOT_WWW_FOLDER
251
    if os.path.exists(hash_path+'/'+hash_num) != True:
252 253 254
        abort(404, "File not found!")
    else:
        filename = os.path.basename(os.path.realpath(hash_path+'/'+hash_num))
255 256
        return static_file(hash_num, root=hash_path, download=filename)
@route('/ul/<hash_num>', method='POST')
257 258
def upload(hash_num):
    if not os.path.exists(ROOT_WWW_FOLDER+'/'+hash_num):
259
        abort (404, 'Token not found!')
260 261 262 263 264 265 266 267 268 269
    try:
        file_data = request.files.data
        file_name = file_data.filename
    except:
        if os.path.exists(ROOT_WWW_FOLDER+'/'+hash_num):
            os.remove(ROOT_WWW_FOLDER+'/'+hash_num)
        abort(400, 'No file was specified!')
    up_path = os.path.realpath(ROOT_WWW_FOLDER+'/'+hash_num+'/'+file_name)
    if os.path.exists(up_path):
        abort(400, 'File already exists')
270
    # Check if upload path valid
271
    if not up_path.startswith('/home'):
272 273
        abort(400, 'Invalid path.')
    os.remove(ROOT_WWW_FOLDER+'/'+hash_num)
274 275 276 277 278 279 280 281
    # Get the real upload path
    # Delete the hash link
    # Get the username from path for proper ownership
    username=up_path.split('/', 3)[2]
    # os.setegid(getpwnam(username).pw_gid)
    # os.seteuid(getpwnam(username).pw_uid)
    # TODO setuid subcommand
    # Check if file exist (root can overwrite anything not safe)
282
    f = open(up_path , 'wb')
283 284
    os.chown(up_path, getpwnam(username).pw_uid, getpwnam(username).pw_gid)
    os.chmod(up_path, 0644)
285 286 287 288 289 290
    f.close()
    with open(up_path , 'wb') as f:
        datalength = 0
        for chunk in fbuffer(file_data.file):
            f.write(chunk)
            datalength += len(chunk)
291 292 293 294 295
    return 'Upload finished: '+file_name+' - '+str(datalength)+' Byte'




296
# Define filebuffer for big uploads
297 298 299 300 301 302
def fbuffer(f, chunk_size=4096):
   while True:
      chunk = f.read(chunk_size)
      if not chunk: break
      yield chunk

303 304
# Update users .ssh/authorized_keys
def updateSSHAuthorizedKeys(username, key_list):
305 306 307 308 309
    user_home_ssh = '/home/'+username+'/home/.ssh'
    user_uid=getpwnam(username).pw_uid
    user_gid=getpwnam(username).pw_gid
    if not os.path.exists(user_home_ssh):
        os.mkdir(user_home_ssh, 0700)
310
    os.chown(user_home_ssh, user_uid, user_gid)
311
    auth_file_name = user_home_ssh+'/authorized_keys'
312
    auth_file = open(auth_file_name, 'w')
313 314 315
    for key in key_list:
        auth_file.write(key+'\n')
    auth_file.close()
316 317
    os.chmod(auth_file_name, 0600)
    os.chown(auth_file_name, user_uid, user_gid)
318 319
    return

320 321 322
# For debug purpose
# @route('/ul/<hash_num>', method='GET')
# def upload_get(hash_num):
323 324
#    return """<form method="POST" action="/ul/{hash}" enctype="multipart/form-data">
#   <input name="data" type="file" />
325 326
#   <input type="submit" />
# </form>""".format(hash=hash_num)
327

328 329
def list_directory(home, path):
    # Check for path breakout
330
    if not os.path.realpath(path).startswith(home):
331
        abort(400, 'Invalid path.')
332
    # Check if path exist
333
    if os.path.exists(path) != True:
334
        abort(404, 'No such file or directory')
335
    else:
336
        # If it's a file return with list
337 338
        if os.path.isdir(path) != True:
            return json.dumps((os.path.basename(path), 'F', os.path.getsize(path), os.path.getmtime(path)))
339
        # List directory and return list
340 341
        else:
            filelist = os.listdir(path)
342 343 344 345 346 347 348 349 350 351 352 353 354 355
            dictlist = [file_dict(os.path.join(path, f), home) for f in filelist]
            return json.dumps(dictlist)

def file_dict(path, home):
    basename = os.path.basename(path.rstrip('/'))
    if os.path.isdir(path):
        is_dir = 'D'
    else:
        is_dir = 'F'
    return {'NAME': basename,
            'TYPE': is_dir,
            'SIZE': os.path.getsize(path)/1024,
            'MTIME': os.path.getmtime(path),
            'DIR': os.path.relpath(os.path.dirname(path), home)}
356 357

def getQuotaStatus(neptun):
358
    output=subprocess.check_output([ROOT_BIN_FOLDER+'/'+USER_MANAGER, 'status', neptun], stderr=subprocess.STDOUT)
359
    return output.split()
360

361
if __name__ == "__main__":
362
    run(host=SITE_HOST, port=SITE_PORT)
363 364
else:
    application=app()