Skip to content

Commit

Permalink
Lots of bugfixing, refactoring and (hopefully) improvements.
Browse files Browse the repository at this point in the history
  • Loading branch information
JedMeister committed May 21, 2021
1 parent c048398 commit 5696021
Showing 1 changed file with 108 additions and 62 deletions.
170 changes: 108 additions & 62 deletions overlay/usr/lib/inithooks/bin/domain-controller.py
Expand Up @@ -82,6 +82,7 @@
HOSTS_BAK = '{}.bak'.format(HOSTS_FILE)
COMMAND_LOG = '/var/log/inithooks/samba_dc.log'


def usage(s=None):
if s:
print("Error:", s, file=sys.stderr)
Expand All @@ -95,12 +96,6 @@ def fatal(s):
sys.exit(1)


def getoutput(command):
return subprocess.run(command,
encoding='utf-8',
stdout=PIPE).stdout.strip()


def error_msg(msg, interactive):
if interactive:
return False, msg
Expand Down Expand Up @@ -139,34 +134,61 @@ def validate_realm(realm, interactive):
def validate_netbios(domain, interactive):
err = []
if len(domain) < 1 or len(domain) > 15:
err = error_msg("Netbios domain (aka workgroup) must be greater than 0"
" and less than 15 characters (7+ recommend).",
interactive)
return error_msg("Netbios domain (aka workgroup) must be greater than 0"
" and less than 15 characters (7+ recommend).",
interactive)
if not domain.isalnum() or not domain[0].isalpha():
err = error_msg("Netbios domain (aka workgroup) must only contain"
" alphanumeric characters and start with a letter.",
interactive)
if err:
return err
return error_msg("Netbios domain (aka workgroup) must only contain"
" alphanumeric characters and start with a letter.",
interactive)
else:
return (domain.upper())


def validate_hostname(hostname, interactive):
err = []
def ping_client(fqdn):
proc = subprocess.run(['ping', '-c1', fqdn])
if proc.returncode == 0:
return True
return False


def check_dns(fqdn):
proc = subprocess.run(['host', '-s', fqdn])
if proc.returncode == 0:
return True
return False


def get_hostname():
return subprocess.run(['hostname', '-s'],
encoding='utf-8', stdout=PIPE).stdout.strip()


def validate_hostname(hostname, domain, interactive, default):
if hostname == default:
return error_msg(
"Hostname matches default '{}'.".format(default),
interactive)
pattern = r"^[-\w]*$"
if len(hostname.split('.')) > 1:
err = error_msg("Only the hostname (not the domain/realm) should be"
" supplied.",
interactive)
return error_msg("Only the hostname (not the domain/realm) should be"
" supplied.",
interactive)
match = re.match(pattern, hostname)
if not match or (len(hostname) != len(match.group(0))):
err = error_msg("Hostname is invalid &/or includes invalid characters.",
interactive)
if err:
return err
else:
return (hostname)
return error_msg("Invalid hostname &/or includes invalid characters.",
interactive)
fqdn = '.'.join([hostname, domain]).lower()
if check_dns(fqdn):
return error_msg("Host {} already registered on network.".format(fqdn),
interactive)
return (hostname)


def set_hostname(hostname):
with open('/etc/hostname', 'w') as fob:
fob.write(hostname+'\n')
run_command(['hostname', hostname])


def rm_f(path):
Expand All @@ -188,12 +210,10 @@ def run_command(command, stdin=False):
if stdin:
proc = subprocess.Popen(command, text=True, stdin=PIPE,
stdout=PIPE, stderr=STDOUT)
output = proc.communicate(input=stdin)[0]
else:
proc = subprocess.Popen(command, text=True,
stdout=PIPE, stderr=STDOUT)
if stdin:
output = proc.communicate(input=stdin)[0]
else:
output = []
while True:
out = proc.stdout.read(1)
Expand All @@ -207,7 +227,11 @@ def run_command(command, stdin=False):
return proc.returncode, output


def update_resolvconf(domain, nameserver):
def update_resolvconf(domain, nameserver, interactive):
if not ping_client(nameserver):
return error_msg(
"No client is responding to ping at ip address {}.".format(nameserver),
interactive)
shutil.copy2(RESOLVCNF_HEAD, RESOLVCNF_BAK)
with open(RESOLVCNF_HEAD, 'r') as fob:
resolvconf = fob.readlines()
Expand All @@ -221,6 +245,7 @@ def update_resolvconf(domain, nameserver):
else:
value = domain
line = '{} {}\n'.format(term, value)
print('Updating {} ({}) in resolv.conf'.format(term, value))
new_resolvconf.append(line)
with open(RESOLVCNF_HEAD, 'w') as fob:
fob.writelines(new_resolvconf)
Expand All @@ -237,25 +262,25 @@ def update_hosts(ip, hostname, domain):
may result in unexpected results. Only updates IPv4 results and will
remove existing IPv6 values for FQDN."""
shutil.copy2(HOSTS_FILE, HOSTS_BAK)
fqdn = '.'.join([hostname, domain])
fqdn = '.'.join([hostname, domain]).lower()
with open(HOSTS_FILE, 'r') as fob:
hosts = fob.readlines()
new_hosts = []
found = False
inserted = False
localdomain = '127.0.1.1'
print('Updating {}:'.format(HOSTS_FILE))
for line in hosts:
if fqdn in line:
line = ''
if found and not inserted:
new_hosts.append('{} {}'.format(ip, fqdn))
inserted = True
if line.startswith('127.0.1.1'):
if not ip: # assumes joining existing domain
line = '127.0.1.1 {}\t{}'.format(hostname, fqdn)
inserted = True
else:
found = True # insert entry for this machine next line
if not line.startswith('#'):
if line.startswith(localdomain):
line = ' '.join((localdomain, hostname, fqdn))+'\n'
if ip != localdomain:
print(line.rstrip())
new_hosts.append(line)
line = ' '.join((ip, hostname, fqdn))+'\n'
elif line.startswith(ip):
line = ''
print(line.rstrip())
new_hosts.append(line)
print('### End of hosts file ###')
with open(HOSTS_FILE, 'w') as fob:
fob.writelines(new_hosts)

Expand All @@ -271,20 +296,24 @@ def cleanup():

def main():

HOSTNAME = getoutput(['hostname', '-s'])
NET_IP = getoutput(['hostname', '-I'])
HOSTNAME = subprocess.run(['hostname', '-s'],
encoding='utf-8', stdout=PIPE).stdout.strip()
NET_IP = subprocess.run(['hostname', '-I'],
encoding='utf-8', stdout=PIPE).stdout.strip()

# disabled for now, will reimplment at some point...
#NET_IP321 = NET_IP.split('.')[:-1]
#NET_IP321.reverse()
#NET_IP321 = '.'.join(NET_IP321)
#NET_IP4 = NET_IP.split('.')[-1]

DEFAULT_HOSTNAME = "dc1"
DEFAULT_REALM = "DOMAIN.LAN"
DEFAULT_DOMAIN = "DOMAIN"
DEFAULT_NS = ""
DEFAULT_NEW_HOSTNAME = "dc2"


try:
opts, args = getopt.gnu_getopt(sys.argv[1:], "h",
['help',
Expand Down Expand Up @@ -328,19 +357,20 @@ def main():
create = True
elif realm and domain and admin_password and join_nameserver and hostname:
join_nameserver = valid_ip(join_nameserver)
hostname = validate_hostname(hostname, interactive)
update_resolvconf(realm.lower(), join_nameserver, interactive)
hostname = validate_hostname(hostname, realm, interactive, DEFAULT_HOSTNAME)
if join_nameserver and hostname[0]: # both valid
create = False
HOSTNAME = hostname[0]
elif join_nameserver: # invalid hostname
restore_resolvconf()
interactive = True
HOSTNAME = ""
hostname = ""
elif hostname[0]: # invalid nameserver IPv4
interactive = True
HOSTNAME = hostname[0]
else: # both invalid
restore_resolvconf()
interactive = True
HOSTNAME = ""
hostname = ""
join_nameserver = ""
elif realm and domain and admin_password and not join_nameserver:
create = True
Expand Down Expand Up @@ -401,7 +431,6 @@ def main():
DEFAULT_DOMAIN)
domain = validate_netbios(domain, interactive)
if domain[0]:
domain = domain[0]
break
else:
d.error(domain[1])
Expand Down Expand Up @@ -434,18 +463,20 @@ def main():
continue
else:
break
if not HOSTNAME:
# set up nameserver now, so we can check for existing client hostname
update_resolvconf(realm.lower(), join_nameserver, interactive)
if not hostname:
while True:
hostname = d.get_input(
"Set new hostname",
"Set new unique hostname for this domain-controller.",
DEFAULT_NEW_HOSTNAME)
hostname = validate_hostname(hostname, interactive)
hostname = validate_hostname(hostname, realm.lower(), interactive, DEFAULT_HOSTNAME)
if not hostname[0]:
d.error(hostname[1])
continue
else:
HOSTNAME = hostname[0]
set_hostname(hostname)
break

# Stop any Samba services
Expand Down Expand Up @@ -480,13 +511,14 @@ def main():
'--option=interfaces=127.0.0.1 {}'.format(NET_IP)]
commands = [samba_domain, set_expiry, export_krb]
nameserver = '127.0.0.1'
hostname = HOSTNAME
else: # join
with open('/etc/krb5.conf', 'w') as fob:
fob.write('[libdefaults]\n')
fob.write(' dns_lookup_realm = false\n')
fob.write(' dns_lookup_kdc = true\n')
fob.write(' default_realm = {}'.format(realm))
ip = None # will update 127.0.1.1 hosts entry
ip = None # will update 127.0.1.1 hosts entry only
config_krb = ['kinit', 'administrator']
krb_pass = admin_password
samba_domain = ['samba-tool', 'domain', 'join',
Expand All @@ -496,13 +528,15 @@ def main():
nameserver = join_nameserver

finalize = False
# some initial set up required when joining
update_resolvconf(realm.lower(), nameserver)
update_hosts(None, HOSTNAME, domain)

update_resolvconf(realm.lower(), nameserver, interactive)
print('hostname', hostname, 'realm', realm)
update_hosts('127.0.1.1', hostname, realm)
if ip:
update_hosts(ip, HOSTNAME, domain)
update_hosts(ip, hostname, realm)

for samba_command in commands:
print('Running command: {}'.format(' '.join(samba_command)))
if krb_pass:
samba_run_code, samba_run_out = run_command(samba_command,
stdin=krb_pass)
Expand All @@ -511,15 +545,15 @@ def main():
samba_run_code, samba_run_out = run_command(samba_command)
if samba_run_code != 0:
os.makedirs(os.path.dirname(COMMAND_LOG), exist_ok=True)
with open(COMMAND_LOG, 'w') as fob:
with open(COMMAND_LOG, 'a') as fob:
fob.write("Command: {}\n\n".format(
" ".join(samba_command)))
fob.write("\n")
fob.write("{}\n".format(samba_run_out))

if interactive:
d = Dialog('Turnkey Linux - First boot configuration')
# handle incorrect Admin pass
# handle incorrect details
lines_to_print = []
end = False
for line in samba_run_out.split('\n'):
Expand All @@ -529,6 +563,7 @@ def main():
elif line.startswith('Failed to connect'):
lines_to_print.append(
line.split("-", 1)[:1][0])

elif line.startswith('ERROR'):
lines_to_print.append(
"-".join(line.split("-", 2)[:2]))
Expand Down Expand Up @@ -569,7 +604,18 @@ def main():
msg = "\nPlease ensure that you have set a static IP. If you" \
" haven't already, please ensure that you do that ASAP," \
" and update IP addresses in DNS and hosts file (please" \
" see docs for more info)."
" see docs for more info).\n"

if create:
msg = msg + \
"\nWhen adding clients, you'll need this info:\n" \
" nameserver: {}\n" \
" * - set client to use this nameserver first!\n" \
" AD DNS domain: {}\n" \
" AD admin account name: {}\n" \
" AD admin user password: (what you set)\n" \
"".format(nameserver, realm.lower(), ADMIN_USER)

if interactive:
d = Dialog('Turnkey Linux - First boot configuration')
d.infobox(msg)
Expand Down

0 comments on commit 5696021

Please sign in to comment.