manage_ftp.py 10.9 KB
Newer Older
Daniela Daniel's avatar
Daniela Daniel committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
"""
Flash FTP application main controller.
"""
import os
import smtplib
import hashlib
import datetime
import logging
import subprocess
from django.utils import timezone
from django.utils.crypto import get_random_string
from django.core.mail import send_mail
from django.core.exceptions import ObjectDoesNotExist
from django.template.loader import render_to_string
from ftp_manager.models import Account, SecondaryAccount
from ftp_site.settings import FTP_DIRECTORY, VSFTPD_USER_CONF_DIR,\
    VSFTPD_USER_CONF_SKEL, VALID_DAYS, FTP_HOSTNAME, FTP_SECRETE, FROM_EMAIL,\
    FTP_IPADDRESS, MAX_GUEST_COUNT, FTP_SERVICE_USER    


LOGGER = logging.getLogger('django')


# BioHPC user account methods
def get_user_info(username):
    ''' returns user account info: username, password and expiration date'''
    LOGGER.info('%s logged in', username)
    if Account.objects.filter(username=username).count() == 0:
        account = get_new_user_password(username)
        return account
    account = Account.objects.get(username=username)
    return account


def disable_ftp_account(username):
    '''disables user account by username by setting expiration time to now'''
    account = Account.objects.get(username=username)
    if account.expiration_time > timezone.now():
        account.disable_user_password()
        LOGGER.info('%s account disabled', username)
        return True
    return False


def get_new_user_password(username):
    '''
    creates user account by username, if new user logs in for the first time,
    creates new directory and database entry for user
    '''
    password, hash_pass = generate_pass_md5()
    expiration_time = timezone.now() + datetime.timedelta(days=VALID_DAYS)

    if Account.objects.filter(username=username).count() == 0:
        ftp_user = Account(username=username, password=hash_pass,
                           expiration_time=expiration_time, pass_plain=password)
    elif Account.objects.filter(username=username).count() == 1:
        ftp_user = Account.objects.get(username=username)
        ftp_user.pass_plain = password
        ftp_user.password = hash_pass
        ftp_user.expiration_time = expiration_time

    ftp_user.save()
    success = check_user_directory(username)

    if success:
        LOGGER.info('%s renewed password', username)
        return ftp_user
    return None


def check_user_directory(username):
    '''
    check whether the directory is correctly set up,
    this is now done by the create_user_dir script.
    '''

    newpath = os.path.join(FTP_DIRECTORY, username)

    if not os.path.exists(str(newpath)):
        LOGGER.info("user %s directory doesn't exist, please create", username)
        return False
    return True


# Guest account methods
def generate_guestname(user):
    '''
    :param user: user for which guest belongs to
    :return unique guestname:
    '''
    random_string = get_random_string(4)
    try:
        sec_acc = SecondaryAccount.objects.latest('id')
        guestid = 100 + sec_acc.id + 1
    except ObjectDoesNotExist:
        LOGGER.error('no guests in the table, using 1001')
        guestid = 1001

    guest_name = "guest" + str(user.id) + str(guestid) + str(random_string)

    if SecondaryAccount.objects.filter(username=guest_name).count() == 0:
        return guest_name
    return generate_guestname(user)


def generate_secondary_user(username, email='', notes='', message='', directory=None):
    '''
    generates secondary user accounts
    :param username: logged in user's username from request
    :param email: user specified email address for which the invitation email will be sent to
    :param notes: user note to himself
    :param message: email message sent with the invitation
    :param directory: a directory which the user is sharing with guest,
     None to use user's own directory, becomes the root directory for the guest
    :return: guest's name if successful, False if not
    '''

    user = Account.objects.get(username=username)
    guest_name = generate_guestname(user)

    expire_time = timezone.now() + datetime.timedelta(days=VALID_DAYS)
    if directory:
        LOGGER.info("%s: creating directory for guest", username)
        guest_root_dir = os.path.join(FTP_DIRECTORY, username, directory)
        create_guest_dir(username, guest_root_dir)

    else:
        LOGGER.info("{%s: no dir specified", username)
        #guest_root_dir = os.path.join(FTP_DIRECTORY, username)
        return False

    guest_conf_dir = os.path.join(VSFTPD_USER_CONF_DIR, guest_name)
    password, hash_pass = generate_pass_md5()
    LOGGER.info('%s added the guest root dir %s', username, guest_root_dir)
    if os.path.exists(guest_root_dir):
        LOGGER.info(
            "%s dir detected, adding to guest's root dir and writting to database", username)
        try:
            if not os.path.exists(guest_conf_dir):
                conf_file = open(guest_conf_dir, 'w')
                conf_file.write('dirlist_enable=YES\n')
                conf_file.write('download_enable=YES\n')
                conf_file.write('write_enable=YES\n')
                conf_file.write('local_root=' + guest_root_dir + '\n')
                conf_file.close()
                # the config file has to be owned and only writable by root.root...
                # or it wont work, this has to be done either manually or using sudo
                subprocess.check_call(['sudo', 'chown', 'root:root', guest_conf_dir])

            else:
                LOGGER.error(
                    '%s exist, but is not a entry in the database, probably a deleted user, '
                    'will overwrite this file', guest_conf_dir)
                subprocess.check_call(['sudo', 'chown', 'root:root', guest_conf_dir])
                conf_file = open(guest_conf_dir, 'w')
                conf_file.write('dirlist_enable=YES\n')
                conf_file.write('download_enable=YES\n')
                conf_file.write('write_enable=YES\n')
                conf_file.write('local_root=' + str(guest_root_dir)+'\n')
                conf_file.close()
                # the config file has to be owned and only writable by root.root.. or it wont work,
                # this has to be done either manually or using sudo
                subprocess.check_call(['sudo', 'chown', 'root.root', guest_conf_dir])
            SecondaryAccount(username=guest_name, pass_plain=password, password=hash_pass,
                             expiration_time=expire_time, owner=user, email=email, user_notes=notes,
                             email_notes=message, directory=guest_root_dir).save()

            user.expiration_time = expire_time
            user.save()

            LOGGER.info('guestuser: %s has been created by user: %s', guest_name, username)

            return guest_name

        except (OSError, subprocess.CalledProcessError) as error:
            LOGGER.info('could not create secondary user for %s\n error: %s', username, str(error))
            return False

    else:
        LOGGER.info("%s dir does not exist, return false", username)
        return False


def verify_directory_name(name):
    '''check directory name compliance'''
    chars = set('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789')
    specialchar = ' _-.'

    if name == '' or name is None:
        LOGGER.info('empty directory name')
        return False

    if any((char in name) for char in chars):
        for char in name:
            if char not in chars:
                if char not in specialchar:
                    return False
        return True
    return False


def create_guest_dir(username, directory):
    '''create directory to 'chroot' guest to'''
    if os.path.exists(directory):
        return True
    try:
        os.umask(0o002)
        os.mkdir(str(directory), mode=0o770)
        os.chmod(str(directory), mode=0o770)
        #subprocess.check_call(['mkdir', str(directory)])
        LOGGER.info("%s dir created", username)
        return True
    except OSError:
        LOGGER.info("%s %s cannot be created, please look into the problem", username, directory)
        return False


def generate_guest_hash_link(guestname):
    '''create the hash link for guest'''
    guest = SecondaryAccount.objects.get(username=guestname)
    guest.hash_address = generate_hash()
    guest.save()
    hash_link = FTP_HOSTNAME + '/manage/view_guest_account/?q=' + guest.hash_address
    LOGGER.info('hash address was created for user: %s', guestname)
    return hash_link


def remove_secondary_user(guestname):
    '''remove guest account'''
    guest = SecondaryAccount.objects.get(username=guestname)
    guest.delete()


def renew_guests_expiration(username, guestname=None):
    '''renew guest expiration time only, not password'''
    user = Account.objects.get(username=username)

    if guestname:
        guest = SecondaryAccount.objects.get(username=guestname)
        if guest.owner == user:
            guest.renew_expiration()
    else:
        user.renew_all_guests()


def disable_guest_account(guestname):
    '''disable guest account'''
    guest = SecondaryAccount.objects.get(username=guestname)
    guest.disable_pass()


def disable_all_guest(username):
    '''disable all guest accounts'''
    user = Account.objects.get(username=username)
    user.disable_all_guests()


def renew_guest_password(guestname):
    '''renew guest password and expiration time'''
    plain, hash_pass = generate_pass_md5()
    guest = SecondaryAccount.objects.get(username=guestname)
    guest.pass_plain = plain
    guest.password = hash_pass
    guest.save()
    guest.renew_expiration()

    return guest


# Support methods
def send_email_to_guest(username, email, message, hash_link):
    '''    send_mail(
        'ftp folder sharing',
        message,
        'from@example.com',
        [email],
        fail_silently=False,
    )
'''
    if "http" not in hash_link:
        hash_link = "https://" + str(hash_link)
    html_message = render_to_string('ftp_manager/sharing_email.html',
                                    {'username': username, 'message': message,
                                     'hash_link': hash_link})
    message = 'Dear BioHPC user: \n'
    title = 'FTP Folder Sharing'
    try:
        send_mail(title, message, FROM_EMAIL, [email.lower(), ], html_message=html_message)
        LOGGER.info('%s has shared folder with %s', username, email)
        return True
    except smtplib.SMTPException as exception:
        LOGGER.error('sending email fail with message %s', str(exception))
        return False


def generate_hash():
    '''create hash for guest'''
    random_string = get_random_string(16)
    hash_string = FTP_SECRETE+random_string
    hash_link = hashlib.sha384(hash_string.encode('utf-8')).hexdigest()
    return hash_link


def generate_pass_md5():
    '''generate random password, called by get_new_user_pass and generate_secondary_user functions
    generates random password with length 12, and hass the pass with MD5'''
    password = get_random_string(12)
    hash_pass = hashlib.md5(password.encode('utf-8')).hexdigest()

    return password, hash_pass