summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJohn Dennis <jdennis@redhat.com>2007-11-21 13:11:10 -0500
committerJohn Dennis <jdennis@redhat.com>2007-11-21 13:11:10 -0500
commitd98686e96758870cb4a56d41fb0aaae54d4067c5 (patch)
tree6fc7101684591afdfb9677732352e59067066bc1
parent087d11af5cebe7bb7a87d0581c7fa95353d9aa3b (diff)
downloadfreeipa-d98686e96758870cb4a56d41fb0aaae54d4067c5.tar.gz
freeipa-d98686e96758870cb4a56d41fb0aaae54d4067c5.tar.xz
freeipa-d98686e96758870cb4a56d41fb0aaae54d4067c5.zip
Add radius profile implementations:
get_radius_profile_by_uid add_radius_profile update_radius_profile delete_radius_profile find_radius_profiles Rewrite command line arg handling, now support pair entry, interactive mode with auto completion, reading pairs from a file, better handling of mandatory values, better help, long arg names now match attribute name in pairs Establish mappings for all attributes and names used in clients and profiles Add notion of containers to radius clients and profiles in LDAP Move common code, variables, constants, and strings into the files radius_client.py, radius_util.py, ipautil.py to eliminate redundant elements which could get out of sync if modified and to provide access to other code which might benefit from using these items in the future. Add utility functions: format_list() parse_key_value_pairs() Add utility class: AttributeValueCompleter Unify attribute usage in radius ldap schema
-rw-r--r--ipa-admintools/ipa-addradiusclient195
-rw-r--r--ipa-admintools/ipa-findradiusclient15
-rw-r--r--ipa-admintools/ipa-radiusclientmod92
-rw-r--r--ipa-python/ipaclient.py16
-rw-r--r--ipa-python/ipautil.py330
-rw-r--r--ipa-python/radius_client.py39
-rw-r--r--ipa-python/radius_util.py231
-rw-r--r--ipa-python/rpcclient.py24
-rw-r--r--ipa-server/ipa-install/share/60radius.ldif12
-rw-r--r--ipa-server/ipa-install/share/bootstrap-template.ldif4
-rw-r--r--ipa-server/ipaserver/radiusinstance.py67
-rw-r--r--ipa-server/xmlrpc-server/funcs.py155
-rw-r--r--ipa-server/xmlrpc-server/ipaxmlrpc.py5
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