diff options
-rw-r--r-- | ipa-admintools/ipa-addradiusclient | 195 | ||||
-rw-r--r-- | ipa-admintools/ipa-findradiusclient | 15 | ||||
-rw-r--r-- | ipa-admintools/ipa-radiusclientmod | 92 | ||||
-rw-r--r-- | ipa-python/ipaclient.py | 16 | ||||
-rw-r--r-- | ipa-python/ipautil.py | 330 | ||||
-rw-r--r-- | ipa-python/radius_client.py | 39 | ||||
-rw-r--r-- | ipa-python/radius_util.py | 231 | ||||
-rw-r--r-- | ipa-python/rpcclient.py | 24 | ||||
-rw-r--r-- | ipa-server/ipa-install/share/60radius.ldif | 12 | ||||
-rw-r--r-- | ipa-server/ipa-install/share/bootstrap-template.ldif | 4 | ||||
-rw-r--r-- | ipa-server/ipaserver/radiusinstance.py | 67 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/funcs.py | 155 | ||||
-rw-r--r-- | ipa-server/xmlrpc-server/ipaxmlrpc.py | 5 |
13 files changed, 950 insertions, 235 deletions
diff --git a/ipa-admintools/ipa-addradiusclient b/ipa-admintools/ipa-addradiusclient index 55926214..b5d829ac 100644 --- a/ipa-admintools/ipa-addradiusclient +++ b/ipa-admintools/ipa-addradiusclient @@ -19,13 +19,16 @@ # import sys +import os from optparse import OptionParser -import ipa +import copy + from ipa.radius_client import * import ipa.ipaclient as ipaclient -import ipa.ipavalidate as ipavalidate +import ipa.ipautil as ipautil import ipa.config import ipa.ipaerror +import ipa.radius_util as radius_util import xmlrpclib import kerberos @@ -33,97 +36,127 @@ import ldap #------------------------------------------------------------------------------ -def parse_options(): - parser = OptionParser() - parser.add_option("--usage", action="store_true", - help="Program usage") - parser.add_option("-a", "--address", dest="ip_addr", - help="RADIUS client IP address (required)") - parser.add_option("-s", "--secret", dest="secret", - help="RADIUS client secret (required)") - parser.add_option("-n", "--name", dest="name", +attrs = radius_util.client_name_to_ldap_attr.keys() +mandatory_attrs = ['Client-IP-Address', 'Secret'] + +#------------------------------------------------------------------------------ + +def help_option_callback(option, opt_str, value, parser, *args, **kwargs): + parser.print_help() + print + print "Valid interative attributes are:" + print ipautil.format_list(attrs, quote='"') + print + print "Required attributes are:" + print ipautil.format_list(mandatory_attrs, quote='"') + sys.exit(0) + +def main(): + pairs = {} + + opt_parser = OptionParser(add_help_option=False) + + opt_parser.add_option("-a", "--Client-IP-Address", dest="ip_addr", + help="RADIUS client ip address") + opt_parser.add_option("-s", "--Secret", dest="secret", + help="RADIUS client ip address") + opt_parser.add_option("-n", "--Name", dest="name", help="RADIUS client name") - parser.add_option("-t", "--type", dest="nastype", + opt_parser.add_option("-t", "--NAS-Type", dest="nastype", help="RADIUS client NAS Type") - parser.add_option("-d", "--description", dest="desc", + opt_parser.add_option("-d", "--Description", dest="desc", help="description of the RADIUS client") - args = ipa.config.init_config(sys.argv) - options, args = parser.parse_args(args) + opt_parser.add_option("-h", "--help", action="callback", callback=help_option_callback, + help="detailed help information") + opt_parser.add_option("-i", "--interactive", dest="interactive", action='store_true', default=False, + help="interactive mode, prompts with auto-completion") + opt_parser.add_option("-p", "--pair", dest="pairs", action='append', + help="specify one or more attribute=value pair(s), value may be optionally quoted, pairs are delimited by whitespace") + opt_parser.add_option("-f", "--file", dest="pair_file", + help="attribute=value pair(s) are read from file, value may be optionally quoted, pairs are delimited by whitespace. Reads from stdin if file is -") + opt_parser.add_option("-v", "--verbose", dest="verbose", action='store_true', + help="print information") - return options, args + #opt_parser.set_usage("Usage: %s [options] %s" % (os.path.basename(sys.argv[0]), ' '.join(mandatory_attrs))) -#------------------------------------------------------------------------------ - -def main(): - ip_addr = None - secret = None - name = None - nastype = None - desc = None + args = ipa.config.init_config(sys.argv) + options, args = opt_parser.parse_args(args) + + # Get pairs from a file or stdin + if options.pair_file: + try: + av = radius_util.read_pairs_file(options.pair_file) + pairs.update(av) + except Exception, e: + print "ERROR, could not read pairs (%s)" % (e) + + # Get pairs specified on the command line as a named argument + if options.ip_addr: pairs['Client-IP-Address'] = options.ip_addr + if options.secret: pairs['Secret'] = options.secret + if options.name: pairs['Name'] = options.name + if options.nastype: pairs['NAS-Type'] = options.nastype + if options.desc: pairs['Description'] = options.desc + + # Get pairs specified on the command line as a pair argument + if options.pairs: + for p in options.pairs: + av = ipautil.parse_key_value_pairs(p) + pairs.update(av) + + # Get pairs interactively + if options.interactive: + # Remove any mandatory attriubtes which have been previously specified + interactive_mandatory_attrs = copy.copy(mandatory_attrs) + for attr in pairs.keys(): + try: + interactive_mandatory_attrs.remove(attr) + except ValueError: + pass + c = ipautil.AttributeValueCompleter(attrs, pairs) + c.open() + av = c.get_pairs("Enter: ", interactive_mandatory_attrs, validate) + pairs.update(av) + c.close() + + # Data collection done, assure mandatory data has been specified + valid = True + for attr in mandatory_attrs: + if not pairs.has_key(attr): + valid = False + print "ERROR, %s is mandatory, but has not been specified" % (attr) + if not valid: + return 1 - radius_client = ipa.radius_client.RadiusClient() - options, args = parse_options() - - # client address is required - if options.ip_addr: - ip_addr = options.ip_addr - if not validate_ip_addr(ip_addr): return 1 - else: - valid = False - while not valid: - ip_addr = raw_input("Client IP: ") - if validate_ip_addr(ip_addr): valid = True - - # client secret is required - if options.secret: - secret = options.secret - if not validate_secret(secret): return 1 - else: - valid = False - while not valid: - secret = get_secret() - if validate_secret(secret): valid = True - - # client name is optional - if options.name: - name = options.name - if not validate_name(name): return 1 - - # client NAS Type is optional - if options.nastype: - nastype = options.nastype - if not validate_nastype(nastype): return 1 - - # client description is optional - if options.desc: - desc = options.desc - if not validate_desc(desc): return 1 - - - #print "ip_addr=%s secret=%s name=%s nastype=%s desc=%s" % (ip_addr, secret, name, nastype, desc) - - if ip_addr is not None: - radius_client.setValue('radiusClientNASIpAddress', ip_addr) - else: - print "client IP Address is required" + # Make sure each attribute is a member of the set of valid attributes + valid = True + for attr,value in pairs.items(): + if attr not in attrs: + valid = False + print "ERROR, %s is not a valid attribute" % (attr) + if not valid: + print "Valid attributes are:" + print ipautil.format_list(attrs, quote='"') return 1 - if secret is not None: - radius_client.setValue('radiusClientSecret', secret) - else: - print "client secret is required" + # Makse sure each value is valid + valid = True + for attr,value in pairs.items(): + if not validate(attr, value): + valid = False + if not valid: return 1 - if name is not None: - radius_client.setValue('radiusClientShortName', name) + # Dump what we've got so far + if options.verbose: + print "Pairs:" + for attr,value in pairs.items(): + print "\t%s = %s" % (attr, value) + + radius_client = ipa.radius_client.RadiusClient() + for attr,value in pairs.items(): + radius_client.setValue(radius_util.client_name_to_ldap_attr[attr], value) - if nastype is not None: - radius_client.setValue('radiusClientNASType', nastype) - - if desc is not None: - radius_client.setValue('description', desc) - try: ipa_client = ipaclient.IPAClient() ipa_client.add_radius_client(radius_client) diff --git a/ipa-admintools/ipa-findradiusclient b/ipa-admintools/ipa-findradiusclient index 63d51007..a922c6ea 100644 --- a/ipa-admintools/ipa-findradiusclient +++ b/ipa-admintools/ipa-findradiusclient @@ -22,6 +22,7 @@ import sys from optparse import OptionParser import ipa from ipa.radius_client import * +from ipa import radius_util import ipa.ipaclient as ipaclient import ipa.ipavalidate as ipavalidate import ipa.config @@ -45,21 +46,13 @@ def parse_options(): #------------------------------------------------------------------------------ -attr_to_name = ipa.ipautil.CIDict({ - 'radiusClientNASIpAddress' : 'IP Address', - 'radiusClientSecret' : 'Secret', - 'radiusClientNASType' : 'NAS Type', - 'radiusClientShortName' : 'Name', - 'description' : 'Description', - }) - # FIXME def usage(): print "ipa-findradiusclients ip_addr [ip_addr ...]" sys.exit(1) def main(): - attrs=['radiusClientNASIpAddress', 'radiusClientSecret', 'radiusClientNASType', 'radiusClientShortName', 'description'] + attrs=['radiusClientIPAddress', 'radiusClientSecret', 'radiusClientNASType', 'radiusClientShortName', 'description'] options, args = parse_options() @@ -82,10 +75,10 @@ def main(): attrs = radius_client.attrList() attrs.sort() - print "%s:" % radius_client.getValues('radiusClientNASIpAddress') + print "%s:" % radius_client.getValues('radiusClientIPAddress') for attr in attrs: value = radius_client.getValues(attr) - print "\t%s = %s" % (attr_to_name[attr], value) + print "\t%s = %s" % (radius_util.client_ldap_attr_to_name[attr], value) except xmlrpclib.Fault, f: print f.faultString diff --git a/ipa-admintools/ipa-radiusclientmod b/ipa-admintools/ipa-radiusclientmod index 3f40b7b7..9f5d8d75 100644 --- a/ipa-admintools/ipa-radiusclientmod +++ b/ipa-admintools/ipa-radiusclientmod @@ -19,13 +19,14 @@ # import sys +import os from optparse import OptionParser -import ipa from ipa.radius_client import * import ipa.ipaclient as ipaclient -import ipa.ipavalidate as ipavalidate +import ipa.ipautil as ipautil import ipa.config import ipa.ipaerror +import ipa.radius_util as radius_util import xmlrpclib import kerberos @@ -33,49 +34,76 @@ import ldap #------------------------------------------------------------------------------ -def parse_options(): - parser = OptionParser() - parser.add_option("--usage", action="store_true", - help="Program usage") - parser.add_option("-s", "--secret", dest="secret", - help="RADIUS client secret (required)") - parser.add_option("-n", "--name", dest="name", - help="RADIUS client name") - parser.add_option("-t", "--type", dest="nastype", - help="RADIUS client NAS Type") - parser.add_option("-d", "--description", dest="desc", - help="description of the RADIUS client") +attrs = radius_util.client_name_to_ldap_attr.keys() +mandatory_attrs = ['Client-IP-Address'] - args = ipa.config.init_config(sys.argv) - options, args = parser.parse_args(args) +#------------------------------------------------------------------------------ - return options, args +def help_option_callback(option, opt_str, value, parser, *args, **kwargs): + parser.print_help() + print + print "Valid interative attributes are:" + print ipautil.format_list(attrs, quote='"') + print + print "Required attributes are:" + print ipautil.format_list(mandatory_attrs, quote='"') + sys.exit(0) #------------------------------------------------------------------------------ -# FIXME -def usage(): - print "ipa-radiusclientmod ip_addr" - sys.exit(1) - def main(): - ip_addr = None - secret = None - name = None - nastype = None - desc = None + opt_parser = OptionParser(add_help_option=False) + opt_parser.add_option("-h", "--help", action="callback", callback=help_option_callback, + help="detailed help information") + opt_parser.add_option("-i", "--interactive", dest="interactive", action='store_true', default=False, + help="interactive mode, prompts with auto-completion") + opt_parser.add_option("-n", "--name", dest="name", + help="RADIUS client name") + opt_parser.add_option("-t", "--type", dest="nastype", + help="RADIUS client NAS Type") + opt_parser.add_option("-d", "--description", dest="desc", + help="description of the RADIUS client") - options, args = parse_options() + #FIXME interactive vs. non-interactive usage + opt_parser.set_usage("Usage: %s [options] %s" % (os.path.basename(sys.argv[0]), ' '.join(mandatory_attrs))) + #FIXME, map options name to our name? + #FIXME if mandatory is on command line remove it from mandatory passed to completer - if len(args) != 2: - usage() + args = ipa.config.init_config(sys.argv) + options, args = opt_parser.parse_args(args) + + if options.interactive: + c = ipautil.AttributeValueCompleter(attrs) + c.open() + pairs = c.get_pairs("Enter: ", mandatory_attrs, validate) + c.close() + else: + pairs = {} + + if False and len(args) != 2: + print "wrong number of arguments" + opt_parser.print_help() + sys.exit(1) + + pairs['Client-IP-Address'] = args[1] + pairs['Secret'] = args[2] + if options.name: pairs['Name'] = options.name + if options.nastype: pairs['NAS-Type'] = options.nastype + if options.desc: pairs['Description'] = options.desc + + for name,value in pairs.items(): + if not validate(name, value): return 1 ip_addr = args[1] + radius_client = ipa.radius_client.RadiusClient() ipa_client = ipaclient.IPAClient() try: - radius_client = ipa_client.get_radius_client_by_ip_addr(ip_addr) + #radius_client = ipa_client.get_radius_client_by_ip_addr(ip_addr) + dn = radius_util.radius_client_dn(ip_addr, 'dc=ipatest,dc=jrd') + print dn + radius_client = ipa_client.get_entry_by_dn(dn) pass except ipa.ipaerror.exception_for(ipa.ipaerror.LDAP_NOT_FOUND): print "client %s not found" % ip_addr @@ -87,6 +115,8 @@ def main(): print "Could not initialize GSSAPI: %s/%s" % (e[0][0][0], e[0][1][0]) return 1 + sys.exit(0) + if options.secret: secret = options.secret if not validate_secret(secret): return 1 diff --git a/ipa-python/ipaclient.py b/ipa-python/ipaclient.py index f487bec2..e9b0002f 100644 --- a/ipa-python/ipaclient.py +++ b/ipa-python/ipaclient.py @@ -334,29 +334,29 @@ class IPAClient: return entries # radius support - def get_radius_client_by_ip_addr(self,ip_addr,sattrs=None): - result = self.transport.get_radius_client_by_ip_addr(ip_addr,sattrs) + def get_radius_client_by_ip_addr(self, ip_addr, container=None, sattrs=None): + result = self.transport.get_radius_client_by_ip_addr(ip_addr, container, sattrs) return radius_client.RadiusClient(result) - def add_radius_client(self,client): + def add_radius_client(self,client, container=None): client_dict = client.toDict() # dn is set on the server-side del client_dict['dn'] # convert to a regular dict before sending - result = self.transport.add_radius_client(client_dict) + result = self.transport.add_radius_client(client_dict, container) return result def update_radius_client(self,client): result = self.transport.update_radius_client(client.origDataDict(), client.toDict()) return result - def delete_radius_client(self,ip_addr): - return self.transport.delete_radius_client(ip_addr) + def delete_radius_client(self, ip_addr, container=None): + return self.transport.delete_radius_client(ip_addr, container) - def find_radius_clients(self, criteria, sattrs=None, searchlimit=0, timelimit=-1): - result = self.transport.find_radius_clients(criteria, sattrs, searchlimit, timelimit) + def find_radius_clients(self, criteria, container=None, sattrs=None, searchlimit=0, timelimit=-1): + result = self.transport.find_radius_clients(criteria, container, sattrs, searchlimit, timelimit) counter = result[0] users = [counter] diff --git a/ipa-python/ipautil.py b/ipa-python/ipautil.py index e7f59419..407406de 100644 --- a/ipa-python/ipautil.py +++ b/ipa-python/ipautil.py @@ -25,6 +25,10 @@ import logging import subprocess import os import stat +import copy +import readline +import traceback +from types import * from string import lower import re @@ -331,3 +335,329 @@ def parse_generalized_time(timestr): except ValueError: return None + +def format_list(items, quote=None, page_width=80): + '''Format a list of items formatting them so they wrap to fit the + available width. The items will be sorted. + + The items may optionally be quoted. The quote parameter may either be + a string, in which case it is added before and after the item. Or the + quote parameter may be a pair (either a tuple or list). In this case + quote[0] is left hand quote and quote[1] is the right hand quote. + ''' + left_quote = right_quote = '' + num_items = len(items) + if not num_items: return text + + if quote is not None: + if type(quote) in StringTypes: + left_quote = right_quote = quote + elif type(quote) is TupleType or type(quote) is ListType: + left_quote = quote[0] + right_quote = quote[1] + + max_len = max(map(len, items)) + max_len += len(left_quote) + len(right_quote) + num_columns = (page_width + max_len) / (max_len+1) + num_rows = (num_items + num_columns - 1) / num_columns + items.sort() + + rows = [''] * num_rows + i = row = col = 0 + + while i < num_items: + row = 0 + if col == 0: + separator = '' + else: + separator = ' ' + + while i < num_items and row < num_rows: + rows[row] += "%s%*s" % (separator, -max_len, "%s%s%s" % (left_quote, items[i], right_quote)) + i += 1 + row += 1 + col += 1 + return '\n'.join(rows) + +key_value_re = re.compile("([^\s=]+)\s*=\s*((\S+)|(?P<quote>['\\\"])((?P=quote)|(.*?[^\\\])(?P=quote)))") +def parse_key_value_pairs(input): + ''' Given a string composed of key=value pairs parse it and return + a dict of the key/value pairs. Keys must be a word, a key must be followed + by an equal sign (=) and a value. The value may be a single word or may be + quoted. Quotes may be either single or double quotes, but must be balanced. + Inside the quoted text the same quote used to start the quoted value may be + used if it is escaped by preceding it with a backslash (\). + White space between the key, the equal sign, and the value is ignored. + Values are always strings. Empty values must be specified with an empty + quoted string, it's value after parsing will be an empty string. + + Example: The string + + arg0 = '' arg1 = 1 arg2='two' arg3 = "three's a crowd" arg4 = "this is a \" quote" + + will produce + + arg0= arg1=1 + arg2=two + arg3=three's a crowd + arg4=this is a " quote + ''' + + kv_dict = {} + for match in key_value_re.finditer(input): + key = match.group(1) + quote = match.group('quote') + if match.group(5): + value = match.group(6) + if value is None: value = '' + value = re.sub('\\\%s' % quote, quote, value) + else: + value = match.group(2) + kv_dict[key] = value + return kv_dict + +class AttributeValueCompleter: + ''' + Gets input from the user in the form "lhs operator rhs" + TAB completes partial input. + lhs completes to a name in @lhs_names + The lhs is fully parsed if a lhs_delim delimiter is seen, then TAB will + complete to the operator and a default value. + Default values for a lhs value can specified as: + - a string, all lhs values will use this default + - a dict, the lhs value is looked up in the dict to return the default or None + - a function with a single arg, the lhs value, it returns the default or None + + After creating the completer you must open it to set the terminal + up, Then get a line of input from the user by calling read_input() + which returns two values, the lhs and rhs, which might be None if + lhs or rhs was not parsed. After you are done getting input you + should close the completer to restore the terminal. + + Example: (note this is essentially what the convenience function get_pairs() does) + + This will allow the user to autocomplete foo & foobar, both have + defaults defined in a dict. In addition the foobar attribute must + be specified before the prompting loop will exit. Also, this + example show how to require that each attrbute entered by the user + is valid. + + attrs = ['foo', 'foobar'] + defaults = {'foo' : 'foo_default', 'foobar' : 'foobar_default'} + mandatory_attrs = ['foobar'] + + c = AttributeValueCompleter(attrs, defaults) + c.open() + mandatory_attrs_remaining = copy.copy(mandatory_attrs) + + while True: + if mandatory_attrs_remaining: + attribute, value = c.read_input("Enter: ", mandatory_attrs_remaining[0]) + try: + mandatory_attrs_remaining.remove(attribute) + except ValueError: + pass + else: + attribute, value = c.read_input("Enter: ") + if attribute is None: + # Are we done? + if mandatory_attrs_remaining: + print "ERROR, you must specify: %s" % (','.join(mandatory_attrs_remaining)) + continue + else: + break + if attribute not in attrs: + print "ERROR: %s is not a valid attribute" % (attribute) + else: + print "got '%s' = '%s'" % (attribute, value) + + c.close() + print "exiting..." + ''' + + def __init__(self, lhs_names, default_value=None, lhs_regexp=r'^\s*(?P<lhs>[^ =]+)', lhs_delims=' =', + operator='=', strip_rhs=True): + self.lhs_names = lhs_names + self.default_value = default_value + # lhs_regexp must have named group 'lhs' which returns the contents of the lhs + self.lhs_regexp = lhs_regexp + self.lhs_re = re.compile(self.lhs_regexp) + self.lhs_delims = lhs_delims + self.operator = operator + self.strip_rhs = strip_rhs + self._reset() + + def _reset(self): + self.lhs = None + self.lhs_complete = False + self.operator_complete = False + self.rhs = None + + def open(self): + # Save state + self.prev_completer = readline.get_completer() + self.prev_completer_delims = readline.get_completer_delims() + + # Set up for ourself + readline.parse_and_bind("tab: complete") + readline.set_completer(self.complete) + readline.set_completer_delims(self.lhs_delims) + + def close(self): + # Restore previous state + readline.set_completer_delims(self.prev_completer_delims) + readline.set_completer(self.prev_completer) + + def _debug(self): + print >> output_fd, "lhs='%s' lhs_complete=%s operator='%s' operator_complete=%s rhs='%s'" % \ + (self.lhs, self.lhs_complete, self.operator, self.operator_complete, self.rhs) + + + def parse_input(self): + '''We are looking for 3 tokens: <lhs,op,rhs> + Extract as much of each token as possible. + Set flags indicating if token is fully parsed. + ''' + try: + self._reset() + buf_len = len(self.line_buffer) + pos = 0 + lhs_match = self.lhs_re.search(self.line_buffer, pos) + if not lhs_match: return # no lhs content + self.lhs = lhs_match.group('lhs') # get lhs contents + pos = lhs_match.end('lhs') # new scanning position + if pos == buf_len: return # nothing after lhs, lhs incomplete + self.lhs_complete = True # something trails the lhs, lhs is complete + operator_beg = self.line_buffer.find(self.operator, pos) # locate operator + if operator_beg == -1: return # did not find the operator + self.operator_complete = True # operator fully parsed + operator_end = operator_beg + len(self.operator) + pos = operator_end # step over the operator + self.rhs = self.line_buffer[pos:] + except Exception, e: + traceback.print_exc() + print "Exception in %s.parse_input(): %s" % (self.__class__.__name__, e) + + def get_default_value(self): + '''default_value can be a string, a dict, or a function. + If it's a string it's a global default for all attributes. + If it's a dict the default is looked up in the dict index by attribute. + If it's a function, the function is called with 1 parameter, the attribute + and it should return the default value for the attriubte or None''' + + if not self.lhs_complete: raise ValueError("attribute not parsed") + default_value_type = type(self.default_value) + if default_value_type is DictType: + return self.default_value.get(self.lhs, None) + elif default_value_type is FunctionType: + return self.default_value(self.lhs) + elif default_value_type is StringsType: + return self.default_value + else: + return None + + def get_lhs_completions(self, text): + if text: + self.completions = [lhs for lhs in self.lhs_names if lhs.startswith(text)] + else: + self.completions = self.lhs_names + + def complete(self, text, state): + self.line_buffer= readline.get_line_buffer() + self.parse_input() + if not self.lhs_complete: + # lhs is not complete, set up to complete the lhs + if state == 0: + beg = readline.get_begidx() + end = readline.get_endidx() + self.get_lhs_completions(self.line_buffer[beg:end]) + if state >= len(self.completions): return None + return self.completions[state] + + + elif not self.operator_complete: + # lhs is complete, but the operator is not so we complete + # by inserting the operator manually. + # Also try to complete the default value at this time. + readline.insert_text('%s ' % self.operator) + default_value = self.get_default_value() + if default_value is not None: + readline.insert_text(default_value) + readline.redisplay() + return None + else: + # lhs and operator are complete, if the the rhs is blank + # (either empty or only only whitespace) then attempt + # to complete by inserting the default value, otherwise + # there is nothing we can complete to so we're done. + if self.rhs.strip(): + return None + default_value = self.get_default_value() + if default_value is not None: + readline.insert_text(default_value) + readline.redisplay() + return None + + def pre_input_hook(self): + readline.insert_text('%s %s ' % (self.initial_lhs, self.operator)) + readline.redisplay() + + def read_input(self, prompt, initial_lhs=None): + self.initial_lhs = initial_lhs + try: + self._reset() + if initial_lhs is None: + readline.set_pre_input_hook(None) + else: + readline.set_pre_input_hook(self.pre_input_hook) + self.line_buffer = raw_input(prompt).strip() + self.parse_input() + if self.strip_rhs and self.rhs is not None: + return self.lhs, self.rhs.strip() + else: + return self.lhs, self.rhs + except EOFError: + return None, None + + def get_pairs(self, prompt, mandatory_attrs=None, validate_callback=None, must_match=True, value_required=True): + pairs = {} + if mandatory_attrs: + mandatory_attrs_remaining = copy.copy(mandatory_attrs) + else: + mandatory_attrs_remaining = [] + + print "Enter name = value" + print "Press <ENTER> to accept, a blank line terminates input" + print "Pressing <TAB> will auto completes name, assignment, and value" + print + while True: + if mandatory_attrs_remaining: + attribute, value = self.read_input(prompt, mandatory_attrs_remaining[0]) + else: + attribute, value = self.read_input(prompt) + if attribute is None: + # Are we done? + if mandatory_attrs_remaining: + print "ERROR, you must specify: %s" % (','.join(mandatory_attrs_remaining)) + continue + else: + break + if value is None: + if value_required: + print "ERROR: you must specify a value for %s" % attribute + continue + else: + if must_match and attribute not in self.lhs_names: + print "ERROR: %s is not a valid name" % (attribute) + continue + if validate_callback is not None: + if not validate_callback(attribute, value): + print "ERROR: %s is not valid for %s" % (value, attribute) + continue + try: + mandatory_attrs_remaining.remove(attribute) + except ValueError: + pass + + pairs[attribute] = value + return pairs diff --git a/ipa-python/radius_client.py b/ipa-python/radius_client.py index 2709c4d9..907e0210 100644 --- a/ipa-python/radius_client.py +++ b/ipa-python/radius_client.py @@ -21,6 +21,7 @@ import getpass import re from ipa.entity import Entity +import ipa.ipavalidate as ipavalidate __all__ = ['RadiusClient', 'get_secret', @@ -29,6 +30,7 @@ __all__ = ['RadiusClient', 'validate_name', 'validate_nastype', 'validate_desc', + 'validate', ] #------------------------------------------------------------------------------ @@ -41,7 +43,10 @@ valid_name_len = (1,31) valid_nastype_len = (1,31) valid_ip_addr_len = (1,255) -valid_ip_addr_msg = "IP address must be either a DNS name (letters,digits,dot,hyphen, beginning with a letter),or a dotted octet followed by an optional mask (e.g 192.168.1.0/24)" +valid_ip_addr_msg = '''\ +IP address must be either a DNS name (letters,digits,dot,hyphen, beginning with +a letter),or a dotted octet followed by an optional mask (e.g 192.168.1.0/24)''' + valid_desc_msg = "Description must text string" #------------------------------------------------------------------------------ @@ -101,38 +106,60 @@ def validate_length(value, limits): def valid_length_msg(name, limits): return "%s length must be at least %d and not more than %d" % (name, limits[0], limits[1]) +def err_msg(variable, variable_name=None): + if variable_name is None: variable_name = 'value' + print "ERROR: %s = %s" % (variable_name, variable) + #------------------------------------------------------------------------------ -def validate_ip_addr(ip_addr): +def validate_ip_addr(ip_addr, variable_name=None): if not validate_length(ip_addr, valid_ip_addr_len): + err_msg(ip_addr, variable_name) print valid_length_msg('ip address', valid_ip_addr_len) return False if not valid_ip_addr(ip_addr): + err_msg(ip_addr, variable_name) print valid_ip_addr_msg return False return True -def validate_secret(secret): +def validate_secret(secret, variable_name=None): if not validate_length(secret, valid_secret_len): + err_msg(secret, variable_name) print valid_length_msg('secret', valid_secret_len) return False return True -def validate_name(name): +def validate_name(name, variable_name=None): if not validate_length(name, valid_name_len): + err_msg(name, variable_name) print valid_length_msg('name', valid_name_len) return False return True -def validate_nastype(nastype): +def validate_nastype(nastype, variable_name=None): if not validate_length(nastype, valid_nastype_len): + err_msg(nastype, variable_name) print valid_length_msg('NAS Type', valid_nastype_len) return False return True -def validate_desc(desc): +def validate_desc(desc, variable_name=None): if ipavalidate.plain(desc, notEmpty=True) != 0: print valid_desc_msg return False return True +def validate(attribute, value): + if attribute == 'Client-IP-Address': + return validate_ip_addr(value, attribute) + if attribute == 'Secret': + return validate_secret(value, attribute) + if attribute == 'NAS-Type': + return validate_nastype(value, attribute) + if attribute == 'Name': + return validate_name(value, attribute) + if attribute == 'Description': + return validate_desc(value, attribute) + return True + diff --git a/ipa-python/radius_util.py b/ipa-python/radius_util.py new file mode 100644 index 00000000..482b9f19 --- /dev/null +++ b/ipa-python/radius_util.py @@ -0,0 +1,231 @@ +# Authors: John Dennis <jdennis@redhat.com> +# +# Copyright (C) 2007 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License as +# published by the Free Software Foundation; version 2 only +# +# This program is distributed 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +# + +import sys +import os +import re +import ldap +import ldap.filter + +from ipa import ipautil + + +__all__ = [ + 'RADIUS_PKG_NAME', + 'RADIUS_PKG_CONFIG_DIR', + 'RADIUS_SERVICE_NAME', + 'RADIUS_USER', + 'RADIUS_IPA_KEYTAB_FILEPATH', + 'RADIUS_LDAP_ATTR_MAP_FILEPATH', + 'RADIUSD_CONF_FILEPATH', + 'RADIUSD_CONF_TEMPLATE_FILEPATH', + 'RADIUSD', + + 'clients_container', + 'radius_clients_basedn', + 'radius_client_filter', + 'radius_client_dn', + + 'profiles_container', + 'radius_profiles_basedn', + 'radius_profile_filter', + 'radius_profile_dn', + + 'client_ldap_attr_to_name', + 'client_name_to_ldap_attr', + + 'read_pairs_file', +] + +#------------------------------------------------------------------------------ + +RADIUS_PKG_NAME = 'freeradius' +RADIUS_PKG_CONFIG_DIR = '/etc/raddb' + +RADIUS_SERVICE_NAME = 'radius' +RADIUS_USER = 'radiusd' + +RADIUS_IPA_KEYTAB_FILEPATH = os.path.join(RADIUS_PKG_CONFIG_DIR, 'ipa.keytab') +RADIUS_LDAP_ATTR_MAP_FILEPATH = os.path.join(RADIUS_PKG_CONFIG_DIR, 'ldap.attrmap') +RADIUSD_CONF_FILEPATH = os.path.join(RADIUS_PKG_CONFIG_DIR, 'radiusd.conf') +RADIUSD_CONF_TEMPLATE_FILEPATH = os.path.join(ipautil.SHARE_DIR, 'radius.radiusd.conf.template') + +RADIUSD = '/usr/sbin/radiusd' + +#------------------------------------------------------------------------------ + +def reverse_map_dict(src_dict): + reverse_dict = {} + + for k,v in src_dict.items(): + if reverse_dict.has_key(v): + raise ValueError("reverse_map_dict: collision on (%s) with values (%s),(%s)" % \ + v, reverse_dict[v], src_dict[k]) + reverse_dict[v] = k + return reverse_dict + +#------------------------------------------------------------------------------ + +client_ldap_attr_to_name = ipautil.CIDict({ + 'radiusClientIPAddress' : 'Client-IP-Address', + 'radiusClientSecret' : 'Secret', + 'radiusClientNASType' : 'NAS-Type', + 'radiusClientShortName' : 'Name', + 'description' : 'Description', + }) + +client_name_to_ldap_attr = reverse_map_dict(client_ldap_attr_to_name) + +#------------------------------------------------------------------------------ + +profile_ldap_attr_to_name = { + 'radiusArapFeatures' : 'Arap-Features', + 'radiusArapSecurity' : 'Arap-Security', + 'radiusArapZoneAccess' : 'Arap-Zone-Access', + 'radiusAuthType' : 'Auth-Type', + 'radiusCallbackId' : 'Callback-Id', + 'radiusCallbackNumber' : 'Callback-Number', + 'radiusCalledStationId' : 'Called-Station-Id', + 'radiusCallingStationId' : 'Calling-Station-Id', + 'radiusClass' : 'Class', + 'radiusClientIPAddress' : 'Client-IP-Address', + 'radiusExpiration' : 'Expiration', + 'radiusFilterId' : 'Filter-Id', + 'radiusFramedAppleTalkLink' : 'Framed-AppleTalk-Link', + 'radiusFramedAppleTalkNetwork' : 'Framed-AppleTalk-Network', + 'radiusFramedAppleTalkZone' : 'Framed-AppleTalk-Zone', + 'radiusFramedCompression' : 'Framed-Compression', + 'radiusFramedIPAddress' : 'Framed-IP-Address', + 'radiusFramedIPNetmask' : 'Framed-IP-Netmask', + 'radiusFramedIPXNetwork' : 'Framed-IPX-Network', + 'radiusFramedMTU' : 'Framed-MTU', + 'radiusFramedProtocol' : 'Framed-Protocol', + 'radiusFramedRoute' : 'Framed-Route', + 'radiusFramedRouting' : 'Framed-Routing', + 'radiusGroupName' : 'Group-Name', + 'radiusHint' : 'Hint', + 'radiusHuntgroupName' : 'Huntgroup-Name', + 'radiusIdleTimeout' : 'Idle-Timeout', + 'radiusLoginIPHost' : 'Login-IP-Host', + 'radiusLoginLATGroup' : 'Login-LAT-Group', + 'radiusLoginLATNode' : 'Login-LAT-Node', + 'radiusLoginLATPort' : 'Login-LAT-Port', + 'radiusLoginLATService' : 'Login-LAT-Service', + 'radiusLoginService' : 'Login-Service', + 'radiusLoginTCPPort' : 'Login-TCP-Port', + 'radiusLoginTime' : 'Login-Time', + 'radiusNASIpAddress' : 'NAS-IP-Address', + 'radiusPasswordRetry' : 'Password-Retry', + 'radiusPortLimit' : 'Port-Limit', + 'radiusProfileDn' : 'Profile-Dn', + 'radiusPrompt' : 'Prompt', + 'radiusProxyToRealm' : 'Proxy-To-Realm', + 'radiusRealm' : 'Realm', + 'radiusReplicateToRealm' : 'Replicate-To-Realm', + 'radiusReplyMessage' : 'Reply-Message', + 'radiusServiceType' : 'Service-Type', + 'radiusSessionTimeout' : 'Session-Timeout', + 'radiusSimultaneousUse' : 'Simultaneous-Use', + 'radiusStripUserName' : 'Strip-User-Name', + 'radiusTerminationAction' : 'Termination-Action', + 'radiusTunnelAssignmentId' : 'Tunnel-Assignment-Id', + 'radiusTunnelClientEndpoint' : 'Tunnel-Client-Endpoint', + 'radiusTunnelMediumType' : 'Tunnel-Medium-Type', + 'radiusTunnelPassword' : 'Tunnel-Password', + 'radiusTunnelPreference' : 'Tunnel-Preference', + 'radiusTunnelPrivateGroupId' : 'Tunnel-Private-Group-Id', + 'radiusTunnelServerEndpoint' : 'Tunnel-Server-Endpoint', + 'radiusTunnelType' : 'Tunnel-Type', + 'radiusUserCategory' : 'User-Category', + 'radiusVSA' : 'VSA', +} + +profile_name_to_ldap_attr = reverse_map_dict(profile_ldap_attr_to_name) + +#------------------------------------------------------------------------------ + +clients_container = 'cn=clients,cn=radius,cn=services,cn=etc' + +def radius_clients_basedn(container, suffix): + if container is None: container = clients_container + return '%s,%s' % (container, suffix) + +def radius_client_filter(ip_addr): + return "(&(radiusClientIPAddress=%s)(objectclass=radiusClientProfile))" % \ + ldap.filter.escape_filter_chars(ip_addr) + +def radius_client_dn(client, container, suffix): + if container is None: container = clients_container + return 'radiusClientIPAddress=%s,%s,%s' % (ldap.dn.escape_dn_chars(client), container, suffix) + +# -- + +profiles_container = 'cn=profiles,cn=radius,cn=services,cn=etc' + +def radius_profiles_basedn(container, suffix): + if container is None: container = profiles_container + return '%s,%s' % (container, suffix) + +def radius_profile_filter(uid): + return "(&(uid=%s)(objectclass=radiusprofile))" % \ + ldap.filter.escape_filter_chars(uid) + +def radius_profile_dn(uid, container, suffix): + if container is None: container = profiles_container + return 'uid=%s,%s,%s' % (ldap.dn.escape_dn_chars(uid), container, suffix) + + +#------------------------------------------------------------------------------ + +comment_re = re.compile('#.*$', re.MULTILINE) +def read_pairs_file(filename): + if filename == '-': + fd = sys.stdin + else: + fd = open(filename) + data = fd.read() + data = comment_re.sub('', data) # kill comments + pairs = ipautil.parse_key_value_pairs(data) + if fd != sys.stdin: fd.close() + return pairs + + +def get_ldap_attr_translations(): + comment_re = re.compile('#.*$') + radius_attr_to_ldap_attr = {} + ldap_attr_to_radius_attr = {} + try: + f = open(LDAP_ATTR_MAP_FILEPATH) + for line in f.readlines(): + line = comment_re.sub('', line).strip() + if not line: continue + attr_type, radius_attr, ldap_attr = line.split() + print 'type="%s" radius="%s" ldap="%s"' % (attr_type, radius_attr, ldap_attr) + radius_attr_to_ldap_attr[radius_attr] = {'ldap_attr':ldap_attr, 'attr_type':attr_type} + ldap_attr_to_radius_attr[ldap_attr] = {'radius_attr':radius_attr, 'attr_type':attr_type} + f.close() + except Exception, e: + logging.error('cold not read radius ldap attribute map file (%s): %s', LDAP_ATTR_MAP_FILEPATH, e) + pass # FIXME + + #for k,v in radius_attr_to_ldap_attr.items(): + # print '%s --> %s' % (k,v) + #for k,v in ldap_attr_to_radius_attr.items(): + # print '%s --> %s' % (k,v) + diff --git a/ipa-python/rpcclient.py b/ipa-python/rpcclient.py index e756058a..531bf72b 100644 --- a/ipa-python/rpcclient.py +++ b/ipa-python/rpcclient.py @@ -594,12 +594,12 @@ class RPCClient: # radius support - def get_radius_client_by_ip_addr(self,ip_addr,sattrs=None): + def get_radius_client_by_ip_addr(self,ip_addr, container, sattrs=None): server = self.setup_server() - if sattrs is None: - sattrs = "__NONE__" + if container is None: container = "__NONE__" + if sattrs is None: sattrs = "__NONE__" try: - result = server.get_radius_client_by_ip_addr(ip_addr, sattrs) + result = server.get_radius_client_by_ip_addr(ip_addr, container, sattrs) except xmlrpclib.Fault, fault: raise ipaerror.gen_exception(fault.faultCode, fault.faultString) except socket.error, (value, msg): @@ -607,11 +607,13 @@ class RPCClient: return ipautil.unwrap_binary_data(result) - def add_radius_client(self,client): + def add_radius_client(self,client, container=None): server = self.setup_server() + if container is None: container = "__NONE__" + try: - result = server.add_radius_client(ipautil.wrap_binary_data(client)) + result = server.add_radius_client(ipautil.wrap_binary_data(client), container) except xmlrpclib.Fault, fault: raise ipaerror.gen_exception(fault.faultCode, fault.faultString) except socket.error, (value, msg): @@ -633,11 +635,12 @@ class RPCClient: return ipautil.unwrap_binary_data(result) - def delete_radius_client(self,ip_addr): + def delete_radius_client(self,ip_addr, container=None): server = self.setup_server() + if container is None: container = "__NONE__" try: - result = server.delete_radius_client(ip_addr) + result = server.delete_radius_client(ip_addr, container) except xmlrpclib.Fault, fault: raise ipaerror.gen_exception(fault.faultCode, fault.faultString) except socket.error, (value, msg): @@ -645,13 +648,14 @@ class RPCClient: return ipautil.unwrap_binary_data(result) - def find_radius_clients(self, criteria, sattrs=None, searchlimit=0, timelimit=-1): + def find_radius_clients(self, criteria, container=None, sattrs=None, searchlimit=0, timelimit=-1): server = self.setup_server() + if container is None: container = "__NONE__" try: # None values are not allowed in XML-RPC if sattrs is None: sattrs = "__NONE__" - result = server.find_radius_clients(criteria, sattrs, searchlimit, timelimit) + result = server.find_radius_clients(criteria, container, sattrs, searchlimit, timelimit) except xmlrpclib.Fault, fault: raise ipaerror.gen_exception(fault.faultCode, fault.faultString) except socket.error, (value, msg): diff --git a/ipa-server/ipa-install/share/60radius.ldif b/ipa-server/ipa-install/share/60radius.ldif index 47692352..3562312a 100644 --- a/ipa-server/ipa-install/share/60radius.ldif +++ b/ipa-server/ipa-install/share/60radius.ldif @@ -492,7 +492,7 @@ objectClasses: NAME 'radiusprofile' SUP top AUXILIARY DESC '' - MUST cn + MUST uid MAY ( radiusArapFeatures $ radiusArapSecurity $ radiusArapZoneAccess $ radiusAuthType $ radiusCallbackId $ radiusCallbackNumber $ radiusCalledStationId $ radiusCallingStationId $ radiusClass $ @@ -527,14 +527,6 @@ objectClasses: MAY ( uid $ userPassword $ description ) ) attributeTypes: - ( 1.3.6.1.4.1.3317.4.3.1.63 - NAME 'radiusClientNASIpAddress' - DESC '' - EQUALITY caseIgnoreIA5Match - SYNTAX 1.3.6.1.4.1.1466.115.121.1.26 - SINGLE-VALUE - ) -attributeTypes: ( 1.3.6.1.4.1.3317.4.3.1.64 NAME 'radiusClientSecret' DESC '' @@ -564,6 +556,6 @@ objectClasses: NAME 'radiusClientProfile' SUP top STRUCTURAL DESC 'A Container Objectclass to be used for describing radius clients' - MUST (radiusClientNASIpAddress $ radiusClientSecret) + MUST (radiusClientIPAddress $ radiusClientSecret) MAY ( radiusClientNASType $ radiusClientShortName $ description ) ) diff --git a/ipa-server/ipa-install/share/bootstrap-template.ldif b/ipa-server/ipa-install/share/bootstrap-template.ldif index fcc2506d..df59bc0e 100644 --- a/ipa-server/ipa-install/share/bootstrap-template.ldif +++ b/ipa-server/ipa-install/share/bootstrap-template.ldif @@ -92,11 +92,11 @@ objectClass: nsContainer objectClass: top cn: profiles -dn: cn=ipa_default, cn=profiles,cn=radius,cn=services,cn=etc,$SUFFIX +dn: uid=ipa_default, cn=profiles,cn=radius,cn=services,cn=etc,$SUFFIX changetype: add objectClass: top objectClass: radiusprofile -cn: ipa_default +uid: ipa_default dn: cn=admins,cn=groups,cn=accounts,$SUFFIX changetype: add diff --git a/ipa-server/ipaserver/radiusinstance.py b/ipa-server/ipaserver/radiusinstance.py index 8317da03..0c94c713 100644 --- a/ipa-server/ipaserver/radiusinstance.py +++ b/ipa-server/ipaserver/radiusinstance.py @@ -26,6 +26,7 @@ import logging import pwd import time from ipa.ipautil import * +from ipa import radius_util import service @@ -33,18 +34,6 @@ import os import re IPA_RADIUS_VERSION = '0.0.0' -PKG_NAME = 'freeradius' -PKG_CONFIG_DIR = '/etc/raddb' - -RADIUS_SERVICE_NAME = 'radius' -RADIUS_USER = 'radiusd' - -IPA_KEYTAB_FILEPATH = os.path.join(PKG_CONFIG_DIR, 'ipa.keytab') -LDAP_ATTR_MAP_FILEPATH = os.path.join(PKG_CONFIG_DIR, 'ldap.attrmap') -RADIUSD_CONF_FILEPATH = os.path.join(PKG_CONFIG_DIR, 'radiusd.conf') -RADIUSD_CONF_TEMPLATE_FILEPATH = os.path.join(SHARE_DIR, 'radius.radiusd.conf.template') - -RADIUSD = '/usr/sbin/radiusd' # FIXME there should a utility to get the user base dn from ipaserver.funcs import DefaultUserContainer, DefaultGroupContainer @@ -58,7 +47,7 @@ def ldap_mod(fd, dn, pwd): def get_radius_version(): version = None try: - p = subprocess.Popen([RADIUSD, '-v'], stdout=subprocess.PIPE, + p = subprocess.Popen([radius_util.RADIUSD, '-v'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() status = p.returncode @@ -86,7 +75,7 @@ class RadiusInstance(service.Service): self.suffix = realm_to_suffix(self.realm) self.fqdn = host_name self.ldap_server = ldap_server - self.principal = "%s/%s@%s" % (RADIUS_SERVICE_NAME, self.fqdn, self.realm) + self.principal = "%s/%s@%s" % (radius_util.RADIUS_SERVICE_NAME, self.fqdn, self.realm) self.basedn = self.suffix self.user_basedn = "%s,%s" % (DefaultUserContainer, self.basedn) # FIXME, should be utility to get this self.radius_version = get_radius_version() @@ -117,34 +106,34 @@ class RadiusInstance(service.Service): version = 'IPA_RADIUS_VERSION=%s FREE_RADIUS_VERSION=%s' % (IPA_RADIUS_VERSION, self.radius_version) sub_dict = {'CONFIG_FILE_VERSION_INFO' : version, 'LDAP_SERVER' : self.ldap_server, - 'RADIUS_KEYTAB' : IPA_KEYTAB_FILEPATH, + 'RADIUS_KEYTAB' : radius_util.RADIUS_IPA_KEYTAB_FILEPATH, 'RADIUS_PRINCIPAL' : self.principal, 'RADIUS_USER_BASE_DN' : self.user_basedn, 'ACCESS_ATTRIBUTE' : '', 'ACCESS_ATTRIBUTE_DEFAULT' : 'TRUE', - 'CLIENTS_BASEDN' : 'cn=clients,cn=radius,cn=services,cn=etc,%s' % self.suffix, + 'CLIENTS_BASEDN' : radius_util.radius_clients_basedn(None, self.suffix), 'SUFFIX' : self.suffix, } try: - radiusd_conf = template_file(RADIUSD_CONF_TEMPLATE_FILEPATH, sub_dict) - radiusd_fd = open(RADIUSD_CONF_FILEPATH, 'w+') + radiusd_conf = template_file(radius_util.RADIUSD_CONF_TEMPLATE_FILEPATH, sub_dict) + radiusd_fd = open(radius_util.RADIUSD_CONF_FILEPATH, 'w+') radiusd_fd.write(radiusd_conf) radiusd_fd.close() except Exception, e: - logging.error("could not create %s: %s", RADIUSD_CONF_FILEPATH, e) + logging.error("could not create %s: %s", radius_util.RADIUSD_CONF_FILEPATH, e) def __create_radius_keytab(self): self.step("create radiusd keytab") try: - if file_exists(IPA_KEYTAB_FILEPATH): - os.remove(IPA_KEYTAB_FILEPATH) + if file_exists(radius_util.RADIUS_IPA_KEYTAB_FILEPATH): + os.remove(radius_util.RADIUS_IPA_KEYTAB_FILEPATH) except os.error: - logging.error("Failed to remove %s", IPA_KEYTAB_FILEPATH) + logging.error("Failed to remove %s", radius_util.RADIUS_IPA_KEYTAB_FILEPATH) (kwrite, kread, kerr) = os.popen3("/usr/kerberos/sbin/kadmin.local") kwrite.write("addprinc -randkey %s\n" % (self.principal)) kwrite.flush() - kwrite.write("ktadd -k %s %s\n" % (IPA_KEYTAB_FILEPATH, self.principal)) + kwrite.write("ktadd -k %s %s\n" % (radius_util.RADIUS_IPA_KEYTAB_FILEPATH, self.principal)) kwrite.flush() kwrite.close() kread.close() @@ -152,7 +141,7 @@ class RadiusInstance(service.Service): # give kadmin time to actually write the file before we go on retry = 0 - while not file_exists(IPA_KEYTAB_FILEPATH): + while not file_exists(radius_util.RADIUS_IPA_KEYTAB_FILEPATH): time.sleep(1) retry += 1 if retry > 15: @@ -160,10 +149,10 @@ class RadiusInstance(service.Service): os.exit() try: - pent = pwd.getpwnam(RADIUS_USER) - os.chown(IPA_KEYTAB_FILEPATH, pent.pw_uid, pent.pw_gid) + pent = pwd.getpwnam(radius_util.RADIUS_USER) + os.chown(radius_util.RADIUS_IPA_KEYTAB_FILEPATH, pent.pw_uid, pent.pw_gid) except Exception, e: - logging.error("could not chown on %s to %s: %s", IPA_KEYTAB_FILEPATH, RADIUS_USER, e) + logging.error("could not chown on %s to %s: %s", radius_util.RADIUS_IPA_KEYTAB_FILEPATH, radius_util.RADIUS_USER, e) #FIXME, should use IPAdmin method def __set_ldap_encrypted_attributes(self): @@ -179,27 +168,3 @@ class RadiusInstance(service.Service): #------------------------------------------------------------------------------- -# FIXME: this should be in a common area so it can be shared -def get_ldap_attr_translations(): - comment_re = re.compile('#.*$') - radius_attr_to_ldap_attr = {} - ldap_attr_to_radius_attr = {} - try: - f = open(LDAP_ATTR_MAP_FILEPATH) - for line in f.readlines(): - line = comment_re.sub('', line).strip() - if not line: continue - attr_type, radius_attr, ldap_attr = line.split() - print 'type="%s" radius="%s" ldap="%s"' % (attr_type, radius_attr, ldap_attr) - radius_attr_to_ldap_attr[radius_attr] = {'ldap_attr':ldap_attr, 'attr_type':attr_type} - ldap_attr_to_radius_attr[ldap_attr] = {'radius_attr':radius_attr, 'attr_type':attr_type} - f.close() - except Exception, e: - logging.error('cold not read radius ldap attribute map file (%s): %s', LDAP_ATTR_MAP_FILEPATH, e) - pass # FIXME - - #for k,v in radius_attr_to_ldap_attr.items(): - # print '%s --> %s' % (k,v) - #for k,v in ldap_attr_to_radius_attr.items(): - # print '%s --> %s' % (k,v) - diff --git a/ipa-server/xmlrpc-server/funcs.py b/ipa-server/xmlrpc-server/funcs.py index 26fdba48..aa557f79 100644 --- a/ipa-server/xmlrpc-server/funcs.py +++ b/ipa-server/xmlrpc-server/funcs.py @@ -30,6 +30,7 @@ import xmlrpclib import copy import attrs from ipa import ipaerror +from ipa import radius_util import string from types import * @@ -458,41 +459,40 @@ class IPAServer: # radius support - # FIXME, why not just use get_entry_by_dn? - def get_radius_client_by_ip_addr(self, ip_addr, sattrs=None, opts=None): - ip_addr = self.__safe_filter(ip_addr) - basedn = 'cn=clients,cn=radius,cn=services,cn=etc,%s' % self.basedn # FIXME, should not be hardcoded - filter = "(&(radiusClientNASIpAddress=%s)(objectclass=radiusClientProfile))" % ip_addr + # clients + def get_radius_client_by_ip_addr(self, ip_addr, container=None, sattrs=None, opts=None): + filter = radius_util.radius_client_filter(ip_addr) + basedn = radius_util.radius_clients_basedn(container, self.basedn) return self.__get_sub_entry(basedn, filter, sattrs, opts) - def __is_radius_client_unique(self, ip_addr, opts): - """Return 1 if the radius client is unique in the tree, 0 otherwise.""" - ip_addr = self.__safe_filter(ip_addr) - basedn = 'cn=clients,cn=radius,cn=services,cn=etc,%s' % self.basedn # FIXME, should not be hardcoded - filter = "(&(radiusClientNASIpAddress=%s)(objectclass=radiusClientProfile))" % ip_addr + def __radius_client_exists(self, ip_addr, container, opts): + filter = radius_util.radius_client_filter(ip_addr) + basedn = radius_util.radius_clients_basedn(container, self.basedn) try: entry = self.__get_sub_entry(basedn, filter, ['dn','uid'], opts) - return 0 + return True except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): - return 1 + return False - def add_radius_client (self, client, opts=None): - client_container = 'cn=clients,cn=radius,cn=services,cn=etc' # FIXME, should not be hardcoded - if self.__is_radius_client_unique(client['radiusClientNASIpAddress'], opts) == 0: - raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE) + def add_radius_client (self, client, container=None, opts=None): + if container is None: + container = radius_util.clients_container - dn="radiusClientNASIpAddress=%s,%s,%s" % (ldap.dn.escape_dn_chars(client['radiusClientNASIpAddress']), - client_container,self.basedn) + ip_addr = client['radiusClientIPAddress'] + + if self.__radius_client_exists(ip_addr, container, opts): + raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE) + dn = radius_util.radius_client_dn(ip_addr, container, self.basedn) entry = ipaserver.ipaldap.Entry(dn) # some required objectclasses entry.setValues('objectClass', 'top', 'radiusClientProfile') # fill in our new entry with everything sent by the client - for u in client: - entry.setValues(u, client[u]) + for attr in client: + entry.setValues(attr, client[attr]) conn = self.getConnection(opts) try: @@ -504,8 +504,8 @@ class IPAServer: def update_radius_client(self, oldentry, newentry, opts=None): return self.update_entry(oldentry, newentry, opts) - def delete_radius_client(self, ip_addr, opts=None): - client = self.get_radius_client_by_ip_addr(ip_addr, ['dn', 'cn'], opts) + def delete_radius_client(self, ip_addr, container=None, opts=None): + client = self.get_radius_client_by_ip_addr(ip_addr, container, ['dn', 'cn'], opts) if client is None: raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) @@ -516,7 +516,7 @@ class IPAServer: self.releaseConnection(conn) return res - def find_radius_clients(self, ip_attrs, sattrs=None, searchlimit=0, timelimit=-1, opts=None): + def find_radius_clients(self, ip_attrs, container=None, sattrs=None, searchlimit=0, timelimit=-1, opts=None): def gen_filter(objectclass, attr, values): '''Given ('myclass', 'myattr', [v1, v2]) returns (&(objectclass=myclass)(|(myattr=v1)(myattr=v2))) @@ -527,8 +527,8 @@ class IPAServer: filter = "(&(objectclass=%s)(|%s))" % (objectclass, attrs) return filter - basedn = 'cn=clients,cn=radius,cn=services,cn=etc,%s' % self.basedn # FIXME, should not be hardcoded - filter = gen_filter('radiusClientProfile', 'radiusClientNASIpAddress', ip_attrs) + basedn = radius_util.radius_clients_basedn(container, self.basedn) + filter = gen_filter('radiusClientProfile', 'radiusClientIPAddress', ip_attrs) conn = self.getConnection(opts) try: try: @@ -546,6 +546,111 @@ class IPAServer: return radius_clients + # profiles + def get_radius_profile_by_uid(self, uid, user_profile=True, sattrs=None, opts=None): + if user_profile: + container = DefaultUserContainer + else: + container = radius_util.profiles_container + + uid = self.__safe_filter(uid) + filter = radius_util.radius_profile_filter(uid) + basedn = radius_util.radius_profiles_basedn(container, self.basedn) + return self.__get_sub_entry(basedn, filter, sattrs, opts) + + def __radius_profile_exists(self, uid, user_profile, opts): + if user_profile: + container = DefaultUserContainer + else: + container = radius_util.profiles_container + + uid = self.__safe_filter(uid) + filter = radius_util.radius_profile_filter(uid) + basedn = radius_util.radius_profiles_basedn(container, self.basedn) + + try: + entry = self.__get_sub_entry(basedn, filter, ['dn','uid'], opts) + return True + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + return False + + def add_radius_profile (self, uid, user_profile=True, opts=None): + if self.__radius_profile_exists(profile['uid'], user_profile, opts): + raise ipaerror.gen_exception(ipaerror.LDAP_DUPLICATE) + + if user_profile: + container = DefaultUserContainer + else: + container = radius_util.profiles_container + + dn = radius_util.radius_profile_dn(uid, container, self.basedn) + entry = ipaserver.ipaldap.Entry(dn) + + # some required objectclasses + entry.setValues('objectClass', 'top', 'radiusClientProfile') + + # fill in our new entry with everything sent by the profile + for attr in profile: + entry.setValues(attr, profile[attr]) + + conn = self.getConnection(opts) + try: + res = conn.addEntry(entry) + finally: + self.releaseConnection(conn) + return res + + def update_radius_profile(self, oldentry, newentry, opts=None): + return self.update_entry(oldentry, newentry, opts) + + def delete_radius_profile(self, uid, user_profile, opts=None): + profile = self.get_radius_profile_by_uid(uid, user_profile, ['dn', 'cn'], opts) + if profile is None: + raise ipaerror.gen_exception(ipaerror.LDAP_NOT_FOUND) + + conn = self.getConnection(opts) + try: + res = conn.deleteEntry(profile['dn']) + finally: + self.releaseConnection(conn) + return res + + def find_radius_profiles(self, uids, user_profile=True, sattrs=None, searchlimit=0, timelimit=-1, opts=None): + def gen_filter(objectclass, attr, values): + '''Given ('myclass', 'myattr', [v1, v2]) returns + (&(objectclass=myclass)(|(myattr=v1)(myattr=v2))) + ''' + # Don't use __safe_filter, prevents wildcarding + #attrs = ''.join(['(%s=%s)' % (attr, self.__safe_filter(val)) for val in values]) + attrs = ''.join(['(%s=%s)' % (attr, val) for val in values]) + filter = "(&(objectclass=%s)(|%s))" % (objectclass, attrs) + return filter + + if user_profile: + container = DefaultUserContainer + else: + container = radius_util.profiles_container + + uid = self.__safe_filter(uid) + filter = gen_filter('radiusClientProfile' 'uid', uids) + basedn="%s,%s" % (container, self.basedn) + conn = self.getConnection(opts) + try: + try: + results = conn.getListAsync(basedn, self.scope, filter, sattrs, 0, None, None, timelimit, searchlimit) + except ipaerror.exception_for(ipaerror.LDAP_NOT_FOUND): + results = [0] + finally: + self.releaseConnection(conn) + + counter = results[0] + results = results[1:] + radius_profiles = [counter] + for radius_profile in results: + radius_profiles.append(self.convert_entry(radius_profile)) + + return radius_profiles + def get_add_schema (self): """Get the list of fields to be used when adding users in the GUI.""" diff --git a/ipa-server/xmlrpc-server/ipaxmlrpc.py b/ipa-server/xmlrpc-server/ipaxmlrpc.py index d5bdb6b2..ef48f4aa 100644 --- a/ipa-server/xmlrpc-server/ipaxmlrpc.py +++ b/ipa-server/xmlrpc-server/ipaxmlrpc.py @@ -356,6 +356,11 @@ def handler(req, profiling=False): h.register_function(f.update_radius_client) h.register_function(f.delete_radius_client) h.register_function(f.find_radius_clients) + h.register_function(f.get_radius_profile_by_uid) + h.register_function(f.add_radius_profile) + h.register_function(f.update_radius_profile) + h.register_function(f.delete_radius_profile) + h.register_function(f.find_radius_profiles) h.handle_request(req) finally: pass |