1# -*- coding: utf-8 -*- 2# 3"""Bareos Client Configuration Listener Module.""" 4 5__package__ = '' # workaround for PEP 366 6import grp 7import os 8import stat 9import string 10from subprocess import Popen, PIPE, STDOUT 11import random 12 13from listener import setuid, unsetuid 14import univention.debug as ud 15 16name = 'bareos' 17description = 'Generate Bareos Configuration for Clients' 18filter = '(objectClass=bareosClientHost)' 19attributes = [] 20 21PATH_PREFIX = '/etc/bareos/autogenerated' 22JOBS_PATH = PATH_PREFIX + '/clients' 23INCLUDES_PATH = PATH_PREFIX + '/clients.include' 24BCONSOLE_CMD = ['/usr/bin/bconsole'] 25 26JOB_DISABLED = 'Not' 27 28bareos_gid = grp.getgrnam('bareos').gr_gid 29 30 31def getFqdn(entry): 32 if not entry.has_key('cn'): 33 return None 34 35 name = entry['cn'][0] 36 if entry.has_key('associatedDomain'): 37 name = name + '.' + entry['associatedDomain'][0] 38 39 return name 40 41 42def initialize(): 43 """Initialize the module once on first start or after clean.""" 44 ud.debug(ud.LISTENER, ud.INFO, 'BAREOS: Initialize') 45 46 47def handler(dn, new, old): 48 """Handle changes to 'dn'.""" 49 setuid(0) 50 try: 51 # if configRegistry['server/role'] != 'domaincontroller_master': 52 # return 53 54 # ud.debug(ud.LISTENER, ud.INFO, 'BAREOS: handler '+dn+' '+str(bareos_gid)) 55 56 if new and not old: 57 # changeType: add 58 name = getFqdn(new) 59 processClient(name, new) 60 61 elif old and not new: 62 # changeType: delete 63 try: 64 name = getFqdn(old) 65 processClient(name, old, delete=True) 66 except: 67 pass 68 else: 69 # changeType: modify 70 name = getFqdn(new) 71 processClient(name, new) 72 finally: 73 unsetuid() 74 75 76def clean(): 77 """Handle request to clean-up the module.""" 78 return 79 80 81def postrun(): 82 """Transition from prepared-state to not-prepared.""" 83 return 84 85 86def processClient(client_name, entry, delete=False): 87 if client_name is None: 88 return 89 90 client_type = 'generic' 91 if 'univentionWindows' in entry['objectClass']: 92 client_type = 'windows' 93 94 if delete == True: 95 removeClient(client_name, client_type) 96 return 97 98 if entry.has_key('bareosEnableJob'): 99 if entry['bareosEnableJob'][0] == JOB_DISABLED: 100 removeClient(client_name, client_type) 101 return 102 103 addClient(client_name, client_type) 104 105 106def addClient(client_name, client_type): 107 createClientJob(client_name, client_type) 108 addClientInclude(client_name) 109 exportBareosFdDirectorResource(client_name, client_type) 110 111 112def removeClient(client_name, client_type): 113 if client_name == None: 114 return 115 disableClientJob(client_name, client_type) 116 addClientInclude(client_name) 117 118 119def getClientSecret(client_name): 120 path = getClientSecretPath(client_name) 121 password = None 122 123 try: 124 f = open(path, 'r') 125 password = f.read().strip() 126 except: 127 password = createClientSecret(client_name) 128 129 return password 130 131 132def exportBareosFdDirectorResource(client_name, client_type): 133 # send commands via pipe to bconsole 134 process = Popen(BCONSOLE_CMD, stdin=PIPE, stdout=PIPE, stderr=STDOUT) 135 # additional reload required, to gurantee that client is known to director 136 out = process.communicate( 137 b'reload\nconfigure export client="{client_name}-fd"\n'.format( 138 client_name=client_name))[0] 139 ud.debug(ud.LISTENER, ud.INFO, "bareos export output:\n" + str(out)) 140 141 142def createClientSecret(client_name): 143 path = getClientSecretPath(client_name) 144 145 char_set = string.ascii_uppercase + string.digits + string.ascii_lowercase 146 password = ''.join(random.sample(char_set * 40, 40)) 147 with os.fdopen( 148 os.open(path, os.O_CREAT | os.O_WRONLY, 149 stat.S_IRUSR | stat.S_IWUSR), 'w') as f: 150 f.write(password) 151 os.chown(path, -1, 0) 152 153 return password 154 155 156def removeClientJob(client_name): 157 path = JOBS_PATH + '/' + client_name + '.include' 158 os.remove(path) 159 160 161def createClientJob(client_name, client_type, enable='Yes'): 162 password = getClientSecret(client_name) 163 path = JOBS_PATH + '/' + client_name + '.include' 164 templatefile = JOBS_PATH + '/' + client_type + '.template' 165 with open(templatefile, 'r') as f: 166 content = f.read() 167 168 t = string.Template(content) 169 with os.fdopen( 170 os.open(path, os.O_CREAT | os.O_WRONLY, 171 stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP), 'w') as f: 172 f.write( 173 t.substitute( 174 enable=enable, password=password, client_name=client_name)) 175 os.chown(path, -1, bareos_gid) 176 177 178def disableClientJob(client_name, client_type): 179 createClientJob(client_name, client_type, 'No') 180 181 182def getClientIncludePath(client_name): 183 return '@' + JOBS_PATH + '/' + client_name + '.include' 184 185 186def getClientSecretPath(client_name): 187 return JOBS_PATH + '/' + client_name + '.secret' 188 189 190def addClientInclude(client_name): 191 # is the client already in the include list? 192 if isClientIncluded(client_name): 193 # update the timestamp on the file 194 # to let the cron script know the configuration 195 # has changed 196 os.utime(INCLUDES_PATH, None) 197 return 198 199 # if not, add it at the end of the file 200 with open(INCLUDES_PATH, 'a') as f: 201 f.write(getClientIncludePath(client_name)) 202 f.write('\n') 203 204 205def isClientIncluded(client_name): 206 want = getClientIncludePath(client_name) 207 with open(INCLUDES_PATH, 'r') as f: 208 for l in f.readlines(): 209 if want in l: 210 return True 211 return False 212