#!/usr/bin/env python

import argparse
import json
import sys
import time

try:
	import pwd
	import spwd
except ImportError:
	pwd = None


class ConvertUsers:

	def __init__(self, input_file, users_format, output_file, min_uid, max_uid, usernames, force_uid, force_gid):
		self.input_file = input_file
		self.users_format = users_format
		self.output_file = output_file
		self.min_uid = min_uid
		self.max_uid = max_uid
		self.usernames = usernames
		self.force_uid = force_uid
		self.force_gid = force_gid
		self.SFTPGoUsers = []

	def buildUserObject(self, username, password, home_dir, uid, gid, max_sessions, quota_size, quota_files, upload_bandwidth,
					download_bandwidth, status, expiration_date, allowed_ip=[], denied_ip=[]):
		return {'id':0, 'username':username, 'password':password, 'home_dir':home_dir, 'uid':uid, 'gid':gid,
			'max_sessions':max_sessions, 'quota_size':quota_size, 'quota_files':quota_files, 'permissions':{'/':["*"]},
			'upload_bandwidth':upload_bandwidth, 'download_bandwidth':download_bandwidth,
			'status':status, 'expiration_date':expiration_date,
			'filters':{'allowed_ip':allowed_ip, 'denied_ip':denied_ip}}

	def addUser(self, user):
		user['id'] = len(self.SFTPGoUsers) + 1
		print('')
		print('New user imported: {}'.format(user))
		print('')
		self.SFTPGoUsers.append(user)

	def saveUsers(self):
		if self.SFTPGoUsers:
			data = {'users':self.SFTPGoUsers}
			jsonData = json.dumps(data)
			with open(self.output_file, 'w') as f:
				f.write(jsonData)
			print()
			print('Number of users saved to "{}": {}. You can import them using loaddata'.format(self.output_file,
																								len(self.SFTPGoUsers)))
			print()
			sys.exit(0)
		else:
			print('No user imported')
			sys.exit(1)

	def convert(self):
		if self.users_format == 'unix-passwd':
			self.convertFromUnixPasswd()
		elif self.users_format == 'pure-ftpd':
			self.convertFromPureFTPD()
		else:
			self.convertFromProFTPD()
		self.saveUsers()

	def isUserValid(self, username, uid):
		if self.usernames and not username in self.usernames:
			return False
		if self.min_uid >= 0 and uid < self.min_uid:
			return False
		if self.max_uid >= 0 and uid > self.max_uid:
			return False
		return True

	def convertFromUnixPasswd(self):
		days_from_epoch_time = time.time() / 86400
		for user in pwd.getpwall():
			username = user.pw_name
			password = user.pw_passwd
			uid = user.pw_uid
			gid = user.pw_gid
			home_dir = user.pw_dir
			status = 1
			expiration_date = 0
			if not self.isUserValid(username, uid):
				continue
			if self.force_uid >= 0:
				uid = self.force_uid
			if self.force_gid >= 0:
				gid = self.force_gid
			# FIXME: if the passwords aren't in /etc/shadow they are probably DES encrypted and we don't support them
			if password == 'x' or password == '*':
				user_info = spwd.getspnam(username)
				password = user_info.sp_pwdp
				if not password or password == '!!' or password == '!*':
					print('cannot import user "{}" without a password'.format(username))
					continue
				if user_info.sp_inact > 0:
					last_pwd_change_diff = days_from_epoch_time - user_info.sp_lstchg
					if last_pwd_change_diff > user_info.sp_inact:
						status = 0
				if user_info.sp_expire > 0:
					expiration_date = user_info.sp_expire * 86400
			self.addUser(self.buildUserObject(username, password, home_dir, uid, gid, 0, 0, 0, 0, 0, status,
											expiration_date))

	def convertFromProFTPD(self):
		with open(self.input_file, 'r') as f:
			for line in f:
				fields = line.split(':')
				if len(fields) > 6:
					username = fields[0]
					password = fields[1]
					uid = int(fields[2])
					gid = int(fields[3])
					home_dir = fields[5]
					if not self.isUserValid(username, uid):
						continue
					if self.force_uid >= 0:
						uid = self.force_uid
					if self.force_gid >= 0:
						gid = self.force_gid
					self.addUser(self.buildUserObject(username, password, home_dir, uid, gid, 0, 0, 0, 0, 0, 1, 0))

	def convertPureFTPDIP(self, fields):
		result = []
		if not fields:
			return result
		for v in fields.split(','):
			ip_mask = v.strip()
			if not ip_mask:
				continue
			if ip_mask.count('.') < 3 and ip_mask.count(':') < 3:
				print('cannot import pure-ftpd IP: {}'.format(ip_mask))
				continue
			if '/' not in ip_mask:
				ip_mask += '/32'
			result.append(ip_mask)
		return result

	def convertFromPureFTPD(self):
		with open(self.input_file, 'r') as f:
			for line in f:
				fields = line.split(':')
				if len(fields) > 16:
					username = fields[0]
					password = fields[1]
					uid = int(fields[2])
					gid = int(fields[3])
					home_dir = fields[5]
					upload_bandwidth = 0
					if fields[6]:
						upload_bandwidth = int(int(fields[6]) / 1024)
					download_bandwidth = 0
					if fields[7]:
						download_bandwidth = int(int(fields[7]) / 1024)
					max_sessions = 0
					if fields[10]:
						max_sessions = int(fields[10])
					quota_files = 0
					if fields[11]:
						quota_files = int(fields[11])
					quota_size = 0
					if fields[12]:
						quota_size = int(fields[12])
					allowed_ip = self.convertPureFTPDIP(fields[15])
					denied_ip = self.convertPureFTPDIP(fields[16])
					if not self.isUserValid(username, uid):
						continue
					if self.force_uid >= 0:
						uid = self.force_uid
					if self.force_gid >= 0:
						gid = self.force_gid
					self.addUser(self.buildUserObject(username, password, home_dir, uid, gid, max_sessions, quota_size,
													  quota_files, upload_bandwidth, download_bandwidth, 1, 0, allowed_ip,
													  denied_ip))


if __name__ == '__main__':
	parser = argparse.ArgumentParser(formatter_class=argparse.ArgumentDefaultsHelpFormatter, description=
									'Convert users to a JSON format suitable to use with loadddata')
	supportedUsersFormats = []
	help_text = ''
	if pwd is not None:
		supportedUsersFormats.append('unix-passwd')
		help_text = 'To import from unix-passwd format you need the permission to read /etc/shadow that is typically granted to the root user only'
	supportedUsersFormats.append('pure-ftpd')
	supportedUsersFormats.append('proftpd')
	parser.add_argument('input_file', type=str)
	parser.add_argument('users_format', type=str, choices=supportedUsersFormats, help=help_text)
	parser.add_argument('output_file', type=str)
	parser.add_argument('--min-uid', type=int, default=-1, help='if >= 0 only import users with UID greater or equal ' +
								'to this value. Default: %(default)s')
	parser.add_argument('--max-uid', type=int, default=-1, help='if >= 0 only import users with UID lesser or equal ' +
								'to this value. Default: %(default)s')
	parser.add_argument('--usernames', type=str, nargs='+', default=[], help='Only import users with these usernames. ' +
								'Default: %(default)s')
	parser.add_argument('--force-uid', type=int, default=-1, help='if >= 0 the imported users will have this UID in ' +
								'SFTPGo. Default: %(default)s')
	parser.add_argument('--force-gid', type=int, default=-1, help='if >= 0 the imported users will have this GID in ' +
								'SFTPGo. Default: %(default)s')

	args = parser.parse_args()

	convertUsers = ConvertUsers(args.input_file, args.users_format, args.output_file, args.min_uid, args.max_uid,
								args.usernames, args.force_uid, args.force_gid)
	convertUsers.convert()
