#!/usr/bin/python2.7
# -*- coding: utf-8 -*-
#
# Univention UCS@school
#
# Copyright 2010-2017 Univention GmbH
#
# http://www.univention.de/
#
# All rights reserved.
#
# The source code of this program is made available
# under the terms of the GNU Affero General Public License version 3
# (GNU AGPL V3) as published by the Free Software Foundation.
#
# Binary versions of this program provided by Univention to you as
# well as other copyrighted, protected or trademarked materials like
# Logos, graphics, fonts, specific documentations and configurations,
# cryptographic keys etc. are subject to a license agreement between
# you and Univention and not subject to the GNU AGPL V3.
#
# In the case you use this program under the terms of the GNU AGPL V3,
# the program is provided in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public
# License with the Debian GNU/Linux or Univention distribution in file
# /usr/share/common-licenses/AGPL-3; if not, see
# <http://www.gnu.org/licenses/>.


import univention.admin.objects
import univention.admin.uldap
import univention.admin.modules
import univention.admin.config
import sys
from univention.config_registry import ConfigRegistry
from optparse import OptionParser
import ipaddr

baseDN = None
co = None
lo = None

univention.admin.modules.update()


def _print(_str):
	print _str
	print '-' * len(_str)


def get_object(dn, module, superordinate=None):
	"""Get a single object.

	@param	dn	Object dn
	"""
	_module = univention.admin.modules.get(module)
	position = univention.admin.uldap.position(baseDN)
	univention.admin.modules.init(lo, position, _module)

	return univention.admin.objects.get(_module, co, lo, position=position, superordinate=superordinate, dn=dn)


def get_objects(module, base=baseDN, _filter=None, superordinate=None):
	"""Get objects.

	@param	ou	School OU
	"""
	_module = univention.admin.modules.get(module)
	position = univention.admin.uldap.position(baseDN)
	univention.admin.modules.init(lo, position, _module)
	if not _filter:
		return univention.admin.modules.lookup(_module, co, lo, superordinate=superordinate, base=base, scope='sub')
	else:
		if len(_filter) != 2:
			raise ValueError('_filter consists of other than two arguments!')
		return univention.admin.modules.lookup(
			_module, co, lo,
			base=base, scope='sub', superordinate=superordinate,
			filter=univention.admin.filter.expression(*_filter)
		)


def user(base=baseDN):
	"""List user information for specified ou."""
	users = get_objects('users/user', base=base)
	print 'Users:'
	for u in users:
		u.open()

	users = sorted(users, cmp=lambda x, y: cmp(x['username'], y['username']))

	for u in users:
		_str = '\t%s' % u["username"]
		_options = []
		_o2 = []
		if u["lastname"]:
			_o2.append(u['lastname'])
		if u["firstname"]:
			_o2.append(u['firstname'])
		if _o2:
			_options.append('name: %s' % ', '.join(_o2))
		if u['disabled'] != 'none':
			_options.append('disabled=%s' % u["disabled"])
		if u['locked'] != 'none':
			_options.append('locked=%s' % u["locked"])
		if u["passwordexpiry"]:
			_options.append('password expires: %s' % u["passwordexpiry"])

		if _options:
			tabs = '\t'
			if len(_str) <= 8:
				tabs += '\t'
			print _str + '%s(%s)' % (tabs, '; '.join(_options))
		else:
			print _str
	print


def computer(base=baseDN):
	"""List computer information for specified ou."""
	computers = get_objects('computers/computer', base=base)
	print 'Computers:'
	for c in computers:
		c.open()

	computers = sorted(computers, cmp=lambda x, y: cmp(x['name'], y['name']))

	for c in computers:
		_str = '\t%s' % c["name"]
		_options = []
		if c["ip"] and c["ip"] != ['']:
			_options.append('IP: %s' % ', '.join(c['ip']))
		if c["mac"] and c["mac"] != ['']:
			_options.append('MAC: %s' % ', '.join(c["mac"]))
		if c["inventoryNumber"] and c["inventoryNumber"] != ['']:
			_options.append('inv. number: %s' % ', '.join(c["inventoryNumber"]))

		if _options:
			print _str + '\t(%s)' % '; '.join(_options)
		else:
			print _str
	print


def network(base=baseDN):
	"""List network information for specified ou."""
	networks = get_objects('networks/network', base=base)
	print 'Network:'
	for n in networks:
		n.open()

	networks = sorted(networks, cmp=lambda x, y: cmp(x['name'], y['name']))

	for n in networks:
		print '\t%s' % n['name']
		print '\tNetwork:\t%s' % n["network"]

		net_ip = ipaddr.IPv4Network('%s/%s' % (n['network'], n['netmask']))
		netmask = str(net_ip.netmask)
		print '\tNetmask:\t%s' % netmask

		if n['dhcpEntryZone']:
			_base = base
			dnx = univention.admin.uldap.explodeDn(n.dn)
			if dnx.count('cn=networks'):
				_base = ','.join(dnx[dnx.index('cn=networks') + 1:])

			for subnet in get_objects('dhcp/subnet', base=_base):
				subnet.open()
				brd_ip = ipaddr.IPv4Address(subnet['broadcastaddress'])
				if brd_ip not in net_ip:
					continue

				print '\tBroadcast:\t%s' % subnet['broadcastaddress']
				if subnet['range']:
					print '\tDynamic Range:\t%s' % subnet['range']

				# FIXME: This is very ugly but it seems like you can't get the
				# type of a policy object easily
				policy_dns = lo.get(subnet.dn, ['univentionPolicyReference']).get('univentionPolicyReference', [])
				policy_dns.sort()

				for dn in policy_dns:
					dnx = univention.admin.uldap.explodeDn(dn)
					try:
						if dnx[1] == 'cn=routing':
							policy = get_object(dn, 'policies/dhcp_routing')
							print '\tRouters:\t%s' % '; '.join(policy['routers'])
						elif dnx[1] == 'cn=netbios':
							policy = get_object(dn, 'policies/dhcp_netbios')
							# print '\tNetBIOS Scope:\t%s' % policy['netbios_scope']
							print '\tNetBIOS Server:\t%s' % '; '.join(policy['netbios_name_servers'])
						elif dnx[1] == 'cn=dns':
							policy = get_object(dn, 'policies/dhcp_dns')
							print '\tDomain Name:\t%s' % policy['domain_name']
							print '\tDNS Server:\t%s' % '; '.join(policy['domain_name_servers'])
					except:
						pass

		for iprange in n['ipRange']:
			print '\tIP-Range:\t%s' % '-'.join(iprange)

		print


if __name__ == '__main__':
	# parse arguments
	usage = "%prog [options] <ou> [<ou>...]"

	parser = OptionParser(usage=usage)
	parser.add_option("-u", "--user", dest="user", action="store_true", help="Display user information")
	parser.add_option("-n", "--network", action="store_true", dest="network", help="Display network information")
	parser.add_option("-c", "--computer", action="store_true", dest="computer", help="Display computer information")
	parser.add_option("-a", "--all", action="store_true", dest="all", help="Display all available information (user, network, computer) [default]")

	parser.set_defaults(user=False, network=False, computer=False, all=False)

	(options, args) = parser.parse_args()

	if not args:
		parser.print_help()
		parser.error('No argument <ou> given.')

	# start execution
	configRegistry = ConfigRegistry()
	configRegistry.load()

	baseDN = configRegistry['ldap/base']

	secretFile = open('/etc/machine.secret', 'r')
	machinepwd = secretFile.readline().replace('\n', '')
	machinedn = configRegistry.get('ldap/hostdn')
	if not machinedn or not machinepwd:
		print 'Cannot get machine account dn or machine credentials'
		sys.exit(1)

	co = univention.admin.config.config()
	try:
		lo = univention.admin.uldap.access(host=configRegistry['ldap/server/name'], base=baseDN, binddn=machinedn, bindpw=machinepwd, start_tls=2)
	except Exception, e:
		univention.debug.debug(univention.debug.ADMIN, univention.debug.WARN, 'authentication error: %s' % str(e))
		print 'ERROR: authentication error: %s' % str(e)
		sys.exit(1)

	exitcode = 0
	for _ou in args:
		res = lo.search('ou=%s' % _ou)
		if not res:
			print >>sys.stderr, 'WARNING: Unable to find OU %s.' % _ou
			exitcode = 1
			continue
		elif len(res) > 1:
			print >>sys.stderr, 'WARNING: Multiple OUs found for %s, ignoring request.' % _ou
			exitcode = 2
			continue
		search_base = res[0][0]

		_print('OU %s: %s' % (_ou, search_base))

		if options.user or not (options.network or options.user or options.computer) or options.all:
			user(search_base)

		if options.network or not (options.network or options.user or options.computer) or options.all:
			network(search_base)

		if options.computer or not (options.network or options.user or options.computer) or options.all:
			computer(search_base)
	sys.exit(exitcode)
