diff options
Diffstat (limited to '')
256 files changed, 33677 insertions, 0 deletions
diff --git a/raddb/.gitignore b/raddb/.gitignore new file mode 100644 index 0000000..9daa835 --- /dev/null +++ b/raddb/.gitignore @@ -0,0 +1,6 @@ +dictionary +radiusd.conf +sites-enabled +mods-enabled +radrelay.conf +test.conf diff --git a/raddb/README.rst b/raddb/README.rst new file mode 100644 index 0000000..e3c41a7 --- /dev/null +++ b/raddb/README.rst @@ -0,0 +1,665 @@ +Upgrading to Version 3.2 +======================== + +.. contents:: Sections + :depth: 2 + +.. important:: + The configuration for 3.2 is compatible with 3.0. The only change + is that the `correct_escapes` configuration has been removed, and + is not the default. + +Security +-------- + +A number of configuration items have moved into the "security" +subsection of radiusd.conf. If you use these, you should move them. +Otherwise, they can be ignored. + +The list of moved options is:: + + chroot + user + group + allow_core_dumps + reject_delay + status_server + +These entries should be moved from "radiusd.conf" to the "security" +subsection of that file. + +Naming +------ + +Many names used by configuration items were inconsistent in earlier +versions of the server. These names have been unified in version 3. + +If a file is being referenced or created the config item ``filename`` +is used. + +If a file is being created, the initial permissions are set by the +``permissions`` config item. + +If a directory hierarchy needs to be created, the permissions are set +by ``dir_permissions``. + +If an external host is referenced in the context of a module the +``server`` config item is used. + +Unless the config item is a well recognised portmanteau +(as ``filename`` is for example), it must be written as multiple +distinct words separated by underscores ``_``. + +The configuration items ``file``, ``script_file``, ``module``, +``detail``, ``detailfile``, ``attrsfile``, ``perm``, ``dirperm``, +``detailperm``, and ``hostname`` are deprecated. As well as any false +portmanteaus, and configuration items that used hyphens as word +delimiters. e.g. ``foo-bar`` has been changed to ``foo_bar``. Please +update your module configuration to use the new syntax. + +In most cases the server will tell you the replacement config item to +use. As always, run the server in debugging mode to see these +messages. + +Modules Directory +----------------- + +As of version 3, the ``modules/`` directory no longer exists. + +Instead, all "example" modules have been put into the +``mods-available/`` directory. Modules which can be loaded by the +server are placed in the ``mods-enabled/`` directory. All of the +modules in that directory will be loaded. This means that the +``instantiate`` section of radiusd.conf is less important. The only +reason to list a module in the ``instantiate`` section is to force +ordering when the modules are loaded. + +Modules can be enabled by creating a soft link. For module ``foo``, do:: + + $ cd raddb/mods-enabled + $ ln -s ../mods-available/foo + +To create "local" versions of the modules, we suggest copying the file +instead. This leaves the original file (with documentation) in the +``mods-available/`` directory. Local changes should go into the +``mods-enabled/`` directory. + +Module-specific configuration files are now in the ``mods-config/`` +directory. This change allows for better organization, and means that +there are fewer files in the main ``raddb`` directory. See +``mods-config/README.rst`` for more details. + +Changed Modules +--------------- + +The following modules have been changed. + + +rlm_sql +~~~~~~~ + +The SQL configuration has been moved from ``sql.conf`` to +``mods-available/sql``. The ``sqlippool.conf`` file has also been +moved to ``mods-available/sqlippool``. + +The SQL module configuration has been changed. The old connection +pool options are no longer accepted:: + + num_sql_socks + connect_failure_retry_delay + lifetime + max_queries + +Instead, a connection pool configuration is used. This configuration +contains all of the functionality of the previous configuration, but +in a more generic form. It also is used in multiple modules, meaning +that there are fewer different configuration items. The mapping +between the configuration items is:: + + num_sql_socks -> pool { max } + connect_failure_retry_delay -> pool { retry_delay } + lifetime -> pool { lifetime } + max_queries -> pool { uses } + +The pool configuration adds a number of new configuration options, +which allow the administrator to better control how FreeRADIUS uses +SQL connection pools. + +The following parameters have been changed:: + + trace -> removed + tracefile -> logfile + +The logfile is intended to log SQL queries performed. If you need to +debug the server, use debugging mode. If ``logfile`` is set, then +*all* SQL queries will go to ``logfile``. + +You can now use a NULL SQL database:: + + driver = rlm_sql_null + +This is an empty driver which will always return "success". It is +intended to be used to replace the ``sql_log`` module, and to work in +conjunction with the ``radsqlrelay`` program. Simply take your normal +configuration for raddb/mods-enabled/sql, and set:: + + driver = rlm_sql_null + ... + logfile = ${radacctdir}/sql.log + +All of the SQL queries will be logged to that file. The connection +pool does not need to be configured for the ``null`` SQL driver. It +can be left as-is, or deleted from the SQL configuration file. + +rlm_sql_sybase +~~~~~~~~~~~~~~ + +The ``rlm_sql_sybase`` module has been renamed to ``rlm_sql_freetds`` +and the old ``rlm_sql_freetds`` module has been removed. + +``rlm_sql_sybase`` used the newer ct-lib API, and ``rlm_sql_freetds`` +used an older API and was incomplete. + +The new ``rlm_sql_freetds`` module now also supports database +selection on connection startup so ``use`` statements no longer +have to be included in queries. + +sql/dialup.conf +~~~~~~~~~~~~~~~ + +Queries for post-auth and accounting calls have been re-arranged. The +SQL module will now expand the 'reference' configuration item in the +appropriate sub-section, and resolve this to a configuration +item. This behaviour is similar to rlm_linelog. This dynamic +expansion allows for a dynamic mapping between accounting types and +SQL queries. Previously, the mapping was fixed. Any "new" accounting +type was ignored by the module. Now, support for any accounting type +can be added by just adding a new target, as below. + +Queries from v2 may be manually copied to the new v3 +``dialup.conf`` file (``raddb/mods-config/sql/main/<dialect>/queries.conf``). +When doing this you may also need to update references to the +accounting tables, as their definitions will now be outside of +the subsection containing the query. + +The mapping from old "fixed" query to new "dynamic" query is as follows:: + + accounting_onoff_query -> accounting.type.accounting-on.query + accounting_update_query -> accounting.type.interim-update.query + accounting_update_query_alt +> accounting.type.interim-update.query + accounting_start_query -> accounting.type.start.query + accounting_start_query_alt +> accounting.type.start.query + accounting_stop_query -> accounting.type.stop.query + accounting_stop_query_alt +> accounting.type.stop.query + postauth_query -> post-auth.query + +Alternatively a v2 config may be patched to work with the +v3 module by adding the following:: + + accounting { + reference = "%{tolower:type.%{Acct-Status-Type}.query}" + type { + accounting-on { + query = "${....accounting_onoff_query}" + } + accounting-off { + query = "${....accounting_onoff_query}" + } + start { + query = "${....accounting_start_query}" + query = "${....accounting_start_query_alt}" + } + interim-update { + query = "${....accounting_update_query}" + query = "${....accounting_update_query_alt}" + } + stop { + query = "${....accounting_stop_query}" + query = "${....accounting_stop_query_alt}" + } + } + } + + post-auth { + query = "${..postauth_query}" + } + +In general, it is safer to migrate the configuration rather than +trying to "patch" it, to make it look like a v2 configuration. + +Note that the sub-sections holding the queries are labelled +``accounting-on``, and not ``accounting_on``. The reason is that the +names of these sections are taken directly from the +``Accounting-Request`` packet, and the ``Acct-Status-Type`` field. +The ``sql`` module looks at the value of that field, and then looks +for a section of that name, in order to find the query to use. + +That process means that the server can be extended to support any new +value of ``Acct-Status-Type``, simply by adding a named sub-section, +and a query. This behavior is preferable to that of v2, which had +hard-coded queries for certain ``Acct-Status-Type`` values, and was +ignored all other values. + +rlm_ldap +~~~~~~~~ + +The LDAP module configuration has been substantially changed. Please +read ``raddb/mods-available/ldap``. It now uses a connection pool, +just like the SQL module. + +Many of the configuration items remain the same, but they have been +moved into subsections. This change is largely cosmetic, but it makes +the configuration clearer. Instead of having a large set of random +configuration items, they are now organized into logical groups. + +You will need to read your old LDAP configuration, and migrate it +manually to the new configuration. Simply copying the old +configuration WILL NOT WORK. + +Users upgrading from v2 who used to call the ldap module in +``post-auth`` should now set ``edir_autz = yes``, and remove the ``ldap`` +module from the ``post-auth`` section. + +rlm_ldap and LDAP-Group +~~~~~~~~~~~~~~~~~~~~~~~ + +In v2 the registration of the ``LDAP-Group`` pair comparison was done +by the last instance of rlm_ldap to be instantiated. In v3 this has +changed so that only the default ``ldap {}`` instance registers +``LDAP-Group``. + +If ``<instance>-LDAP-Group`` is already used throughout your configuration +no changes will be needed. + +rlm_ldap authentication +~~~~~~~~~~~~~~~~~~~~~~~ + +In v2 the LDAP module had a ``set_auth_type`` configuration item, +which forced ``Auth-Type := ldap``. This was removed in 3.x.x as it +often did not work, and was not consistent with the rest of the +server. We generally recommend that LDAP should be used as a +database, and that FreeRADIUS should do authentication. + +The only reason to use ``Auth-Type := ldap`` is when the LDAP server +will not supply the "known good" password to FreeRADIUS, *and* where +the Access-Request contains User-Password. This situation happens +only for Active Directory. If you think you need to force ``Auth-Type +:= ldap`` in other situations, you are very likely to be wrong. + +The following is an example of what should be inserted into the +``authorize {}`` and ``authenticate {}`` sections of the relevant +virtual-servers, to get functionality equivalent to v2.x:: + + authorize { + ... + ldap + if ((ok || updated) && User-Password) { + update control { + Auth-Type := ldap + } + } + ... + } + + authenticate { + ... + Auth-Type ldap { + ldap + } + ... + } + +rlm_eap +~~~~~~~ + +The EAP configuration has been moved from ``eap.conf`` to +``mods-available/eap``. A new ``pwd`` subsection has been added for +EAP-PWD. + +rlm_expiration & rlm_logintime +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The rlm_expiration and rlm_logintime modules no longer add a ``Reply-Message``, +the same behaviour can be achieved checking the return code of the module and +adding the ``Reply-Message`` with unlang:: + + expiration + if (userlock) { + update reply { + Reply-Message := "Your account has expired" + } + } + +rlm_unix +~~~~~~~~ + +The ``unix`` module does not have an ``authenticate`` section. So you +cannot set ``Auth-Type := System``. The ``unix`` module has also been +deleted from the examples in ``sites-available/``. Listing it there +has been deprecated for many years. + +The PAP module can do crypt authentication. It should be used instead +of Unix authentication. + +The Unix module still can pull the passwords from ``/etc/passwd``, or +``/etc/shadow``. This is done by listing it in the ``authorize`` +section, as is done in the examples in ``sites-available/``. However, +some systems using NIS or NSS will not supply passwords to the +``unix`` module. For those systems, we recommend putting users and +passwords into a database, instead of relying on ``/etc/passwd``. + +rlm_preprocess +~~~~~~~~~~~~~~ + +In v2 ``huntroups`` and ``users`` files were loaded from default locations +without being configured explicitly. Since 3.x.x you need to set +``huntgroups`` and ``users`` configuration item(s) in module section in order +to get them being processed. + +New Modules +----------- + +rlm_date +~~~~~~~~ + +Instances of rlm_date register an xlat method which can translate +integer and date values to an arbitrarily formatted date time +string, or an arbitrarily formated time string to an integer, +depending on the attribute type passed. + +rlm_rest +~~~~~~~~ + +The ``rest`` module is used to translate RADIUS requests into +RESTfull HTTP requests. Currently supported body types are JSON +and POST. + +rlm_unpack +~~~~~~~~~~ + +The ``unpack`` module is used to turn data buried inside of binary +attributes. e.g. if we have ``Class = 0x00000001020304`` then:: + + Tmp-Integer-0 := "%{unpack:&Class 4 short}" + +will unpack octets 4 and 5 as a "short", which has value 0x0304. +All integers are assumed to be in network byte order. + +rlm_yubikey +~~~~~~~~~~~ + +The ``yubikey`` module can be used to forward yubikey OTP token +values to a Yubico validation server, or decrypt the token +using a PSK. + +Deleted Modules +--------------- + +The following modules have been deleted, and are no longer supported +in Version 3. If you are using one of these modules, your +configuration can probably be changed to not need it. Otherwise email +the freeradius-devel list, and ask about the module. + +rlm_acct_unique +~~~~~~~~~~~~~~~ + +This module has been replaced by the "acct_unique" policy. See +raddb/policy.d/accounting. + +The method for calculating the value of acct_unique has changed. +However, as this method was configurable, this change should not +matter. The only issue is in having a v2 and v3 server writing to the +same database at the same time. They will calculate different values +for Acct-Unique-Id. + +rlm_acctlog +~~~~~~~~~~~ + +You should use rlm_linelog instead. That module has a superset of the +acctlog functionality. + +rlm_attr_rewrite +~~~~~~~~~~~~~~~~ + +The attr_rewrite module looked for an attribute, and then re-wrote it, +or created a new attribute. All of that can be done in "unlang". + +A sample configuration in "unlang" is:: + + if (request:Calling-Station-Id) { + update request { + Calling-Station-Id := "...." + } + } + +We suggest updating all uses of attr_rewrite to use unlang instead. + +rlm_checkval +~~~~~~~~~~~~ + +The checkval module compared two attributes. All of that can be done in "unlang":: + + if (&request:Calling-Station-Id == &control:Calling-Station-Id) { + ok + } + +We suggest updating all uses of checkval to use unlang instead. + +rlm_dbm +~~~~~~~ + +No one seems to use it. There is no sample configuration for it. +There is no speed advantage to using it over the "files" module. +Modern systems are fast enough that 10K entries can be read from the +"users" file in about 10ms. If you need more users than that, use a +real database such as SQL. + +rlm_fastusers +~~~~~~~~~~~~~ + +No one seems to use it. It has been deprecated since Version 2.0.0. +The "files" module was rewritten so that the "fastusers" module was no +longer necessary. + +rlm_policy +~~~~~~~~~~ + +No one seems to use it. Almost all of its functionality is available +via "unlang". + +rlm_sim_files +~~~~~~~~~~~~~ + +The rlm_sim_files module has been deleted. It was never marked "stable", +and was never used in a production environment. There are better ways +to test EAP. + +If you want similar functionality, see rlm_passwd. It can read CSV +files, and create attributes from them. + +rlm_sql_log +~~~~~~~~~~~ + +This has been replaced with the "null" sql driver. See +raddb/mods-available/sql for an example configuration. + +The main SQL module has more functionality than rlm_sql_log, and +results in less code in the server. + +Other Functionality +------------------- + +The following is a list of new / changed functionality. + +RadSec +~~~~~~ + +RadSec (or RADIUS over TLS) is now supported. RADIUS over bare TCP +is also supported, but is recommended only for secure networks. + +See ``sites-available/tls`` for complete details on using TLS. The server +can both receive incoming TLS connections, and also originate outgoing +TLS connections. + +The TLS configuration is taken from the old EAP-TLS configuration. It +is largely identical to the old EAP-TLS configuration, so it should be +simple to use and configure. It re-uses much of the EAP-TLS code, +so it is well-tested and reliable. + +Once RadSec is enabled, normal debugging mode will not work. This is +because the TLS code requires threading to work properly. Instead of doing:: + + $ radiusd -X + +you will need to do:: + + $ radiusd -fxx -l stdout + +That's the price to pay for using RadSec. This limitation may be +lifted in a future version of the server. + + +PAP and User-Password +~~~~~~~~~~~~~~~~~~~~~ + +From version 3 onwards the server no longer supports authenticating +against a cleartext password in the 'User-Password' attribute. Any +occurences of this (for instance, in the users file) should now be changed +to 'Cleartext-Password' instead. + +e.g. change entries like this:: + + bob User-Password == "hello" + +to ones like this:: + + bob Cleartext-Password := "hello" + + +If this is not done, authentication will likely fail. The server will +also print a helpful message in debugging mode. + +If it really is impossible to do this, the following unlang inserted above +the call to the pap module may be used to copy User-Password to the correct +attribute:: + + if (!control:Cleartext-Password && control:User-Password) { + update control { + Cleartext-Password := "%{control:User-Password}" + } + } + +However, this should only be seen as a temporary, not permanent, fix. +It is better to fix your databases to use the correct configuration. + +Unlang +~~~~~~ + +The unlang policy language is compatible with v2, but has a number of +new features. See ``man unlang`` for complete documentation. + +ERRORS + +Many more errors are caught when the server is starting up. Syntax +errors in ``unlang`` are caught, and a helpful error message is +printed. The error message points to the exact place where the error +occurred:: + + ./raddb/sites-enabled/default[230]: Parse error in condition + ERROR: if (User-Name ! "bob") { + ERROR: ^ Invalid operator + +``update`` sections are more generic. Instead of doing ``update +reply``, you can do the following:: + + update { + reply:Class := 0x0000 + control:Cleartext-Password := "hello" + } + +This change means that you need fewer ``update`` sections. + +COMPARISONS + +Attribute comparisons can be done via the ``&`` operator. When you +needed to compare two attributes, the old comparison style was:: + + if (User-Name == "%{control:Tmp-String-0}") { + +This syntax is inefficient, as the ``Tmp-String-0`` attribute would be +printed to an intermediate string, causing unnecessary work. You can +now instead compare the two attributes directly:: + + if (&User-Name == &control:Tmp-String-0) { + +See ``man unlang`` for more details. + +CASTS + +Casts are now permitted. This allows you to force type-specific +comparisons:: + + if (<ipaddr>"%{sql: SELECT...}" == 127.0.0.1) { + +This forces the string returned by the SELECT to be treated as an IP +address, and compare to ``127.0.0.1``. Previously, the comparison +would have been done as a simple string comparison. + +NETWORKS + +IP networks are now supported:: + + if (127.0.0.1/32 == 127.0.0.1) { + +Will be ``true``. The various comparison operators can be used to +check IP network membership:: + + if (127/8 > 127.0.0.1) { + +Returns ``true``, because ``127.0.0.1`` is within the ``127/8`` +network. However, the following comparison will return ``false``:: + + if (127/8 > 192.168.0.1) { + +because ``192.168.0.1`` is outside of the ``127/8`` network. + +OPTIMIZATION + +As ``unlang`` is now pre-compiled, many compile-time optimizations are +done. This means that the debug output may not be exactly the same as +what is in the configuration files:: + + if (0 && (User-Name == "bob')) { + +The result will always be ``false``, as the ``if 0`` prevents the +following ``&& ...`` from being evaluated. + +Not only that, but the entire contents of that section will be ignored +entirely:: + + if (0) { + this_module_does_not_exist + and_this_one_does_not_exist_either + } + +In v2, that configuration would result in a parse error, as there is +no module called ``this_module_does_not_exist``. In v3, that text is +ignored. This ability allows you to have dynamic configurations where +certain parts are used (or not) depending on compile-time configuration. + +Similarly, conditions which always evaluate to ``true`` will be +optimized away:: + + if (1) { + files + } + +That configuration will never show the ``if (1)`` output in debugging mode. + + +Dialup_admin +------------ + +The dialup_admin directory has been removed. No one stepped forward +to maintain it, and the code had not been changed in many years. + diff --git a/raddb/all.mk b/raddb/all.mk new file mode 100644 index 0000000..a7f4f14 --- /dev/null +++ b/raddb/all.mk @@ -0,0 +1,152 @@ +# +# The list of files to install. +# +LOCAL_FILES := clients.conf dictionary templates.conf experimental.conf \ + proxy.conf radiusd.conf trigger.conf README.rst panic.gdb + +DEFAULT_SITES := default inner-tunnel +LOCAL_SITES := $(addprefix raddb/sites-enabled/,$(DEFAULT_SITES)) + +DEFAULT_MODULES := always attr_filter chap date \ + detail detail.log digest dynamic_clients eap \ + echo exec expiration expr files linelog logintime \ + mschap ntlm_auth pap passwd preprocess radutmp realm \ + replicate soh sradutmp totp unix unpack utf8 + +LOCAL_MODULES := $(addprefix raddb/mods-enabled/,$(DEFAULT_MODULES)) + +LOCAL_CERT_FILES := Makefile README.md xpextensions \ + ca.cnf server.cnf inner-server.cnf \ + client.cnf bootstrap + +# +# We don't create the installed certs if we're building a package, +# OR if OpenSSL is not available. +# +ifeq "$(PACKAGE)" "" +ifneq "$(OPENSSL_LIBS)" "" +LOCAL_CERT_PRODUCTS := $(addprefix $(R)$(raddbdir)/certs/,ca.key ca.pem \ + client.key client.pem server.key server.pem) +endif +endif + +LEGACY_LINKS := $(addprefix $(R)$(raddbdir)/,users huntgroups hints) + +RADDB_DIRS := certs mods-available mods-enabled policy.d \ + sites-available sites-enabled \ + $(patsubst raddb/%,%,$(shell find raddb/mods-config -type d -print)) + +# Installed directories +INSTALL_RADDB_DIRS := $(R)$(raddbdir)/ $(addprefix $(R)$(raddbdir)/, $(RADDB_DIRS)) + +# Grab files from the various subdirectories +INSTALL_FILES := $(wildcard raddb/sites-available/* raddb/mods-available/*) \ + $(addprefix raddb/,$(LOCAL_FILES)) \ + $(addprefix raddb/certs/,$(LOCAL_CERT_FILES)) \ + $(shell find raddb/mods-config -type f -print) \ + $(shell find raddb/policy.d -type f -print) + +# Re-write local files to installed files, filtering out editor backups +INSTALL_RADDB := $(patsubst raddb/%,$(R)$(raddbdir)/%,\ + $(filter-out %~,$(INSTALL_FILES))) + +all: build.raddb + +build.raddb: $(LOCAL_SITES) $(LOCAL_MODULES) + +clean: clean.raddb + +install: install.raddb + +# Local build rules +raddb/sites-enabled raddb/mods-enabled: + @echo INSTALL $@ + @$(INSTALL) -d -m 750 $@ + +# Set up the default modules for running in-source builds +raddb/mods-enabled/%: raddb/mods-available/% | raddb/mods-enabled + @echo "LN-S $@" + @cd $(dir $@) && ln -sf ../mods-available/$(notdir $@) + +# Set up the default sites for running in-source builds +raddb/sites-enabled/%: raddb/sites-available/% | raddb/sites-enabled + @echo "LN-S $@" + @cd $(dir $@) && ln -sf ../sites-available/$(notdir $@) + +# Installation rules for directories. Note permissions are 750! +$(INSTALL_RADDB_DIRS): + @echo INSTALL $(patsubst $(R)$(raddbdir)%,raddb%,$@) + @$(INSTALL) -d -m 750 $@ + +# The installed files have ORDER dependencies. This means that they +# will be installed if the target doesn't exist. And they won't be +# installed if the target already exists, even if it is out of date. +# +# This dependency lets us install the server on top of an existing +# system, hopefully without breaking anything. + +ifeq "$(wildcard $(R)$(raddbdir)/mods-available/)" "" +INSTALL_RADDB += $(patsubst raddb/%,$(R)$(raddbdir)/%,\ + $(filter-out %~,$(LOCAL_MODULES))) + +# Installation rules for mods-enabled. Note ORDER dependencies +$(R)$(raddbdir)/mods-enabled/%: | $(R)$(raddbdir)/mods-available/% + @cd $(dir $@) && ln -sf ../mods-available/$(notdir $@) +endif + +ifeq "$(wildcard $(R)$(raddbdir)/sites-available/)" "" +INSTALL_RADDB += $(patsubst raddb/%,$(R)$(raddbdir)/%,\ + $(filter-out %~,$(LOCAL_SITES))) + +# Installation rules for sites-enabled. Note ORDER dependencies +$(R)$(raddbdir)/sites-enabled/%: | $(R)$(raddbdir)/sites-available/% + @cd $(dir $@) && ln -sf ../sites-available/$(notdir $@) +endif + +# Installation rules for plain modules. +$(R)$(raddbdir)/%: | raddb/% + @echo INSTALL $(patsubst $(R)$(raddbdir)/%,raddb/%,$@) + @$(INSTALL) -m 640 $(patsubst $(R)$(raddbdir)/%,raddb/%,$@) $@ + +# Create symbolic links for legacy files +$(R)$(raddbdir)/huntgroups: $(R)$(modconfdir)/preprocess/huntgroups + @[ -e $@ ] || echo LN-S $(patsubst $(R)$(raddbdir)/%,raddb/%,$@) + @[ -e $@ ] || ln -s $(patsubst $(R)$(raddbdir)/%,./%,$<) $@ + +$(R)$(raddbdir)/hints: $(R)$(modconfdir)/preprocess/hints + @[ -e $@ ] || echo LN-S $(patsubst $(R)$(raddbdir)/%,raddb/%,$@) + @[ -e $@ ] || ln -s $(patsubst $(R)$(raddbdir)/%,./%,$<) $@ + +$(R)$(raddbdir)/users: $(R)$(modconfdir)/files/authorize + @[ -e $@ ] || echo LN-S $(patsubst $(R)$(raddbdir)/%,raddb/%,$@) + @[ -e $@ ] || ln -s $(patsubst $(R)$(raddbdir)/%,./%,$<) $@ + +ifneq "$(LOCAL_CERT_PRODUCTS)" "" +$(LOCAL_CERT_PRODUCTS): + @echo BOOTSTRAP raddb/certs/ + @$(MAKE) -C $(R)$(raddbdir)/certs/ + +# Bootstrap is special +$(R)$(raddbdir)/certs/bootstrap: | raddb/certs/bootstrap $(LOCAL_CERT_PRODUCTS) + @echo INSTALL $(patsubst $(R)$(raddbdir)/%,raddb/%,$@) + @$(INSTALL) -m 750 $(patsubst $(R)$(raddbdir)/%,raddb/%,$@) $@ +else +$(R)$(raddbdir)/certs/bootstrap: + @echo INSTALL $(patsubst $(R)$(raddbdir)/%,raddb/%,$@) + @$(INSTALL) -m 750 $(patsubst $(R)$(raddbdir)/%,raddb/%,$@) $@ +endif + +# List directories before the file targets. +# It's not clear why GNU Make doesn't deal well with this. +install.raddb: | $(INSTALL_RADDB_DIRS) $(INSTALL_RADDB) $(LEGACY_LINKS) + +clean.raddb: + @rm -f *~ $(addprefix raddb/sites-enabled/,$(DEFAULT_SITES)) \ + $(addprefix raddb/mods-enabled/,$(DEFAULT_MODULES)) + +# +# A handy target to find out which triggers are where. +# Should only be run by SNMP developers. +# +triggers: + @grep exec_trigger `find src -name "*.c" -print` | grep '"' | sed -e 's/.*,//' -e 's/ *"//' -e 's/");.*//' diff --git a/raddb/certs/.gitignore b/raddb/certs/.gitignore new file mode 100644 index 0000000..45e1bcb --- /dev/null +++ b/raddb/certs/.gitignore @@ -0,0 +1,13 @@ +*.pem +*.key +*.crt +*.csr +*.p12 +*.old +*.attr +*.crl +dh +index.txt +random +serial +passwords.mk diff --git a/raddb/certs/Makefile b/raddb/certs/Makefile new file mode 100644 index 0000000..c9fbc9e --- /dev/null +++ b/raddb/certs/Makefile @@ -0,0 +1,186 @@ +###################################################################### +# +# Make file to be installed in /etc/raddb/certs to enable +# the easy creation of certificates. +# +# See the README file in this directory for more information. +# +# $Id$ +# +###################################################################### + +DH_KEY_SIZE = 2048 +OPENSSL = openssl +EXTERNAL_CA = $(wildcard external_ca.*) + +ifneq "$(EXTERNAL_CA)" "" +PARTIAL = -partial_chain +endif + +# +# Set the passwords +# +include passwords.mk + +###################################################################### +# +# Make the necessary files, but not client certificates. +# +###################################################################### +.PHONY: all +all: index.txt serial dh ca server client + +.PHONY: client +client: client.pem + +.PHONY: ca +ca: ca.der ca.crl + +.PHONY: server +server: server.pem server.vrfy + +.PHONY: inner-server +inner-server: inner-server.pem inner-server.vrfy + +.PHONY: verify +verify: server.vrfy client.vrfy + +passwords.mk: server.cnf ca.cnf client.cnf inner-server.cnf + @echo "PASSWORD_SERVER = '$(shell grep output_password server.cnf | sed 's/.*=//;s/^ *//')'" > $@ + @echo "PASSWORD_INNER = '$(shell grep output_password inner-server.cnf | sed 's/.*=//;s/^ *//')'" >> $@ + @echo "PASSWORD_CA = '$(shell grep output_password ca.cnf | sed 's/.*=//;s/^ *//')'" >> $@ + @echo "PASSWORD_CLIENT = '$(shell grep output_password client.cnf | sed 's/.*=//;s/^ *//')'" >> $@ + @echo "USER_NAME = '$(shell grep emailAddress client.cnf | grep '@' | sed 's/.*=//;s/^ *//')'" >> $@ + @echo "CA_DEFAULT_DAYS = '$(shell grep default_days ca.cnf | sed 's/.*=//;s/^ *//')'" >> $@ + +###################################################################### +# +# Diffie-Hellman parameters +# +###################################################################### +dh: + $(OPENSSL) dhparam -out dh -2 $(DH_KEY_SIZE) + +###################################################################### +# +# Create a new self-signed CA certificate +# +###################################################################### +ca.key ca.pem: ca.cnf + @[ -f index.txt ] || $(MAKE) index.txt + @[ -f serial ] || $(MAKE) serial + $(OPENSSL) req -new -x509 -keyout ca.key -out ca.pem \ + -days $(CA_DEFAULT_DAYS) -config ./ca.cnf \ + -passin pass:$(PASSWORD_CA) -passout pass:$(PASSWORD_CA) + chmod g+r ca.key + +ca.der: ca.pem + $(OPENSSL) x509 -inform PEM -outform DER -in ca.pem -out ca.der + +ca.crl: ca.pem + $(OPENSSL) ca -gencrl -keyfile ca.key -cert ca.pem -config ./ca.cnf -out ca-crl.pem -key $(PASSWORD_CA) + $(OPENSSL) crl -in ca-crl.pem -outform der -out ca.crl + rm ca-crl.pem + +###################################################################### +# +# Create a new server certificate, signed by the above CA. +# +###################################################################### +server.csr server.key: server.cnf + $(OPENSSL) req -new -out server.csr -keyout server.key -config ./server.cnf + chmod g+r server.key + +server.crt: ca.key ca.pem server.csr + $(OPENSSL) ca -batch -keyfile ca.key -cert ca.pem -in server.csr -key $(PASSWORD_CA) -out server.crt -extensions xpserver_ext -extfile xpextensions -config ./server.cnf + +server.p12: server.crt + $(OPENSSL) pkcs12 -export -in server.crt -inkey server.key -out server.p12 -passin pass:$(PASSWORD_SERVER) -passout pass:$(PASSWORD_SERVER) + chmod g+r server.p12 + +server.pem: server.p12 + $(OPENSSL) pkcs12 -in server.p12 -out server.pem -passin pass:$(PASSWORD_SERVER) -passout pass:$(PASSWORD_SERVER) + chmod g+r server.pem + +.PHONY: server.vrfy +server.vrfy: ca.pem + @$(OPENSSL) verify $(PARTIAL) -CAfile ca.pem server.pem + +###################################################################### +# +# Create a new client certificate, signed by the the above server +# certificate. +# +###################################################################### +client.csr client.key: client.cnf + $(OPENSSL) req -new -out client.csr -keyout client.key -config ./client.cnf + chmod g+r client.key + +client.crt: ca.key ca.pem client.csr + $(OPENSSL) ca -batch -keyfile ca.key -cert ca.pem -in client.csr -key $(PASSWORD_CA) -out client.crt -extensions xpclient_ext -extfile xpextensions -config ./client.cnf + +client.p12: client.crt + $(OPENSSL) pkcs12 -export -in client.crt -inkey client.key -out client.p12 -passin pass:$(PASSWORD_CLIENT) -passout pass:$(PASSWORD_CLIENT) + chmod g+r client.p12 + cp client.p12 $(USER_NAME).p12 + +client.pem: client.p12 + $(OPENSSL) pkcs12 -in client.p12 -out client.pem -passin pass:$(PASSWORD_CLIENT) -passout pass:$(PASSWORD_CLIENT) + chmod g+r client.pem + cp client.pem $(USER_NAME).pem + +.PHONY: client.vrfy +client.vrfy: ca.pem client.pem + c_rehash . + $(OPENSSL) verify -CApath . client.pem + +###################################################################### +# +# Create a new inner-server certificate, signed by the above CA. +# +###################################################################### +inner-server.csr inner-server.key: inner-server.cnf + $(OPENSSL) req -new -out inner-server.csr -keyout inner-server.key -config ./inner-server.cnf + chmod g+r inner-server.key + +inner-server.crt: ca.key ca.pem inner-server.csr + $(OPENSSL) ca -batch -keyfile ca.key -cert ca.pem -in inner-server.csr -key $(PASSWORD_CA) -out inner-server.crt -extensions xpserver_ext -extfile xpextensions -config ./inner-server.cnf + +inner-server.p12: inner-server.crt + $(OPENSSL) pkcs12 -export -in inner-server.crt -inkey inner-server.key -out inner-server.p12 -passin pass:$(PASSWORD_INNER) -passout pass:$(PASSWORD_INNER) + chmod g+r inner-server.p12 + +inner-server.pem: inner-server.p12 + $(OPENSSL) pkcs12 -in inner-server.p12 -out inner-server.pem -passin pass:$(PASSWORD_INNER) -passout pass:$(PASSWORD_INNER) + chmod g+r inner-server.pem + +.PHONY: inner-server.vrfy +inner-server.vrfy: ca.pem + @$(OPENSSL) verify $(PARTIAL) -CAfile ca.pem inner-server.pem + +###################################################################### +# +# Miscellaneous rules. +# +###################################################################### +index.txt: + @touch index.txt + +serial: + @echo '01' > serial + +print: + $(OPENSSL) x509 -text -in server.crt + +printca: + $(OPENSSL) x509 -text -in ca.pem + +clean: + @rm -f *~ *old client.csr client.key client.crt client.p12 client.pem + +# +# Make a target that people won't run too often. +# +destroycerts: + rm -f *~ dh *.csr *.crt *.p12 *.der *.pem *.key index.txt* \ + serial* *\.0 *\.1 ca-crl.pem ca.crl diff --git a/raddb/certs/README.md b/raddb/certs/README.md new file mode 100644 index 0000000..1d73f99 --- /dev/null +++ b/raddb/certs/README.md @@ -0,0 +1,248 @@ +# Certificate Documentation + +This directory contains scripts to create the server certificates. To +make a set of default (i.e. test) certificates, simply type: + +``` +$ ./bootstrap +``` + +The `openssl` command will be run against the sample configuration +files included here, and will make a self-signed certificate authority +(i.e. root CA), and a server certificate. This "root CA" should be +installed on any client machine needing to do EAP-TLS, PEAP, or +EAP-TTLS. + +The Extended Key Usage (EKU) fields for "TLS web server" will be +automatically included in the server certificate. Without those +extensions many clients will refuse to authenticate to FreeRADIUS. + +The root CA and the "XP Extensions" file also contain a +crlDistributionPoints attribute. Many systems need this to be present +in order to validate the RADIUS server certificate. The RADIUS server +must have the URI defined but the CA need not have...however it is +best practice for a CA to have a revocation URI. Note that whilst the +Windows Mobile client cannot actually use the CRL when doing 802.1X it +is recommended that the URI be an actual working URL and contain a +revocation format file as there may be other OS behaviour at play and +future OSes that may do something with that URI. + +For Windows, you will need to import the `p12` and/or the `der` format +of the certificates. Linux systems need the `pem` format. + +In general, you should use self-signed certificates for 802.1X (EAP) +authentication. When you list root CAs from other organisations in +the `ca_file`, you permit them to masquerade as you, to authenticate +your users, and to issue client certificates for EAP-TLS. + +If you already have CA and server certificates, rename (or delete) +this directory, and create a new `certs` directory containing your +certificates. Note that the `make install` command will **not** +over-write your existing `raddb/certs` directory. + + +## New Installations of FreeRADIUS + +We suggest that new installations use the test certificates for +initial tests, and then create real certificates to use for normal +user authentication. See the instructions below for how to create the +various certificates. The old test certificates can be deleted by +running the following command: + +``` +$ make destroycerts +``` + +Then, follow the instructions below for creating real certificates. + +If you do not want to enable EAP-TLS, PEAP, or EAP-TTLS, then delete +the relevant sub-sections from the `raddb/mods-available/eap` file. +See the comments in that file for more information. + + +## Making a root Certificate + +We recommend using a private certificate authority (CA). While it can +be difficult to install this CA on multiple client machines, it is (in +general) more secure. + +``` +$ vi ca.cnf +``` + +Edit `default_days` to set the desired lifetime of the CA certificate. + +Edit the `input_password` and `output_password` fields to be the +password for the CA certificate. + +Edit the `[certificate_authority]` section to have the correct values +for your country, state, etc. + +Create the CA certificate: + +``` +$ make ca.pem +``` + +Then the `DER` format needed by Windows: + +``` +$ make ca.der +``` + + +## Making a Server Certificate + +The following steps will let you create a server certificate for use +with TLS-based EAP methods, such as EAP-TLS, PEAP, and TTLS. Follow +similar steps to create an `inner-server.pem` file, for use with +EAP-TLS that is tunneled inside of another TLS-based EAP method. + +``` +$ vi server.cnf +``` + +Edit `default_days` to set the lifetime of the server certificate. +The maximum for this is 825 for compatibility with all client devices. + +Edit the `input_password` and `output_password` fields to be the +password for the server certificate. + +Edit the `[server]` section to have the correct values for your +country, state, etc. Be sure that the `commonName` field here is +different from the `commonName` for the CA certificate. + +Create the server certificate: + +``` +$ make server +``` + + +### Making a certificate for a public CA + +If you wish to use an existing certificate authority, you can +create a certificate signing request for the server certificate, edit +`server.cnf` as above, and run the following command. + +``` +$ make server.csr +``` + +This step creates a "Certificate Signing Request" suitable for +submission to a public CA. + + +## Making a Client certificate + +Client certificates are used by EAP-TLS, and optionally by EAP-TTLS +and PEAP. The following steps outline how to create a client +certificate that is signed by the CA certificate created above. You +will have to have the password for the CA certificate in the +`input_password` and `output_password` fields of the `ca.cnf` file. + +``` +$ vi client.cnf +``` + +Edit `default_days` to set the lifetime of the client certificate. + +Edit the `input_password` and `output_password` fields to be the +password for the client certificate. You will have to give these +passwords to the end user who will be using the certificates. + +Edit the `[client]` section to have the correct values for your +country, state, etc. Be sure that the `commonName` field here is +the `User-Name` which will be used for logins! + +``` +$ make client +``` + +The users certificate will be in `emailAddress.pem`, +e.g. `user@example.com.pem`. + +To create another client certificate, just repeat the steps for +making a client certificate, being sure to enter a different login +name for `commonName`, and a different password. + + +## Performance + +EAP performance for EAP-TLS, TTLS, and PEAP is dominated by SSL +calculations. That is, a normal system can handle PAP +authentication at a rate of 10k packets/s. However, SSL involves +RSA calculations, which are very expensive. To benchmark your system, +do: + +``` +$ openssl speed rsa +``` + +or + +``` +$ openssl speed rsa2048 +``` + +to test 2048 bit keys. + +The number that is printed is the **maximum** number of +authentications per second which can be done for EAP-TLS (or TTLS, +or PEAP). In practice, you will see results much lower than this +number, i.e. the actual EAP-TLS performance may be half of the +number printed here. + +The reason is that EAP requires many round-trip packets, whereas +`openssl speed rsa2028` only does RSA calculations, and nothing else. + + +## Compatibility + +The certificates created using this method are known to be compatible +with ALL operating systems. Some common issues are: + +* iOS and macOS have requirements on certificates. See: + https://support.apple.com/en-us/HT210176 + +* Many systems require certain OIDs in the certificates + (`id-kp-serverAuth` for `TLS Web server authentication`). + If the certificate does not contain these fields, the system + will stop doing EAP. The most visible effect is that the client + starts EAP, gets a few Access-Challenge packets, and then a little + while later re-starts EAP. If this happens, see the FAQ, and the + comments in `raddb/mods-available/eap` for how to fix it. + +* All systems requires the root certificates to be on the client PC. + If it doesn't have them, you will see the same issue as above. + +* Windows XP post SP2 has a bug where it has problems with + certificate chains. i.e. if the server certificate is an + intermediate one, and not a root one, then authentication + will silently fail, as above. + +* Some versions of Windows CE cannot handle 4K RSA certificates. + They will (again) silently fail, as above. + +* In none of these cases will Windows give the end user any + reasonable error message describing what went wrong. This leads + people to blame the RADIUS server. That blame is misplaced. + +* Certificate chains of more than 64K bytes are known to not work. + This is partly a problem in FreeRADIUS. However, most clients cannot + handle 64K certificate chains. Most Access Points will shut down the + EAP session after about 50 round trips, while 64K certificate chains + will take about 60 round trips. So don't use large certificate + chains. They will only work after everyone upgrades everything in the + network. + +* All other operating systems are known to work with EAP and + FreeRADIUS. This includes Linux, the BSDs, macOS, iOS, Android, + Solaris, Symbian, along with all known embedded systems, phones, + WiFi devices, etc. + + +## Security Considerations + +The default certificate configuration files uses SHA256 for message +digests for security. diff --git a/raddb/certs/bootstrap b/raddb/certs/bootstrap new file mode 100755 index 0000000..57de8cf --- /dev/null +++ b/raddb/certs/bootstrap @@ -0,0 +1,86 @@ +#!/bin/sh +# +# This is a wrapper script to create default certificates when the +# server first starts in debugging mode. Once the certificates have been +# created, this file should be deleted. +# +# Ideally, this program should be run as part of the installation of any +# binary package. The installation should also ensure that the permissions +# and owners are correct for the files generated by this script. +# +# $Id$ +# +umask 027 +cd `dirname $0` + +make -h > /dev/null 2>&1 + +# +# If we have a working "make", then use it. Otherwise, run the commands +# manually. +# +if [ "$?" = "0" ]; then + make all + exit $? +fi + +# +# The following commands were created by running "make -n", and edited +# to remove the trailing backslash, and to add "exit 1" after the commands. +# +# Don't edit the following text. Instead, edit the Makefile, and +# re-generate these commands. +# +if [ ! -f dh ]; then + openssl dhparam -out dh 2048 || exit 1 + if [ -e /dev/urandom ] ; then + ln -sf /dev/urandom random + else + date > ./random; + fi +fi + +if [ ! -f server.key ]; then + openssl req -new -out server.csr -keyout server.key -config ./server.cnf || exit 1 + chmod g+r server.key +fi + +if [ ! -f ca.key ]; then + openssl req -new -x509 -keyout ca.key -out ca.pem -days `grep default_days ca.cnf | sed 's/.*=//;s/^ *//'` -config ./ca.cnf || exit 1 +fi + +if [ ! -f index.txt ]; then + touch index.txt +fi + +if [ ! -f serial ]; then + echo '01' > serial +fi + +if [ ! -f server.crt ]; then + openssl ca -batch -keyfile ca.key -cert ca.pem -in server.csr -key `grep output_password ca.cnf | sed 's/.*=//;s/^ *//'` -out server.crt -extensions xpserver_ext -extfile xpextensions -config ./server.cnf || exit 1 +fi + +if [ ! -f server.p12 ]; then + openssl pkcs12 -export -in server.crt -inkey server.key -out server.p12 -passin pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` -passout pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` || exit 1 + chmod g+r server.p12 +fi + +if [ ! -f server.pem ]; then + openssl pkcs12 -in server.p12 -out server.pem -passin pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` -passout pass:`grep output_password server.cnf | sed 's/.*=//;s/^ *//'` || exit 1 + openssl verify -CAfile ca.pem server.pem || exit 1 + chmod g+r server.pem +fi + +if [ ! -f ca.der ]; then + openssl x509 -inform PEM -outform DER -in ca.pem -out ca.der || exit 1 +fi + +if [ ! -f client.key ]; then + openssl req -new -out client.csr -keyout client.key -config ./client.cnf + chmod g+r client.key +fi + +if [ ! -f client.crt ]; then + openssl ca -batch -keyfile ca.key -cert ca.pem -in client.csr -key `grep output_password ca.cnf | sed 's/.*=//;s/^ *//'` -out client.crt -extensions xpclient_ext -extfile xpextensions -config ./client.cnf +fi diff --git a/raddb/certs/ca.cnf b/raddb/certs/ca.cnf new file mode 100644 index 0000000..d49991b --- /dev/null +++ b/raddb/certs/ca.cnf @@ -0,0 +1,62 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./ +certs = $dir +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/ca.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/ca.key +RANDFILE = $dir/.rand +name_opt = ca_default +cert_opt = ca_default +default_days = 60 +default_crl_days = 30 +default_md = sha256 +preserve = no +policy = policy_match +crlDistributionPoints = URI:http://www.example.org/example_ca.crl + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +prompt = no +distinguished_name = certificate_authority +default_bits = 2048 +input_password = whatever +output_password = whatever +x509_extensions = v3_ca + +[certificate_authority] +countryName = FR +stateOrProvinceName = Radius +localityName = Somewhere +organizationName = Example Inc. +emailAddress = admin@example.org +commonName = "Example Certificate Authority" + +[v3_ca] +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always +basicConstraints = critical,CA:true +crlDistributionPoints = URI:http://www.example.org/example_ca.crl + diff --git a/raddb/certs/client.cnf b/raddb/certs/client.cnf new file mode 100644 index 0000000..2650e47 --- /dev/null +++ b/raddb/certs/client.cnf @@ -0,0 +1,53 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./ +certs = $dir +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/ca.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/ca.key +RANDFILE = $dir/.rand +name_opt = ca_default +cert_opt = ca_default +default_days = 60 +default_crl_days = 30 +default_md = sha256 +preserve = no +policy = policy_match + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +prompt = no +distinguished_name = client +default_bits = 2048 +input_password = whatever +output_password = whatever + +[client] +countryName = FR +stateOrProvinceName = Radius +localityName = Somewhere +organizationName = Example Inc. +emailAddress = user@example.org +commonName = user@example.org diff --git a/raddb/certs/demoCA/cacert.pem b/raddb/certs/demoCA/cacert.pem new file mode 100644 index 0000000..7ddff4d --- /dev/null +++ b/raddb/certs/demoCA/cacert.pem @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDtjCCAx+gAwIBAgIBADANBgkqhkiG9w0BAQQFADCBnzELMAkGA1UEBhMCQ0Ex +ETAPBgNVBAgTCFByb3ZpbmNlMRIwEAYDVQQHEwlTb21lIENpdHkxFTATBgNVBAoT +DE9yZ2FuaXphdGlvbjESMBAGA1UECxMJbG9jYWxob3N0MRswGQYDVQQDExJDbGll +bnQgY2VydGlmaWNhdGUxITAfBgkqhkiG9w0BCQEWEmNsaWVudEBleGFtcGxlLmNv +bTAeFw0wNDAxMjUxMzI2MDdaFw0wNjAxMjQxMzI2MDdaMIGfMQswCQYDVQQGEwJD +QTERMA8GA1UECBMIUHJvdmluY2UxEjAQBgNVBAcTCVNvbWUgQ2l0eTEVMBMGA1UE +ChMMT3JnYW5pemF0aW9uMRIwEAYDVQQLEwlsb2NhbGhvc3QxGzAZBgNVBAMTEkNs +aWVudCBjZXJ0aWZpY2F0ZTEhMB8GCSqGSIb3DQEJARYSY2xpZW50QGV4YW1wbGUu +Y29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDUxbGXJPFkrPH/sYnbHI+/ +9PFDlup8sekPeNaUUXJTd4ld/lLMuZtB6A3etYsSepQ/T1jLxWKHgZL73G/s6fhx +58Ew01z1GIgX6bEzJJ7dKhx10xBDrodVPOx6d+8mqn10KB25t34XxkRsXdmxiLQy +UMoCKZY3IqEjpyawC0An/QIDAQABo4H/MIH8MB0GA1UdDgQWBBRo020+Hue8nVoF +cCHDY9oTZdGt4zCBzAYDVR0jBIHEMIHBgBRo020+Hue8nVoFcCHDY9oTZdGt46GB +paSBojCBnzELMAkGA1UEBhMCQ0ExETAPBgNVBAgTCFByb3ZpbmNlMRIwEAYDVQQH +EwlTb21lIENpdHkxFTATBgNVBAoTDE9yZ2FuaXphdGlvbjESMBAGA1UECxMJbG9j +YWxob3N0MRswGQYDVQQDExJDbGllbnQgY2VydGlmaWNhdGUxITAfBgkqhkiG9w0B +CQEWEmNsaWVudEBleGFtcGxlLmNvbYIBADAMBgNVHRMEBTADAQH/MA0GCSqGSIb3 +DQEBBAUAA4GBADPAC2ax5Xnvc6BnmCUtq41eVRH8AP0nbYDRL4NHd8Z0P9wnQ/yh +UHcE5LwJeeT2CsOtnug+bzRzaSKdH3cim6LpgjWdpWMCSgAWPbptbJhsC60or4UT +L/jw12UBvxt8Lf9ljOHmLAGZe25k4+jUNzNUzpkShHZRU5BjuFu8VIXF +-----END CERTIFICATE----- diff --git a/raddb/certs/inner-server.cnf b/raddb/certs/inner-server.cnf new file mode 100644 index 0000000..2101c2e --- /dev/null +++ b/raddb/certs/inner-server.cnf @@ -0,0 +1,55 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./ +certs = $dir +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/server.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/server.key +RANDFILE = $dir/.rand +name_opt = ca_default +cert_opt = ca_default +default_days = 60 +default_crl_days = 30 +default_md = sha256 +preserve = no +policy = policy_match +copy_extensions = copy + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +prompt = no +distinguished_name = server +default_bits = 2048 +input_password = whatever +output_password = whatever + +[server] +countryName = FR +stateOrProvinceName = Radius +localityName = Somewhere +organizationName = Example Inc. +emailAddress = admin@example.org +commonName = "Example Inner Server Certificate" + diff --git a/raddb/certs/ocsp.cnf b/raddb/certs/ocsp.cnf new file mode 100644 index 0000000..01225d8 --- /dev/null +++ b/raddb/certs/ocsp.cnf @@ -0,0 +1,61 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./ +certs = $dir +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/server.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/server.key +RANDFILE = $dir/.rand +name_opt = ca_default +cert_opt = ca_default +default_days = 60 +default_crl_days = 30 +default_md = sha256 +preserve = no +policy = policy_match +unique_subject = no +x509_extensions = v3_ocsp + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +prompt = no +distinguished_name = server +default_bits = 2048 +input_password = whatever +output_password = whatever + +[ server ] +countryName = FR +stateOrProvinceName = Radius +localityName = Somewhere +organizationName = Example Inc. +emailAddress = admin@example.org +commonName = "Example OCSP Responder Certificate" +subjectAltName = ocsp.example.org + +[ v3_ocsp ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +extendedKeyUsage = OCSPSigning diff --git a/raddb/certs/realms/README.md b/raddb/certs/realms/README.md new file mode 100644 index 0000000..9f754fa --- /dev/null +++ b/raddb/certs/realms/README.md @@ -0,0 +1,235 @@ +# Multiple Certificate Chains + +As of version 3.0.24, FreeRADIUS supports loading multiple certificate +chains, keyed by a realm name. These chains are in _addition_ to the +default certificates loaded by the `tls` section. + +Loading multiple certificate chains means that the server can have +different identities. i.e. When a user `bob@example.com` requests +network access, the server can present an `example.com` certificate. +On the other hand, when a user `doug@example.org` requests network +access, the server cna present an `example.org` certificate. + +This functionality means that it is possible to configure only one +`eap` module, and then use multiple certificate chains. Previous +versions of the server required that the administrator configure +multiple EAP modules, one for each certificate being used. + +The selection can be performed in one of two ways. First, the +certificates can be loaded dynamically at run-time. Second, the +certificates can be pre-loaded for speed. + +## Dynamic Loading of Certificate Chains + +The server can dynamically load a certificate chain by setting a +special attribute. This has to be done _after_ the server has +received the EAP identity request, and _before_ the TLS session setup +has started. + +The simplest way to do this is via the following `unlang` statements: + +``` +authenticate { + ... + Auth-Type eap { + if ("%{unpack:&EAP-Message 4 byte}" == 1) { + update control { + TLS-Session-Cert-File := "${certdir}/realms/%{Realm}" + } + } + + eap + } + ... +} +``` + +This configuration looks at the `EAP-Message` attribute, and checks if +it is an EAP-Identity packet. If so, it then adds a special attribute +`TLS-Session-Cert-File`, with a value based on the `Realm`, from the +`User-Name`. That setting tells the server to look in the file for a +certificate. + +If the file is there, and contains a correctly formatted `PEM` +certificate chain, then it is loaded and used. + +If the file does not exist, or the file does not contain a correctly +formatted `PEM` certificate chain, then the user is rejected. + +### Format + +This file should contain the server certificate, followed by +intermediate certificates, in order. i.e. If we have a server +certificate signed by CA1, which is signed by CA2, which is signed by +a root CA, then the "certificate_file" should contain server.pem, +followed by CA1.pem, followed by CA2.pem. + +When using `ca_file` or `ca_dir`, the file should contain only the +server certificate. + +### Private Key + +The private should be placed in the same file as the other +certificates, but at the start. + +``` +private key +server cert +... +ca cert +``` + +The private key can also be placed into a separate file. The filename +should be placed into the `TLS-Session-Cert-Private-Key-File` +attribute. + +For simplicity, the private keys _should not_ have passwords. There +is essentially no security benefit to "securing" the key with a +password, and then placing the password into the file system, right +next to the private key. + +### Realms + +There is no need to place the certificates into files named for each +realm. However, it is by far and away the easiest way to manage these +certificate chains. + +For every realm which is handles this way, the `proxy.conf` file +should define a _local_ realm. That is, it should contain a +definition such as: + +``` +example.org { +} +``` + +This defines the realm `example.org`, and tells FreeRADIUS that there +are no home servers associated with the realm. + +The `suffix` module should also be configured, as per the default +configuration. i.e. list `suffix` in the `authorize` section _before_ +the `eap` module. + +### Caveats + +The root CA certificate for the server certificate should be located +in the `ca_dir`, along with other root CAs. If the root CA is not +there, then it *must* be included at the end of the file. + +These certificates will be loaded and parsed _for every matching +authentication request_. That limitation means that dynamic loading +of the certificates is likely be slow, and to severely impact +performance. The good news is that we can fix that with a little more +configuration. + +## Preloading Certificate Chains + +The server can also pre-load certificate chains. In the EAP module, +you can do: + +``` +eap { + ... + tls { + ... + realm_dir = ${certdir}/realms/ + ... + } + ... +} +``` + +Each file in that directory should be a `PEM` encoded certificate +chain, as described in the previous section. For safety, every file +*must* have a `.pem` as the filename extension. +e.g. `example.org.pem`. + +If there is a corresponding private key, it should be placed into a +`.key` file. e.g. `example.org.key`. + +These certificates will be loaded when the server starts, and cached +for the lifetime of the server. There is no way to reload these +certificates dynamically, the server must be restarted. + +Once the `realm_dir` configuration has been added, the selection of +certificates is identical to that described in the previous section. +Just set `TLS-Session-Cert-File`, and the server will figure it out. + +However, it is possible to dynamically add new certificate, and have +the server pick it up. In fact, as the process for choosing +certificates are the same, the server will do this automatically! + +## RadSec + +The above configuration applies to RadSec, too, as the `tls` +configuration in the server is for all TLS functionality, and not just +EAP. + +This means that the server can accept RadSec connections, and then +present different server certificates to different clients. + +For this functionality to work, the certificates for EAP and RadSec +*should* be in separate directories. + +### Clients + +RadSec clients can set the SNI to send in the `tls` subsection of the +`home_server` definition. Look for "SNI" in `sites-available/tls`, +and see the `hostname` configuration item for documentation. + +For example, an identity provider could host multiple sites, but +present itself with one public IP address. If the RadSec clients do +not use SNI, then they must be configured with the certificate of the +identity provider. + +When SNI is used, the RadSec clients can be configured with the +certificate of the hosted system that they're connecting to. This +ability means that there is no need to change certificates when +changing providers. In addition, there is no need to change the +configuration of all RadSec clients when the hosting system changes +its certifiates. Because the hosting system certificates are never +used. + +Instead, each hosted company is responsible for its own certificates, +and for its own RadSec clients. + +SNI also permits the use of a load balancer such as haproxy. That +load balancer can terminate the TLS connection, and then use SNI to +route the underlying RADIUS TCP traffic to a particular host. + +### Servers + +See the `realm_dir` configuration item in the `tls` subsection for the +location of the server certificates. + +If the server receives an SNI for a realm it does not recognize, it +will just use the default TLS configuration. + +If the realm is recognized (i.e. there is a file in +`${realm_dir}/%{TLS-Server-Name-Indication}.pem`, then that certificate will be chosen, and +present to the RadSec client. If there is no such file, then the +default TLS configuration is used. + +The current behavior is to _require_ that the server certificate is in +a file which taken from +`${realm_dir}/%{TLS-Server-Name-Indication}.pem`. Only the +`realm_dir` portion of the filename is configurable. The SNI portion +is taken from the TLS messages, and the `.pem` suffix is hard-coded in +the source code. + +Taking the filename from an untrusted source is fine here. The +standard (RFC 6066 Section 3) says that the Server Name Indication +field is a DNS "A label". Which means that there are a limited number +of characters allowed: + +* `.`, `-`, `a-Z`, `A-Z`, `0-9` + +If the SNI contain anything else, the TLS connection is rejected. + +Note that if session resumption is enabled for RadSec, the session +cache *must* also cache the `TLS-Server-Name-Indication` attribute. +The SNI is sent on resumption for TLS 1.2 and earlier, but it is not +sent for TLS 1.3. As such, the only way to select the right policy on +resumption is to check the value of the cached +TLS-Server-Name-Indication attribute. + diff --git a/raddb/certs/server.cnf b/raddb/certs/server.cnf new file mode 100644 index 0000000..daca18d --- /dev/null +++ b/raddb/certs/server.cnf @@ -0,0 +1,72 @@ +[ ca ] +default_ca = CA_default + +[ CA_default ] +dir = ./ +certs = $dir +crl_dir = $dir/crl +database = $dir/index.txt +new_certs_dir = $dir +certificate = $dir/server.pem +serial = $dir/serial +crl = $dir/crl.pem +private_key = $dir/server.key +RANDFILE = $dir/.rand +name_opt = ca_default +cert_opt = ca_default +default_days = 60 +default_crl_days = 30 +default_md = sha256 +preserve = no +policy = policy_match +copy_extensions = copy + +[ policy_match ] +countryName = match +stateOrProvinceName = match +organizationName = match +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ policy_anything ] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = supplied +emailAddress = optional + +[ req ] +prompt = no +distinguished_name = server +default_bits = 2048 +input_password = whatever +output_password = whatever +#req_extensions = v3_req + +[server] +countryName = FR +stateOrProvinceName = Radius +localityName = Somewhere +organizationName = Example Inc. +emailAddress = admin@example.org +commonName = "Example Server Certificate" + +[ v3_req ] +basicConstraints = CA:FALSE +keyUsage = nonRepudiation, digitalSignature, keyEncipherment +subjectAltName = @alt_names +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer:always + +# This should be a host name of the RADIUS server. +# Note that the host name is exchanged in EAP *before* +# the user machine has network access. So the host name +# here doesn't really have to match anything in DNS. +[alt_names] +DNS.1 = radius.example.com + +# NAIRealm from RFC 7585 +otherName.0 = 1.3.6.1.5.5.7.8.8;FORMAT:UTF8,UTF8:*.example.com diff --git a/raddb/certs/xpextensions b/raddb/certs/xpextensions new file mode 100644 index 0000000..ae87f42 --- /dev/null +++ b/raddb/certs/xpextensions @@ -0,0 +1,75 @@ +# +# File containing the OIDs required for Windows +# and iOS +# +# http://support.microsoft.com/kb/814394/en-us +# +# https://support.apple.com/en-us/HT210176 +# +[ xpclient_ext] +extendedKeyUsage = 1.3.6.1.5.5.7.3.2 +crlDistributionPoints = URI:http://www.example.com/example_ca.crl + +[ xpserver_ext] +extendedKeyUsage = 1.3.6.1.5.5.7.3.1 +crlDistributionPoints = URI:http://www.example.com/example_ca.crl + +# Enterprise Wi-Fi clients from 2020 onwards which have the +# Wi-Fi Certified WPA3 Release 2 (December 2019) certification +# honour the following two policies for enhanced security +# posture regarding certificate validation: +# +# https://www.wi-fi.org/discover-wi-fi/security +# +# Adding the 'Trust Override Disabled - STRICT' policy means that +# the client device is not allowed to request and accept ad-hoc +# trust decisions from the user ("Is this the certificate you +# expect here?") and instead aborts authentication until the +# device has been properly configured using out-of-band means +# with all the details needed to verify the certificate (i.e. +# either the tuple (CA, server name) or the literal server cert). +# +# Adding the 'Trust Override Disabled - TOFU' policy means that +# the client device is allowed to ask the end user for such an +# override exactly once, when first connecting to an unknown +# network. Once the network is known and the trust decision made, +# any other certificate that is presented and would require +# another override is rejected and authentication aborted. +# +# Both of these policies provide a protection against rogue +# authentication servers in that they make sure configurations +# on end user devices are sufficient to identify the genuine +# server. +# +# The difference is that the TOFU policy allows a leap of faith +# on first sight of a network ONCE - very much comparable to +# how SSH establishes trust in a new host. This adds convenience +# for end users who did not bother to configure their devices +# beforehand, but adds an element of uncertainty in that the +# attacker could be present on that first contact with the network. +# +# Network administrators who consider the TOFU leap of faith +# unacceptable should choose STRICT; everyone else gains security +# by choosing TOFU without giving up on convenience for their +# end users. +# +# For completeness, it is also possible to include none of the +# two to stay with the "anything goes" that was the situation +# prior to Wi-Fi Certified WPA3 Release December 2019. +# +# This is the 'Trust Override Disabled - STRICT' policy. +#certificatePolicies = 1.3.6.1.4.1.40808.1.3.1 +# This is the 'Trust Override Disabled - TOFU' policy. +certificatePolicies = 1.3.6.1.4.1.40808.1.3.2 + +# +# Add this to the PKCS#7 keybag attributes holding the client's private key +# for machine authentication. +# +# the presence of this OID tells Windows XP that the cert is intended +# for use by the computer itself, and not by an end-user. +# +# The other solution is to use Microsoft's web certificate server +# to generate these certs. +# +# 1.3.6.1.4.1.311.17.2 diff --git a/raddb/clients.conf b/raddb/clients.conf new file mode 100644 index 0000000..60f9f4b --- /dev/null +++ b/raddb/clients.conf @@ -0,0 +1,288 @@ +# -*- text -*- +## +## clients.conf -- client configuration directives +## +## $Id$ + +####################################################################### +# +# Define RADIUS clients (usually a NAS, Access Point, etc.). + +# +# Defines a RADIUS client. +# +# '127.0.0.1' is another name for 'localhost'. It is enabled by default, +# to allow testing of the server after an initial installation. If you +# are not going to be permitting RADIUS queries from localhost, we suggest +# that you delete, or comment out, this entry. +# +# + +# +# Each client has a "short name" that is used to distinguish it from +# other clients. +# +# In version 1.x, the string after the word "client" was the IP +# address of the client. In 2.0, the IP address is configured via +# the "ipaddr" or "ipv6addr" fields. For compatibility, the 1.x +# format is still accepted. +# +client localhost { + # Only *one* of ipaddr, ipv4addr, ipv6addr may be specified for + # a client. + # + # ipaddr will accept IPv4 or IPv6 addresses with optional CIDR + # notation '/<mask>' to specify ranges. + # + # ipaddr will accept domain names e.g. example.org resolving + # them via DNS. + # + # If both A and AAAA records are found, A records will be + # used in preference to AAAA. + ipaddr = 127.0.0.1 + + # Same as ipaddr but allows v4 addresses only. Requires A + # record for domain names. +# ipv4addr = * # any. 127.0.0.1 == localhost + + # Same as ipaddr but allows v6 addresses only. Requires AAAA + # record for domain names. +# ipv6addr = :: # any. ::1 == localhost + + # + # A note on DNS: We STRONGLY recommend using IP addresses + # rather than host names. Using host names means that the + # server will do DNS lookups when it starts, making it + # dependent on DNS. i.e. If anything goes wrong with DNS, + # the server won't start! + # + # The server also looks up the IP address from DNS once, and + # only once, when it starts. If the DNS record is later + # updated, the server WILL NOT see that update. + # + + # + # The transport protocol. + # + # If unspecified, defaults to "udp", which is the traditional + # RADIUS transport. It may also be "tcp", in which case the + # server will accept connections from this client ONLY over TCP. + # + proto = * + + # + # The shared secret use to "encrypt" and "sign" packets between + # the NAS and FreeRADIUS. You MUST change this secret from the + # default, otherwise it's not a secret any more! + # + # The secret can be any string, up to 8k characters in length. + # + # Control codes can be entered vi octal encoding, + # e.g. "\101\102" == "AB" + # Quotation marks can be entered by escaping them, + # e.g. "foo\"bar" + # + # A note on security: The security of the RADIUS protocol + # depends COMPLETELY on this secret! We recommend using a + # shared secret that is composed of: + # + # upper case letters + # lower case letters + # numbers + # + # And is at LEAST 8 characters long, preferably 16 characters in + # length. The secret MUST be random, and should not be words, + # phrase, or anything else that is recognisable. + # + # The default secret below is only for testing, and should + # not be used in any real environment. + # + secret = testing123 + + # + # Old-style clients do not send a Message-Authenticator + # in an Access-Request. RFC 5080 suggests that all clients + # SHOULD include it in an Access-Request. The configuration + # item below allows the server to require it. If a client + # is required to include a Message-Authenticator and it does + # not, then the packet will be silently discarded. + # + # allowed values: yes, no + require_message_authenticator = no + + # + # The short name is used as an alias for the fully qualified + # domain name, or the IP address. + # + # It is accepted for compatibility with 1.x, but it is no + # longer necessary in >= 2.0 + # +# shortname = localhost + + # + # the following three fields are optional, but may be used by + # checkrad.pl for simultaneous use checks + # + + # + # The nas_type tells 'checkrad.pl' which NAS-specific method to + # use to query the NAS for simultaneous use. + # + # Permitted NAS types are: + # + # cisco + # computone + # livingston + # juniper + # max40xx + # multitech + # netserver + # pathras + # patton + # portslave + # tc + # usrhiper + # other # for all other types + + # + nas_type = other # localhost isn't usually a NAS... + + # + # The following two configurations are for future use. + # The 'naspasswd' file is currently used to store the NAS + # login name and password, which is used by checkrad.pl + # when querying the NAS for simultaneous use. + # +# login = !root +# password = someadminpas + + # + # As of 2.0, clients can also be tied to a virtual server. + # This is done by setting the "virtual_server" configuration + # item, as in the example below. + # +# virtual_server = home1 + + # + # A pointer to the "home_server_pool" OR a "home_server" + # section that contains the CoA configuration for this + # client. For an example of a coa home server or pool, + # see raddb/sites-available/originate-coa +# coa_server = coa + + # + # Response window for proxied packets. If non-zero, + # then the lower of (home, client) response_window + # will be used. + # + # i.e. it can be used to lower the response_window + # packets from one client to a home server. It cannot + # be used to raise the response_window. + # +# response_window = 10.0 + + # + # Connection limiting for clients using "proto = tcp". + # + # This section is ignored for clients sending UDP traffic + # + limit { + # + # Limit the number of simultaneous TCP connections from a client + # + # The default is 16. + # Setting this to 0 means "no limit" + max_connections = 16 + + # The per-socket "max_requests" option does not exist. + + # + # The lifetime, in seconds, of a TCP connection. After + # this lifetime, the connection will be closed. + # + # Setting this to 0 means "forever". + lifetime = 0 + + # + # The idle timeout, in seconds, of a TCP connection. + # If no packets have been received over the connection for + # this time, the connection will be closed. + # + # Setting this to 0 means "no timeout". + # + # We STRONGLY RECOMMEND that you set an idle timeout. + # + idle_timeout = 30 + } +} + +# IPv6 Client +client localhost_ipv6 { + ipv6addr = ::1 + secret = testing123 +} + +# All IPv6 Site-local clients +#client sitelocal_ipv6 { +# ipv6addr = fe80::/16 +# secret = testing123 +#} + +#client example.org { +# ipaddr = radius.example.org +# secret = testing123 +#} + +# +# You can now specify one secret for a network of clients. +# When a client request comes in, the BEST match is chosen. +# i.e. The entry from the smallest possible network. +# +#client private-network-1 { +# ipaddr = 192.0.2.0/24 +# secret = testing123-1 +#} + +#client private-network-2 { +# ipaddr = 198.51.100.0/24 +# secret = testing123-2 +#} + +####################################################################### +# +# Per-socket client lists. The configuration entries are exactly +# the same as above, but they are nested inside of a section. +# +# You can have as many per-socket client lists as you have "listen" +# sections, or you can re-use a list among multiple "listen" sections. +# +# Un-comment this section, and edit a "listen" section to add: +# "clients = per_socket_clients". That IP address/port combination +# will then accept ONLY the clients listed in this section. +# +# There are additional considerations when using clients from SQL. +# +# A client can be link to a virtual server via modules such as SQL. +# This link is done via the following process: +# +# If there is no listener in a virtual server, SQL clients are added +# to the global list for that virtual server. +# +# If there is a listener, and the first listener does not have a +# "clients=..." configuration item, SQL clients are added to the +# global list. +# +# If there is a listener, and the first one does have a "clients=..." +# configuration item, SQL clients are added to that list. The client +# { ...} ` configured in that list are also added for that listener. +# +# The only issue is if you have multiple listeners in a virtual +# server, each with a different client list, then the SQL clients are +# added only to the first listener. +# +#clients per_socket_clients { +# client socket_client { +# ipaddr = 192.0.2.4 +# secret = testing123 +# } +#} diff --git a/raddb/debug.conf b/raddb/debug.conf new file mode 100644 index 0000000..99f0725 --- /dev/null +++ b/raddb/debug.conf @@ -0,0 +1,9 @@ +# +# Minimal configuration file used when debugging the source code. +# +# This file should not be installed by "make install", or by any +# packaging system. It is only for developers. +# +#libdir = ${confdir}/../build/lib/ + +$INCLUDE ${confdir}/radiusd.conf diff --git a/raddb/dictionary b/raddb/dictionary new file mode 100644 index 0000000..ad56be7 --- /dev/null +++ b/raddb/dictionary @@ -0,0 +1,49 @@ +# +# This is the local dictionary file which can be +# edited by local administrators. It will be loaded +# AFTER the main dictionary files are loaded. +# +# FreeRADIUS will automatically load the main dictionary files +# from: +# +# ${prefix}/share/freeradius/dictionary +# +# It is no longer necessary for this file to $INCLUDE +# the main dictionaries. However, if the $INCLUDE +# line is here, nothing bad will happen. +# +# Any new/changed attributes MUST be placed in this file. +# The pre-defined dictionaries SHOULD NOT be edited. +# +# See "man dictionary" for documentation on its format. +# +# $Id$ +# + +# +# All local attributes and $INCLUDE's should go into +# this file. +# + +# If you want to add entries to the dictionary file, +# which are NOT going to be placed in a RADIUS packet, +# add them to the 'dictionary.local' file. +# +# The numbers you pick should be between 3000 and 4000. +# These attributes will NOT go into a RADIUS packet. +# +# If you want that, you will need to use VSAs. This means +# requesting allocation of a Private Enterprise Code from +# http://iana.org. We STRONGLY suggest doing that only if +# you are a vendor of RADIUS equipment. +# +# See RFC 6158 for more details. +# http://ietf.org/rfc/rfc6158.txt +# + +# +# These attributes are examples +# +#ATTRIBUTE My-Local-String 3000 string +#ATTRIBUTE My-Local-IPAddr 3001 ipaddr +#ATTRIBUTE My-Local-Integer 3002 integer diff --git a/raddb/experimental.conf b/raddb/experimental.conf new file mode 100644 index 0000000..87d9744 --- /dev/null +++ b/raddb/experimental.conf @@ -0,0 +1,116 @@ +# +# This file contains the configuration for experimental modules. +# +# By default, it is NOT included in the build. +# +# $Id$ +# + +# Configuration for the Python module. +# +# Where radiusd is a Python module, radiusd.py, and the +# function 'authorize' is called. Here is a dummy piece +# of code: +# +# def authorize(params): +# print params +# return (5, ('Reply-Message', 'banned')) +# +# The RADIUS value-pairs are passed as a tuple of tuple +# pairs as the first argument, e.g. (('attribute1', +# 'value1'), ('attribute2', 'value2')) +# +# The function return is a tuple with the first element +# being the return value of the function. +# The 5 corresponds to RLM_MODULE_USERLOCK. I plan to +# write the return values as Python symbols to avoid +# confusion. +# +# The remaining tuple members are the string form of +# value-pairs which are passed on to pairmake(). +# +python { + mod_instantiate = radiusd_test + func_instantiate = instantiate + + mod_authorize = radiusd_test + func_authorize = authorize + + mod_accounting = radiusd_test + func_accounting = accounting + + mod_pre_proxy = radiusd_test + func_pre_proxy = pre_proxy + + mod_post_proxy = radiusd_test + func_post_proxy = post_proxy + + mod_post_auth = radiusd_test + func_post_auth = post_auth + + mod_recv_coa = radiusd_test + func_recv_coa = recv_coa + + mod_send_coa = radiusd_test + func_send_coa = send_coa + + mod_detach = radiusd_test + func_detach = detach +} + + +# Configuration for the example module. Uncommenting it will cause it +# to get loaded and initialised, but should have no real effect as long +# it is not referenced in one of the autz/auth/preacct/acct sections +example { + # Boolean variable. + # allowed values: {no, yes} + boolean = yes + + # An integer, of any value. + integer = 16 + + # A string. + string = "This is an example configuration string" + + # An IP address, either in dotted quad (1.2.3.4) or hostname + # (example.com) + ipaddr = 127.0.0.1 + + # A subsection + mysubsection { + anotherinteger = 1000 + # They nest + deeply nested { + string = "This is a different string" + } + } +} + +# +# To create a dbm users file, do: +# +# cat test.users | rlm_dbm_parser -f /etc/raddb/users_db +# +# Then add 'dbm' in 'authorize' section. +# +# Note that even if the file has a ".db" or ".dbm" extension, +# you may have to specify it here without that extension. This +# is because the DBM libraries "helpfully" add a ".db" to the +# filename, but don't check if it's already there. +# +dbm { + usersfile = ${confdir}/users_db +} + +# Instantiate a couple instances of the idn module +idn { +} + +# ...more commonly known as... +idn idna { +} + +idn idna_lenient { + UseSTD3ASCIIRules = no +} diff --git a/raddb/home_servers/README.md b/raddb/home_servers/README.md new file mode 100644 index 0000000..01267b8 --- /dev/null +++ b/raddb/home_servers/README.md @@ -0,0 +1,21 @@ +# Dynamic Home Servers + +This directory is where dynamic home servers are stored. + +Each file in the directory should be named for the home server domain +name. In the above example, the filename should be +`${raddb}/home_servers/example.com`. The name of the home server in +the file should be the same as the filename which contains the home +server definition. + +Each file in the directory should have one, and only one, +`home_server` definition. + +See doc/configuration/dynamic_home_servers.md for more information on +dynamic home_servers. + +See also `mods-config/realm/freeradius-naptr-to-home-server.sh` for a +sample shell script which creates home servers. + +This directory also has a `tls.conf` file which contains site-specific +TLS configuration for home servers. diff --git a/raddb/home_servers/tls.conf b/raddb/home_servers/tls.conf new file mode 100644 index 0000000..7a0a61c --- /dev/null +++ b/raddb/home_servers/tls.conf @@ -0,0 +1,58 @@ +# +# This file contains the configuration for the "outgoing" +# radsec connections. It should be included by all of the +# dynamic home server configuration files. +# +# This file should be customized for your local system. +# +# See sites-available/tls for an example of configuring a home_server +# with TLS. + + # + # The server does not (yet) support RadSec over DTLS. + # + proto = tcp + + # + # Use "auth" for Eduroam, as it does not do accounting. + # + # Other sites may allow "auth+acct". + # + type = auth + + # + # The secret for RadSec is ALWAYS "radsec". + # + secret = radsec + + # + # Similarly to HTTP, the client can use Server Name + # Indication to inform the RadSec server as to which + # domain it is requesting. This selection allows + # multiple sites to exist at the same IP address. + # + # This configuration sets the hostname sent in SNI. + # +# hostname = example.org + + # + # Outbound radsec requires a "tls" subsection. + # + tls { + # + # This is the *client* certificate used to connect outbound to the radsec server. + # + # It MUST be signed by a CA which is known to the radsec server. + # + certificate_file = ${certdir}/radsec-client.pem + + private_key_file = ${certdir}/radsec-client.key + private_key_password = whatever + + ca_path = ${cadir} + + # + # See sites-available/tls, and the "home_server tls" subsection for more + # documentation on which configuration items are allowed here. + # + } diff --git a/raddb/mods-available/README.rst b/raddb/mods-available/README.rst new file mode 100644 index 0000000..79ed5c3 --- /dev/null +++ b/raddb/mods-available/README.rst @@ -0,0 +1,116 @@ +Modules in Version 3 +==================== + +As of Version 3, all of the modules have been placed in the +"mods-available/" directory. This practice follows that used by other +servers such as Nginx, Apache, etc. The "modules" directory should +not be used. + +Modules are enabled by creating a file in the mods-enabled/ directory. +You can also create a soft-link from one directory to another:: + + $ cd raddb/mods-enabled + $ ln -s ../mods-available/foo + +This will enable module "foo". Be sure that you have configured the +module correctly before enabling it, otherwise the server will not +start. You can verify the server configuration by running +"radiusd -XC". + +A large number of modules are enabled by default. This allows the +server to work with the largest number of authentication protocols. +Please be careful when disabling modules. You will likely need to +edit the "sites-enabled/" files to remove references to any disabled +modules. + +Conditional Modules +------------------- + +Version 3 allows modules to be conditionally loaded. This is useful +when you want to have a virtual server which references a module, but +does not require it. Instead of editing the virtual server file, you +can just conditionally enable the module. + +Modules are conditionally enabled by adding a "-" before their name in +a virtual server. For example, you can do:: + + server { + authorize { + ... + ldap + -sql + ... + } + } + +This says "require the LDAP module, but use the SQL module only if it +is configured." + +This feature is not very useful for production configurations. It is, +however, very useful for the default examples that ship with the +server. + +Ignoring module +--------------- + +If you see this message:: + + Ignoring module (see raddb/mods-available/README.rst) + +Then you are in the right place. Most of the time this message can be +ignored. The message can be fixed by finding the references to "-module" +in the virtual server, and deleting them. + +Another way to fix it is to configure the module, as described above. + +Simplification +-------------- + +Allowing conditional modules simplifies the default virtual servers +that are shipped with FreeRADIUS. This means that if you want to +enable LDAP (for example), you no longer need to edit the files in +raddb/sites-available/ in order to enable it. + +Instead, you should edit the raddb/mods-available/ldap file to point +to your local LDAP server. Then, enable the module via the soft-link +method described above. + +Once the module is enabled, it will automatically be used in the +default configuration. + +Multiple Instances +------------------ + +It is sometimes necessary to have the same module do two different +things. The server supports this functionality via "instances" of +modules. + +Normally, a module configuration looks like this: + + sql { + ... sql stuff ... + } + +This module is then refereed to as the "sql" module. + + +But what happens if you want to connect to two different SQL +databases? The solution is simple; copy the "sql" module +configuration, and add an instance name after the "sql" string: + + sql mysql1 { + ... configuration for connecting to mysql11 ... + } + + sql mysql2 { + ... configuration for connecting to mysql12 ... + } + +This configuration says "load the SQL module, but create two copies of +it, with different configurations". The different configurations can +be referred to by name, as "mysql1" and "mysql2". That is, anywhere +you would normally use "sql", you could use either "mysql1" or +"mysql2". + +For further examples of using module instances, see the "attr_filter" +module configuration in this directory. diff --git a/raddb/mods-available/abfab_psk_sql b/raddb/mods-available/abfab_psk_sql new file mode 100644 index 0000000..d75130d --- /dev/null +++ b/raddb/mods-available/abfab_psk_sql @@ -0,0 +1,15 @@ +# -*- text -*- +## +## Module for PSK authorizations from ABFAB trust router +## +## $Id$ + +sql psksql { + + driver = "rlm_sql_sqlite" + + sqlite { + filename = "/var/lib/trust_router/keys" + } + +} diff --git a/raddb/mods-available/always b/raddb/mods-available/always new file mode 100644 index 0000000..b77d00c --- /dev/null +++ b/raddb/mods-available/always @@ -0,0 +1,81 @@ +# -*- text -*- +# +# $Id$ + +# +# The "always" module is here for debugging purposes, or +# for use in complex policies. +# Instance simply returns the same result, always, without +# doing anything. +# +# rcode may be one of the following values: +# - reject - Reject the user. +# - fail - Simulate or indicate a failure. +# - ok - Simulate or indicate a success. +# - handled - Indicate that the request has been handled, +# stop processing, and send response if set. +# - invalid - Indicate that the request is invalid. +# - userlock - Indicate that the user account has been +# locked out. +# - notfound - Indicate that a user account can't be found. +# - noop - Simulate a no-op. +# - updated - Indicate that the request has been updated. +# +# If an instance is listed in a session {} section, +# this simulates a user having <integer> sessions. +# +# simulcount = <integer> +# +# If an instance is listed in a session {} section, +# this simulates the user having multilink +# sessions. +# +# mpp = <integer> +# +# An xlat based on the instance name can be called to change the status +# returned by the instance, in this example "always db_status { ... }" +# +# Force the module status to be alive or dead: +# +# %{db_status:alive} +# %{db_status:dead} +# +# Update the rcode returned by an alive module (a dead module returns fail): +# +# %{db_status:ok} +# %{db_status:fail} +# %{db_status:notfound} +# ... +# +# The above xlats expand to the current status of the module. To fetch the +# current status without affecting it call the xlat with an empty argument: +# +# %{db_status:} +# +always reject { + rcode = reject +} +always fail { + rcode = fail +} +always ok { + rcode = ok +} +always handled { + rcode = handled +} +always invalid { + rcode = invalid +} +always userlock { + rcode = userlock +} +always notfound { + rcode = notfound +} +always noop { + rcode = noop +} +always updated { + rcode = updated +} diff --git a/raddb/mods-available/attr_filter b/raddb/mods-available/attr_filter new file mode 100644 index 0000000..a23d3c0 --- /dev/null +++ b/raddb/mods-available/attr_filter @@ -0,0 +1,61 @@ +# -*- text -*- +# +# $Id$ + +# +# This file defines a number of instances of the "attr_filter" module. +# + +# attr_filter - filters the attributes received in replies from +# proxied servers, to make sure we send back to our RADIUS client +# only allowed attributes. +attr_filter attr_filter.post-proxy { + key = "%{Realm}" + filename = ${modconfdir}/${.:name}/post-proxy +} + +# attr_filter - filters the attributes in the packets we send to +# the RADIUS home servers. +attr_filter attr_filter.pre-proxy { + key = "%{Realm}" + filename = ${modconfdir}/${.:name}/pre-proxy +} + +# Enforce RFC requirements on the contents of Access-Reject +# packets. See the comments at the top of the file for +# more details. +# +attr_filter attr_filter.access_reject { + key = "%{User-Name}" + filename = ${modconfdir}/${.:name}/access_reject +} + +# Enforce RFC requirements on the contents of Access-Challenge +# packets. See the comments at the top of the file for +# more details. +# +attr_filter attr_filter.access_challenge { + key = "%{User-Name}" + filename = ${modconfdir}/${.:name}/access_challenge +} + + +# Enforce RFC requirements on the contents of the +# Accounting-Response packets. See the comments at the +# top of the file for more details. +# +attr_filter attr_filter.accounting_response { + key = "%{User-Name}" + filename = ${modconfdir}/${.:name}/accounting_response +} + +# +# Enforce CoA or Disconnect packets. +# +# Note that you MUST edit the "coa" file below for your +# local configuration. Add in any attributes needed by the NAS. +# +attr_filter attr_filter.coa { + key = "%{User-Name}" + filename = ${modconfdir}/${.:name}/coa +} diff --git a/raddb/mods-available/cache b/raddb/mods-available/cache new file mode 100644 index 0000000..cf0054f --- /dev/null +++ b/raddb/mods-available/cache @@ -0,0 +1,150 @@ +# -*- text -*- +# +# $Id$ + +# +# A module to cache attributes. The idea is that you can look +# up information in a database, and then cache it. Repeated +# requests for the same information will then have the cached +# values added to the request. +# +# The module can cache a fixed set of attributes per key. +# It can be listed in "authorize", "post-auth", "pre-proxy" +# and "post-proxy". +# +# If you want different things cached for authorize and post-auth, +# you will need to define two instances of the "cache" module. +# +# The module returns "ok" if it found or created a cache entry. +# The module returns "updated" if it merged a cached entry. +# The module returns "noop" if it did nothing. +# The module returns "fail" on error. +# +cache { + # The backend datastore used to store the cache entries. + # Current datastores are + # rlm_cache_rbtree - An in memory, non persistent rbtree based datastore. + # Useful for caching data locally. + # rlm_cache_memcached - A non persistent "webscale" distributed datastore. + # Useful if the cached data need to be shared between + # a cluster of RADIUS servers. + # rlm_cache_redis - uses Redis. +# driver = "rlm_cache_rbtree" + + # + # Some drivers accept specific options, to set them a + # config section with the the name as the driver should be added + # to the cache instance. + # + # Driver specific options are: + # +# memcached { +# # Memcached configuration options, as documented here: +# # http://docs.libmemcached.org/libmemcached_configuration.html#memcached +# options = "--SERVER=localhost" +# +# pool { +# start = ${thread[pool].start_servers} +# min = ${thread[pool].min_spare_servers} +# max = ${thread[pool].max_servers} +# spare = ${thread[pool].max_spare_servers} +# uses = 0 +# lifetime = 0 +# idle_timeout = 60 +# } +# } + + # + # See mods-available/redis for documentation on the following + # configuration items. They are identical here. + # + # Note that the "pool" section can re-use the normal Redis + # connections. This is done by setting the "pool" configuration + # item to the name of the Redis module. The other configuration + # items should then be the same as for the original "redis" module. + # +# redis { +# server = ... +# port = +# database = +# query_timeout = ... +# pool = redis +# } + + # The key used to index the cache. It is dynamically expanded + # at run time. + key = "%{User-Name}" + + # The TTL of cache entries, in seconds. Entries older than this + # will be expired. + # + # This value should be between 10 and 86400. + ttl = 10 + + # If yes the following attributes will be added to the request: + # * &request:Cache-Entry-Hits - The number of times this entry + # has been retrieved. + # + # Note: Not supported by the rlm_cache_memcached module. + add_stats = no + + # + # The list of attributes to cache for a particular key. + # + # Each key gets the same set of cached attributes. The attributes + # are dynamically expanded at run time. + # + # The semantics of this construct are identical to an unlang + # update block, except the left hand side of the expression + # represents the cache entry. see man unlang for more information + # on update blocks. + # + # Note: Only request, reply, control and session-state lists + # are available in cache entries. Attempting to store attributes + # in other lists will raise an error during config validation. + # + update { + # <list>:<attribute> <op> <value> + + # Cache all instances of Reply-Message in the reply list + &reply:Reply-Message += &reply:Reply-Message[*] + + # Add our own to show when the cache was last updated + &reply:Reply-Message += "Cache last updated at %t" + + &reply:Class := "%{randstr:ssssssssssssssssssssssssssssssss}" + } + + # This module supports a number of runtime configuration parameters + # represented by attributes in the &control: list. + # + # &control:Cache-TTL - Sets the TTL of an entry to be created, or + # modifies the TTL of an existing entry. + # - Setting a Cache-TTL of > 0 means set the TTL of the entry to + # the new value (and reset the expiry timer). + # - Setting a Cache-TTL of < 0 means expire the existing entry + # (without merging) and create a new one with TTL set to + # value * -1. + # - Setting a Cache-TTL of 0 means expire the existing entry + # (without merging) and don't create a new one. + # + # &control:Cache-Status-Only - If present and set to 'yes' will + # prevent a new entry from being created, and existing entries from + # being merged. It will also alter the module's return codes. + # - The module will return "ok" if a cache entry was found. + # - The module will return "notfound" if no cache entry was found. + # + # &control:Cache-Read-Only - If present and set to 'yes' will + # prevent a new entry from being created, but will allow existing + # entries to be merged. It will also alter the module's return codes. + # - The module will return "updated" if a cache entry was found. + # - The module will return "notfound" if no cache was found. + # + # &control:Cache-Merge - If present and set to 'yes' will merge new + # cache entries into the current request. Useful if results + # of execs or expansions are stored directly in the cache. + # + # All runtime configuration attributes will be removed from the + # &control: list after the cache module is called. + +} diff --git a/raddb/mods-available/cache_auth b/raddb/mods-available/cache_auth new file mode 100644 index 0000000..7485f36 --- /dev/null +++ b/raddb/mods-available/cache_auth @@ -0,0 +1,116 @@ +# -*- text -*- +# +# $Id$ + +# This file contains a collection of cache module configurations +# which have been designed to be used to cache accepts, rejects, and +# LDAP User DNs. The main use of these modules is Google Secure +# LDAP. +# +# In scenarios where there is repeated authentication requests for the same +# user within a short time frame (e.g. 802.1x wifi), these modules can help to +# compensate for slow responses from poor LDAP servers (i.e. Google). +# +# See also mods-available/ldap_google, and sites-available/google-ldap-auth. +# +# The configurations in this file can be used for non-Google LDAP +# servers, too. +# + + +# +# This instance of the cache module caches successful +# authentications. +# +# The TTL controls how often the authentication will be cached. +# +# In addition, if group membership is used as part of the policy, the +# &control:LDAP-Group attribute should be added to the "update: section here. +# +# If a user's authentication is found in the cache, then any data +# which is normally retrieved from LDAP for local policies must also +# be stored in the cache via the "update" section. +# +cache cache_auth_accept { + driver = "rlm_cache_rbtree" + key = "%{md5:%{%{Stripped-User-Name}:-%{User-Name}}%{User-Password}}" + ttl = 7200 + update { + # + # We need to cache something, so we just cache + # a random attribute. This attribute is not used + # for anything else, just as a "place-holder" to + # contain a cache entry. + # + # If you add other attributes to this update section, then + # this attribute can be deleted. + # + &control:User-Category = "success" + } +} + + +# +# This instance of the cache module caches failed authentications. +# +# In many cases, rejected users will repeatedly try to authenticate. +# These repeated authentication attempts can cause significant load +# on the system. By caching the reject, we can avoid hitting the database. +# +# We index the cache by a hash of the client's MAC and the user name +# and password. If a user corrects their user name or password, then +# that authentication attempt won't hit the cache, and their +# credentials will be immediately checked against the database. +# +# The TTL controls how long a combination of device / user and +# password wil be rejected without looking at the database. Once the +# cache entry expires, the server will delete the cache entry, and +# contact the database. +# +cache cache_auth_reject { + driver = "rlm_cache_rbtree" + key = "%{md5:%{Calling-Station-Id}%{Stripped-User-Name}%{User-Password}}" + ttl = 3600 + update { + # + # We need to cache something, so we just cache + # a random attribute. This attribute is not used + # for anything else, just as a "place-holder" to + # contain a cache entry. + # + &control:User-Category = "failure" + } +} + + +# +# An instance of the cache module which caches the LDAP user DN. +# +# If LDAP authentication is being used for a simple auth / reject without +# any need to retrieve other attributes (e.g. group membership), each LDAP +# bind authentication is three steps +# +# - bind as admin user +# - lookup user's DN +# - bind as user using retrieved DN +# +# By caching the DN after the first LDAP querry, the first two steps +# are skipped on subsequent authentications. +# +# If an alternative attribute name is being used for the user DN, you +# should change the update section here appropriately. But that is +# likely rare. +# +# In scenarios where DNs may change, consideration should be given as +# to whether use of this cache may create issues. i.e. if the cache +# doesn't help, then don't use it. +# +cache cache_ldap_user_dn { + driver = "rlm_cache_rbtree" + key = "%{Stripped-User-Name}" + ttl = 86400 + update { + &control:LDAP-UserDN = &control:LDAP-UserDN + } +} + diff --git a/raddb/mods-available/chap b/raddb/mods-available/chap new file mode 100644 index 0000000..e2a3cd3 --- /dev/null +++ b/raddb/mods-available/chap @@ -0,0 +1,11 @@ +# -*- text -*- +# +# $Id$ + +# CHAP module +# +# To authenticate requests containing a CHAP-Password attribute. +# +chap { + # no configuration +} diff --git a/raddb/mods-available/couchbase b/raddb/mods-available/couchbase new file mode 100644 index 0000000..da1a39d --- /dev/null +++ b/raddb/mods-available/couchbase @@ -0,0 +1,204 @@ +couchbase { + # + # List of Couchbase hosts (hosts may be space, tab, comma or semi-colon separated). + # Ports are optional if servers are listening on the standard port. + # Complete pool urls are preferred. + # + server = "http://cb01.blargs.com:8091/pools/ http://cb04.blargs.com:8091/pools/" + + # Couchbase bucket name + bucket = "radius" + + # Couchbase bucket password (optional) + #password = "password" + + # Couchbase accounting document key (unlang supported) + acct_key = "radacct_%{%{Acct-Unique-Session-Id}:-%{Acct-Session-Id}}" + + # Value for the 'docType' element in the json body for accounting documents + doctype = "radacct" + + ## Accounting document expire time in seconds (0 = never) + expire = 2592000 + + # + # Map attribute names to json element names for accounting. + # + # Configuration items are in the format: + # <radius attribute> = '<element name>' + # + # Element names should be single quoted. + # + # Note: Attributes not in this map will not be recorded. + # + update { + Acct-Session-Id = 'sessionId' + Acct-Unique-Session-Id = 'uniqueId' + Acct-Status-Type = 'lastStatus' + Acct-Authentic = 'authentic' + User-Name = 'userName' + Stripped-User-Name = 'strippedUserName' + Stripped-User-Domain = 'strippedUserDomain' + Realm = 'realm' + NAS-IP-Address = 'nasIpAddress' + NAS-Identifier = 'nasIdentifier' + NAS-Port = 'nasPort' + Called-Station-Id = 'calledStationId' + Called-Station-SSID = 'calledStationSSID' + Calling-Station-Id = 'callingStationId' + Framed-Protocol = 'framedProtocol' + Framed-IP-Address = 'framedIpAddress' + NAS-Port-Type = 'nasPortType' + Connect-Info = 'connectInfo' + Acct-Session-Time = 'sessionTime' + Acct-Input-Packets = 'inputPackets' + Acct-Output-Packets = 'outputPackets' + Acct-Input-Octets = 'inputOctets' + Acct-Output-Octets = 'outputOctets' + Acct-Input-Gigawords = 'inputGigawords' + Acct-Output-Gigawords = 'outputGigawords' + Event-Timestamp = 'lastUpdated' + } + + # Couchbase document key for user documents (unlang supported) + user_key = "raduser_%{md5:%{tolower:%{%{Stripped-User-Name}:-%{User-Name}}}}" + + # Set to 'yes' to read radius clients from the Couchbase view specified below. + # NOTE: Clients will ONLY be read on server startup. + #read_clients = no + + # + # Map attribute names to json element names when loading clients. + # + # Configuration follows the same rules as the accounting map above. + # + client { + # Couchbase view that should return all available client documents. + view = "_design/client/_view/by_id" + + # + # Sets default values (not obtained from couchbase) for new client entries + # + template { +# login = 'test' +# password = 'test' +# proto = tcp +# require_message_authenticator = yes + + # Uncomment to add a home_server with the same + # attributes as the client. +# coa_server { +# response_window = 2.0 +# } + } + + # + # Client mappings are in the format: + # <client attribute> = '<element name>' + # + # Element names should be single quoted. + # + # The following attributes are required: + # * ipaddr | ipv4addr | ipv6addr - Client IP Address. + # * secret - RADIUS shared secret. + # + # All attributes usually supported in a client + # definition are also supported here. + # + attribute { + ipaddr = 'clientIdentifier' + secret = 'clientSecret' + shortname = 'clientShortname' + nas_type = 'nasType' + virtual_server = 'virtualServer' + require_message_authenticator = 'requireMessageAuthenticator' + limit { + max_connections = 'maxConnections' + lifetime = 'clientLifetime' + idle_timeout = 'idleTimeout' + } + } + } + + # Set to 'yes' to enable simultaneous use checking (multiple logins). + # NOTE: This will cause the execution of a view request on every check + # and may be a performance penalty. +# check_simul = no + + # Couchbase view that should return all account documents keyed by username. +# simul_view = "_design/acct/_view/by_user" + + # The key to the above view. + # NOTE: This will need to match EXACTLY what you emit from your view. +# simul_vkey = "%{tolower:%{%{Stripped-User-Name}:-%{User-Name}}}" + + # Set to 'yes' to enable verification of the results returned from the above view. + # NOTE: This may be an additional performance penalty to the actual check and + # should be avoided unless absolutely neccessary. +# verify_simul = no + + # Remove stale session if checkrad does not see a double login. + # NOTE: This will only be executed if both check_simul and verify_simul + # are set to 'yes' above. +# delete_stale_sessions = yes + + # + # The connection pool is used to pool outgoing connections. + # + pool { + # Connections to create during module instantiation. + # If the server cannot create specified number of + # connections during instantiation it will exit. + # Set to 0 to allow the server to start without the + # couchbase being available. + start = ${thread[pool].start_servers} + + # Minimum number of connections to keep open + min = ${thread[pool].min_spare_servers} + + # Maximum number of connections + # + # If these connections are all in use and a new one + # is requested, the request will NOT get a connection. + # + # Setting 'max' to LESS than the number of threads means + # that some threads may starve, and you will see errors + # like 'No connections available and at max connection limit' + # + # Setting 'max' to MORE than the number of threads means + # that there are more connections than necessary. + max = ${thread[pool].max_servers} + + # Spare connections to be left idle + # + # NOTE: Idle connections WILL be closed if "idle_timeout" + # is set. This should be less than or equal to "max" above. + spare = ${thread[pool].max_spare_servers} + + # Number of uses before the connection is closed + # + # 0 means "infinite" + uses = 0 + + # The lifetime (in seconds) of the connection + # + # NOTE: A setting of 0 means infinite (no limit). + lifetime = 0 + + # The idle timeout (in seconds). A connection which is + # unused for this length of time will be closed. + # + # NOTE: A setting of 0 means infinite (no timeout). + idle_timeout = 1200 + + # NOTE: All configuration settings are enforced. If a + # connection is closed because of "idle_timeout", + # "uses", or "lifetime", then the total number of + # connections MAY fall below "min". When that + # happens, it will open a new connection. It will + # also log a WARNING message. + # + # The solution is to either lower the "min" connections, + # or increase lifetime/idle_timeout. + } +} diff --git a/raddb/mods-available/counter b/raddb/mods-available/counter new file mode 100644 index 0000000..a5ac1e6 --- /dev/null +++ b/raddb/mods-available/counter @@ -0,0 +1,82 @@ +# -*- text -*- +# +# $Id$ + +# counter module: +# This module takes an attribute (count-attribute). +# It also takes a key, and creates a counter for each unique +# key. The count is incremented when accounting packets are +# received by the server. The value of the increment depends +# on the attribute type. +# If the attribute is Acct-Session-Time or of an integer type we add +# the value of the attribute. If it is anything else we increase the +# counter by one. +# +# The 'reset' parameter defines when the counters are all reset to +# zero. It can be hourly, daily, weekly, monthly or never. +# +# hourly: Reset on 00:00 of every hour +# daily: Reset on 00:00:00 every day +# weekly: Reset on 00:00:00 on sunday +# monthly: Reset on 00:00:00 of the first day of each month +# +# It can also be user defined. It should be of the form: +# num[hdwm] where: +# h: hours, d: days, w: weeks, m: months +# If the letter is omitted days will be assumed. In example: +# reset = 10h (reset every 10 hours) +# reset = 12 (reset every 12 days) +# +# +# The check_name attribute defines an attribute which will be +# registered by the counter module and can be used to set the +# maximum allowed value for the counter after which the user +# is rejected. +# Something like: +# +# DEFAULT Max-Daily-Session := 36000 +# Fall-Through = 1 +# +# You should add the counter module in the instantiate +# section so that it registers check_name before the files +# module reads the users file. +# +# If check_name is set and the user is to be rejected then we +# send back a Reply-Message and we log a Failure-Message in +# the radius.log +# +# If the count attribute is Acct-Session-Time then on each +# login we send back the remaining online time as a +# Session-Timeout attribute ELSE and if the reply_name is +# set, we send back that attribute. The reply_name attribute +# MUST be of an integer type. +# +# The counter-name can also be used instead of using the check_name +# like below: +# +# DEFAULT Daily-Session-Time > 3600, Auth-Type = Reject +# Reply-Message = "You've used up more than one hour today" +# +# The allowed_service_type attribute can be used to only take +# into account specific sessions. For example if a user first +# logs in through a login menu and then selects ppp there will +# be two sessions. One for Login-User and one for Framed-User +# service type. We only need to take into account the second one. +# +# The module should be added in the instantiate, authorize and +# accounting sections. Make sure that in the authorize +# section it comes after any module which sets the +# 'check_name' attribute. +# +counter daily { + filename = ${db_dir}/db.daily + key = User-Name + count_attribute = Acct-Session-Time + reset = daily + counter_name = Daily-Session-Time + check_name = Max-Daily-Session + reply_name = Session-Timeout + allowed_service_type = Framed-User + cache_size = 5000 +} + diff --git a/raddb/mods-available/cui b/raddb/mods-available/cui new file mode 100644 index 0000000..54842d4 --- /dev/null +++ b/raddb/mods-available/cui @@ -0,0 +1,53 @@ +# -*- text -*- +# +# $Id$ + +# +# Write Chargeable-User-Identity to the database. +# +# Schema raddb/mods-config/sql/cui/<DB>/schema.sql +# Queries raddb/mods-config/sql/cui/<DB>/queries.conf +# +sql cuisql { + + # The dialect of SQL you want to use, this should usually match + # the driver below. + # + # If you're using rlm_sql_null, then it should be the type of + # database the logged queries are going to be executed against. + dialect = "sqlite" + + # The sub-module to use to execute queries. This should match + # the database you're attempting to connect to. + # + # There are CUI queries available for: + # * rlm_sql_mysql + # * rlm_sql_postgresql + # * rlm_sql_sqlite + # * rlm_sql_null (log queries to disk) + # + driver = "rlm_sql_${dialect}" + + sqlite { + filename = ${radacctdir}/cui.sqlite + bootstrap = ${modconfdir}/${..:name}/cui/sqlite/schema.sql + } + + # Write CUI queries to a logfile. Useful for debugging. +# logfile = ${logdir}/cuilog.sql + + pool { + start = 5 + min = 4 + max = 10 + spare = 3 + uses = 0 + lifetime = 0 + idle_timeout = 60 + } + + cui_table = "cui" + sql_user_name = "%{User-Name}" + + $INCLUDE ${modconfdir}/${.:name}/cui/${dialect}/queries.conf +} diff --git a/raddb/mods-available/date b/raddb/mods-available/date new file mode 100644 index 0000000..25a64da --- /dev/null +++ b/raddb/mods-available/date @@ -0,0 +1,35 @@ +# +# Registers xlat to convert between time formats. +# +# xlat input string is an attribute name. If this attribute is of date +# or integer type, the date xlat will convert it to a time string in +# the format of the format config item. +# +# If the attribute is a string type, date will attempt to parse it in +# the format specified by the format config item, and will expand +# to a Unix timestamp. +# +date { + format = "%b %e %Y %H:%M:%S %Z" + + # Use UTC instead of local time. + # + # default = no +# utc = yes +} + +# +# The WISPr-Session-Terminate-Time attribute is of type "string", +# and not "date". Use this expansion to create an attribute +# that holds an actual date: +# +# Tmp-Date-0 := "%{wispr2date:&reply:WISPr-Session-Terminate-Time}" +# +date wispr2date { + format = "%Y-%m-%dT%H:%M:%S" + + # Use UTC instead of local time. + # + # default = no +# utc = yes +} diff --git a/raddb/mods-available/detail b/raddb/mods-available/detail new file mode 100644 index 0000000..ccf65f9 --- /dev/null +++ b/raddb/mods-available/detail @@ -0,0 +1,109 @@ +# -*- text -*- +# +# $Id$ + +# Write a detailed log of all accounting records received. +# +detail { + # Note that we do NOT use NAS-IP-Address here, as + # that attribute MAY BE from the originating NAS, and + # NOT from the proxy which actually sent us the + # request. + # + # The following line creates a new detail file for + # every radius client (by IP address or hostname). + # In addition, a new detail file is created every + # day, so that the detail file doesn't have to go + # through a 'log rotation' + # + # If your detail files are large, you may also want to add + # a ':%H' (see doc/configuration/variables.rst) to the end + # of it, to create a new detail file every hour, e.g.: + # + # ..../detail-%Y%m%d:%H + # + # This will create a new detail file for every hour. + # + # If you are reading detail files via the "listen" section + # (e.g. as in raddb/sites-available/robust-proxy-accounting), + # you MUST use a unique directory for each combination of a + # detail file writer, and reader. That is, there can only + # be ONE "listen" section reading detail files from a + # particular directory. + # + # The configuration below puts the detail files into separate + # directories for each client. If you are reading the detail + # files via the "listen" section, just use one directory. + # + # e.g. filename = ${radacctdir}/reader1/detail-%Y%m%d + # + # AND use a separate directory (reader2, reader3, etc.) for each + # reader. + # + filename = ${radacctdir}/%{%{Packet-Src-IP-Address}:-%{Packet-Src-IPv6-Address}}/detail-%Y%m%d + + # + # If you are using radrelay, delete the above line for "file", + # and use this one instead: + # +# filename = ${radacctdir}/detail + + # + # Most file systems can handly nearly the full range of UTF-8 + # characters. Ones that can deal with a limited range should + # set this to "yes". + # + escape_filenames = no + + # + # The Unix-style permissions on the 'detail' file. + # + # The detail file often contains secret or private + # information about users. So by keeping the file + # permissions restrictive, we can prevent unwanted + # people from seeing that information. + permissions = 0600 + + # The Unix group of the log file. + # + # The user that the server runs as must be in the specified + # system group otherwise this will fail to work. + # +# group = ${security.group} + + # + # Every entry in the detail file has a header which + # is a timestamp. By default, we use the ctime + # format (see "man ctime" for details). + # + # The header can be customised by editing this + # string. See "doc/configuration/variables.rst" for a + # description of what can be put here. + # + header = "%t" + + # + # Uncomment this line if the detail file reader will be + # reading this detail file. + # +# locking = yes + + # + # Log the Packet src/dst IP/port. This is disabled by + # default, as that information isn't used by many people. + # +# log_packet_header = yes + + # + # Certain attributes such as User-Password may be + # "sensitive", so they should not be printed in the + # detail file. This section lists the attributes + # that should be suppressed. + # + # The attributes should be listed one to a line. + # + #suppress { + # User-Password + #} + +} diff --git a/raddb/mods-available/detail.example.com b/raddb/mods-available/detail.example.com new file mode 100644 index 0000000..827cdf5 --- /dev/null +++ b/raddb/mods-available/detail.example.com @@ -0,0 +1,27 @@ +# -*- text -*- +# +# Detail file writer, used in the following examples: +# +# raddb/sites-available/robust-proxy-accounting +# raddb/sites-available/decoupled-accounting +# +# Note that this module can write detail files that are read by +# only ONE "listen" section. If you use BOTH of the examples +# above, you will need to define TWO "detail" modules. +# +# e.g. detail1.example.com && detail2.example.com +# +# +# We write *multiple* detail files here. They will be processed by +# the detail "listen" section in the order that they were created. +# The directory containing these files should NOT be used for any +# other purposes. i.e. It should have NO other files in it. +# +# Writing multiple detail enables the server to process the pieces +# in smaller chunks. This helps in certain catastrophic corner cases. +# +# $Id$ +# +detail detail.example.com { + filename = ${radacctdir}/detail.example.com/detail-%Y%m%d:%H:%G +} diff --git a/raddb/mods-available/detail.log b/raddb/mods-available/detail.log new file mode 100644 index 0000000..b91cf7c --- /dev/null +++ b/raddb/mods-available/detail.log @@ -0,0 +1,75 @@ +# -*- text -*- +# +# $Id$ + +# +# More examples of doing detail logs. + +# +# Many people want to log authentication requests. +# Rather than modifying the server core to print out more +# messages, we can use a different instance of the 'detail' +# module, to log the authentication requests to a file. +# +# You will also need to un-comment the 'auth_log' line +# in the 'authorize' section, below. +# +detail auth_log { + filename = ${radacctdir}/%{%{Packet-Src-IP-Address}:-%{Packet-Src-IPv6-Address}}/auth-detail-%Y%m%d + + # + # This MUST be 0600, otherwise anyone can read + # the users passwords! + permissions = 0600 + + # You may also strip out passwords completely + suppress { + User-Password + } +} + +# +# This module logs authentication reply packets sent +# to a NAS. Both Access-Accept and Access-Reject packets +# are logged. +# +# You will also need to un-comment the 'reply_log' line +# in the 'post-auth' section, below. +# +detail reply_log { + filename = ${radacctdir}/%{%{Packet-Src-IP-Address}:-%{Packet-Src-IPv6-Address}}/reply-detail-%Y%m%d + + permissions = 0600 +} + +# +# This module logs packets proxied to a home server. +# +# You will also need to un-comment the 'pre_proxy_log' line +# in the 'pre-proxy' section, below. +# +detail pre_proxy_log { + filename = ${radacctdir}/%{%{Packet-Src-IP-Address}:-%{Packet-Src-IPv6-Address}}/pre-proxy-detail-%Y%m%d + + # + # This MUST be 0600, otherwise anyone can read + # the users passwords! + permissions = 0600 + + # You may also strip out passwords completely + #suppress { + # User-Password + #} +} + +# +# This module logs response packets from a home server. +# +# You will also need to un-comment the 'post_proxy_log' line +# in the 'post-proxy' section, below. +# +detail post_proxy_log { + filename = ${radacctdir}/%{%{Packet-Src-IP-Address}:-%{Packet-Src-IPv6-Address}}/post-proxy-detail-%Y%m%d + + permissions = 0600 +} diff --git a/raddb/mods-available/dhcp b/raddb/mods-available/dhcp new file mode 100644 index 0000000..a431633 --- /dev/null +++ b/raddb/mods-available/dhcp @@ -0,0 +1,19 @@ +# -*- text -*- +# +# $Id$ + +# +# This module is useful only for 'xlat'. To use it, +# put 'dhcp' into the 'instantiate' section. +# +# %{dhcp_options:<Attribute-ref>} may be used to decode +# DHCP options data included in RADIUS packets by vendors +# of DHCP to RADIUS gateways. +# +# This is known to work with the following VSAs: +# * Juniper - ERX-Dhcp-Options +# * Alcatel lucent SR - Alc-ToServer-Dhcp-Options +# - Alc-ToClient-Dhcp-Options +# +dhcp { +} diff --git a/raddb/mods-available/dhcp_files b/raddb/mods-available/dhcp_files new file mode 100644 index 0000000..243a241 --- /dev/null +++ b/raddb/mods-available/dhcp_files @@ -0,0 +1,56 @@ +# -*- text -*- +# +# $Id$ + +# Instances of the "files" module for managing DHCP options +# +files dhcp_network { + # The file containing network-specific DHCP options mapping + filename = ${modconfdir}/files/dhcp + + # For network lookups we use a fixed key. Matching + # actual networks is done by additional filtering within + # the file + key = "network" +} + +files dhcp_subnet { + # The file containing subnet-specific DHCP options mapping + filename = ${modconfdir}/files/dhcp + + # For subnet lookups we use a fixed key. Matching + # actual subnets is done by additional filtering within + # the file + key = "subnet" +} + +files dhcp_set_group_options { + # An example of looking up DHCP group options. This + # is designed to be called from a policy configured in + # policy.d/dhcp. + # + # If clients are never members of more than one group, + # then this could be simplified such that DHCP-Group-Name + # is used here in place of Foreach-Variable-0 and this + # module instance called directly rather than the policy + + # Use the same file as for subnets - could be split + # for large, complex installations + filename = ${modconfdir}/files/dhcp + + # The key is a temporary string populated by the calling policy + # which uses a foreach loop. + key = "%{Foreach-Variable-0}" +} + +files dhcp_hosts { + # An example of a DHCP host mapping for option setting + + # Use the same file as for subnets - could be split + # for large, complex installations + filename = ${modconfdir}/files/dhcp + + # If a different identifier is needed for looking up + # host specific entries then amend this key. + key = "host-%{DHCP-Client-Hardware-Address}" +} diff --git a/raddb/mods-available/dhcp_passwd b/raddb/mods-available/dhcp_passwd new file mode 100644 index 0000000..7884a00 --- /dev/null +++ b/raddb/mods-available/dhcp_passwd @@ -0,0 +1,20 @@ +# -*- text -*- +# +# $Id$ + +# An instance of the passwd module designed for looking up +# DHCP client membership. This example is based on hardware +# address. +# The "groups" file should be of the format: +# <group name>|<hardware address>,<hardware address>,<hardware address> +# <group name>|<hardware address>,<hardware address>,<hardware address> +# +# See the passwd module for more details. + +passwd dhcp_group_membership { + filename = "${modconfdir}/files/dhcp_groups" + format = "~DHCP-Group-Name:*,DHCP-Client-Hardware-Address" + hash_size = 100 + allow_multiple_keys = yes + delimiter = "|" +} diff --git a/raddb/mods-available/dhcp_sql b/raddb/mods-available/dhcp_sql new file mode 100644 index 0000000..20dbe3a --- /dev/null +++ b/raddb/mods-available/dhcp_sql @@ -0,0 +1,92 @@ +# -*- text -*- +## +## mods-available/sql -- SQL modules +## +## $Id$ + +###################################################################### +# +# Configuration for the DHCP-specific instance of the SQL module +# +# The database schemas and queries are located in subdirectories: +# +# sql/dhcp/<DB>/schema.sql Schema +# sql/dhcp/<DB>/queries.conf Reply options lookup queries +# +# Where "DB" is mysql, mssql, oracle, or postgresql. +# + +# +# See raddb/mods-available/sql for a description of the configuration items +# for the sql module. +# +sql dhcp_sql { + dialect = "sqlite" + driver = "rlm_sql_null" +# driver = "rlm_sql_${dialect}" + + sqlite { + filename = "/tmp/freeradius.db" + busy_timeout = 200 + bootstrap = "${modconfdir}/${..:name}/dhcp/sqlite/schema.sql" + } + + mysql { + tls { + ca_file = "/etc/ssl/certs/my_ca.crt" + ca_path = "/etc/ssl/certs/" + certificate_file = "/etc/ssl/certs/private/client.crt" + private_key_file = "/etc/ssl/certs/private/client.key" + cipher = "DHE-RSA-AES256-SHA:AES128-SHA" + + tls_required = yes + tls_check_cert = no + tls_check_cert_cn = no + } + warnings = auto + } + + postgresql { + send_application_name = yes + } + + mongo { + appname = "freeradius" + tls { + certificate_file = /path/to/file + certificate_password = "password" + ca_file = /path/to/file + ca_dir = /path/to/directory + crl_file = /path/to/file + weak_cert_validation = false + allow_invalid_hostname = false + } + } + +# server = "localhost" +# port = 3306 +# login = "radius" +# password = "radpass" + + radius_db = "radius" + + dhcpreply_table = "dhcpreply" + groupreply_table = "dhcpgroupreply" + dhcpgroup_table = "dhcpgroup" + read_groups = no + + pool { + start = ${thread[pool].start_servers} + min = ${thread[pool].min_spare_servers} + max = ${thread[pool].max_servers} + spare = ${thread[pool].max_spare_servers} + uses = 0 + retry_delay = 30 + lifetime = 0 + idle_timeout = 60 + } + + group_attribute = "${.:instance}-SQL-Group" + + $INCLUDE ${modconfdir}/${.:name}/dhcp/${dialect}/queries.conf +} diff --git a/raddb/mods-available/dhcp_sqlippool b/raddb/mods-available/dhcp_sqlippool new file mode 100644 index 0000000..909b93c --- /dev/null +++ b/raddb/mods-available/dhcp_sqlippool @@ -0,0 +1,101 @@ +# Configuration for DHCP for the SQL based IP Pools module (rlm_sqlippool). +# +# See raddb/mods-available/sqlippool for common configuration explanation +# +# See raddb/policy.d/dhcp_sqlippool for the "glue" code that allows +# the RADIUS based "sqlippool" module to be used for DHCP. +# +# See raddb/sites-available/dhcp for instructions on how to configure +# the DHCP server. +# +# The database schemas are available at: +# +# raddb/mods-config/sql/ippool-dhcp/<DB>/schema.sql +# +# $Id$ + +sqlippool dhcp_sqlippool { + # SQL instance to use (from mods-available/sql) + # + # If you have multiple sql instances, such as "sql sql1 {...}", + # use the *instance* name here: sql1. + sql_module_instance = "dhcp_sql" + + # This is duplicative of info available in the SQL module, but + # we have to list it here as we do not yet support nested + # reference expansions. + dialect = "mysql" + + # Name of the check item attribute to be used as a key in the SQL queries + pool_name = "Pool-Name" + + # SQL table to use for ippool range and lease info + ippool_table = "dhcpippool" + + # The duration for which a lease is reserved whilst under offer + offer_duration = 10 + + # IP lease duration. (Leases expire even if no DHCP-Release packet is received) + # Either use the value to be sent to the client or a hard coded one. + lease_duration = "%{reply:DHCP-IP-Address-Lease-Time}" + #lease_duration = 7200 + + # The attribute in which the IP address is returned in the reply + attribute_name = "DHCP-Your-IP-Address" + + # Assign the IP address, even if the above attribute already exists in + # the reply. + # +# allow_duplicates = no + + # The attribute in which an IP address hint may be supplied + req_attribute_name = "DHCP-Requested-IP-Address" + + # + # RFC 2132 allows the DHCP client to supply a unique + # identifier ("uid") using Option 61 (DHCP-Client-Identifier) + # in which case it must be used as the lookup key for + # configuration data. + # + pool_key = "%{%{DHCP-Client-Identifier}:-%{DHCP-Client-Hardware-Address}}" + # + # The "uid" is generated by the OS which means that clients + # whose BMC piggybacks on the main interface (sharing its MAC, + # but generating a distinct uid) and dual-booting clients can + # be allocated multiple IPs, consuming more pool entries. To + # avoid this you can ignore the RFCs and key the configuration + # data based only on the client MAC address. + # + # pool_key = "%{DHCP-Client-Hardware-Address}" + + ################################################################ + # + # WARNING: MySQL (MyISAM) has certain limitations that means it can + # hand out the same IP address to 2 different users. + # + # We suggest using an SQL DB with proper transaction + # support, such as PostgreSQL, or using MySQL + # with InnoDB. + # + ################################################################ + + # These messages are added to the "control" items, as + # Module-Success-Message. They are not logged anywhere else, + # unlike previous versions. If you want to have them logged + # to a file, see the "linelog" module, and create an entry + # which writes Module-Success-Message message. + # + messages { + exists = "DHCP: Existing IP: %{reply:${..attribute_name}} (cid %{DHCP-Client-Identifier} chaddr %{DHCP-Client-Hardware-Address} giaddr %{DHCP-Gateway-IP-Address})" + + success = "DHCP: Allocated IP: %{reply:${..attribute_name}} from %{control:${..pool_name}} (cid %{DHCP-Client-Identifier} chaddr %{DHCP-Client-Hardware-Address} giaddr %{DHCP-Gateway-IP-Address})" + + clear = "DHCP: Released IP %{DHCP-Client-IP-Address} (cid %{DHCP-Client-Identifier} chaddr %{DHCP-Client-Hardware-Address} giaddr %{DHCP-Gateway-IP-Address})" + + failed = "DHCP: IP Allocation FAILED from %{control:${..pool_name}} (cid %{DHCP-Client-Identifier} chaddr %{DHCP-Client-Hardware-Address} giaddr %{DHCP-Gateway-IP-Address})" + + nopool = "DHCP: No ${..pool_name} defined (cid %{DHCP-Client-Identifier} chaddr %{DHCP-Client-Hardware-Address} giaddr %{DHCP-Gateway-IP-Address})" + } + + $INCLUDE ${modconfdir}/sql/ippool-dhcp/${dialect}/queries.conf +} diff --git a/raddb/mods-available/digest b/raddb/mods-available/digest new file mode 100644 index 0000000..f0aa9ed --- /dev/null +++ b/raddb/mods-available/digest @@ -0,0 +1,13 @@ +# -*- text -*- +# +# $Id$ + +# +# The 'digest' module currently has no configuration. +# +# "Digest" authentication against a Cisco SIP server. +# See 'doc/rfc/draft-sterman-aaa-sip-00.txt' for details +# on performing digest authentication for Cisco SIP servers. +# +digest { +} diff --git a/raddb/mods-available/dynamic_clients b/raddb/mods-available/dynamic_clients new file mode 100644 index 0000000..cc2bd5f --- /dev/null +++ b/raddb/mods-available/dynamic_clients @@ -0,0 +1,32 @@ +# -*- text -*- +# +# $Id$ + +# This module loads RADIUS clients as needed, rather than when the server +# starts. +# +# There are no configuration entries for this module. Instead, it +# relies on the "client" configuration. You must: +# +# 1) link raddb/sites-enabled/dynamic_clients to +# raddb/sites-available/dynamic_clients +# +# 2) Define a client network/mask (see top of the above file) +# +# 3) uncomment the "directory" entry in that client definition +# +# 4) list "dynamic_clients" in the "authorize" section of the +# "dynamic_clients' virtual server. The default example already +# does this. +# +# 5) put files into the above directory, one per IP. +# e.g. file "192.0.2.1" should contain a normal client definition +# for a client with IP address 192.0.2.1. +# +# For more documentation, see the file: +# +# raddb/sites-available/dynamic-clients +# +dynamic_clients { + +} diff --git a/raddb/mods-available/eap b/raddb/mods-available/eap new file mode 100644 index 0000000..ee9e539 --- /dev/null +++ b/raddb/mods-available/eap @@ -0,0 +1,1115 @@ +# -*- text -*- +## +## eap.conf -- Configuration for EAP types (PEAP, TTLS, etc.) +## +## $Id$ + +####################################################################### +# +# Whatever you do, do NOT set 'Auth-Type := EAP'. The server +# is smart enough to figure this out on its own. The most +# common side effect of setting 'Auth-Type := EAP' is that the +# users then cannot use ANY other authentication method. +# +eap { + # Invoke the default supported EAP type when + # EAP-Identity response is received. + # + # The incoming EAP messages DO NOT specify which EAP + # type they will be using, so it MUST be set here. + # + # For now, only one default EAP type may be used at a time. + # + # If the EAP-Type attribute is set by another module, + # then that EAP type takes precedence over the + # default type configured here. + # + default_eap_type = md5 + + # A list is maintained to correlate EAP-Response + # packets with EAP-Request packets. After a + # configurable length of time, entries in the list + # expire, and are deleted. + # + timer_expire = 60 + + # There are many EAP types, but the server has support + # for only a limited subset. If the server receives + # a request for an EAP type it does not support, then + # it normally rejects the request. By setting this + # configuration to "yes", you can tell the server to + # instead keep processing the request. Another module + # MUST then be configured to proxy the request to + # another RADIUS server which supports that EAP type. + # + # If another module is NOT configured to handle the + # request, then the request will still end up being + # rejected. + # + ignore_unknown_eap_types = no + + # Cisco AP1230B firmware 12.2(13)JA1 has a bug. When given + # a User-Name attribute in an Access-Accept, it copies one + # more byte than it should. + # + # We can work around it by configurably adding an extra + # zero byte. + # + cisco_accounting_username_bug = no + + # Help prevent DoS attacks by limiting the number of + # sessions that the server is tracking. For simplicity, + # this is taken from the "max_requests" directive in + # radiusd.conf. + # + max_sessions = ${max_requests} + + + ############################################################ + # + # Supported EAP-types + # + + + # EAP-MD5 + # + # We do NOT recommend using EAP-MD5 authentication + # for wireless connections. It is insecure, and does + # not provide for dynamic WEP keys. + # + md5 { + } + + + # EAP-pwd -- secure password-based authentication + # + #pwd { + # group = 19 + + # server_id = theserver@example.com + + # This has the same meaning as for TLS. + # + # fragment_size = 1020 + + # The virtual server which determines the + # "known good" password for the user. + # Note that unlike TLS, only the "authorize" + # section is processed. EAP-PWD requests can be + # distinguished by having a User-Name, but + # no User-Password, CHAP-Password, EAP-Message, etc. + # + # virtual_server = "inner-tunnel" + #} + + + # Cisco LEAP + # + # We do not recommend using LEAP in new deployments. See: + # http://www.securiteam.com/tools/5TP012ACKE.html + # + # LEAP is not supported. + # It is insecure, and no one should be using it. + # + + + # EAP-GTC -- Generic Token Card + # + # Currently, this is only permitted inside of EAP-TTLS, + # or EAP-PEAP. The module "challenges" the user with + # text, and the response from the user is taken to be + # the User-Password. + # + # Proxying the tunneled EAP-GTC session is a bad idea, + # the users password will go over the wire in plain-text, + # for anyone to see. + # + gtc { + # The default challenge, which many clients + # ignore.. + # + # challenge = "Password: " + + # The plain-text response which comes back + # is put into a User-Password attribute, + # and passed to another module for + # authentication. This allows the EAP-GTC + # response to be checked against plain-text, + # or crypt'd passwords. + # + # If you say "Local" instead of "PAP", then + # the module will look for a User-Password + # configured for the request, and do the + # authentication itself. + # + auth_type = PAP + } + + + # Common TLS configuration for TLS-based EAP types + # ------------------------------------------------ + # + # See raddb/certs/README.md for additional comments + # on certificates. + # + # If OpenSSL was not found at the time the server was + # built, the "tls", "ttls", and "peap" sections will + # be ignored. + # + # If you do not currently have certificates signed by + # a trusted CA you may use the 'snakeoil' certificates. + # Included with the server in raddb/certs. + # + # If these certificates have not been auto-generated: + # cd raddb/certs + # make + # + # These test certificates SHOULD NOT be used in a normal + # deployment. They are created only to make it easier + # to install the server, and to perform some simple + # tests with EAP-TLS, TTLS, or PEAP. + # + # Note that you should NOT use a globally known CA here! + # e.g. using a Verisign cert as a "known CA" means that + # ANYONE who has a certificate signed by them can + # authenticate via EAP-TLS! This is likely not what you want. + # + tls-config tls-common { + private_key_password = whatever + private_key_file = ${certdir}/server.pem + + # If Private key & Certificate are located in + # the same file, then private_key_file & + # certificate_file must contain the same file + # name. + # + # If ca_file (below) is not used, then the + # certificate_file below SHOULD also include all of + # the intermediate CA certificates used to sign the + # server certificate, but NOT the root CA. + # + # Including the ROOT CA certificate is not useful and + # merely inflates the exchanged data volume during + # the TLS negotiation. + # + # This file should contain the server certificate, + # followed by intermediate certificates, in order. + # i.e. If we have a server certificate signed by CA1, + # which is signed by CA2, which is signed by a root + # CA, then the "certificate_file" should contain + # server.pem, followed by CA1.pem, followed by + # CA2.pem. + # + # When using "ca_file" or "ca_path", the + # "certificate_file" should contain only + # "server.pem". And then you may (or may not) need + # to set "auto_chain", depending on your version of + # OpenSSL. + # + # In short, SSL / TLS certificates are complex. + # There are many versions of software, each of which + # behave slightly differently. It is impossible to + # give advice which will work everywhere. Instead, + # we give general guidelines. + # + certificate_file = ${certdir}/server.pem + + # Trusted Root CA list + # + # This file can contain multiple CA certificates. + # ALL of the CA's in this list will be trusted to + # issue client certificates for authentication. + # + # In general, you should use self-signed + # certificates for 802.1x (EAP) authentication. + # In that case, this CA file should contain + # *one* CA certificate. + # + ca_file = ${cadir}/ca.pem + + # + # Directory where multiple CAs are stored. Both + # "ca_file" and "ca_path" can be used at the same time. + # + ca_path = ${cadir} + + # OpenSSL does not reload contents of ca_path dir over time. + # That means that if check_crl is enabled and CRLs are loaded + # from ca_path dir, at some point CRLs will expire and + # the server will stop authenticating users. + # + # If ca_path_reload_interval is non-zero, it will force OpenSSL + # to reload all data from ca_path periodically + # + # Flush ca_path each hour + # ca_path_reload_interval = 3600 + + # OpenSSL will automatically create certificate chains, + # unless we tell it to not do that. The problem is that + # it sometimes gets the chains right from a certificate + # signature view, but wrong from the clients view. + # + # When setting "auto_chain = no", the server certificate + # file MUST include the full certificate chain. + # + # auto_chain = yes + + # If OpenSSL supports TLS-PSK, then we can use a + # fixed PSK identity and (hex) password. These can + # be used at the same time as the certificate + # configuration, but only for TLS 1.0 through 1.2. + # + # If PSK and certificates are configured at the same + # time for TLS 1.3, then the server will warn you, + # and will disable TLS 1.3, as it will not work. + # + # The work around is to have two modules (or for + # RadSec, two listen sections). One will have PSK + # configured, and the other will have certificates + # configured. + # + # psk_identity = "test" + # psk_hexphrase = "036363823" + + # Dynamic queries for the PSK. If TLS-PSK is used, + # and psk_query is set, then you MUST NOT use + # psk_identity or psk_hexphrase. + # + # Instead, use a dynamic expansion similar to the one + # below. It keys off of TLS-PSK-Identity. It should + # return a of string no more than 512 hex characters. + # That string will be converted to binary, and will + # be used as the dynamic PSK hexphrase. + # + # Note that this query is just an example. You will + # need to customize it for your installation. + # + # psk_query = "%{sql:select hex(key) from psk_keys where keyid = '%{TLS-PSK-Identity}'}" + + # For DH cipher suites to work in OpenSSL < 1.1.0, + # you have to run OpenSSL to create the DH file + # first: + # + # openssl dhparam -out certs/dh 2048 + # + # For OpenSSL >= 1.1.0, just leave this commented + # out, and OpenSSL will do the right thing. + # + # dh_file = ${certdir}/dh + + # If your system doesn't have /dev/urandom, + # you will need to create this file, and + # periodically change its contents. + # + # For security reasons, FreeRADIUS doesn't + # write to files in its configuration + # directory. + # + # random_file = /dev/urandom + + # This can never exceed the size of a RADIUS + # packet (4096 bytes), and is preferably half + # that, to accommodate other attributes in + # RADIUS packet. On most APs the MAX packet + # length is configured between 1500 - 1600 + # In these cases, fragment size should be + # 1024 or less. + # + # fragment_size = 1024 + + # include_length is a flag which is + # by default set to yes If set to + # yes, Total Length of the message is + # included in EVERY packet we send. + # If set to no, Total Length of the + # message is included ONLY in the + # First packet of a fragment series. + # + # include_length = yes + + + # Check the Certificate Revocation List + # + # 1) Copy CA certificates and CRLs to same directory. + # 2) Execute 'c_rehash <CA certs&CRLs Directory>'. + # 'c_rehash' is OpenSSL's command. + # 3) uncomment the lines below. + # 5) Restart radiusd + # check_crl = yes + + # Check if intermediate CAs have been revoked. + # check_all_crl = yes + + # Accept an expired Certificate Revocation List + # + # allow_expired_crl = no + + # If check_cert_issuer is set, the value will + # be checked against the DN of the issuer in + # the client certificate. If the values do not + # match, the certificate verification will fail, + # rejecting the user. + # + # This check can be done more generally by checking + # the value of the TLS-Client-Cert-Issuer attribute. + # This check can be done via any mechanism you + # choose. + # + # check_cert_issuer = "/C=GB/ST=Berkshire/L=Newbury/O=My Company Ltd" + + # If check_cert_cn is set, the value will + # be xlat'ed and checked against the CN + # in the client certificate. If the values + # do not match, the certificate verification + # will fail rejecting the user. + # + # This check is done only if the previous + # "check_cert_issuer" is not set, or if + # the check succeeds. + # + # This check can be done more generally by writing + # "unlang" statements to examine the value of the + # TLS-Client-Cert-Common-Name attribute. + # + # check_cert_cn = %{User-Name} + + # + # This configuration item only applies when there is + # an intermediate CA between the "root" CA, and the + # client certificate. If we trust the root CA, then + # by definition we also trust ANY intermediate CA + # which is signed by that root. This means ANOTHER + # intermediate CA can issue client certificates, and + # have them accepted by the EAP module. + # + # The solution is to list ONLY the trusted CAs in the + # FreeRADIUS configuration, and then set this + # configuration item to "yes". + # + # Then, when the server receives a client certificate + # from an untrusted CA, that authentication request + # can be rejected. + # + # It is possible to do these checks in "unlang", by + # checking for unknown names in the + # TLS-Cert-Common-Name attribute, but that is + # more complex. So we add a configuration option + # which can be set once, and which works for all + # possible intermediate CAs, no matter what their + # value. + # + # reject_unknown_intermediate_ca = no + + # Set this option to specify the allowed + # TLS cipher suites. The format is listed + # in "man 1 ciphers". + # + cipher_list = "DEFAULT" + + # Set this option to specify the allowed + # TLS signature algorithms for OpenSSL 1.1.1 and above. + # The format and available signature algorithms are listed + # in "man 3 SSL_CTX_set1_sigalgs_list". + # + # sigalgs_list = "" + + # If enabled, OpenSSL will use server cipher list + # (possibly defined by cipher_list option above) + # for choosing right cipher suite rather than + # using client-specified list which is OpenSSl default + # behavior. Setting this to "yes" means that OpenSSL + # will choose the servers ciphers, even if they do not + # best match what the client sends. + # + # TLS negotiation is usually good, but can be imperfect. + # This setting allows administrators to "fine tune" it + # if necessary. + # + cipher_server_preference = no + + # You can selectively disable TLS versions for + # compatability with old client devices. + # + # If your system has OpenSSL 1.1.0 or greater, do NOT + # use these. Instead, set tls_min_version and + # tls_max_version. + # +# disable_tlsv1_2 = yes +# disable_tlsv1_1 = yes +# disable_tlsv1 = yes + + + # Set min / max TLS version. + # + # Generally speaking you should NOT use TLS 1.0 or + # TLS 1.1. They are old, possibly insecure, and + # deprecated. However, it is sometimes necessary to + # enable it for compatibility with legact systems. + # We recommend replacing those legacy systems, and + # using at least TLS 1.2. + # + # Some Debian versions disable older versions of TLS, + # and requires the application to manually enable + # them. + # + # If you are running such a distribution, you should + # set these options, otherwise older clients will not + # be able to connect. + # + # Allowed values are "1.0", "1.1", "1.2", and "1.3". + # + # As of 2021, it is STRONGLY RECOMMENDED to set + # + # tls_min_version = "1.2" + # + # Older TLS versions are insecure and deprecated. + # + # In order to enable TLS 1.0 and TLS 1.1, you may + # also need to update cipher_list below to: + # + # * OpenSSL >= 3.x + # + # cipher_list = "DEFAULT@SECLEVEL=0" + # + # * OpenSSL < 3.x + # + # cipher_list = "DEFAULT@SECLEVEL=1" + # + # The values must be in quotes. + # + # We also STRONGLY RECOMMEND to set + # + # tls_max_version = "1.2" + # + # While the server will accept "1.3" as a value, + # most EAP supplicants WILL NOT DO TLS 1.3 PROPERLY. + # + # i.e. they WILL NOT WORK, SO DO NOT ASK QUESTIONS ON + # THE LIST ABOUT WHY IT DOES NOT WORK. + # + # The TLS 1.3 support is here for future + # compatibility, as clients get upgraded, and people + # don't upgrade their copies of FreeRADIUS. + # + # Also note that we only support TLS 1.3 for EAP-TLS, + # TTLS, and PEAP. It is not supported for EAP-FAST. + # + tls_min_version = "1.2" + tls_max_version = "1.2" + + # Elliptical cryptography configuration + # + # This configuration should be one of the following: + # + # * a name of the curve to use, e.g. "prime256v1". + # + # * a colon separated list of curve NIDs or names. + # + # * an empty string, in which case OpenSSL will choose + # the "best" curve for the situation. + # + # For supported curve names, please run + # + # openssl ecparam -list_curves + # + ecdh_curve = "" + + # Session resumption / fast reauthentication + # cache. + # + # The cache contains the following information: + # + # session Id - unique identifier, managed by SSL + # User-Name - from the Access-Accept + # Stripped-User-Name - from the Access-Request + # Cached-Session-Policy - from the Access-Accept + # + # See also the "store" subsection below for + # additional attributes which can be cached. + # + # The "Cached-Session-Policy" is the name of a + # policy which should be applied to the cached + # session. This policy can be used to assign + # VLANs, IP addresses, etc. It serves as a useful + # way to re-apply the policy from the original + # Access-Accept to the subsequent Access-Accept + # for the cached session. + # + # On session resumption, these attributes are + # copied from the cache, and placed into the + # reply list. + # + # You probably also want "use_tunneled_reply = yes" + # when using fast session resumption. + # + # You can check if a session has been resumed by + # looking for the existence of the EAP-Session-Resumed + # attribute. Note that this attribute will *only* + # exist in the "post-auth" section. + # + # CAVEATS: The cache is stored and reloaded BEFORE + # the "post-auth" section is run. This limitation + # makes caching more difficult than it should be. In + # practice, it means that the first authentication + # session must set the reply attributes before the + # post-auth section is run. + # + # When the session is resumed, the attributes are + # restored and placed into the session-state list. + # + cache { + # Enable it. The default is "no". Deleting the entire "cache" + # subsection also disables caching. + # + # The session cache requires the use of the + # "name" and "persist_dir" configuration + # items, below. + # + # The internal OpenSSL session cache has been permanently + # disabled. + # + # You can disallow resumption for a particular user by adding the + # following attribute to the control item list: + # + # Allow-Session-Resumption = No + # + # If "enable = no" below, you CANNOT enable resumption for just one + # user by setting the above attribute to "yes". + # + enable = no + + # Lifetime of the cached entries, in hours. The sessions will be + # deleted/invalidated after this time. + # + lifetime = 24 # hours + + # Internal "name" of the session cache. Used to + # distinguish which TLS context sessions belong to. + # + # The server will generate a random value if unset. + # This will change across server restart so you MUST + # set the "name" if you want to persist sessions (see + # below). + # + # name = "EAP module" + + # Simple directory-based storage of sessions. + # Two files per session will be written, the SSL + # state and the cached VPs. This will persist session + # across server restarts. + # + # The default directory is ${logdir}, for historical + # reasons. You should ${db_dir} instead. And check + # the value of db_dir in the main radiusd.conf file. + # It should not point to ${raddb} + # + # The server will need write perms, and the directory + # should be secured from anyone else. You might want + # a script to remove old files from here periodically: + # + # find ${logdir}/tlscache -mtime +2 -exec rm -f {} \; + # + # This feature REQUIRES "name" option be set above. + # + # persist_dir = "${logdir}/tlscache" + + # + # It is possible to partially + # control which attributes exist in the + # session cache. This subsection lists + # attributes which are taken from the reply, + # and saved to the on-disk cache. When the + # session is resumed, these attributes are + # added to the "session-state" list. The + # default configuration will then take care + # of copying them to the reply. + # + store { + Tunnel-Private-Group-Id + } + } + + # Client certificates can be validated via an + # external command. This allows dynamic CRLs or OCSP + # to be used. + # + # This configuration is commented out in the + # default configuration. Uncomment it, and configure + # the correct paths below to enable it. + # + # If OCSP checking is enabled, and the OCSP checks fail, + # the verify section is not run. + # + # If OCSP checking is disabled, the verify section is + # run on successful certificate validation. + # + verify { + # If the OCSP checks succeed, the verify section + # is run to allow additional checks. + # + # If you want to skip verify on OCSP success, + # uncomment this configuration item, and set it + # to "yes". + # + # skip_if_ocsp_ok = no + + # A temporary directory where the client + # certificates are stored. This directory + # MUST be owned by the UID of the server, + # and MUST not be accessible by any other + # users. When the server starts, it will do + # "chmod go-rwx" on the directory, for + # security reasons. The directory MUST + # exist when the server starts. + # + # You should also delete all of the files + # in the directory when the server starts. + # + # tmpdir = /tmp/radiusd + + # The command used to verify the client cert. + # We recommend using the OpenSSL command-line + # tool. + # + # The ${..ca_path} text is a reference to + # the ca_path variable defined above. + # + # The %{TLS-Client-Cert-Filename} is the name + # of the temporary file containing the cert + # in PEM format. This file is automatically + # deleted by the server when the command + # returns. + # + # client = "/path/to/openssl verify -CApath ${..ca_path} %{TLS-Client-Cert-Filename}" + } + + # OCSP Configuration + # + # Certificates can be verified against an OCSP + # Responder. This makes it possible to immediately + # revoke certificates without the distribution of + # new Certificate Revocation Lists (CRLs). + # + ocsp { + # Enable it. The default is "no". + # Deleting the entire "ocsp" subsection + # also disables ocsp checking + # + enable = no + + # The OCSP Responder URL can be automatically + # extracted from the certificate in question. + # To override the OCSP Responder URL set + # "override_cert_url = yes". + # + override_cert_url = yes + + # If the OCSP Responder address is not extracted from + # the certificate, the URL can be defined here. + # + url = "http://127.0.0.1/ocsp/" + + # If the OCSP Responder can not cope with nonce + # in the request, then it can be disabled here. + # + # For security reasons, disabling this option + # is not recommended as nonce protects against + # replay attacks. + # + # Note that Microsoft AD Certificate Services OCSP + # Responder does not enable nonce by default. It is + # more secure to enable nonce on the responder than + # to disable it in the query here. + # See http://technet.microsoft.com/en-us/library/cc770413%28WS.10%29.aspx + # + # use_nonce = yes + + # Number of seconds before giving up waiting + # for OCSP response. 0 uses system default. + # + # timeout = 0 + + # Normally an error in querying the OCSP + # responder (no response from server, server did + # not understand the request, etc) will result in + # a validation failure. + # + # To treat these errors as 'soft' failures and + # still accept the certificate, enable this + # option. + # + # Warning: this may enable clients with revoked + # certificates to connect if the OCSP responder + # is not available. Use with caution. + # + # softfail = no + } + + # + # The server can present different certificates based + # on the realm presented in EAP. See + # raddb/certs/realms/README.md for examples of how to + # configure this. + # + # Note that the default is to use the same set of + # realm certificates for both EAP and RadSec! If + # this is not what you want, you should use different + # subdirectories or each, e.g. ${certdir}/realms/radsec/, + # and ${certdir}/realms/eap/ + # + # realm_dir = ${certdir}/realms/ + } + + + # EAP-TLS + # + # The TLS configuration for TLS-based EAP types is held in + # the "tls-config" section, above. + # + tls { + # Point to the common TLS configuration + # + tls = tls-common + + # As part of checking a client certificate, the EAP-TLS + # sets some attributes such as TLS-Client-Cert-Common-Name. This + # virtual server has access to these attributes, and can + # be used to accept or reject the request. + # + # virtual_server = check-eap-tls + + # You can control whether or not EAP-TLS requires a + # client certificate by setting + # + # configurable_client_cert = yes + # + # Once that setting has been changed, you can then set + # + # EAP-TLS-Require-Client-Cert = No + # + # in the control items for a request, and the EAP-TLS + # module will not require a client certificate from + # the supplicant. + # + # WARNING: This configuration should only be used + # when the users are placed into a "captive portal" + # or "walled garden", where they have limited network + # access. Otherwise the configuraton will allow + # anyone on the network, without authenticating them! + # +# configurable_client_cert = no + } + + + # EAP-TTLS -- Tunneled TLS + # + # The TTLS module implements the EAP-TTLS protocol, + # which can be described as EAP inside of Diameter, + # inside of TLS, inside of EAP, inside of RADIUS... + # + # Surprisingly, it works quite well. + # + ttls { + # Which tls-config section the TLS negotiation parameters + # are in - see EAP-TLS above for an explanation. + # + # In the case that an old configuration from FreeRADIUS + # v2.x is being used, all the options of the tls-config + # section may also appear instead in the 'tls' section + # above. If that is done, the tls= option here (and in + # tls above) MUST be commented out. + # + tls = tls-common + + # The tunneled EAP session needs a default EAP type + # which is separate from the one for the non-tunneled + # EAP module. Inside of the TTLS tunnel, we recommend + # using EAP-MD5. If the request does not contain an + # EAP conversation, then this configuration entry is + # ignored. + # + default_eap_type = md5 + + # The tunneled authentication request does not usually + # contain useful attributes like 'Calling-Station-Id', + # etc. These attributes are outside of the tunnel, + # and normally unavailable to the tunneled + # authentication request. + # + # By setting this configuration entry to 'yes', + # any attribute which is NOT in the tunneled + # authentication request, but which IS available + # outside of the tunnel, is copied to the tunneled + # request. + # + # allowed values: {no, yes} + # + copy_request_to_tunnel = no + + # This configuration item is deprecated. Instead, + # you should use: + # + # update outer.session-state { + # ... + # } + # + # This will cache attributes for the final Access-Accept. + # + # See "update outer.session-state" in the "post-auth" + # sections of sites-available/default, and of + # sites-available/inner-tunnel + # + # The reply attributes sent to the NAS are usually + # based on the name of the user 'outside' of the + # tunnel (usually 'anonymous'). If you want to send + # the reply attributes based on the user name inside + # of the tunnel, then set this configuration entry to + # 'yes', and the reply to the NAS will be taken from + # the reply to the tunneled request. + # + # allowed values: {no, yes} + # + use_tunneled_reply = no + + # The inner tunneled request can be sent + # through a virtual server constructed + # specifically for this purpose. + # + # A virtual server MUST be specified. + # + virtual_server = "inner-tunnel" + + # This has the same meaning, and overwrites, the + # same field in the "tls" configuration, above. + # The default value here is "yes". + # + # include_length = yes + + # Unlike EAP-TLS, EAP-TTLS does not require a client + # certificate. However, you can require one by setting the + # following option. You can also override this option by + # setting + # + # EAP-TLS-Require-Client-Cert = Yes + # + # in the control items for a request. + # + # Note that the majority of supplicants do not support using a + # client certificate with EAP-TTLS, so this option is unlikely + # to be usable for most people. + # + # require_client_cert = yes + } + + + # EAP-PEAP + # + + ################################################## + # + # !!!!! WARNINGS for Windows compatibility !!!!! + # + ################################################## + # + # If you see the server send an Access-Challenge, + # and the client never sends another Access-Request, + # then + # + # STOP! + # + # The server certificate has to have special OID's + # in it, or else the Microsoft clients will silently + # fail. See the "scripts/xpextensions" file for + # details, and the following page: + # + # https://support.microsoft.com/en-us/help/814394/ + # + # If is still doesn't work, and you're using Samba, + # you may be encountering a Samba bug. See: + # + # https://bugzilla.samba.org/show_bug.cgi?id=6563 + # + # Note that we do not necessarily agree with their + # explanation... but the fix does appear to work. + # + ################################################## + + # The tunneled EAP session needs a default EAP type + # which is separate from the one for the non-tunneled + # EAP module. Inside of the TLS/PEAP tunnel, we + # recommend using EAP-MS-CHAPv2. + # + peap { + # Which tls-config section the TLS negotiation parameters + # are in - see EAP-TLS above for an explanation. + # + # In the case that an old configuration from FreeRADIUS + # v2.x is being used, all the options of the tls-config + # section may also appear instead in the 'tls' section + # above. If that is done, the tls= option here (and in + # tls above) MUST be commented out. + # + tls = tls-common + + # The tunneled EAP session needs a default + # EAP type which is separate from the one for + # the non-tunneled EAP module. Inside of the + # PEAP tunnel, we recommend using MS-CHAPv2, + # as that is the default type supported by + # Windows clients. + # + default_eap_type = mschapv2 + + # The PEAP module also has these configuration + # items, which are the same as for TTLS. + # + copy_request_to_tunnel = no + + # This configuration item is deprecated. Instead, + # you should use: + # + # update outer.session-state { + # ... + # } + # + # This will cache attributes for the final Access-Accept. + # + # See "update outer.session-state" in the "post-auth" + # sections of sites-available/default, and of + # sites-available/inner-tunnel + # + use_tunneled_reply = no + + # When the tunneled session is proxied, the + # home server may not understand EAP-MSCHAP-V2. + # Set this entry to "no" to proxy the tunneled + # EAP-MSCHAP-V2 as normal MSCHAPv2. + # + # This setting can be over-ridden on a packet by + # packet basis by setting + # + # &control:Proxy-Tunneled-Request-As-EAP = yes + # + # proxy_tunneled_request_as_eap = yes + + # The inner tunneled request can be sent + # through a virtual server constructed + # specifically for this purpose. + # + # A virtual server MUST be specified. + # + virtual_server = "inner-tunnel" + + # This option enables support for MS-SoH + # see doc/SoH.txt for more info. + # It is disabled by default. + # + # soh = yes + + # The SoH reply will be turned into a request which + # can be sent to a specific virtual server: + # + # soh_virtual_server = "soh-server" + + # Unlike EAP-TLS, PEAP does not require a client certificate. + # However, you can require one by setting the following + # option. You can also override this option by setting + # + # EAP-TLS-Require-Client-Cert = Yes + # + # in the control items for a request. + # + # Note that the majority of supplicants do not support using a + # client certificate with PEAP, so this option is unlikely to + # be usable for most people. + # + # require_client_cert = yes + } + + + # EAP-MSCHAPv2 + # + # Note that it is the EAP MS-CHAPv2 sub-module, not + # the main 'mschap' module. + # + # Note also that in order for this sub-module to work, + # the main 'mschap' module MUST ALSO be configured. + # + # This module is the *Microsoft* implementation of MS-CHAPv2 + # in EAP. There is another (incompatible) implementation + # of MS-CHAPv2 in EAP by Cisco, which FreeRADIUS does not + # currently support. + # + mschapv2 { + # In earlier versions of the server, this module + # never sent the MS-CHAP-Error message to the client. + # This worked, but it had issues when the cached + # password was wrong. The server *should* send + # "E=691 R=0" to the client, which tells it to prompt + # the user for a new password. + # + # The default is to use that functionality. which is + # known to work. If you set "send_error = yes", then + # the error message will be sent back to the client. + # This *may* help some clients work better, but *may* + # also cause other clients to stop working. + # + # send_error = no + + # Server identifier to send back in the challenge. + # This should generally be the host name of the + # RADIUS server. Or, some information to uniquely + # identify it. + # + # identity = "FreeRADIUS" + } + + + # EAP-FAST + # + # The FAST module implements the EAP-FAST protocol + # + #fast { + # Point to the common TLS configuration + # + # tls = tls-common + + # If 'cipher_list' is set here, it will over-ride the + # 'cipher_list' configuration from the 'tls-common' + # configuration. The EAP-FAST module has it's own + # over-ride for 'cipher_list' because the + # specifications mandata a different set of ciphers + # than are used by the other EAP methods. + # + # cipher_list though must include "ADH" for anonymous provisioning. + # This is not as straight forward as appending "ADH" alongside + # "DEFAULT" as "DEFAULT" contains "!aNULL" so instead it is + # recommended "ALL:!EXPORT:!eNULL:!SSLv2" is used + # + # cipher_list = "ALL:!EXPORT:!eNULL:!SSLv2" + + # PAC lifetime in seconds (default: seven days) + # + # pac_lifetime = 604800 + + # Authority ID of the server + # + # If you are running a cluster of RADIUS servers, you should make + # the value chosen here (and for "pac_opaque_key") the same on all + # your RADIUS servers. This value should be unique to your + # installation. We suggest using a domain name. + # + # authority_identity = "1234" + + # PAC Opaque encryption key (must be exactly 32 bytes in size) + # + # This value MUST be secret, and MUST be generated using + # a secure method, such as via 'openssl rand -hex 32' + # + # pac_opaque_key = "0123456789abcdef0123456789ABCDEF" + + # Same as for TTLS, PEAP, etc. + # + # virtual_server = inner-tunnel + #} +} diff --git a/raddb/mods-available/echo b/raddb/mods-available/echo new file mode 100644 index 0000000..ad3e159 --- /dev/null +++ b/raddb/mods-available/echo @@ -0,0 +1,123 @@ +# -*- text -*- +# +# $Id$ + +# +# This is a more general example of the execute module. +# +# This one is called "echo". +# +# Attribute-Name = `%{echo:/path/to/program args}` +# +# If you wish to execute an external program in more than +# one section (e.g. 'authorize', 'pre_proxy', etc), then it +# is probably best to define a different instance of the +# 'exec' module for every section. +# +# The return value of the program run determines the result +# of the exec instance call as follows: +# (See doc/configurable_failover for details) +# +# < 0 : fail the module failed +# = 0 : ok the module succeeded +# = 1 : reject the module rejected the user +# = 2 : fail the module failed +# = 3 : ok the module succeeded +# = 4 : handled the module has done everything to handle the request +# = 5 : invalid the user's configuration entry was invalid +# = 6 : userlock the user was locked out +# = 7 : notfound the user was not found +# = 8 : noop the module did nothing +# = 9 : updated the module updated information in the request +# > 9 : fail the module failed +# +exec echo { + # + # Wait for the program to finish. + # + # If we do NOT wait, then the program is "fire and + # forget", and any output attributes from it are ignored. + # + # If we are looking for the program to output + # attributes, and want to add those attributes to the + # request, then we MUST wait for the program to + # finish, and therefore set 'wait=yes' + # + # allowed values: {no, yes} + wait = yes + + # + # The name of the program to execute, and it's + # arguments. Dynamic translation is done on this + # field, so things like the following example will + # work. + # + program = "/bin/echo %{User-Name}" + + # + # The attributes which are placed into the + # environment variables for the program. + # + # Allowed values are: + # + # request attributes from the request + # config attributes from the configuration items list + # reply attributes from the reply + # proxy-request attributes from the proxy request + # proxy-reply attributes from the proxy reply + # + # Note that some attributes may not exist at some + # stages. e.g. There may be no proxy-reply + # attributes if this module is used in the + # 'authorize' section. + # + input_pairs = request + + # + # Where to place the output attributes (if any) from + # the executed program. The values allowed, and the + # restrictions as to availability, are the same as + # for the input_pairs. + # + output_pairs = reply + + # + # When to execute the program. If the packet + # type does NOT match what's listed here, then + # the module does NOT execute the program. + # + # For a list of allowed packet types, see + # the 'dictionary' file, and look for VALUEs + # of the Packet-Type attribute. + # + # By default, the module executes on ANY packet. + # Un-comment out the following line to tell the + # module to execute only if an Access-Accept is + # being sent to the NAS. + # + #packet_type = Access-Accept + + # + # Should we escape the environment variables? + # + # If this is set, all the RADIUS attributes + # are capitalised and dashes replaced with + # underscores. Also, RADIUS values are surrounded + # with double-quotes. + # + # That is to say: User-Name=BobUser => USER_NAME="BobUser" + shell_escape = yes + + # + # How long should we wait for the program to finish? + # + # Default is 10 seconds, which should be plenty for nearly + # anything. Range is 1 to 30 seconds. You are strongly + # encouraged to NOT increase this value. Decreasing can + # be used to cause authentication to fail sooner when you + # know it's going to fail anyway due to the time taken, + # thereby saving resources. + # + #timeout = 10 + +} diff --git a/raddb/mods-available/etc_group b/raddb/mods-available/etc_group new file mode 100644 index 0000000..114f24a --- /dev/null +++ b/raddb/mods-available/etc_group @@ -0,0 +1,32 @@ +# -*- text -*- +# +# $Id$ + +# "passwd" configuration, for the /etc/group file. Adds a Etc-Group-Name +# attribute for every group that the user is member of. +# +# You will have to define the Etc-Group-Name in the 'dictionary' file +# as a 'string' type. +# +# The module name "etc_group" should also be listed in the "authorize" +# section. Make sure it's listed before any other part of the server +# tries to use the Etc-Group-Name attribute. +# +# The Group and Group-Name attributes are automatically created by +# the Unix module, and do checking against /etc/group automatically. +# This means that you CANNOT use Group or Group-Name to do any other +# kind of grouping in the server. You MUST define a new group +# attribute. +# +# i.e. this module should NOT be used as-is, but should be edited to +# point to a different group file. +# +passwd etc_group { + filename = /etc/group + format = "=Etc-Group-Name:::*,User-Name" + hash_size = 50 + ignore_nislike = yes + allow_multiple_keys = yes + delimiter = ":" +} + diff --git a/raddb/mods-available/exec b/raddb/mods-available/exec new file mode 100644 index 0000000..bb1d437 --- /dev/null +++ b/raddb/mods-available/exec @@ -0,0 +1,29 @@ +# -*- text -*- +# +# $Id$ + +# +# Execute external programs +# +# This module is useful only for 'xlat'. To use it, +# put 'exec' into the 'instantiate' section. You can then +# do dynamic translation of attributes like: +# +# Attribute-Name = `%{exec:/path/to/program args}` +# +# The value of the attribute will be replaced with the output +# of the program which is executed. Due to RADIUS protocol +# limitations, any output over 253 bytes will be ignored. +# +# The RADIUS attributes from the user request will be placed +# into environment variables of the executed program, as +# described in "man unlang" and in doc/configuration/variables.rst +# +# See also "echo" for more sample configuration. +# +exec { + wait = no + input_pairs = request + shell_escape = yes + timeout = 10 +} diff --git a/raddb/mods-available/expiration b/raddb/mods-available/expiration new file mode 100644 index 0000000..5d06454 --- /dev/null +++ b/raddb/mods-available/expiration @@ -0,0 +1,13 @@ +# -*- text -*- +# +# $Id$ + +# +# The expiration module. This handles the Expiration attribute +# It should be included in the *end* of the authorize section +# in order to handle user Expiration. It should also be included +# in the instantiate section in order to register the Expiration +# compare function +# +expiration { +} diff --git a/raddb/mods-available/expr b/raddb/mods-available/expr new file mode 100644 index 0000000..ca0b3bf --- /dev/null +++ b/raddb/mods-available/expr @@ -0,0 +1,148 @@ +# -*- text -*- +# +# $Id$ + +# +# This module performs mathematical calculations: +# +# Attribute-Name = "%{expr:2 + 3 + &NAS-Port}" +# +# It supports the following operators (in order of precedence) +# +# & binary AND +# | binary OR +# << left shift +# >> right shift +# + addition +# - subtraction +# * multiply +# / divide +# %% remainder +# ^ exponentiation +# (...) sub-expression +# +# Operator precedence follows the normal rules. +# Division by zero means that the entire expression is invalid. +# +# It also allows unary negation: -1 +# And twos complement: ~1 +# +# All calculations are done on signed 63-bit integers. +# e.g. int64_t. This should be sufficient for all normal +# purposes. +# +# Hex numbers are supported: 0xabcdef +# +# As with all string expansions, you can nest the expansions: +# +# %{expr: %{NAS-Port} + 1} +# %{expr: %{sql:SELECT ... } + 1} +# +# Attribute references are supported for integer attributes. +# e.g. &NAS-Port. The benefit of using attribute references +# is that the expression is calculated directly on the +# attribute. It skips the step of "print to string, and then +# parse to number". This means it's a little faster. +# +# Otherwise, all numbers are decimal. +# + +# +# The module also registers a few paircompare functions, and +# many string manipulation functions, including: +# +# rand get random number from 0 to n-1 +# "%{rand:10}" == "9" +# +# randstr get random string built from character classes: +# c lowercase letters +# C uppercase letters +# n numbers +# a alphanumeric +# ! punctuation +# . alphanumeric + punctuation +# s alphanumeric + "./" +# o characters suitable for OTP (easily confused removed) +# h binary data as lowercase hex +# H binary data as uppercase hex +# +# "%{randstr:CCCC!!cccnnn}" == "IPFL>{saf874" +# "%{randstr:oooooooo}" == "rfVzyA4y" +# "%{randstr:hhhh}" == "68d60de3" +# +# urlquote quote special characters in URI +# "%{urlquote:http://example.org/}" == "http%3A%47%47example.org%47" +# +# urlunquote unquote URL special characters +# "%{urlunquote:http%%3A%%47%%47example.org%%47}" == "http://example.org/" +# +# escape escape string similar to rlm_sql safe_characters +# "%{escape:<img>foo.jpg</img>}" == "=60img=62foo.jpg=60/img=62" +# +# unescape reverse of escape +# "%{unescape:=60img=62foo.jpg=60/img=62}" == "<img>foo.jpg</img>" +# +# tolower convert to lowercase +# "%{tolower:Bar}" == "bar" +# +# toupper convert to uppercase +# "%{toupper:Foo}" == "FOO" +# +# md5 get md5sum hash +# "%{md5:foo}" == "acbd18db4cc2f85cedef654fccc4a4d8" +# +# sha1 get sha1 hash +# "%{sha1:foo}" == "0beec7b5ea3f0fdbc95d0dd47f3c5bc275da8a33" +# +# sha256 get sha256 hash +# "%{sha256:foo}" == "2c26b46b68ffc68ff99b453c1d30413413422d706..." +# +# sha512 get sha512 hash +# "%{sha512:foo}" == "f7fbba6e0636f890e56fbbf3283e524c6fa3204ae29838..." +# +# hmacmd5 generate HMAC-MD5 of string +# "%{hmacmd5:foo bar}" == "31b6db9e5eb4addb42f1a6ca07367adc" +# +# hmacsha1 generate HMAC-SHA1 of string +# "%{hmacsha1:foo bar}" == "85d155c55ed286a300bd1cf124de08d87e914f3a" +# +# crypt encrypt with a salt: %{crypt:salt:password} +# "%{crypt:aa:foo}" == "aaKNIEDOaueR6" +# "%{crypt:$1$abcdefgh:foo}" == "$1$abcdefgh$XxzGe9Muun7wTYbZO4sdr0" +# "%{crypt:$5$%{randstr:aaaaaaaaaaaaaaaa}:foo}" == "$1$fu4P2fcAdo9gM..." +# +# pairs serialize attributes as comma-delimited string +# "%{pairs:request:}" == "User-Name = 'foo', User-Password = 'bar', ..." +# +# base64 encode string as base64 +# "%{base64:foo}" == "Zm9v" +# +# base64tohex convert base64 to hex +# "%{base64tohex:Zm9v}" == "666f6f" +# +# explode split an attribute into multiple new attributes based on a delimiter +# "%{explode:&ref <delim>}" +# +# nexttime calculate number of seconds until next n hour(s), day(s), week(s), year(s) +# if it were 16:18, %{nexttime:1h} would expand to 2520 +# +# lasttime calculate number of seconds until last n hour(s), day(s), week(s), year(s) +# if it were 16:18, %{lasttime:1h} would expand to 4680 +# +# lpad left-pad a string +# if User-Name is "foo": "%{lpad:&User-Name 6 x}" == "xxxfoo" +# +# rpad right-pad a string +# if User-Name is "foo": "%{rpad:&User-Name 5 -}" == "foo--" +# +# concat concatenate a set of attributes, separated by a character. +# "%{concat:foo[*] ;}" +# + +expr { + # + # Characters that will not be encoded by the %{escape} + # xlat function. + # + safe_characters = "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /äéöüàâæçèéêëîïôœùûüaÿÄÉÖÜßÀÂÆÇÈÉÊËÎÏÔŒÙÛÜŸ" +} diff --git a/raddb/mods-available/files b/raddb/mods-available/files new file mode 100644 index 0000000..e3f3bf5 --- /dev/null +++ b/raddb/mods-available/files @@ -0,0 +1,30 @@ +# -*- text -*- +# +# $Id$ + +# Livingston-style 'users' file +# +# See "man users" for more information. +# +files { + # Search for files in a subdirectory of mods-config which + # matches this instance of the files module. + moddir = ${modconfdir}/${.:instance} + + # The default key attribute to use for matches. The content + # of this attribute is used to match the "name" of the + # entry. + #key = "%{%{Stripped-User-Name}:-%{User-Name}}" + + # The old "users" style file is now located here. + filename = ${moddir}/authorize + + # This is accepted for backwards compatibility + # It will be removed in a future release. +# usersfile = ${moddir}/authorize + + # These are accepted for backwards compatibility. + # They will be renamed in a future release. + acctusersfile = ${moddir}/accounting + preproxy_usersfile = ${moddir}/pre-proxy +} diff --git a/raddb/mods-available/idn b/raddb/mods-available/idn new file mode 100644 index 0000000..5340540 --- /dev/null +++ b/raddb/mods-available/idn @@ -0,0 +1,28 @@ +# -*- text -*- +# +# $Id$ + +# +# Internationalised domain names. +# + +# The expansion string: %{idn: example.com} results in an ASCII +# punycode version of the domain name. That version can then be used +# for name comparisons. Using an i18n version of the name is NOT +# RECOMMENDED, as that version is not canonical. +# +# i.e. the "same" domain name can be represented in many, many, +# different ways. Only the idn version has *one* representation. +# +idn { + # + # Allow use of unassigned Unicode code points. + # + allow_unassigned = no + + # + # Prohibit underscores and other invalid characters in domain + # names. + use_std3_ascii_rules = yes + +}
\ No newline at end of file diff --git a/raddb/mods-available/inner-eap b/raddb/mods-available/inner-eap new file mode 100644 index 0000000..576eb77 --- /dev/null +++ b/raddb/mods-available/inner-eap @@ -0,0 +1,107 @@ +# -*- text -*- +# +# $Id$ + +# +# Sample configuration for an EAP module that occurs *inside* +# of a tunneled method. It is used to limit the EAP types that +# can occur inside of the inner tunnel. +# +# See also raddb/sites-available/inner-tunnel +# +# See raddb/mods-available/eap for full documentation on the meaning of these +# configuration entries. +# +eap inner-eap { + # This is the best choice for PEAP. + default_eap_type = mschapv2 + + timer_expire = 60 + + # This should be the same as the outer eap "max sessions" + max_sessions = 2048 + + # Supported EAP-types + md5 { + } + + gtc { + # The default challenge, which many clients + # ignore.. + #challenge = "Password: " + + auth_type = PAP + } + + mschapv2 { + # See eap for documentation +# send_error = no + } + + # No TTLS or PEAP configuration should be listed here. + + ## EAP-TLS + # + # You SHOULD use different certificates than are used + # for the outer EAP configuration! + # + # You can create the "inner-server.pem" file by doing: + # + # cd raddb/certs + # vi inner-server.cnf + # make inner-server + # + # The certificate MUST be different from the "server.cnf" + # file. + # + # Support for PEAP/TLS and RFC 5176 TLS/TLS is experimental. + # It might work, or it might not. + # + tls { + private_key_password = whatever + private_key_file = ${certdir}/inner-server.pem + + # If Private key & Certificate are located in + # the same file, then private_key_file & + # certificate_file must contain the same file + # name. + # + # If ca_file (below) is not used, then the + # certificate_file below MUST include not + # only the server certificate, but ALSO all + # of the CA certificates used to sign the + # server certificate. + certificate_file = ${certdir}/inner-server.pem + + # You may want different CAs for inner and outer + # certificates. If so, edit this file. + ca_file = ${cadir}/ca.pem + + cipher_list = "DEFAULT" + + # You may want to set a very small fragment size. + # The TLS data here needs to go inside of the + # outer EAP-TLS protocol. + # + # Try values and see if they work... + # fragment_size = 1024 + + # Other needful things + dh_file = ${certdir}/dh + random_file = /dev/urandom + + # CRL and OCSP things go here. See the main "eap" + # file for details. + # check_crl = yes + # ca_path = /path/to/directory/with/ca_certs/and/crls/ + + # Accept an expired Certificate Revocation List + # +# allow_expired_crl = no + + # + # The session resumption / fast re-authentication + # cache CANNOT be used for inner sessions. + # + } +} diff --git a/raddb/mods-available/ippool b/raddb/mods-available/ippool new file mode 100644 index 0000000..1d3305b --- /dev/null +++ b/raddb/mods-available/ippool @@ -0,0 +1,66 @@ +# -*- text -*- +# +# $Id$ + +# Do server side ip pool management. Should be added in +# post-auth and accounting sections. +# +# The module also requires the existence of the Pool-Name +# attribute. That way the administrator can add the Pool-Name +# attribute in the user profiles and use different pools for +# different users. The Pool-Name attribute is a *check* item +# not a reply item. +# +# The Pool-Name should be set to the ippool module instance +# name or to DEFAULT to match any module. + +# +# Example: +# radiusd.conf: ippool students { [...] } +# ippool teachers { [...] } +# users file : DEFAULT Group == students, Pool-Name := "students" +# DEFAULT Group == teachers, Pool-Name := "teachers" +# DEFAULT Group == other, Pool-Name := "DEFAULT" +# +# Note: If you change the range parameters you must then erase the +# db files. +# +ippool main_pool { + # The main db file used to allocate addresses. + filename = ${db_dir}/db.ippool + + # The start and end ip addresses for this pool. + range_start = 192.0.2.1 + range_stop = 192.0.2.254 + + # The network mask used for this pool. + netmask = 255.255.255.0 + + # The gdbm cache size for the db files. Should + # be equal to the number of ip's available in + # the ip pool + cache_size = 800 + + # Helper db index file used in multilink + ip_index = ${db_dir}/db.ipindex + + # If set, the Framed-IP-Address already in the + # reply (if any) will be discarded, and replaced + # ith a Framed-IP-Address assigned here. + override = no + + # Specifies the maximum time in seconds that an + # entry may be active. If set to zero, means + # "no timeout". The default value is 0 + maximum_timeout = 0 + + # The key to use for the session database (which + # holds the allocated ip's) normally it should + # just be the nas ip/port (which is the default). + # + # If your NAS sends the same value of NAS-Port + # all requests, the key should be based on some + # other attribute that is in ALL requests, AND + # is unique to each machine needing an IP address. +# key = "%{NAS-IP-Address} %{NAS-Port}" +} diff --git a/raddb/mods-available/json b/raddb/mods-available/json new file mode 100644 index 0000000..02a62ae --- /dev/null +++ b/raddb/mods-available/json @@ -0,0 +1,271 @@ +# -*- text -*- +# +# +# $Id$ + +####################################################################### +# +# = JSON Module +# +# The `json` module provides the `json_encode` XLAT, which may be +# used to encode a given list of attributes into different +# formats of JSON object. +# + +# +# ## Configuration Settings +# +json { + + # + # The only options for the JSON module are to control the output + # format of the `json_encode` xlat. + # + encode { + + # + # output_mode:: set the format of JSON documenta + # that should be created. This may be one of: + # + # - object + # - object_simple + # - array + # - array_of_names + # - array_of_values + # + # Examples of each format are given below. + # +# output_mode = object + + # + # ### Formatting options for attribute names + # + attribute { + # + # prefix:: Add a colon-delimited prefix to all + # attribute names in the output document. For example, + # with a prefix of "foo", `User-Name` will be output as + # `foo:User-Name`. + # +# prefix = + } + + # + # ### Formatting options for attribute values + # + value { + + # + # single_value_as_array:: always put values in an array + # + # Output formats will by default put single values as a + # JSON object (string, integer, etc). More than one + # value will, depending on the output format, be added + # as an array. + # + # When this option is enabled, values will always be + # added as an array. + # +# single_value_as_array = no + + # + # enum_as_integer:: output the integer value of + # enumerated attributes + # + # Where an attribute has enum values, the textual + # representation of the value will normally be output. + # Enable this option to force the numeric value + # instead. + # +# enum_as_integer = no + + # + # always_string:: force all values to be strings + # + # Integer values are normally written to the JSON + # document as numbers (i.e. without quotes). Enable + # this option to force all values to be as quoted + # strings. + # +# always_string = no + } + + } + +} + + +# +# ## Expansions +# +# rlm_json provides the below xlat function. +# +# ### %{json_encode:...} +# +# Generates a JSON document from a given list of attribute templates. The +# format of document generated can be controlled with the 'encode' section in +# the module configuration. Attribute values will automatically be escaped so +# they are JSON-safe. +# +# NOTE: The name of the xlat is based on the instance name of this module. If +# the module was defined as `json jdoc {...}`, then the xlat name will be +# `jdoc_encode`. +# +# The xlat should be passed a list of attributes to encode. Each attribute +# (after template expansion) will be added to a list of attributes to include +# in the JSON document. If any of the attributes given are preceeded with a `!` +# then they are removed from the list. Once all attributes have been processed, +# the JSON document will be created using this list. +# +# For example, the following will produce a JSON document with two attributes in +# it, `User-Name` and `Calling-Station-Id`, from the RADIUS request: +# +# .Example +# +# ``` +# %{json_encode:&User-Name &Calling-Station-Id} +# ``` +# +# The following will include all attributes in the RADIUS request, except for +# `User-Password`: +# +# .Example +# +# ``` +# %{json_encode:&request[*] !&User-Password} +# ``` +# +# In another (contrived) example, all the attributes in the RADIUS request will +# be included in the document, _except_ any attributes in the RADIUS reply. +# `&User-Name` will be included from the control list, too, if it exists: +# +# .Example +# +# ``` +# %{json_encode:&request[*] !&reply[*] &control.User-Name} +# ``` +# +# #### Output format modes +# +# There are a number of output modes, each generating a different format of +# JSON document. +# +# NOTE: In the JSON document, "type" is the type of the _attribute_, which is +# not necessarily the same as the type of the "value" in the document. See e.g. +# `Login-Service` above, an enumerated value. +# +# The following examples assume the three attributes are being added to the +# JSON document: +# +# ``` +# User-Name = bob +# Filter-Id = ab +# Filter-Id += cd +# ``` +# +# #### Object output mode examples +# +# These modes output a JSON object. +# +# .Output mode "object" +# +# [source,json] +# ---- +# { +# "User-Name": { +# "type": "string", +# "value": "bob" +# }, +# "Filter-Id": { +# "type": "string", +# "value": ["ab","cd"] +# } +# } +# ---- +# +# .Output mode "object_simple" +# +# [source,json] +# ---- +# { +# "User-Name": "bob", +# "Filter-Id": ["ab","cd"] +# } +# ---- +# +# #### Array output mode examples +# +# The "array" mode is a list of objects, each containing an attribute. If the +# "single_value_as_array" value option is set then each attribute will only +# appear once in the array, and "value" will be a list of all the values from +# the same attribute. +# +# .Output mode "array" +# +# [source,json] +# ---- +# [ +# { +# "name": "User-Name", +# "type": "string", +# "value": "bob" +# }, +# { +# "name": "Filter-Id", +# "type": "string", +# "value": "ab" +# }, +# { +# "name": "Filter-Id", +# "type": "string", +# "value": "cd" +# } +# ] +# ---- +# +# .Output mode "array" when "single_value_as_array" is also set +# +# [source,json] +# ---- +# [ +# { +# "name": "User-Name", +# "type": "string", +# "value": ["bob"] +# }, +# { +# "name": "Filter-Id", +# "type": "string", +# "value": ["ab","cd"] +# } +# ] +# ---- +# +# The following output modes either do not include the attribute names or +# values. They are likely to be useful only when the attributes are +# individually specified and _guaranteed to exist_. In this case the attribute +# names in `array_of_names` will have corresponding indexes to the values in +# `array_of_values`. +# +# .Output mode "array_of_names" +# +# [source,json] +# ---- +# [ +# "User-Name", +# "Filter-Id", +# "Filter-Id" +# ] +# ---- +# +# .Output mode "array_of_values" +# +# [source,json] +# ---- +# [ +# "bob", +# "ab", +# "cd" +# ] +# ---- +# diff --git a/raddb/mods-available/krb5 b/raddb/mods-available/krb5 new file mode 100644 index 0000000..c88b5fb --- /dev/null +++ b/raddb/mods-available/krb5 @@ -0,0 +1,82 @@ +# -*- text -*- +# +# $Id$ + +# +# Kerberos. See doc/modules/rlm_krb5 for minimal docs. +# +krb5 { + # + # The keytab file MUST be owned by the UID/GID used by the server. + # The keytab file MUST be writable by the server. + # The keytab file MUST NOT be readable by other users on the system. + # The keytab file MUST exist before the server is started. + # + keytab = ${localstatedir}/lib/radiusd/keytab + service_principal = name_of_principle + + # Pool of krb5 contexts, this allows us to make the module multithreaded + # and to avoid expensive operations like resolving and opening keytabs + # on every request. It may also allow TCP connections to the KDC to be + # cached if that is supported by the version of libkrb5 used. + # + # The context pool is only used if the underlying libkrb5 reported + # that it was thread safe at compile time. + # + pool { + # Connections to create during module instantiation. + # If the server cannot create specified number of + # connections during instantiation it will exit. + # Set to 0 to allow the server to start without the + # KDC being available. + start = ${thread[pool].start_servers} + + # Minimum number of connections to keep open + min = ${thread[pool].min_spare_servers} + + # Maximum number of connections + # + # If these connections are all in use and a new one + # is requested, the request will NOT get a connection. + # + # Setting 'max' to LESS than the number of threads means + # that some threads may starve, and you will see errors + # like 'No connections available and at max connection limit' + # + # Setting 'max' to MORE than the number of threads means + # that there are more connections than necessary. + max = ${thread[pool].max_servers} + + # Spare connections to be left idle + # + # NOTE: Idle connections WILL be closed if "idle_timeout" + # is set. This should be less than or equal to "max" above. + spare = ${thread[pool].max_spare_servers} + + # Number of uses before the connection is closed + # + # 0 means "infinite" + uses = 0 + + # The lifetime (in seconds) of the connection + # + # NOTE: A setting of 0 means infinite (no limit). + lifetime = 0 + + # The idle timeout (in seconds). A connection which is + # unused for this length of time will be closed. + # + # NOTE: A setting of 0 means infinite (no timeout). + idle_timeout = 0 + + # NOTE: All configuration settings are enforced. If a + # connection is closed because of "idle_timeout", + # "uses", or "lifetime", then the total number of + # connections MAY fall below "min". When that + # happens, it will open a new connection. It will + # also log a WARNING message. + # + # The solution is to either lower the "min" connections, + # or increase lifetime/idle_timeout. + } +} diff --git a/raddb/mods-available/ldap b/raddb/mods-available/ldap new file mode 100644 index 0000000..997d41e --- /dev/null +++ b/raddb/mods-available/ldap @@ -0,0 +1,712 @@ +# -*- text -*- +# +# $Id$ + +# +# Lightweight Directory Access Protocol (LDAP) +# +ldap { + # Note that this needs to match the name(s) in the LDAP server + # certificate, if you're using ldaps. See OpenLDAP documentation + # for the behavioral semantics of specifying more than one host. + # + # Depending on the libldap in use, server may be an LDAP URI. + # In the case of OpenLDAP this allows additional the following + # additional schemes: + # - ldaps:// (LDAP over SSL) + # - ldapi:// (LDAP over Unix socket) + # - ldapc:// (Connectionless LDAP) + server = 'localhost' +# server = 'ldap.rrdns.example.org' +# server = 'ldap.rrdns.example.org' + + # Port to connect on, defaults to 389, will be ignored for LDAP URIs. +# port = 389 + + # Administrator account for searching and possibly modifying. + # If using SASL + KRB5 these should be commented out. +# identity = 'cn=admin,dc=example,dc=org' +# password = mypass + + # Unless overridden in another section, the dn from which all + # searches will start from. + base_dn = 'dc=example,dc=org' + + # + # You can run the 'ldapsearch' command line tool using the + # parameters from this module's configuration. + # + # ldapsearch -D ${identity} -w ${password} -h ${server} -b 'CN=user,${base_dn}' + # + # That will give you the LDAP information for 'user'. + # + # Group membership can be queried by using the above "ldapsearch" string, + # and adding "memberof" qualifiers. For ActiveDirectory, use: + # + # ldapsearch ... '(&(objectClass=user)(sAMAccountName=user)(memberof=CN=group,${base_dn}))' + # + # Where 'user' is the user as above, and 'group' is the group you are querying for. + # + + # + # SASL parameters to use for admin binds + # + # When we're prompted by the SASL library, these control + # the responses given, as well as the identity and password + # directives above. + # + # If any directive is commented out, a NULL response will be + # provided to cyrus-sasl. + # + # Unfortunately the only way to control Keberos here is through + # environmental variables, as cyrus-sasl provides no API to + # set the krb5 config directly. + # + # Full documentation for MIT krb5 can be found here: + # + # http://web.mit.edu/kerberos/krb5-devel/doc/admin/env_variables.html + # + # At a minimum you probably want to set KRB5_CLIENT_KTNAME. + # + sasl { + # SASL mechanism +# mech = 'PLAIN' + + # SASL authorisation identity to proxy. +# proxy = 'autz_id' + + # SASL realm. Used for kerberos. +# realm = 'example.org' + } + + # + # Generic valuepair attribute + # + + # If set, this will attribute will be retrieved in addition to any + # mapped attributes. + # + # Values should be in the format: + # <radius attr> <op> <value> + # + # Where: + # <radius attr>: Is the attribute you wish to create + # with any valid list and request qualifiers. + # <op>: Is any assignment operator (=, :=, +=). + # <value>: Is the value to parse into the new valuepair. + # If the value is wrapped in double quotes it + # will be xlat expanded. +# valuepair_attribute = 'radiusAttribute' + + # + # Mapping of LDAP directory attributes to RADIUS dictionary attributes. + # + + # WARNING: Although this format is almost identical to the unlang + # update section format, it does *NOT* mean that you can use other + # unlang constructs in module configuration files. + # + # Configuration items are in the format: + # <radius attr> <op> <ldap attr> + # + # Where: + # <radius attr>: Is the destination RADIUS attribute + # with any valid list and request qualifiers. + # <op>: Is any assignment attribute (=, :=, +=, -=). + # <ldap attr>: Is the attribute associated with user or + # profile objects in the LDAP directory. + # If the attribute name is wrapped in double + # quotes it will be xlat expanded. + # + # Request and list qualifiers may also be placed after the 'update' + # section name to set defaults destination requests/lists + # for unqualified RADIUS attributes. + # + # Note: LDAP attribute names should be single quoted unless you want + # the name value to be derived from an xlat expansion, or an + # attribute ref. + update { + control:Password-With-Header += 'userPassword' +# control:NT-Password := 'ntPassword' +# reply:Reply-Message := 'radiusReplyMessage' +# reply:Tunnel-Type := 'radiusTunnelType' +# reply:Tunnel-Medium-Type := 'radiusTunnelMediumType' +# reply:Tunnel-Private-Group-ID := 'radiusTunnelPrivategroupId' + + # Where only a list is specified as the RADIUS attribute, + # the value of the LDAP attribute is parsed as a valuepair + # in the same format as the 'valuepair_attribute' (above). + control: += 'radiusControlAttribute' + request: += 'radiusRequestAttribute' + reply: += 'radiusReplyAttribute' + } + + # Set to yes if you have eDirectory and want to use the universal + # password mechanism. +# edir = no + + # Set to yes if you want to bind as the user after retrieving the + # Cleartext-Password. This will consume the login grace, and + # verify user authorization. +# edir_autz = no + + # LDAP "bind as user" configuration to check PAP passwords. + # + # Active Directory needs "bind as user", which can be done by + # adding the following "if" statement to the authorize {} section + # of the virtual server, after the "ldap" module. For + # example: + # + # ... + # ldap + # if ((ok || updated) && User-Password && !control:Auth-Type) { + # update { + # control:Auth-Type := ldap + # } + # } + # ... + # + # You will also need to uncomment the "Auth-Type LDAP" block in the + # "authenticate" section. + # + # This configuration is required because AD will not return the users + # "known good" password to FreeRADIUS. Instead, FreeRADIUS has to run + # "Auth-Type LDAP" in order to do an LDAP "bind as user", which will hand + # the user name / password to AD for verification. + # + + # + # Name of the attribute that contains the user DN. + # The default name is LDAP-UserDn. + # + # If you have multiple LDAP instances, you should + # change this configuration item to: + # + # ${.:instance}-LDAP-UserDn + # + # That change allows the modules to set their own + # User DN, and to not conflict with each other. + # + user_dn = "LDAP-UserDn" + + # + # User object identification. + # + user { + # Where to start searching in the tree for users + base_dn = "${..base_dn}" + + # Filter for user objects, should be specific enough + # to identify a single user object. + # + # For Active Directory, you should use + # "samaccountname=" instead of "uid=" + # + filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})" + + # For Active Directory nested group, you should comment out the previous 'filter = ...' + # and use the below. Where 'group' is the group you are querying for. + # + # NOTE: The string '1.2.840.113556.1.4.1941' specifies LDAP_MATCHING_RULE_IN_CHAIN. + # This applies only to DN attributes. This is an extended match operator that walks + # the chain of ancestry in objects all the way to the root until it finds a match. + # This reveals group nesting. It is available only on domain controllers with + # Windows Server 2003 SP2 or Windows Server 2008 (or above). + # + # See: https://social.technet.microsoft.com/wiki/contents/articles/5392.active-directory-ldap-syntax-filters.aspx + # +# filter = "(&(objectClass=user)(sAMAccountName=%{%{Stripped-User-Name}:-%{User-Name}})(memberOf:1.2.840.113556.1.4.1941:=cn=group,${..base_dn}))" + + # SASL parameters to use for user binds + # + # When we're prompted by the SASL library, these control + # the responses given. + # + # Any of the config items below may be an attribute ref + # or and expansion, so different SASL mechs, proxy IDs + # and realms may be used for different users. + sasl { + # SASL mechanism +# mech = 'PLAIN' + + # SASL authorisation identity to proxy. +# proxy = &User-Name + + # SASL realm. Used for kerberos. +# realm = 'example.org' + } + + # Search scope, may be 'base', 'one', sub' or 'children' +# scope = 'sub' + + # Server side result sorting + # + # A list of space delimited attributes to order the result + # set by, if the filter matches multiple objects. + # Only the first result in the set will be processed. + # + # If the attribute name is prefixed with a hyphen '-' the + # sorting order will be reversed for that attribute. + # + # If sort_by is set, and the server does not support sorting + # the search will fail. +# sort_by = '-uid' + + # If this is undefined, anyone is authorised. + # If it is defined, the contents of this attribute + # determine whether or not the user is authorised +# access_attribute = 'dialupAccess' + + # Control whether the presence of 'access_attribute' + # allows access, or denys access. + # + # If 'yes', and the access_attribute is present, or + # 'no' and the access_attribute is absent then access + # will be allowed. + # + # If 'yes', and the access_attribute is absent, or + # 'no' and the access_attribute is present, then + # access will not be allowed. + # + # If the value of the access_attribute is 'false', it + # will negate the result. + # + # e.g. + # access_positive = yes + # access_attribute = userAccessAllowed + # + # With an LDAP object containing: + # userAccessAllowed: false + # + # Will result in the user being locked out. +# access_positive = yes + } + + # + # User membership checking. + # + group { + # Where to start searching in the tree for groups + base_dn = "${..base_dn}" + + # Filter for group objects, should match all available + # group objects a user might be a member of. + # + # If using Active Directory you are likely to need "group" + # instead of "posixGroup". + filter = '(objectClass=posixGroup)' + + # Search scope, may be 'base', 'one', sub' or 'children' +# scope = 'sub' + + # Attribute that uniquely identifies a group. + # Is used when converting group DNs to group + # names. +# name_attribute = cn + + # Filter to find all group objects a user is a member of. + # That is, group objects with attributes that + # identify members (the inverse of membership_attribute). + # + # Note that this configuration references the "user_dn" + # configuration defined above. + # +# membership_filter = "(|(member=%{control:${..user_dn}})(memberUid=%{%{Stripped-User-Name}:-%{User-Name}}))" + + # The attribute, in user objects, which contain the names + # or DNs of groups a user is a member of. + # + # Unless a conversion between group name and group DN is + # needed, there's no requirement for the group objects + # referenced to actually exist. + # + # If the LDAP server does not support the "memberOf" + # attribute (or equivalent), then you will need to use the + # membership_filter option above instead. If you can't see + # the memberOf attribute then it is also possible that the + # LDAP bind user does not have the correct permissions to + # view it. + membership_attribute = 'memberOf' + + # If cacheable_name or cacheable_dn are enabled, + # all group information for the user will be + # retrieved from the directory and written to LDAP-Group + # attributes appropriate for the instance of rlm_ldap. + # + # For group comparisons these attributes will be checked + # instead of querying the LDAP directory directly. + # + # This feature is intended to be used with rlm_cache. + # + # If you wish to use this feature, you should enable + # the type that matches the format of your check items + # i.e. if your groups are specified as DNs then enable + # cacheable_dn else enable cacheable_name. +# cacheable_name = 'no' +# cacheable_dn = 'no' + + # Override the normal cache attribute (<inst>-LDAP-Group or + # LDAP-Group if using the default instance) and create a + # custom attribute. This can help if multiple module instances + # are used in fail-over. +# cache_attribute = 'LDAP-Cached-Membership' + + # If the group being checked is specified as a name, but + # the user's groups are referenced by DN, and one of those + # group DNs is invalid, the whole group check is treated as + # invalid, and a negative result will be returned. + # When set to 'yes', this option ignores invalid DN + # references. +# allow_dangling_group_ref = 'no' + } + + # + # User profiles. RADIUS profile objects contain sets of attributes + # to insert into the request. These attributes are mapped using + # the same mapping scheme applied to user objects (the update section above). + # + profile { + # Filter for RADIUS profile objects +# filter = '(objectclass=radiusprofile)' + + # The default profile. This may be a DN or an attribute + # reference. + # To get old v2.2.x style behaviour, or to use the + # &User-Profile attribute to specify the default profile, + # set this to &control:User-Profile. +# default = 'cn=radprofile,dc=example,dc=org' + + # The LDAP attribute containing profile DNs to apply + # in addition to the default profile above. These are + # retrieved from the user object, at the same time as the + # attributes from the update section, are are applied + # if authorization is successful. +# attribute = 'radiusProfileDn' + } + + # + # Bulk load clients from the directory + # + client { + # Where to start searching in the tree for clients + base_dn = "${..base_dn}" + + # + # Filter to match client objects + # + filter = '(objectClass=radiusClient)' + + # Search scope, may be 'base', 'one', 'sub' or 'children' +# scope = 'sub' + + # + # Sets default values (not obtained from LDAP) for new client entries + # + template { +# login = 'test' +# password = 'test' +# proto = tcp +# require_message_authenticator = yes + + # Uncomment to add a home_server with the same + # attributes as the client. +# coa_server { +# response_window = 2.0 +# } + } + + # + # Client attribute mappings are in the format: + # <client attribute> = <ldap attribute> + # + # The following attributes are required: + # * ipaddr | ipv4addr | ipv6addr - Client IP Address. + # * secret - RADIUS shared secret. + # + # All other attributes usually supported in a client + # definition are also supported here. + # + # Schemas are available in doc/schemas/ldap for openldap and eDirectory + # + attribute { + ipaddr = 'radiusClientIdentifier' + secret = 'radiusClientSecret' +# shortname = 'radiusClientShortname' +# nas_type = 'radiusClientType' +# virtual_server = 'radiusClientVirtualServer' +# require_message_authenticator = 'radiusClientRequireMa' + } + } + + # Load clients on startup +# read_clients = no + + # + # Modify user object on receiving Accounting-Request + # + + # Useful for recording things like the last time the user logged + # in, or the Acct-Session-ID for CoA/DM. + # + # LDAP modification items are in the format: + # <ldap attr> <op> <value> + # + # Where: + # <ldap attr>: The LDAP attribute to add modify or delete. + # <op>: One of the assignment operators: + # (:=, +=, -=, ++). + # Note: '=' is *not* supported. + # <value>: The value to add modify or delete. + # + # WARNING: If using the ':=' operator with a multi-valued LDAP + # attribute, all instances of the attribute will be removed and + # replaced with a single attribute. + accounting { + reference = "%{tolower:type.%{Acct-Status-Type}}" + + type { + start { + update { + description := "Online at %S" + } + } + + interim-update { + update { + description := "Last seen at %S" + } + } + + stop { + update { + description := "Offline at %S" + } + } + } + } + + # + # Post-Auth can modify LDAP objects too + # + post-auth { + update { + description := "Authenticated at %S" + } + } + + # + # LDAP connection-specific options. + # + # These options set timeouts, keep-alives, etc. for the connections. + # + options { + # Control under which situations aliases are followed. + # May be one of 'never', 'searching', 'finding' or 'always' + # default: libldap's default which is usually 'never'. + # + # LDAP_OPT_DEREF is set to this value. +# dereference = 'always' + + # + # The following two configuration items control whether the + # server follows references returned by LDAP directory. + # They are mostly for Active Directory compatibility. + # If you set these to 'no', then searches will likely return + # 'operations error', instead of a useful result. + # + # 'rebind' causes any connections being established to follow + # referrals to be bound using the admin credentials defined + # for this module. If it is set to 'no' libldap will bind + # to those connections anonymously. + # + chase_referrals = yes + rebind = yes + + # SASL Security Properties (see SASL_SECPROPS in ldap.conf man page). + # Note - uncomment when using GSS-API sasl mechanism along with TLS + # encryption against Active-Directory LDAP servers (this disables + # sealing and signing at the GSS level as required by AD). + #sasl_secprops = 'noanonymous,noplain,maxssf=0' + + # Seconds to wait for LDAP query to finish. default: 20 + res_timeout = 10 + + # Seconds LDAP server has to process the query (server-side + # time limit). default: 20 + # + # LDAP_OPT_TIMELIMIT is set to this value. + srv_timelimit = 3 + + # Seconds to wait for response of the server. (network + # failures) default: 10 + # + # LDAP_OPT_NETWORK_TIMEOUT is set to this value. + net_timeout = 1 + + # LDAP_OPT_X_KEEPALIVE_IDLE + idle = 60 + + # LDAP_OPT_X_KEEPALIVE_PROBES + probes = 3 + + # LDAP_OPT_X_KEEPALIVE_INTERVAL + interval = 3 + + # ldap_debug: debug flag for LDAP SDK + # (see OpenLDAP documentation). Set this to enable + # huge amounts of LDAP debugging on the screen. + # You should only use this if you are an LDAP expert. + # + # default: 0x0000 (no debugging messages) + # Example:(LDAP_DEBUG_FILTER+LDAP_DEBUG_CONNS) + ldap_debug = 0x0028 + } + + # + # This subsection configures the tls related items + # that control how FreeRADIUS connects to an LDAP + # server. It contains all of the 'tls_*' configuration + # entries used in older versions of FreeRADIUS. Those + # configuration entries can still be used, but we recommend + # using these. + # + # Note that some distributions use NSS for libldap instead + # of OpenSSL. + # + # If you see something like this in the debug output: + # + # TLSMC: MozNSS compatibility interception begins. + # + # Then there is a problem. + # + # THIS LDAP INSTALLATION WILL NOT WORK WITH FREERADIUS. + # + # You MUST install fixed LDAP libraries which use OpenSSL. + # + # For more details, see: + # + # http://packages.networkradius.com + # + tls { + # Set this to 'yes' to use TLS encrypted connections + # to the LDAP database by using the StartTLS extended + # operation. + # + # The StartTLS operation is supposed to be + # used with normal ldap connections instead of + # using ldaps (port 636) connections +# start_tls = yes + +# ca_file = ${certdir}/cacert.pem + +# ca_path = ${certdir} +# certificate_file = /path/to/radius.crt +# private_key_file = /path/to/radius.key +# random_file = /dev/urandom + + # Certificate Verification requirements. Can be: + # 'never' (do not even bother trying) + # 'allow' (try, but don't fail if the certificate + # cannot be verified) + # 'demand' (fail if the certificate does not verify) + # 'hard' (similar to 'demand' but fails if TLS + # cannot negotiate) + # + # The default is libldap's default, which varies based + # on the contents of ldap.conf. + +# require_cert = 'demand' + + # + # Check the CRL, as with the EAP module. + # + # The default is "no". + # +# check_crl = yes + + # + # Minimum TLS version to accept. We STRONGLY recommend + # setting this to "1.2" + # +# tls_min_version = "1.2" + + # Set this option to specify the allowed + # TLS cipher suites. The format is listed + # in "man 1 ciphers". + # + cipher_list = "DEFAULT" + } + + # As of v3, the 'pool' section has replaced the + # following v2 configuration items: + # + # ldap_connections_number + + # + # The connection pool is used to pool outgoing connections. + # + # When the server is not threaded, the connection pool + # limits are ignored, and only one connection is used. + pool { + # Connections to create during module instantiation. + # If the server cannot create specified number of + # connections during instantiation it will exit. + # Set to 0 to allow the server to start without the + # directory being available. + start = ${thread[pool].start_servers} + + # Minimum number of connections to keep open + min = ${thread[pool].min_spare_servers} + + # Maximum number of connections + # + # If these connections are all in use and a new one + # is requested, the request will NOT get a connection. + # + # Setting 'max' to LESS than the number of threads means + # that some threads may starve, and you will see errors + # like 'No connections available and at max connection limit' + # + # Setting 'max' to MORE than the number of threads means + # that there are more connections than necessary. + max = ${thread[pool].max_servers} + + # Spare connections to be left idle + # + # NOTE: Idle connections WILL be closed if "idle_timeout" + # is set. This should be less than or equal to "max" above. + spare = ${thread[pool].max_spare_servers} + + # Number of uses before the connection is closed + # + # 0 means "infinite" + uses = 0 + + # The number of seconds to wait after the server tries + # to open a connection, and fails. During this time, + # no new connections will be opened. + retry_delay = 30 + + # The lifetime (in seconds) of the connection + lifetime = 0 + + # Idle timeout (in seconds). A connection which is + # unused for this length of time will be closed. + idle_timeout = 60 + + # NOTE: All configuration settings are enforced. If a + # connection is closed because of 'idle_timeout', + # 'uses', or 'lifetime', then the total number of + # connections MAY fall below 'min'. When that + # happens, it will open a new connection. It will + # also log a WARNING message. + # + # The solution is to either lower the 'min' connections, + # or increase lifetime/idle_timeout. + + # Maximum number of times an operation can be retried + # if it returns an error which indicates the connection + # needs to be restarted. This includes timeouts. + max_retries = 5 + } +} diff --git a/raddb/mods-available/ldap_google b/raddb/mods-available/ldap_google new file mode 100644 index 0000000..03c98d3 --- /dev/null +++ b/raddb/mods-available/ldap_google @@ -0,0 +1,262 @@ +# -*- text -*- +# +# $Id$ + +# +# This file contains an instance of the ldap module which has been +# configured for the G Suite / Google Workspace Secure LDAP server. +# There are a few steps which still need to be taken, but they are +# documented clearly below. +# +# In order to use the Google LDAP server, a client must first be +# created. See Google's documentation for doing this: +# +# https://support.google.com/a/answer/9048434?hl=en&ref_topic=9173976 +# +# Google LDAP requires that any system connecting to it use a client +# certificate. However, FreeRADIUS also requires a username and +# password in the "ldap" module configuration. Therere before +# downloading the client certificate from Google, you should choose +# the option to generate access credentials in order to obtain a +# username and password. That username and password should be used +# below. +# +# Ensure the Goolge client configuration which is used for FreeRADIUS +# has sufficient permissions to read user information, and, if group +# membership is part of the FreeRADIUS policy, ensure that the client +# can read group information. This configuration is done on Google's +# systems. Please see the Google documentation for more information. +# +# NOTE: The Google LDAP database does NOT return user passwords in +# the search results! +# +# Therefore, if Google LDAP is being used for authentication, it will +# ONLY work when using "LDAP bind as user". The authentication +# method used there MUST also provide the user password in plain +# text. This limits the use of Google LDAP to PAP, and TTLS+PAP. +# Anything else simply will not work, and nothing you do will ever +# make it work. +# +# The Google LDAP service has been observed to have poor +# performance compared to a dedicated / local LDAP server like +# OpenLDAP. In order to improve performance, we simply bypass it +# completely by caching things associated with accept and reject. +# See mods-available/cache_auth for the cache configuration, and +# sites-available/google-ldap-auth for a sample virtual server which +# uses this module, and the cache. +# +# In addition, if you are using Google LDAP service as part of WiFi +# authentication (remember, only TTLS+PAP will work!), then we also +# recommend enabling the "cache" configuration in mods-available/eap. +# That cache is a separate one from mods-available/cache_auth, and +# both caches can be used at the same time. +# +# +# The comments in this file are specific to using the Google Secure +# LDAP service. For more general LDAP module configuration, see the +# mods-available/ldap. +# +ldap ldap_google { + # The standard Google LDAP server URL + server = 'ldaps://ldap.google.com:636/' + + # Google LDAP client username and password as generated during + # client creation. +# identity = 'myuser' +# password = 'mypass' + + # Base dn for your organisation. + base_dn = 'dc=example,dc=org' + + # + # The default Google LDAP schema can be seen here + # + # https://support.google.com/a/answer/9188164 + # + # Custom attributes can be added to user profiles, and those + # custom attributes can then be accessed in the LDAP + # directory: + # + # https://support.google.com/a/answer/6208725 + # + # You can run the 'ldapsearch' command line tool using the + # parameters from this module's configuration. + # + # LDAPTLS_REQCERT=ALLOW \ + # LDAPTLS_CERT="<Google certificate file>" \ + # LDAPTLS_KEY="<Google key file>" \ + # ldapsearch -H ${server} -b '${base_dn}' '(uid=user)' + # + # That command will return the LDAP information for 'user'. + # + # Group membership can be queried by using the above "ldapsearch" string, + # and adding "memberof" qualifiers. + # + +# valuepair_attribute = 'radiusAttribute' + + update { +# reply:Reply-Message := 'radiusReplyMessage' +# reply:Tunnel-Type := 'radiusTunnelType' +# reply:Tunnel-Medium-Type := 'radiusTunnelMediumType' +# reply:Tunnel-Private-Group-ID := 'radiusTunnelPrivategroupId' + + control: += 'radiusControlAttribute' + request: += 'radiusRequestAttribute' + reply: += 'radiusReplyAttribute' + } + + # + # In order to use LDAP "bind as user" authentication, you + # should add following "if" statement to the authorize {} + # section of the virtual server, after the "ldap" module. + # For example: + # + # ... + # ldap_google + # if ((ok || updated) && User-Password && !control:Auth-Type) { + # update { + # &control:Auth-Type := ldap + # } + # } + # ... + # + # You will also need to uncomment the "Auth-Type LDAP" block in the + # "authenticate" section. + # + # Note that these configuration steps have already been done + # in the sample virtual server, in + # sites-available/google-ldap-auth. + # + + # + # If you change this, you will also need to update the + # "cache_ldap_user_dn" module in mods-available/cache_auth. + # + user_dn = "LDAP-UserDn" + + # + # User object identification. + # + user { + # The typical Google LDAP configuration has users under "ou=Users..." + base_dn = "ou=Users,${..base_dn}" + + filter = "(uid=%{%{Stripped-User-Name}:-%{User-Name}})" + + scope = 'sub' + +# sort_by = '-uid' + +# access_attribute = 'dialupAccess' + +# access_positive = yes + } + + # + # User membership checking. + # + group { + # The typical Google LDAP configuration has groups under "ou=Groups..." + base_dn = "ou=Groups,${..base_dn}" + + filter = '(objectClass=posixGroup)' + + scope = 'sub' + + name_attribute = cn + + # + # Google Secure LDAP supports the "memberOf" + # attribute, which is more efficient than using this + # filter. + # + # You should also check the permissions of the client + # in Google's systems to ensure that it is allowed to + # read group information. + # +# membership_filter = "(|(member=%{control:${..user_dn}})(memberUid=%{%{Stripped-User-Name}:-%{User-Name}}))" + + membership_attribute = 'memberOf' + + # + # If the "memberOf" attribute is used for retrieving group membership, + # then you should also use "cacheable_dn", in orser to cache the group details. + # "memberOf" is a list of fully quallified group DNs which the user belongs to, + # so using the DN for the cache avoids further lookups to retrieve group names. + # +# cacheable_name = 'no' +# cacheable_dn = 'no' + +# cache_attribute = 'LDAP-Cached-Membership' + +# allow_dangling_group_ref = 'no' + } + + options { +# dereference = 'always' + + # Google Secure LDAP does not appear to do referrals, so we might as well + # turn this off. + chase_referrals = no +# rebind = yes + + # Some reasonable defaults for use with Google Secure LDAP + # + # See mods-available/ldap for a complete description + # of what these configuration options mean. + # + res_timeout = 10 + srv_timelimit = 3 + net_timeout = 3 + idle = 60 + probes = 3 + interval = 3 + + ldap_debug = 0x0000 + } + + tls { + + # + # The certificate and key which were downloaded from the Google + # client tools are configured here. + # + # By default ${certdir} is raddb/certs/. You can + # please these files anywhere you want. The only + # requirement is that they are readable by + # FreeRADIUS, and NOT readable by anyone else on the + # system! + # +# certificate_file = ${certdir}/google/certificate.crt +# private_key_file = ${certdir}/google/key.key +# random_file = /dev/urandom + + # + # Google Secure LDAP uses a self signed certificate + # so this configuration needs to be set to 'allow' + # + require_cert = 'allow' + + # + # We recommend not using TLS 1.0 or 1.1. + # +# tls_min_version = "1.2" + } + + # + # See mods-available/ldap for documentation on the "pool" + # section and its configuration items. + # + pool { + start = ${thread[pool].start_servers} + min = ${thread[pool].min_spare_servers} + max = ${thread[pool].max_servers} + spare = ${thread[pool].max_spare_servers} + + uses = 0 + retry_delay = 30 + lifetime = 0 + idle_timeout = 60 + } +} diff --git a/raddb/mods-available/linelog b/raddb/mods-available/linelog new file mode 100644 index 0000000..66d2682 --- /dev/null +++ b/raddb/mods-available/linelog @@ -0,0 +1,170 @@ +# -*- text -*- +# +# $Id$ + +# +# The "linelog" module will log one line of text to a file. +# Both the filename and the line of text are dynamically expanded. +# +# We STRONGLY suggest that you do not use data from the +# packet as part of the filename. +# +linelog { + # + # The file where the logs will go. + # + # If the filename is "syslog", then the log messages will + # go to syslog. + # + # The output can be directed to stdout by using /dev/stdout + # + filename = ${logdir}/linelog + + # + # Most file systems can handly nearly the full range of UTF-8 + # characters. Ones that can deal with a limited range should + # set this to "yes". + # + escape_filenames = no + + # + # The Unix-style permissions on the log file. + # + # Depending on format string, the log file may contain secret or + # private information about users. Keep the file permissions as + # restrictive as possible. + permissions = 0600 + + # The Unix group which owns the log file. + # + # The user that freeradius runs as must be in the specified + # group, otherwise it will not be possible to set the group. +# group = ${security.group} + + # Syslog facility (if logging via syslog). + # Defaults to the syslog_facility config item in radiusd.conf. + # Standard facilities are: + # - kern Messages generated by the kernel. These cannot + # be generated by any user processes. + # - user Messages generated by random user processes. + # This is the default facility identifier if + # none is specified. + # - mail The mail system. + # - daemon System daemons, such as routed(8), that are not + # provided for explicitly by other facilities. + # - auth The authorization system: login(1), su(1), + # getty(8), etc. + # - lpr The line printer spooling system: cups-lpd(8), + # cupsd(8), etc. + # - news The network news system. + # - uucp The uucp system. + # - cron The cron daemon: cron(8). + # - authpriv The same as LOG_AUTH, but logged to a file + # readable only by selected individuals. + # - ftp The file transfer protocol daemons: ftpd(8), + # tftpd(8). + # - local[0-7] Reserved for local use. +# syslog_facility = daemon + + # Syslog severity (if logging via syslog). Defaults to info. + # Possible values are: + # - emergency A panic condition. This is normally broadcast + # to all users. + # - alert A condition that should be corrected immediately, + # such as a corrupted system database. + # - critical Critical conditions, e.g., hard device errors. + # - error Errors. + # - warning Warning messages. + # - notice Conditions that are not error conditions, but + # should possibly be handled specially. + # - info Informational messages. + # - debug Messages that contain information normally of use + # only when debugging a program. +# syslog_severity = info + + # If logging via syslog, the severity can be set here. + # Defaults to info. + + # + # Optional header format string. + # Written to the first line of any newly created log file +# header = "This is a header line" + + # + # The default format string. + format = "This is a log message for %{User-Name}" + + # + # This next line can be omitted. If it is omitted, then + # the log message is static, and is always given by "format", + # above. + # + # If it is defined, then the string is dynamically expanded, + # and the result is used to find another configuration entry + # here, with the given name. That name is then used as the + # format string. + # + # If the configuration entry cannot be found, then no log + # message is printed. + # + # i.e. You can have many log messages in one "linelog" module. + # If this two-step expansion did not exist, you would have + # needed to configure one "linelog" module for each log message. + + # + # Reference the Packet-Type (Access-Accept, etc.) If it doesn't + # exist, reference the "default" entry. + # + # This is for "linelog" being used in the post-auth section + # If you want to use it in "authorize", you need to change + # the reference to "messages.%{%{Packet-Type}:-default}", + # and then add the appropriate messages. + # + reference = "messages.%{%{reply:Packet-Type}:-default}" + + # + # The messages defined here are taken from the "reference" + # expansion, above. + # + messages { + default = "Unknown packet type %{Packet-Type}" + + Access-Accept = "Accepted user: %{User-Name}" + Access-Reject = "Rejected user: %{User-Name}" + Access-Challenge = "Sent challenge: %{User-Name}" + } +} + +# +# Another example, for accounting packets. +# +linelog log_accounting { + # + # Used if the expansion of "reference" fails. + # + format = "" + + filename = ${logdir}/linelog-accounting + + permissions = 0600 + + reference = "Accounting-Request.%{%{Acct-Status-Type}:-unknown}" + + # + # Another example: + # + # + Accounting-Request { + Start = "Connect: [%{User-Name}] (did %{Called-Station-Id} cli %{Calling-Station-Id} port %{NAS-Port} ip %{Framed-IP-Address})" + Stop = "Disconnect: [%{User-Name}] (did %{Called-Station-Id} cli %{Calling-Station-Id} port %{NAS-Port} ip %{Framed-IP-Address}) %{Acct-Session-Time} seconds" + + # Don't log anything for these packets. + Alive = "" + + Accounting-On = "NAS %{%{Packet-Src-IP-Address}:-%{Packet-Src-IPv6-Address}} (%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}}) just came online" + Accounting-Off = "NAS %{%{Packet-Src-IP-Address}:-%{Packet-Src-IPv6-Address}} (%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}}) just went offline" + + # don't log anything for other Acct-Status-Types. + unknown = "NAS %{%{Packet-Src-IP-Address}:-%{Packet-Src-IPv6-Address}} (%{%{NAS-IP-Address}:-%{NAS-IPv6-Address}}) sent unknown Acct-Status-Type %{Acct-Status-Type}" + } +} diff --git a/raddb/mods-available/logintime b/raddb/mods-available/logintime new file mode 100644 index 0000000..2534452 --- /dev/null +++ b/raddb/mods-available/logintime @@ -0,0 +1,23 @@ +# -*- text -*- +# +# $Id$ + +# The logintime module. This handles the Login-Time, +# Current-Time, and Time-Of-Day attributes. It should be +# included in the *end* of the authorize section in order to +# handle Login-Time checks. It should also be included in the +# instantiate section in order to register the Current-Time +# and Time-Of-Day comparison functions. +# +# When the Login-Time attribute is set to some value, and the +# user has been permitted to log in, a Session-Timeout is +# calculated based on the remaining time. See "doc/README". +# +logintime { + # The minimum timeout (in seconds) a user is allowed + # to have. If the calculated timeout is lower we don't + # allow the login. Some NAS do not handle values + # lower than 60 seconds well. + minimum_timeout = 60 +} + diff --git a/raddb/mods-available/mac2ip b/raddb/mods-available/mac2ip new file mode 100644 index 0000000..a4ead1d --- /dev/null +++ b/raddb/mods-available/mac2ip @@ -0,0 +1,25 @@ +# -*- text -*- +# +# $Id$ + +###################################################################### +# +# This next section is a sample configuration for the "passwd" +# module, that reads flat-text files. +# +# The file is in the format <mac>,<ip> +# +# 00:01:02:03:04:05,192.0.2.100 +# 01:01:02:03:04:05,192.0.2.101 +# 02:01:02:03:04:05,192.0.2.102 +# +# This lets you perform simple static IP assignments from a flat-text +# file. You will have to define lease times yourself. +# +###################################################################### + +passwd mac2ip { + filename = ${modconfdir}/${.:name}/${.:instance} + format = "*DHCP-Client-Hardware-Address:=DHCP-Your-IP-Address" + delimiter = "," +} diff --git a/raddb/mods-available/mac2vlan b/raddb/mods-available/mac2vlan new file mode 100644 index 0000000..a1db803 --- /dev/null +++ b/raddb/mods-available/mac2vlan @@ -0,0 +1,18 @@ +# -*- text -*- +# +# $Id$ + +# A simple file to map a MAC address to a VLAN. +# +# The file should be in the format MAC,VLAN +# the VLAN name cannot have spaces in it, for example: +# +# 00:01:02:03:04:05,VLAN1 +# 03:04:05:06:07:08,VLAN2 +# ... +# +passwd mac2vlan { + filename = ${modconfdir}/${.:name}/${.:instance} + format = "*VMPS-Mac:=VMPS-VLAN-Name" + delimiter = "," +} diff --git a/raddb/mods-available/moonshot-targeted-ids b/raddb/mods-available/moonshot-targeted-ids new file mode 100644 index 0000000..1b27b44 --- /dev/null +++ b/raddb/mods-available/moonshot-targeted-ids @@ -0,0 +1,57 @@ +# -*- text -*- +# +# $Id$ + +# +# Write Moonshot-*-TargetedId (MSTID) to the database. +# +# Schema raddb/mods-config/sql/moonshot-targeted-ids/<DB>/schema.sql +# Queries raddb/mods-config/sql/moonshot-targeted-ids/<DB>/queries.conf +# +sql moonshot_tid_sql { + + # The dialect of SQL you want to use, this should usually match + # the driver below. + # + # If you're using rlm_sql_null, then it should be the type of + # database the logged queries are going to be executed against. + dialect = "sqlite" + + # The sub-module to use to execute queries. This should match + # the database you're attempting to connect to. + # + # There are MSTID queries available for: + # * rlm_sql_mysql + # * rlm_sql_postgresql + # * rlm_sql_sqlite + # * rlm_sql_null (log queries to disk) + # + driver = "rlm_sql_${dialect}" + + sqlite { + filename = ${radacctdir}/moonshot-targeted-ids.sqlite + bootstrap = ${modconfdir}/${..:name}/moonshot-targeted-ids/sqlite/schema.sql + } + + # Write MSTID queries to a logfile. Useful for debugging. +# logfile = ${logdir}/moonshot-targeted-id-log.sql + + pool { + start = 5 + min = 4 + max = 10 + spare = 3 + uses = 0 + lifetime = 0 + idle_timeout = 60 + } + + # If you adjust the table name here, you must also modify the table name in + # the moonshot_get_targeted_id.post-auth policy in policy.d/moonshot-targeted-ids + # and the schema.sql files in the mods-config/sql/moonshot-targeted-ids tree. + # + moonshot_tid_table = "moonshot_targeted_ids" + sql_user_name = "%{User-Name}" + + $INCLUDE ${modconfdir}/${.:name}/moonshot-targeted-ids/${dialect}/queries.conf +} diff --git a/raddb/mods-available/mschap b/raddb/mods-available/mschap new file mode 100644 index 0000000..1748d57 --- /dev/null +++ b/raddb/mods-available/mschap @@ -0,0 +1,253 @@ +# -*- text -*- +# +# $Id$ + +# +# Microsoft CHAP authentication +# +# This module supports MS-CHAP and MS-CHAPv2 authentication. +# It also enforces the SMB-Account-Ctrl attribute. +# +mschap { + # + # If you are using /etc/smbpasswd, see the 'passwd' + # module for an example of how to use /etc/smbpasswd + # + + # + # If use_mppe is not set to no mschap, will + # add MS-CHAP-MPPE-Keys for MS-CHAPv1 and + # MS-MPPE-Recv-Key/MS-MPPE-Send-Key for MS-CHAPv2 + # +# use_mppe = no + + # + # If MPPE is enabled, require_encryption makes + # encryption moderate + # +# require_encryption = yes + + # + # require_strong always requires 128 bit key + # encryption + # +# require_strong = yes + + # + # This module can perform authentication itself, OR + # use a Windows Domain Controller. This configuration + # directive tells the module to call the ntlm_auth + # program, which will do the authentication, and return + # the NT-Key. Note that you MUST have "winbindd" and + # "nmbd" running on the local machine for ntlm_auth + # to work. See the ntlm_auth program documentation + # for details. + # + # If ntlm_auth is configured below, then the mschap + # module will call ntlm_auth for every MS-CHAP + # authentication request. If there is a cleartext + # or NT hashed password available, you can set + # "MS-CHAP-Use-NTLM-Auth := No" in the control items, + # and the mschap module will do the authentication itself, + # without calling ntlm_auth. + # + # Be VERY careful when editing the following line! + # + # You can also try setting the user name as: + # + # ... --username=%{mschap:User-Name} ... + # + # In that case, the mschap module will look at the User-Name + # attribute, and do prefix/suffix checks in order to obtain + # the "best" user name for the request. + # + # For Samba 4, you should also set the "ntlm auth" parameter + # in the Samba configuration: + # + # ntlm auth = yes + # + # or + # + # ntlm auth = mschapv2-and-ntlmv2-only + # + # This will let Samba 4 accept the MS-CHAP authentication + # method that is needed by FreeRADIUS. + # + # Depending on the Samba version, you may also need to add: + # + # --allow-mschapv2 + # + # to the command-line parameters. + # +# ntlm_auth = "/path/to/ntlm_auth --request-nt-key --allow-mschapv2 --username=%{%{Stripped-User-Name}:-%{%{User-Name}:-None}} --challenge=%{%{mschap:Challenge}:-00} --nt-response=%{%{mschap:NT-Response}:-00}" + + # + # The default is to wait 10 seconds for ntlm_auth to + # complete. This is a long time, and if it's taking that + # long then you likely have other problems in your domain. + # The length of time can be decreased with the following + # option, which can save clients waiting if your ntlm_auth + # usually finishes quicker. Range 1 to 10 seconds. + # +# ntlm_auth_timeout = 10 + + # + # An alternative to using ntlm_auth is to connect to the + # winbind daemon directly for authentication. This option + # is likely to be faster and may be useful on busy systems, + # but is less well tested. + # + # Using this option requires libwbclient from Samba 4.2.1 + # or later to be installed. Make sure that ntlm_auth above is + # commented out. + # +# winbind_username = "%{mschap:User-Name}" +# winbind_domain = "%{mschap:NT-Domain}" + + # + # When using single sign-on with a winbind connection and the + # client uses a different casing for the username than the + # casing is according to the backend, reauth may fail because + # of some Windows internals. This switch tries to find the + # user in the correct casing in the backend, and retry + # authentication with that username. + # +# winbind_retry_with_normalised_username = no + + # + # Information for the winbind connection pool. The configuration + # items below are the same for all modules which use the new + # connection pool. + # + pool { + # + # Connections to create during module instantiation. + # If the server cannot create specified number of + # connections during instantiation it will exit. + # Set to 0 to allow the server to start without the + # winbind daemon being available. + # + start = ${thread[pool].start_servers} + + # + # Minimum number of connections to keep open + # + min = ${thread[pool].min_spare_servers} + + # + # Maximum number of connections + # + # If these connections are all in use and a new one + # is requested, the request will NOT get a connection. + # + # Setting 'max' to LESS than the number of threads means + # that some threads may starve, and you will see errors + # like 'No connections available and at max connection limit' + # + # Setting 'max' to MORE than the number of threads means + # that there are more connections than necessary. + # + max = ${thread[pool].max_servers} + + # + # Spare connections to be left idle + # + # NOTE: Idle connections WILL be closed if "idle_timeout" + # is set. This should be less than or equal to "max" above. + # + spare = ${thread[pool].max_spare_servers} + + # + # Number of uses before the connection is closed + # + # 0 means "infinite" + # + uses = 0 + + # + # The number of seconds to wait after the server tries + # to open a connection, and fails. During this time, + # no new connections will be opened. + # + retry_delay = 30 + + # + # The lifetime (in seconds) of the connection + # + # NOTE: A setting of 0 means infinite (no limit). + # + lifetime = 86400 + + # + # The pool is checked for free connections every + # "cleanup_interval". If there are free connections, + # then one of them is closed. + # + cleanup_interval = 300 + + # + # The idle timeout (in seconds). A connection which is + # unused for this length of time will be closed. + # + # NOTE: A setting of 0 means infinite (no timeout). + # + idle_timeout = 600 + + # + # NOTE: All configuration settings are enforced. If a + # connection is closed because of "idle_timeout", + # "uses", or "lifetime", then the total number of + # connections MAY fall below "min". When that + # happens, it will open a new connection. It will + # also log a WARNING message. + # + # The solution is to either lower the "min" connections, + # or increase lifetime/idle_timeout. + # + } + + passchange { + # + # This support MS-CHAPv2 (not v1) password change + # requests. See doc/mschap.rst for more IMPORTANT + # information. + # + # Samba/ntlm_auth - if you are using ntlm_auth to + # validate passwords, you will need to use ntlm_auth + # to change passwords. Uncomment the three lines + # below, and change the path to ntlm_auth. + # +# ntlm_auth = "/usr/bin/ntlm_auth --helper-protocol=ntlm-change-password-1" +# ntlm_auth_username = "username: %{mschap:User-Name}" +# ntlm_auth_domain = "nt-domain: %{mschap:NT-Domain}" + + # + # To implement a local password change, you need to + # supply a string which is then expanded, so that the + # password can be placed somewhere. e.g. passed to a + # script (exec), or written to SQL (UPDATE/INSERT). + # We give both examples here, but only one will be + # used. + # +# local_cpw = "%{exec:/path/to/script %{mschap:User-Name} %{MS-CHAP-New-Cleartext-Password}}" + # +# local_cpw = "%{sql:UPDATE radcheck set value='%{MS-CHAP-New-NT-Password}' where username='%{SQL-User-Name}' and attribute='NT-Password'}" + } + + # + # For Apple Server, when running on the same machine as + # Open Directory. It has no effect on other systems. + # +# use_open_directory = yes + + # + # On failure, set (or not) the MS-CHAP error code saying + # "retries allowed". + # +# allow_retry = yes + + # + # An optional retry message. + # +# retry_msg = "Re-enter (or reset) the password" +} diff --git a/raddb/mods-available/ntlm_auth b/raddb/mods-available/ntlm_auth new file mode 100644 index 0000000..ab0017c --- /dev/null +++ b/raddb/mods-available/ntlm_auth @@ -0,0 +1,18 @@ +# +# For testing ntlm_auth authentication with PAP. +# +# If you have problems with authentication failing, even when the +# password is good, it may be a bug in Samba: +# +# https://bugzilla.samba.org/show_bug.cgi?id=6563 +# +# Depending on the AD / Samba configuration, you may also need to add: +# +# --allow-mschapv2 +# +# to the list of command-line options. +# +exec ntlm_auth { + wait = yes + program = "/path/to/ntlm_auth --request-nt-key --domain=MYDOMAIN --username=%{mschap:User-Name} --password=%{User-Password}" +} diff --git a/raddb/mods-available/opendirectory b/raddb/mods-available/opendirectory new file mode 100644 index 0000000..443d74d --- /dev/null +++ b/raddb/mods-available/opendirectory @@ -0,0 +1,26 @@ +# -*- text -*- +# +# $Id$ + +# This module is only used when the server is running on the same +# system as OpenDirectory. The configuration of the module is hard-coded +# by Apple, and cannot be changed here. +# +# There are no configuration entries for this module. +# +# The MS-CHAP module will automatically talk to OpenDirectory, if the +# server is built on an OSX machine. However, you must also set +# dsAttrTypeNative:apple-enabled-auth-mech attribute in the +# /config/dirserv OpenDirectory record. You will probably also need +# to change the user passwords in order to re-generate the +# appropriate hashes. +# +# Complete OSX configuration information is available on Apple's web site: +# +# https://developer.apple.com/support/macos-server/macOS-Server-Service-Migration-Guide.pdf +# +# See also https://discussions.apple.com/thread/6053980?tstart=0 +# +opendirectory { + +} diff --git a/raddb/mods-available/pam b/raddb/mods-available/pam new file mode 100644 index 0000000..f4a91a9 --- /dev/null +++ b/raddb/mods-available/pam @@ -0,0 +1,26 @@ +# -*- text -*- +# +# $Id$ + + +# Pluggable Authentication Modules +# +# For Linux, see: +# http://www.kernel.org/pub/linux/libs/pam/index.html +# +# WARNING: On many systems, the system PAM libraries have +# memory leaks! We STRONGLY SUGGEST that you do not +# use PAM for authentication, due to those memory leaks. +# +pam { + # + # The name to use for PAM authentication. + # PAM looks in /etc/pam.d/${pam_auth_name} + # for it's configuration. See 'redhat/radiusd-pam' + # for a sample PAM configuration file. + # + # Note that any Pam-Auth attribute set in the 'authorize' + # section will over-ride this one. + # + pam_auth = radiusd +} diff --git a/raddb/mods-available/pap b/raddb/mods-available/pap new file mode 100644 index 0000000..0038ecd --- /dev/null +++ b/raddb/mods-available/pap @@ -0,0 +1,18 @@ +# -*- text -*- +# +# $Id$ + +# PAP module to authenticate users based on their stored password +# +# Supports multiple encryption/hash schemes. See "man rlm_pap" +# for details. +# +# For instructions on creating the various types of passwords, see: +# +# http://www.openldap.org/faq/data/cache/347.html +pap { + # By default the server will use heuristics to try and automatically + # handle base64 or hex encoded passwords. This behaviour can be + # stopped by setting the following to "no". +# normalise = yes +} diff --git a/raddb/mods-available/passwd b/raddb/mods-available/passwd new file mode 100644 index 0000000..11bd224 --- /dev/null +++ b/raddb/mods-available/passwd @@ -0,0 +1,55 @@ +# -*- text -*- +# +# $Id$ + +# passwd module allows to do authorization via any passwd-like +# file and to extract any attributes from these files. +# +# See the "smbpasswd" and "etc_group" files for more examples. +# +# parameters are: +# filename - path to file +# +# format - format for filename record. This parameters +# correlates record in the passwd file and RADIUS +# attributes. +# +# Field marked as '*' is a key field. That is, the parameter +# with this name from the request is used to search for +# the record from passwd file +# +# Attributes marked as '=' are added to reply_items instead +# of default configure_items +# +# Attributes marked as '~' are added to request_items +# +# Field marked as ',' may contain a comma separated list +# of attributes. +# +# hash_size - hashtable size. Setting it to 0 is no longer permitted +# A future version of the server will have the module +# automatically determine the hash size. Having it set +# manually should not be necessary. +# +# allow_multiple_keys - if many records for a key are allowed +# +# ignore_nislike - ignore NIS-related records +# +# delimiter - symbol to use as a field separator in passwd file, +# for format ':' symbol is always used. '\0', '\n' are +# not allowed +# + +# An example configuration for using /etc/passwd. +# +# This is an example which will NOT WORK if you have shadow passwords, +# NIS, etc. The "unix" module is normally responsible for reading +# system passwords. You should use it instead of this example. +# +passwd etc_passwd { + filename = /etc/passwd + format = "*User-Name:Crypt-Password:" + hash_size = 100 + ignore_nislike = no + allow_multiple_keys = no +} diff --git a/raddb/mods-available/perl b/raddb/mods-available/perl new file mode 100644 index 0000000..99215b8 --- /dev/null +++ b/raddb/mods-available/perl @@ -0,0 +1,94 @@ +# -*- text -*- +# +# $Id$ + +# Persistent, embedded Perl interpreter. +# +perl { + # + # The Perl script to execute on authorize, authenticate, + # accounting, xlat, etc. This is very similar to using + # 'rlm_exec' module, but it is persistent, and therefore + # faster. + # + filename = ${modconfdir}/${.:instance}/example.pl + + # + # Options which are passed to the Perl interpreter. + # These are (mostly) the same options as are passed + # to the "perl" command line. + # + # The most useful flag is "-T". This sets tainting on, which + # makes it impossible to leverage bad User-Names into local + # command execution. + # + perl_flags = "-T" + + # + # The following hashes are given to the module and + # filled with value-pairs (Attribute names and values) + # + # %RAD_CHECK Check items + # %RAD_REQUEST Attributes from the request + # %RAD_REPLY Attributes for the reply + # %RAD_REQUEST_PROXY Attributes from the proxied request + # %RAD_REQUEST_PROXY_REPLY Attributes from the proxy reply + # + # The interface between FreeRADIUS and Perl is strings. + # That is, attributes of type "octets" are converted to + # printable strings, such as "0xabcdef". If you want to + # access the binary values of the attributes, you should + # call the Perl "pack" function. Then to send any binary + # data back to FreeRADIUS, call the Perl "unpack" function, + # so that the contents of the hashes are printable strings. + # + # IP addresses are sent as strings, e.g. "192.0.2.25", and + # not as a 4-byte binary value. The same applies to other + # attribute data types. + # + # Attributes of type "string" are copied to Perl as-is. + # They are not escaped or interpreted. + # + # The return codes from functions in the perl_script + # are passed directly back to the server. These + # codes are defined in mods-config/example.pl + # + + # You can define configuration items (and nested sub-sections) in perl "config" section. + # These items will be accessible in the perl script through %RAD_PERLCONF hash. + # For instance: $RAD_PERLCONF{'name'} $RAD_PERLCONF{'sub-config'}->{'name'} + # + #config { + # name = "value" + # sub-config { + # name = "value of name from config.sub-config" + # } + #} + + # + # List of functions in the module to call. + # Uncomment and change if you want to use function + # names other than the defaults. + # + #func_authenticate = authenticate + #func_authorize = authorize + #func_preacct = preacct + #func_accounting = accounting + #func_checksimul = checksimul + #func_pre_proxy = pre_proxy + #func_post_proxy = post_proxy + #func_post_auth = post_auth + #func_recv_coa = recv_coa + #func_send_coa = send_coa + #func_xlat = xlat + #func_detach = detach + + # + # Uncomment the following lines if you wish + # to use separate functions for Start and Stop + # accounting packets. In that case, the + # func_accounting function is not called. + # + #func_start_accounting = accounting_start + #func_stop_accounting = accounting_stop +} diff --git a/raddb/mods-available/preprocess b/raddb/mods-available/preprocess new file mode 100644 index 0000000..8baec79 --- /dev/null +++ b/raddb/mods-available/preprocess @@ -0,0 +1,62 @@ +# -*- text -*- +# +# $Id$ + +# Preprocess the incoming RADIUS request, before handing it off +# to other modules. +# +# This module processes the 'huntgroups' and 'hints' files. +# In addition, it re-writes some weird attributes created +# by some NAS, and converts the attributes into a form which +# is a little more standard. +# +preprocess { + # Search for files in a subdirectory of mods-config which + # matches this instance of the preprocess module. + moddir = ${modconfdir}/${.:instance} + + huntgroups = ${moddir}/huntgroups + hints = ${moddir}/hints + + # This hack changes Ascend's weird port numbering + # to standard 0-??? port numbers so that the "+" works + # for IP address assignments. + with_ascend_hack = no + ascend_channels_per_line = 23 + + # Windows NT machines often authenticate themselves as + # NT_DOMAIN\username + # + # If this is set to 'yes', then the NT_DOMAIN portion + # of the user-name is silently discarded. + # + # This configuration entry SHOULD NOT be used. + # See the "realms" module for a better way to handle + # NT domains. + with_ntdomain_hack = no + + # Specialix Jetstream 8500 24 port access server. + # + # If the user name is 10 characters or longer, a "/" + # and the excess characters after the 10th are + # appended to the user name. + # + # If you're not running that NAS, you don't need + # this hack. + with_specialix_jetstream_hack = no + + # Cisco (and Quintum in Cisco mode) sends it's VSA attributes + # with the attribute name *again* in the string, like: + # + # H323-Attribute = "h323-attribute=value". + # + # If this configuration item is set to 'yes', then + # the redundant data in the the attribute text is stripped + # out. The result is: + # + # H323-Attribute = "value" + # + # If you're not running a Cisco or Quintum NAS, you don't + # need this hack. + with_cisco_vsa_hack = no +} diff --git a/raddb/mods-available/python b/raddb/mods-available/python new file mode 100644 index 0000000..371a56d --- /dev/null +++ b/raddb/mods-available/python @@ -0,0 +1,65 @@ +# +# Make sure the PYTHONPATH environmental variable contains the +# directory(s) for the modules listed below. +# +# Uncomment any func_* which are included in your module. If +# rlm_python is called for a section which does not have +# a function defined, it will return NOOP. +# +python { + # Path to the python modules + # + # Note that due to limitations on Python, this configuration + # item is GLOBAL TO THE SERVER. That is, you cannot have two + # instances of the python module, each with a different path. + # +# python_path="${modconfdir}/${.:name}:/path/to/python/files:/another_path/to/python_files/" + + module = example + + # Pass all VPS lists as a 6-tuple to the callbacks + # (request, reply, config, state, proxy_req, proxy_reply) +# pass_all_vps = no + + # Pass all VPS lists as a dictionary to the callbacks + # Keys: "request", "reply", "config", "session-state", "proxy-request", + # "proxy-reply" + # This option prevales over "pass_all_vps" +# pass_all_vps_dict = no + +# mod_instantiate = ${.module} +# func_instantiate = instantiate + +# mod_detach = ${.module} +# func_detach = detach + +# mod_authorize = ${.module} +# func_authorize = authorize + +# mod_authenticate = ${.module} +# func_authenticate = authenticate + +# mod_preacct = ${.module} +# func_preacct = preacct + +# mod_accounting = ${.module} +# func_accounting = accounting + +# mod_checksimul = ${.module} +# func_checksimul = checksimul + +# mod_pre_proxy = ${.module} +# func_pre_proxy = pre_proxy + +# mod_post_proxy = ${.module} +# func_post_proxy = post_proxy + +# mod_post_auth = ${.module} +# func_post_auth = post_auth + +# mod_recv_coa = ${.module} +# func_recv_coa = recv_coa + +# mod_send_coa = ${.module} +# func_send_coa = send_coa +} diff --git a/raddb/mods-available/python3 b/raddb/mods-available/python3 new file mode 100644 index 0000000..f0e0424 --- /dev/null +++ b/raddb/mods-available/python3 @@ -0,0 +1,65 @@ +# +# Make sure the PYTHONPATH environmental variable contains the +# directory(s) for the modules listed below. +# +# Uncomment any func_* which are included in your module. If +# rlm_python is called for a section which does not have +# a function defined, it will return NOOP. +# +python3 { + # Path to the python modules + # + # Note that due to limitations on Python, this configuration + # item is GLOBAL TO THE SERVER. That is, you cannot have two + # instances of the python module, each with a different path. + # +# python_path="${modconfdir}/${.:name}:/another_path/to/python_files" + + module = example + + # Pass all VPS lists as a 6-tuple to the callbacks + # (request, reply, config, state, proxy_req, proxy_reply) +# pass_all_vps = no + + # Pass all VPS lists as a dictionary to the callbacks + # Keys: "request", "reply", "config", "session-state", "proxy-request", + # "proxy-reply" + # This option prevales over "pass_all_vps" +# pass_all_vps_dict = no + +# mod_instantiate = ${.module} +# func_instantiate = instantiate + +# mod_detach = ${.module} +# func_detach = detach + +# mod_authorize = ${.module} +# func_authorize = authorize + +# mod_authenticate = ${.module} +# func_authenticate = authenticate + +# mod_preacct = ${.module} +# func_preacct = preacct + +# mod_accounting = ${.module} +# func_accounting = accounting + +# mod_checksimul = ${.module} +# func_checksimul = checksimul + +# mod_pre_proxy = ${.module} +# func_pre_proxy = pre_proxy + +# mod_post_proxy = ${.module} +# func_post_proxy = post_proxy + +# mod_post_auth = ${.module} +# func_post_auth = post_auth + +# mod_recv_coa = ${.module} +# func_recv_coa = recv_coa + +# mod_send_coa = ${.module} +# func_send_coa = send_coa +} diff --git a/raddb/mods-available/radutmp b/raddb/mods-available/radutmp new file mode 100644 index 0000000..82319c0 --- /dev/null +++ b/raddb/mods-available/radutmp @@ -0,0 +1,53 @@ +# -*- text -*- +# +# $Id$ + +# Write a 'utmp' style file, of which users are currently +# logged in, and where they've logged in from. +# +# This file is used mainly for Simultaneous-Use checking, +# and also 'radwho', to see who's currently logged in. +# +radutmp { + # Where the file is stored. It's not a log file, + # so it doesn't need rotating. + # + filename = ${logdir}/radutmp + + # The field in the packet to key on for the + # 'user' name, If you have other fields which you want + # to use to key on to control Simultaneous-Use, + # then you can use them here. + # + # Note, however, that the size of the field in the + # 'utmp' data structure is small, around 32 + # characters, so that will limit the possible choices + # of keys. + # + # You may want instead: %{%{Stripped-User-Name}:-%{User-Name}} + username = %{User-Name} + + + # Whether or not we want to treat "user" the same + # as "USER", or "User". Some systems have problems + # with case sensitivity, so this should be set to + # 'no' to enable the comparisons of the key attribute + # to be case insensitive. + # + case_sensitive = yes + + # Accounting information may be lost, so the user MAY + # have logged off of the NAS, but we haven't noticed. + # If so, we can verify this information with the NAS, + # + # If we want to believe the 'utmp' file, then this + # configuration entry can be set to 'no'. + # + check_with_nas = yes + + # Set the file permissions, as the contents of this file + # are usually private. + permissions = 0600 + + caller_id = "yes" +} diff --git a/raddb/mods-available/realm b/raddb/mods-available/realm new file mode 100644 index 0000000..947a42d --- /dev/null +++ b/raddb/mods-available/realm @@ -0,0 +1,80 @@ +# -*- text -*- +# +# $Id$ + +# Realm module, for proxying. +# +# You can have multiple instances of the realm module to +# support multiple realm syntaxes at the same time. The +# search order is defined by the order that the modules are listed +# in the authorize and preacct sections. +# +# Four config options: +# format - must be "prefix" or "suffix" +# The special cases of "DEFAULT" +# and "NULL" are allowed, too. +# delimiter - must be a single character + +# +# For dynamic home servers, see doc/configuration/dynamic_home_servers.md, +# and the script in mods-config/realm/freeradius-naptr-to-home-server.sh +# + +# 'realm/username' +# +# Using this entry, IPASS users have their realm set to "IPASS". +realm IPASS { + format = prefix + delimiter = "/" +} + +# 'username@realm' +# +realm suffix { + format = suffix + delimiter = "@" + + # The next configuration items are valid ONLY for a trust-router. + # For all other realms, they are ignored. +# trust_router = "localhost" +# tr_port = 12309 +# rp_realm = "realm.example.com" +# default_community = "apc.communities.example.com" +# # if rekey_enabled is enabled, dynamic realms are automatically rekeyed +# # before they expire to avoid having to recreate them from scrach on +# # demand (implying lengthy authentications) +# rekey_enabled = no +# # if realm_lifetime is > 0, the rekey is scheduled to happen the +# # specified number of seconds after its creation or rekeying. Otherwise, +# # the key material expiration timestamp is used +# realm_lifetime = 0 +} + +# 'realm!username' +# +realm bangpath { + format = prefix + delimiter = "!" + +# trust_router = "localhost" +# tr_port = 12309 +# rp_realm = "realm.example.com" +# default_community = "apc.communities.example.com" +# rekey_enabled = no +# realm_lifetime = 0 +} + +# 'username%realm' +# +realm realmpercent { + format = suffix + delimiter = "%" +} + +# +# 'domain\user' +# +realm ntdomain { + format = prefix + delimiter = "\\" +} diff --git a/raddb/mods-available/redis b/raddb/mods-available/redis new file mode 100644 index 0000000..64789f5 --- /dev/null +++ b/raddb/mods-available/redis @@ -0,0 +1,99 @@ +# -*- text -*- +# +# $Id$ + +# +# Configuration file for the "redis" module. This module does nothing +# Other than provide connections to a redis database, and a %{redis: ...} +# expansion. +# +redis { + # Host where the redis server is located. + # We recommend using ONLY 127.0.0.1 ! + server = 127.0.0.1 + + # Select the Redis logical database having the specified zero-based numeric index. +# database = 0 + + # The default port. + port = 6379 + + # The password used to authenticate to the server. + # We recommend using a strong password. +# password = thisisreallysecretandhardtoguess + + # Set connection and query timeout for rlm_redis + query_timeout = 5 + + # + # Information for the connection pool. The configuration items + # below are the same for all modules which use the new + # connection pool. + # + pool { + # Connections to create during module instantiation. + # If the server cannot create specified number of + # connections during instantiation it will exit. + # Set to 0 to allow the server to start without the + # web service being available. + start = ${thread[pool].start_servers} + + # Minimum number of connections to keep open + min = ${thread[pool].min_spare_servers} + + # Maximum number of connections + # + # If these connections are all in use and a new one + # is requested, the request will NOT get a connection. + # + # Setting 'max' to LESS than the number of threads means + # that some threads may starve, and you will see errors + # like 'No connections available and at max connection limit' + # + # Setting 'max' to MORE than the number of threads means + # that there are more connections than necessary. + max = ${thread[pool].max_servers} + + # Spare connections to be left idle + # + # NOTE: Idle connections WILL be closed if "idle_timeout" + # is set. This should be less than or equal to "max" above. + spare = ${thread[pool].max_spare_servers} + + # Number of uses before the connection is closed + # + # 0 means "infinite" + uses = 0 + + # The number of seconds to wait after the server tries + # to open a connection, and fails. During this time, + # no new connections will be opened. + retry_delay = 30 + + # The lifetime (in seconds) of the connection + # + # NOTE: A setting of 0 means infinite (no limit). + lifetime = 86400 + + # The pool is checked for free connections every + # "cleanup_interval". If there are free connections, + # then one of them is closed. + cleanup_interval = 300 + + # The idle timeout (in seconds). A connection which is + # unused for this length of time will be closed. + # + # NOTE: A setting of 0 means infinite (no timeout). + idle_timeout = 600 + + # NOTE: All configuration settings are enforced. If a + # connection is closed because of "idle_timeout", + # "uses", or "lifetime", then the total number of + # connections MAY fall below "min". When that + # happens, it will open a new connection. It will + # also log a WARNING message. + # + # The solution is to either lower the "min" connections, + # or increase lifetime/idle_timeout. + } +} diff --git a/raddb/mods-available/rediswho b/raddb/mods-available/rediswho new file mode 100644 index 0000000..d303550 --- /dev/null +++ b/raddb/mods-available/rediswho @@ -0,0 +1,52 @@ +# -*- text -*- +# +# $Id$ + +# +# Configuration file for the "rediswho" module. +# +# This module tracks the last set of login sessions for a user. +# +rediswho { + # REDIS instance to use (from mods-available/redis) + # + # If you have multiple redis instances, such as "redis redis1 {...}", + # use the *instance* name here: redis1. +# redis_module_instance = redis + + # How many sessions to keep track of per user. + # If there are more than this number, older sessions are deleted. + trim_count = 15 + + # Expiry time in seconds. Any sessions which have not received + # an update in this time will be automatically expired. + expire_time = 86400 + + # + # Each subsection contains insert / trim / expire queries. + # The subsections are named after the contents of the + # Acct-Status-Type attribute. See dictionary.rfc2866 for names + # of the various Acct-Status-Type values, or look at the output + # of debug mode. + # + # This module supports *any* Acct-Status-Type. Just add a subsection + # of the appropriate name, along with insert / trim / expire queries. + # + Start { + insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{NAS-IP-Address},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}" + trim = "LTRIM %{User-Name} 0 ${..trim_count}" + expire = "EXPIRE %{User-Name} ${..expire_time}" + } + + Interim-Update { + insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{NAS-IP-Address},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}" + trim = "LTRIM %{User-Name} 0 ${..trim_count}" + expire = "EXPIRE %{User-Name} ${..expire_time}" + } + + Stop { + insert = "LPUSH %{User-Name} %l,%{Acct-Session-Id},%{NAS-IP-Address},%{Acct-Session-Time},%{Framed-IP-Address},%{%{Acct-Input-Gigawords}:-0},%{%{Acct-Output-Gigawords}:-0},%{%{Acct-Input-Octets}:-0},%{%{Acct-Output-Octets}:-0}" + trim = "LTRIM %{User-Name} 0 ${..trim_count}" + expire = "EXPIRE %{User-Name} ${..expire_time}" + } +} diff --git a/raddb/mods-available/replicate b/raddb/mods-available/replicate new file mode 100644 index 0000000..3ba88c1 --- /dev/null +++ b/raddb/mods-available/replicate @@ -0,0 +1,42 @@ +# Replicate packet(s) to a home server. +# +# This module will open a new socket for each packet, and "clone" +# the incoming packet to the destination realm (i.e. home server). +# These packets are only sent to UDP home servers. TCP and TLS +# are not supported. +# +# Use it by setting "Replicate-To-Realm = name" in the control list, +# just like Proxy-To-Realm. The configurations for the two attributes +# are identical. The realm must exist, the home_server_pool must exist, +# and the home_server must exist. +# +# The only difference is that the "replicate" module sends requests +# and does not expect a reply. Any reply is ignored. +# +# Both Replicate-To-Realm and Proxy-To-Realm can be used at the same time. +# +# To use this module, list "replicate" in the "authorize" or +# "accounting" section. Then, ensure that Replicate-To-Realm is set. +# The contents of the "packet" attribute list will be sent to the +# home server. The usual load-balancing, etc. features of the home +# server will be used. +# +# "radmin" can be used to mark home servers alive/dead, in order to +# enable/disable replication to specific servers. +# +# Packets can be replicated to multiple destinations. Just set +# Replicate-To-Realm multiple times. One packet will be sent for +# each of the Replicate-To-Realm attribute in the "control" list. +# +# If no packets are sent, the module returns "noop". If at least one +# packet is sent, the module returns "ok". If an error occurs, the +# module returns "fail" +# +# Note that replication does NOT change any of the packet statistics. +# If you use "radmin" to look at the statistics for a home server, +# the replicated packets will cause NO counters to increment. This +# is not a bug, this is how replication works. +# +replicate { + +} diff --git a/raddb/mods-available/rest b/raddb/mods-available/rest new file mode 100644 index 0000000..2c33acb --- /dev/null +++ b/raddb/mods-available/rest @@ -0,0 +1,301 @@ +rest { + # + # This subsection configures the tls related items + # that control how FreeRADIUS connects to a HTTPS + # server. + # + tls { + # Certificate Authorities: + # "ca_file" (libcurl option CURLOPT_ISSUERCERT). + # File containing a single CA, which is the issuer of the server + # certificate. + # "ca_info_file" (libcurl option CURLOPT_CAINFO). + # File containing a bundle of certificates, which allow to handle + # certificate chain validation. + # "ca_path" (libcurl option CURLOPT_CAPATH). + # Directory holding CA certificates to verify the peer with. +# ca_file = ${certdir}/cacert.pem +# ca_info_file = ${certdir}/cacert_bundle.pem +# ca_path = ${certdir} + +# certificate_file = /path/to/radius.crt +# private_key_file = /path/to/radius.key +# private_key_password = "supersecret" +# random_file = /dev/urandom + + # Server certificate verification requirements. Can be: + # "no" (don't even bother trying) + # "yes" (verify the cert was issued by one of the + # trusted CAs) + # + # The default is "yes" +# check_cert = yes + + # Server certificate CN verification requirements. Can be: + # "no" (don't even bother trying) + # "yes" (verify the CN in the certificate matches the host + # in the URI) + # + # The default is "yes" +# check_cert_cn = yes + } + + # rlm_rest will open a connection to the server specified in connect_uri + # to populate the connection cache, ready for the first request. + # The server will not start if the server specified is unreachable. + # + # If you wish to disable this pre-caching and reachability check, + # comment out the configuration item below. + connect_uri = "http://127.0.0.1/" + + # + # How long before new connection attempts timeout, defaults to 4.0 seconds. + # +# connect_timeout = 4.0 + + # + # Specify HTTP protocol version to use. one of '1.0', '1.1', '2.0', '2.0+auto', + # '2.0+tls' or 'default'. (libcurl option CURLOPT_HTTP_VERSION) + # +# http_negotiation = 1.1 + + # + # The following config items can be used in each of the sections. + # The sections themselves reflect the sections in the server. + # For example if you list rest in the authorize section of a virtual server, + # the settings from the authorize section here will be used. + # + # The following config items may be listed in any of the sections: + # uri - to send the request to. + # method - HTTP method to use, one of 'get', 'post', 'put', 'patch', + # 'delete' or any custom HTTP method. + # body - The format of the HTTP body sent to the remote server. + # May be 'none', 'post' or 'json', defaults to 'none'. + # attr_num - If true, the attribute number is supplied for each attribute. + # Defaults to false. + # raw_value - If true, enumerated attribute values are provided as numeric + # values. Defaults to false. + # data - Send custom freeform data in the HTTP body. Content-type + # may be specified with 'body'. Will be expanded. + # Values from expansion will not be escaped, this should be + # done using the appropriate xlat method e.g. %{urlencode:<attr>}. + # force_to - Force the response to be decoded with this decoder. + # May be 'plain' (creates reply:REST-HTTP-Body), 'post' + # or 'json'. + # tls - TLS settings for HTTPS. + # auth - HTTP auth method to use, one of 'none', 'srp', 'basic', + # 'digest', 'digest-ie', 'gss-negotiate', 'ntlm', + # 'ntlm-winbind', 'any', 'safe'. defaults to 'none'. + # username - User to authenticate as, will be expanded. + # password - Password to use for authentication, will be expanded. + # require_auth - Require HTTP authentication. + # timeout - HTTP request timeout in seconds, defaults to 4.0. + # chunk - Chunk size to use. If set, HTTP chunked encoding is used to + # send data to the REST server. Make sure that this is large + # enough to fit your largest attribute value's text + # representation. + # A number like 8192 is good. + # + # Additional HTTP headers may be specified with control:REST-HTTP-Header. + # The values of those attributes should be in the format: + # + # control:REST-HTTP-Header := "<HTTP attribute>: <value>" + # + # The control:REST-HTTP-Header attributes will be consumed + # (i.e. deleted) after each call to the rest module, and each + # %{rest:} expansion. This is so that headers from one REST + # call do not affect headers from a different REST call. + # + # Body encodings are the same for requests and responses + # + # POST - All attributes and values are urlencoded + # [outer.][<list>:]<attribute0>=<value0>&[outer.][<list>:]<attributeN>=<valueN> + # + # JSON - All attributes and values are escaped according to the JSON specification + # - attribute Name of the attribute. + # - attr_num Number of the attribute. Only available if the configuration item + # 'attr_num' is enabled. + # - type Type of the attribute (e.g. "integer", "string", "ipaddr", "octets", ...). + # - value Attribute value, for enumerated attributes the human readable value is + # provided and not the numeric value (Depends on the 'raw_value' config item). + # { + # "<attribute0>":{ + # "attr_num":<attr_num0>, + # "type":"<type0>", + # "value":[<value0>,<value1>,<valueN>] + # }, + # "<attribute1>":{ + # "attr_num":<attr_num1>, + # "type":"<type1>", + # "value":[...] + # }, + # "<attributeN>":{ + # "attr_num":<attr_numN>, + # "type":"<typeN>", + # "value":[...] + # }, + # } + # + # The response format adds three optional fields: + # - do_xlat If true, any values will be xlat expanded. Defaults to true. + # - is_json If true, any nested JSON data will be copied to the attribute + # in string form. Defaults to true. + # - op Controls how the attribute is inserted into the target list. + # Defaults to ':='. To create multiple attributes from multiple + # values, this should be set to '+=', otherwise only the last + # value will be used, and it will be assigned to a single + # attribute. + # { + # "<attribute0>":{ + # "is_json":<bool>, + # "do_xlat":<bool>, + # "op":"<operator>", + # "value":[<value0>,<value1>,<valueN>] + # }, + # "<attribute1>":"value", + # "<attributeN>":{ + # "value":[<value0>,<value1>,<valueN>], + # "op":"+=" + # } + # } + + # + # Module return codes are determined by HTTP response codes. These vary depending on the + # section. + # + # If the body is processed and found to be malformed or unsupported fail will be returned. + # If the body is processed and found to contain attribute updated will be returned, + # except in the case of a 401 code. + # + + # Authorize/Authenticate + # + # Code Meaning Process body Module code + # 404 not found no notfound + # 410 gone no notfound + # 403 forbidden no userlock + # 401 unauthorized yes reject + # 204 no content no ok + # 2xx successful yes ok/updated + # 5xx server error no fail + # xxx - no invalid + # + # The status code is held in %{reply:REST-HTTP-Status-Code}. + # + authorize { + uri = "${..connect_uri}/user/%{User-Name}/mac/%{Called-Station-ID}?action=authorize" + method = 'get' + tls = ${..tls} + } + authenticate { + uri = "${..connect_uri}/user/%{User-Name}/mac/%{Called-Station-ID}?action=authenticate" + method = 'get' + tls = ${..tls} + } + + # Preacct/Accounting/Post-auth/Pre-Proxy/Post-Proxy + # + # Code Meaning Process body Module code + # 204 no content no ok + # 2xx successful yes ok/updated + # 5xx server error no fail + # xxx - no invalid + preacct { + uri = "${..connect_uri}/user/%{User-Name}/sessions/%{Acct-Unique-Session-ID}?action=preacct" + method = 'post' + tls = ${..tls} + } + accounting { + uri = "${..connect_uri}/user/%{User-Name}/sessions/%{Acct-Unique-Session-ID}?action=accounting" + method = 'post' + tls = ${..tls} + } + post-auth { + uri = "${..connect_uri}/user/%{User-Name}/mac/%{Called-Station-ID}?action=post-auth" + method = 'post' + tls = ${..tls} + } + pre-proxy { + uri = "${..connect_uri}/user/%{User-Name}/mac/%{Called-Station-ID}?action=pre-proxy" + method = 'post' + tls = ${..tls} + } + post-proxy { + uri = "${..connect_uri}/user/%{User-Name}/mac/%{Called-Station-ID}?action=post-proxy" + method = 'post' + tls = ${..tls} + } + + # Options for calling rest xlats + # uri and method will be derived from the string provided to the xlat + xlat { + # + # The whole string passed to a REST xlat is URI encoded. + # With body_uri_encode = yes, any body data will remain encoded. + # With body_uri_encode = no, the body data will be decoded and sent as provided. + # + body_uri_encode = yes + tls = ${..tls} + } + + # + # The connection pool is used to pool outgoing connections. + # + pool { + # Connections to create during module instantiation. + # If the server cannot create specified number of + # connections during instantiation it will exit. + # Set to 0 to allow the server to start without the + # web service being available. + start = ${thread[pool].start_servers} + + # Minimum number of connections to keep open + min = ${thread[pool].min_spare_servers} + + # Maximum number of connections + # + # If these connections are all in use and a new one + # is requested, the request will NOT get a connection. + # + # Setting 'max' to LESS than the number of threads means + # that some threads may starve, and you will see errors + # like 'No connections available and at max connection limit' + # + # Setting 'max' to MORE than the number of threads means + # that there are more connections than necessary. + max = ${thread[pool].max_servers} + + # Spare connections to be left idle + # + # NOTE: Idle connections WILL be closed if "idle_timeout" + # is set. This should be less than or equal to "max" above. + spare = ${thread[pool].max_spare_servers} + + # Number of uses before the connection is closed + # + # 0 means "infinite" + uses = 0 + + # The number of seconds to wait after the server tries + # to open a connection, and fails. During this time, + # no new connections will be opened. + retry_delay = 30 + + # The lifetime (in seconds) of the connection + lifetime = 0 + + # idle timeout (in seconds). A connection which is + # unused for this length of time will be closed. + idle_timeout = 60 + + # NOTE: All configuration settings are enforced. If a + # connection is closed because of "idle_timeout", + # "uses", or "lifetime", then the total number of + # connections MAY fall below "min". When that + # happens, it will open a new connection. It will + # also log a WARNING message. + # + # The solution is to either lower the "min" connections, + # or increase lifetime/idle_timeout. + } +} diff --git a/raddb/mods-available/smbpasswd b/raddb/mods-available/smbpasswd new file mode 100644 index 0000000..d5ad2a0 --- /dev/null +++ b/raddb/mods-available/smbpasswd @@ -0,0 +1,16 @@ +# -*- text -*- +# +# $Id$ + +# An example configuration for using /etc/smbpasswd. +# +# See the "passwd" file for documentation on the configuration items +# for this module. +# +passwd smbpasswd { + filename = /etc/smbpasswd + format = "*User-Name::LM-Password:NT-Password:SMB-Account-CTRL-TEXT::" + hash_size = 100 + ignore_nislike = no + allow_multiple_keys = no +} diff --git a/raddb/mods-available/smsotp b/raddb/mods-available/smsotp new file mode 100644 index 0000000..c594a9a --- /dev/null +++ b/raddb/mods-available/smsotp @@ -0,0 +1,94 @@ +# -*- text -*- +# +# $Id$ + +# SMS One-Time Password system +# +# This module extends FreeRADIUS with a socket interface to create and +# validate One-Time-Passwords. The program for that creates the socket +# and interacts with this module is not included here. +# +# The module does not check the User-Password, this should be done with +# the "pap" module. See the example below. +# +# The module must be used in the "authorize" section to set +# Auth-Type properly. The first time through, the module is called +# in the "authenticate" section to authenticate the user password, and +# to send the challenge. The second time through, it authenticates +# the response to the challenge. e.g.: +# +# authorize { +# ... +# smsotp +# ... +# } +# +# authenticate { +# ... +# Auth-Type smsotp { +# pap +# smsotp +# } +# +# Auth-Type smsotp-reply { +# smsotp +# } +# ... +# } +# +smsotp { + # The location of the socket. + socket = "/var/run/smsotp_socket" + + # Defines the challenge message that will be send to the + # NAS. Default is "Enter Mobile PIN" } + challenge_message = "Enter Mobile PIN:" + + # Defines the Auth-Type section that is run for the response to + # the challenge. Default is "smsotp-reply". + challenge_type = "smsotp-reply" + + # Control how many sockets are used to talk to the SMSOTPd + # + pool { + # Number of connections to start + start = 5 + + # Minimum number of connections to keep open + min = 4 + + # Maximum number of connections + # + # If these connections are all in use and a new one + # is requested, the request will NOT get a connection. + max = 10 + + # Spare connections to be left idle + # + # NOTE: Idle connections WILL be closed if "idle_timeout" + # is set. + spare = 3 + + # Number of uses before the connection is closed + # + # 0 means "infinite" + uses = 0 + + # The lifetime (in seconds) of the connection + lifetime = 0 + + # idle timeout (in seconds). A connection which is + # unused for this length of time will be closed. + idle_timeout = 60 + + # NOTE: All configuration settings are enforced. If a + # connection is closed because of "idle_timeout", + # "uses", or "lifetime", then the total number of + # connections MAY fall below "min". When that + # happens, it will open a new connection. It will + # also log a WARNING message. + # + # The solution is to either lower the "min" connections, + # or increase lifetime/idle_timeout. + } +} diff --git a/raddb/mods-available/soh b/raddb/mods-available/soh new file mode 100644 index 0000000..d125ce4 --- /dev/null +++ b/raddb/mods-available/soh @@ -0,0 +1,4 @@ +# SoH module +soh { + dhcp = yes +} diff --git a/raddb/mods-available/sometimes b/raddb/mods-available/sometimes new file mode 100644 index 0000000..3a96622 --- /dev/null +++ b/raddb/mods-available/sometimes @@ -0,0 +1,12 @@ +# -*- text -*- +# +# $Id$ + +# +# The "sometimes" module is here for debugging purposes. Each instance +# randomly returns the configured result, or "noop". +# +# It is based on the "always" module. +sometimes { + rcode = fail +} diff --git a/raddb/mods-available/sql b/raddb/mods-available/sql new file mode 100644 index 0000000..0f435ad --- /dev/null +++ b/raddb/mods-available/sql @@ -0,0 +1,376 @@ +# -*- text -*- +## +## mods-available/sql -- SQL modules +## +## $Id$ + +###################################################################### +# +# Configuration for the SQL module +# +# The database schemas and queries are located in subdirectories: +# +# sql/<DB>/main/schema.sql Schema +# sql/<DB>/main/queries.conf Authorisation and Accounting queries +# +# Where "DB" is mysql, mssql, oracle, or postgresql. +# +# The name used to query SQL is sql_user_name, which is set in the file +# +# raddb/mods-config/sql/main/${dialect}/queries.conf +# +# If you are using realms, that configuration should be changed to use +# the Stripped-User-Name attribute. See the comments around sql_user_name +# for more information. +# + +sql { + # + # The dialect of SQL being used. + # + # Allowed dialects are: + # + # mssql + # mysql + # oracle + # postgresql + # sqlite + # mongo + # + dialect = "sqlite" + + # + # The driver module used to execute the queries. Since we + # don't know which SQL drivers are being used, the default is + # "rlm_sql_null", which just logs the queries to disk via the + # "logfile" directive, below. + # + # In order to talk to a real database, delete the next line, + # and uncomment the one after it. + # + # If the dialect is "mssql", then the driver should be set to + # one of the following values, depending on your system: + # + # rlm_sql_db2 + # rlm_sql_firebird + # rlm_sql_freetds + # rlm_sql_iodbc + # rlm_sql_unixodbc + # + driver = "rlm_sql_null" +# driver = "rlm_sql_${dialect}" + + # + # Driver-specific subsections. They will only be loaded and + # used if "driver" is something other than "rlm_sql_null". + # When a real driver is used, the relevant driver + # configuration section is loaded, and all other driver + # configuration sections are ignored. + # + sqlite { + # Path to the sqlite database + filename = "/tmp/freeradius.db" + + # How long to wait for write locks on the database to be + # released (in ms) before giving up. + busy_timeout = 200 + + # If the file above does not exist and bootstrap is set + # a new database file will be created, and the SQL statements + # contained within the bootstrap file will be executed. + bootstrap = "${modconfdir}/${..:name}/main/sqlite/schema.sql" + } + + mysql { + # If any of the files below are set, TLS encryption is enabled + tls { + ca_file = "/etc/ssl/certs/my_ca.crt" + ca_path = "/etc/ssl/certs/" + certificate_file = "/etc/ssl/certs/private/client.crt" + private_key_file = "/etc/ssl/certs/private/client.key" + cipher = "DHE-RSA-AES256-SHA:AES128-SHA" + + tls_required = yes + tls_check_cert = no + tls_check_cert_cn = no + } + + # If yes, (or auto and libmysqlclient reports warnings are + # available), will retrieve and log additional warnings from + # the server if an error has occured. Defaults to 'auto' + warnings = auto + } + + postgresql { + + # unlike MySQL, which has a tls{} connection configuration, postgresql + # uses its connection parameters - see the radius_db option below in + # this file + + # Send application_name to the postgres server + # Only supported in PG 9.0 and greater. Defaults to no. + send_application_name = yes + + # + # The default application name is "FreeRADIUS - .." with the current version. + # The application name can be customized here to any non-zero value. + # +# application_name = "" + } + + # + # Configuration for Mongo. + # + # Note that the Mongo driver is experimental. The FreeRADIUS developers + # are unable to help with the syntax of the Mongo queries. Please see + # the Mongo documentation for that syntax. + # + # The Mongo driver supports only the following methods: + # + # aggregate + # findAndModify + # findOne + # insert + # + # For examples, see the query files: + # + # raddb/mods-config/sql/main/mongo/queries.conf + # raddb/mods-config/sql/main/ippool/queries.conf + # + # In order to use findAndModify with an aggretation pipleline, make + # sure that you are running MongoDB version 4.2 or greater. FreeRADIUS + # assumes that the paramaters passed to the methods are supported by the + # version of MongoDB which it is connected to. + # + mongo { + # + # The application name to use. + # + appname = "freeradius" + + # + # The TLS parameters here map directly to the Mongo TLS configuration + # + tls { + certificate_file = /path/to/file + certificate_password = "password" + ca_file = /path/to/file + ca_dir = /path/to/directory + crl_file = /path/to/file + weak_cert_validation = false + allow_invalid_hostname = false + } + } + + # Connection info: + # +# server = "localhost" +# port = 3306 +# login = "radius" +# password = "radpass" + + # Connection info for Mongo + # Authentication Without SSL + # server = "mongodb://USER:PASSWORD@192.16.0.2:PORT/DATABASE?authSource=admin&ssl=false" + + # Authentication With SSL + # server = "mongodb://USER:PASSWORD@192.16.0.2:PORT/DATABASE?authSource=admin&ssl=true" + + # Authentication with Certificate + # Use this command for retrieve Derived username: + # openssl x509 -in mycert.pem -inform PEM -subject -nameopt RFC2253 + # server = mongodb://<DERIVED USERNAME>@192.168.0.2:PORT/DATABASE?authSource=$external&ssl=true&authMechanism=MONGODB-X509 + + # Database table configuration for everything except Oracle + radius_db = "radius" + + # If you are using Oracle then use this instead +# radius_db = "(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SID=your_sid)))" + + # If you're using postgresql this can also be used instead of the connection info parameters +# radius_db = "dbname=radius host=localhost user=radius password=raddpass" + + # Postgreql doesn't take tls{} options in its module config like mysql does - if you want to + # use SSL connections then use this form of connection info parameter +# radius_db = "host=localhost port=5432 dbname=radius user=radius password=raddpass sslmode=verify-full sslcert=/etc/ssl/client.crt sslkey=/etc/ssl/client.key sslrootcert=/etc/ssl/ca.crt" + + # If you want both stop and start records logged to the + # same SQL table, leave this as is. If you want them in + # different tables, put the start table in acct_table1 + # and stop table in acct_table2 + acct_table1 = "radacct" + acct_table2 = "radacct" + + # Allow for storing data after authentication + postauth_table = "radpostauth" + + # Tables containing 'check' items + authcheck_table = "radcheck" + groupcheck_table = "radgroupcheck" + + # Tables containing 'reply' items + authreply_table = "radreply" + groupreply_table = "radgroupreply" + + # Table to keep group info + usergroup_table = "radusergroup" + + # If set to 'yes' (default) we read the group tables unless Fall-Through = no in the reply table. + # If set to 'no' we do not read the group tables unless Fall-Through = yes in the reply table. +# read_groups = yes + + # If set to 'yes' (default) we read profiles unless Fall-Through = no in the groupreply table. + # If set to 'no' we do not read profiles unless Fall-Through = yes in the groupreply table. +# read_profiles = yes + + # Remove stale session if checkrad does not see a double login + delete_stale_sessions = yes + + # Write SQL queries to a logfile. This is potentially useful for tracing + # issues with authorization queries. See also "logfile" directives in + # mods-config/sql/main/*/queries.conf. You can enable per-section logging + # by enabling "logfile" there, or global logging by enabling "logfile" here. + # + # Per-section logging can be disabled by setting "logfile = ''" +# logfile = ${logdir}/sqllog.sql + + # Set the maximum query duration and connection timeout + # for rlm_sql_mysql. +# query_timeout = 5 + + # As of v3, the "pool" section has replaced the + # following v2 configuration items: + # + # num_sql_socks + # connect_failure_retry_delay + # lifetime + # max_queries + + # + # The connection pool is used to pool outgoing connections. + # + # When the server is not threaded, the connection pool + # limits are ignored, and only one connection is used. + # + # If you want to have multiple SQL modules re-use the same + # connection pool, use "pool = name" instead of a "pool" + # section. e.g. + # + # sql sql1 { + # ... + # pool { + # ... + # } + # } + # + # # sql2 will use the connection pool from sql1 + # sql sql2 { + # ... + # pool = sql1 + # } + # + pool { + # Connections to create during module instantiation. + # If the server cannot create specified number of + # connections during instantiation it will exit. + # Set to 0 to allow the server to start without the + # database being available. + start = ${thread[pool].start_servers} + + # Minimum number of connections to keep open + min = ${thread[pool].min_spare_servers} + + # Maximum number of connections + # + # If these connections are all in use and a new one + # is requested, the request will NOT get a connection. + # + # Setting 'max' to LESS than the number of threads means + # that some threads may starve, and you will see errors + # like 'No connections available and at max connection limit' + # + # Setting 'max' to MORE than the number of threads means + # that there are more connections than necessary. + max = ${thread[pool].max_servers} + + # Spare connections to be left idle + # + # NOTE: Idle connections WILL be closed if "idle_timeout" + # is set. This should be less than or equal to "max" above. + spare = ${thread[pool].max_spare_servers} + + # Number of uses before the connection is closed + # + # 0 means "infinite" + uses = 0 + + # The number of seconds to wait after the server tries + # to open a connection, and fails. During this time, + # no new connections will be opened. + retry_delay = 30 + + # The lifetime (in seconds) of the connection + lifetime = 0 + + # idle timeout (in seconds). A connection which is + # unused for this length of time will be closed. + idle_timeout = 60 + + # NOTE: All configuration settings are enforced. If a + # connection is closed because of "idle_timeout", + # "uses", or "lifetime", then the total number of + # connections MAY fall below "min". When that + # happens, it will open a new connection. It will + # also log a WARNING message. + # + # The solution is to either lower the "min" connections, + # or increase lifetime/idle_timeout. + + # Maximum number of times an operation can be retried + # if it returns an error which indicates the connection + # needs to be restarted. This includes timeouts. + max_retries = 5 + } + + # Set to 'yes' to read radius clients from the database ('nas' table) + # Clients will ONLY be read on server startup. + # + # A client can be link to a virtual server via the SQL + # module. This link is done via the following process: + # + # If there is no listener in a virtual server, SQL clients + # are added to the global list for that virtual server. + # + # If there is a listener, and the first listener does not + # have a "clients=..." configuration item, SQL clients are + # added to the global list. + # + # If there is a listener, and the first one does have a + # "clients=..." configuration item, SQL clients are added to + # that list. The client { ...} ` configured in that list are + # also added for that listener. + # + # The only issue is if you have multiple listeners in a + # virtual server, each with a different client list, then + # the SQL clients are added only to the first listener. + # +# read_clients = yes + + # Table to keep radius client info + client_table = "nas" + + # + # The group attribute specific to this instance of rlm_sql + # + + # This entry should be used for additional instances (sql foo {}) + # of the SQL module. +# group_attribute = "${.:instance}-SQL-Group" + + # This entry should be used for the default instance (sql {}) + # of the SQL module. + group_attribute = "SQL-Group" + + # Read database-specific queries + $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf +} diff --git a/raddb/mods-available/sql_map b/raddb/mods-available/sql_map new file mode 100644 index 0000000..93b2636 --- /dev/null +++ b/raddb/mods-available/sql_map @@ -0,0 +1,49 @@ +# Configuration for the SQL based Map (rlm_sql_map) +sql_map { + # SQL instance to use (from mods-available/sql) + # + # If you have multiple sql instances, such as "sql sql1 {...}", + # use the *instance* name here: sql1. + sql_module_instance = "sql" + + # This is duplicative of info available in the SQL module, but + # we have to list it here as we do not yet support nested + # reference expansions. + dialect = "mysql" + + # Name of the check item attribute to be used as a key in the SQL queries + query = "SELECT ... FROM ... " + + # + # Mapping of SQL columns to RADIUS dictionary attributes. + # + + # WARNING: Although this format is almost identical to the unlang + # update section format, it does *NOT* mean that you can use other + # unlang constructs in module configuration files. + # + # Configuration items are in the format: + # <radius attr> <op> <sql column number> + # + # Where: + # <radius attr>: Is the destination RADIUS attribute + # with any valid list and request qualifiers. + # <op>: Is any assignment attribute (=, :=, +=, -=). + # <column num>: The column number (not name), starting from 0 + # + # Request and list qualifiers may also be placed after the 'update' + # section name to set defaults destination requests/lists + # for unqualified RADIUS attributes. + # + update { + control:Password-With-Header += 0 +# control:NT-Password := 1 +# reply:Reply-Message := 2 +# reply:Tunnel-Type := 3 +# reply:Tunnel-Medium-Type := 4 +# reply:Tunnel-Private-Group-ID := 5 + } + + # If the 'query' results in multiple rows, it creates the <radius attr>[*] array entry. +# multiple_rows = yes +} diff --git a/raddb/mods-available/sqlcounter b/raddb/mods-available/sqlcounter new file mode 100644 index 0000000..a2b206e --- /dev/null +++ b/raddb/mods-available/sqlcounter @@ -0,0 +1,122 @@ +# Rather than maintaining separate (GDBM) databases of +# accounting info for each counter, this module uses the data +# stored in the raddacct table by the sql modules. This +# module NEVER does any database INSERTs or UPDATEs. It is +# totally dependent on the SQL module to process Accounting +# packets. +# +# The sql-module-instance' parameter holds the instance of the sql +# module to use when querying the SQL database. Normally it +# is just "sql". If you define more and one SQL module +# instance (usually for failover situations), you can +# specify which module has access to the Accounting Data +# (radacct table). +# +# The 'reset' parameter defines when the counters are all +# reset to zero. It can be hourly, daily, weekly, monthly or +# never. It can also be user defined. It should be of the +# form: +# num[hdwm] where: +# h: hours, d: days, w: weeks, m: months +# If the letter is ommited days will be assumed. In example: +# reset = 10h (reset every 10 hours) +# reset = 12 (reset every 12 days) +# +# The 'reset_day' parameter defines which day of the month the +# 'monthly' counter should be reset; valid values are 1 to 28. +# +# The 'key' parameter specifies the unique identifier for the +# counter records (usually 'User-Name'). +# +# The 'query' parameter specifies the SQL query used to get +# the current Counter value from the database. There are four +# parameters that can be used in the query: +# +# %%b unix time value of beginning of reset period. +# %%e unix time value of end of reset period. +# %%k value of 'key' parameter. +# %%r day of month the counter should be reset. +# +# The 'check_name' parameter is the name of the 'check' +# attribute to use to access the counter in the 'users' file +# or SQL radcheck or radgroupcheck tables. +# +# DEFAULT Max-Daily-Session > 3600, Auth-Type = Reject +# Reply-Message = "You've used up more than one hour today" +# +# The "dailycounter" (or any other sqlcounter module) should be added +# to "post-auth" section. It will then update the Session-Timeout +# attribute in the reply. If there is no Session-Timeout attribute, +# the module will add one. If there is an attribute, the sqlcounter +# module will make sure that the value is no higher than the limit. +# +sqlcounter dailycounter { + sql_module_instance = sql + dialect = ${modules.sql.dialect} + + counter_name = Daily-Session-Time + check_name = Max-Daily-Session + reply_name = Session-Timeout + + key = User-Name + reset = daily + + $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf +} + +sqlcounter weeklycounter { + sql_module_instance = sql + dialect = ${modules.sql.dialect} + + counter_name = Weekly-Session-Time + check_name = Max-Weekly-Session + reply_name = Session-Timeout + + key = User-Name + reset = weekly + + $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf +} + +sqlcounter monthlycounter { + sql_module_instance = sql + dialect = ${modules.sql.dialect} + + counter_name = Monthly-Session-Time + check_name = Max-Monthly-Session + reply_name = Session-Timeout + key = User-Name + reset = monthly + reset_day = 1 + + $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf +} + +sqlcounter noresetcounter { + sql_module_instance = sql + dialect = ${modules.sql.dialect} + + counter_name = Max-All-Session-Time + check_name = Max-All-Session + key = User-Name + reset = never + + $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf +} + +# +# Set an account to expire T seconds after first login. +# Requires the Expire-After attribute to be set, in seconds. +# You may need to edit raddb/dictionary to add the Expire-After +# attribute. +sqlcounter expire_on_login { + sql_module_instance = sql + dialect = ${modules.sql.dialect} + + counter_name = Expire-After-Initial-Login + check_name = Expire-After + key = User-Name + reset = never + + $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf +} diff --git a/raddb/mods-available/sqlippool b/raddb/mods-available/sqlippool new file mode 100644 index 0000000..f17a989 --- /dev/null +++ b/raddb/mods-available/sqlippool @@ -0,0 +1,109 @@ +# Configuration for the SQL based IP Pool module (rlm_sqlippool) +# +# The database schemas are available at: +# +# raddb/mods-config/sql/ippool/<DB>/schema.sql +# +# $Id$ + +sqlippool { + # SQL instance to use (from mods-available/sql) + # + # If you have multiple sql instances, such as "sql sql1 {...}", + # use the *instance* name here: sql1. + sql_module_instance = "sql" + + # This is duplicative of info available in the SQL module, but + # we have to list it here as we do not yet support nested + # reference expansions. + dialect = "mysql" + + # Name of the check item attribute to be used as a key in the SQL queries + pool_name = "Pool-Name" + + # SQL table to use for ippool range and lease info + ippool_table = "radippool" + + # IP lease duration. (Leases expire even if Acct Stop packet is lost) + # + # Note that you SHOULD also set Session-Timeout to this value! + # That way the NAS will automatically kick the user offline when the + # lease expires. + # + lease_duration = 3600 + + # + # Timeout between each consecutive 'allocate_clear' queries (default: 1s) + # This will avoid having too many deadlock issues, especially on MySQL backend. + # + allocate_clear_timeout = 1 + + # + # The attribute to use for IP address assignment. The + # default is Framed-IP-Address. You can change this to any + # attribute which is IPv4 or IPv6. + # + # e.g. Framed-IPv6-Prefix, or Delegated-IPv6-Prefix. + # + # All of the default queries use this attribute_name. So you + # can do IPv6 address assignment simply by putting IPv6 + # addresses into the pool, and changing the following line to + # "Framed-IPv6-Prefix" + # + # Note that you MUST use separate pools for each attribute. i.e. one pool + # for Framed-IP-Address, a different one for Framed-IPv6-prefix, etc. + # + # This means configuring separate "sqlippool" instances, and different + # "ippool_table" in SQL. Then, populate the pool with addresses and + # it will all just work. + # + attribute_name = Framed-IP-Address + + # + # Assign the IP address, even if the above attribute already exists + # in the reply. + # +# allow_duplicates = no + + # The attribute in which an IP address hint may be supplied + req_attribute_name = Framed-IP-Address + + # Attribute which should be considered unique per NAS + # + # Using NAS-Port gives behaviour similar to rlm_ippool. (And ACS) + # Using Calling-Station-Id works for NAS that send fixed NAS-Port + # ONLY change this if you know what you are doing! + pool_key = "%{NAS-Port}" + # pool_key = "%{Calling-Station-Id}" + + ################################################################ + # + # WARNING: MySQL (MyISAM) has certain limitations that means it can + # hand out the same IP address to 2 different users. + # + # We suggest using an SQL DB with proper transaction + # support, such as PostgreSQL, or using MySQL + # with InnoDB. + # + ################################################################ + + # These messages are added to the "control" items, as + # Module-Success-Message. They are not logged anywhere else, + # unlike previous versions. If you want to have them logged + # to a file, see the "linelog" module, and create an entry + # which writes Module-Success-Message message. + # + messages { + exists = "Existing IP: %{reply:${..attribute_name}} (did %{Called-Station-Id} cli %{Calling-Station-Id} port %{NAS-Port} user %{User-Name})" + + success = "Allocated IP: %{reply:${..attribute_name}} from %{control:${..pool_name}} (did %{Called-Station-Id} cli %{Calling-Station-Id} port %{NAS-Port} user %{User-Name})" + + clear = "Released IP %{request:${..attribute_name}} (did %{Called-Station-Id} cli %{Calling-Station-Id} user %{User-Name})" + + failed = "IP Allocation FAILED from %{control:${..pool_name}} (did %{Called-Station-Id} cli %{Calling-Station-Id} port %{NAS-Port} user %{User-Name})" + + nopool = "No ${..pool_name} defined (did %{Called-Station-Id} cli %{Calling-Station-Id} port %{NAS-Port} user %{User-Name})" + } + + $INCLUDE ${modconfdir}/sql/ippool/${dialect}/queries.conf +} diff --git a/raddb/mods-available/sradutmp b/raddb/mods-available/sradutmp new file mode 100644 index 0000000..3a2a0e5 --- /dev/null +++ b/raddb/mods-available/sradutmp @@ -0,0 +1,16 @@ +# -*- text -*- +# +# $Id$ + +# "Safe" radutmp - does not contain caller ID, so it can be +# world-readable, and radwho can work for normal users, without +# exposing any information that isn't already exposed by who(1). +# +# This is another 'instance' of the radutmp module, but it is given +# then name "sradutmp" to identify it later in the "accounting" +# section. +radutmp sradutmp { + filename = ${logdir}/sradutmp + permissions = 0644 + caller_id = "no" +} diff --git a/raddb/mods-available/totp b/raddb/mods-available/totp new file mode 100644 index 0000000..695365f --- /dev/null +++ b/raddb/mods-available/totp @@ -0,0 +1,40 @@ +# -*- text -*- +# +# $Id$ + +# +# Time-based One-Time Passwords (TOTP) +# +# Defined in RFC 6238, and used in Google Authenticator. +# +# This module can only be used in the "authenticate" section. +# +# The Base32-encoded secret should be placed into: +# +# &control:TOTP-Secret +# +# The TOTP password entered by the user should be placed into: +# +# &request:TOTP-Password +# +# The module will return "ok" if the passwords match, and "fail" +# if the passwords do not match. +# +# Note that this module will NOT interact with Google. The module is +# intended to be used where the local administrator knows the TOTP +# secret key, and user has an authenticator app on their phone. +# +# Note also that while you can use the Google "chart" APIs to +# generate a QR code, doing this will give the secret to Google! +# +# Administrators should instead install a tool such as "qrcode" +# +# https://linux.die.net/man/1/qrencode +# +# and then run that locally to get an image. +# +# +# The module takes no configuration items. +# +totp { +} diff --git a/raddb/mods-available/unbound b/raddb/mods-available/unbound new file mode 100644 index 0000000..43fbce5 --- /dev/null +++ b/raddb/mods-available/unbound @@ -0,0 +1,13 @@ +unbound dns { + # Configuration for libunbound + # filename = "${raddbdir}/mods-config/unbound/default.conf" + + # File to load resolver details from. + # Without this set unbound will query root servers + # resolvconf = "/etc/resolv.conf" + + # Hosts entries to load + # hosts = "/etc/hosts" + + # timeout = 3000 +} diff --git a/raddb/mods-available/unix b/raddb/mods-available/unix new file mode 100644 index 0000000..5165139 --- /dev/null +++ b/raddb/mods-available/unix @@ -0,0 +1,25 @@ +# -*- text -*- +# +# $Id$ + +# Unix /etc/passwd style authentication +# +# This module calls the system functions to get the "known good" +# password. This password is usually in the "crypt" form, and is +# incompatible with CHAP, MS-CHAP, PEAP, etc. +# +# If passwords are in /etc/shadow, you will need to set the "group" +# configuration in radiusd.conf. Look for "shadow", and follow the +# instructions there. +# +unix { + # + # The location of the "wtmp" file. + # The only use for 'radlast'. If you don't use + # 'radlast', then you can comment out this item. + # + # Note that the radwtmp file may get large! You should + # rotate it (cp /dev/null radwtmp), or just not use it. + # + radwtmp = ${logdir}/radwtmp +} diff --git a/raddb/mods-available/unpack b/raddb/mods-available/unpack new file mode 100644 index 0000000..89ef169 --- /dev/null +++ b/raddb/mods-available/unpack @@ -0,0 +1,105 @@ +# -*- text -*- +# +# $Id$ + +# +# This module is useful only for 'xlat'. +# To use it, add it to the raddb/mods-enabled/ directory. +# +# Two xlat functions are provided by this module: +# - unpack +# - substring +# +# Both are for use on the right-hand side of a variable assignment. +# +# unpack +# ====== +# +# ... = "%{unpack:data 1 integer}" +# +# The arguments are three fields: +# +# data +# Either &Attribute-Name +# the name of the attribute to unpack. +# MUST be a "string" or "octets" type. +# +# or 0xabcdef +# e.g. hex data. +# +# 1 +# The offset into the string from which +# it starts unpacking. The offset starts +# at zero, for the first attribute. +# +# integer +# the data type to unpack at that offset. +# e.g. integer, ipaddr, byte, short, etc. +# +# e.g. if we have Class = 0x0000000102030405, then +# +# %{unpack:&Class 4 short} +# +# will unpack octets 4 and 5 as a "short", which has +# value 0x0304. +# +# This module is used when vendors put multiple fields +# into one attribute of type "octets". +# +# The module can also be used to unpack substrings, by specifing a +# data type of "string(len)" or "octets(len)". Where "len" is an +# actual number. For example: +# +# %{unpack:&User-Name 1 string(2)} +# +# When given a User-Name of "hello", it will start taking the +# substring at offset 1 (i.e. "e"), and it will take two characters +# from that offset, i.e. "el". +# +# As a special case, you can unpack an entire string by specifying +# the offset, and nothing for the length: +# +# %{unpack:&User-Name 1 string()} +# +# When "octets(len)" is used, the output is printed as hex. e.g. for +# the above example with Class: +# +# %{unpack:&Class 4 octets(4)} +# +# Will return the hex string "02030405" +# +# +# substring +# ========= +# +# substring will return a substring of a string or attribute using +# the syntax +# +# %{substring:data start len} +# +# data +# Either an attribute name or string data. String data +# can have leading or trailing spaces. Only a single +# space before "start" is taken as the separator. +# +# start +# the zero based offset for the start of the substring. +# A negative value will count in from the end of the +# string. +# +# len +# the number of characters to return. A Negative value +# will remove that number of characters from the end. +# If len is more than the available number of characters +# then only the available number will be returned. +# +# Examples: +# +# "%{substring:foobar 2 3}" == "oba" +# "%{substring:foobar -3 2}" == "ba" +# "%{substring:foobar 1 -1}" == "ooba" +# if User-Name is "foobar" "%{substring:&User-Name 1 -2}" == "oob" +# + +unpack { +} diff --git a/raddb/mods-available/utf8 b/raddb/mods-available/utf8 new file mode 100644 index 0000000..00812fa --- /dev/null +++ b/raddb/mods-available/utf8 @@ -0,0 +1,14 @@ +# +# Enforces UTF-8 on strings coming in from the NAS. +# +# An attribute of type "string" containing UTF-8 makes +# the module return NOOP. +# +# An attribute of type "string" containing non-UTF-8 data +# makes the module return FAIL. +# +# This module takes no configuration. +# +utf8 { + +} diff --git a/raddb/mods-available/wimax b/raddb/mods-available/wimax new file mode 100644 index 0000000..3add59e --- /dev/null +++ b/raddb/mods-available/wimax @@ -0,0 +1,165 @@ +# +# The WiMAX module currently takes no configuration. +# +# ## Instructions for v1 and v2.0 WiMAX +# +# It should be listed in the "authorize" and "preacct" sections. +# This enables the module to fix the horrible binary version +# of Calling-Station-Id to the normal format, as specified in +# RFC 3580, Section 3.21. +# +# In order to calculate the various WiMAX keys, the module should +# be listed in the "post-auth" section. If EAP authentication +# has been used, AND the EAP method derives MSK and EMSK, then +# the various WiMAX keys can be calculated. +# +# Some useful things to remember: +# +# WiMAX-MSK = EAP MSK, but is 64 octets. +# +# MIP-RK-1 = HMAC-SHA256(ESMK, "miprk@wimaxforum.org" | 0x00020001) +# MIP-RK-2 = HMAC-SHA256(ESMK, MIP-RK-1 | "miprk@wimaxforum.org" | 0x00020002) +# MIP-RK = MIP-RK-1 | MIP-RK-2 +# +# MIP-SPI = first 4 octets of HMAC-SHA256(MIP-RK, "SPI CMIP PMIP") +# plus some magic... you've got to track *all* MIP-SPI's +# on your system! +# +# SPI-CMIP4 = MIP-SPI +# SPI-PMIP4 = MIP-SPI + 1 +# SPI-CMIP6 = MIP-SPI + 2 +# +# MN-NAI is the Mobile node NAI. You have to create it, and put +# it into the request or reply as something like: +# +# WiMAX-MN-NAI = "%{User-Name}" +# +# You will also have to have the appropriate IP address (v4 or v6) +# in order to calculate the keys below. +# +# Lifetimes are derived from Session-Timeout. It needs to be set +# to some useful number. +# +# The hash function below H() is HMAC-SHA1. +# +# +# MN-HA-CMIP4 = H(MIP-RK, "CMIP4 MN HA" | HA-IPv4 | MN-NAI) +# +# Where HA-IPv4 is WiMAX-hHA-IP-MIP4 +# or maybe WiMAX-vHA-IP-MIP4 +# +# Which goes into WiMAX-MN-hHA-MIP4-Key +# or maybe WiMAX-RRQ-MN-HA-Key +# or maybe even WiMAX-vHA-MIP4-Key +# +# The corresponding SPI is SPI-CMIP4, which is MIP-SPI, +# +# which goes into WiMAX-MN-hHA-MIP4-SPI +# or maybe WiMAX-RRQ-MN-HA-SPI +# or even WiMAX-MN-vHA-MIP4-SPI +# +# MN-HA-PMIP4 = H(MIP-RK, "PMIP4 MN HA" | HA-IPv4 | MN-NAI) +# MN-HA-CMIP6 = H(MIP-RK, "CMIP6 MN HA" | HA-IPv6 | MN-NAI) +# +# both with similar comments to above for MN-HA-CMIP4. +# +# In order to tell which one to use (CMIP4, PMIP4, or CMIP6), +# you have to set WiMAX-IP-Technology in the reply to one of +# the appropriate values. +# +# +# FA-RK = H(MIP-RK, "FA-RK") +# +# MN-FA = H(FA-RK, "MN FA" | FA-IP | MN-NAI) +# +# Where does the FA-IP come from? No idea... +# +# +# The next two keys (HA-RK and FA-HA) are not generated +# for every authentication request, but only on demand. +# +# HA-RK = 160-bit random number assigned by the AAA server +# to a specific HA. +# +# FA-HA = H(HA-RK, "FA-HA" | HA-IPv4 | FA-CoAv4 | SPI) +# +# where HA-IPv4 is as above. +# and FA-CoAv4 address of the FA as seen by the HA +# and SPI is the relevant SPI for the HA-RK. +# +# DHCP-RK = 160-bit random number assigned by the AAA server +# to a specific DHCP server. vDHCP-RK is the same +# thing. +# +# +# +# ## Instructions for v2.1 (LTE) WiMAX: +# +# When called from the "authorize" this module will detect the +# presence of the following attributes: +# +# request:WiMAX-Re-synchronization-Info +# control:WiMAX-SIM-Ki +# control:WiMAX-SIM-OPc +# +# If all attributes are present, (i.e. a known SIM is requesting a +# resync) then the module will attempt to extract the new SQN and +# save it in control:WiMAX-SIM-SQN. It will also save a copy of +# RAND from the request in control:WiMAX-SIM-RAND. +# +# The resulting value of SQN can then be saved in a database +# e.g. via a call to the sql module using some unlang +# +# When called in the "post_auth" section it looks for: +# +# control:WiMAX-SIM-Ki +# control:WiMAX-SIM-OPc +# control:WiMAX-SIM-AMF +# control:WiMAX-SIM-SQN +# request:WiMAX-Visited-PLMN-ID +# +# If all these are present then it will attempt to generate the +# keys for EPS AKA. +# +# First it checks for the presence of control:WiMAX-SIM-RAND and +# if it is not present it generates a new RAND value which is +# stored in reply:WiMAX-E-UTRAN-Vector-RAND. If it is present then +# the value is simply copied to the reply attribute. +# +# Then it calls the Milenage algorithm to generate: +# +# reply:WiMAX-E-UTRAN-Vector-XRES +# reply:WiMAX-E-UTRAN-Vector-AUTN +# +# And finally generates KASME which is stored in: +# reply:WiMAX-E-UTRAN-Vector-KASME +# +# +# NOTE: It is up to the system administrator to make sure that all +# the necessary "control" attributes are populated with the +# required values. The IMSI is likely to be found in User-Name in +# the request and this can be used as the key to grab the values +# from a database. +# +# +wimax { + # + # Some WiMAX equipment requires that the MS-MPPE-*-Key + # attributes are sent in the Access-Accept, in addition to + # the WiMAX-MSK attribute. + # + # Other WiMAX equipment request that the MS-MPPE-*-Key + # attributes are NOT sent in the Access-Accept. + # + # By default, the EAP modules sends MS-MPPE-*-Key attributes. + # The default virtual server (raddb/sites-available/default) + # contains examples of adding the WiMAX-MSK. + # + # This configuration option makes the WiMAX module delete + # the MS-MPPE-*-Key attributes. The default is to leave + # them in place. + # + # If the keys are deleted (by setting this to "yes"), then + # the WiMAX-MSK attribute is automatically added to the reply. + delete_mppe_keys = no +} diff --git a/raddb/mods-available/yubikey b/raddb/mods-available/yubikey new file mode 100644 index 0000000..9ba61ef --- /dev/null +++ b/raddb/mods-available/yubikey @@ -0,0 +1,158 @@ +# +# This module decrypts and validates Yubikey static and dynamic +# OTP tokens. +# +yubikey { + # + # The length (number of ASCII bytes) of the Public-ID portion + # of the OTP string. + # + # Yubikey defaults to a 6 byte ID (2 * 6 = 12) +# id_length = 12 + + # + # If true, the authorize method of rlm_yubikey will attempt to split the + # value of User-Password, into the user's password, and the OTP token. + # + # If enabled and successful, the value of &request:User-Password will be + # truncated and &request:Yubikey-OTP will be added. + # +# split = yes + + # + # Decrypt mode - Tokens will be decrypted and processed locally + # + # The module itself does not provide persistent storage as this + # would be duplicative of functionality already in the server. + # + # Yubikey authentication needs two attributes retrieved from + # persistent storage: + # * &control:Yubikey-Key - The AES key used to decrypt the OTP data. + # The Yubikey-Public-Id and/or User-Name + # attributes may be used to retrieve the key. + # * &control:Yubikey-Counter - This is compared with the counter in the OTP + # data and used to prevent replay attacks. + # This attribute will also be available in + # the request list after successful + # decryption. + # + # Yubikey-Counter isn't strictly required, but the server will + # generate warnings if it's not present when yubikey.authenticate + # is called. + # + # These attributes are available after authorization: + # * &request:Yubikey-Public-ID - The public portion of the OTP string. + # and additionally if 'split' is set: + # * &request:Yubikey-OTP - The OTP portion of User-Password. + # + # These attributes are available after authentication (if successful): + # * &request:Yubikey-Private-ID - The encrypted ID included in OTP data, + # must be verified if tokens share keys. + # * &request:Yubikey-Counter - The last counter value (should be recorded). + # * &request:Yubikey-Timestamp - Token's internal clock (mainly useful for + # debugging). + # * &request:Yubikey-Random - Randomly generated value from the token. + # + decrypt = no + + # + # Validation mode - Tokens will be validated against a Yubicloud server + # + validate = no + + # + # Settings for validation mode. + # + validation { + # + # URL of validation server, multiple URL config items may be used + # to list multiple servers. + # + # - %d is a placeholder for public ID of the token + # - %s is a placeholder for the token string itself + # + # If no URLs are listed, will default to the default URLs in the + # ykclient library, which point to the yubico validation servers. + servers { +# uri = 'https://api.yubico.com/wsapi/2.0/verify?id=%d&otp=%s' +# uri = 'https://api2.yubico.com/wsapi/2.0/verify?id=%d&otp=%s' + } + + # + # API Client ID + # + # Must be set to your client id for the validation server. + # +# client_id = 00000 + + # + # API Secret key (Base64 encoded) + # + # Must be set to your API key for the validation server. + # +# api_key = '000000000000000000000000' + + # + # Connection pool parameters + # + pool { + # Connections to create during module instantiation. + # If the server cannot create specified number of + # connections during instantiation it will exit. + # Set to 0 to allow the server to start without the + # yubikey server being available. + start = ${thread[pool].start_servers} + + # Minimum number of connections to keep open + min = ${thread[pool].min_spare_servers} + + # Maximum number of connections + # + # If these connections are all in use and a new one + # is requested, the request will NOT get a connection. + # + # Setting 'max' to LESS than the number of threads means + # that some threads may starve, and you will see errors + # like 'No connections available and at max connection limit' + # + # Setting 'max' to MORE than the number of threads means + # that there are more connections than necessary. + max = ${thread[pool].max_servers} + + # Number of uses before the connection is closed + # + # NOTE: A setting of 0 means infinite (no limit). + uses = 0 + + # The number of seconds to wait after the server tries + # to open a connection, and fails. During this time, + # no new connections will be opened. + retry_delay = 30 + + # The lifetime (in seconds) of the connection + # + # NOTE: A setting of 0 means infinite (no limit). + lifetime = 0 + + # The idle timeout (in seconds). A connection which is + # unused for this length of time will be closed. + # + # NOTE: A setting of 0 means infinite (no timeout). + idle_timeout = 60 + + # Cycle over all connections in a pool instead of concentrating + # connection use on a few connections. + spread = yes + + # NOTE: All configuration settings are enforced. If a + # connection is closed because of "idle_timeout", + # "uses", or "lifetime", then the total number of + # connections MAY fall below "min". When that + # happens, it will open a new connection. It will + # also log a WARNING message. + # + # The solution is to either lower the "min" connections, + # or increase lifetime/idle_timeout. + } + } +} diff --git a/raddb/mods-config/README.rst b/raddb/mods-config/README.rst new file mode 100644 index 0000000..abb4c8d --- /dev/null +++ b/raddb/mods-config/README.rst @@ -0,0 +1,22 @@ +The mods-config Directory +========================= + +This directory contains module-specific configuration files. These +files are in a format different from the one used by the main +`radiusd.conf` files. Earlier versions of the server had many +module-specific files in the main `raddb` directory. The directory +contained many files, and it was not clear which files did what. + +For Version 3 of FreeRADIUS, we have moved to a consistent naming +scheme. Each module-specific configuration file is placed in this +directory, in a subdirectory named for the module. Where necessary, +files in the subdirectory have been named for the processing section +where they are used. + +For example, the `users` file is now located in +`mods-config/files/authorize`. That filename tells us three things: + +1. The file is used in the `authorize` section. +2. The file is used by the `files` module. +3. It is a "module configuration" file, which is a specific format. + diff --git a/raddb/mods-config/attr_filter/access_challenge b/raddb/mods-config/attr_filter/access_challenge new file mode 100644 index 0000000..12ed619 --- /dev/null +++ b/raddb/mods-config/attr_filter/access_challenge @@ -0,0 +1,19 @@ +# +# Configuration file for the rlm_attr_filter module. +# Please see rlm_attr_filter(5) manpage for more information. +# +# $Id$ +# +# This configuration file is used to remove almost all of the +# attributes From an Access-Challenge message. The RFCs say +# that an Access-Challenge packet can contain only a few +# attributes. We enforce that here. +# +DEFAULT + EAP-Message =* ANY, + State =* ANY, + Message-Authenticator =* ANY, + Reply-Message =* ANY, + Proxy-State =* ANY, + Session-Timeout =* ANY, + Idle-Timeout =* ANY diff --git a/raddb/mods-config/attr_filter/access_reject b/raddb/mods-config/attr_filter/access_reject new file mode 100644 index 0000000..47f167b --- /dev/null +++ b/raddb/mods-config/attr_filter/access_reject @@ -0,0 +1,18 @@ +# +# Configuration file for the rlm_attr_filter module. +# Please see rlm_attr_filter(5) manpage for more information. +# +# $Id$ +# +# This configuration file is used to remove almost all of the attributes +# From an Access-Reject message. The RFCs say that an Access-Reject +# packet can contain only a few attributes. We enforce that here. +# +DEFAULT + EAP-Message =* ANY, + State =* ANY, + Message-Authenticator =* ANY, + Error-Cause =* ANY, + Reply-Message =* ANY, + MS-CHAP-Error =* ANY, + Proxy-State =* ANY diff --git a/raddb/mods-config/attr_filter/accounting_response b/raddb/mods-config/attr_filter/accounting_response new file mode 100644 index 0000000..01e9c6f --- /dev/null +++ b/raddb/mods-config/attr_filter/accounting_response @@ -0,0 +1,16 @@ +# +# Configuration file for the rlm_attr_filter module. +# Please see rlm_attr_filter(5) manpage for more information. +# +# $Id$ +# +# This configuration file is used to remove almost all of the attributes +# From an Accounting-Response message. The RFC's say that an +# Accounting-Response packet can contain only a few attributes. +# We enforce that here. +# +DEFAULT + Vendor-Specific =* ANY, + Message-Authenticator =* ANY, + Error-Cause =* ANY, + Proxy-State =* ANY diff --git a/raddb/mods-config/attr_filter/coa b/raddb/mods-config/attr_filter/coa new file mode 100644 index 0000000..89cea2e --- /dev/null +++ b/raddb/mods-config/attr_filter/coa @@ -0,0 +1,22 @@ +# +# Configuration file for the rlm_attr_filter module. +# Please see rlm_attr_filter(5) manpage for more information. +# +# $Id$ +# +# This configuration file is used to remove attributes From an +# CoA-Request or Disconnect-Request message. We have specified +# a sample list here. This will have to be modified to add +# attributes needed by your local configuration. +# +DEFAULT + User-Name =* ANY, + NAS-IP-Address =* ANY, + NAS-IPv6-Address =* ANY, + NAS-Port =* ANY, + NAS-Identifier =* ANY, + NAS-Port-Type =* ANY, + Calling-Station-Id =* ANY, + State =* ANY, + Message-Authenticator =* ANY, + Proxy-State =* ANY diff --git a/raddb/mods-config/attr_filter/post-proxy b/raddb/mods-config/attr_filter/post-proxy new file mode 100644 index 0000000..169fe5c --- /dev/null +++ b/raddb/mods-config/attr_filter/post-proxy @@ -0,0 +1,121 @@ +# +# Configuration file for the rlm_attr_filter module. +# Please see rlm_attr_filter(5) manpage for more information. +# +# $Id$ +# +# This file contains security and configuration information +# for each realm. The first field is the realm name and +# can be up to 253 characters in length. This is followed (on +# the next line) with the list of filter rules to be used to +# decide what attributes and/or values we allow proxy servers +# to pass to the NAS for this realm. +# +# When a proxy-reply packet is received from a home server, +# these attributes and values are tested. Only the first match +# is used unless the "Fall-Through" variable is set to "Yes". +# In that case the rules defined in the DEFAULT case are +# processed as well. +# +# A special realm named "DEFAULT" matches on all realm names. +# You can have only one DEFAULT entry. All entries are processed +# in the order they appear in this file. The first entry that +# matches the login-request will stop processing unless you use +# the Fall-Through variable. +# +# Indented (with the tab character) lines following the first +# line indicate the filter rules. +# +# You can include another `attrs' file with `$INCLUDE attrs.other' +# + +# +# This is a complete entry for realm "fisp". Note that there is no +# Fall-Through entry so that no DEFAULT entry will be used, and the +# server will NOT allow any other a/v pairs other than the ones +# listed here. +# +# These rules allow: +# o Only Framed-User Service-Types ( no telnet, rlogin, tcp-clear ) +# o PPP sessions ( no SLIP, CSLIP, etc. ) +# o dynamic ip assignment ( can't assign a static ip ) +# o an idle timeout value set to 600 seconds (10 min) or less +# o a max session time set to 28800 seconds (8 hours) or less +# +#fisp +# Service-Type == Framed-User, +# Framed-Protocol == PPP, +# Framed-IP-Address == 255.255.255.254, +# Idle-Timeout <= 600, +# Session-Timeout <= 28800 + +# +# This is a complete entry for realm "tisp". Note that there is no +# Fall-Through entry so that no DEFAULT entry will be used, and the +# server will NOT allow any other a/v pairs other than the ones +# listed here. +# +# These rules allow: +# o Only Login-User Service-Type ( no framed/ppp sessions ) +# o Telnet sessions only ( no rlogin, tcp-clear ) +# o Login host of 192.0.2.1 +# +#tisp +# Service-Type == Login-User, +# Login-Service == Telnet, +# Login-TCP-Port == 23, +# Login-IP-Host == 192.0.2.1 + +# +# The following example can be used for a home server which is only +# allowed to supply a Reply-Message, a Session-Timeout attribute of +# maximum 86400, a Idle-Timeout attribute of maximum 600 and a +# Acct-Interim-Interval attribute between 300 and 3600. +# All other attributes sent back will be filtered out. +# +#strictrealm +# Reply-Message =* ANY, +# Session-Timeout <= 86400, +# Idle-Timeout <= 600, +# Acct-Interim-Interval >= 300, +# Acct-Interim-Interval <= 3600 + +# +# This is a complete entry for realm "spamrealm". Fall-Through is used, +# so that the DEFAULT filter rules are used in addition to these. +# +# These rules allow: +# o Force the application of Filter-ID attribute to be returned +# in the proxy reply, whether the proxy sent it or not. +# o The standard DEFAULT rules as defined below +# +#spamrealm +# Framed-Filter-Id := "nosmtp.in", +# Fall-Through = Yes + +# +# The rest of this file contains the DEFAULT entry. +# DEFAULT matches with all realm names. (except if the realm previously +# matched an entry with no Fall-Through) +# + +DEFAULT + Framed-IP-Address == 255.255.255.254, + Framed-IP-Netmask == 255.255.255.255, + Framed-MTU >= 576, + Framed-Filter-ID =* ANY, + Reply-Message =* ANY, + Proxy-State =* ANY, + EAP-Message =* ANY, + Message-Authenticator =* ANY, + MS-MPPE-Recv-Key =* ANY, + MS-MPPE-Send-Key =* ANY, + MS-CHAP-MPPE-Keys =* ANY, + State =* ANY, + Session-Timeout <= 28800, + Idle-Timeout <= 600, + Calling-Station-Id =* ANY, + Operator-Name =* ANY, + User-Name =* ANY, + Chargeable-User-Identity =* ANY, + Port-Limit <= 2 diff --git a/raddb/mods-config/attr_filter/pre-proxy b/raddb/mods-config/attr_filter/pre-proxy new file mode 100644 index 0000000..36d84e8 --- /dev/null +++ b/raddb/mods-config/attr_filter/pre-proxy @@ -0,0 +1,67 @@ +# +# Configuration file for the rlm_attr_filter module. +# Please see rlm_attr_filter(5) manpage for more information. +# +# $Id$ +# +# This file contains security and configuration information +# for each realm. It can be used be an rlm_attr_filter module +# instance to filter attributes before sending packets to the +# home server of a realm. +# +# When a packet is sent to a home server, these attributes +# and values are tested. Only the first match is used unless +# the "Fall-Through" variable is set to "Yes". In that case +# the rules defined in the DEFAULT case are processed as well. +# +# A special realm named "DEFAULT" matches on all realm names. +# You can have only one DEFAULT entry. All entries are processed +# in the order they appear in this file. The first entry that +# matches the login-request will stop processing unless you use +# the Fall-Through variable. +# +# The first line indicates the realm to which the rules apply. +# Indented (with the tab character) lines following the first +# line indicate the filter rules. +# + +# This is a complete entry for 'nochap' realm. It allows to send very +# basic attributes to the home server. Note that there is no Fall-Through +# entry so that no DEFAULT entry will be used. Only the listed attributes +# will be sent in the packet, all other attributes will be filtered out. +# +#nochap +# User-Name =* ANY, +# User-Password =* ANY, +# NAS-IP-Address =* ANY, +# NAS-Identifier =* ANY + +# The entry for the 'brokenas' realm removes the attribute NAS-Port-Type +# if its value is different from 'Ethernet'. Then the default rules are +# applied. +# +#brokenas +# NAS-Port-Type == Ethernet +# Fall-Through = Yes + +# The rest of this file contains the DEFAULT entry. +# DEFAULT matches with all realm names. + +DEFAULT + User-Name =* ANY, + User-Password =* ANY, + CHAP-Password =* ANY, + CHAP-Challenge =* ANY, + MS-CHAP-Challenge =* ANY, + MS-CHAP-Response =* ANY, + EAP-Message =* ANY, + Message-Authenticator =* ANY, + State =* ANY, + NAS-IP-Address =* ANY, + NAS-Identifier =* ANY, + Operator-Name =* ANY, + Calling-Station-Id =* ANY, + Called-Station-Id =* ANY, + Operator-Name =* ANY, + Chargeable-User-Identity =* ANY, + Proxy-State =* ANY diff --git a/raddb/mods-config/files/accounting b/raddb/mods-config/files/accounting new file mode 100644 index 0000000..eaf952a --- /dev/null +++ b/raddb/mods-config/files/accounting @@ -0,0 +1,27 @@ +# +# $Id$ +# +# This is like the 'users' file, but it is processed only for +# accounting packets. +# + +# Select between different accounting methods based for example on the +# Realm, the Huntgroup-Name or any combinaison of the attribute/value +# pairs contained in an accounting packet. +# +# You will need to add an "Acct-Type foo {...}" subsection to the +# main "accounting" section in order for these sample configurations +# to work. +# +#DEFAULT Realm == "foo.net", Acct-Type := foo +# +#DEFAULT Huntgroup-Name == "wifi", Acct-Type := wifi +# +#DEFAULT Client-IP-Address == 10.0.0.1, Acct-Type := other +# +#DEFAULT Acct-Status-Type == Start, Acct-Type := start + +# Replace the User-Name with the Stripped-User-Name, if it exists. +# +#DEFAULT +# User-Name := "%{%{Stripped-User-Name}:-%{User-Name}}" diff --git a/raddb/mods-config/files/authorize b/raddb/mods-config/files/authorize new file mode 100644 index 0000000..ddf805f --- /dev/null +++ b/raddb/mods-config/files/authorize @@ -0,0 +1,206 @@ +# +# Configuration file for the rlm_files module. +# Please see rlm_files(5) manpage for more information. +# +# This file contains authentication security and configuration +# information for each user. Accounting requests are NOT processed +# through this file. Instead, see 'accounting', in this directory. +# +# The first field is the user's name and can be up to +# 253 characters in length. This is followed (on the same line) with +# the list of authentication requirements for that user. This can +# include password, comm server name, comm server port number, protocol +# type (perhaps set by the "hints" file), and huntgroup name (set by +# the "huntgroups" file). +# +# If you are not sure why a particular reply is being sent by the +# server, then run the server in debugging mode (radiusd -X), and +# you will see which entries in this file are matched. +# +# When an authentication request is received from the comm server, +# these values are tested. Only the first match is used unless the +# "Fall-Through" variable is set to "Yes". +# +# A special user named "DEFAULT" matches on all usernames. +# You can have several DEFAULT entries. All entries are processed +# in the order they appear in this file. The first entry that +# matches the login-request will stop processing unless you use +# the Fall-Through variable. +# +# Indented (with the tab character) lines following the first +# line indicate the configuration values to be passed back to +# the comm server to allow the initiation of a user session. +# This can include things like the PPP configuration values +# or the host to log the user onto. +# +# You can include another `users' file with `$INCLUDE users.other' + +# +# For a list of RADIUS attributes, and links to their definitions, +# see: http://www.freeradius.org/rfc/attributes.html +# +# Entries below this point are examples included in the server for +# educational purposes. They may be deleted from the deployed +# configuration without impacting the operation of the server. +# + +# +# Deny access for a specific user. Note that this entry MUST +# be before any other 'Auth-Type' attribute which results in the user +# being authenticated. +# +# Note that there is NO 'Fall-Through' attribute, so the user will not +# be given any additional resources. +# +#lameuser Auth-Type := Reject +# Reply-Message = "Your account has been disabled." + +# +# Deny access for a group of users. +# +# Note that there is NO 'Fall-Through' attribute, so the user will not +# be given any additional resources. +# +#DEFAULT Group == "disabled", Auth-Type := Reject +# Reply-Message = "Your account has been disabled." +# + +# +# This is a complete entry for "steve". Note that there is no Fall-Through +# entry so that no DEFAULT entry will be used, and the user will NOT +# get any attributes in addition to the ones listed here. +# +#steve Cleartext-Password := "testing" +# Service-Type = Framed-User, +# Framed-Protocol = PPP, +# Framed-IP-Address = 172.16.3.33, +# Framed-IP-Netmask = 255.255.255.0, +# Framed-Routing = Broadcast-Listen, +# Framed-Filter-Id = "std.ppp", +# Framed-MTU = 1500, +# Framed-Compression = Van-Jacobsen-TCP-IP + +# +# The canonical testing user which is in most of the +# examples. +# +#bob Cleartext-Password := "hello" +# Reply-Message := "Hello, %{User-Name}" +# + +# +# This is an entry for a user with a space in their name. +# Note the double quotes surrounding the name. If you have +# users with spaces in their names, you must also change +# the "filter_username" policy to allow spaces. +# +# See raddb/policy.d/filter, filter_username {} section. +# +#"John Doe" Cleartext-Password := "hello" +# Reply-Message = "Hello, %{User-Name}" + +# +# Dial user back and telnet to the default host for that port +# +#Deg Cleartext-Password := "ge55ged" +# Service-Type = Callback-Login-User, +# Login-IP-Host = 0.0.0.0, +# Callback-Number = "9,5551212", +# Login-Service = Telnet, +# Login-TCP-Port = Telnet + +# +# Another complete entry. After the user "dialbk" has logged in, the +# connection will be broken and the user will be dialed back after which +# he will get a connection to the host "timeshare1". +# +#dialbk Cleartext-Password := "callme" +# Service-Type = Callback-Login-User, +# Login-IP-Host = timeshare1, +# Login-Service = PortMaster, +# Callback-Number = "9,1-800-555-1212" + +# +# user "swilson" will only get a static IP number if he logs in with +# a framed protocol on a terminal server in Alphen (see the huntgroups file). +# +# Note that by setting "Fall-Through", other attributes will be added from +# the following DEFAULT entries +# +#swilson Service-Type == Framed-User, Huntgroup-Name == "alphen" +# Framed-IP-Address = 192.0.2.65, +# Fall-Through = Yes + +# +# If the user logs in as 'username.shell', then authenticate them +# using the default method, give them shell access, and stop processing +# the rest of the file. +# +#DEFAULT Suffix == ".shell" +# Service-Type = Login-User, +# Login-Service = Telnet, +# Login-IP-Host = your.shell.machine + + +# +# The rest of this file contains the several DEFAULT entries. +# DEFAULT entries match with all login names. +# Note that DEFAULT entries can also Fall-Through (see first entry). +# A name-value pair from a DEFAULT entry will _NEVER_ override +# an already existing name-value pair. +# + +# Sample defaults for all framed connections. +# +#DEFAULT Service-Type == Framed-User +# Framed-IP-Address = 255.255.255.254, +# Framed-MTU = 576, +# Service-Type = Framed-User, +# Fall-Through = Yes + +# +# Default for PPP: dynamic IP address, PPP mode, VJ-compression. +# NOTE: we do not use Hint = "PPP", since PPP might also be auto-detected +# by the terminal server in which case there may not be a "P" suffix. +# The terminal server sends "Framed-Protocol = PPP" for auto PPP. +# +DEFAULT Framed-Protocol == PPP + Framed-Protocol = PPP, + Framed-Compression = Van-Jacobson-TCP-IP + +# +# Default for CSLIP: dynamic IP address, SLIP mode, VJ-compression. +# +DEFAULT Hint == "CSLIP" + Framed-Protocol = SLIP, + Framed-Compression = Van-Jacobson-TCP-IP + +# +# Default for SLIP: dynamic IP address, SLIP mode. +# +DEFAULT Hint == "SLIP" + Framed-Protocol = SLIP + +# +# Last default: rlogin to our main server. +# +#DEFAULT +# Service-Type = Login-User, +# Login-Service = Rlogin, +# Login-IP-Host = shellbox.ispdomain.com + +# # +# # Last default: shell on the local terminal server. +# # +# DEFAULT +# Service-Type = Administrative-User + + +# On no match, the user is denied access. + + +######################################################### +# You should add test accounts to the TOP of this file! # +# See the example user "bob" above. # +######################################################### + diff --git a/raddb/mods-config/files/dhcp b/raddb/mods-config/files/dhcp new file mode 100644 index 0000000..04f37b5 --- /dev/null +++ b/raddb/mods-config/files/dhcp @@ -0,0 +1,153 @@ +# +# This configuration file that may be used by multiple instances of rlm_files +# to set reply and control options for defining DHCP replies. +# +# The content of this file is all made up and needs to be set appropriate to +# the network being served. +# + +############################################ +# Global and network-specific parameters # +############################################ + +# +# Note: This section is matched by calling the dhcp_network instance of the +# files module. +# + + +# +# Default options that can be overridden by subsequent matches. +# +network + DHCP-Domain-Name-Server := 192.0.1.100, + DHCP-Domain-Name-Server += 192.0.1.101, + DHCP-Time-Server := 192.0.1.200, + DHCP-Domain-Name := "example.org", + DHCP-IP-Address-Lease-Time := 7200, + Fall-Through := yes + + +# +# The following examples set options specific to the Layer 2 network, matched +# on whether the internal attribute DHCP-Network-Subnet (that acts as a +# network identifier) is within the indicated range. This is equivalent to a +# "shared-network" or "multinet" configuration (i.e. one that is possibly +# composed of multiple subnets) as defined by some other DHCP servers. +# + +# +# Here is an example for a network containing a single IP subnet. We can set +# the network-specific options *and* we directly set the DHCP-Subnet-Mask, +# DHCP-Router-Address and DHCP-Broadcast-Address since it is a common reply +# parameter for all DHCP requests originating from this network. +# +# The use of the ^= "prepend" operator for setting DHCP-Domain-Name-Server +# results in this new value being inserted at the start of the list, meaning +# this will become the first DNS server presented in the reply. +# +# Note: If the architecture has only a single subnet for each Layer 2 network +# then by placing all subnet-related options here we can avoid calling the +# dhcp_subnet policy after IP allocation. +# +network DHCP-Network-Subnet < 10.20.0.0/16, Pool-Name := "smalldept" + DHCP-IP-Address-Lease-Time := 3600, + DHCP-Domain-Name := "smalldept.example.org", + DHCP-Subnet-Mask := 255.255.0.0, + DHCP-Router-Address := 10.20.0.1, + DHCP-Domain-Name-Server ^= 10.20.0.2, + DHCP-Broadcast-Address := 10.20.255.255 + +# +# Here is an example for a network that consists of multiple IP subnets, each +# of which is valid for a DHCP request originating from the network. We set +# the Pool-Name parameter to identify a single pool that contains the IP +# address within each subnet, any of which is suitable. +# +# We set the options that are common to the network but we defer the setting +# of DHCP-Subnet-Mask, DHCP-Router-Address and DHCP-Broadcast-Address until an +# address has been allocated. Only then do we know which subnet parameters are +# required. See the next section. +# +network DHCP-Network-Subnet < 10.30.0.0/16, Pool-Name := "bigdept" + DHCP-Domain-Name := "bigdept.example.org" + + +# +# Here is an example for a network that has a dedicated pool for admin staff +# and a seperate pool for everything else. +# +network DHCP-Network-Subnet < 192.0.2.0/24, DHCP-Group-Name == "admin", Pool-Name := "admin-only" +network DHCP-Network-Subnet < 192.0.2.0/24, Pool-Name := "general" + + +################################ +# Subnet-specific parameters # +################################ + +# +# Note: This section is matched by calling the dhcp_subnet policy which sets +# DHCP-Network-Subnet to the allocated IP address of the device and then +# calls the dhcp_subnet instance of the files module. +# +# Layer 2 networks many contain multiple subnets, each with their own gateway. +# We call this section *after* the allocation of an IP address (e.g. from a +# single pool containing addresses within multiple equally-valid subnets for +# the network) so that we then know which subnet-specific parameters to +# return. +# + +# +# Subnet-specific options, matched on whether the allocated IP address is +# within the indicated range. +# +subnet DHCP-Network-Subnet < 10.30.10.0/24 + DHCP-Subnet-Mask := 255.255.255.0, + DHCP-Router-Address := 10.30.10.1, + DHCP-Broadcast-Address := 10.30.10.255 + +subnet DHCP-Network-Subnet < 10.30.20.0/24 + DHCP-Subnet-Mask := 255.255.255.0, + DHCP-Router-Address := 10.30.20.1, + DHCP-Broadcast-Address := 10.30.20.255 + + +############################### +# Group-specific parameters # +############################### + +# +# Note: This section is matched by calling the dhcp_group_options policy. +# +# It should be called *after* defining the device's group memberships in +# DHCP-Group-Name request attributes. In the default dhcp virtual server this +# is demonstrated with the help of the dhcp_group_membership instance of the +# passwd module. +# + +# +# Group-specific options, keyed by DHCP-Group-Name +# +group1 + DHCP-Server-Host-Name := "terminal-booter.example.org", + DHCP-Boot-Filename := "bootfile.pxe" + + +############################## +# Host-specific parameters # +############################## + +# +# Note: This section is matched by calling the dhcp_hosts instance of the +# files module. +# + +# +# Host-specific options, keyed by DHCP-Client-Hardware-Address +# +host-00:10:20:30:40:50 + DHCP-Boot-Filename := "customboot.pxe" + +host-10:90:80:70:aa:bb + DHCP-X-Window-Font-Server := 10.20.1.10, + DHCP-Impress-Server := 10.20.1.20 diff --git a/raddb/mods-config/files/pre-proxy b/raddb/mods-config/files/pre-proxy new file mode 100644 index 0000000..7292e23 --- /dev/null +++ b/raddb/mods-config/files/pre-proxy @@ -0,0 +1,31 @@ +# +# Configuration file for the rlm_files module. +# Please see rlm_files(5) manpage for more information. +# +# $Id$ +# +# This file is similar to the "users" file. The check items +# are compared against the request, but the "reply" items are +# used to update the proxied packet, not the reply to the NAS. +# +# You can use this file to re-write requests which are about to +# be sent to a home server. +# + +# +# Requests destinated to realm "extisp" are sent to a RADIUS +# home server hosted by an other company which doesn't know about +# the IP addresses of our NASes. Therefore we replace the value of +# the NAS-IP-Address attribute by a unique value we communicated +# to them. +# +#DEFAULT Realm == "extisp" +# NAS-IP-Address := 10.1.2.3 + +# +# For all proxied packets, set the User-Name in the proxied packet +# to the Stripped-User-Name, if it exists. If not, set it to the +# User-Name from the original request. +# +#DEFAULT +# User-Name := `%{%{Stripped-User-Name}:-%{User-Name}}` diff --git a/raddb/mods-config/perl/example.pl b/raddb/mods-config/perl/example.pl new file mode 100644 index 0000000..f00b17b --- /dev/null +++ b/raddb/mods-config/perl/example.pl @@ -0,0 +1,230 @@ + +# +# 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; either version 2 of the License, or +# (at your option) any later version. +# +# 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA +# +# Copyright 2002 The FreeRADIUS server project +# Copyright 2002 Boian Jordanov <bjordanov@orbitel.bg> +# + +# +# Example code for use with rlm_perl +# +# You can use every module that comes with your perl distribution! +# +# If you are using DBI and do some queries to DB, please be sure to +# use the CLONE function to initialize the DBI connection to DB. +# + +use strict; +use warnings; + +# use ... +use Data::Dumper; + +# Bring the global hashes into the package scope +our (%RAD_REQUEST, %RAD_REPLY, %RAD_CHECK, %RAD_STATE, %RAD_PERLCONF); + +# This is hash wich hold original request from radius +#my %RAD_REQUEST; +# In this hash you add values that will be returned to NAS. +#my %RAD_REPLY; +#This is for check items +#my %RAD_CHECK; +# This is the session-sate +#my %RAD_STATE; +# This is configuration items from "config" perl module configuration section +#my %RAD_PERLCONF; + +# Multi-value attributes are mapped to perl arrayrefs. +# +# update request { +# Filter-Id := 'foo' +# Filter-Id += 'bar' +# } +# +# This results to the following entry in %RAD_REQUEST: +# +# $RAD_REQUEST{'Filter-Id'} = [ 'foo', 'bar' ]; +# +# Likewise, you can assign an arrayref to return multi-value attributes + +# +# This the remapping of return values +# +use constant { + RLM_MODULE_REJECT => 0, # immediately reject the request + RLM_MODULE_OK => 2, # the module is OK, continue + RLM_MODULE_HANDLED => 3, # the module handled the request, so stop + RLM_MODULE_INVALID => 4, # the module considers the request invalid + RLM_MODULE_USERLOCK => 5, # reject the request (user is locked out) + RLM_MODULE_NOTFOUND => 6, # user not found + RLM_MODULE_NOOP => 7, # module succeeded without doing anything + RLM_MODULE_UPDATED => 8, # OK (pairs modified) + RLM_MODULE_NUMCODES => 9 # How many return codes there are +}; + +# Same as src/include/log.h +use constant { + L_AUTH => 2, # Authentication message + L_INFO => 3, # Informational message + L_ERR => 4, # Error message + L_WARN => 5, # Warning + L_PROXY => 6, # Proxy messages + L_ACCT => 7, # Accounting messages + L_DBG => 16, # Only displayed when debugging is enabled + L_DBG_WARN => 17, # Warning only displayed when debugging is enabled + L_DBG_ERR => 18, # Error only displayed when debugging is enabled + L_DBG_WARN_REQ => 19, # Less severe warning only displayed when debugging is enabled + L_DBG_ERR_REQ => 20, # Less severe error only displayed when debugging is enabled +}; + +# Global variables can persist across different calls to the module. +# +# +# { +# my %static_global_hash = (); +# +# sub post_auth { +# ... +# } +# ... +# } + + +# Function to handle authorize +sub authorize { + # For debugging purposes only +# &log_request_attributes; + + # Here's where your authorization code comes + # You can call another function from here: + &test_call; + + return RLM_MODULE_OK; +} + +# Function to handle authenticate +sub authenticate { + # For debugging purposes only +# &log_request_attributes; + + if ($RAD_REQUEST{'User-Name'} =~ /^baduser/i) { + # Reject user and tell him why + $RAD_REPLY{'Reply-Message'} = "Denied access by rlm_perl function"; + return RLM_MODULE_REJECT; + } else { + # Accept user and set some attribute + if (&radiusd::xlat("%{client:group}") eq 'UltraAllInclusive') { + # User called from NAS with unlim plan set, set higher limits + $RAD_REPLY{'h323-credit-amount'} = "1000000"; + } else { + $RAD_REPLY{'h323-credit-amount'} = "100"; + } + return RLM_MODULE_OK; + } +} + +# Function to handle preacct +sub preacct { + # For debugging purposes only +# &log_request_attributes; + + return RLM_MODULE_OK; +} + +# Function to handle accounting +sub accounting { + # For debugging purposes only +# &log_request_attributes; + + # You can call another subroutine from here + &test_call; + + return RLM_MODULE_OK; +} + +# Function to handle checksimul +sub checksimul { + # For debugging purposes only +# &log_request_attributes; + + return RLM_MODULE_OK; +} + +# Function to handle pre_proxy +sub pre_proxy { + # For debugging purposes only +# &log_request_attributes; + + return RLM_MODULE_OK; +} + +# Function to handle post_proxy +sub post_proxy { + # For debugging purposes only +# &log_request_attributes; + + return RLM_MODULE_OK; +} + +# Function to handle post_auth +sub post_auth { + # For debugging purposes only +# &log_request_attributes; + + return RLM_MODULE_OK; +} + +# Function to handle xlat +sub xlat { + # For debugging purposes only +# &log_request_attributes; + + # Loads some external perl and evaluate it + my ($filename,$a,$b,$c,$d) = @_; + &radiusd::radlog(L_DBG, "From xlat $filename "); + &radiusd::radlog(L_DBG,"From xlat $a $b $c $d "); + local *FH; + open FH, $filename or die "open '$filename' $!"; + local($/) = undef; + my $sub = <FH>; + close FH; + my $eval = qq{ sub handler{ $sub;} }; + eval $eval; + eval {main->handler;}; +} + +# Function to handle detach +sub detach { + # For debugging purposes only +# &log_request_attributes; +} + +# +# Some functions that can be called from other functions +# + +sub test_call { + # Some code goes here +} + +sub log_request_attributes { + # This shouldn't be done in production environments! + # This is only meant for debugging! + for (keys %RAD_REQUEST) { + &radiusd::radlog(L_DBG, "RAD_REQUEST: $_ = $RAD_REQUEST{$_}"); + } +} + diff --git a/raddb/mods-config/preprocess/hints b/raddb/mods-config/preprocess/hints new file mode 100644 index 0000000..84d4d78 --- /dev/null +++ b/raddb/mods-config/preprocess/hints @@ -0,0 +1,86 @@ +# +# hints +# +# The hints file. This file is used to match +# a request, and then add attributes to it. This +# process allows a user to login as "bob.ppp" (for example), +# and receive a PPP connection, even if the NAS doesn't +# ask for PPP. The "hints" file is used to match the +# ".ppp" portion of the username, and to add a set of +# "user requested PPP" attributes to the request. +# +# Matching can take place with the the Prefix and Suffix +# attributes, just like in the "users" file. +# These attributes operate ONLY on the username, though. +# +# Note that the attributes that are set for each entry are +# NOT added to the reply attributes passed back to the NAS. +# Instead they are added to the list of attributes in the +# request that has been SENT by the NAS. +# +# This extra information can be used in the users file to +# match on. Usually this is done in the DEFAULT entries, +# of which there can be more than one. +# +# In addition a matching entry can transform a username +# for authentication purposes if the "Strip-User-Name" +# variable is set to Yes in an entry (default is Yes). +# +# A special non-protocol name-value pair called "Hint" +# can be set to match on in the "users" file. +# +# As with the "users" file, the first entry that matches the +# incoming request will cause the server to stop looking for +# more hints. If the "Fall-Through" attribute is set to +# "Yes" in an entry then the server will not stop, but +# continue to process further hints from the file. Matches +# on subsequent hints will be against the altered request +# from the previous hints, not against the original request. +# +# The following is how most dial-up ISPs want to set this up. +# +# Version: $Id$ +# + + +DEFAULT Suffix == ".ppp", Strip-User-Name = Yes + Hint = "PPP", + Service-Type = Framed-User, + Framed-Protocol = PPP + +DEFAULT Suffix == ".slip", Strip-User-Name = Yes + Hint = "SLIP", + Service-Type = Framed-User, + Framed-Protocol = SLIP + +DEFAULT Suffix == ".cslip", Strip-User-Name = Yes + Hint = "CSLIP", + Service-Type = Framed-User, + Framed-Protocol = SLIP, + Framed-Compression = Van-Jacobson-TCP-IP + +###################################################################### +# +# These entries are old, and commented out by default. +# They confuse too many people when "Peter" logs in, and the +# server thinks that the user "eter" is asking for PPP. +# +#DEFAULT Prefix == "U", Strip-User-Name = No +# Hint = "UUCP" + +#DEFAULT Prefix == "P", Strip-User-Name = Yes +# Hint = "PPP", +# Service-Type = Framed-User, +# Framed-Protocol = PPP + +#DEFAULT Prefix == "S", Strip-User-Name = Yes +# Hint = "SLIP", +# Service-Type = Framed-User, +# Framed-Protocol = SLIP + +#DEFAULT Prefix == "C", Strip-User-Name = Yes +# Hint = "CSLIP", +# Service-Type = Framed-User, +# Framed-Protocol = SLIP, +# Framed-Compression = Van-Jacobson-TCP-IP + diff --git a/raddb/mods-config/preprocess/huntgroups b/raddb/mods-config/preprocess/huntgroups new file mode 100644 index 0000000..da28dba --- /dev/null +++ b/raddb/mods-config/preprocess/huntgroups @@ -0,0 +1,43 @@ +# +# huntgroups This file defines the `huntgroups' that you have. A +# huntgroup is defined by specifying the IP address of +# the NAS and possibly a port. +# +# Matching is done while RADIUS scans the user file; if it +# includes the selection criteria "Huntgroup-Name == XXX" +# the huntgroup is looked up in this file to see if it +# matches. There can be multiple definitions of the same +# huntgroup; the first one that matches will be used. +# +# This file can also be used to define restricted access +# to certain huntgroups. The second and following lines +# define the access restrictions (based on username and +# UNIX usergroup) for the huntgroup. +# + +# +# Our POP in Alphen a/d Rijn has 3 terminal servers. Create a Huntgroup-Name +# called Alphen that matches on all three terminal servers. +# +#alphen NAS-IP-Address == 192.0.2.5 +#alphen NAS-IP-Address == 192.0.2.6 +#alphen NAS-IP-Address == 192.0.2.7 + +# +# The POP in Delft consists of only one terminal server. +# +#delft NAS-IP-Address == 198.51.100.5 + +# +# Port 0 on the first terminal server in Alphen are connected to +# a huntgroup that is for business users only. Note that only one +# of the username or groupname has to match to get access (OR/OR). +# +# Note that this huntgroup is a subset of the "alphen" huntgroup. +# +#business NAS-IP-Address == 198.51.100.5, NAS-Port-Id == 0 +# User-Name == rogerl, +# User-Name == henks, +# Group == business, +# Group == staff + diff --git a/raddb/mods-config/realm/freeradius-naptr-to-home-server.sh b/raddb/mods-config/realm/freeradius-naptr-to-home-server.sh new file mode 100755 index 0000000..66388d3 --- /dev/null +++ b/raddb/mods-config/realm/freeradius-naptr-to-home-server.sh @@ -0,0 +1,161 @@ +#!/bin/sh + +# This script looks up radsec srv records in DNS for the one realm +# given as argument, and creates a home_server template based on the +# information found. +# +# It currently ignores weight markers, but does sort servers on +# priority marker, lowest number first. For host command this is +# column 5, for dig it is column 1. +# +# It then tells FreeRADIUS (via radmin) that +# there is a new home server. +# +# Note that in order for it to work, you need to have the +# "control-socket" enabled. + +usage() { + echo "Usage: ${0} [OPTIONS] <realm> <optional NAPTR tag>" + echo " -d RADIUS_DIR Set radius directory" + echo " -t test (skip running radmin)" + exit 1 +} + +test -n "${1}" || usage + +RADDB=/etc/raddb +RADMIN=y + +# +# Parse command-line options +# +while [ `echo "$1" | cut -c 1` = "-" ] +do + case "$1" in + -d) + RADDB=$2 + shift;shift + ;; + -t) + RADMIN= + shift + ;; + + *) + usage + ;; + esac +done + +test -n "${2}" && NAPTRTAG="${2}" || NAPTRTAG="x-eduroam:radius.tls" + +DIGCMD=$(command -v dig) +HOSTCMD=$(command -v host) +PRINTCMD=$(command -v printf) + +# +# These validations prevent rogue DNS records from pwning your RADIUS installation. +# +# See https://github.com/radsecproxy/radsecproxy/security/advisories/GHSA-56gw-9rj9-55rc +# and https://www.usenix.org/conference/usenixsecurity21/presentation/jeitner +# +# The contents of these validation routines should NOT be changed without a deep understanding +# of DNS! +# +validate_host() { + echo ${@} | tr -d '\n\t\r' | grep -E '^[_0-9a-zA-Z][-._0-9a-zA-Z]*$' +} + +validate_port() { + echo ${@} | tr -d '\n\t\r' | grep -E '^[0-9]+$' +} + +dig_it_srv() { + ${DIGCMD} +short srv $SRV_HOST | sort -n -k1 | + while read line; do + set $line + PORT=$(validate_port $3) + HOST=$(validate_host $4) + if [ -n "${HOST}" ] && [ -n "${PORT}" ]; then + $PRINTCMD "\tipaddr = ${HOST%.}\n\tport = ${PORT}\n" + fi + done +} + +dig_it_naptr() { + ${DIGCMD} +short naptr "${REALM}" | grep $NAPTRTAG | sort -n -k1 | + while read line; do + set $line + TYPE=$3 + HOST=$(validate_host $6) + if ( [ "$TYPE" = "\"s\"" ] || [ "$TYPE" = "\"S\"" ] ) && [ -n "${HOST}" ]; then + SRV_HOST=${HOST%.} + dig_it_srv + fi + done +} + +host_it_srv() { + ${HOSTCMD} -t srv $SRV_HOST | sort -n -k5 | + while read line; do + set $line + PORT=$(validate_port $7) + HOST=$(validate_host $8) + if [ -n "${HOST}" ] && [ -n "${PORT}" ]; then + $PRINTCMD "\tipaddr ${HOST%.}:${PORT}\n" + fi + done +} + +host_it_naptr() { + ${HOSTCMD} -t naptr "${REALM}" | grep $NAPTRTAG | sort -n -k5 | + while read line; do + set $line + TYPE=$7 + HOST=$(validate_host ${10}) + if ( [ "$TYPE" = "\"s\"" ] || [ "$TYPE" = "\"S\"" ] ) && [ -n "${HOST}" ]; then + SRV_HOST=${HOST%.} + host_it_srv + fi + done +} + +REALM=$(validate_host ${1}) +if [ -z "${REALM}" ]; then + echo "realm \"${1}\" failed validation" >&2 + usage +fi + +if [ -x "${DIGCMD}" ]; then + SERVERS=$(dig_it_naptr) + +elif [ -x "${HOSTCMD}" ]; then + SERVERS=$(host_it_naptr) + +else + echo "${0} requires either \"dig\" or \"host\" command." >&2 + exit 1 +fi + +if [ ! -n "${SERVERS}" ]; then + echo "No servers found" >&2 + exit 1 +fi + +# +# Just testing - don't do anything else. +# +if [ -z "${RADMIN}" ]; then + $PRINTCMD "home_server ${REALM} {\n${SERVERS}\n\t\$INCLUDE tls.conf\n}\n" + exit 0 +fi + +# +# Print out the template, and include the site-local tls.conf file. +# +$PRINTCMD "home_server ${REALM} {\n${SERVERS}\n\t\$INCLUDE tls.conf\n}\n" > $RADDB/home_servers/$1 + +# +# @todo - use ${prefix} or some such thing to find radmin. +# +/usr/sbin/radmin -e "add home_server file $RADDB/home_servers/$1" diff --git a/raddb/mods-config/sql/counter/mysql/dailycounter.conf b/raddb/mods-config/sql/counter/mysql/dailycounter.conf new file mode 100644 index 0000000..67c7f35 --- /dev/null +++ b/raddb/mods-config/sql/counter/mysql/dailycounter.conf @@ -0,0 +1,46 @@ +# +# This query properly handles calls that span from the +# previous reset period into the current period but +# involves more work for the SQL server than those +# below +# +query = "\ + SELECT SUM(acctsessiontime - GREATEST((%%b - UNIX_TIMESTAMP(acctstarttime)), 0)) \ + FROM radacct \ + WHERE username = '%{${key}}' \ + AND UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%%b'" + +# +# This query ignores calls that started in a previous +# reset period and continue into into this one. But it +# is a little easier on the SQL server +# +#query = "\ +# SELECT SUM(acctsessiontime) \ +# FROM radacct \ +# WHERE username = '%{${key}}' \ +# AND acctstarttime > FROM_UNIXTIME('%%b')" + +# +# This query is the same as above, but demonstrates an +# additional counter parameter '%%e' which is the +# timestamp for the end of the period +# +#query = "\ +# SELECT SUM(acctsessiontime) \ +# FROM radacct \ +# WHERE username = '%{${key}}' \ +# AND acctstarttime BETWEEN FROM_UNIXTIME('%%b') AND FROM_UNIXTIME('%%e')" + +# +# This query allows retrieving the entries based on a +# period that resets on a particular day of the month. +# +#reset_day = 21 +#query = "\ +# SELECT SUM(acctsessiontime) FROM radacct WHERE username = '%{${key}}' AND \ +# IF (DAY(CURDATE()) >= ${reset_day}, \ +# acctstarttime > DATE(DATE_FORMAT(NOW(), '%Y-%m-${reset_day}')), \ +# acctstarttime > DATE(DATE_FORMAT(NOW() - INTERVAL 1 MONTH, '%Y-%m-${reset_day}')) \ +# )" +# diff --git a/raddb/mods-config/sql/counter/mysql/expire_on_login.conf b/raddb/mods-config/sql/counter/mysql/expire_on_login.conf new file mode 100644 index 0000000..73e2ca3 --- /dev/null +++ b/raddb/mods-config/sql/counter/mysql/expire_on_login.conf @@ -0,0 +1,6 @@ +query = "\ + SELECT IFNULL( MAX(TIME_TO_SEC(TIMEDIFF(NOW(), acctstarttime))),0) \ + FROM radacct \ + WHERE UserName='%{${key}}' \ + ORDER BY acctstarttime \ + LIMIT 1;" diff --git a/raddb/mods-config/sql/counter/mysql/monthlycounter.conf b/raddb/mods-config/sql/counter/mysql/monthlycounter.conf new file mode 100644 index 0000000..8999765 --- /dev/null +++ b/raddb/mods-config/sql/counter/mysql/monthlycounter.conf @@ -0,0 +1,34 @@ +# +# This query properly handles calls that span from the +# previous reset period into the current period but +# involves more work for the SQL server than those +# below +# +query = "\ + SELECT SUM(acctsessiontime - GREATEST((%%b - UNIX_TIMESTAMP(acctstarttime)), 0)) \ + FROM radacct \ + WHERE username='%{${key}}' \ + AND UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%%b'" + +# +# This query ignores calls that started in a previous +# reset period and continue into into this one. But it +# is a little easier on the SQL server +# +#query = "\ +# SELECT SUM(acctsessiontime) \ +# FROM radacct\ +# WHERE username='%{${key}}' \ +# AND acctstarttime > FROM_UNIXTIME('%%b')" + +# +# This query is the same as above, but demonstrates an +# additional counter parameter '%%e' which is the +# timestamp for the end of the period +# +#query = "\ +# SELECT SUM(acctsessiontime) \ +# FROM radacct \ +# WHERE username='%{${key}}' \ +# AND acctstarttime BETWEEN FROM_UNIXTIME('%%b') \ +# AND FROM_UNIXTIME('%%e')" diff --git a/raddb/mods-config/sql/counter/mysql/noresetcounter.conf b/raddb/mods-config/sql/counter/mysql/noresetcounter.conf new file mode 100644 index 0000000..abcb21b --- /dev/null +++ b/raddb/mods-config/sql/counter/mysql/noresetcounter.conf @@ -0,0 +1,4 @@ +query = "\ + SELECT IFNULL(SUM(AcctSessionTime),0) \ + FROM radacct \ + WHERE UserName='%{${key}}'" diff --git a/raddb/mods-config/sql/counter/mysql/weeklycounter.conf b/raddb/mods-config/sql/counter/mysql/weeklycounter.conf new file mode 100644 index 0000000..bf8a4c4 --- /dev/null +++ b/raddb/mods-config/sql/counter/mysql/weeklycounter.conf @@ -0,0 +1,11 @@ +# +# This query properly handles calls that span from the +# previous reset period into the current period but +# involves more work for the SQL server than those +# below +# +query = "\ + SELECT SUM(acctsessiontime - GREATEST((%%b - UNIX_TIMESTAMP(acctstarttime)), 0)) \ + FROM radacct \ + WHERE username = '%{${key}}' \ + AND UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%%b'" diff --git a/raddb/mods-config/sql/counter/postgresql/dailycounter.conf b/raddb/mods-config/sql/counter/postgresql/dailycounter.conf new file mode 100644 index 0000000..1e2f7fa --- /dev/null +++ b/raddb/mods-config/sql/counter/postgresql/dailycounter.conf @@ -0,0 +1,34 @@ +# +# This query properly handles calls that span from the +# previous reset period into the current period but +# involves more work for the SQL server than those +# below +# +query = "\ + SELECT SUM(AcctSessionTime - GREATEST((%%b - EXTRACT(epoch FROM AcctStartTime)), 0)) \ + FROM radacct \ + WHERE UserName='%{${key}}' \ + AND EXTRACT(epoch FROM AcctStartTime) + AcctSessionTime > '%%b'" + +# +# This query ignores calls that started in a previous +# reset period and continue into into this one. But it +# is a little easier on the SQL server +# +#query = "\ +# SELECT SUM(AcctSessionTime) \ +# FROM radacct \ +# WHERE UserName='%{${key}}' \ +# AND EXTRACT(epoch FROM AcctStartTime) > '%%b'" + +# +# This query is the same as above, but demonstrates an +# additional counter parameter '%%e' which is the +# timestamp for the end of the period +# +#query = "\ +# SELECT SUM(AcctSessionTime) \ +# FROM radacct \ +# WHERE UserName='%{${key}}' \ +# AND EXTRACT(epoch FROM AcctStartTime) BETWEEN '%%b' \ +# AND '%%e'" diff --git a/raddb/mods-config/sql/counter/postgresql/expire_on_login.conf b/raddb/mods-config/sql/counter/postgresql/expire_on_login.conf new file mode 100644 index 0000000..6ec4c4e --- /dev/null +++ b/raddb/mods-config/sql/counter/postgresql/expire_on_login.conf @@ -0,0 +1,6 @@ +query = "\ + SELECT EXTRACT(EPOCH FROM (NOW() - acctstarttime)) \ + FROM radacct \ + WHERE UserName='%{${key}}' \ + ORDER BY acctstarttime \ + LIMIT 1;" diff --git a/raddb/mods-config/sql/counter/postgresql/monthlycounter.conf b/raddb/mods-config/sql/counter/postgresql/monthlycounter.conf new file mode 100644 index 0000000..cdaf83a --- /dev/null +++ b/raddb/mods-config/sql/counter/postgresql/monthlycounter.conf @@ -0,0 +1,31 @@ +# This query properly handles calls that span from the +# previous reset period into the current period but +# involves more work for the SQL server than those +# below +query = "\ + SELECT SUM(AcctSessionTime - GREATEST((%%b - EXTRACT(epoch FROM AcctStartTime)), 0)) \ + FROM radacct \ + WHERE UserName='%{${key}}' \ + AND EXTRACT(epoch FROM AcctStartTime) + AcctSessionTime > '%%b'" + +# +# This query ignores calls that started in a previous +# reset period and continue into into this one. But it +# is a little easier on the SQL server +# +#query = "\ +# SELECT SUM(AcctSessionTime) \ +# FROM radacct \ +# WHERE UserName='%{${key}}' \ +# AND EXTRACT(epoch FROM AcctStartTime) > '%%b'" + +# +# This query is the same as above, but demonstrates an +# additional counter parameter '%%e' which is the +# timestamp for the end of the period +# +#query = "\ +# SELECT SUM(AcctSessionTime) \ +# FROM radacct \ +# WHERE UserName='%{${key}}' \ +# AND EXTRACT(epoch FROM AcctStartTime) BETWEEN '%%b' AND '%%e'" diff --git a/raddb/mods-config/sql/counter/postgresql/noresetcounter.conf b/raddb/mods-config/sql/counter/postgresql/noresetcounter.conf new file mode 100644 index 0000000..ac5182e --- /dev/null +++ b/raddb/mods-config/sql/counter/postgresql/noresetcounter.conf @@ -0,0 +1,4 @@ +query = "\ + SELECT SUM(AcctSessionTime) \ + FROM radacct \ + WHERE UserName='%{${key}}'" diff --git a/raddb/mods-config/sql/counter/postgresql/weeklycounter.conf b/raddb/mods-config/sql/counter/postgresql/weeklycounter.conf new file mode 100644 index 0000000..0d809c1 --- /dev/null +++ b/raddb/mods-config/sql/counter/postgresql/weeklycounter.conf @@ -0,0 +1,12 @@ +# +# This query properly handles calls that span from the +# previous reset period into the current period but +# involves more work for the SQL server than those +# below +# +query = "\ + SELECT SUM(AcctSessionTime - GREATEST((%%b - EXTRACT(epoch FROM AcctStartTime)), 0)) \ + FROM radacct \ + WHERE UserName='%{${key}}' \ + AND EXTRACT(epoch FROM AcctStartTime) + AcctSessionTime > '%%b'" + diff --git a/raddb/mods-config/sql/counter/sqlite/dailycounter.conf b/raddb/mods-config/sql/counter/sqlite/dailycounter.conf new file mode 100644 index 0000000..9a2ec38 --- /dev/null +++ b/raddb/mods-config/sql/counter/sqlite/dailycounter.conf @@ -0,0 +1,33 @@ +# +# This query properly handles calls that span from the +# previous reset period into the current period but +# involves more work for the SQL server than those +# below +# +query = "\ + SELECT SUM(acctsessiontime - GREATEST((%%b - strftime('%%s', acctstarttime)), 0)) \ + FROM radacct \ + WHERE username = '%{${key}}' \ + AND (strftime('%%s', acctstarttime) + acctsessiontime) > %%b" + +# +# This query ignores calls that started in a previous +# reset period and continue into into this one. But it +# is a little easier on the SQL server +# +#query = "\ +# SELECT SUM(acctsessiontime) \ +# FROM radacct \ +# WHERE \username = '%{${key}}' \ +# AND acctstarttime > %%b" + +# +# This query is the same as above, but demonstrates an +# additional counter parameter '%%e' which is the +# timestamp for the end of the period +# +#query = "\ +# SELECT SUM(acctsessiontime) FROM radacct \ +# WHERE username = '%{${key}}' \ +# AND acctstarttime BETWEEN %%b \ +# AND %%e" diff --git a/raddb/mods-config/sql/counter/sqlite/expire_on_login.conf b/raddb/mods-config/sql/counter/sqlite/expire_on_login.conf new file mode 100644 index 0000000..f4e95a5 --- /dev/null +++ b/raddb/mods-config/sql/counter/sqlite/expire_on_login.conf @@ -0,0 +1,6 @@ +query = "\ + SELECT GREATEST(strftime('%%s', NOW()) - strftime('%%s', acctstarttime), 0) AS expires \ + FROM radacct \ + WHERE username = '%{${key}}' \ + ORDER BY acctstarttime \ + LIMIT 1;" diff --git a/raddb/mods-config/sql/counter/sqlite/monthlycounter.conf b/raddb/mods-config/sql/counter/sqlite/monthlycounter.conf new file mode 100644 index 0000000..5262097 --- /dev/null +++ b/raddb/mods-config/sql/counter/sqlite/monthlycounter.conf @@ -0,0 +1,34 @@ +# +# This query properly handles calls that span from the +# previous reset period into the current period but +# involves more work for the SQL server than those +# below +# +query = "\ + SELECT SUM(acctsessiontime - GREATEST((%%b - strftime('%%s', acctstarttime)), 0)) \ + FROM radacct \ + WHERE username = '%{${key}}' AND \ + (strftime('%%s', acctstarttime) + acctsessiontime) > %%b" + +# +# This query ignores calls that started in a previous +# reset period and continue into into this one. But it +# is a little easier on the SQL server +# +#query = "\ +# SELECT SUM(acctsessiontime) \ +# FROM radacct \ +# WHERE username = '%{${key}}' \ +# AND acctstarttime > %%b" + +# +# This query is the same as above, but demonstrates an +# additional counter parameter '%%e' which is the +# timestamp for the end of the period +# +#query = "\ +# SELECT SUM(acctsessiontime) \ +# FROM radacct \ +# WHERE username = '%{${key}}' \ +# AND acctstarttime BETWEEN %%b \ +# AND %%e" diff --git a/raddb/mods-config/sql/counter/sqlite/noresetcounter.conf b/raddb/mods-config/sql/counter/sqlite/noresetcounter.conf new file mode 100644 index 0000000..ac2d869 --- /dev/null +++ b/raddb/mods-config/sql/counter/sqlite/noresetcounter.conf @@ -0,0 +1,4 @@ +query = "\ + SELECT IFNULL(SUM(acctsessiontime),0) \ + FROM radacct \ + WHERE username = '%{${key}}'" diff --git a/raddb/mods-config/sql/counter/sqlite/weeklycounter.conf b/raddb/mods-config/sql/counter/sqlite/weeklycounter.conf new file mode 100644 index 0000000..06ce3b6 --- /dev/null +++ b/raddb/mods-config/sql/counter/sqlite/weeklycounter.conf @@ -0,0 +1,12 @@ +# +# This query properly handles calls that span from the +# previous reset period into the current period but +# involves more work for the SQL server than those +# below +# +query = "\ + SELECT SUM(acctsessiontime - GREATEST((%%b - strftime('%%s', acctstarttime)), 0)) \ + FROM radacct \ + WHERE username = '%{${key}}' \ + AND (strftime('%%s', acctstarttime) + acctsessiontime) > %%b" + diff --git a/raddb/mods-config/sql/cui/mysql/queries.conf b/raddb/mods-config/sql/cui/mysql/queries.conf new file mode 100644 index 0000000..f8f18ca --- /dev/null +++ b/raddb/mods-config/sql/cui/mysql/queries.conf @@ -0,0 +1,50 @@ +# -*- text -*- +# +# cui/mysql/queries.conf -- Queries to update a MySQL CUI table. +# +# $Id$ + +post-auth { + query = "\ + INSERT IGNORE INTO ${..cui_table} \ + (clientipaddress, callingstationid, username, cui, lastaccounting) \ + VALUES \ + ('%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}', '%{Calling-Station-Id}', \ + '%{User-Name}', '%{reply:Chargeable-User-Identity}', NULL) \ + ON DUPLICATE KEY UPDATE \ + lastaccounting='0000-00-00 00:00:00', \ + cui='%{reply:Chargeable-User-Identity}'" + +} + +accounting { + reference = "%{tolower:type.%{Acct-Status-Type}.query}" + type { + start { + query = "\ + UPDATE ${....cui_table} SET \ + lastaccounting = CURRENT_TIMESTAMP \ + WHERE clientipaddress = '%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND username = '%{User-Name}' \ + AND cui = '%{Chargeable-User-Identity}'" + } + interim-update { + query ="\ + UPDATE ${....cui_table} SET \ + lastaccounting = CURRENT_TIMESTAMP \ + WHERE clientipaddress = '%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND username = '%{User-Name}' \ + AND cui = '%{Chargeable-User-Identity}'" + } + stop { + query ="\ + DELETE FROM ${....cui_table} \ + WHERE clientipaddress = '%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND username = '%{User-Name}' \ + AND cui = '%{Chargeable-User-Identity}'" + } + } +} diff --git a/raddb/mods-config/sql/cui/mysql/schema.sql b/raddb/mods-config/sql/cui/mysql/schema.sql new file mode 100644 index 0000000..da9b2f7 --- /dev/null +++ b/raddb/mods-config/sql/cui/mysql/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE `cui` ( + `clientipaddress` varchar(46) NOT NULL default '', + `callingstationid` varchar(50) NOT NULL default '', + `username` varchar(64) NOT NULL default '', + `cui` varchar(32) NOT NULL default '', + `creationdate` timestamp NOT NULL default CURRENT_TIMESTAMP, + `lastaccounting` timestamp NOT NULL default '0000-00-00 00:00:00', + PRIMARY KEY (`username`,`clientipaddress`,`callingstationid`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/raddb/mods-config/sql/cui/postgresql/queries.conf b/raddb/mods-config/sql/cui/postgresql/queries.conf new file mode 100644 index 0000000..6c2215f --- /dev/null +++ b/raddb/mods-config/sql/cui/postgresql/queries.conf @@ -0,0 +1,47 @@ +# -*- text -*- +# +# cui/postgresql/queries.conf -- Queries to update a PostgreSQL CUI table. +# +# $Id$ + +post-auth { + query = "\ + INSERT INTO ${..cui_table} \ + (clientipaddress, callingstationid, username, cui) \ + VALUES \ + ('%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}', '%{Calling-Station-Id}', \ + '%{User-Name}', '%{reply:Chargeable-User-Identity}')" + +} + +accounting { + reference = "%{tolower:type.%{Acct-Status-Type}.query}" + type { + start { + query = "\ + UPDATE ${....cui_table} SET \ + lastaccounting = now() \ + WHERE clientipaddress = '%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND username = '%{User-Name}' \ + AND cui = '%{Chargeable-User-Identity}'" + } + interim-update { + query ="\ + UPDATE ${....cui_table} SET \ + lastaccounting = now() \ + WHERE clientipaddress = '%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND username = '%{User-Name}' \ + AND cui = '%{Chargeable-User-Identity}'" + } + stop { + query ="\ + DELETE FROM ${....cui_table} \ + WHERE clientipaddress = '%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND username = '%{User-Name}' \ + AND cui = '%{Chargeable-User-Identity}'" + } + } +} diff --git a/raddb/mods-config/sql/cui/postgresql/schema.sql b/raddb/mods-config/sql/cui/postgresql/schema.sql new file mode 100644 index 0000000..3b24401 --- /dev/null +++ b/raddb/mods-config/sql/cui/postgresql/schema.sql @@ -0,0 +1,14 @@ +CREATE TABLE cui ( + clientipaddress INET NOT NULL DEFAULT '0.0.0.0', + callingstationid varchar(50) NOT NULL DEFAULT '', + username varchar(64) NOT NULL DEFAULT '', + cui varchar(32) NOT NULL DEFAULT '', + creationdate TIMESTAMP with time zone NOT NULL default 'now()', + lastaccounting TIMESTAMP with time zone NOT NULL default '-infinity'::timestamp, + PRIMARY KEY (username, clientipaddress, callingstationid) +); + +CREATE RULE postauth_query AS ON INSERT TO cui + WHERE EXISTS(SELECT 1 FROM cui WHERE (username, clientipaddress, callingstationid)=(NEW.username, NEW.clientipaddress, NEW.callingstationid)) + DO INSTEAD UPDATE cui SET lastaccounting ='-infinity'::timestamp with time zone, cui=NEW.cui WHERE (username, clientipaddress, callingstationid)=(NEW.username, NEW.clientipaddress, NEW.callingstationid); + diff --git a/raddb/mods-config/sql/cui/sqlite/queries.conf b/raddb/mods-config/sql/cui/sqlite/queries.conf new file mode 100644 index 0000000..41741eb --- /dev/null +++ b/raddb/mods-config/sql/cui/sqlite/queries.conf @@ -0,0 +1,47 @@ +# -*- text -*- +# +# cui/sqlite/queries.conf -- Queries to update a sqlite CUI table. +# +# $Id$ + +post-auth { + query = "\ + INSERT OR REPLACE INTO ${..cui_table} \ + (clientipaddress, callingstationid, username, cui, lastaccounting) \ + VALUES \ + ('%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}', '%{Calling-Station-Id}', \ + '%{User-Name}', '%{reply:Chargeable-User-Identity}', NULL)" + +} + +accounting { + reference = "%{tolower:type.%{Acct-Status-Type}.query}" + type { + start { + query = "\ + UPDATE ${....cui_table} SET \ + lastaccounting = CURRENT_TIMESTAMP \ + WHERE clientipaddress = '%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND username = '%{User-Name}' \ + AND cui = '%{Chargeable-User-Identity}'" + } + interim-update { + query ="\ + UPDATE ${....cui_table} SET \ + lastaccounting = CURRENT_TIMESTAMP \ + WHERE clientipaddress = '%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND username = '%{User-Name}' \ + AND cui = '%{Chargeable-User-Identity}'" + } + stop { + query ="\ + DELETE FROM ${....cui_table} \ + WHERE clientipaddress = '%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND username = '%{User-Name}' \ + AND cui = '%{Chargeable-User-Identity}'" + } + } +} diff --git a/raddb/mods-config/sql/cui/sqlite/schema.sql b/raddb/mods-config/sql/cui/sqlite/schema.sql new file mode 100644 index 0000000..8473534 --- /dev/null +++ b/raddb/mods-config/sql/cui/sqlite/schema.sql @@ -0,0 +1,9 @@ +CREATE TABLE `cui` ( + `clientipaddress` varchar(46) NOT NULL default '', + `callingstationid` varchar(50) NOT NULL default '', + `username` varchar(64) NOT NULL default '', + `cui` varchar(32) NOT NULL default '', + `creationdate` timestamp NOT NULL default CURRENT_TIMESTAMP, + `lastaccounting` timestamp NOT NULL default '0000-00-00 00:00:00', + PRIMARY KEY (`username`,`clientipaddress`,`callingstationid`) +); diff --git a/raddb/mods-config/sql/dhcp/mssql/queries.conf b/raddb/mods-config/sql/dhcp/mssql/queries.conf new file mode 100644 index 0000000..8345c70 --- /dev/null +++ b/raddb/mods-config/sql/dhcp/mssql/queries.conf @@ -0,0 +1,52 @@ +# -*- text -*- +# +# dhcp/mssql/queries.conf -- MSSQL configuration for DHCP schema (schema.sql) +# +# $Id$ + +# Safe characters list for sql queries. Everything else is replaced +# with their mime-encoded equivalents. +# The default list should be ok +# safe_characters = "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" + +####################################################################### +# Query config: Identifier +####################################################################### +# This is the identifier that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used +# below everywhere an identifier substitution is needed so you you can +# be sure the identifier passed from the client is escaped properly. +# +sql_user_name = "%{control:DHCP-SQL-Option-Identifier}" + +####################################################################### +# Attribute Lookup Queries +####################################################################### +# These queries setup the reply items in ${dhcpreply_table} and +# ${group_reply_query}. You can use any query/tables you want, but +# the return data for each row MUST be in the following order: +# +# 0. Row ID (currently unused) +# 1. Identifier +# 2. Item Attr Name +# 3. Item Attr Value +# 4. Item Attr Operation +####################################################################### + +authorize_reply_query = "\ + SELECT id, Identifier, Attribute, Value, op \ + FROM ${dhcpreply_table} \ + WHERE Identifier = '%{SQL-User-Name}' AND Context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY id" + +authorize_group_reply_query = "\ + SELECT id, GroupName, Attribute, Value, op \ + FROM ${groupreply_table} \ + WHERE GroupName = '%{${group_attribute}}' AND Context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY id" + +group_membership_query = "\ + SELECT GroupName \ + FROM ${dhcpgroup_table} \ + WHERE Identifier='%{SQL-User-Name}' AND Context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY priority" diff --git a/raddb/mods-config/sql/dhcp/mssql/schema.sql b/raddb/mods-config/sql/dhcp/mssql/schema.sql new file mode 100644 index 0000000..8584949 --- /dev/null +++ b/raddb/mods-config/sql/dhcp/mssql/schema.sql @@ -0,0 +1,91 @@ +-- $Id$ +-- +-- MSSQL schema for DHCP for FreeRADIUS +-- +-- To load: +-- isql -S db_ip_addr -d db_name -U db_login -P db_passwd -i schema.sql + +-- +-- Table structure for table 'dhcpgroupreply' +-- +CREATE TABLE [dhcpgroupreply] ( + [id] [int] IDENTITY (1, 1) NOT NULL, + [GroupName] [varchar] (64) NOT NULL, + [Attribute] [varchar] (32) NOT NULL, + [Value] [varchar] (253) NOT NULL, + [op] [char] (2) NULL, + [prio] [int] NOT NULL, + [Context] [varchar] (16) NOT NULL +) ON [PRIMARY] +GO + +ALTER TABLE [dhcpgroupreply] WITH NOCHECK ADD + CONSTRAINT [DF_dhcpgroupreply_GroupName] DEFAULT ('') FOR [GroupName], + CONSTRAINT [DF_dhcpgroupreply_Attribute] DEFAULT ('') FOR [Attribute], + CONSTRAINT [DF_dhcpgroupreply_Value] DEFAULT ('') FOR [Value], + CONSTRAINT [DF_dhcpgroupreply_op] DEFAULT (null) FOR [op], + CONSTRAINT [DF_dhcpgroupreply_prio] DEFAULT (0) FOR [prio], + CONSTRAINT [DF_dhcpgroupreply_context] DEFAULT ('') FOR [Context], + CONSTRAINT [PK_dhcpgroupreply] PRIMARY KEY NONCLUSTERED + ( + [id] + ) ON [PRIMARY] +GO + +CREATE INDEX [GroupName] ON [dhcpgroupreply]([Context],[GroupName]) ON [PRIMARY] +GO + + +-- +-- Table structure for table 'dhcpreply' +-- +CREATE TABLE [dhcpreply] ( + [id] [int] IDENTITY (1, 1) NOT NULL, + [Identifier] [varchar] (64) NOT NULL, + [Attribute] [varchar] (32) NOT NULL, + [Value] [varchar] (253) NOT NULL, + [op] [char] (2) NULL, + [Context] [varchar] (16) NOT NULL +) ON [PRIMARY] +GO + +ALTER TABLE [dhcpreply] WITH NOCHECK ADD + CONSTRAINT [DF_dhcpreply_Identifier] DEFAULT ('') FOR [Identifier], + CONSTRAINT [DF_dhcpreply_Attribute] DEFAULT ('') FOR [Attribute], + CONSTRAINT [DF_dhcpreply_Value] DEFAULT ('') FOR [Value], + CONSTRAINT [DF_dhcpreply_op] DEFAULT (null) FOR [op], + CONSTRAINT [DF_dhcpreply_Context] DEFAULT ('') FOR [Context], + CONSTRAINT [PK_dhcpreply] PRIMARY KEY NONCLUSTERED + ( + [id] + ) ON [PRIMARY] +GO + +CREATE INDEX [Identifier] ON [dhcpreply]([Context],[Identifier]) ON [PRIMARY] +GO + + +-- +-- Table structure for table 'dhcpgroup' +-- +CREATE TABLE [dhcpgroup] ( + [id] [int] IDENTITY (1, 1) NOT NULL, + [Identifier] [varchar] (64) NOT NULL, + [GroupName] [varchar] (64) NULL, + [Priority] [int] NULL, + [Context] [varchar] (16) NULL +) ON [PRIMARY] +GO + +ALTER TABLE [dhcpgroup] WITH NOCHECK ADD + CONSTRAINT [DF_dhcpgroup_Identifier] DEFAULT ('') FOR [Identifier], + CONSTRAINT [DF_dhcpgroup_GroupName] DEFAULT ('') FOR [GroupName], + CONSTRAINT [DF_dhcpgroup_Context] DEFAULT ('') FOR [Context], + CONSTRAINT [PK_dhcpgroup] PRIMARY KEY NONCLUSTERED + ( + [id] + ) ON [PRIMARY] +GO + +CREATE INDEX [Identifier] ON [dhcpgroup]([Context],[Identifier]) ON [PRIMARY] +GO diff --git a/raddb/mods-config/sql/dhcp/mysql/queries.conf b/raddb/mods-config/sql/dhcp/mysql/queries.conf new file mode 100644 index 0000000..a28037b --- /dev/null +++ b/raddb/mods-config/sql/dhcp/mysql/queries.conf @@ -0,0 +1,75 @@ +# -*- text -*- +# +# dhcp/mysql/queries.conf -- MySQL configuration for DHCP schema (schema.sql) +# +# $Id$ + +# Use the driver specific SQL escape method. +# +# If you enable this configuration item, the "safe_characters" +# configuration is ignored. FreeRADIUS then uses the PostgreSQL escape +# functions to escape input strings. The only downside to making this +# change is that the PostgreSQL escaping method is not the same the one +# used by FreeRADIUS. So characters which are NOT in the +# "safe_characters" list will now be stored differently in the database. +# +#auto_escape = yes + +# Safe characters list for sql queries. Everything else is replaced +# with their mime-encoded equivalents. +# The default list should be ok +# Using 'auto_escape' is preferred +safe_characters = "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" + +####################################################################### +# Connection config +####################################################################### +# The character set is not configurable. The default character set of +# the mysql client library is used. To control the character set, +# create/edit my.cnf (typically in /etc/mysql/my.cnf or /etc/my.cnf) +# and enter +# [client] +# default-character-set = utf8 +# + +####################################################################### +# Query config: Identifier +####################################################################### +# This is the identifier that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used +# below everywhere an identifier substitution is needed so you you can +# be sure the identifier passed from the client is escaped properly. +# +sql_user_name = "%{control:DHCP-SQL-Option-Identifier}" + +####################################################################### +# Attribute Lookup Queries +####################################################################### +# These queries setup the reply items in ${dhcpreply_table} and +# ${group_reply_query}. You can use any query/tables you want, but +# the return data for each row MUST be in the following order: +# +# 0. Row ID (currently unused) +# 1. Identifier +# 2. Item Attr Name +# 3. Item Attr Value +# 4. Item Attr Operation +####################################################################### + +authorize_reply_query = "\ + SELECT id, identifier, attribute, value, Op \ + FROM ${dhcpreply_table} \ + WHERE identifier = '%{SQL-User-Name}' AND context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY id" + +authorize_group_reply_query = "\ + SELECT id, groupname, attribute, value, op \ + FROM ${groupreply_table} \ + WHERE groupname = '%{${group_attribute}}' AND context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY id" + +group_membership_query = "\ + SELECT groupnme \ + FROM ${dhcpgroup_table} \ + WHERE identifier='%{SQL-User-Name}' AND context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY priority" diff --git a/raddb/mods-config/sql/dhcp/mysql/schema.sql b/raddb/mods-config/sql/dhcp/mysql/schema.sql new file mode 100644 index 0000000..85a121a --- /dev/null +++ b/raddb/mods-config/sql/dhcp/mysql/schema.sql @@ -0,0 +1,47 @@ +# +# $Id$ +# +# PostgreSQL schema for DHCP for FreeRADIUS +# +# + +# +# Table structure for table 'dhcpgroupreply' +# +CREATE TABLE IF NOT EXISTS dhcpgroupreply ( + id int(11) unsigned NOT NULL auto_increment, + groupname varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '', + context varchar(16) NOT NULL default '', + PRIMARY KEY (id), + KEY groupname (context,groupname(32)) +); + +# +# Table structure for table 'dhcpreply' +# +CREATE TABLE IF NOT EXISTS dhcpreply ( + id int(11) unsigned NOT NULL auto_increment, + identifier varchar(253) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '', + context varchar(16) NOT NULL default '', + PRIMARY KEY (id), + KEY identifier (context,identifier(32)) +); + +# +# Table structure for table 'dhcpgroup' +# +CREATE TABLE IF NOT EXISTS dhcpgroup ( + id int(11) unsigned NOT NULL auto_increment, + identifier varchar(253) NOT NULL default '', + groupname varchar(64) NOT NULL default '', + priority int(11) NOT NULL default '1', + context varchar(16) NOT NULL default '', + PRIMARY KEY (id), + KEY identifier (context,identifier(32)) +); diff --git a/raddb/mods-config/sql/dhcp/mysql/setup.sql b/raddb/mods-config/sql/dhcp/mysql/setup.sql new file mode 100644 index 0000000..d20a82c --- /dev/null +++ b/raddb/mods-config/sql/dhcp/mysql/setup.sql @@ -0,0 +1,21 @@ +/* + * setup.sql -- MySQL commands for creating the RADIUS user. + * + * WARNING: You should change 'localhost' and 'radpass' + * to something else. Also update raddb/mods-available/sql + * with the new RADIUS password. + * + * WARNING: This example file is untested. Use at your own risk. + * Please send any bug fixes to the mailing list. + * + * $Id$ + */ + +/* + * Create default administrator for RADIUS + */ +CREATE USER 'radius'@'localhost' IDENTIFIED BY 'radpass'; + +GRANT SELECT ON radius.dhcpreply TO 'radius'@'localhost'; +GRANT SELECT ON radius.dhcpgroupreply TO 'radius'@'localhost'; +GRANT SELECT ON radius.dhcpgroup TO 'radius'@'localhost'; diff --git a/raddb/mods-config/sql/dhcp/oracle/queries.conf b/raddb/mods-config/sql/dhcp/oracle/queries.conf new file mode 100644 index 0000000..dd312d5 --- /dev/null +++ b/raddb/mods-config/sql/dhcp/oracle/queries.conf @@ -0,0 +1,47 @@ +# -*- text -*- +# +# dhcp/oracle/queries.conf -- Oracle configuration for DHCP schema (schema.sql) +# +# $Id$ + +####################################################################### +# Query config: Identifier +####################################################################### +# This is the identifier that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used +# below everywhere an identifier substitution is needed so you you can +# be sure the identifier passed from the client is escaped properly. +# +sql_user_name = "%{control:DHCP-SQL-Option-Identifier}" + +####################################################################### +# Attribute Lookup Queries +####################################################################### +# These queries setup the reply items in ${dhcpreply_table} and +# ${group_reply_query}. You can use any query/tables you want, but +# the return data for each row MUST be in the following order: +# +# 0. Row ID (currently unused) +# 1. Identifier +# 2. Item Attr Name +# 3. Item Attr Value +# 4. Item Attr Operation +####################################################################### + +authorize_reply_query = "\ + SELECT id, identifier, attribute, value, op \ + FROM ${dhcpreply_table} \ + WHERE identifier = '%{SQL-User-Name}' AND context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY id" + +authorize_group_reply_query = "\ + SELECT id, groupname, attribute, value, op \ + FROM ${groupreply_table} \ + WHERE groupname = '%{${group_attribute}}' AND context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY id" + +group_membership_query = "\ + SELECT groupname \ + FROM ${dhcpgroup_table} \ + WHERE identifier='%{SQL-User-Name}' AND context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY priority" diff --git a/raddb/mods-config/sql/dhcp/oracle/schema.sql b/raddb/mods-config/sql/dhcp/oracle/schema.sql new file mode 100644 index 0000000..085e346 --- /dev/null +++ b/raddb/mods-config/sql/dhcp/oracle/schema.sql @@ -0,0 +1,81 @@ +/* + * $Id$ + * + * Oracle schema for DHCP for FreeRADIUS + * + */ + +/* + * Table structure for table 'dhcpgroupreply' + */ +CREATE TABLE dhcpgroupreply ( + id INT PRIMARY KEY, + groupname VARCHAR(64) NOT NULL, + attribute VARCHAR(64) NOT NULL, + op CHAR(2) NOT NULL, + value VARCHAR(253) NOT NULL, + context VARCHAR(16) NOT NULL +); +CREATE INDEX dhcpgroupreply_idx1 ON dhcpgroupreply(context,groupname); +CREATE SEQUENCE dhcpgroupreply_seq START WITH 1 INCREMENT BY 1; + +/* Trigger to emulate a serial # on the primary key */ +CREATE OR REPLACE TRIGGER dhcpgroupreply_serialnumber + BEFORE INSERT OR UPDATE OF id ON dhcpgroupreply + FOR EACH ROW + BEGIN + if ( :new.id = 0 or :new.id is null ) then + SELECT dhcpgroupreply_seq.nextval into :new.id from dual; + end if; + END; +/ + +/* + * Table structure for table 'dhcpreply' + */ +CREATE TABLE dhcpreply ( + id INT PRIMARY KEY, + identifier VARCHAR(253) NOT NULL, + attribute VARCHAR(64) NOT NULL, + op CHAR(2) NOT NULL, + value VARCHAR(253) NOT NULL, + context VARCHAR(16) NOT NULL +); +CREATE INDEX dhcpreply_idx1 ON dhcpreply(context,identifier); +CREATE SEQUENCE dhcpreply_seq START WITH 1 INCREMENT BY 1; + +/* Trigger to emulate a serial # on the primary key */ +CREATE OR REPLACE TRIGGER dhcpreply_serialnumber + BEFORE INSERT OR UPDATE OF id ON dhcpreply + FOR EACH ROW + BEGIN + if ( :new.id = 0 or :new.id is null ) then + SELECT dhcpreply_seq.nextval into :new.id from dual; + end if; + END; +/ + +/* + * Table structure for table 'dhcpgroup' + */ +CREATE TABLE dhcpgroup ( + id INT PRIMARY KEY, + identifier VARCHAR(253) NOT NULL, + groupname VARCHAR(64) NOT NULL, + priority INT NOT NULL, + context VARCHAR(16) NOT NULL +); +CREATE INDEX dhcpgroup_idx1 ON dhcpgroup(context,identifier); +CREATE SEQUENCE dhcpgroup_seq START WITH 1 INCREMENT BY 1; + +/* Trigger to emulate a serial # on the primary key */ +CREATE OR REPLACE TRIGGER dhcpgroup_serialnumber + BEFORE INSERT OR UPDATE OF id ON dhcpgroup + FOR EACH ROW + BEGIN + if ( :new.id = 0 or :new.id is null ) then + SELECT dhcpgroup_seq.nextval into :new.id from dual; + end if; + END; +/ + diff --git a/raddb/mods-config/sql/dhcp/postgresql/queries.conf b/raddb/mods-config/sql/dhcp/postgresql/queries.conf new file mode 100644 index 0000000..14ca79a --- /dev/null +++ b/raddb/mods-config/sql/dhcp/postgresql/queries.conf @@ -0,0 +1,76 @@ +# -*- text -*- +# +# dhcp/postgresql/queries.conf -- PostgreSQL configuration for DHCP schema (schema.sql) +# +# $Id$ + +# Use the driver specific SQL escape method. +# +# If you enable this configuration item, the "safe_characters" +# configuration is ignored. FreeRADIUS then uses the PostgreSQL escape +# functions to escape input strings. The only downside to making this +# change is that the PostgreSQL escaping method is not the same the one +# used by FreeRADIUS. So characters which are NOT in the +# "safe_characters" list will now be stored differently in the database. +# +#auto_escape = yes + +# Safe characters list for sql queries. Everything else is replaced +# with their mime-encoded equivalents. +# The default list should be ok +# Using 'auto_escape' is preferred +# safe_characters = "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" + +####################################################################### +# Query config: Identifier +####################################################################### +# This is the identifier that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used +# below everywhere an identifier substitution is needed so you you can +# be sure the identifier passed from the client is escaped properly. +# +sql_user_name = "%{control:DHCP-SQL-Option-Identifier}" + +####################################################################### +# Open Query +####################################################################### +# This query is run whenever a new connection is opened. +# It is commented out by default. +# +# If you have issues with connections hanging for too long, uncomment +# the next line, and set the timeout in milliseconds. As a general +# rule, if the queries take longer than a second, something is wrong +# with the database. +#open_query = "set statement_timeout to 1000" + +####################################################################### +# Attribute Lookup Queries +####################################################################### +# These queries setup the reply items in ${dhcpreply_table} and +# ${group_reply_query}. You can use any query/tables you want, but +# the return data for each row MUST be in the following order: +# +# 0. Row ID (currently unused) +# 1. Identifier +# 2. Item Attr Name +# 3. Item Attr Value +# 4. Item Attr Operation +####################################################################### + +authorize_reply_query = "\ + SELECT id, Identifier, Attribute, Value, Op \ + FROM ${dhcpreply_table} \ + WHERE Identifier = '%{SQL-User-Name}' AND Context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY id" + +authorize_group_reply_query = "\ + SELECT id, GroupName, Attribute, Value, op \ + FROM ${groupreply_table} \ + WHERE GroupName = '%{${group_attribute}}' AND Context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY id" + +group_membership_query = "\ + SELECT GroupName \ + FROM ${dhcpgroup_table} \ + WHERE Identifier='%{SQL-User-Name}' AND Context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY priority" diff --git a/raddb/mods-config/sql/dhcp/postgresql/schema.sql b/raddb/mods-config/sql/dhcp/postgresql/schema.sql new file mode 100644 index 0000000..0d1727f --- /dev/null +++ b/raddb/mods-config/sql/dhcp/postgresql/schema.sql @@ -0,0 +1,44 @@ +/* + * $Id$ + * + * PostgreSQL schema for DHCP for FreeRADIUS + * + */ + +/* + * Table structure for table 'dhcpgroupreply' + */ +CREATE TABLE IF NOT EXISTS dhcpgroupreply ( + id serial PRIMARY KEY, + GroupName text NOT NULL DEFAULT '', + Attribute text NOT NULL DEFAULT '', + op VARCHAR(2) NOT NULL DEFAULT '=', + Value text NOT NULL DEFAULT '', + Context text NOT NULL DEFAULT '' +); +CREATE INDEX dhcpgroupreply_GroupName ON dhcpgroupreply (Context,GroupName,Attribute); + +/* + * Table structure for table 'dhcpreply' + */ +CREATE TABLE IF NOT EXISTS dhcpreply ( + id serial PRIMARY KEY, + Identifier text NOT NULL DEFAULT '', + Attribute text NOT NULL DEFAULT '', + op VARCHAR(2) NOT NULL DEFAULT '=', + Value text NOT NULL DEFAULT '', + Context text NOT NULL DEFAULT '' +); +CREATE INDEX dhcpreply_Identifier ON dhcpreply (Context,Identifier,Attribute); + +/* + * Table structure for table 'dhcpgroup' + */ +CREATE TABLE IF NOT EXISTS dhcpgroup ( + id serial PRIMARY KEY, + Identifier text NOT NULL DEFAULT '', + GroupName text NOT NULL DEFAULT '', + Priority integer NOT NULL DEFAULT 0, + Context text NOT NULL DEFAULT '' +); +CREATE INDEX dhcpgroup_Identifier ON dhcpgroup (Context,Identifier); diff --git a/raddb/mods-config/sql/dhcp/postgresql/setup.sql b/raddb/mods-config/sql/dhcp/postgresql/setup.sql new file mode 100644 index 0000000..884aa5a --- /dev/null +++ b/raddb/mods-config/sql/dhcp/postgresql/setup.sql @@ -0,0 +1,28 @@ +/* + * admin.sql -- PostgreSQL commands for creating the RADIUS user. + * + * WARNING: You should change 'localhost' and 'radpass' + * to something else. Also update raddb/mods-available/sql + * with the new RADIUS password. + * + * WARNING: This example file is untested. Use at your own risk. + * Please send any bug fixes to the mailing list. + * + * $Id$ + */ + +/* + * Create default administrator for RADIUS + */ +CREATE USER radius WITH PASSWORD 'radpass'; + +/* + * The server can read any table in SQL + */ +GRANT SELECT ON dhcpreply TO radius; +GRANT SELECT ON dhcpgroupreply TO radius; +GRANT SELECT ON dhcpgroup TO radius; + +GRANT USAGE, SELECT ON SEQUENCE dhcpgroupreply_id_seq TO radius; +GRANT USAGE, SELECT ON SEQUENCE dhcpreply_id_seq TO radius; +GRANT USAGE, SELECT ON SEQUENCE dhcpgroup_id_seq TO radius; diff --git a/raddb/mods-config/sql/dhcp/sqlite/queries.conf b/raddb/mods-config/sql/dhcp/sqlite/queries.conf new file mode 100644 index 0000000..0cc7202 --- /dev/null +++ b/raddb/mods-config/sql/dhcp/sqlite/queries.conf @@ -0,0 +1,52 @@ +# -*- text -*- +# +# dhcp/sqlite/queries.conf -- SQLite configuration for DHCP schema (schema.sql) +# +# $Id$ + +# Safe characters list for sql queries. Everything else is replaced +# with their mime-encoded equivalents. +# The default list should be ok +# safe_characters = "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" + +####################################################################### +# Query config: Identifier +####################################################################### +# This is the identifier that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used +# below everywhere an identifier substitution is needed so you you can +# be sure the identifier passed from the client is escaped properly. +# +sql_user_name = "%{control:DHCP-SQL-Option-Identifier}" + +####################################################################### +# Attribute Lookup Queries +####################################################################### +# These queries setup the reply items in ${dhcpreply_table} and +# ${group_reply_query}. You can use any query/tables you want, but +# the return data for each row MUST be in the following order: +# +# 0. Row ID (currently unused) +# 1. Identifier +# 2. Item Attr Name +# 3. Item Attr Value +# 4. Item Attr Operation +####################################################################### + +authorize_reply_query = "\ + SELECT id, identifier, attribute, value, op \ + FROM ${dhcpreply_table} \ + WHERE identifier = '%{SQL-User-Name}' AND context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY id" + +authorize_group_reply_query = "\ + SELECT id, groupname, attribute, value, op \ + FROM ${groupreply_table} \ + WHERE groupname = '%{${group_attribute}}' AND context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY id" + +group_membership_query = "\ + SELECT groupname \ + FROM ${dhcpgroup_table} \ + WHERE identifier='%{SQL-User-Name}' AND context = '%{control:DHCP-SQL-Option-Context}' \ + ORDER BY priority" diff --git a/raddb/mods-config/sql/dhcp/sqlite/schema.sql b/raddb/mods-config/sql/dhcp/sqlite/schema.sql new file mode 100644 index 0000000..54a9abb --- /dev/null +++ b/raddb/mods-config/sql/dhcp/sqlite/schema.sql @@ -0,0 +1,46 @@ +----------------------------------------------------------------------------- +-- $Id$ ␉···· -- +-- -- +-- schema.sql rlm_sql - FreeRADIUS SQLite Module -- +-- -- +-- Database schema for SQLite rlm_sql module for DHCP -- +-- -- +----------------------------------------------------------------------------- + +-- +-- Table structure for table 'dhcpgroupreply' +-- +CREATE TABLE IF NOT EXISTS dhcpgroupreply ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + groupname varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '', + context varchar(16) NOT NULL default '' +); +CREATE INDEX dhcpgroupreply_groupname ON dhcpgroupreply(context,groupname); + +-- +-- Table structure for table 'dhcpreply' +-- +CREATE TABLE IF NOT EXISTS dhcpreply ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + identifier varchar(253) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '', + context varchar(16) NOT NULL default '' +); +CREATE INDEX dhcpreply_identifier ON dhcpreply(context,identifier); + +-- +-- Table structure for table 'dhcpgroup' +-- +CREATE TABLE IF NOT EXISTS dhcpgroup ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + identifier varchar(253) NOT NULL default '', + groupname varchar(64) NOT NULL default '', + priority int(11) NOT NULL default '1', + context varchar(16) NOT NULL default '' +); +CREATE INDEX dhcpgroup_identifier ON dhcpgroup(context,identifier); diff --git a/raddb/mods-config/sql/ippool-dhcp/mssql/procedure.sql b/raddb/mods-config/sql/ippool-dhcp/mssql/procedure.sql new file mode 100644 index 0000000..4cfbe1c --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mssql/procedure.sql @@ -0,0 +1,159 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- BEGIN TRAN; "SELECT FOR UPDATE"; UPDATE; COMMIT TRAN; -> EXEC sp +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- EXEC fr_dhcp_allocate_previous_or_new_framedipaddress \ +-- @v_pool_name = '%{control:${pool_name}}', \ +-- @v_gateway = '%{DHCP-Gateway-IP-Address}', \ +-- @v_pool_key = '${pool_key}', \ +-- @v_lease_duration = ${lease_duration}, \ +-- @v_requested_address = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +-- " +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE OR ALTER PROCEDURE fr_dhcp_allocate_previous_or_new_framedipaddress + @v_pool_name VARCHAR(64), + @v_gateway VARCHAR(15), + @v_pool_key VARCHAR(64), + @v_lease_duration INT, + @v_requested_address VARCHAR(15) +AS + BEGIN + + -- MS SQL lacks a "SELECT FOR UPDATE" statement, and its table + -- hints do not provide a direct means to implement the row-level + -- read lock needed to guarentee that concurrent queries do not + -- select the same Framed-IP-Address for allocation to distinct + -- users. + -- + -- The "WITH cte AS ( SELECT ... ) UPDATE cte ... OUTPUT INTO" + -- patterns in this procedure body compensate by wrapping + -- the SELECT in a synthetic UPDATE which locks the row. + + DECLARE @r_address_tab TABLE(id VARCHAR(15)); + DECLARE @r_address VARCHAR(15); + + BEGIN TRAN; + + -- Reissue an existing IP address lease when re-authenticating a session + -- + WITH cte AS ( + SELECT TOP(1) FramedIPAddress + FROM dhcpippool WITH (rowlock, readpast) + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = @v_pool_name + AND expiry_time > CURRENT_TIMESTAMP + AND pool_key = @v_pool_key + AND dhcpstatus.status IN ('dynamic', 'static') + ) + UPDATE cte + SET FramedIPAddress = FramedIPAddress + OUTPUT INSERTED.FramedIPAddress INTO @r_address_tab; + SELECT @r_address = id FROM @r_address_tab; + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- WITH cte AS ( + -- SELECT TOP(1) FramedIPAddress + -- FROM dhcpippool WITH (rowlock, readpast) + -- JOIN dhcpstatus + -- ON dhcpstatus.status_id = dhcpippool.status_id + -- WHERE pool_name = @v_pool_name + -- AND pool_key = @v_pool_key + -- AND dhcpstatus.status IN ('dynamic', 'static') + -- ) + -- UPDATE cte + -- SET FramedIPAddress = FramedIPAddress + -- OUTPUT INSERTED.FramedIPAddress INTO @r_address_tab; + -- SELECT @r_address = id FROM @r_address_tab; + + -- Issue the requested IP address if it is available + -- + IF @r_address IS NULL AND @v_requested_address <> '0.0.0.0' + BEGIN + WITH cte AS ( + SELECT TOP(1) FramedIPAddress + FROM dhcpippool WITH (rowlock, readpast) + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = @v_pool_name + AND framedipaddress = @v_requested_address + AND dhcpstatus.status = 'dynamic' + AND ( pool_key = @v_pool_name OR expiry_time < CURRENT_TIMESTAMP ) + ) + UPDATE cte + SET FramedIPAddress = FramedIPAddress + OUTPUT INSERTED.FramedIPAddress INTO @r_address_tab; + SELECT @r_address = id FROM @r_address_tab; + END + + -- If we didn't reallocate a previous address then pick the least + -- recently used address from the pool which maximises the likelihood + -- of re-assigning the other addresses to their recent user + -- + IF @r_address IS NULL + BEGIN + WITH cte AS ( + SELECT TOP(1) FramedIPAddress + FROM dhcpippool WITH (rowlock, readpast) + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = @v_pool_name + AND expiry_time < CURRENT_TIMESTAMP + AND dhcpstatus.status = 'dynamic' + ORDER BY + expiry_time + ) + UPDATE cte + SET FramedIPAddress = FramedIPAddress + OUTPUT INSERTED.FramedIPAddress INTO @r_address_tab; + SELECT @r_address = id FROM @r_address_tab; + END + + -- Return nothing if we failed to allocated an address + -- + IF @r_address IS NULL + BEGIN + COMMIT TRAN; + RETURN; + END + + -- Update the pool having allocated an IP address + -- + UPDATE dhcpippool + SET + gateway = @v_gateway, + pool_key = @v_pool_key, + expiry_time = DATEADD(SECOND,@v_lease_duration,CURRENT_TIMESTAMP) + WHERE framedipaddress = @r_address; + + COMMIT TRAN; + + -- Return the address that we allocated + SELECT @r_address; + + END +GO diff --git a/raddb/mods-config/sql/ippool-dhcp/mssql/queries.conf b/raddb/mods-config/sql/ippool-dhcp/mssql/queries.conf new file mode 100644 index 0000000..c919e2d --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mssql/queries.conf @@ -0,0 +1,257 @@ +# -*- text -*- +# +# ippool-dhcp/mssql/queries.conf -- MSSQL queries for rlm_sqlippool +# +# $Id$ + +# ***************** +# * DHCP DISCOVER * +# ***************** + +# +# This series of queries allocates an IP address +# + +# +# MSSQL-specific syntax - required if finding the address and updating +# it are separate queries +# +#allocate_begin = "BEGIN TRAN" +#allocate_commit = "COMMIT TRAN" + +allocate_begin = "" +allocate_commit = "" + +# +# Attempt to find the most recent existing IP address for the client +# +allocate_existing = "\ + WITH cte AS ( \ + SELECT TOP(1) framedipaddress, expiry_time, gateway \ + FROM ${ippool_table} WITH (xlock rowlock readpast) \ + JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND dhcpstatus.status IN ('dynamic', 'static') \ + ORDER BY expiry_time DESC \ + ) \ + UPDATE cte \ + SET expiry_time = DATEADD(SECOND,${offer_duration},CURRENT_TIMESTAMP), \ + gateway = '%{DHCP-Gateway-IP-Address}' \ + OUTPUT INSERTED.FramedIPAddress \ + FROM ${ippool_table}" + +# +# Determine whether the requested IP address is available +# +allocate_requested = "\ + WITH cte AS ( \ + SELECT TOP(1) framedipaddress, expiry_time, gateway \ + FROM ${ippool_table} WITH (xlock rowlock readpast) \ + JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ + AND dhcpstatus.status = 'dynamic' \ + AND expiry_time < CURRENT_TIMESTAMP \ + ) \ + UPDATE cte \ + SET expiry_time = DATEADD(SECOND,${offer_duration},CURRENT_TIMESTAMP), \ + gateway = '%{DHCP-Gateway-IP-Address}', \ + pool_key = '${pool_key}' \ + OUTPUT INSERTED.FramedIPAddress \ + FROM ${ippool_table}" + +# +# If the existing address can't be found this query will be run to +# find a free address +# +allocate_find = "\ + WITH cte AS ( \ + SELECT TOP(1) framedipaddress, expiry_time, gateway, pool_key \ + FROM ${ippool_table} WITH (xlock rowlock readpast) \ + JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND expiry_time < CURRENT_TIMESTAMP \ + AND dhcpstatus.status = 'dynamic' \ + ORDER BY expiry_time \ + ) \ + UPDATE cte \ + SET expiry_time = DATEADD(SECOND,${offer_duration},CURRENT_TIMESTAMP), \ + gateway = '%{DHCP-Gateway-IP-Address}', \ + pool_key = '${pool_key}' \ + OUTPUT INSERTED.FramedIPAddress \ + FROM ${ippool_table}" + +# +# Alternatively attempt all in one, more complex, query +# +# The ORDER BY clause of this query tries to allocate the same IP-address +# which user had last session. Ensure that pool_key is unique to the user +# within a given pool. +# +#allocate_find = "\ +# UPDATE TOP(1) ${ippool_table} \ +# SET FramedIPAddress = FramedIPAddress, \ +# pool_key = '${pool_key}', \ +# expiry_time = DATEADD(SECOND,${offer_duration},CURRENT_TIMESTAMP), \ +# GatewayIPAddress = '%{DHCP-Gateway-IP-Address}' \ +# OUTPUT INSERTED.FramedIPAddress \ +# FROM ${ippool_table} \ +# WHERE ${ippool_table}.id IN ( \ +# SELECT TOP (1) id FROM ( \ +# (SELECT TOP(1) id, 1 AS o FROM ${ippool_table} WITH (xlock rowlock readpast) \ +# JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# AND dhcpstatus.status IN ('dynamic', 'static')) \ +# UNION \ +# (SELECT TOP(1) id, 2 AS o FROM ${ippool_table} WITH (xlock rowlock readpast) \ +# JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND dhcpstatus.status = 'dynamic' \ +# AND ( pool_key = '%{pool_key}' OR expiry_time < CURRENT_TIMESTAMP )) \ +# UNION \ +# (SELECT TOP(1) id, 3 AS o FROM ${ippool_table} WITH (xlock rowlock readpast) \ +# JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < CURRENT_TIMESTAMP \ +# AND dhcpstatus.status = 'dynamic' \ +# ORDER BY expiry_time) \ +# ) AS q ORDER BY q.o \ +# )" + +# +# If you prefer to allocate a random IP address every time, use this query instead. +# Note: This is very slow if you have a lot of free IPs. +# +#allocate_find = "\ +# WITH cte AS ( \ +# SELECT TOP(1) FramedIPAddress FROM ${ippool_table} \ +# JOIN dhcpstatus ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < CURRENT_TIMESTAMP \ +# AND dhcpstatus.status = 'dynamic' \ +# ORDER BY \ +# newid() \ +# ) \ +# UPDATE cte WITH (rowlock, readpast) \ +# SET FramedIPAddress = FramedIPAddress \ +# OUTPUT INSERTED.FramedIPAddress" + +# +# If an IP could not be allocated, check to see if the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be +# commented out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT TOP(1) id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}'" + +# +# This is the final IP Allocation query, which saves the allocated ip details. +# Only needed if the initial "find" query is not storing the allocation. +# +#allocate_update = "\ +# UPDATE ${ippool_table} \ +# SET \ +# gateway = '%{DHCP-Gateway-IP-Address}', pool_key = '${pool_key}', \ +# expiry_time = DATEADD(SECOND,${offer_duration},CURRENT_TIMESTAMP) \ +# WHERE FramedIPAddress = '%I'" + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# +#allocate_begin = "" +#allocate_find = "\ +# EXEC fr_dhcp_allocate_previous_or_new_framedipaddress \ +# @v_pool_name = '%{control:${pool_name}}', \ +# @v_gateway = '%{DHCP-Gateway-IP-Address}', \ +# @v_pool_key = '${pool_key}', \ +# @v_lease_duration = ${offer_duration}, \ +# @v_requested_address = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# " +#allocate_update = "" +#allocate_commit = "" + + +# **************** +# * DHCP REQUEST * +# **************** + +# +# This query revokes any active offers for addresses that a client is not +# requesting when a DHCP REQUEST packet arrives +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = CURRENT_TIMESTAMP \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress <> '%{DHCP-Requested-IP-Address}' \ + AND expiry_time > CURRENT_TIMESTAMP \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" + +# +# This query extends an existing lease (or offer) when a DHCP REQUEST packet +# arrives. This query must update a row when a lease is succesfully requested +# - queries that update no rows will result in a "notfound" response to +# the module which by default will give a DHCP-NAK reply. In this example +# incrementing "counter" is used to achieve this. +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = DATEADD(SECOND,${lease_duration},CURRENT_TIMESTAMP), \ + counter = counter + 1 \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}'" + + +# **************** +# * DHCP RELEASE * +# **************** + +# +# This query frees an IP address when a DHCP RELEASE packet arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = CURRENT_TIMESTAMP \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND FramedIPAddress = '%{DHCP-Client-IP-Address}' \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" + +# +# This query is not applicable to DHCP +# +on_clear = "" + + +# **************** +# * DHCP DECLINE * +# **************** + +# +# This query marks an IP address as declined when a DHCP Decline +# packet arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET status_id = (SELECT status_id FROM dhcpstatus WHERE status = 'declined') \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Requested-IP-Address}'" diff --git a/raddb/mods-config/sql/ippool-dhcp/mssql/schema.sql b/raddb/mods-config/sql/ippool-dhcp/mssql/schema.sql new file mode 100644 index 0000000..dae4eff --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mssql/schema.sql @@ -0,0 +1,40 @@ +-- +-- Table structure for table 'dhcpippool' +-- +-- See also "procedure.sql" in this directory for +-- a stored procedure that gives much faster response. +-- + +CREATE TABLE dhcpstatus ( + status_id int NOT NULL, + status varchar(10) NOT NULL, + PRIMARY KEY (status_id) +) +GO + +INSERT INTO dhcpstatus (status_id, status) VALUES (1, 'dynamic'), (2, 'static'), (3, 'declined'), (4, 'disabled') +GO + +CREATE TABLE dhcpippool ( + id int IDENTITY (1,1) NOT NULL, + pool_name varchar(30) NOT NULL, + FramedIPAddress varchar(15) NOT NULL default '', + pool_key varchar(30) NOT NULL default '', + gateway varchar(15) NOT NULL default '', + expiry_time DATETIME NOT NULL default CURRENT_TIMESTAMP, + status_id int NOT NULL default 1, + counter int NOT NULL default 0, + CONSTRAINT fk_status_id FOREIGN KEY (status_id) REFERENCES dhcpstatus (status_id), + PRIMARY KEY (id) +) +GO + +CREATE INDEX dhcp_poolname_expire ON dhcpippool(pool_name, expiry_time) +GO + +CREATE INDEX dhcp_FramedIPAddress ON dhcpippool(FramedIPAddress) +GO + +CREATE INDEX dhcp_poolname_poolkey_FramedIPAddress ON dhcpippool(pool_name, pool_key, FramedIPAddress) +GO + diff --git a/raddb/mods-config/sql/ippool-dhcp/mysql/procedure-no-skip-locked.sql b/raddb/mods-config/sql/ippool-dhcp/mysql/procedure-no-skip-locked.sql new file mode 100644 index 0000000..bee37de --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mysql/procedure-no-skip-locked.sql @@ -0,0 +1,160 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- NOTE: This version of the SP is intended for MySQL variants that do not +-- support the SKIP LOCKED pragma, i.e. MariaDB and versions of MySQL +-- prior to 8.0. It should be a lot faster than using the default SP +-- without the SKIP LOCKED pragma under highly concurrent workloads +-- and not result in thread starvation. +-- +-- It is however a *useful hack* which should not be used if SKIP +-- LOCKED is available. +-- +-- WARNING: This query uses server-local, "user locks" (GET_LOCK and +-- RELEASE_LOCK), without the need for a transaction, to emulate +-- row locking with locked-row skipping. User locks are not +-- supported on clusters such as Galera and MaxScale. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- START TRANSACTION; SELECT FOR UPDATE; UPDATE; COMMIT; -> CALL sp() +-- +-- The stored procedure is executed within a single round trip which often +-- leads to reduced deadlocking and significant performance improvements. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- CALL fr_dhcp_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{DHCP-Gateway-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration}, \ +-- '%{%{${req_attribute_name}}:-0.0.0.0}' \ +-- )" +-- allocate_update = "" +-- allocate_commit = "" +-- + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS fr_dhcp_allocate_previous_or_new_framedipaddress; +CREATE PROCEDURE fr_allocate_previous_or_new_framedipaddress ( + IN v_pool_name VARCHAR(64), + IN v_gateway VARCHAR(15), + IN v_pool_key VARCHAR(64), + IN v_lease_duration INT, + IN v_requested_address VARCHAR(15) +) +SQL SECURITY INVOKER +proc:BEGIN + DECLARE r_address VARCHAR(15); + + -- Reissue an existing IP address lease when re-authenticating a session + -- + -- Note: In this query we get away without the need for FOR UPDATE + -- becase: + -- + -- (a) Each existing lease only belongs to a single device, so + -- no two devices will be racing over a single address. + -- (b) The set of existing leases (not yet expired) are + -- disjoint from the set of free leases, so not subject to + -- reallocation. + -- + SELECT framedipaddress INTO r_address + FROM dhcpippool + WHERE pool_name = v_pool_name + AND expiry_time > NOW() + AND pool_key = v_pool_key + AND `status` IN ('dynamic', 'static') + LIMIT 1; + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- SELECT framedipaddress INTO r_address + -- FROM dhcpippool + -- WHERE pool_name = v_pool_name + -- AND pool_key = v_pool_key + -- AND `status` IN ('dynamic', 'static') + -- LIMIT 1; + + -- + -- Normally here we would honour an IP address hint if the IP were + -- available, however we cannot do that without taking a lock which + -- defeats the purpose of this version of the stored procedure. + -- + -- It you need to honour an IP address hint then use a database with + -- support for SKIP LOCKED and use the normal stored procedure. + -- + + IF r_address IS NOT NULL THEN + UPDATE dhcpippool + SET + gateway = v_gateway, + pool_key = v_pool_key, + expiry_time = NOW() + INTERVAL v_lease_duration SECOND + WHERE + framedipaddress = r_address; + SELECT r_address; + LEAVE proc; + END IF; + + REPEAT + + -- If we didn't reallocate a previous address then pick the least + -- recently used address from the pool which maximises the likelihood + -- of re-assigning the other addresses to their recent user + -- + SELECT framedipaddress INTO r_address + FROM dhcpippool + WHERE pool_name = v_pool_name + AND expiry_time < NOW() + AND `status` = 'dynamic' + -- + -- WHERE ... GET_LOCK(...,0) = 1 is a poor man's SKIP LOCKED that simulates + -- a row-level lock using a "user lock" that allows the locked "rows" to be + -- skipped. After the user lock is acquired and the SELECT retired it does + -- not mean that the entirety of the WHERE clause is still true: Another + -- thread may have updated the expiry time and released the lock after we + -- checked the expiry_time but before we acquired the lock since SQL is free + -- to reorder the WHERE condition. Therefore we must recheck the condition + -- in the UPDATE statement below to detect this race. + -- + AND GET_LOCK(CONCAT('dhcpippool_', framedipaddress), 0) = 1 + LIMIT 1; + + IF r_address IS NULL THEN + DO RELEASE_LOCK(CONCAT('dhcpippool_', r_address)); + LEAVE proc; + END IF; + + UPDATE dhcpippool + SET + gateway = v_gateway, + pool_key = v_pool_key, + expiry_time = NOW() + INTERVAL v_lease_duration SECOND + WHERE + framedipaddress = r_address + -- + -- Here we re-evaluate the original condition for selecting the address + -- to detect a race, in which case we try again... + -- + AND expiry_time<NOW(); + + UNTIL ROW_COUNT() <> 0 END REPEAT; + + DO RELEASE_LOCK(CONCAT('dhcpippool_', r_address)); + SELECT r_address; + +END$$ + +DELIMITER ; diff --git a/raddb/mods-config/sql/ippool-dhcp/mysql/procedure.sql b/raddb/mods-config/sql/ippool-dhcp/mysql/procedure.sql new file mode 100644 index 0000000..b5dfae0 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mysql/procedure.sql @@ -0,0 +1,144 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- START TRANSACTION; SELECT FOR UPDATE; UPDATE; COMMIT; -> CALL sp() +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- CALL fr_dhcp_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{DHCP-Gateway-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration}, \ +-- '%{%{${req_attribute_name}}:-0.0.0.0}' \ +-- )" +-- allocate_update = "" +-- allocate_commit = "" +-- + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS fr_dhcp_allocate_previous_or_new_framedipaddress; +CREATE PROCEDURE fr_dhcp_allocate_previous_or_new_framedipaddress ( + IN v_pool_name VARCHAR(30), + IN v_gateway VARCHAR(15), + IN v_pool_key VARCHAR(30), + IN v_lease_duration INT, + IN v_requested_address VARCHAR(15) +) +SQL SECURITY INVOKER +proc:BEGIN + DECLARE r_address VARCHAR(15); + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + SET TRANSACTION ISOLATION LEVEL READ COMMITTED; + + START TRANSACTION; + + -- Reissue an existing IP address lease when re-authenticating a session + -- + SELECT framedipaddress INTO r_address + FROM dhcpippool + WHERE pool_name = v_pool_name + AND expiry_time > NOW() + AND pool_key = v_pool_key + AND `status` IN ('dynamic', 'static') + LIMIT 1 + FOR UPDATE; +-- FOR UPDATE SKIP LOCKED; -- Better performance, but limited support + + -- NOTE: You should enable SKIP LOCKED here (as well as any other + -- instances) if your database server supports it. If it is not + -- supported and you are not running a multi-master cluster (e.g. + -- Galera or MaxScale) then you should instead consider using the + -- SP in procedure-no-skip-locked.sql which will be faster and + -- less likely to result in thread starvation under highly + -- concurrent load. + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- SELECT framedipaddress INTO r_address + -- FROM dhcpippool + -- WHERE pool_name = v_pool_name + -- AND pool_key = v_pool_key + -- AND `status` IN ('dynamic', 'static') + -- LIMIT 1 + -- FOR UPDATE; + -- -- FOR UPDATE SKIP LOCKED; -- Better performance, but limited support + + -- Issue the requested IP address if it is available + -- + IF r_address IS NULL AND v_requested_address <> '0.0.0.0' THEN + SELECT framedipaddress INTO r_address + FROM dhcpippool + WHERE pool_name = v_pool_name + AND framedipaddress = v_requested_address + AND `status` = 'dynamic' + AND ( pool_key = v_pool_key OR expiry_time < NOW() ) + FOR UPDATE; +-- FOR UPDATE SKIP LOCKED; -- Better performance, but limited support + END IF; + + -- If we didn't reallocate a previous address then pick the least + -- recently used address from the pool which maximises the likelihood + -- of re-assigning the other addresses to their recent user + -- + IF r_address IS NULL THEN + SELECT framedipaddress INTO r_address + FROM dhcpippool + WHERE pool_name = v_pool_name + AND expiry_time < NOW() + AND `status` = 'dynamic' + ORDER BY + expiry_time + LIMIT 1 + FOR UPDATE; +-- FOR UPDATE SKIP LOCKED; -- Better performance, but limited support + END IF; + + -- Return nothing if we failed to allocated an address + -- + IF r_address IS NULL THEN + COMMIT; + LEAVE proc; + END IF; + + -- Update the pool having allocated an IP address + -- + UPDATE dhcpippool + SET + gateway = v_gateway, + pool_key = v_pool_key, + expiry_time = NOW() + INTERVAL v_lease_duration SECOND + WHERE framedipaddress = r_address; + + COMMIT; + + -- Return the address that we allocated + SELECT r_address; + +END$$ + +DELIMITER ; diff --git a/raddb/mods-config/sql/ippool-dhcp/mysql/queries.conf b/raddb/mods-config/sql/ippool-dhcp/mysql/queries.conf new file mode 100644 index 0000000..6aaecb1 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mysql/queries.conf @@ -0,0 +1,221 @@ +# -*- text -*- +# +# ippool-dhcp/mysql/queries.conf -- MySQL queries for rlm_sqlippool +# +# $Id$ + +# ***************** +# * DHCP DISCOVER * +# ***************** + +# +# This series of queries allocates an IP address + +# If using MySQL < 8.0.1 then remove SKIP LOCKED +# +# Attempt to find the most recent existing IP address for the client +# +allocate_existing = "\ + SELECT framedipaddress FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND `status` IN ('dynamic', 'static') \ + ORDER BY expiry_time DESC LIMIT 1 FOR UPDATE SKIP LOCKED" + +# +# Determine whether the requested IP address is available +# +allocate_requested = "\ + SELECT framedipaddress FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ + AND `status` = 'dynamic' \ + AND expiry_time < NOW() \ + FOR UPDATE SKIP LOCKED" + +# +# If the existing address can't be found this query will be run to +# find a free address +# +allocate_find = "\ + SELECT framedipaddress FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND expiry_time < NOW() \ + AND `status` = 'dynamic' \ + ORDER BY expiry_time LIMIT 1 FOR UPDATE SKIP LOCKED" + +# +# The ORDER BY clause of this query tries to allocate the same IP-address +# which the user last had. Ensure that pool_key is unique to the user +# within a given pool. +# + +# +# Alternatively do the operations in one query. Depending on transaction +# isolation mode, this can cause deadlocks +# +#allocate_find = "\ +# (SELECT framedipaddress, 1 AS o FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# AND `status` IN ('dynamic', 'static') \ +# ORDER BY expiry_time DESC LIMIT 1 FOR UPDATE SKIP LOCKED \ +# ) UNION ( \ +# SELECT framedipaddress, 2 AS o FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND `status` = 'dynamic' \ +# AND ( pool_key = '${pool_key}' OR expiry_time < NOW() ) \ +# FOR UPDATE SKIP LOCKED \ +# ) UNION ( \ +# SELECT framedipaddress, 3 AS o FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < NOW() \ +# AND `status` = 'dynamic' \ +# ORDER BY expiry_time LIMIT 1 FOR UPDATE SKIP LOCKED \ +# ) ORDER BY o \ +# LIMIT 1" + +# +# If you prefer to allocate a random IP address every time, use this query instead. +# Note: This is very slow if you have a lot of free IPs. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < NOW() \ +# AND `status` = 'dynamic' \ +# ORDER BY \ +# RAND() \ +# LIMIT 1 \ +# FOR UPDATE" + +# +# The above query again, but with SKIP LOCKED. This requires MySQL >= 8.0.1, +# and InnoDB. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < NOW() \ +# AND `status` = 'dynamic' \ +# ORDER BY \ +# RAND() \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED" + +# +# If an IP could not be allocated, check to see if the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be +# commented out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}' \ + LIMIT 1" + +# +# This is the final IP Allocation query, which saves the allocated ip details. +# +allocate_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '%{DHCP-Gateway-IP-Address}', pool_key = '${pool_key}', \ + expiry_time = NOW() + INTERVAL ${offer_duration} SECOND \ + WHERE framedipaddress = '%I'" + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# +#allocate_begin = "" +#allocate_find = "\ +# CALL fr_dhcp_allocate_previous_or_new_framedipaddress( \ +# '%{control:${pool_name}}', \ +# '%{DHCP-Gateway-IP-Address}', \ +# '${pool_key}', \ +# ${offer_duration}, \ +# '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# )" +#allocate_update = "" +#allocate_commit = "" + + +# **************** +# * DHCP REQUEST * +# **************** + +# +# This query revokes any active offers for addresses that a client is not +# requesting when a DHCP REQUEST packet arrives +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = NOW() \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress <> '%{DHCP-Requested-IP-Address}' \ + AND expiry_time > NOW() \ + AND `status` = 'dynamic'" + +# +# This query extends an existing lease (or offer) when a DHCP REQUEST packet +# arrives. This query must update a row when a lease is succesfully requested +# - queries that update no rows will result in a "notfound" response to +# the module which by default will give a DHCP-NAK reply. In this example +# incrementing "counter" is used to achieve this. +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = NOW() + INTERVAL ${lease_duration} SECOND, \ + counter = counter + 1 \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}'" + + +# **************** +# * DHCP RELEASE * +# **************** + +# +# This query frees an IP address when a DHCP RELEASE packet arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = NOW() \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Client-IP-Address}' \ + AND `status` = 'dynamic'" + + +# +# This query is not applicable to DHCP +# +on_clear = "" + + +# **************** +# * DHCP DECLINE * +# **************** + +# +# This query marks an IP address as declined when a DHCP Decline +# packet arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET status = 'declined' \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Requested-IP-Address}'" diff --git a/raddb/mods-config/sql/ippool-dhcp/mysql/schema.sql b/raddb/mods-config/sql/ippool-dhcp/mysql/schema.sql new file mode 100644 index 0000000..d8b1219 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/mysql/schema.sql @@ -0,0 +1,21 @@ +-- +-- Table structure for table 'dhcpippool' +-- +-- See also "procedure.sql" in this directory for a stored procedure +-- that is much faster. +-- + +CREATE TABLE dhcpippool ( + id int unsigned NOT NULL auto_increment, + pool_name varchar(30) NOT NULL, + framedipaddress varchar(15) NOT NULL default '', + pool_key varchar(30) NOT NULL default '', + gateway varchar(15) NOT NULL default '', + expiry_time DATETIME NOT NULL default NOW(), + `status` ENUM('dynamic', 'static', 'declined', 'disabled') DEFAULT 'dynamic', + counter int unsigned NOT NULL default 0, + PRIMARY KEY (id), + KEY dhcpippool_poolname_expire (pool_name, expiry_time), + KEY framedipaddress (framedipaddress), + KEY dhcpippool_poolname_poolkey_ipaddress (pool_name, pool_key, framedipaddress) +) ENGINE=InnoDB; diff --git a/raddb/mods-config/sql/ippool-dhcp/oracle/procedure.sql b/raddb/mods-config/sql/ippool-dhcp/oracle/procedure.sql new file mode 100644 index 0000000..84b4596 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/oracle/procedure.sql @@ -0,0 +1,217 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- BEGIN; SELECT FOR UPDATE; UPDATE; COMMIT; -> SELECT sp() FROM dual +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- SELECT fr_dhcp_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{DHCP-Gateway-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration}, \ +-- '%{%{${req_attribute_name}}:-0.0.0.0}' \ +-- ) FROM dual" +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE OR REPLACE FUNCTION fr_dhcp_allocate_previous_or_new_framedipaddress ( + v_pool_name IN VARCHAR2, + v_gateway IN VARCHAR2, + v_pool_key IN VARCHAR2, + v_lease_duration IN INTEGER, + v_requested_address IN VARCHAR2 +) +RETURN varchar2 IS + PRAGMA AUTONOMOUS_TRANSACTION; + r_address varchar2(15); +BEGIN + + -- Reissue an existing IP address lease when re-authenticating a session + -- + BEGIN + SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + SELECT id FROM ( + SELECT * + FROM dhcpippool + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = v_pool_name + AND expiry_time > current_timestamp + AND pool_key = v_pool_key + AND dhcpstatus.status IN ('dynamic', 'static') + ) WHERE ROWNUM <= 1 + ) FOR UPDATE SKIP LOCKED; + EXCEPTION + WHEN NO_DATA_FOUND THEN + r_address := NULL; + END; + + -- Oracle >= 12c version of the above query + -- + -- BEGIN + -- SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + -- SELECT id FROM dhcpippool + -- JOIN dhcpstatus + -- ON dhcpstatus.status_id = dhcpippool.status_id + -- WHERE pool_name = v_pool_name + -- AND expiry_time > current_timestamp + -- AND pool_key = v_pool_key + -- AND dhcpstatus.status IN ('dynamic', 'static') + -- FETCH FIRST 1 ROWS ONLY + -- ) FOR UPDATE SKIP LOCKED; + -- EXCEPTION + -- WHEN NO_DATA_FOUND THEN + -- r_address := NULL; + -- END; + + + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- BEGIN + -- SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + -- SELECT id FROM ( + -- SELECT * + -- FROM dhcpippool + -- JOIN dhcpstatus + -- ON dhcpstatus.status_id = dhcpippool.status_id + -- WHERE pool_name = v_pool_name + -- AND pool_key = v_pool_key + -- AND dhcpstatus.status IN ('dynamic', 'static') + -- ) WHERE ROWNUM <= 1 + -- ) FOR UPDATE SKIP LOCKED; + -- EXCEPTION + -- WHEN NO_DATA_FOUND THEN + -- r_address := NULL; + -- END; + + -- Oracle >= 12c version of the above query + -- + -- BEGIN + -- SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + -- SELECT id FROM dhcpippool + -- JOIN dhcpstatus + -- ON dhcpstatus.status_id = dhcpippool.status_id + -- WHERE pool_name = v_pool_name + -- AND pool_key = v_pool_key + -- AND dhcpstatus.status IN ('dynamic', 'static') + -- FETCH FIRST 1 ROWS ONLY + -- ) FOR UPDATE SKIP LOCKED; + -- EXCEPTION + -- WHEN NO_DATA_FOUND THEN + -- r_address := NULL; + -- END; + + + + -- Issue the requested IP address if it is available + -- + IF r_address IS NULL AND v_requested_address <> '0.0.0.0' THEN + BEGIN + SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + SELECT id FROM ( + SELECT * + FROM dhcpippool + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = v_pool_name + AND framedipaddress = v_requested_address + AND dhcpstatus.status = 'dynamic' + AND expiry_time < CURRENT_TIMESTAMP + ) WHERE ROWNUM <= 1 + ) FOR UPDATE SKIP LOCKED; + EXCEPTION + WHEN NO_DATA_FOUND THEN + r_address := NULL; + END; + END IF; + + -- Oracle >= 12c version of the above query + -- + -- IF r_address IS NULL AND v_requested_address <> '0.0.0.0' THEN + -- BEGIN + -- SELECT framedipaddress INTO r_address FROM dhcpippool WHERE id IN ( + -- SELECT id FROM dhcpippool + -- JOIN dhcpstatus + -- ON dhcpstatus.status_id = dhcpippool.status_id + -- WHERE pool_name = v_pool_name + -- AND framedipaddress = v_requested_address + -- AND dhcpstatus.status = 'dynamic' + -- AND expiry_time < CURRENT_TIMESTAMP + -- FETCH FIRST 1 ROWS ONLY + -- ) FOR UPDATE SKIP LOCKED; + -- EXCEPTION + -- WHEN NO_DATA_FOUND THEN + -- r_address := NULL; + -- END; + -- END IF; + + + + -- If we didn't reallocate a previous address then pick the least + -- recently used address from the pool which maximises the likelihood + -- of re-assigning the other addresses to their recent user + -- + IF r_address IS NULL THEN + DECLARE + l_cursor sys_refcursor; + BEGIN + OPEN l_cursor FOR + SELECT framedipaddress + FROM dhcpippool + JOIN dhcpstatus + ON dhcpstatus.status_id = dhcpippool.status_id + WHERE pool_name = v_pool_name + AND expiry_time < CURRENT_TIMESTAMP + AND dhcpstatus.status = 'dynamic' + ORDER BY expiry_time + FOR UPDATE SKIP LOCKED; + FETCH l_cursor INTO r_address; + CLOSE l_cursor; + EXCEPTION + WHEN NO_DATA_FOUND THEN + r_address := NULL; + END; + END IF; + + -- Return nothing if we failed to allocated an address + -- + IF r_address IS NULL THEN + COMMIT; + RETURN r_address; + END IF; + + -- Update the pool having allocated an IP address + -- + UPDATE dhcpippool + SET + gateway = v_gateway, + pool_key = v_pool_key, + expiry_time = CURRENT_TIMESTAMP + v_lease_duration * INTERVAL '1' SECOND(1) + WHERE framedipaddress = r_address; + + -- Return the address that we allocated + COMMIT; + RETURN r_address; + +END; + diff --git a/raddb/mods-config/sql/ippool-dhcp/oracle/queries.conf b/raddb/mods-config/sql/ippool-dhcp/oracle/queries.conf new file mode 100644 index 0000000..0fcffc3 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/oracle/queries.conf @@ -0,0 +1,200 @@ +# -*- text -*- +# +# ippool-dhcp/oracle/queries.conf -- Oracle queries for rlm_sqlippool +# +# $Id$ + +start_begin = "commit" +alive_begin = "commit" +stop_begin = "commit" +on_begin = "commit" +off_begin = "commit" + + +# ***************** +# * DHCP DISCOVER * +# ***************** + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# Oracle's locking mechanism limitations prevents the use of single queries +# that can either find a client's existing address or the first available one. +# +allocate_begin = "" +allocate_find = "\ + SELECT fr_dhcp_allocate_previous_or_new_framedipaddress( \ + '%{control:${pool_name}}', \ + '%{DHCP-Gateway-IP-Address}', \ + '${pool_key}', \ + '${offer_duration}', \ + '%{%{${req_attribute_name}}:-0.0.0.0}' \ + ) FROM dual" +allocate_update = "" +allocate_commit = "" + + +# +# If you prefer to allocate a random IP address every time, use this query instead +# Note: This is very slow if you have a lot of free IPs. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( \ +# SELECT id FROM ( \ +# SELECT * \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < current_timestamp \ +# AND dhcpstatus.status = 'dynamic' \ +# ORDER BY DBMS_RANDOM.VALUE \ +# ) WHERE ROWNUM <= 1 \ +# ) FOR UPDATE" + +# +# The above query again, but with SKIP LOCKED. This requires Oracle > 11g. +# It may work in 9i and 10g, but is not documented, so YMMV. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( \ +# SELECT id FROM (\ +# SELECT * \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < current_timestamp \ +# AND dhcpstatus.status = 'dynamic' \ +# ORDER BY DBMS_RANDOM.VALUE \ +# ) WHERE ROWNUM <= 1 \ +# ) FOR UPDATE SKIP LOCKED" + +# +# A tidier version that needs Oracle >= 12c +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( +# SELECT id FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < current_timestamp \ +# AND dhcpstatus.status = 'dynamic' \ +# ORDER BY DBMS_RANDOM.VALUE \ +# FETCH FIRST 1 ROWS ONLY +# ) FOR UPDATE SKIP LOCKED" + +# +# If an IP could not be allocated, check to see whether the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be commented +# out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM (\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}'\ + ) \ + WHERE ROWNUM = 1" + +# +# This query marks the IP address handed out by "allocate-find" as used +# for the period of "offer_duration" after which time it may be reused. +# Only needed if allocate_find is not using the stored procedure and therefore +# not updating the lease +# +#allocate_update = "\ +# UPDATE ${ippool_table} \ +# SET \ +# gateway = '%{DHCP-Gateway-IP-Address}', \ +# pool_key = '${pool_key}', \ +# expiry_time = current_timestamp + INTERVAL '${offer_duration}' second(1) \ +# WHERE framedipaddress = '%I'" + + +# **************** +# * DHCP REQUEST * +# **************** + +# +# This query revokes any active offers for addresses that a client is not +# requesting when a DHCP REQUEST packet arrives +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '0', \ + expiry_time = current_timestamp - INTERVAL '1' second(1) \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress <> '%{DHCP-Requested-IP-Address}' \ + AND expiry_time > current_timestamp \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" +start_commit = "COMMIT" + +# +# This query extends an existing lease (or offer) when a DHCP REQUEST packet +# arrives. This query must update a row when a lease is succesfully requested +# - queries that update no rows will result in a "notfound" response to +# the module which by default will give a DHCP-NAK reply. In this example +# incrementing "counter" is used to achieve this. +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = current_timestamp + INTERVAL '${lease_duration}' second(1), \ + counter = counter + 1 \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}'" +alive_commit = "COMMIT" + + +# **************** +# * DHCP RELEASE * +# **************** + +# +# This query frees an IP address when a DHCP RELEASE packet arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '0', \ + expiry_time = current_timestamp - INTERVAL '1' second(1) \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Client-IP-Address}' \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" +stop_commit = "COMMIT" + + +# +# This query is not applicable to DHCP +# +on_clear = "" + + +# **************** +# * DHCP DECLINE * +# **************** + +# +# This query marks an IP address as declined when a DHCP Decline +# packet arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET status_id = (SELECT status_id FROM dhcpstatus WHERE status = 'declined') \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Requested-IP-Address}'" +off_commit = "COMMIT" + diff --git a/raddb/mods-config/sql/ippool-dhcp/oracle/schema.sql b/raddb/mods-config/sql/ippool-dhcp/oracle/schema.sql new file mode 100644 index 0000000..32d28bb --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/oracle/schema.sql @@ -0,0 +1,28 @@ +CREATE TABLE dhcpstatus ( + status_id INT PRIMARY KEY, + status VARCHAR(10) NOT NULL +); + +INSERT INTO dhcpstatus (status_id, status) VALUES (1, 'dynamic'); +INSERT INTO dhcpstatus (status_id, status) VALUES (2, 'static'); +INSERT INTO dhcpstatus (status_id, status) VALUES (3, 'declined'); +INSERT INTO dhcpstatus (status_id, status) VALUES (4, 'disabled'); + +CREATE SEQUENCE dhcpippool_seq START WITH 1 INCREMENT BY 1; + +CREATE TABLE dhcpippool ( + id INT DEFAULT ON NULL dhcpippool_seq.NEXTVAL PRIMARY KEY, + pool_name VARCHAR(30) NOT NULL, + framedipaddress VARCHAR(15) NOT NULL, + pool_key VARCHAR(30) DEFAULT '', + gateway VARCHAR(15) DEFAULT '', + expiry_time timestamp(0) DEFAULT CURRENT_TIMESTAMP, + status_id INT DEFAULT 1, + counter INT DEFAULT 0, + FOREIGN KEY (status_id) REFERENCES dhcpstatus(status_id) +); + +CREATE INDEX dhcpippool_poolname_expire ON dhcpippool (pool_name, expiry_time); +CREATE INDEX dhcpippool_framedipaddress ON dhcpippool (framedipaddress); +CREATE INDEX dhcpippool_poolname_poolkey_ipaddress ON dhcpippool (pool_name, pool_key, framedipaddress); + diff --git a/raddb/mods-config/sql/ippool-dhcp/postgresql/procedure.sql b/raddb/mods-config/sql/ippool-dhcp/postgresql/procedure.sql new file mode 100644 index 0000000..379a349 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/postgresql/procedure.sql @@ -0,0 +1,119 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- START TRANSACTION; SELECT FOR UPDATE; UPDATE; COMMIT; -> SELECT sp() +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- SELECT fr_dhcp_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{DHCP-Gateway-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration}, \ +-- '%{%{${req_attribute_name}}:-0.0.0.0}' \ +-- )" +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE OR REPLACE FUNCTION fr_dhcp_allocate_previous_or_new_framedipaddress ( + v_pool_name VARCHAR(64), + v_gateway VARCHAR(16), + v_pool_key VARCHAR(64), + v_lease_duration INT, + v_requested_address INET +) +RETURNS inet +LANGUAGE plpgsql +AS $$ +DECLARE + r_address INET; +BEGIN + + -- Reissue an existing IP address lease when re-authenticating a session + -- + WITH ips AS ( + SELECT framedipaddress FROM dhcpippool + WHERE pool_name = v_pool_name + AND pool_key = v_pool_key + AND expiry_time > NOW() + AND status IN ('dynamic', 'static') + LIMIT 1 FOR UPDATE SKIP LOCKED ) + UPDATE dhcpippool + SET expiry_time = NOW() + v_lease_duration * interval '1 sec' + FROM ips WHERE dhcpippool.framedipaddress = ips.framedipaddress + RETURNING dhcpippool.framedipaddress INTO r_address; + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- WITH ips AS ( + -- SELECT framedipaddress FROM dhcpippool + -- WHERE pool_name = v_pool_name + -- AND pool_key = v_pool_key + -- AND status IN ('dynamic', 'static') + -- LIMIT 1 FOR UPDATE SKIP LOCKED ) + -- UPDATE dhcpippool + -- SET expiry_time = NOW + v_lease_duration * interval '1 sec' + -- FROM ips WHERE dhcpippool.framedipaddress = ips.framedipaddress + -- RETURNING dhcpippool.framedipaddress INTO r_address; + + -- Issue the requested IP address if it is available + -- + IF r_address IS NULL AND v_requested_address != '0.0.0.0' THEN + WITH ips AS ( + SELECT framedipaddress FROM dhcpippool + WHERE pool_name = v_pool_name + AND framedipaddress = v_requested_address + AND status = 'dynamic' + AND ( pool_key = v_pool_key OR expiry_time < NOW() ) + LIMIT 1 FOR UPDATE SKIP LOCKED ) + UPDATE dhcpippool + SET pool_key = v_pool_key, + expiry_time = NOW() + v_lease_duration * interval '1 sec', + gateway = v_gateway + FROM ips WHERE dhcpippool.framedipaddress = ips.framedipaddress + RETURNING dhcpippool.framedipaddress INTO r_address; + END IF; + + -- If we didn't reallocate a previous address then pick the least + -- recently used address from the pool which maximises the likelihood + -- of re-assigning the other addresses to their recent user + -- + IF r_address IS NULL THEN + WITH ips AS ( + SELECT framedipaddress FROM dhcpippool + WHERE pool_name = v_pool_name + AND expiry_time < NOW() + AND status = 'dynamic' + ORDER BY expiry_time + LIMIT 1 FOR UPDATE SKIP LOCKED ) + UPDATE dhcpippool + SET pool_key = v_pool_key, + expiry_time = NOW() + v_lease_duration * interval '1 sec', + gateway = v_gateway + FROM ips WHERE dhcpippool.framedipaddress = ips.framedipaddress + RETURNING dhcpippool.framedipaddress INTO r_address; + END IF; + + -- Return the address that we allocated + RETURN r_address; + +END +$$; diff --git a/raddb/mods-config/sql/ippool-dhcp/postgresql/queries.conf b/raddb/mods-config/sql/ippool-dhcp/postgresql/queries.conf new file mode 100644 index 0000000..632fc70 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/postgresql/queries.conf @@ -0,0 +1,291 @@ +# -*- text -*- +# +# ippool-dhcp/postgresql/queries.conf -- PostgreSQL queries for rlm_sqlippool +# +# $Id$ + +# ***************** +# * DHCP DISCOVER * +# ***************** + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# +# This requires PostgreSQL >= 9.5 as SKIP LOCKED is used. +# +# The "NO LOAD BALANCE" comment is included here to indicate to a PgPool +# system that this needs to be a write transaction. PgPool itself cannot +# detect this from the statement alone. If you are using PgPool and do not +# have this comment, the query may go to a read only server, and will fail. +# This has no negative effect if you are not using PgPool. +# +allocate_begin = "" +allocate_find = "\ + /*NO LOAD BALANCE*/ \ + SELECT fr_dhcp_allocate_previous_or_new_framedipaddress( \ + '%{control:${pool_name}}', \ + '%{DHCP-Gateway-IP-Address}', \ + '${pool_key}', \ + '${offer_duration}', \ + '%{%{${req_attribute_name}}:-0.0.0.0}' \ + )" +allocate_update = "" +allocate_commit = "" + +# +# If stored procedures are not able to be used, the following queries can +# be used. +# Comment out all the above queries and choose the appropriate "allocate_find" +# to match the desired outcome and also the version of "allocate_update" below. +# + +# +# This sequence of queries allocates an IP address from the Pool +# +#allocate_begin = "BEGIN" + + +# Attempt to find the most recent existing IP address for the client +# +#allocate_existing = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# AND status IN ('dynamic', 'static') \ +# ORDER BY expiry_time DESC \ +# LIMIT 1 \ +# FOR UPDATE" + +# The same query with SKIP LOCKED - requires PostgreSQL >= 9.5 +# allocate_existing = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# AND status IN ('dynamic', 'static') \ +# ORDER BY expiry_time DESC \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED" + + +# +# Determine whether the requested IP address is available +# +#allocate_requested = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND status = 'dynamic' \ +# AND expiry_time < 'now'::timestamp(0) \ +# FOR UPDATE" + +# The same query with SKIP LOCKED - requires PostgreSQL >= 9.5 +#allocate_requested = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND status = 'dynamic' \ +# AND expiry_time < 'now'::timestamp(0) \ +# FOR UPDATE SKIP LOCKED" + + +# +# If the existing address can't be found this query will be run to +# find a free address +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < 'now'::timestamp(0) \ +# AND status = 'dynamic' \ +# ORDER BY expiry_time \ +# LIMIT 1 \ +# FOR UPDATE" + +# The same query with SKIP LOCKED - requires PostgreSQL >= 9.5 +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < 'now'::timestamp(0) \ +# AND status = 'dynamic' \ +# ORDER BY expiry_time \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED" + +# +# If you prefer to allocate a random IP address every time, use this query instead +# Note: This is very slow if you have a lot of free IPs. +# Use of either of these next two queries should have the allocate_begin line commented out +# and allocate_update below un-commented. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' AND expiry_time < 'now'::timestamp(0) \ +# AND status = 'dynamic' \ +# ORDER BY RANDOM() \ +# LIMIT 1 \ +# FOR UPDATE" + +# +# The above query again, but with SKIP LOCKED. This requires PostgreSQL >= 9.5. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' AND expiry_time < 'now'::timestamp(0) \ +# AND status = 'dynamic' \ +# ORDER BY RANDOM() \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED" + +# +# This query marks the IP address handed out by "allocate-find" as used +# for the period of "lease_duration" after which time it may be reused. +# +#allocate_update = "\ +# UPDATE ${ippool_table} \ +# SET \ +# gateway = '%{DHCP-Gateway-IP-Address}', \ +# pool_key = '${pool_key}', \ +# expiry_time = 'now'::timestamp(0) + '${offer_duration} second'::interval \ +# WHERE framedipaddress = '%I'" + + +# +# Alternatively, merge the matching of existing IP and free IP into a single query +# This version does the update as well - so allocate_begin, allocate_update and +# allocate_commit should be blank +# +#allocate_begin = "" +#allocate_find = "\ +# WITH found AS ( \ +# WITH existing AS ( \ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# ORDER BY expiry_time DESC \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED \ +# ), requested AS ( \ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND status = 'dynamic' \ +# AND ( pool_key = '${pool_key}' OR expiry_time < 'now'::timestamp(0) ) \ +# FOR UPDATE SKIP LOCKED \ +# ), new AS ( \ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < 'now'::timestamp(0) \ +# AND status = 'dynamic' \ +# ORDER BY expiry_time \ +# LIMIT 1 \ +# FOR UPDATE SKIP LOCKED \ +# ) \ +# SELECT framedipaddress, 1 AS o FROM existing \ +# UNION ALL \ +# SELECT framedipaddress, 2 AS o FROM requested \ +# UNION ALL \ +# SELECT framedipaddress, 3 AS o FROM new \ +# ORDER BY o LIMIT 1 \ +# ) \ +# UPDATE ${ippool_table} \ +# SET pool_key = '${pool_key}', \ +# expiry_time = 'now'::timestamp(0) + '${offer_duration} second'::interval, \ +# gateway = '%{DHCP-Gateway-IP-Address}' \ +# FROM found \ +# WHERE found.framedipaddress = ${ippool_table}.framedipaddress \ +# RETURNING found.framedipaddress" +#allocate_update = "" +#allocate_commit = "" + + +# +# If an IP could not be allocated, check to see whether the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be commented +# out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}' \ + LIMIT 1" + + +# **************** +# * DHCP REQUEST * +# **************** + +# +# This query revokes any active offers for addresses that a client is not +# requesting when a DHCP REQUEST packet arrives, i.e, each server (sharing the +# same database) may have simultaneously offered a unique address. +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = 'now'::timestamp(0) - '1 second'::interval \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress <> '%{DHCP-Requested-IP-Address}' \ + AND expiry_time > 'now'::timestamp(0) \ + AND status = 'dynamic'" + +# +# This query extends an existing lease (or offer) when a DHCP REQUEST packet +# arrives. This query must update a row when a lease is succesfully requested +# - queries that update no rows will result in a "notfound" response to +# the module which by default will give a DHCP-NAK reply. In this example +# incrementing "counter" is used to achieve this. +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = 'now'::timestamp(0) + '${lease_duration} second'::interval, \ + counter = counter + 1 \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}'" + + +# **************** +# * DHCP RELEASE * +# **************** + +# +# This query frees an IP address when a DHCP RELEASE packet arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = 'now'::timestamp(0) - '1 second'::interval \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Client-IP-Address}' \ + AND status = 'dynamic'" + + +# +# This query is not applicable to DHCP +# +on_clear = "" + + +# **************** +# * DHCP DECLINE * +# **************** + +# +# This query marks an IP address as declined when a DHCP DECLINE packet +# arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET status = 'declined' \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Requested-IP-Address}'" diff --git a/raddb/mods-config/sql/ippool-dhcp/postgresql/schema.sql b/raddb/mods-config/sql/ippool-dhcp/postgresql/schema.sql new file mode 100644 index 0000000..af86889 --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/postgresql/schema.sql @@ -0,0 +1,23 @@ +-- +-- Table structure for table 'dhcpippool' +-- +-- See also "procedure.sql" in this directory for +-- a stored procedure that gives much faster response. +-- + +CREATE TYPE dhcp_status AS ENUM ('dynamic', 'static', 'declined', 'disabled'); + +CREATE TABLE dhcpippool ( + id BIGSERIAL PRIMARY KEY, + pool_name varchar(64) NOT NULL, + FramedIPAddress INET NOT NULL, + pool_key VARCHAR(64) NOT NULL default '0', + gateway VARCHAR(16) NOT NULL default '', + expiry_time TIMESTAMP(0) without time zone NOT NULL default NOW(), + status dhcp_status DEFAULT 'dynamic', + counter INT NOT NULL default 0 +); + +CREATE INDEX dhcpippool_poolname_expire ON dhcpippool USING btree (pool_name, expiry_time); +CREATE INDEX dhcpippool_framedipaddress ON dhcpippool USING btree (framedipaddress); +CREATE INDEX dhcpippool_poolname_poolkey_ipaddress ON dhcpippool USING btree (pool_name, pool_key, framedipaddress); diff --git a/raddb/mods-config/sql/ippool-dhcp/sqlite/queries.conf b/raddb/mods-config/sql/ippool-dhcp/sqlite/queries.conf new file mode 100644 index 0000000..d99e09b --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/sqlite/queries.conf @@ -0,0 +1,236 @@ +# -*- text -*- +# +# ippool-dhcp/sqlite/queries.conf -- SQLite queries for rlm_sqlippool +# +# $Id$ + +# ***************** +# * DHCP DISCOVER * +# ***************** + +# +# SQLite does not implement SELECT FOR UPDATE which is normally used to place +# an exclusive lock over rows to prevent the same address from being +# concurrently selected for allocation to multiple users. +# +# The most granular read-blocking lock that SQLite has is an exclusive lock +# over the database, so that's what we use. All locking in SQLite is performed +# over the entire database and we perform a row update for any IP that we +# allocate, requiring an exclusive lock. Taking the exclusive lock from the +# start of the transaction (even if it were not required to guard the SELECT) +# is actually quicker than if we deferred it causing SQLite to "upgrade" the +# automatic shared lock for the transaction to an exclusive lock for the +# subsequent UPDATE. +# +allocate_begin = "BEGIN EXCLUSIVE" +allocate_commit = "COMMIT" + +# +# Attempt to find the most recent existing IP address for the client +# +allocate_existing = "\ + SELECT framedipaddress \ + FROM ${ippool_table} \ + JOIN dhcpstatus \ + ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND status IN ('dynamic', 'static') \ + ORDER BY expiry_time DESC \ + LIMIT 1" + +# +# Determine whether the requested IP address is available +# +allocate_requested = "\ + SELECT framedipaddress \ + FROM ${ippool_table} \ + JOIN dhcpstatus \ + ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ + AND status = 'dynamic' \ + AND expiry_time < datetime('now')" + +# +# If the existing address can't be found this query will be run to +# find a free address +# +allocate_find = "\ + SELECT framedipaddress \ + FROM ${ippool_table} \ + JOIN dhcpstatus \ + ON ${ippool_table}.status_id = dhcpstatus.status_id \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND expiry_time < datetime('now') \ + AND status = 'dynamic' \ + ORDER BY expiry_time LIMIT 1" + +# +# This series of queries allocates an IP address +# +# Either pull the most recent allocated IP for this client or the +# oldest expired one. The first sub query returns the most recent +# lease for the client (if there is one), the second returns the +# oldest expired one. +# Sorting the result by expiry_time DESC will return the client specific +# IP if it exists, otherwise an expired one. +# +#allocate_find = "\ +# SELECT framedipaddress, 1 AS o \ +# FROM ( \ +# SELECT framedipaddress \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND pool_key = '${pool_key}' \ +# AND status IN ('dynamic', 'static') \ +# ORDER BY expiry_time DESC \ +# LIMIT 1 \ +# ) UNION \ +# SELECT framedipaddress, 2 AS o \ +# FROM ( \ +# SELECT framedipaddress \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND framedipaddress = '%{%{${req_attribute_name}}:-0.0.0.0}' \ +# AND status = 'dynamic' \ +# AND ( pool_key = '${pool_key}' OR expiry_time < datetime('now') ) \ +# ) UNION \ +# SELECT framedipaddress, 3 AS o \ +# FROM ( \ +# SELECT framedipaddress \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < datetime('now') \ +# AND status = 'dynamic' \ +# ORDER BY expiry_time LIMIT 1 \ +# ) \ +# ORDER BY o \ +# LIMIT 1" + +# +# If you prefer to allocate a random IP address every time, i +# use this query instead +# Note: This is very slow if you have a lot of free IPs. +# + +#allocate_find = "\ +# SELECT framedipaddress \ +# FROM ${ippool_table} \ +# JOIN dhcpstatus \ +# ON ${ippool_table}.status_id = dhcpstatus.status_id \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < datetime('now') \ +# AND status = 'dynamic' \ +# ORDER BY RAND() \ + + +# +# If an IP could not be allocated, check to see if the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be +# commented out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}' \ + LIMIT 1" + +# +# This is the final IP Allocation query, which saves the allocated ip details +# +allocate_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '%{DHCP-Gateway-IP-Address}', \ + pool_key = '${pool_key}', \ + expiry_time = datetime(strftime('%%s', 'now') + ${offer_duration}, 'unixepoch') \ + WHERE framedipaddress = '%I'" + + +# **************** +# * DHCP REQUEST * +# **************** + +# +# This query revokes any active offers for addresses that a client is not +# requesting when a DHCP REQUEST packet arrives +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = datetime('now') \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress <> '%{DHCP-Requested-IP-Address}' \ + AND expiry_time > datetime('now') \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" + +# +# This query extends an existing lease (or offer) when a DHCP REQUEST packet +# arrives. This query must update a row when a lease is succesfully requested +# - queries that update no rows will result in a "notfound" response to +# the module which by default will give a DHCP-NAK reply. In this example +# incrementing "counter" is used to achieve this. +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = datetime(strftime('%%s', 'now') + ${lease_duration}, 'unixepoch'), \ + counter = counter + 1 \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}'" + + +# **************** +# * DHCP RELEASE * +# **************** + +# +# This query frees an IP address when a DHCP RELEASE packet arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + gateway = '', \ + pool_key = '', \ + expiry_time = datetime('now') \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Client-IP-Address}' \ + AND ${ippool_table}.status_id IN \ + (SELECT status_id FROM dhcpstatus WHERE status = 'dynamic')" + + +# +# This query is not applicable to DHCP +# +on_clear = "" + + +# **************** +# * DHCP DECLINE * +# **************** + +# +# This query marks an IP address as declined when a DHCP Decline +# packet arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET status_id = (SELECT status_id FROM dhcpstatus WHERE status = 'declined') \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{DHCP-Requested-IP-Address}'" + diff --git a/raddb/mods-config/sql/ippool-dhcp/sqlite/schema.sql b/raddb/mods-config/sql/ippool-dhcp/sqlite/schema.sql new file mode 100644 index 0000000..339d58d --- /dev/null +++ b/raddb/mods-config/sql/ippool-dhcp/sqlite/schema.sql @@ -0,0 +1,25 @@ +-- +-- Table structure for table 'dhcpippool' +-- +CREATE TABLE dhcpstatus ( + status_id int PRIMARY KEY, + status varchar(10) NOT NULL +); + +INSERT INTO dhcpstatus (status_id, status) VALUES (1, 'dynamic'), (2, 'static'), (3, 'declined'), (4, 'disabled'); + +CREATE TABLE dhcpippool ( + id int(11) PRIMARY KEY, + pool_name varchar(30) NOT NULL, + framedipaddress varchar(15) NOT NULL default '', + pool_key varchar(30) NOT NULL default '', + gateway varchar(15) NOT NULL default '', + expiry_time DATETIME NOT NULL default (DATETIME('now')), + status_id int NOT NULL default 1, + counter int NOT NULL default 0, + FOREIGN KEY(status_id) REFERENCES dhcpstatus(status_id) +); + +CREATE INDEX dhcpippool_poolname_expire ON dhcpippool(pool_name, expiry_time); +CREATE INDEX dhcpippool_framedipaddress ON dhcpippool(framedipaddress); +CREATE INDEX dhcpippool_poolname_poolkey_ipaddress ON dhcpippool(pool_name, pool_key, framedipaddress); diff --git a/raddb/mods-config/sql/ippool/mongo/queries.conf b/raddb/mods-config/sql/ippool/mongo/queries.conf new file mode 100644 index 0000000..9d7d070 --- /dev/null +++ b/raddb/mods-config/sql/ippool/mongo/queries.conf @@ -0,0 +1,109 @@ +# -*- text -*- +# +# ippool/mongo/queries.conf -- Mongo queries for rlm_sqlippool +# +# $Id$ + +# +# The IP Pool queries expect a result like: +# +# { +# pool_key: "bob" +# pool_name: "my_pool" +# expiry_time: xxx +# value: "192.168.1.1" +# } +# +# i.e. the results are in "value", and not "framed_ip_address". +# +# When using dynamic expansions such as "%{sql:... mongo query ...}", +# Mongo uses a lot of curly brackets, {..}. Any closing braces have +# to be escaped as %}. Sorry, that is a limitation of the FreeRADIUS +# parser. +# + +# +# TBD +# +on_begin = "" +off_begin = "" + +allocate_begin = "" + +# +# This query allocates an IP address from the Pool +# +allocate_find = "db.mypool_collection.findAndModify( \ + { \ + 'query': {' \ + '$and': [ \ + { \ + 'pool_name': '%{control:Pool-Name}' \ + }, \ + { \ + 'nas_ip': '%{Nas-IP-Address}' \ + }, \ + { \ + '$or': [ \ + { \ + 'calling_station_id': '%{Calling-Station-Id}' \ + }, \ + { \ + 'locked': 0 \ + } \ + ] \ + } \ + ] \ + }, \ + 'update': { \ + 'locked': 1', \ + 'calling_station_id': '%{Calling-Station-Id'}' \ + }, \ + 'fields': { \ + '_id': 0, 'framed_ip_address': 1 \ + } \ + })" + +allocate_update = "" + +allocate_clear = "db.mypool_collection.findAndModify( \ + { \ + 'query': { \ + '$and': [ \ + { \ + 'pool_name': '%{Control:Pool-Name}' \ + }, \ + { \ + 'nas_ip': '%{Nas-IP-Address}' \ + }, \ + { \ + 'calling_station_id': '%{Calling-Station-Id}' \ + }, \ + { \ + 'locked': 1 \ + } \ + ] \ + }, \ + 'update': { \ + 'locked': 0, \ + 'calling_station_id': '' \ + } \ + })" + +allocate_commit = "" + +start_begin = "" +start_update = "" +start_commit = "" + +stop_begin = "" +stop_clear = "" +stop_commit = "" + +alive_begin = "" +alive_update = "" +alive_commit = "" + +on_clear = "" +off_clear = "" + diff --git a/raddb/mods-config/sql/ippool/mssql/procedure.sql b/raddb/mods-config/sql/ippool/mssql/procedure.sql new file mode 100644 index 0000000..5c621fb --- /dev/null +++ b/raddb/mods-config/sql/ippool/mssql/procedure.sql @@ -0,0 +1,137 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- BEGIN TRAN; "SELECT FOR UPDATE"; UPDATE; COMMIT TRAN; -> EXEC sp +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- EXEC fr_allocate_previous_or_new_framedipaddress \ +-- @v_pool_name = '%{control:${pool_name}}', \ +-- @v_username = '%{User-Name}', \ +-- @v_callingstationid = '%{Calling-Station-Id}', \ +-- @v_nasipaddress = '%{NAS-IP-Address}', \ +-- @v_pool_key = '${pool_key}', \ +-- @v_lease_duration = ${lease_duration} \ +-- " +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE INDEX UserName_CallingStationId ON radippool(pool_name,UserName,CallingStationId) +GO + +CREATE OR ALTER PROCEDURE fr_allocate_previous_or_new_framedipaddress + @v_pool_name VARCHAR(64), + @v_username VARCHAR(64), + @v_callingstationid VARCHAR(64), + @v_nasipaddress VARCHAR(15), + @v_pool_key VARCHAR(64), + @v_lease_duration INT +AS + BEGIN + + -- MS SQL lacks a "SELECT FOR UPDATE" statement, and its table + -- hints do not provide a direct means to implement the row-level + -- read lock needed to guarentee that concurrent queries do not + -- select the same Framed-IP-Address for allocation to distinct + -- users. + -- + -- The "WITH cte AS ( SELECT ... ) UPDATE cte ... OUTPUT INTO" + -- patterns in this procedure body compensate by wrapping + -- the SELECT in a synthetic UPDATE which locks the row. + + DECLARE @r_address_tab TABLE(id VARCHAR(15)); + DECLARE @r_address VARCHAR(15); + + BEGIN TRAN; + + -- Reissue an existing IP address lease when re-authenticating a session + -- + WITH cte AS ( + SELECT TOP(1) FramedIPAddress + FROM radippool WITH (xlock rowlock readpast) + WHERE pool_name = @v_pool_name + AND expiry_time > CURRENT_TIMESTAMP + AND NASIPAddress = @v_nasipaddress AND pool_key = @v_pool_key + ) + UPDATE cte + SET FramedIPAddress = FramedIPAddress + OUTPUT INSERTED.FramedIPAddress INTO @r_address_tab; + SELECT @r_address = id FROM @r_address_tab; + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- WITH cte AS ( + -- SELECT TOP(1) FramedIPAddress + -- FROM radippool WITH (xlock rowlock readpast) + -- WHERE pool_name = @v_pool_name + -- AND NASIPAddress = @v_nasipaddress AND pool_key = @v_pool_key + -- ) + -- UPDATE cte + -- SET FramedIPAddress = FramedIPAddress + -- OUTPUT INSERTED.FramedIPAddress INTO @r_address_tab; + -- SELECT @r_address = id FROM @r_address_tab; + + -- If we didn't reallocate a previous address then pick the least + -- recently used address from the pool which maximises the likelihood + -- of re-assigning the other addresses to their recent user + -- + IF @r_address IS NULL + BEGIN + WITH cte AS ( + SELECT TOP(1) FramedIPAddress + FROM radippool WITH (xlock rowlock readpast) + WHERE pool_name = @v_pool_name + AND expiry_time < CURRENT_TIMESTAMP + ORDER BY + expiry_time + ) + UPDATE cte + SET FramedIPAddress = FramedIPAddress + OUTPUT INSERTED.FramedIPAddress INTO @r_address_tab; + SELECT @r_address = id FROM @r_address_tab; + END + + -- Return nothing if we failed to allocated an address + -- + IF @r_address IS NULL + BEGIN + COMMIT TRAN; + RETURN; + END + + -- Update the pool having allocated an IP address + -- + UPDATE radippool + SET + NASIPAddress = @v_nasipaddress, + pool_key = @v_pool_key, + CallingStationId = @v_callingstationid, + UserName = @v_username, + expiry_time = DATEADD(SECOND,@v_lease_duration,CURRENT_TIMESTAMP) + WHERE framedipaddress = @r_address; + + COMMIT TRAN; + + -- Return the address that we allocated + SELECT @r_address; + + END +GO diff --git a/raddb/mods-config/sql/ippool/mssql/queries.conf b/raddb/mods-config/sql/ippool/mssql/queries.conf new file mode 100644 index 0000000..8105dcc --- /dev/null +++ b/raddb/mods-config/sql/ippool/mssql/queries.conf @@ -0,0 +1,175 @@ +# -*- text -*- +# +# ippool/mssql/queries.conf -- MSSQL queries for rlm_sqlippool +# +# $Id$ + +# +# MSSQL-specific syntax - required if finding the address and updating +# it are separate queries +# +#allocate_begin = "BEGIN TRAN" +#allocate_commit = "COMMIT TRAN" + +allocate_begin = "" +allocate_commit = "" + +# +# This series of queries allocates an IP address +# + +# +# Attempt to allocate the address a client previously had. This is based on pool_key +# and nasipaddress. Change the criteria if the identifier for "stickyness" is different. +# If different criteria are used, check the indexes on the IP pool table to ensure the fields +# are appropriately indexed. To disable stickyness comment out this query. +# +allocate_existing = "\ + WITH cte AS ( \ + SELECT TOP(1) FramedIPAddress, CallingStationId, UserName, expiry_time \ + FROM ${ippool_table} WITH (xlock rowlock readpast) \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND NASIPAddress = '%{NAS-IP-Address}' AND pool_key = '${pool_key}' \ + ORDER BY expiry_time DESC \ + ) \ + UPDATE cte \ + SET \ + CallingStationId = '%{Calling-Station-Id}', \ + UserName = '%{User-Name}', expiry_time = DATEADD(SECOND,${lease_duration},CURRENT_TIMESTAMP) \ + OUTPUT INSERTED.FramedIPAddress" + +# +# Find a free IP address from the pool, choosing the oldest expired one. +# +allocate_find = "\ + WITH cte AS ( \ + SELECT TOP(1) FramedIPAddress, NASIPAddress, pool_key, \ + CallingStationId, UserName, expiry_time \ + FROM ${ippool_table} WITH (xlock rowlock readpast) \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND expiry_time < CURRENT_TIMESTAMP \ + ORDER BY expiry_time \ + ) \ + UPDATE cte \ + SET \ + NASIPAddress = '%{NAS-IP-Address}', pool_key = '${pool_key}', \ + CallingStationId = '%{Calling-Station-Id}', \ + UserName = '%{User-Name}', expiry_time = DATEADD(SECOND,${lease_duration},CURRENT_TIMESTAMP) \ + OUTPUT INSERTED.FramedIPAddress" + +# +# If you prefer to allocate a random IP address every time, use this query instead. +# Note: This is very slow if you have a lot of free IPs. +# +#allocate_find = "\ +# WITH cte AS ( \ +# SELECT TOP(1) FramedIPAddress, NASIPAddress, pool_key, \ +# CallingStationId, UserName, expiry_time \ +# FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < CURRENT_TIMESTAMP \ +# ORDER BY newid() \ +# ) \ +# UPDATE cte WITH (rowlock, readpast) \ +# SET \ +# NASIPAddress = '%{NAS-IP-Address}', pool_key = '${pool_key}', \ +# CallingStationId = '%{Calling-Station-Id}', \ +# UserName = '%{User-Name}', expiry_time = DATEADD(SECOND,${lease_duration},CURRENT_TIMESTAMP) \ +# OUTPUT INSERTED.FramedIPAddress" + +# +# If an IP could not be allocated, check to see if the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be +# commented out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT TOP(1) id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}'" + +# +# This is the final IP Allocation query, which saves the allocated ip details. +# Only needed if allocate_existing / allocate_find do not also update the pool. +# +#allocate_update = "\ +# UPDATE ${ippool_table} \ +# SET \ +# NASIPAddress = '%{NAS-IP-Address}', pool_key = '${pool_key}', \ +# CallingStationId = '%{Calling-Station-Id}', \ +# UserName = '%{User-Name}', expiry_time = DATEADD(SECOND,${lease_duration},CURRENT_TIMESTAMP) \ +# WHERE FramedIPAddress = '%I'" + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# +#allocate_begin = "" +#allocate_find = "\ +# EXEC fr_allocate_previous_or_new_framedipaddress \ +# @v_pool_name = '%{control:${pool_name}}', \ +# @v_username = '%{User-Name}', \ +# @v_callingstationid = '%{Calling-Station-Id}', \ +# @v_nasipaddress = '%{NAS-IP-Address}', \ +# @v_pool_key = '${pool_key}', \ +# @v_lease_duration = ${lease_duration} \ +# " +#allocate_update = "" +#allocate_commit = "" + +# +# This series of queries frees an IP number when an accounting START record arrives. +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = DATEADD(SECOND,${lease_duration},CURRENT_TIMESTAMP) \ + WHERE NASIPAddress = '%{NAS-IP-Address}' \ + AND pool_key = '${pool_key}' \ + AND UserName = '%{User-Name}' \ + AND CallingStationId = '%{Calling-Station-Id}' \ + AND FramedIPAddress = '%{${attribute_name}}'" + +# +# Expire an IP when an accounting STOP record arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = CURRENT_TIMESTAMP \ + WHERE NASIPAddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}' \ + AND pool_key = '${pool_key}' \ + AND UserName = '%{User-Name}' \ + AND CallingStationId = '%{Calling-Station-Id}' \ + AND FramedIPAddress = '%{${attribute_name}}'" + +# +# Update the expiry time for an IP when an accounting ALIVE record arrives +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = DATEADD(SECOND,${lease_duration},CURRENT_TIMESTAMP) \ + WHERE NASIPAddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}' \ + AND pool_key = '${pool_key}' \ + AND UserName = '%{User-Name}' \ + AND CallingStationId = '%{Calling-Station-Id}' \ + AND FramedIPAddress = '%{${attribute_name}}'" + +# +# Expires all IPs allocated to a NAS when an accounting ON record arrives +# +on_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = CURRENT_TIMESTAMP \ + WHERE NASIPAddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}'" + +# +# Expires all IPs allocated to a NAS when an accounting OFF record arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = CURRENT_TIMESTAMP \ + WHERE NASIPAddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}'" diff --git a/raddb/mods-config/sql/ippool/mssql/schema.sql b/raddb/mods-config/sql/ippool/mssql/schema.sql new file mode 100644 index 0000000..d4bff44 --- /dev/null +++ b/raddb/mods-config/sql/ippool/mssql/schema.sql @@ -0,0 +1,25 @@ +-- +-- Table structure for table 'radippool' +-- +CREATE TABLE radippool ( + id int IDENTITY (1,1) NOT NULL, + pool_name varchar(30) NOT NULL, + FramedIPAddress varchar(15) NOT NULL default '', + NASIPAddress varchar(15) NOT NULL default '', + CalledStationId VARCHAR(32) NOT NULL default '', + CallingStationId VARCHAR(30) NOT NULL default '', + expiry_time DATETIME NOT NULL default CURRENT_TIMESTAMP, + UserName varchar(64) NOT NULL default '', + pool_key varchar(30) NOT NULL default '', + PRIMARY KEY (id) +) +GO + +CREATE INDEX poolname_expire ON radippool(pool_name, expiry_time) +GO + +CREATE INDEX FramedIPAddress ON radippool(FramedIPAddress) +GO + +CREATE INDEX NASIPAddress_poolkey_FramedIPAddress ON radippool(NASIPAddress, pool_key, FramedIPAddress) +GO diff --git a/raddb/mods-config/sql/ippool/mysql/procedure-no-skip-locked.sql b/raddb/mods-config/sql/ippool/mysql/procedure-no-skip-locked.sql new file mode 100644 index 0000000..1c88446 --- /dev/null +++ b/raddb/mods-config/sql/ippool/mysql/procedure-no-skip-locked.sql @@ -0,0 +1,149 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- NOTE: This version of the SP is intended for MySQL variants that do not +-- support the SKIP LOCKED pragma, i.e. MariaDB and versions of MySQL +-- prior to 8.0. It should be a lot faster than using the default SP +-- without the SKIP LOCKED pragma under highly concurrent workloads +-- and not result in thread starvation. +-- +-- It is however a *useful hack* which should not be used if SKIP +-- LOCKED is available. +-- +-- WARNING: This query uses server-local, "user locks" (GET_LOCK and +-- RELEASE_LOCK), without the need for a transaction, to emulate +-- row locking with locked-row skipping. User locks are not +-- supported on clusters such as Galera and MaxScale. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- START TRANSACTION; SELECT FOR UPDATE; UPDATE; COMMIT; -> CALL sp() +-- +-- The stored procedure is executed within a single round trip which often +-- leads to reduced deadlocking and significant performance improvements. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- CALL fr_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{User-Name}', \ +-- '%{Calling-Station-Id}', \ +-- '%{NAS-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration} \ +-- )" +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE INDEX poolname_username_callingstationid ON radippool(pool_name,username,callingstationid); + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS fr_allocate_previous_or_new_framedipaddress; +CREATE PROCEDURE fr_allocate_previous_or_new_framedipaddress ( + IN v_pool_name VARCHAR(64), + IN v_username VARCHAR(64), + IN v_callingstationid VARCHAR(64), + IN v_nasipaddress VARCHAR(15), + IN v_pool_key VARCHAR(64), + IN v_lease_duration INT +) +SQL SECURITY INVOKER +proc:BEGIN + DECLARE r_address VARCHAR(15); + + -- Reissue an existing IP address lease when re-authenticating a session + -- + SELECT framedipaddress INTO r_address + FROM radippool + WHERE pool_name = v_pool_name + AND expiry_time > NOW() + AND nasipaddress = v_nasipaddress + AND pool_key = v_pool_key + LIMIT 1; + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- SELECT framedipaddress INTO r_address + -- FROM radippool + -- WHERE pool_name = v_pool_name + -- AND nasipaddress = v_nasipaddress + -- AND pool_key = v_pool_key + -- LIMIT 1; + + IF r_address IS NOT NULL THEN + UPDATE radippool + SET + nasipaddress = v_nasipaddress, + pool_key = v_pool_key, + callingstationid = v_callingstationid, + username = v_username, + expiry_time = NOW() + INTERVAL v_lease_duration SECOND + WHERE + framedipaddress = r_address; + SELECT r_address; + LEAVE proc; + END IF; + + REPEAT + + -- If we didn't reallocate a previous address then pick the least + -- recently used address from the pool which maximises the likelihood + -- of re-assigning the other addresses to their recent user + -- + SELECT framedipaddress INTO r_address + FROM radippool + WHERE pool_name = v_pool_name + AND expiry_time < NOW() + -- + -- WHERE ... GET_LOCK(...,0) = 1 is a poor man's SKIP LOCKED that simulates + -- a row-level lock using a "user lock" that allows the locked "rows" to be + -- skipped. After the user lock is acquired and the SELECT retired it does + -- not mean that the entirety of the WHERE clause is still true: Another + -- thread may have updated the expiry time and released the lock after we + -- checked the expiry_time but before we acquired the lock since SQL is free + -- to reorder the WHERE condition. Therefore we must recheck the condition + -- in the UPDATE statement below to detect this race. + -- + AND GET_LOCK(CONCAT('radippool_', framedipaddress), 0) = 1 + LIMIT 1; + + IF r_address IS NULL THEN + DO RELEASE_LOCK(CONCAT('radippool_', r_address)); + LEAVE proc; + END IF; + + UPDATE radippool + SET + nasipaddress = v_nasipaddress, + pool_key = v_pool_key, + callingstationid = v_callingstationid, + username = v_username, + expiry_time = NOW() + INTERVAL v_lease_duration SECOND + WHERE + framedipaddress = r_address + -- + -- Here we re-evaluate the original condition for selecting the address + -- to detect a race, in which case we try again... + -- + AND expiry_time<NOW(); + + UNTIL ROW_COUNT() <> 0 END REPEAT; + + DO RELEASE_LOCK(CONCAT('radippool_', r_address)); + SELECT r_address; + +END$$ + +DELIMITER ; diff --git a/raddb/mods-config/sql/ippool/mysql/procedure.sql b/raddb/mods-config/sql/ippool/mysql/procedure.sql new file mode 100644 index 0000000..2a52566 --- /dev/null +++ b/raddb/mods-config/sql/ippool/mysql/procedure.sql @@ -0,0 +1,139 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- START TRANSACTION; SELECT FOR UPDATE; UPDATE; COMMIT; -> CALL sp() +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- CALL fr_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{User-Name}', \ +-- '%{Calling-Station-Id}', \ +-- '%{Called-Station-Id}', \ +-- '%{NAS-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration} \ +-- )" +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE INDEX poolname_username_callingstationid ON radippool(pool_name,username,callingstationid); + +DELIMITER $$ + +DROP PROCEDURE IF EXISTS fr_allocate_previous_or_new_framedipaddress; +CREATE PROCEDURE fr_allocate_previous_or_new_framedipaddress ( + IN v_pool_name VARCHAR(64), + IN v_username VARCHAR(64), + IN v_callingstationid VARCHAR(64), + IN v_calledstationid VARCHAR(64), + IN v_nasipaddress VARCHAR(15), + IN v_pool_key VARCHAR(64), + IN v_lease_duration INT +) +SQL SECURITY INVOKER +proc:BEGIN + DECLARE r_address VARCHAR(15); + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + SET TRANSACTION ISOLATION LEVEL READ COMMITTED; + + START TRANSACTION; + + -- Reissue an existing IP address lease when re-authenticating a session + -- + SELECT framedipaddress INTO r_address + FROM radippool + WHERE pool_name = v_pool_name + AND expiry_time > NOW() + AND nasipaddress = v_nasipaddress + AND pool_key = v_pool_key + LIMIT 1 + FOR UPDATE; +-- FOR UPDATE SKIP LOCKED; -- Better performance, but limited support + + -- NOTE: You should enable SKIP LOCKED here (as well as any other + -- instances) if your database server supports it. If it is not + -- supported and you are not running a multi-master cluster (e.g. + -- Galera or MaxScale) then you should instead consider using the + -- SP in procedure-no-skip-locked.sql which will be faster and + -- less likely to result in thread starvation under highly + -- concurrent load. + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- SELECT framedipaddress INTO r_address + -- FROM radippool + -- WHERE pool_name = v_pool_name + -- AND nasipaddress = v_nasipaddress + -- AND pool_key = v_pool_key + -- LIMIT 1 + -- FOR UPDATE; + -- -- FOR UPDATE SKIP LOCKED; -- Better performance, but limited support + + -- If we didn't reallocate a previous address then pick the least + -- recently used address from the pool which maximises the likelihood + -- of re-assigning the other addresses to their recent user + -- + IF r_address IS NULL THEN + SELECT framedipaddress INTO r_address + FROM radippool + WHERE pool_name = v_pool_name + AND expiry_time < NOW() + ORDER BY + expiry_time + LIMIT 1 + FOR UPDATE; +-- FOR UPDATE SKIP LOCKED; -- Better performance, but limited support + END IF; + + -- Return nothing if we failed to allocated an address + -- + IF r_address IS NULL THEN + COMMIT; + LEAVE proc; + END IF; + + -- Update the pool having allocated an IP address + -- + UPDATE radippool + SET + nasipaddress = v_nasipaddress, + pool_key = v_pool_key, + callingstationid = v_callingstationid, + calledstationid = v_calledstationid, + username = v_username, + expiry_time = NOW() + INTERVAL v_lease_duration SECOND + WHERE framedipaddress = r_address; + + COMMIT; + + -- Return the address that we allocated + SELECT r_address; + +END$$ + +DELIMITER ; diff --git a/raddb/mods-config/sql/ippool/mysql/queries.conf b/raddb/mods-config/sql/ippool/mysql/queries.conf new file mode 100644 index 0000000..c421020 --- /dev/null +++ b/raddb/mods-config/sql/ippool/mysql/queries.conf @@ -0,0 +1,156 @@ +# -*- text -*- +# +# ippool/mysql/queries.conf -- MySQL queries for rlm_sqlippool +# +# $Id$ + + +# Using SKIP LOCKED speeds up selection queries +# However, it requires MySQL >= 8.0.1 or MariaDB >= 10.6. +# Uncomment the following if you are running a suitable +# version of MySQL +# +#skip_locked = "SKIP LOCKED" +skip_locked = "" + +# +# This series of queries allocates an IP address +# + +# +# Attempt to allocate the address a client previously had. This is based on pool_key +# and nasipaddress. Change the criteria if the identifier for "stickyness" is different. +# If different criteria are used, check the indexes on the IP pool table to ensure the fields +# are appropriately indexed. To disable stickyness comment out this query. +# +allocate_existing = "\ + SELECT framedipaddress FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND nasipaddress = '%{NAS-IP-Address}' AND pool_key = '${pool_key}' \ + ORDER BY expiry_time DESC \ + LIMIT 1 \ + FOR UPDATE ${skip_locked}" + +# +# Find a free IP address from the pool, choosing the oldest expired one. +# +allocate_find = "\ + SELECT framedipaddress FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND expiry_time < NOW() \ + ORDER BY expiry_time \ + LIMIT 1 \ + FOR UPDATE ${skip_locked}" + +# +# If you prefer to allocate a random IP address every time, use this query instead. +# Note: This is very slow if you have a lot of free IPs. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < NOW() \ +# ORDER BY \ +# RAND() \ +# LIMIT 1 \ +# FOR UPDATE ${skip_locked}" + +# +# If an IP could not be allocated, check to see if the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be +# commented out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}' \ + LIMIT 1" + +# +# This is the final IP Allocation query, which saves the allocated ip details. +# +allocate_update = "\ + UPDATE ${ippool_table} \ + SET \ + nasipaddress = '%{NAS-IP-Address}', pool_key = '${pool_key}', \ + callingstationid = '%{Calling-Station-Id}', \ + username = '%{User-Name}', expiry_time = NOW() + INTERVAL ${lease_duration} SECOND \ + WHERE framedipaddress = '%I'" + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# +#allocate_begin = "" +#allocate_find = "\ +# CALL fr_allocate_previous_or_new_framedipaddress( \ +# '%{control:${pool_name}}', \ +# '%{User-Name}', \ +# '%{Calling-Station-Id}', \ +# '%{Called-Station-Id}', \ +# '%{NAS-IP-Address}', \ +# '${pool_key}', \ +# ${lease_duration} \ +# )" +#allocate_update = "" +#allocate_commit = "" + +# +# This series of queries frees an IP number when an accounting START record arrives. +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = NOW() + INTERVAL ${lease_duration} SECOND \ + WHERE nasipaddress = '%{NAS-IP-Address}' \ + AND pool_key = '${pool_key}' \ + AND username = '%{User-Name}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND framedipaddress = '%{${attribute_name}}'" + +# +# This query expires an IP number when an accounting STOP record arrives. +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = NOW() \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}' \ + AND pool_key = '${pool_key}' \ + AND username = '%{User-Name}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND framedipaddress = '%{${attribute_name}}'" + +# +# This series of queries frees an IP number when an accounting ALIVE record arrives. +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = NOW() + INTERVAL ${lease_duration} SECOND \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}' \ + AND pool_key = '${pool_key}' \ + AND username = '%{User-Name}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND framedipaddress = '%{${attribute_name}}'" + +# +# This series of queries expires the IP numbers allocate to a +# NAS when an accounting ON record arrives +# +on_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = NOW() \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}'" + +# +# This series of queries expires the IP numbers allocate to a +# NAS when an accounting OFF record arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = NOW() \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}'" diff --git a/raddb/mods-config/sql/ippool/mysql/schema.sql b/raddb/mods-config/sql/ippool/mysql/schema.sql new file mode 100644 index 0000000..f79d1b1 --- /dev/null +++ b/raddb/mods-config/sql/ippool/mysql/schema.sql @@ -0,0 +1,18 @@ +# +# Table structure for table 'radippool' +# +CREATE TABLE IF NOT EXISTS radippool ( + id int(11) unsigned NOT NULL auto_increment, + pool_name varchar(30) NOT NULL, + framedipaddress varchar(15) NOT NULL default '', + nasipaddress varchar(15) NOT NULL default '', + calledstationid VARCHAR(30) NOT NULL default '', + callingstationid VARCHAR(30) NOT NULL default '', + expiry_time DATETIME NOT NULL default NOW(), + username varchar(64) NOT NULL default '', + pool_key varchar(30) NOT NULL default '', + PRIMARY KEY (id), + KEY radippool_poolname_expire (pool_name, expiry_time), + KEY framedipaddress (framedipaddress), + KEY radippool_nasip_poolkey_ipaddress (nasipaddress, pool_key, framedipaddress) +) ENGINE=InnoDB; diff --git a/raddb/mods-config/sql/ippool/oracle/procedure.sql b/raddb/mods-config/sql/ippool/oracle/procedure.sql new file mode 100644 index 0000000..e483236 --- /dev/null +++ b/raddb/mods-config/sql/ippool/oracle/procedure.sql @@ -0,0 +1,129 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- BEGIN; SELECT FOR UPDATE; UPDATE; COMMIT; -> SELECT sp() FROM dual +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- SELECT fr_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{User-Name}', \ +-- '%{%{Calling-Station-Id}:-0}', \ +-- '%{NAS-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration} \ +-- ) FROM dual" +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE OR REPLACE FUNCTION fr_allocate_previous_or_new_framedipaddress ( + v_pool_name IN VARCHAR2, + v_username IN VARCHAR2, + v_callingstationid IN VARCHAR2, + v_nasipaddress IN VARCHAR2, + v_pool_key IN VARCHAR2, + v_lease_duration IN INTEGER +) +RETURN varchar2 IS + PRAGMA AUTONOMOUS_TRANSACTION; + r_address varchar2(15); +BEGIN + + -- Reissue an existing IP address lease when re-authenticating a session + -- + BEGIN + SELECT framedipaddress INTO r_address FROM radippool WHERE id IN ( + SELECT id FROM ( + SELECT * + FROM radippool + WHERE pool_name = v_pool_name + AND expiry_time > current_timestamp + AND username = v_username + AND callingstationid = v_callingstationid + ) WHERE ROWNUM <= 1 + ) FOR UPDATE SKIP LOCKED; + EXCEPTION + WHEN NO_DATA_FOUND THEN + r_address := NULL; + END; + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- BEGIN + -- SELECT framedipaddress INTO r_address FROM radippool WHERE id IN ( + -- SELECT id FROM ( + -- SELECT * + -- FROM radippool + -- WHERE pool_name = v_pool_name + -- AND username = v_username + -- AND callingstationid = v_callingstationid + -- ) WHERE ROWNUM <= 1 + -- ) FOR UPDATE SKIP LOCKED; + -- EXCEPTION + -- WHEN NO_DATA_FOUND THEN + -- r_address := NULL; + -- END; + + -- If we didn't reallocate a previous address then pick the least + -- recently used address from the pool which maximises the likelihood + -- of re-assigning the other addresses to their recent user + -- + IF r_address IS NULL THEN + BEGIN + SELECT framedipaddress INTO r_address FROM radippool WHERE id IN ( + SELECT id FROM ( + SELECT * + FROM radippool + WHERE pool_name = v_pool_name + AND expiry_time < CURRENT_TIMESTAMP + ORDER BY expiry_time + ) WHERE ROWNUM <= 1 + ) FOR UPDATE SKIP LOCKED; + EXCEPTION + WHEN NO_DATA_FOUND THEN + r_address := NULL; + END; + END IF; + + -- Return nothing if we failed to allocated an address + -- + IF r_address IS NULL THEN + COMMIT; + RETURN r_address; + END IF; + + -- Update the pool having allocated an IP address + -- + UPDATE radippool + SET + nasipaddress = v_nasipaddress, + pool_key = v_pool_key, + callingstationid = v_callingstationid, + username = v_username, + expiry_time = CURRENT_TIMESTAMP + v_lease_duration * INTERVAL '1' SECOND(1) + WHERE framedipaddress = r_address; + + -- Return the address that we allocated + COMMIT; + RETURN r_address; + +END; +/ diff --git a/raddb/mods-config/sql/ippool/oracle/queries.conf b/raddb/mods-config/sql/ippool/oracle/queries.conf new file mode 100644 index 0000000..1a64b28 --- /dev/null +++ b/raddb/mods-config/sql/ippool/oracle/queries.conf @@ -0,0 +1,172 @@ +# -*- text -*- +# +# ippool/oracle/queries.conf -- Oracle queries for rlm_sqlippool +# +# $Id$ + +# Using SKIP LOCKED speeds up selection queries +# However, it requires Oracle > 11g. It MAY work in 9i and 10g +# but is not documented. Uncomment the following if you are +# running a suitable version of Oracle +# +#skip_locked = "SKIP LOCKED" +skip_locked = "" + +allocate_begin = "commit" +start_begin = "commit" +alive_begin = "commit" +stop_begin = "commit" +on_begin = "commit" +off_begin = "commit" + +# +# Attempt to allocate the address a client previously had. This is based on pool_key +# and nasipaddress. Change the criteria if the identifier for "stickyness" is different. +# If different criteria are used, check the indexes on the IP pool table to ensure the fields +# are appropriately indexed. To disable stickyness comment out this query. +# +allocate_existing = "\ + SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( \ + SELECT id FROM ( \ + SELECT * \ + FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND nasipaddress = '%{NAS-IP-Address}' AND pool_key = '${pool_key}' \ + ) \ + ORDER BY expiry_time DESC \ + ) WHERE ROWNUM <= 1 \ + ) FOR UPDATE ${skip_locked}" + +# +# Find a free IP address from the pool, choosing the oldest expired one. +# +allocate_find = "\ + SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( \ + SELECT id FROM ( \ + SELECT * \ + FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND expiry_time < current_timestamp \ + ) \ + ORDER BY expiry_time \ + ) WHERE ROWNUM <= 1 \ + ) FOR UPDATE ${skip_locked}" + +# +# If you prefer to allocate a random IP address every time, use this query instead +# Note: This is very slow if you have a lot of free IPs. +# +#allocate_find = "\ +# SELECT framedipaddress FROM ${ippool_table} WHERE id IN ( \ +# SELECT id FROM ( \ +# SELECT * \ +# FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < current_timestamp \ +# ORDER BY DBMS_RANDOM.VALUE \ +# ) WHERE ROWNUM <= 1 \ +# ) FOR UPDATE ${skip_locked}" + +# +# If an IP could not be allocated, check to see whether the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be commented +# out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM (\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}'\ + ) \ + WHERE ROWNUM = 1" + +# +# This query marks the IP address handed out by "allocate-find" as used +# for the period of "lease_duration" after which time it may be reused. +# +allocate_update = "\ + UPDATE ${ippool_table} \ + SET \ + nasipaddress = '%{NAS-IP-Address}', \ + pool_key = '${pool_key}', \ + callingstationid = '%{%{Calling-Station-Id}:-0}', \ + username = '%{SQL-User-Name}', \ + expiry_time = current_timestamp + INTERVAL '${lease_duration}' second(1) \ + WHERE framedipaddress = '%I'" + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# +#allocate_begin = "" +#allocate_find = "\ +# SELECT fr_allocate_previous_or_new_framedipaddress( \ +# '%{control:${pool_name}}', \ +# '%{SQL-User-Name}', \ +# '%{%{Calling-Station-Id}:-0}', \ +# '%{NAS-IP-Address}', \ +# '${pool_key}', \ +# '${lease_duration}' \ +# )" +#allocate_update = "" +#allocate_commit = "" + +# +# This query extends an IP address lease by "lease_duration" when an accounting +# START record arrives +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = current_timestamp + INTERVAL '${lease_duration}' second(1) \ + WHERE nasipaddress = '%{NAS-IP-Address}' \ + AND pool_key = '${pool_key}'" + +# +# This query expires an IP address when an accounting STOP record arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = current_timestamp - INTERVAL '1' second(1) \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}' \ + AND pool_key = '${pool_key}' \ + AND username = '%{SQL-User-Name}' \ + AND callingstationid = '%{%{Calling-Station-Id}:-0}' \ + AND framedipaddress = '%{${attribute_name}}'" + +# +# This query extends an IP address lease by "lease_duration" when an accounting +# ALIVE record arrives +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = current_timestamp + INTERVAL '${lease_duration}' second(1) \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{${attribute_name}}' \ + AND username = '%{SQL-User-Name}' \ + AND callingstationid = '%{%{Calling-Station-Id}:-0}'" + +# +# This query expires all IP addresses allocated to a NAS when an +# accounting ON record arrives from that NAS +# +on_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = current_timestamp - INTERVAL '1' second(1) \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}'" + +# +# This query expires all IP addresses allocated to a NAS when an +# accounting OFF record arrives from that NAS +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = current_timestamp - INTERVAL '1' second(1) \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}'" diff --git a/raddb/mods-config/sql/ippool/oracle/schema.sql b/raddb/mods-config/sql/ippool/oracle/schema.sql new file mode 100644 index 0000000..adf1419 --- /dev/null +++ b/raddb/mods-config/sql/ippool/oracle/schema.sql @@ -0,0 +1,27 @@ +CREATE TABLE radippool ( + id INT PRIMARY KEY, + pool_name VARCHAR(30) NOT NULL, + framedipaddress VARCHAR(15) NOT NULL, + nasipaddress VARCHAR(15) NOT NULL, + pool_key VARCHAR(30) DEFAULT '', + CalledStationId VARCHAR(64) DEFAULT '', + CallingStationId VARCHAR(64) DEFAULT '', + expiry_time timestamp(0) DEFAULT CURRENT_TIMESTAMP, + username VARCHAR(64) DEFAULT '' +); + +CREATE INDEX radippool_poolname_expire ON radippool (pool_name, expiry_time); +CREATE INDEX radippool_framedipaddress ON radippool (framedipaddress); +CREATE INDEX radippool_nasip_poolkey_ipaddress ON radippool (nasipaddress, pool_key, framedipaddress); + +CREATE SEQUENCE radippool_seq START WITH 1 INCREMENT BY 1; + +CREATE OR REPLACE TRIGGER radippool_serialnumber + BEFORE INSERT OR UPDATE OF id ON radippool + FOR EACH ROW + BEGIN + if ( :new.id = 0 or :new.id is null ) then + SELECT radippool_seq.nextval into :new.id from dual; + end if; + END; +/ diff --git a/raddb/mods-config/sql/ippool/postgresql/procedure.sql b/raddb/mods-config/sql/ippool/postgresql/procedure.sql new file mode 100644 index 0000000..b1d580c --- /dev/null +++ b/raddb/mods-config/sql/ippool/postgresql/procedure.sql @@ -0,0 +1,111 @@ +-- +-- A stored procedure to reallocate a user's previous address, otherwise +-- provide a free address. +-- +-- Using this SP reduces the usual set dialogue of queries to a single +-- query: +-- +-- START TRANSACTION; SELECT FOR UPDATE; UPDATE; COMMIT; -> SELECT sp() +-- +-- The stored procedure is executed on an database instance within a single +-- round trip which often leads to reduced deadlocking and significant +-- performance improvements especially on multi-master clusters, perhaps even +-- by an order of magnitude or more. +-- +-- To use this stored procedure the corresponding queries.conf statements must +-- be configured as follows: +-- +-- allocate_begin = "" +-- allocate_find = "\ +-- SELECT fr_allocate_previous_or_new_framedipaddress( \ +-- '%{control:${pool_name}}', \ +-- '%{User-Name}', \ +-- '%{Calling-Station-Id}', \ +-- '%{NAS-IP-Address}', \ +-- '${pool_key}', \ +-- ${lease_duration} \ +-- )" +-- allocate_update = "" +-- allocate_commit = "" +-- + +CREATE INDEX radippool_poolname_username_callingstationid ON radippool(pool_name,username,callingstationid); + +CREATE OR REPLACE FUNCTION fr_allocate_previous_or_new_framedipaddress ( + v_pool_name VARCHAR(64), + v_username VARCHAR(64), + v_callingstationid VARCHAR(64), + v_nasipaddress VARCHAR(16), + v_pool_key VARCHAR(64), + v_lease_duration INT +) +RETURNS inet +LANGUAGE plpgsql +AS $$ +DECLARE + r_address inet; +BEGIN + + -- Reissue an existing IP address lease when re-authenticating a session + -- + SELECT framedipaddress INTO r_address + FROM radippool + WHERE pool_name = v_pool_name + AND expiry_time > NOW() + AND username = v_username + AND callingstationid = v_callingstationid + LIMIT 1 + FOR UPDATE SKIP LOCKED; + + -- Reissue an user's previous IP address, provided that the lease is + -- available (i.e. enable sticky IPs) + -- + -- When using this SELECT you should delete the one above. You must also + -- set allocate_clear = "" in queries.conf to persist the associations + -- for expired leases. + -- + -- SELECT framedipaddress INTO r_address + -- FROM radippool + -- WHERE pool_name = v_pool_name + -- AND username = v_username + -- AND callingstationid = v_callingstationid + -- LIMIT 1 + -- FOR UPDATE SKIP LOCKED; + + -- If we didn't reallocate a previous address then pick the least + -- recently used address from the pool which maximises the likelihood + -- of re-assigning the other addresses to their recent user + -- + IF r_address IS NULL THEN + SELECT framedipaddress INTO r_address + FROM radippool + WHERE pool_name = v_pool_name + AND expiry_time < NOW() + ORDER BY + expiry_time + LIMIT 1 + FOR UPDATE SKIP LOCKED; + END IF; + + -- Return nothing if we failed to allocated an address + -- + IF r_address IS NULL THEN + RETURN r_address; + END IF; + + -- Update the pool having allocated an IP address + -- + UPDATE radippool + SET + nasipaddress = v_nasipaddress, + pool_key = v_pool_key, + callingstationid = v_callingstationid, + username = v_username, + expiry_time = NOW() + v_lease_duration * interval '1 sec' + WHERE framedipaddress = r_address; + + -- Return the address that we allocated + RETURN r_address; + +END +$$; diff --git a/raddb/mods-config/sql/ippool/postgresql/queries.conf b/raddb/mods-config/sql/ippool/postgresql/queries.conf new file mode 100644 index 0000000..ce6f355 --- /dev/null +++ b/raddb/mods-config/sql/ippool/postgresql/queries.conf @@ -0,0 +1,207 @@ +# -*- text -*- +# +# ippool/postgresql/queries.conf -- PostgreSQL queries for rlm_sqlippool +# +# $Id$ + + +# Using SKIP LOCKED speeds up selection queries +# However, it requires PostgreSQL >= 9.5 Uncomment the +# following if you are running a suitable version of PostgreSQL +# +#skip_locked = "SKIP LOCKED" +skip_locked = "" + +# +# This series of queries allocates an IP address +# + +# +# The suggested queries locate IPs and update them in one query +# so no need for transaction wrappers +# +allocate_begin = "" +allocate_commit = "" + +# +# Attempt to allocate the address a client previously had. This is based on pool_key +# and nasipaddress. Change the criteria if the identifier for "stickyness" is different. +# If different criteria are used, check the indexes on the IP pool table to ensure the fields +# are appropriately indexed. To disable stickyness comment out this query. +# +allocate_existing = "\ + WITH cte AS ( \ + SELECT framedipaddress FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND nasipaddress = '%{NAS-IP-Address}' AND pool_key = '${pool_key}' \ + ORDER BY expiry_time DESC \ + LIMIT 1 \ + FOR UPDATE ${skip_locked} \ + ) \ + UPDATE ${ippool_table} \ + SET \ + nasipaddress = '%{NAS-IP-Address}', \ + pool_key = '${pool_key}', \ + callingstationid = '%{Calling-Station-Id}', \ + username = '%{SQL-User-Name}', \ + expiry_time = 'now'::timestamp(0) + '${lease_duration} second'::interval \ + FROM cte WHERE cte.framedipaddress = ${ippool_table}.framedipaddress \ + RETURNING cte.framedipaddress" + +# +# Find a free IP address from the pool, choosing the oldest expired one. +# +allocate_find = "\ + WITH cte AS ( \ + SELECT framedipaddress FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND expiry_time < 'now'::timestamp(0) \ + ORDER BY expiry_time \ + LIMIT 1 \ + FOR UPDATE ${skip_locked} \ + ) \ + UPDATE ${ippool_table} \ + SET \ + nasipaddress = '%{NAS-IP-Address}', \ + pool_key = '${pool_key}', \ + callingstationid = '%{Calling-Station-Id}', \ + username = '%{SQL-User-Name}', \ + expiry_time = 'now'::timestamp(0) + '${lease_duration} second'::interval \ + FROM cte WHERE cte.framedipaddress = ${ippool_table}.framedipaddress \ + RETURNING cte.framedipaddress" + +# +# If you prefer to allocate a random IP address every time, use this query instead +# Note: This is very slow if you have a lot of free IPs. +# +#allocate_find = "\ +# WITH cte AS ( \ +# SELECT framedipaddress FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time < 'now'::timestamp(0) \ +# ORDER BY RANDOM() \ +# LIMIT 1 \ +# FOR UPDATE ${skip_locked} \ +# ) \ +# UPDATE ${ippool_table} \ +# SET \ +# nasipaddress = '%{NAS-IP-Address}', \ +# pool_key = '${pool_key}', \ +# callingstationid = '%{Calling-Station-Id}', \ +# username = '%{SQL-User-Name}', \ +# expiry_time = 'now'::timestamp(0) + '${lease_duration} second'::interval \ +# FROM cte WHERE cte.framedipaddress = ${ippool_table}.framedipaddress \ +# RETURNING cte.framedipaddress" + +# +# If an IP could not be allocated, check to see whether the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be commented +# out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}' \ + LIMIT 1" + +# +# This query marks the IP address handed out by "allocate-find" as used +# for the period of "lease_duration" after which time it may be reused. +# This is only needed if the allocate_existing / allocate_find queries +# do not update the pool +# +#allocate_update = "\ +# UPDATE ${ippool_table} \ +# SET \ +# nasipaddress = '%{NAS-IP-Address}', \ +# pool_key = '${pool_key}', \ +# callingstationid = '%{Calling-Station-Id}', \ +# username = '%{SQL-User-Name}', \ +# expiry_time = 'now'::timestamp(0) + '${lease_duration} second'::interval \ +# WHERE framedipaddress = '%I'" + +# +# Use a stored procedure to find AND allocate the address. Read and customise +# `procedure.sql` in this directory to determine the optimal configuration. +# +# This requires PostgreSQL >= 9.5 as SKIP LOCKED is used. +# +# The "NO LOAD BALANCE" comment is included here to indicate to a PgPool +# system that this needs to be a write transaction. PgPool itself cannot +# detect this from the statement alone. If you are using PgPool and do not +# have this comment, the query may go to a read only server, and will fail. +# This has no negative effect if you are not using PgPool. +# +#allocate_begin = "" +#allocate_find = "\ +# /*NO LOAD BALANCE*/ \ +# SELECT fr_allocate_previous_or_new_framedipaddress( \ +# '%{control:${pool_name}}', \ +# '%{SQL-User-Name}', \ +# '%{Calling-Station-Id}', \ +# '%{NAS-IP-Address}', \ +# '${pool_key}', \ +# '${lease_duration}' \ +# )" +#allocate_update = "" +#allocate_commit = "" + +# +# This query extends an IP address lease by "lease_duration" when an accounting +# START record arrives +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = 'now'::timestamp(0) + '${lease_duration} second'::interval \ + WHERE nasipaddress = '%{NAS-IP-Address}' \ + AND pool_key = '${pool_key}'" + +# +# This query expires an IP address when an accounting +# STOP record arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = 'now'::timestamp(0) - '1 second'::interval \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}' \ + AND pool_key = '${pool_key}' \ + AND username = '%{SQL-User-Name}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND framedipaddress = '%{${attribute_name}}'" + +# +# This query extends an IP address lease by "lease_duration" when an accounting +# ALIVE record arrives +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = 'now'::timestamp(0) + '${lease_duration} seconds'::interval \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}' \ + AND pool_key = '${pool_key}' \ + AND framedipaddress = '%{${attribute_name}}' \ + AND username = '%{SQL-User-Name}' \ + AND callingstationid = '%{Calling-Station-Id}'" + +# +# This query expires all IP addresses allocated to a NAS when an +# accounting ON record arrives from that NAS +# +on_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = 'now'::timestamp(0) - '1 second'::interval \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}'" + +# +# This query expires all IP addresses allocated to a NAS when an +# accounting OFF record arrives from that NAS +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = 'now'::timestamp(0) - '1 second'::interval \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}'" diff --git a/raddb/mods-config/sql/ippool/postgresql/schema.sql b/raddb/mods-config/sql/ippool/postgresql/schema.sql new file mode 100644 index 0000000..1ef57b7 --- /dev/null +++ b/raddb/mods-config/sql/ippool/postgresql/schema.sql @@ -0,0 +1,22 @@ +-- +-- Table structure for table 'radippool' +-- +-- See also "procedure.sql" in this directory for additional +-- indices and a stored procedure that is much faster. +-- + +CREATE TABLE radippool ( + id BIGSERIAL PRIMARY KEY, + pool_name text NOT NULL, + FramedIPAddress INET NOT NULL, + NASIPAddress text NOT NULL default '', + pool_key text NOT NULL default '', + CalledStationId text NOT NULL default '', + CallingStationId text NOT NULL default ''::text, + expiry_time TIMESTAMP(0) without time zone NOT NULL default NOW(), + username text DEFAULT ''::text +); + +CREATE INDEX radippool_poolname_expire ON radippool USING btree (pool_name, expiry_time); +CREATE INDEX radippool_framedipaddress ON radippool USING btree (framedipaddress); +CREATE INDEX radippool_nasip_poolkey_ipaddress ON radippool USING btree (nasipaddress, pool_key, framedipaddress); diff --git a/raddb/mods-config/sql/ippool/sqlite/queries.conf b/raddb/mods-config/sql/ippool/sqlite/queries.conf new file mode 100644 index 0000000..46ce58e --- /dev/null +++ b/raddb/mods-config/sql/ippool/sqlite/queries.conf @@ -0,0 +1,148 @@ +# -*- text -*- +# +# ippool/sqlite/queries.conf -- SQLite queries for rlm_sqlippool +# +# $Id$ + +# +# SQLite does not implement SELECT FOR UPDATE which is normally used to place +# an exclusive lock over rows to prevent the same address from being +# concurrently selected for allocation to multiple users. +# +# The most granular read-blocking lock that SQLite has is an exclusive lock +# over the database, so that's what we use. All locking in SQLite is performed +# over the entire database and we perform a row update for any IP that we +# allocate, requiring an exclusive lock. Taking the exclusive lock from the +# start of the transaction (even if it were not required to guard the SELECT) +# is actually quicker than if we deferred it causing SQLite to "upgrade" the +# automatic shared lock for the transaction to an exclusive lock for the +# subsequent UPDATE. +# +allocate_begin = "BEGIN EXCLUSIVE" +allocate_commit = "COMMIT" + +# +# This series of queries allocates an IP address +# + +# +# Attempt to allocate the address a client previously had. This is based on pool_key +# and nasipaddress. Change the criteria if the identifier for "stickyness" is different. +# If different criteria are used, check the indexes on the IP pool table to ensure the fields +# are appropriately indexed. To disable stickyness comment out this query. +# +allocate_existing = "\ + SELECT framedipaddress \ + FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + AND nasipaddress = '%{NAS-IP-Address}' AND pool_key = '${pool_key}' \ + ORDER BY expiry_time DESC \ + LIMIT 1" + +# +# Find a free IP address from the pool, choosing the oldest expired one. +# +allocate_find = "\ + SELECT framedipaddress \ + FROM ${ippool_table} \ + WHERE pool_name = '%{control:${pool_name}}' \ + expiry_time < datetime('now') \ + ORDER BY expiry_time \ + LIMIT 1" + +# +# If you prefer to allocate a random IP address every time, i +# use this query instead +# Note: This is very slow if you have a lot of free IPs. +# + +#allocate_find = "\ +# SELECT framedipaddress \ +# FROM ${ippool_table} \ +# WHERE pool_name = '%{control:${pool_name}}' \ +# AND expiry_time IS NULL \ +# ORDER BY RAND() \ +# LIMIT 1" + +# +# If an IP could not be allocated, check to see if the pool exists or not +# This allows the module to differentiate between a full pool and no pool +# Note: If you are not running redundant pool modules this query may be +# commented out to save running this query every time an ip is not allocated. +# +pool_check = "\ + SELECT id \ + FROM ${ippool_table} \ + WHERE pool_name='%{control:${pool_name}}' \ + LIMIT 1" + +# +# This is the final IP Allocation query, which saves the allocated ip details +# +allocate_update = "\ + UPDATE ${ippool_table} \ + SET \ + nasipaddress = '%{NAS-IP-Address}', \ + pool_key = '${pool_key}', \ + callingstationid = '%{Calling-Station-Id}', \ + username = '%{User-Name}', \ + expiry_time = datetime(strftime('%%s', 'now') + ${lease_duration}, 'unixepoch') \ + WHERE framedipaddress = '%I'" + +# +# Extend an IP expiry time when an accounting START record arrives +# +start_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = datetime(strftime('%%s', 'now') + ${lease_duration}, 'unixepoch') \ + WHERE nasipaddress = '%{NAS-IP-Address}' \ + AND pool_key = '${pool_key}' \ + AND username = '%{User-Name}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND framedipaddress = '%{${attribute_name}}'" + +# +# Expire an IP when an accounting STOP record arrives +# +stop_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = datetime('now') \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}' \ + AND pool_key = '${pool_key}' \ + AND username = '%{User-Name}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND framedipaddress = '%{${attribute_name}}'" + +# +# Update the expiry time for an IP when an accounting ALIVE record arrives +# +alive_update = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = datetime(strftime('%%s', 'now') + ${lease_duration}, 'unixepoch') \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}' \ + AND pool_key = '${pool_key}' \ + AND username = '%{User-Name}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND framedipaddress = '%{${attribute_name}}'" + +# +# Expires all IPs allocated to a NAS when an accounting ON record arrives +# +on_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = datetime('now') \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}'" + +# +# Expires all IPs allocated to a NAS when an accounting OFF record arrives +# +off_clear = "\ + UPDATE ${ippool_table} \ + SET \ + expiry_time = datetime('now') \ + WHERE nasipaddress = '%{%{Nas-IP-Address}:-%{Nas-IPv6-Address}}'" + diff --git a/raddb/mods-config/sql/ippool/sqlite/schema.sql b/raddb/mods-config/sql/ippool/sqlite/schema.sql new file mode 100644 index 0000000..b020c62 --- /dev/null +++ b/raddb/mods-config/sql/ippool/sqlite/schema.sql @@ -0,0 +1,18 @@ +-- +-- Table structure for table 'radippool' +-- +CREATE TABLE radippool ( + id int(11) PRIMARY KEY, + pool_name varchar(30) NOT NULL, + framedipaddress varchar(15) NOT NULL default '', + nasipaddress varchar(15) NOT NULL default '', + calledstationid VARCHAR(30) NOT NULL default '', + callingstationid VARCHAR(30) NOT NULL default '', + expiry_time DATETIME NOT NULL default (DATETIME('now')), + username varchar(64) NOT NULL default '', + pool_key varchar(30) NOT NULL default '' +); + +CREATE INDEX radippool_poolname_expire ON radippool(pool_name, expiry_time); +CREATE INDEX radippool_framedipaddress ON radippool(framedipaddress); +CREATE INDEX radippool_nasip_poolkey_ipaddress ON radippool(nasipaddress, pool_key, framedipaddress); diff --git a/raddb/mods-config/sql/main/mongo/queries.conf b/raddb/mods-config/sql/main/mongo/queries.conf new file mode 100644 index 0000000..732e1e8 --- /dev/null +++ b/raddb/mods-config/sql/main/mongo/queries.conf @@ -0,0 +1,289 @@ +# -*- text -*- +# +# main/mongo/queries.conf -- Mongo configuration queries +# +# Note that as Mongo is a "schemaless" database, there is no +# default schema. +# +# Note also that the Mongo driver is a work in progress. If it works +# for you, great. If the queries do not work, please send a patch. +# But the FreeRADIUS team are not experts in Mongo, and cannot help +# with creating Mongo queries. +# +# $Id$ + +####################################################################### +# Query config: Username +####################################################################### +# This is the username that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used +# below everywhere a username substitution is needed so you you can +# be sure the username passed from the client is escaped properly. +# +# Uncomment the next line, if you want the sql_user_name to mean: +# +# Use Stripped-User-Name, if it's there. +# Else use User-Name, if it's there, +# Else use hard-coded string "none" as the user name. +# +#sql_user_name = "%{%{Stripped-User-Name}:-%{%{User-Name}:-none}}" + +sql_user_name = "%{User-Name}" + +####################################################################### +# Authorization Queries +####################################################################### +# These queries compare the check items for the user +# in ${authcheck_table} and setup the reply items in +# ${authreply_table}. You can use any query/tables +# you want, but the return data for each row MUST +# be in the following order: +# +# 0. Row ID (currently unused) +# 1. UserName/GroupName +# 2. Item Attr Name +# 3. Item Attr Value +# 4. Item Attr Operation +####################################################################### + +# +# Aggregate query that return like for SQL standard N rows with columns <id>,<username>,<attribute>,<value>,<op> +# +# Example of Result: +# +# { "id" : 0, "username": "bob", "attribute" : "User-Name", "Value" : "pippo", "op" : "==" } +# { "id" : 0, "username": "bob", "attribute" : "ClearText-Password", "value" : "pwd1", "op" : ":=" } +# { "id" : 0, "username": "bob", "attribute" : "Cache-TTL", "value" : 1000, "op" : ":=" } +# +authorize_check_query = "db.${authcheck_table}.aggregate([ \ + { \ + '$match': { \ + 'calling_station_id': '%{Calling-Station-id}', \ + 'auth_blocked': 'false' \ + } \ + }, \ + { \ + '$addFields': { \ + 'attributes.User-Name': '$usr', \ + 'attributes.ClearText-Password': '$pwd', \ + 'attributes.Cache-TTL': '$ttlcache', \ + 'attributes.Enable-Roaming': '$roaming', \ + 'attributes.Pool-Name': '$pool_name' \ + } \ + }, \ + { \ + '$project': { \ + 'calling_station_id': 1, \ + 'attributes': { \ + '$objectToArray': '$attributes' \ + } \ + } \ + }, \ + { \ + '$unwind': '$attributes' \ + }, \ + { \ + '$project': { \ + '_id': 0, \ + 'username': '', \ + 'attribute': '$attributes.k', \ + 'value': '$attributes.v', \ + 'op': ':=' \ + } \ + } \ +])" \ + +# TBD: fill in things here +authorize_reply_query = "" + +################################################################## + +# +# TBD: fill in things here +# +#authorize_group_check_query = "" +#authorize_group_reply_query = "" + +####################################################################### +# Group Membership Queries +####################################################################### +# group_membership_query - Check user group membership +####################################################################### + +# +# TBD: Fill in things here. +# +#group_membership_query = "" + + +####################################################################### +# Accounting and Post-Auth Queries +####################################################################### +# These queries insert/update accounting and authentication records. +# The query to use is determined by the value of 'reference'. +# This value is used as a configuration path and should resolve to one +# or more 'query's. If reference points to multiple queries, and a query +# fails, the next query is executed. +# +# Behaviour is identical to the old 1.x/2.x module, except we can now +# fail between N queries, and query selection can be based on any +# combination of attributes, or custom 'Acct-Status-Type' values. +####################################################################### + +accounting { + reference = "%{tolower:type.%{Acct-Status-Type}.query}" + + type { + + start { + query = "db.connections.findAndModify({ \ + 'query': { \ + 'calling_station_id': '%{Calling-Station-Id}', \ + 'pgw_node': '%{NAS-Identifier}', \ + 'acct_session_id': '%{Acct-Session-Id}', \ + }, \ + 'update': { \ + '$set': { \ + 'update_date': { '$date': { '$numberLong': '%{expr: (%l * 1000) + (%M / 1000)}' } }, \ + 'ip': '%{Framed-IP-Address}', \ + 'start_time': '%{Packet-Original-Timestamp}', \ + }, \ + '$push': { \ + 'events_data': { \ + 'event_id': '%{sha256:%{tolower:%{Calling-Station-Id}', \ + 'event_type': 'Accounting-Start', \ + 'event_time': '%{Packet-Original-Timestamp}', \ + 'creation_date': { '$date': { '$numberLong': '%{expr: (%l * 1000) + (%M / 1000)}' } } \ + } \ + }, \ + '$setOnInsert': { \ + 'pool_name': '%{Control:Pool-Name}', \ + 'ip': '%{Framed-IP-Address}', \ + 'closed': false, \ + 'update_counter': 0, \ + 'creation_date': { '$date': { '$numberLong': '%{expr: (%l * 1000) + (%M / 1000)}' } } \ + } \ + }, \ + 'upsert': true \ + })" + + query = "db.simultaneous_connections.findAndModify({ \ + 'query': { \ + 'pool_name': '%{Control:Pool-Name}' \ + }, \ + 'update': { \ + '$inc': { \ + 'conns_counter': 1 \ + } \ + '$setOnInsert': { \ + 'pool_name': '%{Control:Pool-Name}', \ + 'conns_counter': 1 \ + }, \ + }, \ + 'upsert': true \ + })" + # End Start + } + + interim-update { + query = "db.connections.findAndModify({ \ + 'query': { \ + 'calling_station_id': '%{Calling-Station-Id}', \ + 'pgw_node': '%{NAS-Identifier}', \ + 'acct_session_id': '%{Acct-Session-Id}' \ + }, \ + 'update': { \ + '$set': { \ + 'update_date': { '$date': { '$numberLong': '%{expr: (%l * 1000) + (%M / 1000)}' } }, \ + 'last_upd_interim': '%{Packet-Original-Timestamp}' \ + }, \ + '$inc': { \ + 'update_counter': 1 \ + }, \ + '$push': { \ + 'events_data': { \ + 'event_id': '%{sha256:%{tolower:%{Calling-Station-Id}', \ + 'event_type': 'Accounting-Interim-Update', \ + 'event_time': '%{Packet-Original-Timestamp}', \ + 'creation_date': { '$date': { '$numberLong': '%{expr: (%l * 1000) + (%M / 1000)}' } } \ + } \ + }, \ + '$setOnInsert': { \ + 'pool_name': '%{Control:Pool-Name}', \ + 'ip': '%{Framed-IP-Address}', \ + 'closed': false, \ + 'creation_date': { '$date': { '$numberLong': '%{expr: (%l * 1000) + (%M / 1000)}' } } \ + } \ + }, + 'upsert': true \ + })" + # End Interim-Update + } + + stop { + query = "db.connections.findAndModify({ \ + 'query': { \ + 'calling_station_id': '%{Calling-Station-Id}', \ + 'pgw_node': '%{NAS-Identifier}', \ + 'acct_session_id': '%{Acct-Session-Id}' \ + }, \ + 'update': { \ + '$set': { \ + 'update_date': { '$date': { '$numberLong': '%{expr: (%l * 1000) + (%M / 1000)}' } }, \ + 'stop_time': '%{Packet-Original-Timestamp}', \ + 'closed': true \ + }, \ + '$push': { \ + 'events_data': { \ + 'event_id': '%{sha256:%{tolower:%{Calling-Station-Id}', \ + 'event_type': 'Accounting-Stop', \ + 'event_time': '%{Packet-Original-Timestamp}', \ + 'creation_date': { '$date': { '$numberLong': '%{expr: (%l * 1000) + (%M / 1000)}' } } \ + } \ + }, \ + '$setOnInsert': { \ + 'pool_name': '%{Control:Pool-Name}', \ + 'ip': '%{Framed-IP-Address}', \ + 'update_counter': 0, \ + 'creation_date': { '$date': { '$numberLong': '%{expr: (%l * 1000) + (%M / 1000)}' } } \ + } \ + }, \ + 'upsert': true \ + })" + + # End Stop + } + + } +} + + +####################################################################### +# Authentication Logging Queries +####################################################################### +# postauth_query - Insert some info after authentication +####################################################################### + +post-auth { + query = "db.post_auth.findAndModify({ \ + 'query': { \ + 'calling_station_id': '%{Calling-Station-Id}', \ + 'nas_ip': '%{NAS-Identifier}' \ + }, \ + 'update': { \ + '$set': { \ + 'update_date': { '$date': { '$numberLong': '%{expr: (%l * 1000) + (%M / 1000)}' } }, \ + 'last_event_ts': '%{Packet-Original-Timestamp}' \ + }, \ + '$inc': { \ + 'reject_counter': 1 \ + }, \ + '$setOnInsert': { \ + 'calling_station_id': '%{Calling-Station-Id}', \ + 'nas_ip': '%{NAS-Identifier}', \ + 'creation_date': { '$date': { '$numberLong': '%{expr: (%l * 1000) + (%M / 1000)}' } } \ + } \ + }, \ + 'upsert': true \ + })" +} diff --git a/raddb/mods-config/sql/main/mssql/process-radacct.sql b/raddb/mods-config/sql/main/mssql/process-radacct.sql new file mode 100644 index 0000000..01129b6 --- /dev/null +++ b/raddb/mods-config/sql/main/mssql/process-radacct.sql @@ -0,0 +1,161 @@ +# -*- text -*- +# +# main/mssql/process-radacct.sql -- Schema extensions for processing radacct entries +# +# $Id$ + +-- --------------------------------- +-- - Per-user data usage over time - +-- --------------------------------- +-- +-- An extension to the standard schema to hold per-user data usage statistics +-- for arbitrary periods. +-- +-- The data_usage_by_period table is populated by periodically calling the +-- fr_new_data_usage_period stored procedure. +-- +-- This table can be queried in various ways to produce reports of aggregate +-- data use over time. For example, if the fr_new_data_usage_period SP is +-- invoked one per day just after midnight, to produce usage data with daily +-- granularity, then a reasonably accurate monthly bandwidth summary for a +-- given user could be obtained with: +-- +-- SELECT +-- FORMAT(period_start, 'yyyy-MMMM') AS month, +-- SUM(acctinputoctets)/1000/1000/1000 AS GB_in, +-- SUM(acctoutputoctets)/1000/1000/1000 AS GB_out +-- FROM +-- data_usage_by_period +-- WHERE +-- username='bob' AND +-- period_end <> 0 +-- GROUP BY +-- FORMAT(period_start, 'yyyy-MMMM'); +-- +-- +----------------+----------+-----------+ +-- | month | GB_in | GB_out | +-- +----------------+----------+-----------+ +-- | 2019-July | 5.782279 | 50.545664 | +-- | 2019-August | 4.230543 | 48.523096 | +-- | 2019-September | 4.847360 | 48.631835 | +-- | 2019-October | 6.456763 | 51.686231 | +-- | 2019-November | 6.362537 | 52.385710 | +-- | 2019-December | 4.301524 | 50.762240 | +-- | 2020-January | 5.436280 | 49.067775 | +-- +----------------+----------+-----------+ +-- +CREATE TABLE data_usage_by_period ( + username VARCHAR(64) NOT NULL, + period_start DATETIME NOT NULL, + period_end DATETIME NOT NULL, + acctinputoctets NUMERIC(19), + acctoutputoctets NUMERIC(19), + PRIMARY KEY (username, period_start) +); +GO + +CREATE INDEX idx_data_usage_by_period_period_end ON data_usage_by_period(period_end); +GO + +-- +-- Stored procedure that when run with some arbitrary frequency, say +-- once per day by cron, will process the recent radacct entries to extract +-- time-windowed data containing acct{input,output}octets ("data usage") per +-- username, per period. +-- +-- Each invocation will create new rows in the data_usage_by_period tables +-- containing the data used by each user since the procedure was last invoked. +-- The intervals do not need to be identical but care should be taken to +-- ensure that the start/end of each period aligns well with any intended +-- reporting intervals. +-- +-- It can be invoked by running: +-- +-- EXEC fr_new_data_usage_period; +-- +-- +CREATE OR ALTER PROCEDURE fr_new_data_usage_period +AS +BEGIN + + DECLARE @v_start DATETIME; + DECLARE @v_end DATETIME; + + SELECT @v_start = COALESCE(DATEADD(ss, 1, MAX(period_end)), CAST('1970-01-01' AS DATETIME)) FROM data_usage_by_period; + SELECT @v_end = CAST(CURRENT_TIMESTAMP AS DATETIME2(0)); + + BEGIN TRAN; + + -- + -- Add the data usage for the sessions that were active in the current + -- period to the table. Include all sessions that finished since the start + -- of this period as well as those still ongoing. + -- + MERGE INTO data_usage_by_period d + USING ( + SELECT + username, + @v_start AS period_start, + @v_end AS period_end, + SUM(acctinputoctets) AS acctinputoctets, + SUM(acctoutputoctets) AS acctoutputoctets + FROM (( + SELECT + username, acctinputoctets, acctoutputoctets + FROM + radacct + WHERE + acctstoptime > @v_start + ) UNION ALL ( + SELECT + username, acctinputoctets, acctoutputoctets + FROM + radacct + WHERE + acctstoptime=0 + )) a + GROUP BY + username + ) s + ON ( d.username = s.username AND d.period_start = s.period_start ) + WHEN MATCHED THEN + UPDATE SET + acctinputoctets = d.acctinputoctets + s.acctinputoctets, + acctoutputoctets = d.acctoutputoctets + s.acctoutputoctets, + period_end = @v_end + WHEN NOT MATCHED THEN + INSERT + (username, period_start, period_end, acctinputoctets, acctoutputoctets) + VALUES + (s.username, s.period_start, s.period_end, s.acctinputoctets, s.acctoutputoctets); + + -- + -- Create an open-ended "next period" for all ongoing sessions and carry a + -- negative value of their data usage to avoid double-accounting when we + -- process the next period. Their current data usage has already been + -- allocated to the current and possibly previous periods. + -- + -- MSSQL doesn't allow a DATETIME to be NULL so we use "0" (1900-01-01) to + -- indicate the open-ended interval. + -- + INSERT INTO data_usage_by_period (username, period_start, period_end, acctinputoctets, acctoutputoctets) + SELECT * + FROM ( + SELECT + username, + DATEADD(ss,1,@v_end) AS period_start, + 0 AS period_end, + 0 - SUM(acctinputoctets) AS acctinputoctets, + 0 - SUM(acctoutputoctets) AS acctoutputoctets + FROM + radacct + WHERE + acctstoptime=0 + GROUP BY + username + ) s; + + COMMIT; + +END +GO diff --git a/raddb/mods-config/sql/main/mssql/queries.conf b/raddb/mods-config/sql/main/mssql/queries.conf new file mode 100644 index 0000000..1978463 --- /dev/null +++ b/raddb/mods-config/sql/main/mssql/queries.conf @@ -0,0 +1,617 @@ +# -*- text -*- +# +# main/mssql/queries.conf -- MSSQL configuration for default schema (schema.sql) +# +# $Id$ + +# Safe characters list for sql queries. Everything else is replaced +# with their mime-encoded equivalents. +# The default list should be ok +#safe_characters = "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" + +####################################################################### +# Query config: Username +####################################################################### +# This is the username that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used +# below everywhere a username substitution is needed so you you can +# be sure the username passed from the client is escaped properly. +# +# Uncomment the next line, if you want the sql_user_name to mean: +# +# Use Stripped-User-Name, if it's there. +# Else use User-Name, if it's there, +# Else use hard-coded string "none" as the user name. +#sql_user_name = "%{%{Stripped-User-Name}:-%{%{User-Name}:-none}}" +# +sql_user_name = "%{User-Name}" + +####################################################################### +# Query config: Event-Timestamp +####################################################################### +# event_timestamp_epoch is the basis for the time inserted into +# accounting records. Typically this will be the Event-Timestamp of the +# accounting request, which is usually provided by a NAS. +# +# Uncomment the next line, if you want the timestamp to be based on the +# request reception time recorded by this server, for example if you +# distrust the provided Event-Timestamp. +#event_timestamp_epoch = "%l" + +event_timestamp_epoch = "%{%{integer:Event-Timestamp}:-%l}" + +# event_timestamp is the SQL snippet for converting an epoch timestamp +# to an SQL date. + +event_timestamp = "DATEADD(SS, ${event_timestamp_epoch}, '19700101')" + +####################################################################### +# Query config: Class attribute +####################################################################### +# +# 3.0.22 and later have a "class" column in the accounting table. +# +# However, we do NOT want to break existing configurations by adding +# the Class attribute to the default queries. If we did that, then +# systems using newer versions of the server would fail, because +# there is no "class" column in their accounting tables. +# +# The solution to that is the following "class" subsection. If your +# database has a "class" column for the various tables, then you can +# uncomment the configuration items here. The queries below will +# then automatically insert the Class attribute into radacct, +# radpostauth, etc. +# +class { + # + # Delete the '#' character from each of the configuration + # items in this section. This change puts the Class + # attribute into the various tables. Leave the double-quoted + # string there, as the value for the configuration item. + # + # See also policy.d/accounting, and the "insert_acct_class" + # policy. You will need to list (or uncomment) + # "insert_acct_class" in the "post-auth" section in order to + # create a Class attribute. + # + column_name = # ", class" + packet_xlat = # ", '%{Class}'" + reply_xlat = # ", '%{reply:Class}'" +} + +####################################################################### +# Authorization Queries +####################################################################### +# These queries compare the check items for the user +# in ${authcheck_table} and setup the reply items in +# ${authreply_table}. You can use any query/tables +# you want, but the return data for each row MUST +# be in the following order: +# +# 0. Row ID (currently unused) +# 1. UserName/GroupName +# 2. Item Attr Name +# 3. Item Attr Value +# 4. Item Attr Operation +####################################################################### +# Query for case sensitive usernames was removed. Please contact with me, +# if you know analog of STRCMP functions for MS SQL. + +authorize_check_query = "\ + SELECT id, UserName, Attribute, Value, op \ + FROM ${authcheck_table} \ + WHERE Username = '%{SQL-User-Name}' \ + ORDER BY id" + +authorize_reply_query = "\ + SELECT id, UserName, Attribute, Value, op \ + FROM ${authreply_table} \ + WHERE Username = '%{SQL-User-Name}' \ + ORDER BY id" + +authorize_group_check_query = "\ + SELECT \ + ${groupcheck_table}.id,${groupcheck_table}.GroupName, \ + ${groupcheck_table}.Attribute,${groupcheck_table}.Value, \ + ${groupcheck_table}.op \ + FROM ${groupcheck_table},${usergroup_table} \ + WHERE ${usergroup_table}.Username = '%{SQL-User-Name}' \ + AND ${usergroup_table}.GroupName = ${groupcheck_table}.GroupName \ + ORDER BY ${groupcheck_table}.id" + +authorize_group_reply_query = "\ + SELECT \ + ${groupreply_table}.id, ${groupreply_table}.GroupName, \ + ${groupreply_table}.Attribute,${groupreply_table}.Value, \ + ${groupreply_table}.op \ + FROM ${groupreply_table},${usergroup_table} \ + WHERE ${usergroup_table}.Username = '%{SQL-User-Name}' \ + AND ${usergroup_table}.GroupName = ${groupreply_table}.GroupName \ + ORDER BY ${groupreply_table}.id" + +group_membership_query = "\ + SELECT groupname \ + FROM ${usergroup_table} \ + WHERE username = '%{SQL-User-Name}' \ + ORDER BY priority" + +####################################################################### +# Simultaneous Use Checking Queries +####################################################################### +# simul_count_query - query for the number of current connections +# - If this is not defined, no simultaneous use checking +# - will be performed by this module instance +# simul_verify_query - query to return details of current connections +# for verification +# - Leave blank or commented out to disable verification step +# - Note that the returned field order should not be changed. +####################################################################### + +simul_count_query = "\ + SELECT COUNT(*) \ + FROM ${acct_table1} \ + WHERE UserName = '%{SQL-User-Name}' \ + AND AcctStopTime = 0" + +simul_verify_query = "\ + SELECT \ + RadAcctId, AcctSessionId, UserName, NASIPAddress, NASPortId, FramedIPAddress, \ + CallingStationId, FramedProtocol \ + FROM ${acct_table1} \ + WHERE UserName = '%{SQL-User-Name}' \ + AND AcctStopTime = 0" + +####################################################################### +# Accounting and Post-Auth Queries +####################################################################### +# These queries insert/update accounting and authentication records. +# The query to use is determined by the value of 'reference'. +# This value is used as a configuration path and should resolve to one +# or more 'query's. If reference points to multiple queries, and a query +# fails, the next query is executed. +# +# Behaviour is identical to the old 1.x/2.x module, except we can now +# fail between N queries, and query selection can be based on any +# combination of attributes, or custom 'Acct-Status-Type' values. +####################################################################### +accounting { + reference = "%{tolower:type.%{%{Acct-Status-Type}:-%{Request-Processing-Stage}}.query}" + + # Write SQL queries to a logfile. This is potentially useful for bulk inserts + # when used with the rlm_sql_null driver. +# logfile = ${logdir}/accounting.sql + + type { + accounting-on { + query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctStopTime=${....event_timestamp}, \ + AcctSessionTime=${....event_timestamp_epoch} - \ + DATEDIFF(SS, '1970-01-01', AcctStartTime), \ + AcctTerminateCause='%{%{Acct-Terminate-Cause}:-NAS-Reboot}', \ + AcctStopDelay = %{%{Acct-Delay-Time}:-0} \ + WHERE AcctStopTime = 0 \ + AND NASIPAddress = '%{NAS-IP-Address}' \ + AND AcctStartTime <= ${....event_timestamp}" + } + + accounting-off { + query = "${..accounting-on.query}" + } + + # + # Implement the "sql_session_start" policy. + # See raddb/policy.d/accounting for more details. + # + # You also need to fix the other queries as + # documented below. Look for "sql_session_start". + # + post-auth { + query = "\ + INSERT INTO ${....acct_table1} \ + INSERT INTO ${....acct_table1} ( \ + AcctSessionId, \ + AcctUniqueId, \ + UserName, \ + Realm, \ + NASIPAddress, \ + NASPortId, \ + NASPortType, \ + AcctStartTime, \ + AcctSessionTime, \ + AcctUpdateTime, \ + AcctAuthentic, \ + ConnectInfo_start, \ + ConnectInfo_stop, \ + AcctInputOctets, \ + AcctOutputOctets, \ + CalledStationId, \ + CallingStationId, \ + AcctTerminateCause, \ + ServiceType, \ + FramedProtocol, \ + FramedIPAddress, \ + FramedIPv6Address, \ + FramedIPv6Prefix, \ + FramedInterfaceId, \ + DelegatedIPv6Prefix \ + ${....class.column_name}) \ + VALUES(\ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + 0, \ + '', \ + '%{Connect-Info}', \ + '', \ + 0, \ + 0, \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '', \ + '%{Service-Type}', \ + '', \ + '', \ + '', \ + '', \ + '', \ + '' \ + ${....class.packet_xlat})" + + query = "\ + UPDATE ${....acct_table1} SET \ + AcctStartTime = '%S', \ + ConnectInfo_start = '%{Connect-Info}', \ + AcctSessionId = '%{Acct-Session-Id}' \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime = 0" + } + + start { + query = "\ + INSERT INTO ${....acct_table1} ( \ + AcctSessionId, \ + AcctUniqueId, \ + UserName, \ + Realm, \ + NASIPAddress, \ + NASPortId, \ + NASPortType, \ + AcctStartTime, \ + AcctUpdateTime, \ + AcctSessionTime, \ + AcctAuthentic, \ + ConnectInfo_start, \ + ConnectInfo_stop, \ + AcctInputOctets, \ + AcctOutputOctets, \ + CalledStationId, \ + CallingStationId, \ + AcctTerminateCause, \ + ServiceType, \ + FramedProtocol, \ + FramedIPAddress, \ + FramedIPv6Address, \ + FramedIPv6Prefix, \ + FramedInterfaceId, \ + DelegatedIPv6Prefix, \ + AcctStartDelay, \ + AcctStopDelay \ + ${....class.column_name}) \ + VALUES(\ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + '0', \ + '%{Acct-Authentic}', \ + '%{Connect-Info}', \ + '', \ + '0', \ + '0', \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}', \ + '%{Acct-Delay-Time}', \ + '0' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + FramedIpAddress = '%{Framed-IP-Address}', \ + FramedIpv6Address = '%{Framed-IPv6-Address}', \ + FramedIpv6Prefix = '%{Framed-IPv6-Prefix}', \ + FramedInterfaceId = '%{Framed-Interface-Id}', \ + DelegatedIpv6Prefix = '%{Delegated-IPv6-Prefix}', \ + AcctStartTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp} \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime = 0" + + query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctStartTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp }, \ + AcctStartDelay = '%{%{Acct-Delay-Time}:-0}', \ + ConnectInfo_start = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-ID}' \ + AND AcctStopTime = 0" + } + + interim-update { + query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctInterval = DATEDIFF(second, CASE WHEN AcctUpdateTime > 0 THEN AcctUpdateTime ELSE AcctStartTime END, ${....event_timestamp}), \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctSessionTime = '%{Acct-Session-Time}', \ + AcctInputOctets = convert(bigint, '%{%{Acct-Input-Gigawords}:-0}' * POWER(2.0, 32)) | '%{%{Acct-Input-Octets}:-0}', \ + AcctOutputOctets = convert(bigint, '%{%{Acct-Output-Gigawords}:-0}' * POWER(2.0, 32)) | '%{%{Acct-Output-Octets}:-0}', \ + FramedIPAddress = '%{Framed-IP-Address}', \ + FramedIPv6Address = '%{Framed-IPv6-Address}', \ + FramedIPv6Prefix = '%{Framed-IPv6-Prefix}', \ + FramedInterfaceId = '%{Framed-Interface-Id}', \ + DelegatedIPv6Prefix = '%{Delegated-IPv6-Prefix}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-ID}' \ + AND AcctStopTime = 0" + + query = "\ + INSERT INTO ${....acct_table1} ( \ + AcctSessionId, \ + AcctUniqueId, \ + UserName, \ + Realm, \ + NASIPAddress, \ + NASPortId, \ + NASPortType, \ + AcctStartTime, \ + AcctUpdateTime, \ + AcctSessionTime, \ + AcctAuthentic, \ + ConnectInfo_start, \ + AcctInputOctets, \ + AcctOutputOctets, \ + CalledStationId, \ + CallingStationId, \ + ServiceType, \ + FramedProtocol, \ + FramedIPAddress, \ + FramedIPv6Address, \ + FramedIPv6Prefix, \ + FramedInterfaceId, \ + DelegatedIPv6Prefix, \ + AcctStartDelay \ + ${....class.column_name}) \ + VALUES(\ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + '%{Acct-Session-Time}', \ + '%{Acct-Authentic}', \ + '', \ + '%{Acct-Input-Octets}', \ + '%{Acct-Output-Octets}', \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}', \ + '0' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + FramedIPAddress = '%{Framed-IP-Address}', \ + FramedIPv6Address = '%{Framed-IPv6-Address}', \ + FramedIPv6Prefix = '%{Framed-IPv6-Prefix}', \ + FramedInterfaceId = '%{Framed-Interface-Id}', \ + DelegatedIPv6Prefix = '%{Delegated-IPv6-Prefix}', \ + AcctInputOctets = convert(bigint, '%{%{Acct-Input-Gigawords}:-0}' * POWER(2.0, 32)) | '%{%{Acct-Input-Octets}:-0}', \ + AcctOutputOctets = convert(bigint, '%{%{Acct-Output-Gigawords}:-0}' * POWER(2.0, 32)) | '%{%{Acct-Output-Octets}:-0}' \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime = 0" + } + + stop { + query = "\ + UPDATE ${....acct_table2} \ + SET \ + AcctStopTime = ${....event_timestamp}, \ + AcctSessionTime = '%{Acct-Session-Time}', \ + AcctInputOctets = convert(bigint, '%{%{Acct-Input-Gigawords}:-0}' * POWER(2.0, 32)) | '%{%{Acct-Input-Octets}:-0}', \ + AcctOutputOctets = convert(bigint, '%{%{Acct-Output-Gigawords}:-0}' * POWER(2.0, 32)) | '%{%{Acct-Output-Octets}:-0}', \ + AcctTerminateCause = '%{Acct-Terminate-Cause}', \ + AcctStopDelay = '%{%{Acct-Delay-Time}:-0}', \ + ConnectInfo_stop = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-ID}' \ + AND AcctStopTime = 0" + + query = "\ + INSERT into ${....acct_table2} (\ + AcctSessionId, \ + AcctUniqueId, \ + UserName, \ + Realm, \ + NASIPAddress, \ + NASPortID, \ + NASPortType, \ + AcctStopTime, \ + AcctSessionTime, \ + AcctAuthentic, \ + ConnectInfo_start, \ + ConnectInfo_stop, \ + AcctInputOctets, \ + AcctOutputOctets, \ + CalledStationId, \ + CallingStationId, \ + AcctTerminateCause, \ + ServiceType, \ + FramedProtocol, \ + FramedIPAddress, \ + FramedIPv6Address, \ + FramedIPv6Prefix, \ + FramedInterfaceId, \ + DelegatedIPv6Prefix, \ + AcctStartDelay, \ + AcctStopDelay \ + ${....class.column_name}) \ + VALUES(\ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + '%{Acct-Session-Time}', \ + '%{Acct-Authentic}', \ + '', \ + '%{Connect-Info}', \ + convert(bigint, '%{%{Acct-Input-Gigawords}:-0}' * POWER(2.0, 32)) | '%{%{Acct-Input-Octets}:-0}', \ + convert(bigint, '%{%{Acct-Output-Gigawords}:-0}' * POWER(2.0, 32)) | '%{%{Acct-Output-Octets}:-0}', \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '%{Acct-Terminate-Cause}', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}', \ + '0', \ + '%{%{Acct-Delay-Time}:-0}' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + FramedIPAddress = '%{Framed-IP-Address}', \ + FramedIPv6Address = '%{Framed-IPv6-Address}', \ + FramedIPv6Prefix = '%{Framed-IPv6-Prefix}', \ + FramedInterfaceId = '%{Framed-Interface-Id}', \ + DelegatedIPv6Prefix = '%{Delegated-IPv6-Prefix}', \ + AcctStopTime = '%S', \ + AcctSessionTime = %{Acct-Session-Time}, \ + AcctInputOctets = convert(bigint, '%{%{Acct-Input-Gigawords}:-0}' * POWER(2.0, 32)) | '%{%{Acct-Input-Octets}:-0}', \ + AcctOutputOctets = convert(bigint, '%{%{Acct-Output-Gigawords}:-0}' * POWER(2.0, 32)) | '%{%{Acct-Output-Octets}:-0}', \ + AcctTerminateCause = '%{Acct-Terminate-Cause}', \ + ConnectInfo_stop = '%{Connect-Info}' \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime = 0" + } + + # + # No Acct-Status-Type == ignore the packet + # + accounting { + query = "SELECT true" + } + } +} + +post-auth { + # Write SQL queries to a logfile. This is potentially useful for bulk inserts + # when used with the rlm_sql_null driver. +# logfile = ${logdir}/post-auth.sql + + query = "\ + INSERT INTO ${..postauth_table} \ + (userName, pass, reply, authdate ${..class.column_name}) \ + VALUES(\ + '%{User-Name}', \ + '%{%{User-Password}:-CHAP-PASSWORD}', \ + '%{reply:Packet-Type}', \ + '%S.%{expr:%M / 1000}' \ + ${..class.reply_xlat})" +} diff --git a/raddb/mods-config/sql/main/mssql/schema.sql b/raddb/mods-config/sql/main/mssql/schema.sql new file mode 100644 index 0000000..7f6d633 --- /dev/null +++ b/raddb/mods-config/sql/main/mssql/schema.sql @@ -0,0 +1,302 @@ +-- $Id$d$ +-- +-- schela.sql rlm_sql - FreeRADIUS SQL Module +-- +-- Database schema for MSSQL rlm_sql module +-- +-- To load: +-- isql -S db_ip_addr -d db_name -U db_login -P db_passwd -i db_mssql.sql +-- +-- Based on: db_mysql.sql (Mike Machado <mike@innercite.com>) +-- +-- Dmitri Ageev <d_ageev@ortcc.ru> +-- + + +-- +-- Table structure for table 'radacct' +-- + +CREATE TABLE [radacct] ( + [RadAcctId] [numeric](21, 0) IDENTITY (1, 1) NOT NULL, + [AcctSessionId] [varchar] (64) NOT NULL, + [AcctUniqueId] [varchar] (32) NOT NULL, + [UserName] [varchar] (64) NOT NULL, + [GroupName] [varchar] (64) NOT NULL, + [Realm] [varchar] (64) NOT NULL, + [NASIPAddress] [varchar] (15) NOT NULL, + [NASPortId] [varchar] (32) NULL, + [NASPortType] [varchar] (32) NULL, + [AcctStartTime] [datetime] NOT NULL, + [AcctUpdateTime] [datetime] NOT NULL, + [AcctStopTime] [datetime] NOT NULL, + [AcctInterval] [bigint] NULL, + [AcctSessionTime] [bigint] NULL, + [AcctAuthentic] [varchar] (32) NULL, + [ConnectInfo_start] [varchar] (128) NULL, + [ConnectInfo_stop] [varchar] (128) NULL, + [AcctInputOctets] [bigint] NULL, + [AcctOutputOctets] [bigint] NULL, + [CalledStationId] [varchar] (50) NOT NULL, + [CallingStationId] [varchar] (50) NOT NULL, + [AcctTerminateCause] [varchar] (32) NOT NULL, + [ServiceType] [varchar] (32) NULL, + [FramedProtocol] [varchar] (32) NULL, + [FramedIPAddress] [varchar] (15) NOT NULL, + [FramedIPv6Address] [varchar] (45) NOT NULL, + [FramedIPv6Prefix] [varchar] (45) NOT NULL, + [FramedInterfaceId] [varchar] (44) NOT NULL, + [DelegatedIPv6Prefix] [varchar] (45) NOT NULL, + [AcctStartDelay] [int] NULL, + [AcctStopDelay] [int] NULL, + [Class] [varchar] (64) NULL +) ON [PRIMARY] +GO + +ALTER TABLE [radacct] WITH NOCHECK ADD + CONSTRAINT [DF_radacct_GroupName] DEFAULT ('') FOR [GroupName], + CONSTRAINT [DF_radacct_AcctSessionId] DEFAULT ('') FOR [AcctSessionId], + CONSTRAINT [DF_radacct_AcctUniqueId] DEFAULT ('') FOR [AcctUniqueId], + CONSTRAINT [DF_radacct_UserName] DEFAULT ('') FOR [UserName], + CONSTRAINT [DF_radacct_Realm] DEFAULT ('') FOR [Realm], + CONSTRAINT [DF_radacct_NASIPAddress] DEFAULT ('') FOR [NASIPAddress], + CONSTRAINT [DF_radacct_NASPortId] DEFAULT (null) FOR [NASPortId], + CONSTRAINT [DF_radacct_NASPortType] DEFAULT (null) FOR [NASPortType], + CONSTRAINT [DF_radacct_AcctStartTime] DEFAULT ('1900-01-01 00:00:00') FOR [AcctStartTime], + CONSTRAINT [DF_radacct_AcctUpdateTime] DEFAULT ('1900-01-01 00:00:00') FOR [AcctUpdateTime], + CONSTRAINT [DF_radacct_AcctStopTime] DEFAULT ('1900-01-01 00:00:00') FOR [AcctStopTime], + CONSTRAINT [DF_radacct_AcctSessionTime] DEFAULT (null) FOR [AcctSessionTime], + CONSTRAINT [DF_radacct_AcctAuthentic] DEFAULT (null) FOR [AcctAuthentic], + CONSTRAINT [DF_radacct_ConnectInfo_start] DEFAULT (null) FOR [ConnectInfo_start], + CONSTRAINT [DF_radacct_ConnectInfo_stop] DEFAULT (null) FOR [ConnectInfo_stop], + CONSTRAINT [DF_radacct_AcctInputOctets] DEFAULT (null) FOR [AcctInputOctets], + CONSTRAINT [DF_radacct_AcctOutputOctets] DEFAULT (null) FOR [AcctOutputOctets], + CONSTRAINT [DF_radacct_CalledStationId] DEFAULT ('') FOR [CalledStationId], + CONSTRAINT [DF_radacct_CallingStationId] DEFAULT ('') FOR [CallingStationId], + CONSTRAINT [DF_radacct_AcctTerminateCause] DEFAULT ('') FOR [AcctTerminateCause], + CONSTRAINT [DF_radacct_ServiceType] DEFAULT (null) FOR [ServiceType], + CONSTRAINT [DF_radacct_FramedProtocol] DEFAULT (null) FOR [FramedProtocol], + CONSTRAINT [DF_radacct_FramedIPAddress] DEFAULT ('') FOR [FramedIPAddress], + CONSTRAINT [DF_radacct_FramedIPv6Address] DEFAULT ('') FOR [FramedIPv6Address], + CONSTRAINT [DF_radacct_FramedIPv6Prefix] DEFAULT ('') FOR [FramedIPv6Prefix], + CONSTRAINT [DF_radacct_FramedInterfaceId] DEFAULT ('') FOR [FramedInterfaceId], + CONSTRAINT [DF_radacct_DelegatedIPv6Prefix] DEFAULT ('') FOR [DelegatedIPv6Prefix], + CONSTRAINT [DF_radacct_AcctStartDelay] DEFAULT (null) FOR [AcctStartDelay], + CONSTRAINT [DF_radacct_AcctStopDelay] DEFAULT (null) FOR [AcctStopDelay], + CONSTRAINT [DF_radacct_Class] DEFAULT (null) FOR [Class], + CONSTRAINT [PK_radacct] PRIMARY KEY NONCLUSTERED + ( + [RadAcctId] + ) ON [PRIMARY] +GO + +CREATE INDEX [UserName] ON [radacct]([UserName]) ON [PRIMARY] +GO + +CREATE INDEX [FramedIPAddress] ON [radacct]([FramedIPAddress]) ON [PRIMARY] +GO + +CREATE INDEX [FramedIPv6Address] ON [radacct]([FramedIPv6Address]) ON [PRIMARY] +GO + +CREATE INDEX [FramedIPv6Prefix] ON [radacct]([FramedIPv6Prefix]) ON [PRIMARY] +GO + +CREATE INDEX [FramedInterfaceId] ON [radacct]([FramedInterfaceId]) ON [PRIMARY] +GO + +CREATE INDEX [DelegatedIPv6Prefix] ON [radacct]([DelegatedIPv6Prefix]) ON [PRIMARY] +GO + +CREATE INDEX [AcctSessionId] ON [radacct]([AcctSessionId]) ON [PRIMARY] +GO + +CREATE UNIQUE INDEX [AcctUniqueId] ON [radacct]([AcctUniqueId]) ON [PRIMARY] +GO + +CREATE INDEX [AcctStartTime] ON [radacct]([AcctStartTime]) ON [PRIMARY] +GO + +CREATE INDEX [AcctStopTime] ON [radacct]([AcctStopTime]) ON [PRIMARY] +GO + +CREATE INDEX [NASIPAddress] ON [radacct]([NASIPAddress]) ON [PRIMARY] +GO + +CREATE INDEX [Class] ON [radacct]([Class]) ON [PRIMARY] +GO + +/* For use by onoff */ +CREATE INDEX [RadacctBulkClose] ON [radacct]([NASIPAddress],[AcctStartTime]) WHERE [AcctStopTime] IS NULL ON [PRIMARY] +GO + + +-- +-- Table structure for table 'radacct' +-- + +CREATE TABLE [radcheck] ( + [id] [int] IDENTITY (1, 1) NOT NULL , + [UserName] [varchar] (64) NOT NULL , + [Attribute] [varchar] (32) NOT NULL , + [Value] [varchar] (253) NOT NULL , + [op] [char] (2) NULL +) ON [PRIMARY] +GO + +ALTER TABLE [radcheck] WITH NOCHECK ADD + CONSTRAINT [DF_radcheck_UserName] DEFAULT ('') FOR [UserName], + CONSTRAINT [DF_radcheck_Attribute] DEFAULT ('') FOR [Attribute], + CONSTRAINT [DF_radcheck_Value] DEFAULT ('') FOR [Value], + CONSTRAINT [DF_radcheck_op] DEFAULT (null) FOR [op], + CONSTRAINT [PK_radcheck] PRIMARY KEY NONCLUSTERED + ( + [id] + ) ON [PRIMARY] +GO + +CREATE INDEX [UserName] ON [radcheck]([UserName]) ON [PRIMARY] +GO + + +-- +-- Table structure for table 'radacct' +-- + +CREATE TABLE [radgroupcheck] ( + [id] [int] IDENTITY (1, 1) NOT NULL , + [GroupName] [varchar] (64) NOT NULL , + [Attribute] [varchar] (32) NOT NULL , + [Value] [varchar] (253) NOT NULL , + [op] [char] (2) NULL +) ON [PRIMARY] +GO + +ALTER TABLE [radgroupcheck] WITH NOCHECK ADD + CONSTRAINT [DF_radgroupcheck_GroupName] DEFAULT ('') FOR [GroupName], + CONSTRAINT [DF_radgroupcheck_Attribute] DEFAULT ('') FOR [Attribute], + CONSTRAINT [DF_radgroupcheck_Value] DEFAULT ('') FOR [Value], + CONSTRAINT [DF_radgroupcheck_op] DEFAULT (null) FOR [op], + CONSTRAINT [PK_radgroupcheck] PRIMARY KEY NONCLUSTERED + ( + [id] + ) ON [PRIMARY] +GO + +CREATE INDEX [GroupName] ON [radgroupcheck]([GroupName]) ON [PRIMARY] +GO + + +-- +-- Table structure for table 'radacct' +-- + +CREATE TABLE [radgroupreply] ( + [id] [int] IDENTITY (1, 1) NOT NULL , + [GroupName] [varchar] (64) NOT NULL , + [Attribute] [varchar] (32) NOT NULL , + [Value] [varchar] (253) NOT NULL , + [op] [char] (2) NULL , + [prio] [int] NOT NULL +) ON [PRIMARY] +GO + +ALTER TABLE [radgroupreply] WITH NOCHECK ADD + CONSTRAINT [DF_radgroupreply_GroupName] DEFAULT ('') FOR [GroupName], + CONSTRAINT [DF_radgroupreply_Attribute] DEFAULT ('') FOR [Attribute], + CONSTRAINT [DF_radgroupreply_Value] DEFAULT ('') FOR [Value], + CONSTRAINT [DF_radgroupreply_op] DEFAULT (null) FOR [op], + CONSTRAINT [DF_radgroupreply_prio] DEFAULT (0) FOR [prio], + CONSTRAINT [PK_radgroupreply] PRIMARY KEY NONCLUSTERED + ( + [id] + ) ON [PRIMARY] +GO + +CREATE INDEX [GroupName] ON [radgroupreply]([GroupName]) ON [PRIMARY] +GO + + +-- +-- Table structure for table 'radacct' +-- + +CREATE TABLE [radreply] ( + [id] [int] IDENTITY (1, 1) NOT NULL , + [UserName] [varchar] (64) NOT NULL , + [Attribute] [varchar] (32) NOT NULL , + [Value] [varchar] (253) NOT NULL , + [op] [char] (2) NULL +) ON [PRIMARY] +GO + +ALTER TABLE [radreply] WITH NOCHECK ADD + CONSTRAINT [DF_radreply_UserName] DEFAULT ('') FOR [UserName], + CONSTRAINT [DF_radreply_Attribute] DEFAULT ('') FOR [Attribute], + CONSTRAINT [DF_radreply_Value] DEFAULT ('') FOR [Value], + CONSTRAINT [DF_radreply_op] DEFAULT (null) FOR [op], + CONSTRAINT [PK_radreply] PRIMARY KEY NONCLUSTERED + ( + [id] + ) ON [PRIMARY] +GO + +CREATE INDEX [UserName] ON [radreply]([UserName]) ON [PRIMARY] +GO + + +-- +-- Table structure for table 'radacct' +-- + +CREATE TABLE [radusergroup] ( + [id] [int] IDENTITY (1, 1) NOT NULL , + [UserName] [varchar] (64) NOT NULL , + [GroupName] [varchar] (64) NULL , + [Priority] [int] NULL +) ON [PRIMARY] +GO + +ALTER TABLE [radusergroup] WITH NOCHECK ADD + CONSTRAINT [DF_radusergroup_UserName] DEFAULT ('') FOR [UserName], + CONSTRAINT [DF_radusergroup_GroupName] DEFAULT ('') FOR [GroupName], + CONSTRAINT [PK_radusergroup] PRIMARY KEY NONCLUSTERED + ( + [id] + ) ON [PRIMARY] +GO + +CREATE INDEX [UserName] ON [radusergroup]([UserName]) ON [PRIMARY] +GO + + +-- +-- Table structure for table 'radacct' +-- + +CREATE TABLE [radpostauth] ( + [id] [int] IDENTITY (1, 1) NOT NULL , + [userName] [varchar] (64) NOT NULL , + [pass] [varchar] (64) NOT NULL , + [reply] [varchar] (32) NOT NULL , + [authdate] [datetime] NOT NULL, + [Class] [varchar] (64) NULL +) +GO + +CREATE INDEX [userName] ON [radpostauth]([userName]) ON [PRIMARY] +GO + +CREATE INDEX [Class] ON [radpostauth]([Class]) ON [PRIMARY] +GO + +ALTER TABLE [radpostauth] WITH NOCHECK ADD + CONSTRAINT [DF_radpostauth_userName] DEFAULT ('') FOR [userName], + CONSTRAINT [DF_radpostauth_pass] DEFAULT ('') FOR [pass], + CONSTRAINT [DF_radpostauth_reply] DEFAULT ('') FOR [reply], + CONSTRAINT [DF_radpostauth_authdate] DEFAULT (getdate()) FOR [authdate], + CONSTRAINT [PK_radpostauth] PRIMARY KEY NONCLUSTERED + ( + [id] + ) ON [PRIMARY] +GO diff --git a/raddb/mods-config/sql/main/mysql/extras/wimax/queries.conf b/raddb/mods-config/sql/main/mysql/extras/wimax/queries.conf new file mode 100644 index 0000000..2694230 --- /dev/null +++ b/raddb/mods-config/sql/main/mysql/extras/wimax/queries.conf @@ -0,0 +1,40 @@ +# -*- text -*- +## +## wimax.conf -- MySQL configuration for WiMAX keying +## +## $Id$ + +# Safe characters list for sql queries. Everything else is replaced +# with their mime-encoded equivalents. +# The default list should be ok +#safe_characters = "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" + +####################################################################### +# Query config: Username +####################################################################### +# This is the username that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used below +# everywhere a username substitution is needed so you you can be sure +# the username passed from the client is escaped properly. +# +# Uncomment the next line, if you want the sql_user_name to mean: +# +# Use Stripped-User-Name, if it's there. +# Else use User-Name, if it's there, +# Else use hard-coded string "DEFAULT" as the user name. +#sql_user_name = "%{%{Stripped-User-Name}:-%{%{User-Name}:-DEFAULT}}" +# +sql_user_name = "%{User-Name}" + +####################################################################### +# Logging of WiMAX SPI -> key mappings +####################################################################### +# postauth_query - Insert some info after authentication +####################################################################### + +postauth_query = "INSERT INTO wimax \ + (username, authdate, spi, mipkey, lifetime) \ + VALUES ( \ + '%{User-Name}', '%S' \ + '%{%{reply:WiMAX-MN-hHA-MIP4-SPI}:-%{reply:WiMAX-MN-hHA-MIP6-SPI}}', \ + '%{%{reply:WiMAX-MN-hHA-MIP4-Key}:-%{reply:WiMAX-MN-hHA-MIP6-Key}}', '%{%{reply:Session-Timeout}:-86400}' )" diff --git a/raddb/mods-config/sql/main/mysql/extras/wimax/schema.sql b/raddb/mods-config/sql/main/mysql/extras/wimax/schema.sql new file mode 100644 index 0000000..e32224a --- /dev/null +++ b/raddb/mods-config/sql/main/mysql/extras/wimax/schema.sql @@ -0,0 +1,16 @@ +# +# WiMAX Table structure for table 'wimax', +# which replaces the "radpostauth" table. +# + +CREATE TABLE wimax ( + id int(11) NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + authdate timestamp NOT NULL, + spi varchar(16) NOT NULL default '', + mipkey varchar(400) NOT NULL default '', + lifetime int(12) default NULL, + PRIMARY KEY (id), + KEY username (username), + KEY spi (spi) +) ; diff --git a/raddb/mods-config/sql/main/mysql/process-radacct.sql b/raddb/mods-config/sql/main/mysql/process-radacct.sql new file mode 100644 index 0000000..8902338 --- /dev/null +++ b/raddb/mods-config/sql/main/mysql/process-radacct.sql @@ -0,0 +1,289 @@ +# -*- text -*- +# +# main/mysql/process-radacct.sql -- Schema extensions for processing radacct entries +# +# $Id$ + +-- --------------------------------- +-- - Per-user data usage over time - +-- --------------------------------- +-- +-- An extension to the standard schema to hold per-user data usage statistics +-- for arbitrary periods. +-- +-- The data_usage_by_period table is populated by periodically calling the +-- fr_new_data_usage_period stored procedure. +-- +-- This table can be queried in various ways to produce reports of aggregate +-- data use over time. For example, if the fr_new_data_usage_period SP is +-- invoked one per day just after midnight, to produce usage data with daily +-- granularity, then a reasonably accurate monthly bandwidth summary for a +-- given user could be obtained with: +-- +-- SELECT +-- DATE_FORMAT(period_start, '%Y-%M') AS month, +-- SUM(acctinputoctets)/1000/1000/1000 AS GB_in, +-- SUM(acctoutputoctets)/1000/1000/1000 AS GB_out +-- FROM +-- data_usage_by_period +-- WHERE +-- username='bob' AND +-- period_end IS NOT NULL +-- GROUP BY +-- YEAR(period_start), MONTH(period_start); +-- +-- +----------------+----------------+-----------------+ +-- | month | GB_in | GB_out | +-- +----------------+----------------+-----------------+ +-- | 2019-July | 5.782279230000 | 50.545664820000 | +-- | 2019-August | 4.230543340000 | 48.523096420000 | +-- | 2019-September | 4.847360590000 | 48.631835480000 | +-- | 2019-October | 6.456763250000 | 51.686231930000 | +-- | 2019-November | 6.362537730000 | 52.385710570000 | +-- | 2019-December | 4.301524440000 | 50.762240270000 | +-- | 2020-January | 5.436280540000 | 49.067775280000 | +-- +----------------+----------------+-----------------+ +-- 7 rows in set (0.000 sec) +-- +CREATE TABLE data_usage_by_period ( + username VARCHAR(64), + period_start DATETIME, + period_end DATETIME, + acctinputoctets BIGINT(20), + acctoutputoctets BIGINT(20), + PRIMARY KEY (username,period_start) +); +CREATE INDEX idx_data_usage_by_period_period_start ON data_usage_by_period (period_start); +CREATE INDEX idx_data_usage_by_period_period_end ON data_usage_by_period (period_end); + + +-- +-- Stored procedure that when run with some arbitrary frequency, say +-- once per day by cron, will process the recent radacct entries to extract +-- time-windowed data containing acct{input,output}octets ("data usage") per +-- username, per period. +-- +-- Each invocation will create new rows in the data_usage_by_period tables +-- containing the data used by each user since the procedure was last invoked. +-- The intervals do not need to be identical but care should be taken to +-- ensure that the start/end of each period aligns well with any intended +-- reporting intervals. +-- +-- It can be invoked by running: +-- +-- CALL fr_new_data_usage_period(); +-- +-- +DELIMITER $$ + +DROP PROCEDURE IF EXISTS fr_new_data_usage_period; +CREATE PROCEDURE fr_new_data_usage_period () +SQL SECURITY INVOKER +BEGIN + + DECLARE v_start DATETIME; + DECLARE v_end DATETIME; + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + SELECT IFNULL(DATE_ADD(MAX(period_end), INTERVAL 1 SECOND), FROM_UNIXTIME(0)) INTO v_start FROM data_usage_by_period; + SELECT NOW() INTO v_end; + + START TRANSACTION; + + -- + -- Add the data usage for the sessions that were active in the current + -- period to the table. Include all sessions that finished since the start + -- of this period as well as those still ongoing. + -- + INSERT INTO data_usage_by_period (username, period_start, period_end, acctinputoctets, acctoutputoctets) + SELECT * + FROM ( + SELECT + username, + v_start, + v_end, + SUM(acctinputoctets) AS acctinputoctets, + SUM(acctoutputoctets) AS acctoutputoctets + FROM (( + SELECT + username, acctinputoctets, acctoutputoctets + FROM + radacct + WHERE + acctstoptime > v_start + ) UNION ALL ( + SELECT + username, acctinputoctets, acctoutputoctets + FROM + radacct + WHERE + acctstoptime IS NULL + )) AS a + GROUP BY + username + ) AS s + ON DUPLICATE KEY UPDATE + acctinputoctets = data_usage_by_period.acctinputoctets + s.acctinputoctets, + acctoutputoctets = data_usage_by_period.acctoutputoctets + s.acctoutputoctets, + period_end = v_end; + + -- + -- Create an open-ended "next period" for all ongoing sessions and carry a + -- negative value of their data usage to avoid double-accounting when we + -- process the next period. Their current data usage has already been + -- allocated to the current and possibly previous periods. + -- + INSERT INTO data_usage_by_period (username, period_start, period_end, acctinputoctets, acctoutputoctets) + SELECT * + FROM ( + SELECT + username, + DATE_ADD(v_end, INTERVAL 1 SECOND), + NULL, + 0 - SUM(acctinputoctets), + 0 - SUM(acctoutputoctets) + FROM + radacct + WHERE + acctstoptime IS NULL + GROUP BY + username + ) AS s; + + COMMIT; + +END$$ + +DELIMITER ; + + +-- ------------------------------------------------------ +-- - "Lightweight" Accounting-On/Off strategy resources - +-- ------------------------------------------------------ +-- +-- The following resources are for use only when the "lightweight" +-- Accounting-On/Off strategy is enabled in queries.conf. +-- +-- Instead of bulk closing the radacct sessions belonging to a reloaded NAS, +-- this strategy leaves them open and records the NAS reload time in the +-- nasreload table. +-- +-- Where applicable, the onus is on the administator to: +-- +-- * Consider the nas reload times when deriving a list of +-- active/inactive sessions, and when determining the duration of sessions +-- interrupted by a NAS reload. (Refer to the view below.) +-- +-- * Close the affected sessions out of band. (Refer to the SP below.) +-- +-- +-- The radacct_with_reloads view presents the radacct table with two additional +-- columns: acctstoptime_with_reloads and acctsessiontime_with_reloads +-- +-- Where the session isn't closed (acctstoptime IS NULL), yet it started before +-- the last reload of the NAS (radacct.acctstarttime < nasreload.reloadtime), +-- the derived columns are set based on the reload time of the NAS (effectively +-- the point in time that the session was interrupted.) +-- +CREATE VIEW radacct_with_reloads AS +SELECT + a.*, + COALESCE(a.acctstoptime, + IF(a.acctstarttime < n.reloadtime, n.reloadtime, NULL) + ) AS acctstoptime_with_reloads, + COALESCE(a.acctsessiontime, + IF(a.acctstoptime IS NULL AND a.acctstarttime < n.reloadtime, + UNIX_TIMESTAMP(n.reloadtime) - UNIX_TIMESTAMP(a.acctstarttime), NULL) + ) AS acctsessiontime_with_reloads +FROM radacct a +LEFT OUTER JOIN nasreload n USING (nasipaddress); + + +-- +-- It may be desirable to periodically "close" radacct sessions belonging to a +-- reloaded NAS, replicating the "bulk close" Accounting-On/Off behaviour, +-- just not in real time. +-- +-- The fr_radacct_close_after_reload SP will set radacct.acctstoptime to +-- nasreload.reloadtime, calculate the corresponding radacct.acctsessiontime, +-- and set acctterminatecause to "NAS reboot" for interrupted sessions. It +-- does so in batches, which avoids long-lived locks on the affected rows. +-- +-- It can be invoked as follows: +-- +-- CALL fr_radacct_close_after_reload(); +-- +-- Note: This SP walks radacct in strides of v_batch_size. It will typically +-- skip closed and ongoing sessions at a rate significantly faster than +-- 100,000 rows per second and process batched updates faster than 20,000 +-- orphaned sessions per second. If this isn't fast enough then you should +-- really consider using a custom schema that includes partitioning by +-- nasipaddress or acct{start,stop}time. +-- +DELIMITER $$ + +DROP PROCEDURE IF EXISTS fr_radacct_close_after_reload; +CREATE PROCEDURE fr_radacct_close_after_reload () +SQL SECURITY INVOKER +BEGIN + + DECLARE v_a BIGINT(21); + DECLARE v_z BIGINT(21); + DECLARE v_updated BIGINT(21) DEFAULT 0; + DECLARE v_last_report DATETIME DEFAULT 0; + DECLARE v_last BOOLEAN DEFAULT FALSE; + DECLARE v_batch_size INT(12); + + -- + -- This works for many circumstances + -- + SET v_batch_size = 2500; + + SELECT MIN(radacctid) INTO v_a FROM radacct WHERE acctstoptime IS NULL; + + update_loop: LOOP + + SET v_z = NULL; + SELECT radacctid INTO v_z FROM radacct WHERE radacctid > v_a ORDER BY radacctid LIMIT v_batch_size,1; + + IF v_z IS NULL THEN + SELECT MAX(radacctid) INTO v_z FROM radacct; + SET v_last = TRUE; + END IF; + + UPDATE radacct a INNER JOIN nasreload n USING (nasipaddress) + SET + acctstoptime = n.reloadtime, + acctsessiontime = UNIX_TIMESTAMP(n.reloadtime) - UNIX_TIMESTAMP(acctstarttime), + acctterminatecause = 'NAS reboot' + WHERE + radacctid BETWEEN v_a AND v_z + AND acctstoptime IS NULL + AND acctstarttime < n.reloadtime; + + SET v_updated = v_updated + ROW_COUNT(); + + SET v_a = v_z + 1; + + -- + -- Periodically report how far we've got + -- + IF v_last_report != NOW() OR v_last THEN + SELECT v_z AS latest_radacctid, v_updated AS sessions_closed; + SET v_last_report = NOW(); + END IF; + + IF v_last THEN + LEAVE update_loop; + END IF; + + END LOOP; + +END$$ + +DELIMITER ; diff --git a/raddb/mods-config/sql/main/mysql/queries.conf b/raddb/mods-config/sql/main/mysql/queries.conf new file mode 100644 index 0000000..e7c9782 --- /dev/null +++ b/raddb/mods-config/sql/main/mysql/queries.conf @@ -0,0 +1,694 @@ +# -*- text -*- +# +# main/mysql/queries.conf-- MySQL configuration for default schema (schema.sql) +# +# $Id$ + +# Use the driver specific SQL escape method. +# +# If you enable this configuration item, the "safe_characters" +# configuration is ignored. FreeRADIUS then uses the MySQL escape +# functions to escape input strings. The only downside to making this +# change is that the MySQL escaping method is not the same the one +# used by FreeRADIUS. So characters which are NOT in the +# "safe_characters" list will now be stored differently in the database. +# +#auto_escape = yes + +# Safe characters list for sql queries. Everything else is replaced +# with their mime-encoded equivalents. +# The default list should be ok +# Using 'auto_escape' is preferred +safe_characters = "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" + +####################################################################### +# Connection config +####################################################################### +# The character set is not configurable. The default character set of +# the mysql client library is used. To control the character set, +# create/edit my.cnf (typically in /etc/mysql/my.cnf or /etc/my.cnf) +# and enter +# [client] +# default-character-set = utf8 +# + +####################################################################### +# Query config: Username +####################################################################### +# This is the username that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used below +# everywhere a username substitution is needed so you you can be sure +# the username passed from the client is escaped properly. +# +# Uncomment the next line, if you want the sql_user_name to mean: +# +# Use Stripped-User-Name, if it's there. +# Else use User-Name, if it's there, +# Else use hard-coded string "DEFAULT" as the user name. +#sql_user_name = "%{%{Stripped-User-Name}:-%{%{User-Name}:-DEFAULT}}" +# +sql_user_name = "%{User-Name}" + +####################################################################### +# Query config: Event-Timestamp +####################################################################### +# event_timestamp_epoch is the basis for the time inserted into +# accounting records. Typically this will be the Event-Timestamp of the +# accounting request, which is usually provided by a NAS. +# +# Uncomment the next line, if you want the timestamp to be based on the +# request reception time recorded by this server, for example if you +# distrust the provided Event-Timestamp. +#event_timestamp_epoch = "%l" + +event_timestamp_epoch = "%{%{integer:Event-Timestamp}:-%l}" + +# event_timestamp is the SQL snippet for converting an epoch timestamp +# to an SQL date. + +event_timestamp = "FROM_UNIXTIME(${event_timestamp_epoch})" + +####################################################################### +# Query config: Class attribute +####################################################################### +# +# 3.0.22 and later have a "class" column in the accounting table. +# +# However, we do NOT want to break existing configurations by adding +# the Class attribute to the default queries. If we did that, then +# systems using newer versions of the server would fail, because +# there is no "class" column in their accounting tables. +# +# The solution to that is the following "class" subsection. If your +# database has a "class" column for the various tables, then you can +# uncomment the configuration items here. The queries below will +# then automatically insert the Class attribute into radacct, +# radpostauth, etc. +# +class { + # + # Delete the '#' character from each of the configuration + # items in this section. This change puts the Class + # attribute into the various tables. Leave the double-quoted + # string there, as the value for the configuration item. + # + # See also policy.d/accounting, and the "insert_acct_class" + # policy. You will need to list (or uncomment) + # "insert_acct_class" in the "post-auth" section in order to + # create a Class attribute. + # + column_name = # ", class" + packet_xlat = # ", '%{Class}'" + reply_xlat = # ", '%{reply:Class}'" +} + +####################################################################### +# Default profile +####################################################################### +# This is the default profile. It is found in SQL by group membership. +# That means that this profile must be a member of at least one group +# which will contain the corresponding check and reply items. +# This profile will be queried in the authorize section for every user. +# The point is to assign all users a default profile without having to +# manually add each one to a group that will contain the profile. +# The SQL module will also honor the User-Profile attribute. This +# attribute can be set anywhere in the authorize section (ie the users +# file). It is found exactly as the default profile is found. +# If it is set then it will *overwrite* the default profile setting. +# The idea is to select profiles based on checks on the incoming packets, +# not on user group membership. For example: +# -- users file -- +# DEFAULT Service-Type == Outbound-User, User-Profile := "outbound" +# DEFAULT Service-Type == Framed-User, User-Profile := "framed" +# +# By default the default_user_profile is not set +# +#default_user_profile = "DEFAULT" + +####################################################################### +# NAS Query +####################################################################### +# This query retrieves the radius clients +# +# 0. Row ID (currently unused) +# 1. Name (or IP address) +# 2. Shortname +# 3. Type +# 4. Secret +# 5. Server +####################################################################### + +client_query = "\ + SELECT id, nasname, shortname, type, secret, server \ + FROM ${client_table}" + +####################################################################### +# Authorization Queries +####################################################################### +# These queries compare the check items for the user +# in ${authcheck_table} and setup the reply items in +# ${authreply_table}. You can use any query/tables +# you want, but the return data for each row MUST +# be in the following order: +# +# 0. Row ID (currently unused) +# 1. UserName/GroupName +# 2. Item Attr Name +# 3. Item Attr Value +# 4. Item Attr Operation +####################################################################### +# Use these for case sensitive usernames. + +#authorize_check_query = "\ +# SELECT id, username, attribute, value, op \ +# FROM ${authcheck_table} \ +# WHERE username = BINARY '%{SQL-User-Name}' \ +# ORDER BY id" + +#authorize_reply_query = "\ +# SELECT id, username, attribute, value, op \ +# FROM ${authreply_table} \ +# WHERE username = BINARY '%{SQL-User-Name}' \ +# ORDER BY id" + +# +# The default queries are case insensitive. (for compatibility with +# older versions of FreeRADIUS) +# +authorize_check_query = "\ + SELECT id, username, attribute, value, op \ + FROM ${authcheck_table} \ + WHERE username = '%{SQL-User-Name}' \ + ORDER BY id" + +authorize_reply_query = "\ + SELECT id, username, attribute, value, op \ + FROM ${authreply_table} \ + WHERE username = '%{SQL-User-Name}' \ + ORDER BY id" + +# +# Use these for case sensitive usernames. +# +#group_membership_query = "\ +# SELECT groupname \ +# FROM ${usergroup_table} \ +# WHERE username = BINARY '%{SQL-User-Name}' \ +# ORDER BY priority" + +group_membership_query = "\ + SELECT groupname \ + FROM ${usergroup_table} \ + WHERE username = '%{SQL-User-Name}' \ + ORDER BY priority" + +authorize_group_check_query = "\ + SELECT id, groupname, attribute, \ + Value, op \ + FROM ${groupcheck_table} \ + WHERE groupname = '%{${group_attribute}}' \ + ORDER BY id" + +authorize_group_reply_query = "\ + SELECT id, groupname, attribute, \ + value, op \ + FROM ${groupreply_table} \ + WHERE groupname = '%{${group_attribute}}' \ + ORDER BY id" + +####################################################################### +# Simultaneous Use Checking Queries +####################################################################### +# simul_count_query - query for the number of current connections +# - If this is not defined, no simultaneous use checking +# - will be performed by this module instance +# simul_verify_query - query to return details of current connections +# for verification +# - Leave blank or commented out to disable verification step +# - Note that the returned field order should not be changed. +# +# Note: Sessions that started prior to the most recent reload of their NAS will +# be correctly considered inactive, even if the radacct entry itself is not +# marked as stopped. +# +####################################################################### + +simul_count_query = "\ + SELECT COUNT(*) \ + FROM ${acct_table1} a \ + LEFT OUTER JOIN nasreload n USING (nasipaddress) \ + WHERE username = '%{SQL-User-Name}' \ + AND acctstoptime IS NULL \ + AND (a.acctstarttime > n.reloadtime OR n.reloadtime IS NULL)" + +simul_verify_query = "\ + SELECT \ + radacctid, acctsessionid, username, nasipaddress, nasportid, framedipaddress, \ + callingstationid, framedprotocol \ + FROM ${acct_table1} a \ + LEFT OUTER JOIN nasreload n USING (nasipaddress) \ + WHERE username = '%{SQL-User-Name}' \ + AND acctstoptime IS NULL \ + AND (a.acctstarttime > n.reloadtime OR n.reloadtime IS NULL)" + +####################################################################### +# Accounting and Post-Auth Queries +####################################################################### +# These queries insert/update accounting and authentication records. +# The query to use is determined by the value of 'reference'. +# This value is used as a configuration path and should resolve to one +# or more 'query's. If reference points to multiple queries, and a query +# fails, the next query is executed. +# +# Behaviour is identical to the old 1.x/2.x module, except we can now +# fail between N queries, and query selection can be based on any +# combination of attributes, or custom 'Acct-Status-Type' values. +####################################################################### +accounting { + reference = "%{tolower:type.%{%{Acct-Status-Type}:-%{Request-Processing-Stage}}.query}" + + # Write SQL queries to a logfile. This is potentially useful for bulk inserts + # when used with the rlm_sql_null driver. +# logfile = ${logdir}/accounting.sql + + column_list = "\ + acctsessionid, acctuniqueid, username, \ + realm, nasipaddress, nasportid, \ + nasporttype, acctstarttime, acctupdatetime, \ + acctstoptime, acctsessiontime, acctauthentic, \ + connectinfo_start, connectinfo_stop, acctinputoctets, \ + acctoutputoctets, calledstationid, callingstationid, \ + acctterminatecause, servicetype, framedprotocol, \ + framedipaddress, framedipv6address, framedipv6prefix, \ + framedinterfaceid, delegatedipv6prefix ${..class.column_name}" + + type { + accounting-on { + + # + # "Bulk update" Accounting-On/Off strategy. + # + # Immediately terminate all sessions associated with a + # given NAS. + # + # Note: If a large number of sessions require closing + # then the bulk update may be take a long time to run + # and lock an excessive number of rows. See the + # strategy below for an alternative approach that does + # not touch the radacct session data. + # + query = "\ + UPDATE ${....acct_table1} \ + SET \ + acctstoptime = ${....event_timestamp}, \ + acctsessiontime = '${....event_timestamp_epoch}' \ + - UNIX_TIMESTAMP(acctstarttime), \ + acctterminatecause = '%{%{Acct-Terminate-Cause}:-NAS-Reboot}' \ + WHERE acctstoptime IS NULL \ + AND nasipaddress = '%{NAS-IP-Address}' \ + AND acctstarttime <= ${....event_timestamp}" + + # + # "Lightweight" Accounting-On/Off strategy. + # + # Record the reload time of the NAS and let the + # administrator actually close the sessions in radacct + # out-of-band, if desired. + # + # Implementation advice, together with a stored + # procedure for closing sessions and a view showing + # the effective stop time of each session is provided + # in process-radacct.sql. + # + # To enable this strategy, just change the previous + # query to "-query", and this one to "query". The + # previous one will be ignored, and this one will be + # enabled. + # + -query = "\ + INSERT INTO nasreload \ + SET \ + nasipaddress = '%{NAS-IP-Address}', \ + reloadtime = ${....event_timestamp} \ + ON DUPLICATE KEY UPDATE reloadtime = ${....event_timestamp}" + + } + + accounting-off { + query = "${..accounting-on.query}" + } + + # + # Implement the "sql_session_start" policy. + # See raddb/policy.d/accounting for more details. + # + # You also need to fix the other queries as + # documented below. Look for "sql_session_start". + # + post-auth { + query = "\ + INSERT INTO ${....acct_table1} \ + (${...column_list}) \ + VALUES(\ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}', \ + NULLIF('%{%{NAS-Port-ID}:-%{NAS-Port}}', ''), \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + NULL, \ + 0, \ + '', \ + '%{Connect-Info}', \ + NULL, \ + 0, \ + 0, \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '', \ + '%{Service-Type}', \ + NULL, \ + '', \ + '', \ + '', \ + '', \ + '' \ + ${....class.packet_xlat})" + + query = "\ + UPDATE ${....acct_table1} SET \ + AcctStartTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp}, \ + ConnectInfo_start = '%{Connect-Info}', \ + AcctSessionId = '%{Acct-Session-Id}' \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime IS NULL" + } + + start { + # + # Insert a new record into the sessions table + # + query = "\ + INSERT INTO ${....acct_table1} \ + (${...column_list}) \ + VALUES \ + ('%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + NULL, \ + '0', \ + '%{Acct-Authentic}', \ + '%{Connect-Info}', \ + '', \ + '0', \ + '0', \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + framedipaddress = '%{Framed-IP-Address}', \ + framedipv6address = '%{Framed-IPv6-Address}', \ + framedipv6prefix = '%{Framed-IPv6-Prefix}', \ + framedinterfaceid = '%{Framed-Interface-Id}', \ + delegatedipv6prefix = '%{Delegated-IPv6-Prefix}', \ + AcctStartTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp} \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime IS NULL" + + # + # Key constraints prevented us from inserting a new session, + # use the alternate query to update an existing session. + # + query = "\ + UPDATE ${....acct_table1} SET \ + acctstarttime = ${....event_timestamp}, \ + acctupdatetime = ${....event_timestamp}, \ + connectinfo_start = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}'" + + } + + interim-update { + # + # Update an existing session and calculate the interval + # between the last data we received for the session and this + # update. This can be used to find stale sessions. + # + query = "\ + UPDATE ${....acct_table1} \ + SET \ + acctupdatetime = (@acctupdatetime_old:=acctupdatetime), \ + acctupdatetime = ${....event_timestamp}, \ + acctinterval = ${....event_timestamp_epoch} - \ + UNIX_TIMESTAMP(@acctupdatetime_old), \ + framedipaddress = '%{Framed-IP-Address}', \ + framedipv6address = '%{Framed-IPv6-Address}', \ + framedipv6prefix = '%{Framed-IPv6-Prefix}', \ + framedinterfaceid = '%{Framed-Interface-Id}', \ + delegatedipv6prefix = '%{Delegated-IPv6-Prefix}', \ + acctsessiontime = %{%{Acct-Session-Time}:-NULL}, \ + acctinputoctets = '%{%{Acct-Input-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Input-Octets}:-0}', \ + acctoutputoctets = '%{%{Acct-Output-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Output-Octets}:-0}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}'" + + # + # The update condition matched no existing sessions. Use + # the values provided in the update to create a new session. + # + query = "\ + INSERT INTO ${....acct_table1} \ + (${...column_list}) \ + VALUES \ + ('%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + FROM_UNIXTIME(${....event_timestamp_epoch} - %{%{Acct-Session-Time}:-0}), \ + ${....event_timestamp}, \ + NULL, \ + %{%{Acct-Session-Time}:-NULL}, \ + '%{Acct-Authentic}', \ + '%{Connect-Info}', \ + '', \ + '%{%{Acct-Input-Gigawords}:-0}' << 32 | '%{%{Acct-Input-Octets}:-0}', \ + '%{%{Acct-Output-Gigawords}:-0}' << 32 | '%{%{Acct-Output-Octets}:-0}', \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + framedipaddress = '%{Framed-IP-Address}', \ + framedipv6address = '%{Framed-IPv6-Address}', \ + framedipv6prefix = '%{Framed-IPv6-Prefix}', \ + framedinterfaceid = '%{Framed-Interface-Id}', \ + delegatedipv6prefix = '%{Delegated-IPv6-Prefix}', \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctSessionTime = %{%{Acct-Session-Time}:-NULL}, \ + AcctInputOctets = '%{%{Acct-Input-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Input-Octets}:-0}', \ + AcctOutputOctets = '%{%{Acct-Output-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Output-Octets}:-0}' \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime IS NULL" + + } + + stop { + # + # Session has terminated, update the stop time and statistics. + # + query = "\ + UPDATE ${....acct_table2} SET \ + acctstoptime = ${....event_timestamp}, \ + acctsessiontime = %{%{Acct-Session-Time}:-NULL}, \ + acctinputoctets = '%{%{Acct-Input-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Input-Octets}:-0}', \ + acctoutputoctets = '%{%{Acct-Output-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Output-Octets}:-0}', \ + acctterminatecause = '%{Acct-Terminate-Cause}', \ + connectinfo_stop = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}'" + + # + # The update condition matched no existing sessions. Use + # the values provided in the update to create a new session. + # + query = "\ + INSERT INTO ${....acct_table2} \ + (${...column_list}) \ + VALUES \ + ('%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + FROM_UNIXTIME(${....event_timestamp_epoch} - %{%{Acct-Session-Time}:-0}), \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + %{%{Acct-Session-Time}:-NULL}, \ + '%{Acct-Authentic}', \ + '', \ + '%{Connect-Info}', \ + '%{%{Acct-Input-Gigawords}:-0}' << 32 | '%{%{Acct-Input-Octets}:-0}', \ + '%{%{Acct-Output-Gigawords}:-0}' << 32 | '%{%{Acct-Output-Octets}:-0}', \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '%{Acct-Terminate-Cause}', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + framedipaddress = '%{Framed-IP-Address}', \ + framedipv6address = '%{Framed-IPv6-Address}', \ + framedipv6prefix = '%{Framed-IPv6-Prefix}', \ + framedinterfaceid = '%{Framed-Interface-Id}', \ + delegatedipv6prefix = '%{Delegated-IPv6-Prefix}', \ + AcctStopTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctSessionTime = %{Acct-Session-Time}, \ + AcctInputOctets = '%{%{Acct-Input-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Input-Octets}:-0}', \ + AcctOutputOctets = '%{%{Acct-Output-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Output-Octets}:-0}', \ + AcctTerminateCause = '%{Acct-Terminate-Cause}', \ + ConnectInfo_stop = '%{Connect-Info}' \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime IS NULL" + + } + + # + # No Acct-Status-Type == ignore the packet + # + accounting { + query = "SELECT true" + } + } +} + + +####################################################################### +# Authentication Logging Queries +####################################################################### +# postauth_query - Insert some info after authentication +####################################################################### + +post-auth { + # Write SQL queries to a logfile. This is potentially useful for bulk inserts + # when used with the rlm_sql_null driver. +# logfile = ${logdir}/post-auth.sql + + query = "\ + INSERT INTO ${..postauth_table} \ + (username, pass, reply, authdate ${..class.column_name}) \ + VALUES ( \ + '%{SQL-User-Name}', \ + '%{%{User-Password}:-%{Chap-Password}}', \ + '%{reply:Packet-Type}', \ + '%S.%M' \ + ${..class.reply_xlat})" +} diff --git a/raddb/mods-config/sql/main/mysql/schema.sql b/raddb/mods-config/sql/main/mysql/schema.sql new file mode 100644 index 0000000..84846b2 --- /dev/null +++ b/raddb/mods-config/sql/main/mysql/schema.sql @@ -0,0 +1,179 @@ +########################################################################### +# $Id$ # +# # +# schema.sql rlm_sql - FreeRADIUS SQL Module # +# # +# Database schema for MySQL rlm_sql module # +# # +# To load: # +# mysql -uroot -prootpass radius < schema.sql # +# # +# Mike Machado <mike@innercite.com> # +########################################################################### +# +# Table structure for table 'radacct' +# + +CREATE TABLE IF NOT EXISTS radacct ( + radacctid bigint(21) NOT NULL auto_increment, + acctsessionid varchar(64) NOT NULL default '', + acctuniqueid varchar(32) NOT NULL default '', + username varchar(64) NOT NULL default '', + realm varchar(64) default '', + nasipaddress varchar(15) NOT NULL default '', + nasportid varchar(32) default NULL, + nasporttype varchar(32) default NULL, + acctstarttime datetime NULL default NULL, + acctupdatetime datetime NULL default NULL, + acctstoptime datetime NULL default NULL, + acctinterval int(12) default NULL, + acctsessiontime int(12) unsigned default NULL, + acctauthentic varchar(32) default NULL, + connectinfo_start varchar(128) default NULL, + connectinfo_stop varchar(128) default NULL, + acctinputoctets bigint(20) default NULL, + acctoutputoctets bigint(20) default NULL, + calledstationid varchar(50) NOT NULL default '', + callingstationid varchar(50) NOT NULL default '', + acctterminatecause varchar(32) NOT NULL default '', + servicetype varchar(32) default NULL, + framedprotocol varchar(32) default NULL, + framedipaddress varchar(15) NOT NULL default '', + framedipv6address varchar(45) NOT NULL default '', + framedipv6prefix varchar(45) NOT NULL default '', + framedinterfaceid varchar(44) NOT NULL default '', + delegatedipv6prefix varchar(45) NOT NULL default '', + class varchar(64) default NULL, + PRIMARY KEY (radacctid), + UNIQUE KEY acctuniqueid (acctuniqueid), + KEY username (username), + KEY framedipaddress (framedipaddress), + KEY framedipv6address (framedipv6address), + KEY framedipv6prefix (framedipv6prefix), + KEY framedinterfaceid (framedinterfaceid), + KEY delegatedipv6prefix (delegatedipv6prefix), + KEY acctsessionid (acctsessionid), + KEY acctsessiontime (acctsessiontime), + KEY acctstarttime (acctstarttime), + KEY acctinterval (acctinterval), + KEY acctstoptime (acctstoptime), + KEY nasipaddress (nasipaddress), + KEY class (class) +) ENGINE = INNODB; + +# +# Table structure for table 'radcheck' +# + +CREATE TABLE IF NOT EXISTS radcheck ( + id int(11) unsigned NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '==', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY username (username(32)) +); + +# +# Table structure for table 'radgroupcheck' +# + +CREATE TABLE IF NOT EXISTS radgroupcheck ( + id int(11) unsigned NOT NULL auto_increment, + groupname varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '==', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY groupname (groupname(32)) +); + +# +# Table structure for table 'radgroupreply' +# + +CREATE TABLE IF NOT EXISTS radgroupreply ( + id int(11) unsigned NOT NULL auto_increment, + groupname varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY groupname (groupname(32)) +); + +# +# Table structure for table 'radreply' +# + +CREATE TABLE IF NOT EXISTS radreply ( + id int(11) unsigned NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY username (username(32)) +); + + +# +# Table structure for table 'radusergroup' +# + +CREATE TABLE IF NOT EXISTS radusergroup ( + id int(11) unsigned NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + groupname varchar(64) NOT NULL default '', + priority int(11) NOT NULL default '1', + PRIMARY KEY (id), + KEY username (username(32)) +); + +# +# Table structure for table 'radpostauth' +# +# Note: MySQL versions since 5.6.4 support fractional precision timestamps +# which we use here. Replace the authdate definition with the following +# if your software is too old: +# +# authdate timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP +# +CREATE TABLE IF NOT EXISTS radpostauth ( + id int(11) NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + pass varchar(64) NOT NULL default '', + reply varchar(32) NOT NULL default '', + authdate timestamp(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + class varchar(64) default NULL, + PRIMARY KEY (id), + KEY username (username), + KEY class (class) +) ENGINE = INNODB; + +# +# Table structure for table 'nas' +# +CREATE TABLE IF NOT EXISTS nas ( + id int(10) NOT NULL auto_increment, + nasname varchar(128) NOT NULL, + shortname varchar(32), + type varchar(30) DEFAULT 'other', + ports int(5), + secret varchar(60) DEFAULT 'secret' NOT NULL, + server varchar(64), + community varchar(50), + description varchar(200) DEFAULT 'RADIUS Client', + PRIMARY KEY (id), + KEY nasname (nasname) +) ENGINE = INNODB; + +# +# Table structure for table 'nasreload' +# +CREATE TABLE IF NOT EXISTS nasreload ( + nasipaddress varchar(15) NOT NULL, + reloadtime datetime NOT NULL, + PRIMARY KEY (nasipaddress) +) ENGINE = INNODB; diff --git a/raddb/mods-config/sql/main/mysql/setup.sql b/raddb/mods-config/sql/main/mysql/setup.sql new file mode 100755 index 0000000..5ae98cc --- /dev/null +++ b/raddb/mods-config/sql/main/mysql/setup.sql @@ -0,0 +1,40 @@ +# -*- text -*- +## +## setup.sql -- MySQL commands for creating the RADIUS user. +## +## WARNING: You should change 'localhost' and 'radpass' +## to something else. Also update raddb/mods-available/sql +## with the new RADIUS password. +## +## $Id$ + +# +# Create default administrator for RADIUS +# +CREATE USER 'radius'@'localhost' IDENTIFIED BY 'radpass'; + +# +# The server can read the authorisation data +# +GRANT SELECT ON radius.radcheck TO 'radius'@'localhost'; +GRANT SELECT ON radius.radreply TO 'radius'@'localhost'; +GRANT SELECT ON radius.radusergroup TO 'radius'@'localhost'; +GRANT SELECT ON radius.radgroupcheck TO 'radius'@'localhost'; +GRANT SELECT ON radius.radgroupreply TO 'radius'@'localhost'; + +# +# The server can write accounting and post-auth data +# +GRANT SELECT, INSERT, UPDATE ON radius.radacct TO 'radius'@'localhost'; +GRANT SELECT, INSERT, UPDATE ON radius.radpostauth TO 'radius'@'localhost'; + +# +# The server can read the NAS data +# +GRANT SELECT ON radius.nas TO 'radius'@'localhost'; + +# +# In the case of the "lightweight accounting-on/off" strategy, the server also +# records NAS reload times +# +GRANT SELECT, INSERT, UPDATE ON radius.nasreload TO 'radius'@'localhost'; diff --git a/raddb/mods-config/sql/main/ndb/README b/raddb/mods-config/sql/main/ndb/README new file mode 100644 index 0000000..71f5aa3 --- /dev/null +++ b/raddb/mods-config/sql/main/ndb/README @@ -0,0 +1,5 @@ + The SQL schema and 'create admin user" scripts are here in order to +simplify the process of using MySQL cluster. + + The queries are NOT located here, because the database driver for +MySQL cluster is just "mysql", and not "ndb". diff --git a/raddb/mods-config/sql/main/ndb/schema.sql b/raddb/mods-config/sql/main/ndb/schema.sql new file mode 100644 index 0000000..d115d06 --- /dev/null +++ b/raddb/mods-config/sql/main/ndb/schema.sql @@ -0,0 +1,144 @@ +########################################################################### +# $Id$ # +# # +# schema.sql rlm_sql - FreeRADIUS SQL Module # +# # +# Database schema for MySQL Cluster. # +# The only difference between this file and ../mysql/schema.sql # +# is the definition of the storage engine. # +# # +# To load: # +# mysql -uroot -prootpass radius < schema.sql # +# # +# Mike Machado <mike@innercite.com> # +########################################################################### +# +# Table structure for table 'radacct' +# + +CREATE TABLE radacct ( + radacctid bigint(21) NOT NULL auto_increment, + acctsessionid varchar(64) NOT NULL default '', + acctuniqueid varchar(32) NOT NULL default '', + username varchar(64) NOT NULL default '', + realm varchar(64) default '', + nasipaddress varchar(15) NOT NULL default '', + nasportid varchar(32) default NULL, + nasporttype varchar(32) default NULL, + acctstarttime datetime NULL default NULL, + acctupdatetime datetime NULL default NULL, + acctstoptime datetime NULL default NULL, + acctinterval int(12) default NULL, + acctsessiontime int(12) unsigned default NULL, + acctauthentic varchar(32) default NULL, + connectinfo_start varchar(128) default NULL, + connectinfo_stop varchar(128) default NULL, + acctinputoctets bigint(20) default NULL, + acctoutputoctets bigint(20) default NULL, + calledstationid varchar(50) NOT NULL default '', + callingstationid varchar(50) NOT NULL default '', + acctterminatecause varchar(32) NOT NULL default '', + servicetype varchar(32) default NULL, + framedprotocol varchar(32) default NULL, + framedipaddress varchar(15) NOT NULL default '', + framedipv6address varchar(45) NOT NULL default '', + framedipv6prefix varchar(45) NOT NULL default '', + framedinterfaceid varchar(44) NOT NULL default '', + delegatedipv6prefix varchar(45) NOT NULL default '', + class varchar(64) default NULL, + PRIMARY KEY (radacctid), + UNIQUE KEY acctuniqueid (acctuniqueid), + KEY username (username), + KEY framedipaddress (framedipaddress), + KEY framedipv6address (framedipv6address), + KEY framedipv6prefix (framedipv6prefix), + KEY framedinterfaceid (framedinterfaceid), + KEY delegatedipv6prefix (delegatedipv6prefix), + KEY acctsessionid (acctsessionid), + KEY acctsessiontime (acctsessiontime), + KEY acctstarttime (acctstarttime), + KEY acctinterval (acctinterval), + KEY acctstoptime (acctstoptime), + KEY nasipaddress (nasipaddress) +) ENGINE=ndbcluster; + +# +# Table structure for table 'radcheck' +# + +CREATE TABLE radcheck ( + id int(11) unsigned NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '==', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY username (username(32)) +) ENGINE=ndbcluster; + +# +# Table structure for table 'radgroupcheck' +# + +CREATE TABLE radgroupcheck ( + id int(11) unsigned NOT NULL auto_increment, + groupname varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '==', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY groupname (groupname(32)) +) ENGINE=ndbcluster; + +# +# Table structure for table 'radgroupreply' +# + +CREATE TABLE radgroupreply ( + id int(11) unsigned NOT NULL auto_increment, + groupname varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY groupname (groupname(32)) +) ENGINE=ndbcluster; + +# +# Table structure for table 'radreply' +# + +CREATE TABLE radreply ( + id int(11) unsigned NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '', + PRIMARY KEY (id), + KEY username (username(32)) +) ENGINE=ndbcluster; + + +# +# Table structure for table 'radusergroup' +# + +CREATE TABLE radusergroup ( + username varchar(64) NOT NULL default '', + groupname varchar(64) NOT NULL default '', + priority int(11) NOT NULL default '1', + KEY username (username(32)) +) ENGINE=ndbcluster; + +# +# Table structure for table 'radpostauth' +# + +CREATE TABLE radpostauth ( + id int(11) NOT NULL auto_increment, + username varchar(64) NOT NULL default '', + pass varchar(64) NOT NULL default '', + reply varchar(32) NOT NULL default '', + authdate timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP(6) ON UPDATE CURRENT_TIMESTAMP(6), + PRIMARY KEY (id) +) ENGINE=ndbcluster; diff --git a/raddb/mods-config/sql/main/ndb/setup.sql b/raddb/mods-config/sql/main/ndb/setup.sql new file mode 100644 index 0000000..003fc10 --- /dev/null +++ b/raddb/mods-config/sql/main/ndb/setup.sql @@ -0,0 +1,25 @@ +# -*- text -*- +## +## admin.sql -- MySQL commands for creating the RADIUS user. +## +## WARNING: You should change 'localhost' and 'radpass' +## to something else. Also update raddb/mods-available/sql +## with the new RADIUS password. +## +## $Id$ + +# +# Create default administrator for RADIUS +# +CREATE USER 'radius'@'localhost'; +SET PASSWORD FOR 'radius'@'localhost' = PASSWORD('radpass'); + +# The server can read any table in SQL +GRANT ALL ON radius.* TO 'radius'@'localhost' identified by 'radpass'; +GRANT ALL ON radius.* TO 'radius'@'radsrvr' identified by 'radpass'; + +# The server can write to the accounting and post-auth logging table. +# +# i.e. +#GRANT ALL on radius.radacct TO 'radius'@'localhost' identified by 'radpass'; +#GRANT ALL on radius.radacct TO 'radius'@'radsrvr' identified by 'radpass'; diff --git a/raddb/mods-config/sql/main/oracle/process-radacct.sql b/raddb/mods-config/sql/main/oracle/process-radacct.sql new file mode 100644 index 0000000..858d946 --- /dev/null +++ b/raddb/mods-config/sql/main/oracle/process-radacct.sql @@ -0,0 +1,147 @@ +# -*- text -*- +# +# main/oracle/process-radacct.sql -- Schema extensions for processing radacct entries +# +# $Id$ + +-- --------------------------------- +-- - Per-user data usage over time - +-- --------------------------------- +-- +-- An extension to the standard schema to hold per-user data usage statistics +-- for arbitrary periods. +-- +-- The data_usage_by_period table is populated by periodically calling the +-- fr_new_data_usage_period stored procedure. +-- +-- This table can be queried in various ways to produce reports of aggregate +-- data use over time. For example, if the fr_new_data_usage_period SP is +-- invoked one per day just after midnight, to produce usage data with daily +-- granularity, then a reasonably accurate monthly bandwidth summary for a +-- given user could be obtained with: +-- +-- SELECT +-- MIN(TO_CHAR(period_start, 'YYYY-Month')) AS month, +-- SUM(acctinputoctets)/1000/1000/1000 AS GB_in, +-- SUM(acctoutputoctets)/1000/1000/1000 AS GB_out +-- FROM +-- data_usage_by_period +-- WHERE +-- username='bob' AND +-- period_end IS NOT NULL +-- GROUP BY +-- TRUNC(period_start,'month'); +-- +-- +----------------+----------------+-----------------+ +-- | MONTH | GB_IN | GB_OUT | +-- +----------------+----------------+-----------------+ +-- | 2019-July | 5.782279230000 | 50.545664820000 | +-- | 2019-August | 4.230543340000 | 48.523096420000 | +-- | 2019-September | 4.847360590000 | 48.631835480000 | +-- | 2019-October | 6.456763250000 | 51.686231930000 | +-- | 2019-November | 6.362537730000 | 52.385710570000 | +-- | 2019-December | 4.301524440000 | 50.762240270000 | +-- | 2020-January | 5.436280540000 | 49.067775280000 | +-- +----------------+----------------+-----------------+ +-- +CREATE TABLE data_usage_by_period ( + id NUMBER GENERATED BY DEFAULT AS IDENTITY, + username VARCHAR(64) NOT NULL, + period_start TIMESTAMP WITH TIME ZONE NOT NULL, + period_end TIMESTAMP WITH TIME ZONE, + acctinputoctets NUMERIC(19), + acctoutputoctets NUMERIC(19), + PRIMARY KEY (id) +); +CREATE UNIQUE INDEX idx_data_usage_by_period_username_period_start ON data_usage_by_period (username,period_start); +CREATE INDEX idx_data_usage_by_period_period_start ON data_usage_by_period (period_start); +CREATE INDEX idx_data_usage_by_period_period_end ON data_usage_by_period (period_end); + +-- +-- Stored procedure that when run with some arbitrary frequency, say +-- once per day by cron, will process the recent radacct entries to extract +-- time-windowed data containing acct{input,output}octets ("data usage") per +-- username, per period. +-- +-- Each invocation will create new rows in the data_usage_by_period tables +-- containing the data used by each user since the procedure was last invoked. +-- The intervals do not need to be identical but care should be taken to +-- ensure that the start/end of each period aligns well with any intended +-- reporting intervals. +-- +-- It can be invoked by running: +-- +-- CALL fr_new_data_usage_period(); +-- +-- +CREATE OR REPLACE PROCEDURE fr_new_data_usage_period +AS + v_start TIMESTAMP WITH TIME ZONE; + v_end TIMESTAMP WITH TIME ZONE; +BEGIN + + SELECT COALESCE(MAX(period_end) + NUMTODSINTERVAL(1,'SECOND'), TO_DATE('1970-01-01','YYYY-MM-DD')) INTO v_start FROM data_usage_by_period; + SELECT CAST(CURRENT_TIMESTAMP AS DATE) INTO v_end FROM dual; + + BEGIN + + -- + -- Add the data usage for the sessions that were active in the current + -- period to the table. Include all sessions that finished since the start + -- of this period as well as those still ongoing. + -- + MERGE INTO data_usage_by_period d + USING ( + SELECT + username, + MIN(v_start) period_start, + MIN(v_end) period_end, + SUM(acctinputoctets) AS acctinputoctets, + SUM(acctoutputoctets) AS acctoutputoctets + FROM + radacct + WHERE + acctstoptime > v_start OR + acctstoptime IS NULL + GROUP BY + username + ) s + ON ( d.username = s.username AND d.period_start = s.period_start ) + WHEN MATCHED THEN + UPDATE SET + acctinputoctets = d.acctinputoctets + s.acctinputoctets, + acctoutputoctets = d.acctoutputoctets + s.acctoutputoctets, + period_end = v_end + WHEN NOT MATCHED THEN + INSERT + (username, period_start, period_end, acctinputoctets, acctoutputoctets) + VALUES + (s.username, s.period_start, s.period_end, s.acctinputoctets, s.acctoutputoctets); + + -- + -- Create an open-ended "next period" for all ongoing sessions and carry a + -- negative value of their data usage to avoid double-accounting when we + -- process the next period. Their current data usage has already been + -- allocated to the current and possibly previous periods. + -- + INSERT INTO data_usage_by_period (username, period_start, period_end, acctinputoctets, acctoutputoctets) + SELECT * + FROM ( + SELECT + username, + v_end + NUMTODSINTERVAL(1,'SECOND'), + NULL, + 0 - SUM(acctinputoctets), + 0 - SUM(acctoutputoctets) + FROM + radacct + WHERE + acctstoptime IS NULL + GROUP BY + username + ) s; + + END; + +END; +/ diff --git a/raddb/mods-config/sql/main/oracle/queries.conf b/raddb/mods-config/sql/main/oracle/queries.conf new file mode 100644 index 0000000..58c3ba8 --- /dev/null +++ b/raddb/mods-config/sql/main/oracle/queries.conf @@ -0,0 +1,694 @@ +# -*- text -*- +# +# main/oracle/queries.conf -- Oracle configuration for default schema (schema.sql) +# +# $Id$ + +####################################################################### +# Query config: Username +####################################################################### +# This is the username that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used below +# everywhere a username substitution is needed so you you can be sure +# the username passed from the client is escaped properly. +# +# Uncomment the next line, if you want the sql_user_name to mean: +# +# Use Stripped-User-Name, if it's there. +# Else use User-Name, if it's there, +# Else use hard-coded string "DEFAULT" as the user name. +#sql_user_name = "%{%{Stripped-User-Name}:-%{%{User-Name}:-DEFAULT}}" +# +sql_user_name = "%{User-Name}" + +####################################################################### +# Query config: Event-Timestamp +####################################################################### +# event_timestamp_epoch is the basis for the time inserted into +# accounting records. Typically this will be the Event-Timestamp of the +# accounting request, which is provided by a NAS. +# +# Uncomment the next line, if you want the timestamp to be based on the +# request reception time recorded by this server, for example if you +# distrust the provided Event-Timestamp. +#event_timestamp_epoch = "%l" + +event_timestamp_epoch = "%{%{integer:Event-Timestamp}:-%l}" + +# event_timestamp is the SQL snippet for converting an epoch timestamp +# to an SQL date. + +event_timestamp = "TO_DATE('1970-01-01','YYYY-MM-DD') + NUMTODSINTERVAL(${event_timestamp_epoch},'SECOND')" + +####################################################################### +# Query config: Class attribute +####################################################################### +# +# 3.0.22 and later have a "class" column in the accounting table. +# +# However, we do NOT want to break existing configurations by adding +# the Class attribute to the default queries. If we did that, then +# systems using newer versions of the server would fail, because +# there is no "class" column in their accounting tables. +# +# The solution to that is the following "class" subsection. If your +# database has a "class" column for the various tables, then you can +# uncomment the configuration items here. The queries below will +# then automatically insert the Class attribute into radacct, +# radpostauth, etc. +# +class { + # + # Delete the '#' character from each of the configuration + # items in this section. This change puts the Class + # attribute into the various tables. Leave the double-quoted + # string there, as the value for the configuration item. + # + # See also policy.d/accounting, and the "insert_acct_class" + # policy. You will need to list (or uncomment) + # "insert_acct_class" in the "post-auth" section in order to + # create a Class attribute. + # + column_name = # ", class" + packet_xlat = # ", '%{Class}'" + reply_xlat = # ", '%{reply:Class}'" +} + +####################################################################### +# Default profile +####################################################################### +# This is the default profile. It is found in SQL by group membership. +# That means that this profile must be a member of at least one group +# which will contain the corresponding check and reply items. +# This profile will be queried in the authorize section for every user. +# The point is to assign all users a default profile without having to +# manually add each one to a group that will contain the profile. +# The SQL module will also honor the User-Profile attribute. This +# attribute can be set anywhere in the authorize section (ie the users +# file). It is found exactly as the default profile is found. +# If it is set then it will *overwrite* the default profile setting. +# The idea is to select profiles based on checks on the incoming packets, +# not on user group membership. For example: +# -- users file -- +# DEFAULT Service-Type == Outbound-User, User-Profile := "outbound" +# DEFAULT Service-Type == Framed-User, User-Profile := "framed" +# +# By default the default_user_profile is not set +# +#default_user_profile = "DEFAULT" +# +# Determines if we will query the default_user_profile or the User-Profile +# if the user is not found. If the profile is found then we consider the user +# found. By default this is set to 'no'. +# +#query_on_not_found = no + + +####################################################################### +# NAS Query +####################################################################### +# This query retrieves the radius clients +# +# 0. Row ID (currently unused) +# 1. Name (or IP address) +# 2. Shortname +# 3. Type +# 4. Secret +# 5. Virtual server +####################################################################### + +client_query = "\ + SELECT id, nasname, shortname, type, secret, server \ + FROM ${client_table}" + +####################################################################### +# Authorization Queries +####################################################################### +# These queries compare the check items for the user +# in ${authcheck_table} and setup the reply items in +# ${authreply_table}. You can use any query/tables +# you want, but the return data for each row MUST +# be in the following order: +# +# 0. Row ID (currently unused) +# 1. UserName/GroupName +# 2. Item Attr Name +# 3. Item Attr Value +# 4. Item Attr Operation +####################################################################### +# +# WARNING: Oracle is case sensitive +# +# The main difference between MySQL and Oracle queries is the date format. +# You must use the TO_DATE function to transform the radius date format to +# the Oracle date format, and put NULL otherwise '0' in a void date field. +# +####################################################################### + +authorize_check_query = "\ + SELECT id, UserName, Attribute, Value, op \ + FROM ${authcheck_table} \ + WHERE Username = '%{SQL-User-Name}' \ + ORDER BY id" + +authorize_reply_query = "\ + SELECT id, UserName, Attribute, Value, op \ + FROM ${authreply_table} \ + WHERE Username = '%{SQL-User-Name}' \ + ORDER BY id" + +authorize_group_check_query = "\ + SELECT \ + ${groupcheck_table}.id, ${groupcheck_table}.GroupName, ${groupcheck_table}.Attribute, \ + ${groupcheck_table}.Value,${groupcheck_table}.op \ + FROM ${groupcheck_table}, ${usergroup_table} \ + WHERE ${usergroup_table}.Username = '%{SQL-User-Name}' \ + AND ${usergroup_table}.GroupName = ${groupcheck_table}.GroupName \ + ORDER BY ${groupcheck_table}.id" + +authorize_group_reply_query = "\ + SELECT \ + ${groupreply_table}.id, ${groupreply_table}.GroupName, ${groupreply_table}.Attribute, \ + ${groupreply_table}.Value, ${groupreply_table}.op \ + FROM ${groupreply_table}, ${usergroup_table} \ + WHERE ${usergroup_table}.Username = '%{SQL-User-Name}' \ + AND ${usergroup_table}.GroupName = ${groupreply_table}.GroupName \ + ORDER BY ${groupreply_table}.id" + +####################################################################### +# Simultaneous Use Checking Queries +####################################################################### +# simul_count_query - query for the number of current connections +# - If this is not defined, no simultaneous use checking +# - will be performed by this module instance +# simul_verify_query - query to return details of current connections for verification +# - Leave blank or commented out to disable verification step +# - Note that the returned field order should not be changed. +####################################################################### + +simul_count_query = "\ + SELECT COUNT(*) \ + FROM ${acct_table1} \ + WHERE UserName = '%{SQL-User-Name}' \ + AND AcctStopTime IS NULL" + +simul_verify_query = "\ + SELECT \ + RadAcctId, AcctSessionId, UserName, NASIPAddress, NASPortId, \ + FramedIPAddress, CallingStationId, FramedProtocol \ + FROM ${acct_table1} \ + WHERE UserName='%{SQL-User-Name}' \ + AND AcctStopTime IS NULL" + +####################################################################### +# Group Membership Queries +####################################################################### +# group_membership_query - Check user group membership +####################################################################### + +group_membership_query = "\ + SELECT GroupName \ + FROM ${usergroup_table} \ + WHERE UserName='%{SQL-User-Name}'" + +####################################################################### +# Accounting and Post-Auth Queries +####################################################################### +# These queries insert/update accounting and authentication records. +# The query to use is determined by the value of 'reference'. +# This value is used as a configuration path and should resolve to one +# or more 'query's. If reference points to multiple queries, and a query +# fails, the next query is executed. +# +# Behaviour is identical to the old 1.x/2.x module, except we can now +# fail between N queries, and query selection can be based on any +# combination of attributes, or custom 'Acct-Status-Type' values. +####################################################################### +accounting { + reference = "%{tolower:type.%{%{Acct-Status-Type}:-%{Request-Processing-Stage}}.query}" + + # Write SQL queries to a logfile. This is potentially useful for bulk inserts + # when used with the rlm_sql_null driver. +# logfile = ${logdir}/accounting.sql + + type { + accounting-on { + query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctStopTime = ${....event_timestamp}, \ + AcctSessionTime = ROUND((${....event_timestamp} - \ + TO_DATE(TO_CHAR(acctstarttime, 'yyyy-mm-dd hh24:mi:ss'),'yyyy-mm-dd hh24:mi:ss'))*86400), \ + AcctTerminateCause='%{%{Acct-Terminate-Cause}:-NAS-Reboot}', \ + AcctStopDelay = %{%{Acct-Delay-Time}:-0} \ + WHERE AcctStopTime IS NULL \ + AND NASIPAddress = '%{NAS-IP-Address}' \ + AND AcctStartTime <= ${....event_timestamp}" + } + + accounting-off { + query = "${..accounting-on.query}" + } + + # + # Implement the "sql_session_start" policy. + # See raddb/policy.d/accounting for more details. + # + # You also need to fix the other queries as + # documented below. Look for "sql_session_start". + # + post-auth { + query = "\ + INSERT INTO ${....acct_table1} (\ + AcctSessionId, \ + AcctUniqueId, \ + UserName, \ + Realm, \ + NASIPAddress, \ + NASPortId, \ + NASPortType, \ + AcctStartTime, \ + AcctUpdateTime, \ + AcctStopTime, \ + AcctSessionTime, \ + AcctAuthentic, \ + ConnectInfo_start, \ + ConnectInfo_stop, \ + AcctInputOctets, \ + AcctOutputOctets, \ + CalledStationId, \ + CallingStationId, \ + AcctTerminateCause, \ + ServiceType, \ + FramedProtocol, \ + FramedIPAddress, \ + FramedIPv6Address, \ + FramedIPv6Prefix, \ + FramedInterfaceId, \ + DelegatedIPv6Prefix) \ + VALUES(\ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + TO_DATE('%S','yyyy-mm-dd hh24:mi:ss'), \ + TO_DATE('%S','yyyy-mm-dd hh24:mi:ss'), \ + NULL, \ + 0, \ + '', \ + '%{Connect-Info}', \ + NULL, \ + 0, \ + 0, \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + NULL, \ + '%{Service-Type}', \ + NULL, \ + '', \ + '', \ + '', \ + '', \ + '')" + + query = "\ + UPDATE ${....acct_table1} SET \ + AcctStartTime = TO_DATE('%S','yyyy-mm-dd hh24:mi:ss'), \ + AcctUpdateTime = TO_DATE('%S','yyyy-mm-dd hh24:mi:ss'), \ + ConnectInfo_start = '%{Connect-Info}', \ + AcctSessionId = '%{Acct-Session-Id}' \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime IS NULL" + } + + start { + query = "\ + INSERT INTO ${....acct_table1} (\ + RadAcctId, \ + AcctSessionId, \ + AcctUniqueId, \ + UserName, \ + Realm, \ + NASIPAddress, \ + NASPortId, \ + NASPortType, \ + AcctStartTime, \ + AcctUpdateTime, \ + AcctStopTime, \ + AcctSessionTime, \ + AcctAuthentic, \ + ConnectInfo_start, \ + ConnectInfo_stop, \ + AcctInputOctets, \ + AcctOutputOctets, \ + CalledStationId, \ + CallingStationId, \ + AcctTerminateCause, \ + ServiceType, \ + FramedProtocol, \ + FramedIPAddress, \ + FramedIPv6Address, \ + FramedIPv6Prefix, \ + FramedInterfaceId, \ + DelegatedIPv6Prefix, \ + AcctStartDelay, \ + AcctStopDelay, \ + XAscendSessionSvrKey \ + ${....class.column_name}) \ + VALUES(\ + '', \ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + NULL, \ + '0', \ + '%{Acct-Authentic}', \ + '%{Connect-Info}', \ + '', \ + '0', \ + '0', \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}', \ + '%{Acct-Delay-Time}', \ + '0', \ + '%{X-Ascend-Session-Svr-Key}' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + FramedIPAddress = NULLIF('%{Framed-IP-Address}', ''), \ + FramedIPv6Address = NULLIF('%{Framed-IPv6-Address}', ''), \ + FramedIPv6Prefix = NULLIF('%{Framed-IPv6-Prefix}', ''), \ + FramedInterfaceId = NULLIF('%{Framed-Interface-Id}', ''), \ + DelegatedIPv6Prefix = NULLIF('%{Delegated-IPv6-Prefix}', ''), \ + AcctStartTime = TO_DATE('%S','yyyy-mm-dd hh24:mi:ss'), \ + AcctUpdateTime = TO_DATE('%S','yyyy-mm-dd hh24:mi:ss'), \ + AcctSessionTime = '0' \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime IS NULL" + + query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctStartTime = ${....event_timestamp}, \ + AcctStartDelay = '%{%{Acct-Delay-Time}:-0}', \ + ConnectInfo_start = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-ID}' \ + AND AcctStopTime IS NULL" + } + + interim-update { + query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctUpdateTime = ${....event_timestamp}, \ + FramedIPAddress = NULLIF('%{Framed-IP-Address}', ''), \ + FramedIPv6Address = NULLIF('%{Framed-IPv6-Address}', ''), \ + FramedIPv6Prefix = NULLIF('%{Framed-IPv6-Prefix}', ''), \ + FramedInterfaceId = NULLIF('%{Framed-Interface-Id}', ''), \ + DelegatedIPv6Prefix = NULLIF('%{Delegated-IPv6-Prefix}', ''), \ + AcctSessionTime = '%{Acct-Session-Time}', \ + AcctInputOctets = '%{Acct-Input-Octets}' + \ + ('%{%{Acct-Input-Gigawords}:-0}' * 4294967296), \ + AcctOutputOctets = '%{Acct-Output-Octets}' + \ + ('%{%{Acct-Output-Gigawords}:-0}' * 4294967296) \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-ID}' \ + AND AcctStopTime IS NULL" + + query = "\ + INSERT into ${....acct_table1} (\ + RadAcctId, \ + AcctSessionId, \ + AcctUniqueId, \ + UserName, \ + Realm, \ + NASIPAddress, \ + NASPortId, \ + NASPortType, \ + AcctStartTime, \ + AcctUpdateTime, \ + AcctSessionTime, \ + AcctAuthentic, \ + ConnectInfo_start, \ + AcctInputOctets, \ + AcctOutputOctets, \ + CalledStationId, \ + CallingStationId, \ + ServiceType, \ + FramedProtocol, \ + FramedIPAddress, \ + FramedIPv6Address, \ + FramedIPv6Prefix, \ + FramedInterfaceId, \ + DelegatedIPv6Prefix, \ + AcctStartDelay, \ + XAscendSessionSvrKey \ + ${....class.column_name}) \ + VALUES(\ + '', \ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + '%{Acct-Session-Time}', \ + '%{Acct-Authentic}', \ + '', \ + '%{Acct-Input-Octets}' + \ + ('%{%{Acct-Input-Gigawords}:-0}' * 4294967296), \ + '%{Acct-Output-Octets}' + \ + ('%{%{Acct-Output-Gigawords}:-0}' * 4294967296), \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}', \ + '0', \ + '%{X-Ascend-Session-Svr-Key}' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + FramedIPAddress = NULLIF('%{Framed-IP-Address}', ''), \ + FramedIPv6Address = NULLIF('%{Framed-IPv6-Address}', ''), \ + FramedIPv6Prefix = NULLIF('%{Framed-IPv6-Prefix}', ''), \ + FramedInterfaceId = NULLIF('%{Framed-Interface-Id}', ''), \ + DelegatedIPv6Prefix = NULLIF('%{Delegated-IPv6-Prefix}', ''), \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctSessionTime = '%{Acct-Session-Time}', \ + AcctInputOctets = '%{Acct-Input-Octets}' + \ + ('%{%{Acct-Input-Gigawords}:-0}' * 4294967296), \ + AcctOutputOctets = '%{Acct-Output-Octets}' + \ + ('%{%{Acct-Output-Gigawords}:-0}' * 4294967296) \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime IS NULL" + + } + + stop { + query = "\ + UPDATE ${....acct_table2} \ + SET \ + AcctStopTime = ${....event_timestamp}, \ + AcctSessionTime = '%{Acct-Session-Time}', \ + AcctInputOctets = '%{Acct-Input-Octets}' + \ + ('%{%{Acct-Input-Gigawords}:-0}' * 4294967296), \ + AcctOutputOctets = '%{Acct-Output-Octets}' + \ + ('%{%{Acct-Output-Gigawords}:-0}' * 4294967296), \ + AcctTerminateCause = '%{Acct-Terminate-Cause}', \ + AcctStopDelay = '%{%{Acct-Delay-Time}:-0}', \ + ConnectInfo_stop = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-ID}' \ + AND AcctStopTime IS NULL" + + query = "\ + INSERT into ${....acct_table2} (\ + RadAcctId, \ + AcctSessionId, \ + AcctUniqueId, \ + UserName, \ + Realm, \ + NASIPAddress, \ + NASPortId, \ + NASPortType, \ + AcctStartTime, \ + AcctStopTime, \ + AcctSessionTime, \ + AcctAuthentic, \ + ConnectInfo_start, \ + ConnectInfo_stop, \ + AcctInputOctets, \ + AcctOutputOctets, \ + CalledStationId, \ + CallingStationId, \ + AcctTerminateCause, \ + ServiceType, \ + FramedProtocol, \ + FramedIPAddress, \ + FramedIPv6Address, \ + FramedIPv6Prefix, \ + FramedInterfaceId, \ + DelegatedIPv6Prefix, \ + AcctStartDelay, \ + AcctStopDelay \ + ${....class.column_name}) \ + VALUES(\ + '', \ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + NULL, \ + ${....event_timestamp}, \ + '%{Acct-Session-Time}', \ + '%{Acct-Authentic}', \ + NULL, \ + '%{Connect-Info}', \ + '%{Acct-Input-Octets}' + \ + ('%{%{Acct-Input-Gigawords}:-0}' * 4294967296), \ + '%{Acct-Output-Octets}' + \ + ('%{%{Acct-Output-Gigawords}:-0}' * 4294967296), \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '%{Acct-Terminate-Cause}', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}', \ + '0', \ + '%{%{Acct-Delay-Time}:-0}' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + FramedIPAddress = NULLIF('%{Framed-IP-Address}', ''), \ + FramedIPv6Address = NULLIF('%{Framed-IPv6-Address}', ''), \ + FramedIPv6Prefix = NULLIF('%{Framed-IPv6-Prefix}', ''), \ + FramedInterfaceId = NULLIF('%{Framed-Interface-Id}', ''), \ + DelegatedIPv6Prefix = NULLIF('%{Delegated-IPv6-Prefix}', ''), \ + AcctStopTime = TO_DATE('%S','yyyy-mm-dd hh24:mi:ss'), \ + AcctSessionTime = '%{Acct-Session-Time}', \ + AcctInputOctets = '%{Acct-Input-Octets}' + \ + ('%{%{Acct-Input-Gigawords}:-0}' * 4294967296), \ + AcctOutputOctets = '%{Acct-Output-Octets}' + \ + ('%{%{Acct-Output-Gigawords}:-0}' * 4294967296), \ + AcctTerminateCause = '%{Acct-Terminate-Cause}', \ + ConnectInfo_stop = '%{Connect-Info}' \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime IS NULL" + + } + } +} + +####################################################################### +# Authentication Logging Queries +####################################################################### +# postauth_query - Insert some info after authentication +####################################################################### + +post-auth { + # Write SQL queries to a logfile. This is potentially useful for bulk inserts + # when used with the rlm_sql_null driver. +# logfile = ${logdir}/post-auth.sql + query = "\ + INSERT INTO ${..postauth_table} \ + (username, pass, reply, authdate ${..class.column_name}) \ + VALUES (\ + '%{User-Name}', \ + '%{%{User-Password}:-%{Chap-Password}}', \ + '%{reply:Packet-Type}', \ + TO_TIMESTAMP('%S.%M','YYYY-MM-DDHH24:MI:SS.FF') \ + ${..class.reply_xlat})" +} diff --git a/raddb/mods-config/sql/main/oracle/schema.sql b/raddb/mods-config/sql/main/oracle/schema.sql new file mode 100644 index 0000000..8f89e9d --- /dev/null +++ b/raddb/mods-config/sql/main/oracle/schema.sql @@ -0,0 +1,205 @@ +/* + * $Id$ + * + * Oracle schema for FreeRADIUS + * + * + * NOTE: Which columns are NULLable?? + */ + +/* + * Table structure for table 'radacct' + */ +CREATE TABLE radacct ( + radacctid INT PRIMARY KEY, + acctsessionid VARCHAR(96) NOT NULL, + acctuniqueid VARCHAR(32), + username VARCHAR(64) NOT NULL, + realm VARCHAR(64), + nasipaddress VARCHAR(15) NOT NULL, + nasportid VARCHAR(32), + nasporttype VARCHAR(32), + acctstarttime TIMESTAMP WITH TIME ZONE, + acctupdatetime TIMESTAMP WITH TIME ZONE, + acctstoptime TIMESTAMP WITH TIME ZONE, + acctsessiontime NUMERIC(19), + acctauthentic VARCHAR(32), + connectinfo_start VARCHAR(128), + connectinfo_stop VARCHAR(128), + acctinputoctets NUMERIC(19), + acctoutputoctets NUMERIC(19), + calledstationid VARCHAR(50), + callingstationid VARCHAR(50), + acctterminatecause VARCHAR(32), + servicetype VARCHAR(32), + framedprotocol VARCHAR(32), + framedipaddress VARCHAR(15), + framedipv6address VARCHAR(45), + framedipv6prefix VARCHAR(45), + framedinterfaceid VARCHAR(44), + delegatedipv6prefix VARCHAR(45), + acctstartdelay NUMERIC(12), + acctstopdelay NUMERIC(12), + XAscendSessionSvrKey VARCHAR(10), + Class VARCHAR(64) +); + +CREATE UNIUQE INDEX radacct_idx0 + ON radacct(acctuniqueid); +CREATE UNIQUE INDEX radacct_idx1 + ON radacct(acctsessionid,username,acctstarttime, + acctstoptime,nasipaddress,framedipaddress,framedipv6address,framedipv6prefix,framedinterfaceid,delegatedipv6prefix); +CREATE INDEX radacct_idx2 + ON radacct(class); + +CREATE SEQUENCE radacct_seq START WITH 1 INCREMENT BY 1; + +/* Trigger to emulate a serial # on the primary key */ +CREATE OR REPLACE TRIGGER radacct_serialnumber + BEFORE INSERT OR UPDATE OF radacctid ON radacct + FOR EACH ROW + BEGIN + if ( :new.radacctid = 0 or :new.radacctid is null ) then + SELECT radacct_seq.nextval into :new.radacctid from dual; + end if; + END; +/ + +/* + * Table structure for table 'radcheck' + */ +CREATE TABLE radcheck ( + id INT PRIMARY KEY, + username VARCHAR(30) NOT NULL, + attribute VARCHAR(64), + op VARCHAR(2) NOT NULL, + value VARCHAR(40) +); +CREATE SEQUENCE radcheck_seq START WITH 1 INCREMENT BY 1; + +/* Trigger to emulate a serial # on the primary key */ +CREATE OR REPLACE TRIGGER radcheck_serialnumber + BEFORE INSERT OR UPDATE OF id ON radcheck + FOR EACH ROW + BEGIN + if ( :new.id = 0 or :new.id is null ) then + SELECT radcheck_seq.nextval into :new.id from dual; + end if; + END; +/ + +/* + * Table structure for table 'radgroupcheck' + */ +CREATE TABLE radgroupcheck ( + id INT PRIMARY KEY, + groupname VARCHAR(20) NOT NULL, + attribute VARCHAR(64), + op CHAR(2) NOT NULL, + value VARCHAR(40) +); +CREATE SEQUENCE radgroupcheck_seq START WITH 1 INCREMENT BY 1; + +/* + * Table structure for table 'radgroupreply' + */ +CREATE TABLE radgroupreply ( + id INT PRIMARY KEY, + GroupName VARCHAR(20) NOT NULL, + Attribute VARCHAR(64), + op CHAR(2) NOT NULL, + Value VARCHAR(40) +); +CREATE SEQUENCE radgroupreply_seq START WITH 1 INCREMENT BY 1; + +/* + * Table structure for table 'radreply' + */ +CREATE TABLE radreply ( + id INT PRIMARY KEY, + UserName VARCHAR(30) NOT NULL, + Attribute VARCHAR(64), + op CHAR(2) NOT NULL, + Value VARCHAR(40) +); +CREATE INDEX radreply_idx1 ON radreply(UserName); +CREATE SEQUENCE radreply_seq START WITH 1 INCREMENT BY 1; + +/* Trigger to emulate a serial # on the primary key */ +CREATE OR REPLACE TRIGGER radreply_serialnumber + BEFORE INSERT OR UPDATE OF id ON radreply + FOR EACH ROW + BEGIN + if ( :new.id = 0 or :new.id is null ) then + SELECT radreply_seq.nextval into :new.id from dual; + end if; + END; +/ + +/* + * Table structure for table 'radusergroup' + */ +CREATE TABLE radusergroup ( + id INT PRIMARY KEY, + UserName VARCHAR(30) NOT NULL, + GroupName VARCHAR(30) +); +CREATE SEQUENCE radusergroup_seq START WITH 1 INCREMENT BY 1; + +/* Trigger to emulate a serial # on the primary key */ +CREATE OR REPLACE TRIGGER radusergroup_serialnumber + BEFORE INSERT OR UPDATE OF id ON radusergroup + FOR EACH ROW + BEGIN + if ( :new.id = 0 or :new.id is null ) then + SELECT radusergroup_seq.nextval into :new.id from dual; + end if; + END; +/ + + +CREATE TABLE radpostauth ( + id INT PRIMARY KEY, + UserName VARCHAR(64) NOT NULL, + Pass VARCHAR(64), + Reply VARCHAR(64), + AuthDate TIMESTAMP(6) WITH TIME ZONE, + Class VARCHAR(64) +); +CREATE INDEX radpostauth_idx0 + ON radpostauth(UserName); +CREATE INDEX radpostauth_idx1 + ON radpostauth(class); + +CREATE SEQUENCE radpostauth_seq START WITH 1 INCREMENT BY 1; + +CREATE OR REPLACE TRIGGER radpostauth_TRIG + BEFORE INSERT OR UPDATE OF id ON radpostauth + FOR EACH ROW + BEGIN + if ( :new.id = 0 or :new.id is null ) then + SELECT radpostauth_seq.nextval into :new.id from dual; + end if; + if (:new.AuthDate is null) then + select systimestamp into :new.AuthDate from dual; + end if; + END; + +/ + +/* + * Table structure for table 'nas' + */ +CREATE TABLE nas ( + id INT PRIMARY KEY, + nasname VARCHAR(128), + shortname VARCHAR(32), + type VARCHAR(30), + ports INT, + secret VARCHAR(60), + server VARCHAR(64), + community VARCHAR(50), + description VARCHAR(200) +); +CREATE SEQUENCE nas_seq START WITH 1 INCREMENT BY 1; + diff --git a/raddb/mods-config/sql/main/postgresql/extras/cisco_h323_db_schema.sql b/raddb/mods-config/sql/main/postgresql/extras/cisco_h323_db_schema.sql new file mode 100644 index 0000000..0fabd43 --- /dev/null +++ b/raddb/mods-config/sql/main/postgresql/extras/cisco_h323_db_schema.sql @@ -0,0 +1,295 @@ +/* + * $Id$ + * + * --- Peter Nixon [ codemonkey@peternixon.net ] + * + * This is a custom SQL schema for doing H323 and SIP VoIP accounting + * with FreeRadius and Cisco equipment. It is currently known to work + * with 3640, 5300 and 5350 series as well as CSPS (Cisco SIP Proxy + * Server). It will scale A LOT better than the default radius schema + * which is designed for simple dialup installations of FreeRadius. + * + * For this schema to work properly you MUST use + * raddb/mods-config/sql/postgresql/voip-postpaid.conf rather than + * raddb/mods-config/sql/postgresql/dialup.conf + * + * If you wish to do RADIUS Authentication using the same database, + * you MUST use use raddb/mods-config/sql/postgresql/schema.sql as well as this schema. + */ + +/* + * Table structure for 'Start' tables + */ + +CREATE TABLE StartVoIP ( + RadAcctId BIGSERIAL PRIMARY KEY, + AcctTime TIMESTAMP with time zone NOT NULL, + h323SetupTime TIMESTAMP with time zone, + H323ConnectTime TIMESTAMP with time zone, + UserName VARCHAR(64), + RadiusServerName VARCHAR(32), + NASIPAddress INET NOT NULL, + CalledStationId VARCHAR(80), + CallingStationId VARCHAR(80), + AcctDelayTime INTEGER, + H323GWID VARCHAR(32), + h323CallOrigin VARCHAR(10), + CallID VARCHAR(80) NOT NULL, + processed BOOLEAN DEFAULT false +); +create index startvoipcombo on startvoip (AcctTime, nasipaddress); + + +CREATE TABLE StartTelephony ( + RadAcctId BIGSERIAL PRIMARY KEY, + AcctTime TIMESTAMP with time zone NOT NULL, + h323SetupTime TIMESTAMP with time zone, + H323ConnectTime TIMESTAMP with time zone, + UserName VARCHAR(64), + RadiusServerName VARCHAR(32), + NASIPAddress INET NOT NULL, + CalledStationId VARCHAR(80), + CallingStationId VARCHAR(80), + AcctDelayTime INTEGER, + H323GWID VARCHAR(32), + h323CallOrigin VARCHAR(10), + CallID VARCHAR(80) NOT NULL, + processed BOOLEAN DEFAULT false +); +create index starttelephonycombo on starttelephony (AcctTime, nasipaddress); + + + +/* + * Table structure for 'Stop' tables + */ +CREATE TABLE StopVoIP ( + RadAcctId BIGSERIAL PRIMARY KEY, + AcctTime TIMESTAMP with time zone NOT NULL, + H323SetupTime TIMESTAMP with time zone, + H323ConnectTime TIMESTAMP with time zone, + H323DisconnectTime TIMESTAMP with time zone, + UserName VARCHAR(32), + RadiusServerName VARCHAR(32), + NASIPAddress INET NOT NULL, + AcctSessionTime BIGINT, + AcctInputOctets BIGINT, + AcctOutputOctets BIGINT, + CalledStationId VARCHAR(80), + CallingStationId VARCHAR(80), + AcctDelayTime SMALLINT, + CiscoNASPort VARCHAR(1), + H323GWID VARCHAR(32), + H323CallOrigin VARCHAR(10), + H323DisconnectCause VARCHAR(20), + H323RemoteAddress INET, + H323VoiceQuality INTEGER, + CallID VARCHAR(80) NOT NULL, + processed BOOLEAN DEFAULT false +); +create UNIQUE index stopvoipcombo on stopvoip (AcctTime, nasipaddress, CallID); + + +CREATE TABLE StopTelephony ( + RadAcctId BIGSERIAL PRIMARY KEY, + AcctTime TIMESTAMP with time zone NOT NULL, + H323SetupTime TIMESTAMP with time zone NOT NULL, + H323ConnectTime TIMESTAMP with time zone NOT NULL, + H323DisconnectTime TIMESTAMP with time zone NOT NULL, + UserName VARCHAR(32) DEFAULT '' NOT NULL, + RadiusServerName VARCHAR(32), + NASIPAddress INET NOT NULL, + AcctSessionTime BIGINT, + AcctInputOctets BIGINT, + AcctOutputOctets BIGINT, + CalledStationId VARCHAR(80), + CallingStationId VARCHAR(80), + AcctDelayTime SMALLINT, + CiscoNASPort VARCHAR(16), + H323GWID VARCHAR(32), + H323CallOrigin VARCHAR(10), + H323DisconnectCause VARCHAR(20), + H323RemoteAddress INET, + H323VoiceQuality INTEGER, + CallID VARCHAR(80) NOT NULL, + processed BOOLEAN DEFAULT false +); +-- You can have more than one record that is identical except for CiscoNASPort if you have a dial peer hungroup +-- configured for multiple PRIs. +create UNIQUE index stoptelephonycombo on stoptelephony (AcctTime, nasipaddress, CallID, CiscoNASPort); + +/* + * Table structure for 'gateways' + * + * This table should list the IP addresses, names and locations of all your gateways + * This can be used to make more useful reports. + * + * Note: This table should be removed in favour of using the "nas" table. + */ + +CREATE TABLE gateways ( + gw_ip INET NOT NULL, + gw_name VARCHAR(32) NOT NULL, + gw_city VARCHAR(32) +); + + +/* + * Table structure for 'customers' + * + * This table should list your Customers names and company + * This can be used to make more useful reports. + */ + +CREATE TABLE customers ( + cust_id SERIAL NOT NULL, + company VARCHAR(32), + customer VARCHAR(32) +); + +/* + * Table structure for 'cust_gw' + * + * This table should list the IP addresses and Customer IDs of all your Customers gateways + * This can be used to make more useful reports. + */ + +CREATE TABLE cust_gw ( + cust_gw INET PRIMARY KEY, + cust_id INTEGER NOT NULL, + "location" VARCHAR(32) +); + + +CREATE VIEW customerip AS + SELECT gw.cust_gw AS ipaddr, cust.company, cust.customer, gw."location" FROM customers cust, cust_gw gw WHERE (cust.cust_id = gw.cust_id); + + +-- create plpgsql language (You need to be a database superuser to be able to do this) +CREATE FUNCTION "plpgsql_call_handler" () RETURNS LANGUAGE_HANDLER AS '$libdir/plpgsql' LANGUAGE C; +CREATE TRUSTED LANGUAGE "plpgsql" HANDLER "plpgsql_call_handler"; + +/* + * Function 'strip_dot' + * removes "." from the start of cisco timestamps + * + * From the cisco website: + * "A timestamp that is preceded by an asterisk (*) or a dot (.) may not be accurate. + * An asterisk (*) means that after a gateway reboot, the gateway clock was not manually set + * and the gateway has not synchronized with an NTP server yet. A dot (.) means the gateway + * NTP has lost synchronization with an NTP server." + * + * We therefore do not bother to strip asterisks (*) from timestamps, as you NEED ntp setup + * unless you don't care about billing at all! + * + * * Example useage: + * insert into mytable values (strip_dot('.16:46:02.356 EET Wed Dec 11 2002')); + * + */ + + +CREATE OR REPLACE FUNCTION strip_dot (VARCHAR) RETURNS TIMESTAMPTZ AS ' + DECLARE + original_timestamp ALIAS FOR $1; + BEGIN + IF original_timestamp = '''' THEN + RETURN NULL; + END IF; + IF substring(original_timestamp from 1 for 1) = ''.'' THEN + RETURN substring(original_timestamp from 2); + ELSE + RETURN original_timestamp; + END IF; + END; +' LANGUAGE 'plpgsql'; + + +CREATE OR REPLACE FUNCTION pick_id (VARCHAR, VARCHAR) RETURNS VARCHAR AS ' + DECLARE + h323confid ALIAS FOR $1; + callid ALIAS FOR $2; + BEGIN + IF h323confid <> '''' THEN + RETURN h323confid; + END IF; + IF callid <> '''' THEN + RETURN callid; + END IF; + RETURN NULL; + END; +' LANGUAGE 'plpgsql'; + + + +/* + * Table structure for 'isdn_error_codes' table + * + * Taken from cisco.com this data can be JOINED against h323DisconnectCause to + * give human readable error reports. + * + */ + + +CREATE TABLE isdn_error_codes ( + error_code VARCHAR(2) PRIMARY KEY, + desc_short VARCHAR(90), + desc_long TEXT +); + +/* + * Data for 'isdn_error_codes' table + */ + +INSERT INTO isdn_error_codes VALUES ('1', 'Unallocated (unassigned) number', 'The ISDN number was sent to the switch in the correct format; however, the number is not assigned to any destination equipment.'); +INSERT INTO isdn_error_codes VALUES ('10', 'Normal call clearing', 'Normal call clearing has occurred.'); +INSERT INTO isdn_error_codes VALUES ('11', 'User busy', 'The called system acknowledges the connection request but is unable to accept the call because all B channels are in use.'); +INSERT INTO isdn_error_codes VALUES ('12', 'No user responding', 'The connection cannot be completed because the destination does not respond to the call.'); +INSERT INTO isdn_error_codes VALUES ('13', 'No answer from user (user alerted)', 'The destination responds to the connection request but fails to complete the connection within the prescribed time. The problem is at the remote end of the connection.'); +INSERT INTO isdn_error_codes VALUES ('15', 'Call rejected', 'The destination is capable of accepting the call but rejected the call for an unknown reason.'); +INSERT INTO isdn_error_codes VALUES ('16', 'Number changed', 'The ISDN number used to set up the call is not assigned to any system.'); +INSERT INTO isdn_error_codes VALUES ('1A', 'Non-selected user clearing', 'The destination is capable of accepting the call but rejected the call because it was not assigned to the user.'); +INSERT INTO isdn_error_codes VALUES ('1B', 'Designation out of order', 'The destination cannot be reached because the interface is not functioning correctly, and a signaling message cannot be delivered. This might be a temporary condition, but it could last for an extended period of time. For example, the remote equipment might be turned off.'); +INSERT INTO isdn_error_codes VALUES ('1C', 'Invalid number format', 'The connection could be established because the destination address was presented in an unrecognizable format or because the destination address was incomplete.'); +INSERT INTO isdn_error_codes VALUES ('1D', 'Facility rejected', 'The facility requested by the user cannot be provided by the network.'); +INSERT INTO isdn_error_codes VALUES ('1E', 'Response to STATUS ENQUIRY', 'The status message was generated in direct response to the prior receipt of a status enquiry message.'); +INSERT INTO isdn_error_codes VALUES ('1F', 'Normal, unspecified', 'Reports the occurrence of a normal event when no standard cause applies. No action required.'); +INSERT INTO isdn_error_codes VALUES ('2', 'No route to specified transit network', 'The ISDN exchange is asked to route the call through an unrecognized intermediate network.'); +INSERT INTO isdn_error_codes VALUES ('22', 'No circuit/channel available', 'The connection cannot be established because no appropriate channel is available to take the call.'); +INSERT INTO isdn_error_codes VALUES ('26', 'Network out of order', 'The destination cannot be reached because the network is not functioning correctly, and the condition might last for an extended period of time. An immediate reconnect attempt will probably be unsuccessful.'); +INSERT INTO isdn_error_codes VALUES ('29', 'Temporary failure', 'An error occurred because the network is not functioning correctly. The problem will be resolved shortly.'); +INSERT INTO isdn_error_codes VALUES ('2A', 'Switching equipment congestion', 'The destination cannot be reached because the network switching equipment is temporarily overloaded.'); +INSERT INTO isdn_error_codes VALUES ('2B', 'Access information discarded', 'The network cannot provide the requested access information.'); +INSERT INTO isdn_error_codes VALUES ('2C', 'Requested circuit/channel not available', 'The remote equipment cannot provide the requested channel for an unknown reason. This might be a temporary problem.'); +INSERT INTO isdn_error_codes VALUES ('2F', 'Resources unavailable, unspecified', 'The requested channel or service is unavailable for an unknown reason. This might be a temporary problem.'); +INSERT INTO isdn_error_codes VALUES ('3', 'No route to destination', 'The call was routed through an intermediate network that does not serve the destination address.'); +INSERT INTO isdn_error_codes VALUES ('31', 'Quality of service unavailable', 'The requested quality of service cannot be provided by the network. This might be a subscription problem.'); +INSERT INTO isdn_error_codes VALUES ('32', 'Requested facility not subscribed', 'The remote equipment supports the requested supplementary service by subscription only.'); +INSERT INTO isdn_error_codes VALUES ('39', 'Bearer capability not authorized', 'The user requested a bearer capability that the network provides, but the user is not authorized to use it. This might be a subscription problem.'); +INSERT INTO isdn_error_codes VALUES ('3A', 'Bearer capability not presently available', 'The network normally provides the requested bearer capability, but it is unavailable at the present time. This might be due to a temporary network problem or to a subscription problem.'); +INSERT INTO isdn_error_codes VALUES ('3F', 'Service or option not available, unspecified', 'The network or remote equipment was unable to provide the requested service option for an unspecified reason. This might be a subscription problem.'); +INSERT INTO isdn_error_codes VALUES ('41', 'Bearer capability not implemented', 'The network cannot provide the bearer capability requested by the user.'); +INSERT INTO isdn_error_codes VALUES ('42', 'Channel type not implemented', 'The network or the destination equipment does not support the requested channel type.'); +INSERT INTO isdn_error_codes VALUES ('45', 'Requested facility not implemented', 'The remote equipment does not support the requested supplementary service.'); +INSERT INTO isdn_error_codes VALUES ('46', 'Only restricted digital information bearer capability is available', 'The network is unable to provide unrestricted digital information bearer capability.'); +INSERT INTO isdn_error_codes VALUES ('4F', 'Service or option not implemented, unspecified', 'The network or remote equipment is unable to provide the requested service option for an unspecified reason. This might be a subscription problem.'); +INSERT INTO isdn_error_codes VALUES ('51', 'Invalid call reference value', 'The remote equipment received a call with a call reference that is not currently in use on the user-network interface.'); +INSERT INTO isdn_error_codes VALUES ('52', 'Identified channel does not exist', 'The receiving equipment is requested to use a channel that is not activated on the interface for calls.'); +INSERT INTO isdn_error_codes VALUES ('53', 'A suspended call exists, but this call identity does not', 'The network received a call resume request. The call resume request contained a Call Identify information element that indicates that the call identity is being used for a suspended call.'); +INSERT INTO isdn_error_codes VALUES ('54', 'Call identity in use', 'The network received a call resume request. The call resume request contained a Call Identify information element that indicates that it is in use for a suspended call.'); +INSERT INTO isdn_error_codes VALUES ('55', 'No call suspended', 'The network received a call resume request when there was not a suspended call pending. This might be a transient error that will be resolved by successive call retries.'); +INSERT INTO isdn_error_codes VALUES ('56', 'Call having the requested call identity has been cleared', 'The network received a call resume request. The call resume request contained a Call Identity information element, which once indicated a suspended call. However, the suspended call was cleared either by timeout or by the remote user.'); +INSERT INTO isdn_error_codes VALUES ('58', 'Incompatible destination', 'Indicates that an attempt was made to connect to non-ISDN equipment. For example, to an analog line.'); +INSERT INTO isdn_error_codes VALUES ('5B', 'Invalid transit network selection', 'The ISDN exchange was asked to route the call through an unrecognized intermediate network.'); +INSERT INTO isdn_error_codes VALUES ('5F', 'Invalid message, unspecified', 'An invalid message was received, and no standard cause applies. This is usually due to a D-channel error. If this error occurs systematically, report it to your ISDN service provider.'); +INSERT INTO isdn_error_codes VALUES ('6', 'Channel unacceptable', 'The service quality of the specified channel is insufficient to accept the connection.'); +INSERT INTO isdn_error_codes VALUES ('60', 'Mandatory information element is missing', 'The receiving equipment received a message that did not include one of the mandatory information elements. This is usually due to a D-channel error. If this error occurs systematically, report it to your ISDN service provider.'); +INSERT INTO isdn_error_codes VALUES ('61', 'Message type non-existent or not implemented', 'The receiving equipment received an unrecognized message, either because the message type was invalid or because the message type was valid but not supported. The cause is due to either a problem with the remote configuration or a problem with the local D channel.'); +INSERT INTO isdn_error_codes VALUES ('62', 'Message not compatible with call state or message type non-existent or not implemented', 'The remote equipment received an invalid message, and no standard cause applies. This cause is due to a D-channel error. If this error occurs systematically, report it to your ISDN service provider.'); +INSERT INTO isdn_error_codes VALUES ('63', 'Information element non-existent or not implemented', 'The remote equipment received a message that includes information elements, which were not recognized. This is usually due to a D-channel error. If this error occurs systematically, report it to your ISDN service provider.'); +INSERT INTO isdn_error_codes VALUES ('64', 'Invalid information element contents', 'The remote equipment received a message that includes invalid information in the information element. This is usually due to a D-channel error.'); +INSERT INTO isdn_error_codes VALUES ('65', 'Message not compatible with call state', 'The remote equipment received an unexpected message that does not correspond to the current state of the connection. This is usually due to a D-channel error.'); +INSERT INTO isdn_error_codes VALUES ('66', 'Recovery on timer expires', 'An error-handling (recovery) procedure was initiated by a timer expiry. This is usually a temporary problem.'); +INSERT INTO isdn_error_codes VALUES ('6F', 'Protocol error, unspecified', 'An unspecified D-channel error when no other standard cause applies.'); +INSERT INTO isdn_error_codes VALUES ('7', 'Call awarded and being delivered in an established channel', 'The user is assigned an incoming call that is being connected to an already-established call channel.'); +INSERT INTO isdn_error_codes VALUES ('7F', 'Internetworking, unspecified', 'An event occurred, but the network does not provide causes for the action that it takes. The precise problem is unknown.'); + diff --git a/raddb/mods-config/sql/main/postgresql/extras/voip-postpaid.conf b/raddb/mods-config/sql/main/postgresql/extras/voip-postpaid.conf new file mode 100644 index 0000000..9f1449c --- /dev/null +++ b/raddb/mods-config/sql/main/postgresql/extras/voip-postpaid.conf @@ -0,0 +1,70 @@ +# -*- text -*- +## +## voip-postpaid.conf -- PostgreSQL configuration for H323 VoIP billingx +## (cisco_h323_db_schema.sql) +## +## $Id$ + + + ####################################################################### + # Query config: Username + ####################################################################### + # This is the username that will get substituted, escaped, and added + # as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used below + # everywhere a username substitution is needed so you you can be sure + # the username passed from the client is escaped properly. + # + # Uncomment the next line, if you want the sql_user_name to mean: + # + # Use Stripped-User-Name, if it's there. + # Else use User-Name, if it's there, + # Else use hard-coded string "none" as the user name. + # + #sql_user_name = "%{%{Stripped-User-Name}:-%{%{User-Name}:-none}}" + # + sql_user_name = "%{User-Name}" + + accounting { + reference = "%{tolower:type.%{Acct-Status-Type}.query}" + + # Write SQL queries to a logfile. This is potentially useful for bulk inserts + # when used with the rlm_sql_null driver. +# logfile = ${logdir}/accounting.sql + + type { + start { + query = "INSERT INTO ${....acct_table1}%{h323-call-type} \ + (RadiusServerName, UserName, NASIPAddress, AcctTime, CalledStationId, \ + CallingStationId, AcctDelayTime, h323gwid, h323callorigin, \ + h323setuptime, H323ConnectTime, callid) \ + VALUES(\ + '${radius_server_name}', '%{SQL-User-Name}', \ + '%{NAS-IP-Address}', now(), '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', '%{%{Acct-Delay-Time}:-0}', '%{h323-gw-id}', \ + '%{h323-call-origin}', strip_dot('%{h323-setup-time}'), \ + strip_dot('%{h323-connect-time}'), pick_id('%{h323-conf-id}', \ + '%{call-id}'))" + } + + stop { + query = "INSERT INTO $....acct_table2}%{h323-call-type} \ + (RadiusServerName, UserName, NASIPAddress, AcctTime, \ + AcctSessionTime, AcctInputOctets, AcctOutputOctets, CalledStationId, \ + CallingStationId, AcctDelayTime, H323RemoteAddress, H323VoiceQuality, \ + CiscoNASPort, h323callorigin, callid, h323connecttime, \ + h323disconnectcause, h323disconnecttime, h323gwid, h323setuptime) \ + VALUES(\ + '${radius_server_name}', '%{SQL-User-Name}', '%{NAS-IP-Address}', \ + NOW(), '%{%{Acct-Session-Time}:-0}', \ + '%{%{Acct-Input-Octets}:-0}', '%{%{Acct-Output-Octets}:-0}', \ + '%{Called-Station-Id}', '%{Calling-Station-Id}', \ + '%{%{Acct-Delay-Time}:-0}', NULLIF('%{h323-remote-address}', '')::inet, \ + NULLIF('%{h323-voice-quality}','')::integer, \ + NULLIF('%{Cisco-NAS-Port}', ''), \ + '%{h323-call-origin}', pick_id('%{h323-conf-id}', '%{call-id}'), \ + strip_dot('%{h323-connect-time}'), '%{h323-disconnect-cause}', \ + strip_dot('%{h323-disconnect-time}'), '%{h323-gw-id}', \ + strip_dot('%{h323-setup-time}'))" + } + } + } diff --git a/raddb/mods-config/sql/main/postgresql/process-radacct.sql b/raddb/mods-config/sql/main/postgresql/process-radacct.sql new file mode 100644 index 0000000..a454369 --- /dev/null +++ b/raddb/mods-config/sql/main/postgresql/process-radacct.sql @@ -0,0 +1,288 @@ +# -*- text -*- +# +# main/postgresql/process-radacct.sql -- Schema extensions for processing radacct entries +# +# $Id$ + +-- --------------------------------- +-- - Per-user data usage over time - +-- --------------------------------- +-- +-- An extension to the standard schema to hold per-user data usage statistics +-- for arbitrary periods. +-- +-- The data_usage_by_period table is populated by periodically calling the +-- fr_new_data_usage_period stored procedure. +-- +-- This table can be queried in various ways to produce reports of aggregate +-- data use over time. For example, if the fr_new_data_usage_period SP is +-- invoked one per day just after midnight, to produce usage data with daily +-- granularity, then a reasonably accurate monthly bandwidth summary for a +-- given user could be obtained by queriing this table with: +-- +-- SELECT +-- TO_CHAR(period_start, 'YYYY-Month') AS month, +-- TRUNC(SUM(acctinputoctets)/1000/1000/1000,9) AS gb_in, +-- TRUNC(SUM(acctoutputoctets)/1000/1000/1000,9) AS gb_out +-- FROM +-- data_usage_by_period +-- WHERE +-- username='bob' AND +-- period_end IS NOT NULL +-- GROUP BY +-- month; +-- +-- month | gb_in | gb_out +-- ----------------+-------------+-------------- +-- 2019-July | 5.782279231 | 50.545664824 +-- 2019-August | 4.230543344 | 48.523096424 +-- 2019-September | 4.847360599 | 48.631835488 +-- 2019-October | 6.456763254 | 51.686231937 +-- 2019-November | 6.362537735 | 52.385710572 +-- 2019-December | 4.301524442 | 50.762240277 +-- 2020-January | 5.436280545 | 49.067775286 +-- (7 rows) +-- +CREATE TABLE data_usage_by_period ( + username text, + period_start timestamp with time zone, + period_end timestamp with time zone, + acctinputoctets bigint, + acctoutputoctets bigint +); +ALTER TABLE data_usage_by_period ADD CONSTRAINT data_usage_by_period_pkey PRIMARY KEY (username, period_start); +CREATE INDEX data_usage_by_period_pkey_period_end ON data_usage_by_period(period_end); + + +-- +-- Stored procedure that when run with some arbitrary frequency, say +-- once per day by cron, will process the recent radacct entries to extract +-- time-windowed data containing acct{input,output}octets ("data usage") per +-- username, per period. +-- +-- Each invocation will create new rows in the data_usage_by_period tables +-- containing the data used by each user since the procedure was last invoked. +-- The intervals do not need to be identical but care should be taken to +-- ensure that the start/end of each period aligns well with any intended +-- reporting intervals. +-- +-- It can be invoked by running: +-- +-- SELECT fr_new_data_usage_period(); +-- +-- +CREATE OR REPLACE FUNCTION fr_new_data_usage_period () +RETURNS void +LANGUAGE plpgsql +AS $$ +DECLARE v_start timestamp; +DECLARE v_end timestamp; +BEGIN + + SELECT COALESCE(MAX(period_end) + INTERVAL '1 SECOND', TO_TIMESTAMP(0)) INTO v_start FROM data_usage_by_period; + SELECT DATE_TRUNC('second',CURRENT_TIMESTAMP) INTO v_end; + + -- + -- Add the data usage for the sessions that were active in the current + -- period to the table. Include all sessions that finished since the start + -- of this period as well as those still ongoing. + -- + INSERT INTO data_usage_by_period (username, period_start, period_end, acctinputoctets, acctoutputoctets) + SELECT * + FROM ( + SELECT + username, + v_start, + v_end, + SUM(acctinputoctets) AS acctinputoctets, + SUM(acctoutputoctets) AS acctoutputoctets + FROM (( + SELECT + username, acctinputoctets, acctoutputoctets + FROM + radacct + WHERE + acctstoptime > v_start + ) UNION ALL ( + SELECT + username, acctinputoctets, acctoutputoctets + FROM + radacct + WHERE + acctstoptime IS NULL + )) AS a + GROUP BY + username + ) AS s + ON CONFLICT ON CONSTRAINT data_usage_by_period_pkey + DO UPDATE + SET + acctinputoctets = data_usage_by_period.acctinputoctets + EXCLUDED.acctinputoctets, + acctoutputoctets = data_usage_by_period.acctoutputoctets + EXCLUDED.acctoutputoctets, + period_end = v_end; + + -- + -- Create an open-ended "next period" for all ongoing sessions and carry a + -- negative value of their data usage to avoid double-accounting when we + -- process the next period. Their current data usage has already been + -- allocated to the current and possibly previous periods. + -- + INSERT INTO data_usage_by_period (username, period_start, period_end, acctinputoctets, acctoutputoctets) + SELECT * + FROM ( + SELECT + username, + v_end + INTERVAL '1 SECOND', + NULL::timestamp, + 0 - SUM(acctinputoctets), + 0 - SUM(acctoutputoctets) + FROM + radacct + WHERE + acctstoptime IS NULL + GROUP BY + username + ) AS s; + +END +$$; + + +-- ------------------------------------------------------ +-- - "Lightweight" Accounting-On/Off strategy resources - +-- ------------------------------------------------------ +-- +-- The following resources are for use only when the "lightweight" +-- Accounting-On/Off strategy is enabled in queries.conf. +-- +-- Instead of bulk closing the radacct sessions belonging to a reloaded NAS, +-- this strategy leaves them open and records the NAS reload time in the +-- nasreload table. +-- +-- Where applicable, the onus is on the administator to: +-- +-- * Consider the nas reload times when deriving a list of +-- active/inactive sessions, and when determining the duration of sessions +-- interrupted by a NAS reload. (Refer to the view below.) +-- +-- * Close the affected sessions out of band. (Refer to the SP below.) +-- +-- +-- The radacct_with_reloads view presents the radacct table with two additional +-- columns: acctstoptime_with_reloads and acctsessiontime_with_reloads +-- +-- Where the session isn't closed (acctstoptime IS NULL), yet it started before +-- the last reload of the NAS (radacct.acctstarttime < nasreload.reloadtime), +-- the derived columns are set based on the reload time of the NAS (effectively +-- the point in time that the session was interrupted.) +-- +CREATE VIEW radacct_with_reloads AS +SELECT + a.*, + COALESCE(a.AcctStopTime, + CASE WHEN a.AcctStartTime < n.ReloadTime THEN n.ReloadTime END + ) AS AcctStopTime_With_Reloads, + COALESCE(a.AcctSessionTime, + CASE WHEN a.AcctStopTime IS NULL AND a.AcctStartTime < n.ReloadTime THEN + EXTRACT(EPOCH FROM (n.ReloadTime - a.AcctStartTime)) + END + ) AS AcctSessionTime_With_Reloads +FROM radacct a +LEFT OUTER JOIN nasreload n USING (nasipaddress); + + +-- +-- It may be desirable to periodically "close" radacct sessions belonging to a +-- reloaded NAS, replicating the "bulk close" Accounting-On/Off behaviour, +-- just not in real time. +-- +-- The fr_radacct_close_after_reload SP will set radacct.acctstoptime to +-- nasreload.reloadtime, calculate the corresponding radacct.acctsessiontime, +-- and set acctterminatecause to "NAS reboot" for interrupted sessions. It +-- does so in batches, which avoids long-lived locks on the affected rows. +-- +-- It can be invoked as follows: +-- +-- CALL fr_radacct_close_after_reload(); +-- +-- Note: This SP requires PostgreSQL >= 11 which was the first version to +-- introduce PROCEDUREs which permit transaction control. This allows COMMIT +-- to be called to incrementally apply successive batch updates prior to the +-- end of the procedure. Prior to version 11 there exists only FUNCTIONs that +-- execute atomically. You can convert this procedure to a function, but by +-- doing so you are really no better off than performing a single, +-- long-running bulk update. +-- +-- Note: This SP walks radacct in strides of v_batch_size. It will typically +-- skip closed and ongoing sessions at a rate significantly faster than +-- 500,000 rows per second and process batched updates faster than 25,000 +-- orphaned sessions per second. If this isn't fast enough then you should +-- really consider using a custom schema that includes partitioning by +-- nasipaddress or acct{start,stop}time. +-- +CREATE OR REPLACE PROCEDURE fr_radacct_close_after_reload () +LANGUAGE plpgsql +AS $$ + +DECLARE v_a bigint; +DECLARE v_z bigint; +DECLARE v_updated bigint DEFAULT 0; +DECLARE v_last_report bigint DEFAULT 0; +DECLARE v_now bigint; +DECLARE v_last boolean DEFAULT false; +DECLARE v_rowcount integer; + +-- +-- This works for many circumstances +-- +DECLARE v_batch_size CONSTANT integer := 2500; + +BEGIN + + SELECT MIN(RadAcctId) INTO v_a FROM radacct WHERE AcctStopTime IS NULL; + + LOOP + + v_z := NULL; + SELECT RadAcctId INTO v_z FROM radacct WHERE RadAcctId > v_a ORDER BY RadAcctId OFFSET v_batch_size LIMIT 1; + + IF v_z IS NULL THEN + SELECT MAX(RadAcctId) INTO v_z FROM radacct; + v_last := true; + END IF; + + UPDATE radacct a + SET + AcctStopTime = n.reloadtime, + AcctSessionTime = EXTRACT(EPOCH FROM (n.ReloadTime - a.AcctStartTime)), + AcctTerminateCause = 'NAS reboot' + FROM nasreload n + WHERE + a.NASIPAddress = n.NASIPAddress + AND RadAcctId BETWEEN v_a AND v_z + AND AcctStopTime IS NULL + AND AcctStartTime < n.ReloadTime; + + GET DIAGNOSTICS v_rowcount := ROW_COUNT; + v_updated := v_updated + v_rowcount; + + COMMIT; -- Make the update visible + + v_a := v_z + 1; + + -- + -- Periodically report how far we've got + -- + SELECT EXTRACT(EPOCH FROM CURRENT_TIMESTAMP) INTO v_now; + IF v_last_report != v_now OR v_last THEN + RAISE NOTICE 'RadAcctID: %; Sessions closed: %', v_z, v_updated; + v_last_report := v_now; + END IF; + + EXIT WHEN v_last; + + END LOOP; + +END +$$; + diff --git a/raddb/mods-config/sql/main/postgresql/queries.conf b/raddb/mods-config/sql/main/postgresql/queries.conf new file mode 100644 index 0000000..18a1ed0 --- /dev/null +++ b/raddb/mods-config/sql/main/postgresql/queries.conf @@ -0,0 +1,742 @@ +# -*- text -*- +# +# main/postgresql/queries.conf -- PostgreSQL configuration for default schema (schema.sql) +# +# $Id$ + +# Use the driver specific SQL escape method. +# +# If you enable this configuration item, the "safe_characters" +# configuration is ignored. FreeRADIUS then uses the PostgreSQL escape +# functions to escape input strings. The only downside to making this +# change is that the PostgreSQL escaping method is not the same the one +# used by FreeRADIUS. So characters which are NOT in the +# "safe_characters" list will now be stored differently in the database. +# +#auto_escape = yes + +# Safe characters list for sql queries. Everything else is replaced +# with their mime-encoded equivalents. +# The default list should be ok +# Using 'auto_escape' is preferred +# safe_characters = "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" + +####################################################################### +# Query config: Username +####################################################################### +# This is the username that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used +# below everywhere a username substitution is needed so you you can +# be sure the username passed from the client is escaped properly. +# +# Uncomment the next line, if you want the sql_user_name to mean: +# +# Use Stripped-User-Name, if it's there. +# Else use User-Name, if it's there, +# Else use hard-coded string "none" as the user name. +# +#sql_user_name = "%{%{Stripped-User-Name}:-%{%{User-Name}:-none}}" + +sql_user_name = "%{User-Name}" + +####################################################################### +# Query config: Event-Timestamp +####################################################################### +# event_timestamp_epoch is the basis for the time inserted into +# accounting records. Typically this will be the Event-Timestamp of the +# accounting request, which is usually provided by a NAS. +# +# Uncomment the next line, if you want the timestamp to be based on the +# request reception time recorded by this server, for example if you +# distrust the provided Event-Timestamp. +#event_timestamp_epoch = "%l" + +event_timestamp_epoch = "%{%{integer:Event-Timestamp}:-%l}" + +# event_timestamp is the SQL snippet for converting an epoch timestamp +# to an SQL date. + +event_timestamp = "TO_TIMESTAMP(${event_timestamp_epoch})" + +####################################################################### +# Query config: Class attribute +####################################################################### +# +# 3.0.22 and later have a "class" column in the accounting table. +# +# However, we do NOT want to break existing configurations by adding +# the Class attribute to the default queries. If we did that, then +# systems using newer versions of the server would fail, because +# there is no "class" column in their accounting tables. +# +# The solution to that is the following "class" subsection. If your +# database has a "class" column for the various tables, then you can +# uncomment the configuration items here. The queries below will +# then automatically insert the Class attribute into radacct, +# radpostauth, etc. +# +class { + # + # Delete the '#' character from each of the configuration + # items in this section. This change puts the Class + # attribute into the various tables. Leave the double-quoted + # string there, as the value for the configuration item. + # + # See also policy.d/accounting, and the "insert_acct_class" + # policy. You will need to list (or uncomment) + # "insert_acct_class" in the "post-auth" section in order to + # create a Class attribute. + # + column_name = # ", Class" + packet_xlat = # ", '%{Class}'" + reply_xlat = # ", '%{reply:Class}'" +} + +####################################################################### +# Default profile +####################################################################### +# This is the default profile. It is found in SQL by group membership. +# That means that this profile must be a member of at least one group +# which will contain the corresponding check and reply items. +# This profile will be queried in the authorize section for every user. +# The point is to assign all users a default profile without having to +# manually add each one to a group that will contain the profile. +# The SQL module will also honor the User-Profile attribute. This +# attribute can be set anywhere in the authorize section (ie the users +# file). It is found exactly as the default profile is found. +# If it is set then it will *overwrite* the default profile setting. +# The idea is to select profiles based on checks on the incoming +# packets, not on user group membership. For example: +# -- users file -- +# DEFAULT Service-Type == Outbound-User, User-Profile := "outbound" +# DEFAULT Service-Type == Framed-User, User-Profile := "framed" +# +# By default the default_user_profile is not set +# +# default_user_profile = "DEFAULT" + +####################################################################### +# Open Query +####################################################################### +# This query is run whenever a new connection is opened. +# It is commented out by default. +# +# If you have issues with connections hanging for too long, uncomment +# the next line, and set the timeout in milliseconds. As a general +# rule, if the queries take longer than a second, something is wrong +# with the database. +#open_query = "set statement_timeout to 1000" + +####################################################################### +# NAS Query +####################################################################### +# This query retrieves the radius clients +# +# 0. Row ID (currently unused) +# 1. Name (or IP address) +# 2. Shortname +# 3. Type +# 4. Secret +# 5. Server +####################################################################### + +client_query = "\ + SELECT id, nasname, shortname, type, secret, server \ + FROM ${client_table}" + +####################################################################### +# Authorization Queries +####################################################################### +# These queries compare the check items for the user +# in ${authcheck_table} and setup the reply items in +# ${authreply_table}. You can use any query/tables +# you want, but the return data for each row MUST +# be in the following order: +# +# 0. Row ID (currently unused) +# 1. UserName/GroupName +# 2. Item Attr Name +# 3. Item Attr Value +# 4. Item Attr Operation +####################################################################### + +# +# Use these for case insensitive usernames. WARNING: Slower queries! +# +#authorize_check_query = "\ +# SELECT id, UserName, Attribute, Value, Op \ +# FROM ${authcheck_table} \ +# WHERE LOWER(UserName) = LOWER('%{SQL-User-Name}') \ +# ORDER BY id" + +#authorize_reply_query = "\ +# SELECT id, UserName, Attribute, Value, Op \ +# FROM ${authreply_table} \ +# WHERE LOWER(UserName) = LOWER('%{SQL-User-Name}') \ +# ORDER BY id" + +authorize_check_query = "\ + SELECT id, UserName, Attribute, Value, Op \ + FROM ${authcheck_table} \ + WHERE Username = '%{SQL-User-Name}' \ + ORDER BY id" + +authorize_reply_query = "\ + SELECT id, UserName, Attribute, Value, Op \ + FROM ${authreply_table} \ + WHERE Username = '%{SQL-User-Name}' \ + ORDER BY id" + +# +# Use these for case insensitive usernames. WARNING: Slower queries! +# +#authorize_group_check_query = "\ +# SELECT \ +# ${groupcheck_table}.id, ${groupcheck_table}.GroupName, ${groupcheck_table}.Attribute, \ +# ${groupcheck_table}.Value, ${groupcheck_table}.Op \ +# FROM ${groupcheck_table}, ${usergroup_table} \ +# WHERE LOWER(${usergroup_table}.UserName) = LOWER('%{SQL-User-Name}') \ +# AND ${usergroup_table}.GroupName = ${groupcheck_table}.GroupName \ +# ORDER BY ${groupcheck_table}.id" + +#authorize_group_reply_query = "\ +# SELECT \ +# ${groupreply_table}.id, ${groupreply_table}.GroupName, \ +# ${groupreply_table}.Attribute, ${groupreply_table}.Value, ${groupreply_table}.Op \ +# FROM ${groupreply_table}, ${usergroup_table} \ +# WHERE LOWER(${usergroup_table}.UserName) = LOWER('%{SQL-User-Name}') \ +# AND ${usergroup_table}.GroupName = ${groupreply_table}.GroupName \ +# ORDER BY ${groupreply_table}.id" + +authorize_group_check_query = "\ + SELECT id, GroupName, Attribute, Value, op \ + FROM ${groupcheck_table} \ + WHERE GroupName = '%{${group_attribute}}' \ + ORDER BY id" + +authorize_group_reply_query = "\ + SELECT id, GroupName, Attribute, Value, op \ + FROM ${groupreply_table} \ + WHERE GroupName = '%{${group_attribute}}' \ + ORDER BY id" + +####################################################################### +# Simultaneous Use Checking Queries +####################################################################### +# simul_count_query - query for the number of current connections +# - If this is not defined, no simultaneous use checking +# - will be performed by this module instance +# simul_verify_query - query to return details of current connections for verification +# - Leave blank or commented out to disable verification step +# - Note that the returned field order should not be changed. +####################################################################### + +simul_count_query = "\ + SELECT COUNT(RadAcctId) \ + FROM ${acct_table1} a \ + LEFT OUTER JOIN nasreload n USING (NASIPAddress) \ + WHERE UserName='%{SQL-User-Name}' \ + AND AcctStopTime IS NULL \ + AND (a.AcctStartTime > n.ReloadTime OR n.ReloadTime IS NULL)" + +simul_verify_query = "\ + SELECT RadAcctId, AcctSessionId, UserName, NASIPAddress, NASPortId, FramedIPAddress, CallingStationId, \ + FramedProtocol \ + FROM ${acct_table1} a \ + LEFT OUTER JOIN nasreload n USING (nasipaddress) \ + WHERE UserName='%{SQL-User-Name}' \ + AND AcctStopTime IS NULL \ + AND (a.AcctStartTime > n.reloadtime OR n.reloadtime IS NULL)" + +####################################################################### +# Group Membership Queries +####################################################################### +# group_membership_query - Check user group membership +####################################################################### + +# Use these for case insensitive usernames. WARNING: Slower queries! +#group_membership_query = "\ +# SELECT GroupName \ +# FROM ${usergroup_table} \ +# WHERE LOWER(UserName) = LOWER('%{SQL-User-Name}') \ +# ORDER BY priority" + +group_membership_query = "\ + SELECT GroupName \ + FROM ${usergroup_table} \ + WHERE UserName='%{SQL-User-Name}' \ + ORDER BY priority" + +####################################################################### +# Accounting and Post-Auth Queries +####################################################################### +# These queries insert/update accounting and authentication records. +# The query to use is determined by the value of 'reference'. +# This value is used as a configuration path and should resolve to one +# or more 'query's. If reference points to multiple queries, and a query +# fails, the next query is executed. +# +# Behaviour is identical to the old 1.x/2.x module, except we can now +# fail between N queries, and query selection can be based on any +# combination of attributes, or custom 'Acct-Status-Type' values. +####################################################################### + +accounting { + reference = "%{tolower:type.%{%{Acct-Status-Type}:-%{Request-Processing-Stage}}.query}" + + # Write SQL queries to a logfile. This is potentially useful for bulk inserts + # when used with the rlm_sql_null driver. +# logfile = ${logdir}/accounting.sql + + column_list = "\ + AcctSessionId, \ + AcctUniqueId, \ + UserName, \ + Realm, \ + NASIPAddress, \ + NASPortId, \ + NASPortType, \ + AcctStartTime, \ + AcctUpdateTime, \ + AcctStopTime, \ + AcctSessionTime, \ + AcctAuthentic, \ + ConnectInfo_start, \ + ConnectInfo_Stop, \ + AcctInputOctets, \ + AcctOutputOctets, \ + CalledStationId, \ + CallingStationId, \ + AcctTerminateCause, \ + ServiceType, \ + FramedProtocol, \ + FramedIpAddress, \ + FramedIpv6Address, \ + FramedIpv6Prefix, \ + FramedInterfaceId, \ + DelegatedIpv6Prefix \ + ${..class.column_name}" + + type { + + accounting-on { + + # + # "Bulk update" Accounting-On/Off strategy. + # + # Immediately terminate all sessions associated with a + # given NAS. + # + # Note: If a large number of sessions require closing + # then the bulk update may be take a long time to run + # and lock an excessive number of rows. See the + # strategy below for an alternative approach that does + # not touch the radacct session data. + # + query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctStopTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctSessionTime = (${....event_timestamp_epoch} - EXTRACT(EPOCH FROM(AcctStartTime))), \ + AcctTerminateCause = '%{%{Acct-Terminate-Cause}:-NAS-Reboot}' \ + WHERE AcctStopTime IS NULL \ + AND NASIPAddress= '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND AcctStartTime <= ${....event_timestamp}" + + # + # "Lightweight" Accounting-On/Off strategy. + # + # Record the reload time of the NAS and let the + # administrator actually close the sessions in radacct + # out-of-band, if desired. + # + # Implementation advice, together with a stored + # procedure for closing sessions and a view showing + # the effective stop time of each session is provided + # in process-radacct.sql. + # + # To enable this strategy, just change the previous + # query to "-query", and this one to "query". The + # previous one will be ignored, and this one will be + # enabled. + # + -query = "\ + INSERT INTO nasreload (NASIPAddress, ReloadTime) \ + VALUES ('%{NAS-IP-Address}', ${....event_timestamp}) \ + ON CONFLICT ON (NASIPAddress) \ + DO UPDATE SET \ + ReloadTime = ${....event_timestamp}" + + } + + accounting-off { + query = "${..accounting-on.query}" + } + + # + # Implement the "sql_session_start" policy. + # See raddb/policy.d/accounting for more details. + # + # You also need to fix the other queries as + # documented below. Look for "sql_session_start". + # + post-auth { + query = "\ + INSERT INTO ${....acct_table1} \ + (${...column_list}) \ + VALUES(\ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + NULLIF('%{Realm}', ''), \ + '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}', \ + NULLIF('%{%{NAS-Port-ID}:-%{NAS-Port}}', ''), \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + NULL, \ + 0, \ + '', \ + '%{Connect-Info}', \ + NULL, \ + 0, \ + 0, \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + NULL, \ + '%{Service-Type}', \ + '', \ + NULL, \ + NULL, \ + NULL, \ + NULL, \ + NULL \ + ${....class.reply_xlat})" + + query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctStartTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp}, \ + ConnectInfo_start = '%{Connect-Info}', \ + AcctSessionId = '%{Acct-Session-Id}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}' \ + AND AcctStopTime IS NULL" + } + + start { + query = "\ + INSERT INTO ${....acct_table1} \ + (${...column_list}) \ + VALUES(\ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + NULLIF('%{Realm}', ''), \ + '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}', \ + NULLIF('%{%{NAS-Port-ID}:-%{NAS-Port}}', ''), \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + NULL, \ + 0, \ + '%{Acct-Authentic}', \ + '%{Connect-Info}', \ + NULL, \ + 0, \ + 0, \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + NULL, \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + NULLIF('%{Framed-IP-Address}', '')::inet, \ + NULLIF('%{Framed-IPv6-Address}', '')::inet, \ + NULLIF('%{Framed-IPv6-Prefix}', '')::inet, \ + NULLIF('%{Framed-Interface-Id}', ''), \ + NULLIF('%{Delegated-IPv6-Prefix}', '')::inet \ + ${....class.packet_xlat} ) \ + ON CONFLICT (AcctUniqueId) \ + DO UPDATE \ + SET \ + AcctStartTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp}, \ + ConnectInfo_start = '%{Connect-Info}' \ + WHERE ${....acct_table1}.AcctUniqueId = '%{Acct-Unique-Session-Id}' \ + AND ${....acct_table1}.AcctStopTime IS NULL" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + FramedIPAddress = NULLIF('%{Framed-IP-Address}', '')::inet, \ + FramedIPv6Address = NULLIF('%{Framed-IPv6-Address}', '')::inet, \ + FramedIPv6Prefix = NULLIF('%{Framed-IPv6-Prefix}', '')::inet, \ + FramedInterfaceId = NULLIF('%{Framed-Interface-Id}', ''), \ + DelegatedIPv6Prefix = NULLIF('%{Delegated-IPv6-Prefix}', '')::inet, \ + AcctStartTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp} \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}' \ + AND AcctStopTime IS NULL" + + # and again where we don't have "AND AcctStopTime IS NULL" + query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctStartTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp}, \ + ConnectInfo_start = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}'" + } + + interim-update { + query = "\ + UPDATE ${....acct_table1} \ + SET \ + FramedIPAddress = NULLIF('%{Framed-IP-Address}', '')::inet, \ + FramedIPv6Address = NULLIF('%{Framed-IPv6-Address}', '')::inet, \ + FramedIPv6Prefix = NULLIF('%{Framed-IPv6-Prefix}', '')::inet, \ + FramedInterfaceId = NULLIF('%{Framed-Interface-Id}', ''), \ + DelegatedIPv6Prefix = NULLIF('%{Delegated-IPv6-Prefix}', '')::inet, \ + AcctSessionTime = %{%{Acct-Session-Time}:-NULL}, \ + AcctInterval = (${....event_timestamp_epoch} - EXTRACT(EPOCH FROM (COALESCE(AcctUpdateTime, AcctStartTime)))), \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctInputOctets = (('%{%{Acct-Input-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Input-Octets}:-0}'::bigint), \ + AcctOutputOctets = (('%{%{Acct-Output-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Output-Octets}:-0}'::bigint) \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}' \ + AND AcctStopTime IS NULL" + + query = "\ + INSERT INTO ${....acct_table1} \ + (${...column_list}) \ + VALUES(\ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + NULLIF('%{Realm}', ''), \ + '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}', \ + NULLIF('%{%{NAS-Port-ID}:-%{NAS-Port}}', ''), \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + NULL, \ + %{%{Acct-Session-Time}:-NULL}, \ + '%{Acct-Authentic}', \ + '%{Connect-Info}', \ + NULL, \ + (('%{%{Acct-Input-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Input-Octets}:-0}'::bigint), \ + (('%{%{Acct-Output-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Output-Octets}:-0}'::bigint), \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + NULL, \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + NULLIF('%{Framed-IP-Address}', '')::inet, \ + NULLIF('%{Framed-IPv6-Address}', '')::inet, \ + NULLIF('%{Framed-IPv6-Prefix}', '')::inet, \ + NULLIF('%{Framed-Interface-Id}', ''), \ + NULLIF('%{Delegated-IPv6-Prefix}', '')::inet \ + ${....class.packet_xlat}) \ + ON CONFLICT (AcctUniqueId) \ + DO NOTHING" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + FramedIPAddress = NULLIF('%{Framed-IP-Address}', '')::inet, \ + FramedIPv6Address = NULLIF('%{Framed-IPv6-Address}', '')::inet, \ + FramedIPv6Prefix = NULLIF('%{Framed-IPv6-Prefix}', '')::inet, \ + FramedInterfaceId = NULLIF('%{Framed-Interface-Id}', ''), \ + DelegatedIPv6Prefix = NULLIF('%{Delegated-IPv6-Prefix}', '')::inet, \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctSessionTime = COALESCE(%{%{Acct-Session-Time}:-NULL}, \ + (${....event_timestamp_epoch} - EXTRACT(EPOCH FROM(AcctStartTime)))), \ + AcctInputOctets = (('%{%{Acct-Input-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Input-Octets}:-0}'::bigint), \ + AcctOutputOctets = (('%{%{Acct-Output-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Output-Octets}:-0}'::bigint) \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}' \ + AND AcctStopTime IS NULL" + } + + stop { + query = "\ + UPDATE ${....acct_table2} \ + SET \ + AcctStopTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctSessionTime = COALESCE(%{%{Acct-Session-Time}:-NULL}, \ + (${....event_timestamp_epoch} - EXTRACT(EPOCH FROM(AcctStartTime)))), \ + AcctInputOctets = (('%{%{Acct-Input-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Input-Octets}:-0}'::bigint), \ + AcctOutputOctets = (('%{%{Acct-Output-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Output-Octets}:-0}'::bigint), \ + AcctTerminateCause = '%{Acct-Terminate-Cause}', \ + FramedIPAddress = NULLIF('%{Framed-IP-Address}', '')::inet, \ + FramedIPv6Address = NULLIF('%{Framed-IPv6-Address}', '')::inet, \ + FramedIPv6Prefix = NULLIF('%{Framed-IPv6-Prefix}', '')::inet, \ + FramedInterfaceId = NULLIF('%{Framed-Interface-Id}', ''), \ + DelegatedIPv6Prefix = NULLIF('%{Delegated-IPv6-Prefix}', '')::inet, \ + ConnectInfo_stop = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}' \ + AND AcctStopTime IS NULL" + + query = "\ + INSERT INTO ${....acct_table1} \ + (${...column_list}) \ + VALUES(\ + '%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + NULLIF('%{Realm}', ''), \ + '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}', \ + NULLIF('%{%{NAS-Port-ID}:-%{NAS-Port}}', ''), \ + '%{NAS-Port-Type}', \ + TO_TIMESTAMP(${....event_timestamp_epoch} - %{%{Acct-Session-Time}:-0}), \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + NULLIF('%{Acct-Session-Time}', '')::bigint, \ + '%{Acct-Authentic}', \ + '%{Connect-Info}', \ + NULL, \ + (('%{%{Acct-Input-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Input-Octets}:-0}'::bigint), \ + (('%{%{Acct-Output-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Output-Octets}:-0}'::bigint), \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '%{Acct-Terminate-Cause}', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + NULLIF('%{Framed-IP-Address}', '')::inet, \ + NULLIF('%{Framed-IPv6-Address}', '')::inet, \ + NULLIF('%{Framed-IPv6-Prefix}', '')::inet, \ + NULLIF('%{Framed-Interface-Id}', ''), \ + NULLIF('%{Delegated-IPv6-Prefix}', '')::inet \ + ${....class.packet_xlat}) \ + ON CONFLICT (AcctUniqueId) \ + DO NOTHING" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + FramedIPAddress = NULLIF('%{Framed-IP-Address}', '')::inet, \ + FramedIPv6Address = NULLIF('%{Framed-IPv6-Address}', '')::inet, \ + FramedIPv6Prefix = NULLIF('%{Framed-IPv6-Prefix}', '')::inet, \ + FramedInterfaceId = NULLIF('%{Framed-Interface-Id}', ''), \ + DelegatedIPv6Prefix = NULLIF('%{Delegated-IPv6-Prefix}', '')::inet, \ + AcctStopTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctSessionTime = COALESCE(%{%{Acct-Session-Time}:-NULL}, \ + (${....event_timestamp_epoch} - EXTRACT(EPOCH FROM(AcctStartTime)))), \ + AcctInputOctets = (('%{%{Acct-Input-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Input-Octets}:-0}'::bigint), \ + AcctOutputOctets = (('%{%{Acct-Output-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Output-Octets}:-0}'::bigint), \ + AcctTerminateCause = '%{Acct-Terminate-Cause}', \ + ConnectInfo_stop = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}' \ + AND AcctStopTime IS NULL" + + # and again where we don't have "AND AcctStopTime IS NULL" + query = "\ + UPDATE ${....acct_table2} \ + SET \ + AcctStopTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctSessionTime = COALESCE(%{%{Acct-Session-Time}:-NULL}, \ + (${....event_timestamp_epoch} - EXTRACT(EPOCH FROM(AcctStartTime)))), \ + AcctInputOctets = (('%{%{Acct-Input-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Input-Octets}:-0}'::bigint), \ + AcctOutputOctets = (('%{%{Acct-Output-Gigawords}:-0}'::bigint << 32) + \ + '%{%{Acct-Output-Octets}:-0}'::bigint), \ + AcctTerminateCause = '%{Acct-Terminate-Cause}', \ + FramedIPAddress = NULLIF('%{Framed-IP-Address}', '')::inet, \ + FramedIPv6Address = NULLIF('%{Framed-IPv6-Address}', '')::inet, \ + FramedIPv6Prefix = NULLIF('%{Framed-IPv6-Prefix}', '')::inet, \ + FramedInterfaceId = NULLIF('%{Framed-Interface-Id}', ''), \ + DelegatedIPv6Prefix = NULLIF('%{Delegated-IPv6-Prefix}', '')::inet, \ + ConnectInfo_stop = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}'" + } + + # + # No Acct-Status-Type == ignore the packet + # + accounting { + query = "SELECT true" + } + } +} + + +####################################################################### +# Authentication Logging Queries +####################################################################### +# postauth_query - Insert some info after authentication +####################################################################### + +post-auth { + # Write SQL queries to a logfile. This is potentially useful for bulk inserts + # when used with the rlm_sql_null driver. +# logfile = ${logdir}/post-auth.sql + + query = "\ + INSERT INTO ${..postauth_table} \ + (username, pass, reply, authdate ${..class.column_name}) \ + VALUES(\ + '%{User-Name}', \ + '%{%{User-Password}:-%{Chap-Password}}', \ + '%{reply:Packet-Type}', \ + '%S.%M' \ + ${..class.reply_xlat})" +} diff --git a/raddb/mods-config/sql/main/postgresql/schema.sql b/raddb/mods-config/sql/main/postgresql/schema.sql new file mode 100644 index 0000000..518bc5d --- /dev/null +++ b/raddb/mods-config/sql/main/postgresql/schema.sql @@ -0,0 +1,178 @@ +/* + * $Id$ + * + * PostgreSQL schema for FreeRADIUS + * + */ + +/* + * Table structure for table 'radacct' + * + */ +CREATE TABLE IF NOT EXISTS radacct ( + RadAcctId bigserial PRIMARY KEY, + AcctSessionId text NOT NULL, + AcctUniqueId text NOT NULL UNIQUE, + UserName text, + Realm text, + NASIPAddress inet NOT NULL, + NASPortId text, + NASPortType text, + AcctStartTime timestamp with time zone, + AcctUpdateTime timestamp with time zone, + AcctStopTime timestamp with time zone, + AcctInterval bigint, + AcctSessionTime bigint, + AcctAuthentic text, + ConnectInfo_start text, + ConnectInfo_stop text, + AcctInputOctets bigint, + AcctOutputOctets bigint, + CalledStationId text, + CallingStationId text, + AcctTerminateCause text, + ServiceType text, + FramedProtocol text, + FramedIPAddress inet, + FramedIPv6Address inet, + FramedIPv6Prefix inet, + FramedInterfaceId text, + DelegatedIPv6Prefix inet, + Class text +); +-- This index may be useful.. +-- CREATE UNIQUE INDEX radacct_whoson on radacct (AcctStartTime, nasipaddress); + +-- For use by update-, stop- and simul_* queries +CREATE INDEX radacct_active_session_idx ON radacct (AcctUniqueId) WHERE AcctStopTime IS NULL; + +-- Add if you you regularly have to replay packets +-- CREATE INDEX radacct_session_idx ON radacct (AcctUniqueId); + +-- For backwards compatibility +-- CREATE INDEX radacct_active_user_idx ON radacct (AcctSessionId, UserName, NASIPAddress) WHERE AcctStopTime IS NULL; + +-- For use by onoff- +CREATE INDEX radacct_bulk_close ON radacct (NASIPAddress, AcctStartTime) WHERE AcctStopTime IS NULL; + +-- and for common statistic queries: +CREATE INDEX radacct_start_user_idx ON radacct (AcctStartTime, UserName); + +-- and, optionally +-- CREATE INDEX radacct_stop_user_idx ON radacct (acctStopTime, UserName); + +-- and for Class +CREATE INDEX radacct_calss_idx ON radacct (Class); + + +/* + * Table structure for table 'radcheck' + */ +CREATE TABLE IF NOT EXISTS radcheck ( + id serial PRIMARY KEY, + UserName text NOT NULL DEFAULT '', + Attribute text NOT NULL DEFAULT '', + op VARCHAR(2) NOT NULL DEFAULT '==', + Value text NOT NULL DEFAULT '' +); +create index radcheck_UserName on radcheck (UserName,Attribute); +/* + * Use this index if you use case insensitive queries + */ +-- create index radcheck_UserName_lower on radcheck (lower(UserName),Attribute); + +/* + * Table structure for table 'radgroupcheck' + */ +CREATE TABLE IF NOT EXISTS radgroupcheck ( + id serial PRIMARY KEY, + GroupName text NOT NULL DEFAULT '', + Attribute text NOT NULL DEFAULT '', + op VARCHAR(2) NOT NULL DEFAULT '==', + Value text NOT NULL DEFAULT '' +); +create index radgroupcheck_GroupName on radgroupcheck (GroupName,Attribute); + +/* + * Table structure for table 'radgroupreply' + */ +CREATE TABLE IF NOT EXISTS radgroupreply ( + id serial PRIMARY KEY, + GroupName text NOT NULL DEFAULT '', + Attribute text NOT NULL DEFAULT '', + op VARCHAR(2) NOT NULL DEFAULT '=', + Value text NOT NULL DEFAULT '' +); +create index radgroupreply_GroupName on radgroupreply (GroupName,Attribute); + +/* + * Table structure for table 'radreply' + */ +CREATE TABLE IF NOT EXISTS radreply ( + id serial PRIMARY KEY, + UserName text NOT NULL DEFAULT '', + Attribute text NOT NULL DEFAULT '', + op VARCHAR(2) NOT NULL DEFAULT '=', + Value text NOT NULL DEFAULT '' +); +create index radreply_UserName on radreply (UserName,Attribute); +/* + * Use this index if you use case insensitive queries + */ +-- create index radreply_UserName_lower on radreply (lower(UserName),Attribute); + +/* + * Table structure for table 'radusergroup' + */ +CREATE TABLE IF NOT EXISTS radusergroup ( + id serial PRIMARY KEY, + UserName text NOT NULL DEFAULT '', + GroupName text NOT NULL DEFAULT '', + priority integer NOT NULL DEFAULT 0 +); +create index radusergroup_UserName on radusergroup (UserName); +/* + * Use this index if you use case insensitive queries + */ +-- create index radusergroup_UserName_lower on radusergroup (lower(UserName)); + +-- +-- Table structure for table 'radpostauth' +-- + +CREATE TABLE IF NOT EXISTS radpostauth ( + id bigserial PRIMARY KEY, + username text NOT NULL, + pass text, + reply text, + CalledStationId text, + CallingStationId text, + authdate timestamp with time zone NOT NULL default now(), + Class text +); +CREATE INDEX radpostauth_username_idx ON radpostauth (username); +CREATE INDEX radpostauth_class_idx ON radpostauth (Class); + +/* + * Table structure for table 'nas' + */ +CREATE TABLE IF NOT EXISTS nas ( + id serial PRIMARY KEY, + nasname text NOT NULL, + shortname text NOT NULL, + type text NOT NULL DEFAULT 'other', + ports integer, + secret text NOT NULL, + server text, + community text, + description text +); +create index nas_nasname on nas (nasname); + +/* + * Table structure for table 'nasreload' + */ +CREATE TABLE IF NOT EXISTS nasreload ( + NASIPAddress inet PRIMARY KEY, + ReloadTime timestamp with time zone NOT NULL +); diff --git a/raddb/mods-config/sql/main/postgresql/setup.sql b/raddb/mods-config/sql/main/postgresql/setup.sql new file mode 100644 index 0000000..def5531 --- /dev/null +++ b/raddb/mods-config/sql/main/postgresql/setup.sql @@ -0,0 +1,58 @@ +/* + * setup.sql -- PostgreSQL commands for creating the RADIUS user. + * + * WARNING: You should change 'localhost' and 'radpass' + * to something else. Also update raddb/mods-available/sql + * with the new RADIUS password. + * + * $Id$ + */ + +/* + * Create default administrator for RADIUS + * + */ +CREATE USER radius WITH PASSWORD 'radpass'; + +/* + * The server can read the authorisation data + * + */ +GRANT SELECT ON radcheck TO radius; +GRANT SELECT ON radreply TO radius; +GRANT SELECT ON radusergroup TO radius; +GRANT SELECT ON radgroupcheck TO radius; +GRANT SELECT ON radgroupreply TO radius; + +/* + * The server can write accounting and post-auth data + * + */ +GRANT SELECT, INSERT, UPDATE on radacct TO radius; +GRANT SELECT, INSERT, UPDATE on radpostauth TO radius; + +/* + * The server can read the NAS data + * + */ +GRANT SELECT ON nas TO radius; + +/* + * In the case of the "lightweight accounting-on/off" strategy, the server also + * records NAS reload times + * + */ +GRANT SELECT, INSERT, UPDATE ON nasreload TO radius; + +/* + * Grant permissions on sequences + * + */ +GRANT USAGE, SELECT ON SEQUENCE radcheck_id_seq TO radius; +GRANT USAGE, SELECT ON SEQUENCE radreply_id_seq TO radius; +GRANT USAGE, SELECT ON SEQUENCE radusergroup_id_seq TO radius; +GRANT USAGE, SELECT ON SEQUENCE radgroupcheck_id_seq TO radius; +GRANT USAGE, SELECT ON SEQUENCE radgroupreply_id_seq TO radius; +GRANT USAGE, SELECT ON SEQUENCE radacct_radacctid_seq TO radius; +GRANT USAGE, SELECT ON SEQUENCE radpostauth_id_seq TO radius; +GRANT USAGE, SELECT ON SEQUENCE nas_id_seq TO radius; diff --git a/raddb/mods-config/sql/main/sqlite/process-radacct-close-after-reload.pl b/raddb/mods-config/sql/main/sqlite/process-radacct-close-after-reload.pl new file mode 100755 index 0000000..c43da06 --- /dev/null +++ b/raddb/mods-config/sql/main/sqlite/process-radacct-close-after-reload.pl @@ -0,0 +1,119 @@ +#!/usr/bin/perl -Tw + +# +# main/sqlite/process-radacct-close-after_reload.pl -- Script for +# processing radacct entries to close sessions interrupted by a NAS reload +# +# Requires the DBD::SQLite module: perl-DBD-SQLite (RedHat); libdbd-sqlite3-perl (Debian) +# +# $Id$ +# +# It may be desirable to periodically "close" radacct sessions belonging to a +# reloaded NAS, replicating the "bulk close" Accounting-On/Off behaviour, +# just not in real time. +# +# This script will set radacct.acctstoptime to nasreload.reloadtime, calculate +# the corresponding radacct.acctsessiontime, and set acctterminatecause to +# "NAS reboot" for interrupted sessions. It does so in batches, which avoids a +# single long-lived lock on the table. +# +# It can be invoked as follows: +# +# ./process-radacct-close-after-reload.pl <sqlite_db_file> +# +# Note: This script walks radacct in strides of v_batch_size. It will +# typically skip closed and ongoing sessions at a rate significantly faster +# than 10,000 rows per second and process batched updates faster than 5000 +# orphaned sessions per second. If this isn't fast enough then you should +# really consider using a server-based database for accounting purposes. +# + +use strict; +use DBI; + +# +# Fine for most purposes +# +my $batch_size = 2500; + +if ($#ARGV != 0) { + print "Usage: process-radacct-close-after_reload.pl SQLITE_DB_FILE\n\n"; + exit 1; +} +die "The SQLite database must exist: $ARGV[0]" unless -r $ARGV[0]; + + +my $dbh = DBI->connect("DBI:SQLite:dbname=$ARGV[0]", '', '', { RaiseError => 1 }) or die $DBI::errstr; + +# +# There is no UPDATE ... JOIN/FROM in SQLite, so we have to resort to this +# construction # which does not provide an accurate rows updated count... +# +my $sth_upd = $dbh->prepare(<<'EOF'); + UPDATE radacct + SET + acctstoptime = ( + SELECT COALESCE(acctstoptime, CASE WHEN radacct.acctstarttime < reloadtime THEN reloadtime END) + FROM nasreload WHERE nasipaddress = radacct.nasipaddress + ), + acctsessiontime = ( + SELECT COALESCE(acctsessiontime, + CASE WHEN radacct.acctstoptime IS NULL AND radacct.acctstarttime < reloadtime THEN + CAST((julianday(reloadtime) - julianday(radacct.acctstarttime)) * 86400 AS integer) + END) + FROM nasreload WHERE nasipaddress = radacct.nasipaddress + ), + acctterminatecause = ( + SELECT + CASE WHEN radacct.acctstoptime IS NULL AND radacct.acctstarttime < reloadtime THEN + 'NAS reboot' + ELSE + acctterminatecause + END + FROM nasreload WHERE nasipaddress = radacct.nasipaddress + ) + WHERE + radacctid BETWEEN ? AND ? + AND acctstoptime IS NULL +EOF + +my $sth = $dbh->prepare('SELECT MIN(radacctid), MAX(radacctid) FROM radacct WHERE acctstoptime IS NULL'); +$sth->execute() or die $DBI::errstr; +(my $a, my $m) = $sth->fetchrow_array(); +$sth->finish; + +my $sth_nxt = $dbh->prepare('SELECT radacctid FROM radacct WHERE radacctid > ? ORDER BY radacctid LIMIT ?,1'); + + +my $last = 0; +my $last_report = 0; + +unless ($last) { + + $sth_nxt->execute($a, $batch_size) or die $DBI::errstr; + (my $z) = $sth_nxt->fetchrow_array(); + + unless ($z) { + $z = $m; + $last = 1; + } + + my $rc = $sth_upd->execute($a, $z) or die $DBI::errstr; + + $a = $z + 1; + + # + # Periodically report how far we've got + # + my $now = time(); + if ($last_report != $now || $last) { + print "RadAcctID: $z\n"; + $last_report = $now; + } + +} + +$sth_upd->finish; +$sth_nxt->finish; + +$dbh->disconnect; diff --git a/raddb/mods-config/sql/main/sqlite/process-radacct-new-data-usage-period.sh b/raddb/mods-config/sql/main/sqlite/process-radacct-new-data-usage-period.sh new file mode 100755 index 0000000..0deb391 --- /dev/null +++ b/raddb/mods-config/sql/main/sqlite/process-radacct-new-data-usage-period.sh @@ -0,0 +1,124 @@ +#!/bin/sh +# +# main/sqlite/process-radacct-new-data-usage-period.sh -- Script for +# processing radacct entries to extract daily usage +# +# $Id$ + +# +# See process-radacct-schema.sql for details. +# + +if [ "$#" -ne 1 ]; then + echo "Usage: process-radacct-new-data-usage-period.sh SQLITE_DB_FILE" 2>&1 + exit 1 +fi + +if [ ! -r "$1" ]; then + echo "The SQLite database must exist: $1" 1>&2 + exit 1 +fi + +cat <<EOF | sqlite3 "$1" + + -- + -- SQLite doesn't have a concept of session variables so we fake it. + -- + DROP TABLE IF EXISTS vars; + CREATE TEMPORARY TABLE vars ( + key text, + value text, + PRIMARY KEY (key) + ); + + INSERT INTO vars SELECT 'v_start', COALESCE(DATETIME(MAX(period_end), '+1 seconds'), DATETIME(0, 'unixepoch')) FROM data_usage_by_period; + INSERT INTO vars SELECT 'v_end', CURRENT_TIMESTAMP; + + + -- + -- Make of copy of the sessions that were active during this period to + -- avoid having to execute a potentially long transaction that might hold a + -- global database lock. + -- + DROP TABLE IF EXISTS radacct_sessions; + CREATE TEMPORARY TABLE radacct_sessions ( + username text, + acctstarttime datetime, + acctstoptime datetime, + acctinputoctets bigint, + acctoutputoctets bigint + ); + CREATE INDEX temp.idx_radacct_sessions_username ON radacct_sessions(username); + CREATE INDEX temp.idx_radacct_sessions_acctstoptime ON radacct_sessions(acctstoptime); + + INSERT INTO radacct_sessions + SELECT + username, + acctstarttime, + acctstoptime, + acctinputoctets, + acctoutputoctets + FROM + radacct + WHERE + acctstoptime > (SELECT value FROM vars WHERE key='v_start'); + + INSERT INTO radacct_sessions + SELECT + username, + acctstarttime, + acctstoptime, + acctinputoctets, + acctoutputoctets + FROM + radacct + WHERE + acctstoptime IS NULL; + + + -- + -- Add the data usage for the sessions that were active in the current + -- period to the table. Include all sessions that finished since the start + -- of this period as well as those still ongoing. + -- + INSERT INTO data_usage_by_period (username, period_start, period_end, acctinputoctets, acctoutputoctets) + SELECT + username, + (SELECT value FROM vars WHERE key='v_start'), + (SELECT value FROM vars WHERE key='v_end'), + SUM(acctinputoctets) AS acctinputoctets, + SUM(acctoutputoctets) AS acctoutputoctets + FROM + radacct_sessions + GROUP BY + username + ON CONFLICT(username,period_start) DO UPDATE + SET + acctinputoctets = data_usage_by_period.acctinputoctets + EXCLUDED.acctinputoctets, + acctoutputoctets = data_usage_by_period.acctoutputoctets + EXCLUDED.acctoutputoctets, + period_end = (SELECT value FROM vars WHERE key='v_end'); + + -- + -- Create an open-ended "next period" for all ongoing sessions and carry a + -- negative value of their data usage to avoid double-accounting when we + -- process the next period. Their current data usage has already been + -- allocated to the current and possibly previous periods. + -- + INSERT INTO data_usage_by_period (username, period_start, period_end, acctinputoctets, acctoutputoctets) + SELECT + username, + (SELECT DATETIME(value, '+1 seconds') FROM vars WHERE key='v_end'), + NULL, + 0 - SUM(acctinputoctets), + 0 - SUM(acctoutputoctets) + FROM + radacct_sessions + WHERE + acctstoptime IS NULL + GROUP BY + username; + + DROP TABLE vars; + DROP TABLE radacct_sessions; + +EOF diff --git a/raddb/mods-config/sql/main/sqlite/process-radacct-schema.sql b/raddb/mods-config/sql/main/sqlite/process-radacct-schema.sql new file mode 100644 index 0000000..b429d4c --- /dev/null +++ b/raddb/mods-config/sql/main/sqlite/process-radacct-schema.sql @@ -0,0 +1,95 @@ +# -*- text -*- +# +# main/sqlite/process-radacct.sql -- Schema extensions for processing radacct entries +# +# $Id$ + +-- --------------------------------- +-- - Per-user data usage over time - +-- --------------------------------- +-- +-- An extension to the standard schema to hold per-user data usage statistics +-- for arbitrary periods. +-- +-- The data_usage_by_period table is populated by periodically calling the +-- process-radacct-new-data-usage-period.sh script. +-- +-- This table can be queried in various ways to produce reports of aggregate +-- data use over time. For example, if the refresh script is invoked once per +-- day just after midnight, to produce usage data with daily granularity, then +-- a reasonably accurate monthly bandwidth summary for a given user could be +-- obtained by queriing this table with: +-- +-- SELECT +-- STRFTIME('%Y-%m',CURRENT_TIMESTAMP) AS month, +-- SUM(acctinputoctets)*1.0/1000/1000/1000 AS gb_in, +-- SUM(acctoutputoctets)*1.0/1000/1000/1000 AS gb_out +-- FROM +-- data_usage_by_period +-- WHERE +-- username='bob' AND +-- period_end IS NOT NULL +-- GROUP BY +-- month; +-- +-- 2019-07|5.782279231|50.545664824 +-- 2019-08|4.230543344|48.523096424 +-- 2019-09|4.847360599|48.631835488 +-- 2019-10|6.456763254|51.686231937 +-- 2019-11|6.362537735|52.385710572 +-- 2019-12|4.301524442|50.762240277 +-- 2020-01|5.436280545|49.067775286 +-- +CREATE TABLE data_usage_by_period ( + username text, + period_start datetime, + period_end datetime, + acctinputoctets bigint, + acctoutputoctets bigint, + PRIMARY KEY (username, period_start) +); +CREATE INDEX idx_data_usage_by_period_period_start ON data_usage_by_period(period_start); +CREATE INDEX idx_data_usage_by_period_period_end ON data_usage_by_period(period_end); + + +-- ------------------------------------------------------ +-- - "Lightweight" Accounting-On/Off strategy resources - +-- ------------------------------------------------------ +-- +-- The following resources are for use only when the "lightweight" +-- Accounting-On/Off strategy is enabled in queries.conf. +-- +-- Instead of bulk closing the radacct sessions belonging to a reloaded NAS, +-- this strategy leaves them open and records the NAS reload time in the +-- nasreload table. +-- +-- Where applicable, the onus is on the administator to: +-- +-- * Consider the nas reload times when deriving a list of +-- active/inactive sessions, and when determining the duration of sessions +-- interrupted by a NAS reload. (Refer to the view below.) +-- +-- * Close the affected sessions out of band. (Refer to the +-- process-radacct-close-after_reload.pl script.) +-- +-- The radacct_with_reloads view presents the radacct table with two additional +-- columns: acctstoptime_with_reloads and acctsessiontime_with_reloads +-- +-- Where the session isn't closed (acctstoptime IS NULL), yet it started before +-- the last reload of the NAS (radacct.acctstarttime < nasreload.reloadtime), +-- the derived columns are set based on the reload time of the NAS (effectively +-- the point in time that the session was interrupted.) +-- +CREATE VIEW radacct_with_reloads AS +SELECT + a.*, + COALESCE(a.AcctStopTime, + CASE WHEN a.AcctStartTime < n.ReloadTime THEN n.ReloadTime END + ) AS AcctStopTime_With_Reloads, + COALESCE(a.AcctSessionTime, + CASE WHEN a.AcctStopTime IS NULL AND a.AcctStartTime < n.ReloadTime THEN + CAST((julianday(n.ReloadTime) - julianday(a.AcctStartTime)) * 86400 AS integer) + END + ) AS AcctSessionTime_With_Reloads +FROM radacct a +LEFT OUTER JOIN nasreload n USING (nasipaddress); diff --git a/raddb/mods-config/sql/main/sqlite/queries.conf b/raddb/mods-config/sql/main/sqlite/queries.conf new file mode 100644 index 0000000..35016f4 --- /dev/null +++ b/raddb/mods-config/sql/main/sqlite/queries.conf @@ -0,0 +1,635 @@ +# -*- text -*- +# +# main/sqlite/queries.conf -- SQLite configuration for default schema (schema.sql) +# +# Id: e1e83bf94814ed8be6239977b7bacfed21c0cd6a $ + +# Safe characters list for sql queries. Everything else is replaced +# with their mime-encoded equivalents. +# The default list should be ok +#safe_characters = "@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /" + +####################################################################### +# Query config: Username +####################################################################### +# This is the username that will get substituted, escaped, and added +# as attribute 'SQL-User-Name'. '%{SQL-User-Name}' should be used below +# everywhere a username substitution is needed so you you can be sure +# the username passed from the client is escaped properly. +# +# Uncomment the next line, if you want the sql_user_name to mean: +# +# Use Stripped-User-Name, if it's there. +# Else use User-Name, if it's there, +# Else use hard-coded string "DEFAULT" as the user name. +#sql_user_name = "%{%{Stripped-User-Name}:-%{%{User-Name}:-DEFAULT}}" +# +sql_user_name = "%{User-Name}" + +####################################################################### +# Query config: Event-Timestamp +####################################################################### +# event_timestamp_epoch is the basis for the time inserted into +# accounting records. Typically this will be the Event-Timestamp of the +# accounting request, which is usually provided by a NAS. +# +# Uncomment the next line, if you want the timestamp to be based on the +# request reception time recorded by this server, for example if you +# distrust the provided Event-Timestamp. +#event_timestamp_epoch = "%l" + +event_timestamp_epoch = "%{%{integer:Event-Timestamp}:-%l}" + +# event_timestamp is the SQL snippet for converting an epoch timestamp +# to an SQL date. + +event_timestamp = "${event_timestamp_epoch}" + +# NOTE: Recent SQLite versions allow proper arithmetic with dates +# stored as strings including comparison using an index, so we keep +# these variables differentiated in preparation for switching away from +# integer storage. + +####################################################################### +# Query config: Class attribute +####################################################################### +# +# 3.0.22 and later have a "class" column in the accounting table. +# +# However, we do NOT want to break existing configurations by adding +# the Class attribute to the default queries. If we did that, then +# systems using newer versions of the server would fail, because +# there is no "class" column in their accounting tables. +# +# The solution to that is the following "class" subsection. If your +# database has a "class" column for the various tables, then you can +# uncomment the configuration items here. The queries below will +# then automatically insert the Class attribute into radacct, +# radpostauth, etc. +# +class { + # + # Delete the '#' character from each of the configuration + # items in this section. This change puts the Class + # attribute into the various tables. Leave the double-quoted + # string there, as the value for the configuration item. + # + # See also policy.d/accounting, and the "insert_acct_class" + # policy. You will need to list (or uncomment) + # "insert_acct_class" in the "post-auth" section in order to + # create a Class attribute. + # + column_name = # ", class" + packet_xlat = # ", '%{Class}'" + reply_xlat = # ", '%{reply:Class}'" +} + +####################################################################### +# Default profile +####################################################################### +# This is the default profile. It is found in SQL by group membership. +# That means that this profile must be a member of at least one group +# which will contain the corresponding check and reply items. +# This profile will be queried in the authorize section for every user. +# The point is to assign all users a default profile without having to +# manually add each one to a group that will contain the profile. +# The SQL module will also honor the User-Profile attribute. This +# attribute can be set anywhere in the authorize section (ie the users +# file). It is found exactly as the default profile is found. +# If it is set then it will *overwrite* the default profile setting. +# The idea is to select profiles based on checks on the incoming packets, +# not on user group membership. For example: +# -- users file -- +# DEFAULT Service-Type == Outbound-User, User-Profile := "outbound" +# DEFAULT Service-Type == Framed-User, User-Profile := "framed" +# +# By default the default_user_profile is not set +# +#default_user_profile = "DEFAULT" + +####################################################################### +# NAS Query +####################################################################### +# This query retrieves the radius clients +# +# 0. Row ID (currently unused) +# 1. Name (or IP address) +# 2. Shortname +# 3. Type +# 4. Secret +# 5. Server +####################################################################### + +client_query = "\ + SELECT id, nasname, shortname, type, secret, server \ + FROM ${client_table}" + +####################################################################### +# Authorization Queries +####################################################################### +# These queries compare the check items for the user +# in ${authcheck_table} and setup the reply items in +# ${authreply_table}. You can use any query/tables +# you want, but the return data for each row MUST +# be in the following order: +# +# 0. Row ID (currently unused) +# 1. UserName/GroupName +# 2. Item Attr Name +# 3. Item Attr Value +# 4. Item Attr Operation +####################################################################### + +# +# Use these for case sensitive usernames. +# +#authorize_check_query = "\ +# SELECT id, username, attribute, value, op \ +# FROM ${authcheck_table} \ +# WHERE username = BINARY '%{SQL-User-Name}' \ +# ORDER BY id" + +#authorize_reply_query = "\ +# SELECT id, username, attribute, value, op \ +# FROM ${authreply_table} \ +# WHERE username = BINARY '%{SQL-User-Name}' \ +# ORDER BY id" + +# +# The default queries are case insensitive. (for compatibility with older versions of FreeRADIUS) +# +authorize_check_query = "\ + SELECT id, username, attribute, value, op \ + FROM ${authcheck_table} \ + WHERE username = '%{SQL-User-Name}' \ + ORDER BY id" + +authorize_reply_query = "\ + SELECT id, username, attribute, value, op \ + FROM ${authreply_table} \ + WHERE username = '%{SQL-User-Name}' \ + ORDER BY id" + +# +# Use these for case sensitive usernames. +# +#group_membership_query = "\ +# SELECT groupname \ +# FROM ${usergroup_table} \ +# WHERE username = BINARY '%{SQL-User-Name}' \ +# ORDER BY priority" + +group_membership_query = "\ + SELECT groupname \ + FROM ${usergroup_table} \ + WHERE username = '%{SQL-User-Name}' \ + ORDER BY priority" + +authorize_group_check_query = "\ + SELECT id, groupname, attribute, \ + Value, op \ + FROM ${groupcheck_table} \ + WHERE groupname = '%{${group_attribute}}' \ + ORDER BY id" + +authorize_group_reply_query = "\ + SELECT id, groupname, attribute, \ + value, op \ + FROM ${groupreply_table} \ + WHERE groupname = '%{${group_attribute}}' \ + ORDER BY id" + +####################################################################### +# Simultaneous Use Checking Queries +####################################################################### +# simul_count_query - query for the number of current connections +# - If this is not defined, no simultaneous use checking +# - will be performed by this module instance +# simul_verify_query - query to return details of current connections +# for verification +# - Leave blank or commented out to disable verification step +# - Note that the returned field order should not be changed. +####################################################################### + +simul_count_query = "\ + SELECT COUNT(*) \ + FROM ${acct_table1} a \ + LEFT OUTER JOIN nasreload n USING (nasipaddress) \ + WHERE username = '%{SQL-User-Name}' \ + AND acctstoptime IS NULL \ + AND (a.acctstarttime > n.reloadtime OR n.reloadtime IS NULL)" + +simul_verify_query = "\ + SELECT radacctid, acctsessionid, username, nasipaddress, nasportid, framedipaddress, \ + callingstationid, framedprotocol \ + FROM ${acct_table1} a \ + LEFT OUTER JOIN nasreload n USING (nasipaddress) \ + WHERE username = '%{${group_attribute}}' \ + AND acctstoptime IS NULL \ + AND (a.acctstarttime > n.reloadtime OR n.reloadtime IS NULL)" + +####################################################################### +# Accounting and Post-Auth Queries +####################################################################### +# These queries insert/update accounting and authentication records. +# The query to use is determined by the value of 'reference'. +# This value is used as a configuration path and should resolve to one +# or more 'query's. If reference points to multiple queries, and a query +# fails, the next query is executed. +# +# Behaviour is identical to the old 1.x/2.x module, except we can now +# fail between N queries, and query selection can be based on any +# combination of attributes, or custom 'Acct-Status-Type' values. +####################################################################### +accounting { + reference = "%{tolower:type.%{%{Acct-Status-Type}:-%{Request-Processing-Stage}}.query}" + + # Write SQL queries to a logfile. This is potentially useful for bulk inserts + # when used with the rlm_sql_null driver. +# logfile = ${logdir}/accounting.sql + + column_list = "\ + acctsessionid, \ + acctuniqueid, \ + username, \ + realm, \ + nasipaddress, \ + nasportid, \ + nasporttype, \ + acctstarttime, \ + acctupdatetime, \ + acctstoptime, \ + acctsessiontime, \ + acctauthentic, \ + connectinfo_start, \ + connectinfo_stop, \ + acctinputoctets, \ + acctoutputoctets, \ + calledstationid, \ + callingstationid, \ + acctterminatecause, \ + servicetype, \ + framedprotocol, \ + framedipaddress, \ + framedipv6address, \ + framedipv6prefix, \ + framedinterfaceid, \ + delegatedipv6prefix \ + ${..class.column_name}" + + type { + accounting-on { + + # + # "Bulk update" Accounting-On/Off strategy. + # + # Immediately terminate all sessions associated with a + # given NAS. + # + # Note: If a large number of sessions require closing + # then the bulk update may be take a long time to run + # and lock an excessive number of rows. See the + # strategy below for an alternative approach that does + # not touch the radacct session data. + # + query = "\ + UPDATE ${....acct_table1} \ + SET \ + acctstoptime = ${....event_timestamp}, \ + acctsessiontime = \ + (${....event_timestamp_epoch} \ + - acctstarttime), \ + acctterminatecause = '%{Acct-Terminate-Cause}' \ + WHERE acctstoptime IS NULL \ + AND nasipaddress = '%{NAS-IP-Address}' \ + AND acctstarttime <= ${....event_timestamp}" + + # + # "Lightweight" Accounting-On/Off strategy. + # + # Record the reload time of the NAS and let the + # administrator actually close the sessions in radacct + # out-of-band, if desired. + # + # Implementation advice, together with a stored + # procedure for closing sessions and a view showing + # the effective stop time of each session is provided + # in process-radacct.sql. + # + # To enable this strategy, just change the previous + # query to "-query", and this one to "query". The + # previous one will be ignored, and this one will be + # enabled. + # + -query = "\ + INSERT OR REPLACE INTO nasreload (nasipaddress, reloadtime) \ + VALUES ('%{NAS-IP-Address}', ${....event_timestamp})" + + } + + accounting-off { + query = "${..accounting-on.query}" + } + + start { + # + # Insert a new record into the sessions table + # + query = "\ + INSERT INTO ${....acct_table1} \ + (${...column_list}) \ + VALUES \ + ('%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + NULL, \ + '0', \ + '%{Acct-Authentic}', \ + '%{Connect-Info}', \ + '', \ + '0', \ + '0', \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + framedipaddress = '%{Framed-IP-Address}', \ + framedipv6address = '%{Framed-IPv6-Address}', \ + framedipv6prefix = '%{Framed-IPv6-Prefix}', \ + framedinterfaceid = '%{Framed-Interface-Id}', \ + delegatedipv6prefix = '%{Delegated-IPv6-Prefix}', \ + AcctStartTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp} \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime IS NULL" + + # + # Key constraints prevented us from inserting a new session, + # use the alternate query to update an existing session. + # + query = "\ + UPDATE ${....acct_table1} SET \ + acctstarttime = ${....event_timestamp}, \ + acctupdatetime = ${....event_timestamp}, \ + connectinfo_start = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}'" + } + + interim-update { + # + # Update an existing session and calculate the interval + # between the last data we received for the session and this + # update. This can be used to find stale sessions. + # + query = "\ + UPDATE ${....acct_table1} \ + SET \ + acctupdatetime = ${....event_timestamp}, \ + acctinterval = 0, \ + framedipaddress = '%{Framed-IP-Address}', \ + framedipv6address = '%{Framed-IPv6-Address}', \ + framedipv6prefix = '%{Framed-IPv6-Prefix}', \ + framedinterfaceid = '%{Framed-Interface-Id}', \ + delegatedipv6prefix = '%{Delegated-IPv6-Prefix}', \ + acctsessiontime = %{%{Acct-Session-Time}:-NULL}, \ + acctinputoctets = %{%{Acct-Input-Gigawords}:-0} \ + << 32 | %{%{Acct-Input-Octets}:-0}, \ + acctoutputoctets = %{%{Acct-Output-Gigawords}:-0} \ + << 32 | %{%{Acct-Output-Octets}:-0} \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}'" + + # + # The update condition matched no existing sessions. Use + # the values provided in the update to create a new session. + # + query = "\ + INSERT INTO ${....acct_table1} \ + (${...column_list}) \ + VALUES \ + ('%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + (${....event_timestamp_epoch} - %{%{Acct-Session-Time}:-0}), \ + ${....event_timestamp}, \ + NULL, \ + %{%{Acct-Session-Time}:-NULL}, \ + '%{Acct-Authentic}', \ + '%{Connect-Info}', \ + '', \ + %{%{Acct-Input-Gigawords}:-0} << 32 | \ + %{%{Acct-Input-Octets}:-0}, \ + %{%{Acct-Output-Gigawords}:-0} << 32 | \ + %{%{Acct-Output-Octets}:-0}, \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + framedipaddress = '%{Framed-IP-Address}', \ + framedipv6address = '%{Framed-IPv6-Address}', \ + framedipv6prefix = '%{Framed-IPv6-Prefix}', \ + framedinterfaceid = '%{Framed-Interface-Id}', \ + delegatedipv6prefix = '%{Delegated-IPv6-Prefix}', \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctSessionTime = %{%{Acct-Session-Time}:-NULL}, \ + AcctInputOctets = '%{%{Acct-Input-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Input-Octets}:-0}', \ + AcctOutputOctets = '%{%{Acct-Output-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Output-Octets}:-0}' \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime IS NULL" + + } + + stop { + # + # Session has terminated, update the stop time and statistics. + # + query = "\ + UPDATE ${....acct_table2} SET \ + acctstoptime = ${....event_timestamp}, \ + acctsessiontime = %{%{Acct-Session-Time}:-NULL}, \ + acctinputoctets = %{%{Acct-Input-Gigawords}:-0} \ + << 32 | %{%{Acct-Input-Octets}:-0}, \ + acctoutputoctets = %{%{Acct-Output-Gigawords}:-0} \ + << 32 | %{%{Acct-Output-Octets}:-0}, \ + acctterminatecause = '%{Acct-Terminate-Cause}', \ + connectinfo_stop = '%{Connect-Info}' \ + WHERE AcctUniqueId = '%{Acct-Unique-Session-Id}'" + + # + # The update condition matched no existing sessions. Use + # the values provided in the update to create a new session. + # + query = "\ + INSERT INTO ${....acct_table2} \ + (${...column_list}) \ + VALUES \ + ('%{Acct-Session-Id}', \ + '%{Acct-Unique-Session-Id}', \ + '%{SQL-User-Name}', \ + '%{Realm}', \ + '%{NAS-IP-Address}', \ + '%{%{NAS-Port-ID}:-%{NAS-Port}}', \ + '%{NAS-Port-Type}', \ + (${....event_timestamp_epoch} - %{%{Acct-Session-Time}:-0}), \ + ${....event_timestamp}, \ + ${....event_timestamp}, \ + %{%{Acct-Session-Time}:-NULL}, \ + '%{Acct-Authentic}', \ + '', \ + '%{Connect-Info}', \ + %{%{Acct-Input-Gigawords}:-0} << 32 | \ + %{%{Acct-Input-Octets}:-0}, \ + %{%{Acct-Output-Gigawords}:-0} << 32 | \ + %{%{Acct-Output-Octets}:-0}, \ + '%{Called-Station-Id}', \ + '%{Calling-Station-Id}', \ + '%{Acct-Terminate-Cause}', \ + '%{Service-Type}', \ + '%{Framed-Protocol}', \ + '%{Framed-IP-Address}', \ + '%{Framed-IPv6-Address}', \ + '%{Framed-IPv6-Prefix}', \ + '%{Framed-Interface-Id}', \ + '%{Delegated-IPv6-Prefix}' \ + ${....class.packet_xlat})" + + # + # When using "sql_session_start", you should comment out + # the previous query, and enable this one. + # + # Just change the previous query to "-query", + # and this one to "query". The previous one + # will be ignored, and this one will be + # enabled. + # + -query = "\ + UPDATE ${....acct_table1} \ + SET \ + AcctSessionId = '%{Acct-Session-Id}', \ + AcctUniqueId = '%{Acct-Unique-Session-Id}', \ + AcctAuthentic = '%{Acct-Authentic}', \ + ConnectInfo_start = '%{Connect-Info}', \ + ServiceType = '%{Service-Type}', \ + FramedProtocol = '%{Framed-Protocol}', \ + framedipaddress = '%{Framed-IP-Address}', \ + framedipv6address = '%{Framed-IPv6-Address}', \ + framedipv6prefix = '%{Framed-IPv6-Prefix}', \ + framedinterfaceid = '%{Framed-Interface-Id}', \ + delegatedipv6prefix = '%{Delegated-IPv6-Prefix}', \ + AcctStopTime = ${....event_timestamp}, \ + AcctUpdateTime = ${....event_timestamp}, \ + AcctSessionTime = %{%{Acct-Session-Time}:-NULL}, \ + AcctInputOctets = '%{%{Acct-Input-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Input-Octets}:-0}', \ + AcctOutputOctets = '%{%{Acct-Output-Gigawords}:-0}' \ + << 32 | '%{%{Acct-Output-Octets}:-0}', \ + AcctTerminateCause = '%{Acct-Terminate-Cause}', \ + ConnectInfo_stop = '%{Connect-Info}' \ + WHERE UserName = '%{SQL-User-Name}' \ + AND NASIPAddress = '%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}}' \ + AND NASPortId = '%{%{NAS-Port-ID}:-%{NAS-Port}}' \ + AND NASPortType = '%{NAS-Port-Type}' \ + AND AcctStopTime IS NULL" + + } + + + # + # No Acct-Status-Type == ignore the packet + # + accounting { + query = "SELECT true" + } + } +} + +####################################################################### +# Authentication Logging Queries +####################################################################### +# postauth_query - Insert some info after authentication +####################################################################### + +post-auth { + # Write SQL queries to a logfile. This is potentially useful for bulk inserts + # when used with the rlm_sql_null driver. +# logfile = ${logdir}/post-auth.sql + + query = "\ + INSERT INTO ${..postauth_table} \ + (username, pass, reply, authdate ${..class.column_name}) \ + VALUES ( \ + '%{SQL-User-Name}', \ + '%{%{User-Password}:-%{Chap-Password}}', \ + '%{reply:Packet-Type}', \ + '%S.%M' \ + ${..class.reply_xlat})" +} diff --git a/raddb/mods-config/sql/main/sqlite/schema.sql b/raddb/mods-config/sql/main/sqlite/schema.sql new file mode 100644 index 0000000..4625a58 --- /dev/null +++ b/raddb/mods-config/sql/main/sqlite/schema.sql @@ -0,0 +1,164 @@ +----------------------------------------------------------------------------- +-- $Id$ -- +-- -- +-- schema.sql rlm_sql - FreeRADIUS SQLite Module -- +-- -- +-- Database schema for SQLite rlm_sql module -- +-- -- +----------------------------------------------------------------------------- + +-- +-- Table structure for table 'radacct' +-- +CREATE TABLE IF NOT EXISTS radacct ( + radacctid INTEGER PRIMARY KEY AUTOINCREMENT, + acctsessionid varchar(64) NOT NULL default '', + acctuniqueid varchar(32) NOT NULL default '', + username varchar(64) NOT NULL default '', + realm varchar(64) default '', + nasipaddress varchar(15) NOT NULL default '', + nasportid varchar(32) default NULL, + nasporttype varchar(32) default NULL, + acctstarttime datetime NULL default NULL, + acctupdatetime datetime NULL default NULL, + acctstoptime datetime NULL default NULL, + acctinterval int(12) default NULL, + acctsessiontime int(12) default NULL, + acctauthentic varchar(32) default NULL, + connectinfo_start varchar(128) default NULL, + connectinfo_stop varchar(128) default NULL, + acctinputoctets bigint(20) default NULL, + acctoutputoctets bigint(20) default NULL, + calledstationid varchar(50) NOT NULL default '', + callingstationid varchar(50) NOT NULL default '', + acctterminatecause varchar(32) NOT NULL default '', + servicetype varchar(32) default NULL, + framedprotocol varchar(32) default NULL, + framedipaddress varchar(15) NOT NULL default '', + framedipv6address varchar(45) NOT NULL default '', + framedipv6prefix varchar(45) NOT NULL default '', + framedinterfaceid varchar(44) NOT NULL default '', + delegatedipv6prefix varchar(45) NOT NULL default '', + class varchar(64) default NULL +); + +-- +-- You might not need all of these indexes. It should be safe to +-- delete indexes you do not use. For example, if you're not using +-- IPv6, you can delete the indexes on IPv6 attributes. +-- +-- You MUST however leave the indexes needed by the server, which +-- include username, acctstoptime, nasipaddress, acctstarttime, and +-- acctuniqueid. +-- +CREATE UNIQUE INDEX acctuniqueid ON radacct(acctuniqueid); +CREATE INDEX username ON radacct(username); +CREATE INDEX framedipaddress ON radacct (framedipaddress); +CREATE INDEX framedipv6address ON radacct (framedipv6address); +CREATE INDEX framedipv6prefix ON radacct (framedipv6prefix); +CREATE INDEX framedinterfaceid ON radacct (framedinterfaceid); +CREATE INDEX delegatedipv6prefix ON radacct (delegatedipv6prefix); +CREATE INDEX acctsessionid ON radacct(acctsessionid); +CREATE INDEX acctsessiontime ON radacct(acctsessiontime); +CREATE INDEX acctstarttime ON radacct(acctstarttime); +CREATE INDEX acctinterval ON radacct(acctinterval); +CREATE INDEX acctstoptime ON radacct(acctstoptime); +CREATE INDEX nasipaddress ON radacct(nasipaddress); +CREATE INDEX class ON radacct(class); + +-- +-- Table structure for table 'radcheck' +-- +CREATE TABLE IF NOT EXISTS radcheck ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '==', + value varchar(253) NOT NULL default '' +); +CREATE INDEX check_username ON radcheck(username); + +-- +-- Table structure for table 'radgroupcheck' +-- +CREATE TABLE IF NOT EXISTS radgroupcheck ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + groupname varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '==', + value varchar(253) NOT NULL default '' +); +CREATE INDEX check_groupname ON radgroupcheck(groupname); + +-- +-- Table structure for table 'radgroupreply' +-- +CREATE TABLE IF NOT EXISTS radgroupreply ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + groupname varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '' +); +CREATE INDEX reply_groupname ON radgroupreply(groupname); + +-- +-- Table structure for table 'radreply' +-- +CREATE TABLE IF NOT EXISTS radreply ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username varchar(64) NOT NULL default '', + attribute varchar(64) NOT NULL default '', + op char(2) NOT NULL DEFAULT '=', + value varchar(253) NOT NULL default '' +); +CREATE INDEX reply_username ON radreply(username); + +-- +-- Table structure for table 'radusergroup' +-- +CREATE TABLE IF NOT EXISTS radusergroup ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username varchar(64) NOT NULL default '', + groupname varchar(64) NOT NULL default '', + priority int(11) NOT NULL default '1' +); +CREATE INDEX usergroup_username ON radusergroup(username); + +-- +-- Table structure for table 'radpostauth' +-- +CREATE TABLE IF NOT EXISTS radpostauth ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username varchar(64) NOT NULL default '', + pass varchar(64) NOT NULL default '', + reply varchar(32) NOT NULL default '', + authdate timestamp NOT NULL, + class varchar(64) default NULL +); +CREATE INDEX radpostauth_username ON radpostauth(username); +CREATE INDEX radpostauth_class ON radpostauth(class); + +-- +-- Table structure for table 'nas' +-- +CREATE TABLE IF NOT EXISTS nas ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + nasname varchar(128) NOT NULL, + shortname varchar(32), + type varchar(30) DEFAULT 'other', + ports int(5), + secret varchar(60) DEFAULT 'secret' NOT NULL, + server varchar(64), + community varchar(50), + description varchar(200) DEFAULT 'RADIUS Client' +); +CREATE INDEX nasname ON nas(nasname); + +-- +-- Table structure for table 'nasreload' +-- +CREATE TABLE IF NOT EXISTS nasreload ( + nasipaddress varchar(15) PRIMARY KEY, + reloadtime datetime NOT NULL +); diff --git a/raddb/mods-config/sql/moonshot-targeted-ids/mysql/queries.conf b/raddb/mods-config/sql/moonshot-targeted-ids/mysql/queries.conf new file mode 100644 index 0000000..68306db --- /dev/null +++ b/raddb/mods-config/sql/moonshot-targeted-ids/mysql/queries.conf @@ -0,0 +1,15 @@ +# -*- text -*- +# +# moonshot-targeted-ids/mysql/queries.conf -- Queries to update a MySQL Moonshot-Targeted-Ids table. +# +# $Id$ + +post-auth { + # Query to store the Moonshot-*-TargetedId + query = "\ + INSERT IGNORE INTO ${..moonshot_tid_table} \ + (gss_acceptor, namespace, username, targeted_id) \ + VALUES \ + ('%{control:Moonshot-MSTID-GSS-Acceptor}', '%{control:Moonshot-MSTID-Namespace}', \ + '%{tolower:%{User-Name}}', '%{control:Moonshot-MSTID-TargetedId}')" +} diff --git a/raddb/mods-config/sql/moonshot-targeted-ids/mysql/schema.sql b/raddb/mods-config/sql/moonshot-targeted-ids/mysql/schema.sql new file mode 100644 index 0000000..8a33dc1 --- /dev/null +++ b/raddb/mods-config/sql/moonshot-targeted-ids/mysql/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE `moonshot_targeted_ids` ( + `gss_acceptor` varchar(254) NOT NULL default '', + `namespace` varchar(36) NOT NULL default '', + `username` varchar(64) NOT NULL default '', + `targeted_id` varchar(128) NOT NULL default '', + `creationdate` timestamp NOT NULL default CURRENT_TIMESTAMP, + PRIMARY KEY (`username`,`gss_acceptor`,`namespace`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8; diff --git a/raddb/mods-config/sql/moonshot-targeted-ids/postgresql/queries.conf b/raddb/mods-config/sql/moonshot-targeted-ids/postgresql/queries.conf new file mode 100644 index 0000000..f757a87 --- /dev/null +++ b/raddb/mods-config/sql/moonshot-targeted-ids/postgresql/queries.conf @@ -0,0 +1,15 @@ +# -*- text -*- +# +# moonshot-targeted-ids/postgresql/queries.conf -- Queries to update a PostgreSQL Moonshot-*-Targeted-Ids table. +# +# $Id$ + +post-auth { + # Query to store the Moonshot-*-TargetedId + query = "\ + INSERT INTO ${..moonshot_tid_table} \ + (gss_acceptor, namespace, username, targeted_id) \ + VALUES \ + ('%{control:Moonshot-MSTID-GSS-Acceptor}', '%{control:Moonshot-MSTID-Namespace}', \ + '%{tolower:%{User-Name}}', '%{control:Moonshot-MSTID-TargetedId}')" +} diff --git a/raddb/mods-config/sql/moonshot-targeted-ids/postgresql/schema.sql b/raddb/mods-config/sql/moonshot-targeted-ids/postgresql/schema.sql new file mode 100644 index 0000000..649c627 --- /dev/null +++ b/raddb/mods-config/sql/moonshot-targeted-ids/postgresql/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE moonshot_targeted_ids ( + gss_acceptor varchar(254) NOT NULL DEFAULT '', + namespace varchar(36) NOT NULL DEFAULT '', + username varchar(64) NOT NULL DEFAULT '', + targeted_id varchar(128) NOT NULL DEFAULT '', + creationdate TIMESTAMP with time zone NOT NULL default 'now()', + PRIMARY KEY (username, gss_acceptor, namespace) +); diff --git a/raddb/mods-config/sql/moonshot-targeted-ids/sqlite/queries.conf b/raddb/mods-config/sql/moonshot-targeted-ids/sqlite/queries.conf new file mode 100644 index 0000000..8cdb803 --- /dev/null +++ b/raddb/mods-config/sql/moonshot-targeted-ids/sqlite/queries.conf @@ -0,0 +1,15 @@ +# -*- text -*- +# +# moonshot-targeted-ids/sqlite/queries.conf -- Queries to update a sqlite Moonshot-*-Targeted-Ids table. +# +# $Id$ + +post-auth { + # Query to store the Moonshot-*-TargetedId + query = "\ + INSERT INTO ${..moonshot_tid_table} \ + (gss_acceptor, namespace, username, targeted_id) \ + VALUES \ + ('%{control:Moonshot-MSTID-GSS-Acceptor}', '%{control:Moonshot-MSTID-Namespace}', \ + '%{tolower:%{User-Name}}', '%{control:Moonshot-MSTID-TargetedId}')" +} diff --git a/raddb/mods-config/sql/moonshot-targeted-ids/sqlite/schema.sql b/raddb/mods-config/sql/moonshot-targeted-ids/sqlite/schema.sql new file mode 100644 index 0000000..71195ad --- /dev/null +++ b/raddb/mods-config/sql/moonshot-targeted-ids/sqlite/schema.sql @@ -0,0 +1,8 @@ +CREATE TABLE `moonshot_targeted_ids` ( + `gss_acceptor` varchar(254) NOT NULL default '', + `namespace` varchar(36) NOT NULL default '', + `username` varchar(64) NOT NULL default '', + `targeted_id` varchar(128) NOT NULL default '', + `creationdate` timestamp NOT NULL default CURRENT_TIMESTAMP, + PRIMARY KEY (`username`,`gss_acceptor`,`namespace`) +); diff --git a/raddb/mods-config/unbound/default.conf b/raddb/mods-config/unbound/default.conf new file mode 100644 index 0000000..9aac368 --- /dev/null +++ b/raddb/mods-config/unbound/default.conf @@ -0,0 +1,2 @@ +server: + num-threads: 2 diff --git a/raddb/panic.gdb b/raddb/panic.gdb new file mode 100644 index 0000000..3ae253a --- /dev/null +++ b/raddb/panic.gdb @@ -0,0 +1,4 @@ +info locals +info args +thread apply all bt full +quit diff --git a/raddb/policy.d/abfab-tr b/raddb/policy.d/abfab-tr new file mode 100644 index 0000000..3a08853 --- /dev/null +++ b/raddb/policy.d/abfab-tr @@ -0,0 +1,106 @@ +# +# ABFAB Trust router policies. +# +# $Id$ +# + + +# +# Verify rp parameters +# +psk_authorize { + if (&TLS-PSK-Identity) { + # TODO: may need to check trust-router-apc as well + if ("%{psksql:select distinct keyid from authorizations_keys where keyid = '%{tls-psk-identity}' and '%{trust-router-coi}' like coi and '%{gss-acceptor-realm-name}' like acceptor_realm and '%{gss-acceptor-host-name}' like hostname;}") { + # do things here + } + else { + update reply { + Reply-Message = "RP not authorized for this ABFAB request" + } + reject + } + } +} + +abfab_client_check { + # check that GSS-Acceptor-Host-Name is correct + if ("%{client:gss_acceptor_host_name}") { + if (&request:GSS-Acceptor-Host-Name) { + if (&request:GSS-Acceptor-Host-Name != "%{client:gss_acceptor_host_name}") { + update reply { + Reply-Message = "GSS-Acceptor-Host-Name incorrect" + } + reject + } + } + else { + # set GSS-Acceptor-Host-Name if it is not set by the mechanism + # but it is defined in the client configuration + update request { + GSS-Acceptor-Host-Name = "%{client:gss_acceptor_host_name}" + } + } + } + + # set Trust-Router-COI attribute from the client configuration + if ("%{client:trust_router_coi}") { + update request { + Trust-Router-COI := "%{client:trust_router_coi}" + } + } + + # set GSS-Acceptor-Realm-Name attribute from the client configuration + if ("%{client:gss_acceptor_realm_name}") { + update request { + GSS-Acceptor-Realm-Name := "%{client:gss_acceptor_realm_name}" + } + } + + # set GSS-Acceptor-Service-Name attribute from the client configuration + if ("%{client:gss_acceptor_service_name}") { + update request { + GSS-Acceptor-Service-Name = "%{client:gss_acceptor_service_name}" + } + } + +} + +# A policy which is used to validate channel-bindings. +# +abfab_channel_bindings { + if (&GSS-Acceptor-Service-Name && (&outer.request:GSS-Acceptor-Service-Name != &GSS-Acceptor-Service-Name)) { + reject + } + + if (&GSS-Acceptor-Host-Name && &outer.request:GSS-Acceptor-Host-Name != &GSS-Acceptor-Host-Name ) { + reject + } + + if (&GSS-Acceptor-Realm-Name && &outer.request:GSS-Acceptor-Realm-Name != &GSS-Acceptor-Realm-Name ) { + reject + } + + if (&GSS-Acceptor-Service-Name || &GSS-Acceptor-Realm-Name || &GSS-Acceptor-Host-Name) { + update control { + &Chbind-Response-Code := success + } + + # + # ACK the attributes in the request. + # + # If any one of these attributes don't exist in the request, + # then they won't be copied to the reply. + # + update reply { + &GSS-Acceptor-Service-Name = &GSS-Acceptor-Service-Name + &GSS-Acceptor-Host-Name = &GSS-Acceptor-Host-Name + &GSS-Acceptor-Realm-Name = &GSS-Acceptor-Realm-Name + } + } + + # + # Return "handled" so that the "authenticate" section isn't used. + # + handled +} diff --git a/raddb/policy.d/accounting b/raddb/policy.d/accounting new file mode 100644 index 0000000..6199d37 --- /dev/null +++ b/raddb/policy.d/accounting @@ -0,0 +1,127 @@ +# We check for this prefix to determine whether the class value was +# generated by this server. It should be changed so that it is +# globally unique. +class_value_prefix = 'ai:' + +# +# Replacement for the old rlm_acct_unique module +# +acct_unique { + # + # If we have a class attribute in the format + # 'auth_id:[0-9a-f]{32}' it'll have a local value + # (defined by insert_acct_class), this ensures + # uniqueness and suitability. + # + # We could just use the Class attribute as + # Acct-Unique-Session-Id, but this may cause problems + # with NAS that carry Class values across between + # multiple linked sessions. So we rehash class with + # Acct-Session-ID to provide a truely unique session + # identifier. + # + # Using a Class/Session-ID combination is more robust + # than using elements in the Accounting-Request, + # which may be subject to change, such as + # NAS-IP-Address, Client-IP-Address and + # NAS-Port-ID/NAS-Port. + # + # This policy should ensure that session data is not + # affected if NAS IP addresses change, or the client + # roams to a different 'port' whilst maintaining its + # initial authentication session (Common in a + # wireless environment). + # + update request { + &Tmp-String-9 := "${policy.class_value_prefix}" + } + + if (("%{hex:&Class}" =~ /^%{hex:&Tmp-String-9}/) && \ + ("%{string:&Class}" =~ /^${policy.class_value_prefix}([0-9a-f]{32})/i)) { + update request { + &Acct-Unique-Session-Id := "%{md5:%{1},%{Acct-Session-ID}}" + } + } + + # + # Not All devices respect RFC 2865 when dealing with + # the class attribute, so be prepared to use the + # older style of hashing scheme if a class attribute + # is not included + # + else { + update request { + &Acct-Unique-Session-Id := "%{md5:%{User-Name},%{Acct-Session-ID},%{%{NAS-IPv6-Address}:-%{NAS-IP-Address}},%{NAS-Identifier},%{NAS-Port-ID},%{NAS-Port}}" + } + } + + update request { + &Tmp-String-9 !* ANY + } +} + +# +# Insert a (hopefully unique) value into class +# +insert_acct_class { + update reply { + &Class = "${policy.class_value_prefix}%{md5:%t,%{Packet-Src-Port},%{%{Packet-Src-IP-Address}:-%{Packet-Src-IPv6-Address}},%{NAS-IP-Address},%{Calling-Station-ID},%{User-Name},%{session-state:User-Name} }" + } +} + +# +# Merges Acct-[Input|Output]-Octets and Acct-[Input|Output]-Gigawords into Acct-[Input|Output]-Octets64 +# +# If the &Attr-Foo doesn't exist, it's value is taken as zero. +# +acct_counters64.preacct { + update request { + &Acct-Input-Octets64 = "%{expr:(&Acct-Input-Gigawords << 32) | &Acct-Input-Octets}" + &Acct-Output-Octets64 = "%{expr:(&Acct-Output-Gigawords << 32) | &Acct-Output-Octets}" + } +} + +# +# There is a delay between sending the Access-Accept and receiving +# the corresponding Accounting-Request "start" packet. This delay +# can be leveraged by a user to bypass Simultaneous-Use checks. +# +# The user can start up multiple sessions at the same time. When +# that happens, both Simultaneous-Use checks are performed before any +# Accounting-Request packet is received. Both Simultaneous-Use +# checks will result in "no user session" in the radacct table, and +# both sessions will be allowed. At some point later in time, the +# Accounting-Request packets are received. But by then it's too +# late. +# +# The solution is to insert a temporary session into the "radacct" +# table, during the "post-auth" section. This is done by +# uncommenting the "sql_session_start" entry in +# sites-enabled/default. Then, reading +# raddb/mods-config/sql/main/*/queries.conf, and looking for the +# "sql_session_start" comments. Follow the instructions there to +# finalize the configuration. +# +# The server will then create a temporary entry in "radacct" before +# it returns the Access-Request. Any other Access-Request which is +# received at the same time will then have it's Simultaneous-Use +# check see that entry, and will be rejected. +# +# Subsequent Accounting-Request packets for the first session will +# then UPDATE (not INSERT) the data for the session. +# +# There is still a small race condition as the Simultaneous-Use +# checks are not done at the same time as updating radacct. But the +# window of opportunity is much smaller. i.e. milliseconds, instead +# of seconds. +# +# This policy can also be used to "bootstrap" accounting sessions. +# If there is data which is only available in the Access-Request, +# it can be placed in the accounting table. Then, when accounting +# packets are received, they will update the row which contains +# the session information. +# +sql_session_start.post-auth { + acct_unique + sql.accounting +} diff --git a/raddb/policy.d/canonicalization b/raddb/policy.d/canonicalization new file mode 100644 index 0000000..6d90e37 --- /dev/null +++ b/raddb/policy.d/canonicalization @@ -0,0 +1,113 @@ +# +# Split User-Name in NAI format (RFC 4282) into components +# +# This policy writes the Username and Domain portions of the +# NAI into the Stripped-User-Name and Stripped-User-Domain +# attributes. +# +# The regular expression to do this is not strictly compliant +# with the standard, but it is not possible to write a +# compliant regexp without perl style regular expressions (or +# at least not a legible one). +# +nai_regexp = '^([^@]*)(@([-[:alnum:]]+\.[-[:alnum:].]+))?$' + +split_username_nai { + if (&User-Name && (&User-Name =~ /${policy.nai_regexp}/)) { + update request { + &Stripped-User-Name := "%{1}" + } + + # Only add the Stripped-User-Domain attribute if + # we have a domain. This means presence checks + # for Stripped-User-Domain work. + if ("%{3}" != '') { + update request { + &Stripped-User-Domain = "%{3}" + } + } + + # If any of the expansions result in a null + # string, the update section may return + # something other than updated... + updated + } + else { + noop + } +} + +# +# If called in post-proxy we modify the proxy-reply message +# +split_username_nai.post-proxy { + if (&proxy-reply:User-Name && (&proxy-reply:User-Name =~ /${policy.nai_regexp}/)) { + update proxy-reply { + &Stripped-User-Name := "%{1}" + } + + # Only add the Stripped-User-Domain attribute if + # we have a domain. This means presence checks + # for Stripped-User-Domain work. + if ("%{3}" != '') { + update proxy-reply { + &Stripped-User-Domain = "%{3}" + } + } + updated + } + else { + noop + } +} + +# +# Normalize the MAC Addresses in the Calling/Called-Station-Id +# +mac-addr-regexp = '([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})[^0-9a-f]?([0-9a-f]{2})' + +# +# Add "rewrite_called_station_id" in the "authorize" and +# "preacct" sections. +# +# Makes Called-Station-ID conform to what RFC3580 says should +# be provided by 802.1X authenticators. +# +rewrite_called_station_id { + if (&Called-Station-Id && (&Called-Station-Id =~ /^${policy.mac-addr-regexp}([^0-9a-f](.+))?$/i)) { + update request { + &Called-Station-Id := "%{toupper:%{1}-%{2}-%{3}-%{4}-%{5}-%{6}}" + } + + # SSID component? + if ("%{8}") { + update request { + &Called-Station-SSID := "%{8}" + } + } + updated + } + else { + noop + } +} + +# +# Add "rewrite_calling_station_id" in the "authorize" and +# "preacct" sections. +# +# Makes Calling-Station-ID conform to what RFC3580 says should +# be provided by 802.1X authenticators. +# +rewrite_calling_station_id { + if (&Calling-Station-Id && (&Calling-Station-Id =~ /^${policy.mac-addr-regexp}$/i)) { + update request { + &Calling-Station-Id := "%{toupper:%{1}-%{2}-%{3}-%{4}-%{5}-%{6}}" + } + updated + } + else { + noop + } +} + diff --git a/raddb/policy.d/control b/raddb/policy.d/control new file mode 100644 index 0000000..b3f1e03 --- /dev/null +++ b/raddb/policy.d/control @@ -0,0 +1,40 @@ +# +# If you want the server to pretend that it is dead, +# then use the "do_not_respond" policy. +# +do_not_respond { + update control { + &Response-Packet-Type := Do-Not-Respond + } + handled +} + +# +# Send Access-Accept immediately +# +accept { + update control { + &Response-Packet-Type = Access-Accept + } + handled +} + +# +# Send Access-Challenge immediately +# +challenge { + update control { + &Response-Packet-Type = Access-Challenge + } + handled +} + +# +# Send an Accounting-Response immediately +# +acct_response { + update control { + &Response-Packet-Type = Accounting-Response + } + handled +} diff --git a/raddb/policy.d/cui b/raddb/policy.d/cui new file mode 100644 index 0000000..08b2c91 --- /dev/null +++ b/raddb/policy.d/cui @@ -0,0 +1,131 @@ +# +# The following policies are for the Chargeable-User-Identity +# (CUI) configuration. +# +# The policies below can be called as just 'cui' (not +# cui.authorize etc..) from the various config sections. +# + +# +# cui_hash_key definition +# This key serves the purpose of protecting CUI values against +# dictionary attacks, therefore should be chosen as a "random" +# string and kept secret. +# +cui_hash_key = "changeme" + +# +# cui_require_operator_name switch +# If this is set to nonzero value then CUI will only be added +# when a non-empty Operator-Name value is present in the request +# +cui_require_operator_name = "no" + +# +# The client indicates it can do CUI by sending a CUI attribute +# containing one zero byte. +# A non-empty value in Operator-Name can be an additional requirement. +# Normally CUI support is turned on only for such requests. +# CUI support can be used for local clients which do not +# supports CUI themselves, the server can simulate a CUI request +# adding the missing NUL CUI value and the Operator-Name attribute. +# Clients which are supposed to get this treatment should +# be marked by add_cui flag in clients.conf +# We assume that local clients are marked in the client.conf with +# add_cui flag, e.g. +# client xxxx { +# ... +# add_cui = yes +# } +# +cui.authorize { + if ("%{client:add_cui}" == 'yes') { + update request { + &Chargeable-User-Identity := 0x00 + } + } +} + +# +# Before proxing an Access-Request to a remote server, a NUL CUI +# attribute should be added, unless it is already present in the request. +# +cui.pre-proxy { + if (("%{request:Packet-Type}" == 'Access-Request') && ("%{client:add_cui}" == 'yes')) { + update proxy-request { + &Chargeable-User-Identity = 0x00 + } + } +} + + +# +# Add a CUI attribute based on the User-Name, and a secret key +# known only to this server. +# For EAP-TTLS and EAP-PEAP methods +# use_tunneled_reply parameter MUST be set to yes +# +cui.post-auth { + if (!&control:Proxy-To-Realm && &Chargeable-User-Identity && !&reply:Chargeable-User-Identity && \ + (&Operator-Name || ('${policy.cui_require_operator_name}' != 'yes')) ) { + update reply { + &Chargeable-User-Identity = "%{sha1:${policy.cui_hash_key}%{tolower:%{User-Name}%{%{Operator-Name}:-}}}" + } + } + + # + # The section below will store a CUI for the User in the DB and remove the + # User-Name attribute from the reply if a CUI is present. + # + # You need to configure the cuisql module and your database for this to work. + # If your NAS can do CUI based accounting themselves or you do not care about + # accounting, comment out the 'cuisql' line below. + # + if (&reply:Chargeable-User-Identity) { + # Force User-Name to be the User-Name from the request + update { + &reply:User-Name := &request:User-Name + } + cuisql + } +} + + +cui-inner.post-auth { + if (&outer.request:Chargeable-User-Identity && \ + (&outer.request:Operator-Name || ('${policy.cui_require_operator_name}' != 'yes'))) { + update reply { + &Chargeable-User-Identity := "%{sha1:${policy.cui_hash_key}%{tolower:%{User-Name}%{%{outer.request:Operator-Name}:-}}}" + } + } +} + +# +# If your NAS can do CUI based accounting or you do not care about +# accounting then just comment out the call to cui in ...... +# +# If we had stored a CUI for the User, add it to the request. +# +cui.accounting { + # + # If the CUI isn't in the packet, see if we can find it + # in the DB. + # + if (!&Chargeable-User-Identity) { + update request { + &Chargeable-User-Identity := "%{cuisql:\ + SELECT cui FROM cui \ + WHERE clientipaddress = '%{%{Packet-Src-IPv6-Address}:-%{Packet-Src-IP-Address}}' \ + AND callingstationid = '%{Calling-Station-Id}' \ + AND username = '%{User-Name}'}" + } + } + + # + # If it exists now, then write out when we last saw + # this CUI. + # + if (&Chargeable-User-Identity && (&Chargeable-User-Identity != '')) { + cuisql + } +} diff --git a/raddb/policy.d/debug b/raddb/policy.d/debug new file mode 100644 index 0000000..26583f1 --- /dev/null +++ b/raddb/policy.d/debug @@ -0,0 +1,64 @@ +# +# Outputs the contents of the control list in debugging (-X) mode +# +debug_control { + if("%{debug_attr:control:}" == '') { + noop + } +} + +# +# Outputs the contents of the request list in debugging (-X) mode +# +debug_request { + if("%{debug_attr:request:}" == '') { + noop + } +} + +# +# Outputs the contents of the coa list in debugging (-X) mode +# +debug_coa { + if("%{debug_attr:coa:}" == '') { + noop + } +} + +# +# Outputs the contents of the reply list in debugging (-X) mode +# +debug_reply { + if("%{debug_attr:reply:}" == '') { + noop + } +} + +# +# Outputs the contents of the session state list in debugging (-X) mode +# +debug_session_state { + if("%{debug_attr:session-state:}" == '') { + noop + } +} + +# +# Outputs the contents of the proxy-request state list in debugging (-X) mode +# +debug_proxy_request { + if("%{debug_attr:proxy-request:}" == '') { + noop + } +} + +# +# Outputs the contents of the main lists in debugging (-X) mode +# +debug_all { + debug_control + debug_request + debug_coa + debug_reply + debug_session_state +} diff --git a/raddb/policy.d/dhcp b/raddb/policy.d/dhcp new file mode 100644 index 0000000..1752acb --- /dev/null +++ b/raddb/policy.d/dhcp @@ -0,0 +1,327 @@ +# Assign common DHCP reply packet options +dhcp_common { + # The contents here are invented. Change them! + update reply { + &DHCP-Domain-Name-Server = 127.0.0.1 + &DHCP-Domain-Name-Server += 127.0.0.2 + &DHCP-Subnet-Mask = 255.255.255.0 + &DHCP-Router-Address = 192.0.2.1 + &DHCP-Broadcast-Address = 192.0.2.255 + &DHCP-IP-Address-Lease-Time = 7200 + &DHCP-DHCP-Server-Identifier = &control:DHCP-DHCP-Server-Identifier + } +} + +# Lookup DHCP group based options. This policy allows for membership +# of multiple groups so can cover the ISC concepts of "group" and "class" +# To use this enable the "dhcp_files" module +#dhcp_group_options { +# foreach &request:DHCP-Group-Name { +# dhcp_set_group_options +# } +#} + +# Policy to override DHCP-Network-Subnet +# +# Some networks have a "shared-network" or "multinet" configuration (as +# defined by some other DHCP servers) in which multiple IP subnets may +# co-exist in a single Layer 2 network (or VLAN). +# +# In enterprise environments this is often for the purpose of providing loose +# segregation between classes of devices such as local network-attached +# storage or IP telephony. There are valid reasons why each of the subnets is +# not seperately VLANed, such as to enable the use of ICMP redirects to avoid +# hairpinning of cross-subnet traffic via a gateway. +# +# In ISP environments this is a common configuration for edge networks whose +# access is provided by DOCSIS cable modems that share a VLAN with the devices +# they provide a service to but are seperately addressed. +# +# Where it is necessary to force the selection of a particular subnet for a +# device, multiple pools must be configured for each subnet and referenced +# with unique identifiers in the *network-specific* section of +# mods-config/files/dhcp. +# +# By default DHCP-Network-Subnet is populated such that it normally +# refers to the Layer 2 network from which the DHCP query originates - we +# cannot know the intended subnet for the device without additional input to +# the policy. +# +# Override DHCP-Network-Subnet to be an address within the desired +# network to force selection of a particular address pool and/or network +# parameters. +# +# Note: If each subnet within a network is equally valid for the DHCP requests +# originating from that network then you do not need to call this policy, +# rather look at the examples concerning dhcp_subnet in +# mods-config/files/dhcp instead, which use a single pool containing addresses +# from all subnets then set the correct subnet-specific options based on the +# randomly assigned IP address. +# +#dhcp_override_network { +# if (&DHCP-Vendor-Class-Identifier && &DHCP-Vendor-Class-Identifier == "SIP100") +# update request { +# DHCP-Network-Subnet := 10.10.0.0 +# } +# } +#} + + +# Policy that calls the files instance of the same name after first making +# DHCP-Network-Subnet specific to the allocated IP address of the client. +#dhcp_subnet { +# update { +# &DHCP-Network-Subnet := "%{%{reply:DHCP-Your-IP-Address}:-%{DHCP-Client-IP-Address}}" +# } +# +# # Call the dhcp_subnet instance of the files module +# dhcp_subnet +#} + +# Assign compatibility data to request for sqlippool for DHCP Request +dhcp_sqlippool_request { + + # + # During initial address selection (DORA) the REQUEST is broadcast and + # requested-ip must be provided. We revoke any active offers for addresses + # not matching the requested-ip, i.e. those made by other servers when + # processing the DISCOVER. + # + # If there is only a single server then this optimisation can be disabled. + # + if (&DHCP-Requested-IP-Address) { + update request { + &Acct-Status-Type := Start + } + dhcp_sqlippool.accounting + } + + # Extend an existing offer or active lease + update request { + &Acct-Status-Type := Alive + } + dhcp_sqlippool.accounting { + notfound = return + } + + update reply { + &DHCP-Your-IP-Address := "%{%{DHCP-Requested-IP-Address}:-%{DHCP-Client-IP-Address}}" + } + +} + +# Assign compatibility data to request for sqlippool for DHCP Release +dhcp_sqlippool_release { + + # Do some minor hacks to the request so that it looks + # like a RADIUS Accounting Stop request to the SQL IP Pool module. + update request { + &Acct-Status-Type = Stop + } + + # Call the actual module in accounting context + dhcp_sqlippool.accounting + +} + +# Assign compatibility data to request for sqlippool for DHCP Decline +dhcp_sqlippool_decline { + + # Do a minor hack to the request so that it looks + # like a RADIUS Accounting Off request to the SQL IP Pool module. + update request { + &Acct-Status-Type = Accounting-Off + } + + # Call the actual module in accounting context + dhcp_sqlippool.accounting + +} + +# Example policy for fetching option data from SQL +dhcp_policy_sql { + + # + # Network-specific options + # + + # + # We want to lookup the Layer 2 network specific DHCP options to + # include in the reply. For this we need a stable identifier for the + # network from which the request is originating (based on + # DHCP-Network-Subnet) which can be used as the lookup key + # (DHCP-SQL-Option-Identifier) for the network options. + # + # Here we fabricate an example for the purpose of placing all + # configuration elements into SQL. We use a PostgreSQL query that + # returns the network identifier in the row containing the smallest + # enclosing CIDR, which assumes a schema such as the following: + # + # CREATE TABLE fr_network_to_identifier (network CIDR, network_id TEXT) + # + # Note: An rlm_files based lookup of the network_identifier (as per + # the examples in the dhcp virtual server) may be preferable to an ad + # hoc SQL query assuming that the network topology does not change + # frequently. + # +# update control { +# &control:Tmp-String-0 := "%{dhcp_sql:SELECT network_id \ +# FROM fr_network_to_identifier \ +# WHERE '%{DHCP-Network-Subnet}'::inet << network \ +# ORDER BY MASKLEN(network) DESC LIMIT 1;}" +# } + + # + # Use the network identifer to lookup the options specific to the + # originating network, using "network" context. Common network + # settings can be placed into a group and shared, with individual + # networks mapped to one or more option groups. + # + # - Place network-specific options in the dhcpreply table with + # "context = 'network'". + # - Add "Fall-Through := Yes" to the network options in the dhcpreply + # table to trigger group lookups for the network, which are + # disabled by default. + # - Place "identifier = <network_id>, groupname = <group>, + # priority = <priority>, context = 'network'" in the dhcpgroup + # table to map a network to a shared set of network options. + # - Place group-specific options in the dhcpgroupreply table with + # "context = 'network'". + # + # Note: In "shared-network" or "multinet" topologies you can instead + # just set all of the network options once in the subnet-specific + # options (after obtaining an IP address), below. + # +# update control { +# &DHCP-SQL-Option-Context := "network" +# &DHCP-SQL-Option-Identifier := &control:Tmp-String-0 +# } +# dhcp_sql.authorize + + + # + # Allocate IPs from the DHCP pool in SQL. + # + # Here we simply reuse the network_id (obtained previously) as the + # Pool-Name. + # +# update control { +# &Pool-Name := &control:Tmp-String-0 +# } +# dhcp_sqlippool + + + # + # Subnet-specific options + # + + # + # In "shared-network" or "multinet" topologies (in which a Layer 2 + # network has a single pool that contains addresses from multiple + # subnets) it is necessary to set subnet-specific options based on the + # address that has just been allocated. + # + # Again, for this we need to derive a stable identifier for the subnet + # to which the IP address we are issuing belongs that will serve as a + # lookup key for the network options. + # + # Continuing our previous example, we can use a PostgreSQL query to + # find the subnet identifer in the row with the closest enclosing + # CIDR, which assumes a schema such as the following: + # + # CREATE TABLE fr_subnet_to_identifier (subnet CIDR, subnet_id TEXT) + # + # Note: An rlm_files based lookup of the subnet_identifier (as per the + # examples in the dhcp virtual server) is preferable to an ad hoc SQL + # query assuming that the network topology does not change frequently. + # +# update control { +# &control:Tmp-String-0 := "%{dhcp_sql:SELECT subnet_id \ +# FROM fr_subnet_to_identifier \ +# WHERE '%{reply:DHCP-Your-IP-Address}'::inet << subnet \ +# ORDER BY MASKLEN(subnet) DESC LIMIT 1;}" +# } + + # + # Use the subnet identifer to lookup the options specific to the + # subnet for the IP we are allocating, using "subnet" context. Common + # subnet settings can be placed into a group and shared, with + # individual subnets mapped to one or more option groups. + # + # - Place subnet-specific options in the dhcpreply table with + # "context = 'subnet'". + # - Add "Fall-Through := Yes" to the subnet options in the dhcpreply + # table to trigger group lookups for the subnet, which are + # disabled by default. + # - Place "identifier = <subnet_id>, groupname = <group>, + # priority = <priority>, context = 'subnet'" in the dhcpgroup + # table to map a subnet to a shared set of subnet options. + # - Place group-specific options in the dhcpgroupreply table with + # "context = 'subnet'". + # +# update control { +# &DHCP-SQL-Option-Context := "subnet" +# &DHCP-SQL-Option-Identifier := &control:Tmp-String-0 +# } +# dhcp_sql.authorize + + + # + # Host-specific and group-specific options + # + + # "Groups" conventionally differentiate devices based on manual + # groupings using a device-specific identifier such as the MAC + # address. + # + # - Place host-specific options in the dhcpreply table with + # "context = 'group'". + # - Add "Fall-Through := Yes" to the device options in the dhcpreply + # table to trigger group lookups, which are disabled by default. + # - Place "identifier = <MAC-Address>, groupname = <group>, + # priority = <priority>, context='group'" in the dhcpgroup table + # to map a device to its groups. + # - Place group-specific options in the dhcpgroupreply table with + # "context = 'group'". + # +# update control { +# &DHCP-SQL-Option-Context := "group" +# &DHCP-SQL-Option-Identifier := &request:DHCP-Client-Hardware-Address +# } +# dhcp_sql.authorize + + + # + # Class/subclass-specific options + # + + # + # "Classes" conventionally differentiate devices based on all or part + # of one or more DHCP request options, or any combination of + # information that is available in the request or has already looked + # up from some datastore. + # + # Create multiple instances of the following block, one for each + # class. Differentiate between classes by setting + # DHCP-SQL-Option-Context uniquely. + # + # - Place "subclass"-specific options (i.e. each member of a class) + # in the dhcpreply table with "context = <class-name>". + # - For class-level options common to every member of a class, + # either: + # - Duplicate the options for each member of the subclass. + # or: + # - Add "Fall-Through := Yes" to each members options to trigger + # group lookups, which are disabled by default. + # - Map each member of the class to a group in the dhcpgroup + # table with context = '<class-name>'; + # - Create the corresponding class in the dhcpgroupreply table + # with "context = '<class-name>'". + # +# update control { +# &DHCP-SQL-Option-Context := "class-vci-substring" +# &DHCP-SQL-Option-Identifier := "%{substring %{request:DHCP-Vendor-Class-Identifier} 5 4}" +# } +# dhcp_sql.authorize + +} diff --git a/raddb/policy.d/eap b/raddb/policy.d/eap new file mode 100644 index 0000000..c8dac22 --- /dev/null +++ b/raddb/policy.d/eap @@ -0,0 +1,54 @@ +# +# Forbid all EAP types. Enable this by putting "forbid_eap" +# into the "authorize" section. +# +forbid_eap { + if (&EAP-Message) { + reject + } +} + +# +# Forbid all non-EAP types outside of an EAP tunnel. +# +permit_only_eap { + if (!&EAP-Message) { + # We MAY be inside of a TTLS tunnel. + # PEAP and EAP-FAST require EAP inside of + # the tunnel, so this check is OK. + # If so, then there MUST be an outer EAP message. + if (!&outer.request || !&outer.request:EAP-Message) { + reject + } + } +} + +# +# Remove Reply-Message from response if were doing EAP +# +# Be RFC 3579 2.6.5 compliant - EAP-Message and Reply-Message should +# not be present in the same response. +# +remove_reply_message_if_eap { + if (&reply:EAP-Message && &reply:Reply-Message) { + update reply { + &Reply-Message !* ANY + } + } + else { + noop + } +} + +verify_tls_client_common_name { + # + # If the User-Name is anonymized, then don't check it. + # + # But if User-Name is realm AND there's a certificate name, then check + # if they match. This is not always the case, but it is the case + # often enough that it matters. + # + if ((&User-Name !~ /^@/) && &TLS-Client-Cert-Common-Name && (&TLS-Client-Cert-Common-Name != &User-Name)) { + reject + } +} diff --git a/raddb/policy.d/filter b/raddb/policy.d/filter new file mode 100644 index 0000000..ff8f531 --- /dev/null +++ b/raddb/policy.d/filter @@ -0,0 +1,211 @@ +# +# Example of forbidding all attempts to login via +# realms. +# +deny_realms { + if (&User-Name && (&User-Name =~ /@|\\/)) { + reject + } +} + +# +# Filter the username +# +# Force some sanity on User-Name. This helps to avoid issues +# issues where the back-end database is "forgiving" about +# what constitutes a user name. +# +filter_username { + if (&User-Name) { + # + # reject mixed case e.g. "UseRNaMe" + # + #if (&User-Name != "%{tolower:%{User-Name}}") { + # reject + #} + + # + # reject all whitespace + # e.g. "user@ site.com", or "us er", or " user", or "user " + # + if (&User-Name =~ / /) { + update request { + &Module-Failure-Message += 'Rejected: User-Name contains whitespace' + } + reject + } + + # + # reject Multiple @'s + # e.g. "user@site.com@site.com" + # + if (&User-Name =~ /@[^@]*@/ ) { + update request { + &Module-Failure-Message += 'Rejected: Multiple @ in User-Name' + } + reject + } + + # + # reject double dots + # e.g. "user@site..com" + # + if (&User-Name =~ /\.\./ ) { + update request { + &Module-Failure-Message += 'Rejected: User-Name contains multiple ..s' + } + reject + } + + # + # must have at least 1 string-dot-string after @ + # e.g. "user@site.com" + # + if ((&User-Name =~ /@/) && (&User-Name !~ /@(.+)\.(.+)$/)) { + update request { + &Module-Failure-Message += 'Rejected: Realm does not have at least one dot separator' + } + reject + } + + # + # Realm ends with a dot + # e.g. "user@site.com." + # + if (&User-Name =~ /\.$/) { + update request { + &Module-Failure-Message += 'Rejected: Realm ends with a dot' + } + reject + } + + # + # Realm begins with a dot + # e.g. "user@.site.com" + # + if (&User-Name =~ /@\./) { + update request { + &Module-Failure-Message += 'Rejected: Realm begins with a dot' + } + reject + } + } +} + +# +# Filter the User-Password +# +# Some equipment sends passwords with embedded zeros. +# This policy filters them out. +# +filter_password { + if (&User-Password && \ + (&User-Password != "%{string:User-Password}")) { + update request { + &Tmp-String-0 := "%{string:User-Password}" + &User-Password := "%{string:Tmp-String-0}" + &Tmp-String-0 !* "" + } + } +} + +filter_inner_identity { + # + # No names, reject. + # + if (!&outer.request:User-Name || !&User-Name) { + update request { + Module-Failure-Message = "User-Name is required for tunneled authentication" + } + reject + } + + # + # Do detailed checks only if the inner and outer + # NAIs are different. + # + # If the NAIs are the same, it violates user privacy, + # but is allowed. + # + if (&outer.request:User-Name != &User-Name) { + # + # Get the outer realm. + # + if (&outer.request:User-Name =~ /@([^@]+)$/) { + update request { + Outer-Realm-Name = "%{1}" + } + + # + # When we have an outer realm name, the user portion + # MUST either be empty, or begin with "anon". + # + # We don't check for the full "anonymous", because + # some vendors don't follow the standards. + # + if (&outer.request:User-Name !~ /^(anon|@)/) { + update request { + Module-Failure-Message = "User-Name is not anonymized" + } + reject + } + } + + # + # There's no outer realm. The outer NAI is different from the + # inner NAI. The User-Name MUST be anonymized. + # + # Otherwise, you could log in as outer "bob", and inner "doug", + # and we'd have no idea which one was correct. + # + elsif (&outer.request:User-Name !~ /^anon/) { + update request { + Module-Failure-Message = "User-Name is not anonymized" + } + reject + } + + # + # Get the inner realm. + # + if (&User-Name =~ /@([^@]+)$/) { + update request { + Inner-Realm-Name = "%{1}" + } + + # + # Note that we do EQUALITY checks for realm names. + # There is no simple way to do case insensitive checks + # on internationalized domain names. There is no reason + # to allow outer "anonymous@EXAMPLE.COM" and inner + # "user@example.com". The user should enter the same + # realm for both identities. + # + # If the inner realm isn't the same as the outer realm, + # the inner realm MUST be a subdomain of the outer realm. + # + if (&Outer-Realm-Name && \ + (&Inner-Realm-Name != &Outer-Realm-Name) && \ + (&Inner-Realm-Name !~ /\.%{Outer-Realm-Name}$/)) { + update request { + Module-Failure-Message = "Inner realm '%{Inner-Realm-Name}' and outer realm '%{Outer-Realm-Name}' are not from the same domain." + } + reject + } + + # + # It's OK to have an inner realm and no outer realm. + # + # That won't work for roaming, but the local RADIUS server + # can still authenticate the user. + # + } + + # + # It's OK to have an outer realm and no inner realm. + # + # It will work for roaming, and the local RADIUS server + # can authenticate the user without the realm. + # + } +} diff --git a/raddb/policy.d/moonshot-targeted-ids b/raddb/policy.d/moonshot-targeted-ids new file mode 100644 index 0000000..98ae4a1 --- /dev/null +++ b/raddb/policy.d/moonshot-targeted-ids @@ -0,0 +1,249 @@ +# +# The following policies generate targeted IDs for ABFAB (Moonshot) +# +# This policy requires that the UUID package is installed on your platform +# and that this is called from the inner-tunnel +# +# The following string attributes need to exist in the UKERNA dictionary +# Moonshot-Host-TargetedId (138) +# Moonshot-Realm-TargetedId (139) +# Moonshot-TR-COI-TargetedId (140) +# Moonshot-MSTID-GSS-Acceptor (141) +# Moonshot-MSTID-Namespace (142) +# Moonshot-MSTID-TargetedId (143) +# +# These attributes should also be listed in the attr_filter policies +# post-proxy and pre-proxy when you use attribute filtering: +# Moonshot-Host-TargetedId =* ANY, +# Moonshot-Realm-TargetedId =* ANY, +# Moonshot-TR-COI-TargetedId =* ANY, +# + +# +# targeted_id_salt definition +# This salt serves the purpose of protecting targeted IDs against +# dictionary attacks, therefore should be chosen as a "random" +# string and kept secret. +# +# If you use special characters %, { and }, escape them with a \ first +# +targeted_id_salt = 'changeme' + +# +# Moonshot namespaces +# These namespaces are used for UUID generation. +# They should not be changed by implementors +# +moonshot_host_namespace = 'a574a04e-b7ff-4850-aa24-a8599c7de1c6' +moonshot_realm_namespace = 'dea5f26d-a013-4444-977d-d09fc990d2e6' +moonshot_coi_namespace = '145d7e7e-7d54-43ee-bbcb-3c6ad9428247' + + +# This policy generates a host-specific TargetedId +# +moonshot_host_tid.post-auth { + # retrieve or generate a UUID for Moonshot-Host-TargetedId + if (&outer.request:GSS-Acceptor-Host-Name) { + # prep some variables (used regardless of SQL backing or not!) + update control { + Moonshot-MSTID-GSS-Acceptor := "%{tolower:%{outer.request:GSS-Acceptor-Host-Name}}" + Moonshot-MSTID-Namespace := "${policy.moonshot_host_namespace}" + } + + # if you want to use SQL-based backing, remove the comment from + # this line. You also have to configure and enable the + # moonshot-targeted-ids sql module in mods-enabled. + # +# moonshot_get_targeted_id + + # generate a UUID for Moonshot-Host-TargetedId + if (!&control:Moonshot-MSTID-TargetedId) { + # generate the TID + moonshot_make_targeted_id + + # if you want to store your TargetedId in SQL-based backing, + # remove the comment from this line. You also have to configure + # and enable the moonshot-targeted-ids sql module in mods-enabled. + # +# moonshot_tid_sql + } + + # set the actual TargetedId in the session-state list + if (&control:Moonshot-MSTID-TargetedId) { + update outer.session-state { + Moonshot-Host-TargetedId := &control:Moonshot-MSTID-TargetedId + } + update control { + Moonshot-MSTID-TargetedId !* ANY + } + } + + # Sanitise the control list to remove the internal attributes + update control { + Moonshot-MSTID-GSS-Acceptor !* ANY + Moonshot-MSTID-Namespace !* ANY + } + } +} + +# This policy generates a realm-specific TargetedId +# +moonshot_realm_tid.post-auth { + # retrieve or generate a UUID for Moonshot-Realm-TargetedId + if (&outer.request:GSS-Acceptor-Realm-Name) { + # prep some variables (used regardless of SQL backing or not!) + update control { + Moonshot-MSTID-GSS-Acceptor := "%{tolower:%{outer.request:GSS-Acceptor-Realm-Name}}" + Moonshot-MSTID-Namespace := "${policy.moonshot_realm_namespace}" + } + + # if you want to use SQL-based backing, remove the comment from + # this line. You also have to configure and enable the + # moonshot-targeted-ids sql module in mods-enabled. + # +# moonshot_get_targeted_id + + # generate a UUID for Moonshot-Realm-TargetedId + if (!&control:Moonshot-MSTID-TargetedId) { + # generate the TID + moonshot_make_targeted_id + + # if you want to store your TargetedId in SQL-based backing, + # remove the comment from this line. You also have to configure + # and enable the moonshot-targeted-ids sql module in mods-enabled. + # +# moonshot_tid_sql + } + + # set the actual TargetedId in the session-state list + if (&control:Moonshot-MSTID-TargetedId) { + update outer.session-state { + Moonshot-Realm-TargetedId := &control:Moonshot-MSTID-TargetedId + } + update control { + Moonshot-MSTID-TargetedId !* ANY + } + } + + # Sanitise the control list to remove the internal attributes + update control { + Moonshot-MSTID-GSS-Acceptor !* ANY + Moonshot-MSTID-Namespace !* ANY + } + } +} + +# This policy generates a COI-specific targeted ID +# +moonshot_coi_tid.post-auth { + # retrieve or generate a UUID for Moonshot-TR-COI-TargetedId + if (&outer.request:Trust-Router-COI) { + # prep some variables (used regardless of SQL backing or not!) + update control { + Moonshot-MSTID-GSS-Acceptor := "%{tolower:%{outer.request:Trust-Router-COI}}" + Moonshot-MSTID-Namespace := "${policy.moonshot_coi_namespace}" + } + + # if you want to use SQL-based backing, remove the comment from + # this line. You also have to configure and enable the + # moonshot-targeted-ids sql module in mods-enabled. + # +# moonshot_get_targeted_id + + # generate a UUID for Moonshot-TR-COI-TargetedId + if (!&control:Moonshot-MSTID-TargetedId) { + # generate the TID + moonshot_make_targeted_id + + # if you want to store your TargetedId in SQL-based backing, + # remove the comment from this line. You also have to configure + # and enable the moonshot-targeted-ids sql module in mods-enabled. + # +# moonshot_tid_sql + } + + # set the actual TargetedId in the session-state list + if (&control:Moonshot-MSTID-TargetedId) { + update outer.session-state { + Moonshot-TR-COI-TargetedId := &control:Moonshot-MSTID-TargetedId + } + update control { + Moonshot-MSTID-TargetedId !* ANY + } + } + + # Sanitise the control list to remove the internal attributes + update control { + Moonshot-MSTID-GSS-Acceptor !* ANY + Moonshot-MSTID-Namespace !* ANY + } + } +} + +# This is the generic generation policy. It requires moonshot_host_tid, moonshot_realm_tid, or moonshot_coi_tid to set variables +# +moonshot_make_targeted_id.post-auth { + # uses variables set in the control list + # + if (&control:Moonshot-MSTID-Namespace && &control:Moonshot-MSTID-GSS-Acceptor) { + # targeted id = (uuid -v 5 [namespace] [username][salt][GSS acceptor value])@[IdP realm name] + # + if ("%{echo:/usr/bin/uuid -v 5 %{control:Moonshot-MSTID-Namespace} %{tolower:%{User-Name}}${policy.targeted_id_salt}%{control:Moonshot-MSTID-GSS-Acceptor}}" =~ /^([^ ]+)([ ]*)$/) { + update control { + Moonshot-MSTID-TargetedId := "%{1}@%{tolower:%{request:Realm}}" + } + if (&control:Moonshot-MSTID-TargetedId =~ /([\%\{\}]+)/) { + update control { + Moonshot-MSTID-TargetedId !* ANY + } + update outer.session-state { + Module-Failure-Message = 'Invalid TargetedId generated, check your targeted_id_salt!' + } + reject + } + } + else { + # we simply return the 'echo' error message as the Module-Failure-Message, usually a lack of 'uuid' + reject + } + } + else { + # Our variables were not set, so we'll throw an error because there's no point in continuing! + update outer.session-state { + Module-Failure-Message = 'Required variables for moonshot_make_targeted_id not set!' + } + reject + } +} + +# This is the generic retrieval policy. It requires moonshot_host_tid, moonshot_realm_tid, or moonshot_coi_tid to set variables +# +moonshot_get_targeted_id.post-auth { + # uses variables set in the control list + # + if (&control:Moonshot-MSTID-Namespace && &control:Moonshot-MSTID-GSS-Acceptor) { + # retrieve the TargetedId + # + update control { + Moonshot-MSTID-TargetedId := "%{moonshot_tid_sql:\ + SELECT targeted_id FROM moonshot_targeted_ids \ + WHERE gss_acceptor = '%{control:Moonshot-MSTID-GSS-Acceptor}' \ + AND namespace = '%{control:Moonshot-MSTID-Namespace}' \ + AND username = '%{tolower:%{User-Name}}'}" + } + + # if the value is empty, there's no point in setting it and delete it from the control list! + if (&control:Moonshot-MSTID-TargetedId == '') { + update control { + Moonshot-MSTID-TargetedId !* ANY + } + } + } + else { + # Our variables were not set, so we'll throw an error because there's no point in continuing! + update outer.session-state { + Module-Failure-Message = 'Required variables for moonshot_get_targeted_id not set!' + } + reject + } +} diff --git a/raddb/policy.d/operator-name b/raddb/policy.d/operator-name new file mode 100644 index 0000000..6d042d4 --- /dev/null +++ b/raddb/policy.d/operator-name @@ -0,0 +1,46 @@ +# +# The following policies are for the Operator-Name +# configuration. +# +# The policies below can be called as just 'operator-name' (not +# operator-name.authorize etc..) from the various config sections. +# + +# If you require that the Operator-Name be set +# for local clients then call the 'operator-name' policy +# in the authorize section of the virtual-server for your clients in clients.conf + +# To inject an Operator-Name whilst proxying, call the +# 'operator-name' policy in the pre-proxy section of the virtual server +# No need to call this if you have already enabled this in +# the authorize section. + +# +# We assume that clients can have the operator-name definition +# in the client.conf, e.g. +# client xxxx { +# ... +# Operator-Name = 1your.domain +# } +# If this parameter is found for a client, then we add +# an Operator-Name attribute +# +operator-name.authorize { + if ("%{client:Operator-Name}") { + update request { + &Operator-Name = "%{client:Operator-Name}" + } + } +} + +# +# Before proxing the client add an Operator-Name +# attribute identifying this site if the operator-name is found for this client +# +operator-name.pre-proxy { + if (("%{request:Packet-Type}" == 'Access-Request') && "%{client:Operator-Name}") { + update proxy-request { + &Operator-Name := "%{client:Operator-Name}" + } + } +} diff --git a/raddb/policy.d/rfc7542 b/raddb/policy.d/rfc7542 new file mode 100644 index 0000000..84a5c17 --- /dev/null +++ b/raddb/policy.d/rfc7542 @@ -0,0 +1,46 @@ +# +# The following policy is for RFC7542-style bang path +# management. +# +# It hands control from the standard 'suffix' realm +# processor to the 'bangpath' processer, allowing the +# definition of specific routing information in the +# decoration of the User-Name. +# +# Use this with caution. In particular, read the following +# RFC document sections for reasons why you shouldn't use +# this, and also why this is used: +# +# 1. https://tools.ietf.org/html/rfc4282#section-2.7 +# 2. https://tools.ietf.org/html/rfc7542#section-3.3.1 +# +# $Id$ +# + +# This is a |-separated list of realms this specific service +# is responsible for. We cannot read this from the proxy.conf +# file, so we turn this into an 'or list' regex. +# Examples: rfc7542_realms = 'example.com' +# rfc7542_realms = 'example.com|another.net|this.org' +# +rfc7542_realms = 'changeme' + +# This policy checks the User-Name attribute whether it is in +# RFC7542 bang-path format. If it is, it lets the bangpath realm +# processor handle it, otherwise it leaves it for suffix to handle +# +rfc7542.authorize { + # Format: not_local_realm!...@local_realm: Handle with bangpath + if ( (&request:User-Name =~ /(.+)!(.*)\@(${policy.rfc7542_realms})/) && \ + !(&request:User-Name =~ /(${policy.rfc7542_realms})!(.*)\@(.+)/) ) { + bangpath + updated + } + + # Format: local_realm!...@not_local_realm: Handle with bangpath + elsif ( (&request:User-Name =~ /(${policy.rfc7542_realms})!(.*)\@(.+)/) && \ + !(&request:User-Name =~ /(.+)!(.*)\@(${policy.rfc7542_realms})/) ) { + bangpath + updated + } +} diff --git a/raddb/proxy.conf b/raddb/proxy.conf new file mode 100644 index 0000000..26f620c --- /dev/null +++ b/raddb/proxy.conf @@ -0,0 +1,872 @@ +# -*- text -*- +## +## proxy.conf -- proxy radius and realm configuration directives +## +## $Id$ + +####################################################################### +# +# Proxy server configuration +# +# This entry controls the servers behaviour towards ALL other servers +# to which it sends proxy requests. +# +proxy server { + # + # Note that as of 2.0, the "synchronous", "retry_delay", + # "retry_count", and "dead_time" have all been deprecated. + # For backwards compatibility, they are are still accepted + # by the server, but they ONLY apply to the old-style realm + # configuration. i.e. realms with "authhost" and/or "accthost" + # entries. + # + # i.e. "retry_delay" and "retry_count" have been replaced + # with per-home-server configuration. See the "home_server" + # example below for details. + # + # i.e. "dead_time" has been replaced with a per-home-server + # "revive_interval". We strongly recommend that this not + # be used, however. The new method is much better. + + # + # In 2.0, the server is always "synchronous", and setting + # "synchronous = no" is impossible. This simplifies the + # server and increases the stability of the network. + # However, it means that the server (i.e. proxy) NEVER + # originates packets. It proxies packets ONLY when it receives + # a packet or a re-transmission from the NAS. If the NAS never + # re-transmits, the proxy never re-transmits, either. This can + # affect fail-over, where a packet does *not* fail over to a + # second home server.. because the NAS never retransmits the + # packet. + # + # If you need to set "synchronous = no", please send a + # message to the list <freeradius-users@lists.freeradius.org> + # explaining why this feature is vital for your network. + + # + # If a realm exists, but there are no live home servers for + # it, we can fall back to using the "DEFAULT" realm. This is + # most useful for accounting, where the server can proxy + # accounting requests to home servers, but if they're down, + # use a DEFAULT realm that is LOCAL (i.e. accthost = LOCAL), + # and then store the packets in the "detail" file. That data + # can be later proxied to the home servers by radrelay, when + # those home servers come back up again. + + # Setting this to "yes" may have issues for authentication. + # i.e. If you are proxying for two different ISP's, and then + # act as a general dial-up for Gric. If one of the first two + # ISP's has their RADIUS server go down, you do NOT want to + # proxy those requests to GRIC. Instead, you probably want + # to just drop the requests on the floor. In that case, set + # this value to 'no'. + # + # allowed values: {yes, no} + # + default_fallback = no + + # + # Whether or not we allow dynamic home servers. + # + # This setting should be "no" by default. If set to "yes", + # it can slow the server down, due to mutex locking across + # multiple threads. + # + # Dynamic servers will work ONLY with the "directory" + # configuration below. + # +# dynamic = yes + + # + # The directory which contains dynamic home servers. Each + # file in the directory should be a normal "home_server" + # definitions. This directory does not exist by default. + # + # e.g: The content of home_servers/example.com should be + # a home server definition. + # + # The name of the home server MUST be the same as the + # filename. + # + # Each home server must be set to only one type. e.g. + # "type = auth", and not "type = auth+acct" + # + # For example: + # + # home_server example.com { + # type = auth + # ipaddr = ... + # ... + # } + # + # For complete documentation, please see + # + # doc/configuration/dynamic_home_servers.md + # +# directory = ${confdir}/home_servers + +} + +####################################################################### +# +# Configuration for the proxy realms. +# +# As of 2.0, the "realm" configuration has changed. Instead of +# specifying "authhost" and "accthost" in a realm section, the home +# servers are specified separately in a "home_server" section. For +# backwards compatibility, you can still use the "authhost" and +# "accthost" directives. If you only have one home server for a +# realm, it is easier to use the old-style configuration. +# +# However, if you have multiple servers for a realm, we STRONGLY +# suggest moving to the new-style configuration. +# +# +# Load-balancing and failover between home servers is handled via +# a "home_server_pool" section. +# +# Finally, The "realm" section defines the realm, some options, and +# indicates which server pool should be used for the realm. +# +# This change means that simple configurations now require multiple +# sections to define a realm. However, complex configurations +# are much simpler than before, as multiple realms can share the same +# server pool. +# +# That is, realms point to server pools, and server pools point to +# home servers. Multiple realms can point to one server pool. One +# server pool can point to multiple home servers. Each home server +# can appear in one or more pools. +# +# See sites-available/tls for an example of configuring home servers, +# pools, and realms with TLS. +# + +###################################################################### +# +# This section defines a "Home Server" which is another RADIUS +# server that gets sent proxied requests. In earlier versions +# of FreeRADIUS, home servers were defined in "realm" sections, +# which was awkward. In 2.0, they have been made independent +# from realms, which is better for a number of reasons. +# +# You can proxy to a specific home server by doing: +# +# update control { +# Home-Server-Name = "name of home server" +# } +# +home_server localhost { + # + # Home servers can be sent Access-Request packets + # or Accounting-Request packets. + # + # Allowed values are: + # auth - Handles Access-Request packets + # acct - Handles Accounting-Request packets + # auth+acct - Handles Access-Request packets at "port", + # and Accounting-Request packets at "port + 1" + # coa - Handles CoA-Request and Disconnect-Request packets. + # See also raddb/sites-available/originate-coa + type = auth + + # + # Configure ONE OF the following entries: + # + # IPv4 address + # + ipaddr = 127.0.0.1 + + # OR IPv6 address + # ipv6addr = ::1 + + # OR virtual server + # virtual_server = foo + + # Note that while both ipaddr and ipv6addr will accept + # both addresses and host names, we do NOT recommend + # using host names. When you specify a host name, the + # server has to do a DNS lookup to find the IP address + # of the home server. If the DNS server is slow or + # unresponsive, it means that FreeRADIUS will NOT be + # able to determine the address, and will therefore NOT + # start. + # + # Also, the mapping of host name to address is done ONCE + # when the server starts. If DNS is later updated to + # change the address, FreeRADIUS will NOT discover that + # until after a re-start, or a HUP. + # + # If you specify a virtual_server here, then requests + # will be proxied internally to that virtual server. + # These requests CANNOT be proxied again, however. The + # intent is to have the local server handle packets + # when all home servers are dead. + # + # Unlike proxying to a regular home server, requests + # proxied to a virtual server will be passed through + # pre-proxy and post-proxy sections in the + # destination virtual server, rather than those in + # the virtual server currently processing the request. + # See also the sample "realm" configuration, below. + # + # None of the rest of the home_server configuration is used + # for the "virtual_server" configuration. + + # + # The port to which packets are sent. + # + # Usually 1812 for type "auth", and 1813 for type "acct". + # Older servers may use 1645 and 1646. + # Use 3799 for type "coa" + # + port = 1812 + + # + # The transport protocol. + # + # If unspecified, defaults to "udp", which is the traditional + # RADIUS transport. It may also be "tcp", in which case TCP + # will be used to talk to this home server. + # + # When home servers are put into pools, the pool can contain + # home servers with both UDP and TCP transports. + # + #proto = udp + + # + # The shared secret use to "encrypt" and "sign" packets between + # FreeRADIUS and the home server. + # + # The secret can be any string, up to 8k characters in length. + # + # Control codes can be entered vi octal encoding, + # e.g. "\101\102" == "AB" + # Quotation marks can be entered by escaping them, + # e.g. "foo\"bar" + # Spaces or other "special" characters can be entered + # by putting quotes around the string. + # e.g. "foo bar" + # "foo;bar" + # + secret = testing123 + + ############################################################ + # + # The rest of the configuration items listed here are optional, + # and do not have to appear in every home server definition. + # + ############################################################ + + # + # You can optionally specify the source IP address used when + # proxying requests to this home server. When the src_ipaddr + # it set, the server will automatically create a proxy + # listener for that IP address. + # + # If you specify this field for one home server, you will + # likely need to specify it for ALL home servers. + # + # If you don't care about the source IP address, leave this + # entry commented. + # +# src_ipaddr = 127.0.0.1 + + # + # If the home server does not respond to a request within + # this time, the server marks the request as timed out. + # After "response_timeouts", the home server is marked + # as being "zombie", and "zombie_period" starts. + # + # The response window can be a number between 0.001 and 60.000 + # Values on the low end are discouraged, as they will likely + # not work due to limitations of operating system timers. + # + # The default response window is large because responses may + # be slow, especially when proxying across the Internet. + # + # Useful range of values: 5 to 60 + response_window = 20 + + # + # Start "zombie_period" after this many responses have + # timed out. + # +# response_timeouts = 1 + + # + # If the home server does not respond to ANY packets during + # the "zombie period", it will be considered to be dead. + # + # A home server that is marked "zombie" will be used for + # proxying as a low priority. If there are live servers, + # they will always be preferred to a zombie. Requests will + # be proxied to a zombie server ONLY when there are no + # live servers. + # + # Any request that is proxied to a home server will continue + # to be sent to that home server until the home server is + # marked dead. At that point, it will fail over to another + # server, if a live server is available. If none is available, + # then the "post-proxy-type fail" handler will be called. + # + # If "status_check" below is something other than "none", then + # the server will start sending status checks at the start of + # the zombie period. It will continue sending status checks + # until the home server is marked "alive". + # + # Useful range of values: 20 to 120 + zombie_period = 40 + + ############################################################ + # + # As of 2.0, FreeRADIUS supports RADIUS layer "status + # checks". These are used by a proxy server to see if a home + # server is alive. + # + # These status packets are sent ONLY if the proxying server + # believes that the home server is dead. They are NOT sent + # if the proxying server believes that the home server is + # alive. They are NOT sent if the proxying server is not + # proxying packets. + # + # If the home server responds to the status check packet, + # then it is marked alive again, and is returned to use. + # + ############################################################ + + # + # Some home servers do not support status checks via the + # Status-Server packet. Others may not have a "test" user + # configured that can be used to query the server, to see if + # it is alive. For those servers, we have NO WAY of knowing + # when it becomes alive again. Therefore, after the server + # has been marked dead, we wait a period of time, and mark + # it alive again, in the hope that it has come back to + # life. + # + # If it has NOT come back to life, then FreeRADIUS will wait + # for "zombie_period" before marking it dead again. During + # the "zombie_period", ALL AUTHENTICATIONS WILL FAIL, because + # the home server is still dead. There is NOTHING that can + # be done about this, other than to enable the status checks, + # as documented below. + # + # e.g. if "zombie_period" is 40 seconds, and "revive_interval" + # is 300 seconds, the for 40 seconds out of every 340, or about + # 10% of the time, all authentications will fail. + # + # If the "zombie_period" and "revive_interval" configurations + # are set smaller, than it is possible for up to 50% of + # authentications to fail. + # + # As a result, we recommend enabling status checks, and + # we do NOT recommend using "revive_interval". + # + # The "revive_interval" is used ONLY if the "status_check" + # entry below is "none". Otherwise, it will not be used, + # and should be deleted. + # + # Useful range of values: 10 to 3600 + revive_interval = 120 + + # + # The proxying server (i.e. this one) can do periodic status + # checks to see if a dead home server has come back alive. + # + # If set to "none", then the other configuration items listed + # below are not used, and the "revive_interval" time is used + # instead. + # + # If set to "status-server", the Status-Server packets are + # sent. Many RADIUS servers support Status-Server. If a + # server does not support it, please contact the server + # vendor and request that they add it. With status-server if + # the home server is marked as a zombie and a status-server + # response is received, it will be immediately marked as live. + # + # This prevents spurious failovers in federations such as + # eduroam, where intermediary proxy servers may be functional + # but the servers of a home institution may not be, + # + # If set to "request", then Access-Request, or Accounting-Request + # packets are sent, depending on the "type" entry above (auth/acct). + # + # Allowed values: none, status-server, request + status_check = status-server + + # + # If the home server does not support Status-Server packets, + # then the server can still send Access-Request or + # Accounting-Request packets, with a pre-defined user name. + # + # This practice is NOT recommended, as it may potentially let + # users gain network access by using these "test" accounts! + # + # If it is used, we recommend that the home server ALWAYS + # respond to these Access-Request status checks with + # Access-Reject. The status check just needs an answer, it + # does not need an Access-Accept. + # + # For Accounting-Request status checks, only the username + # needs to be set. The rest of the accounting attribute are + # set to default values. The home server that receives these + # accounting packets SHOULD NOT treat them like normal user + # accounting packets. i.e It should probably NOT log them to + # a database. + # + # username = "test_user_please_reject_me" + # password = "this is really secret" + + # + # Configure the interval between sending status check packets. + # + # Setting it too low increases the probability of spurious + # fail-over and fallback attempts. + # + # Useful range of values: 6 to 120 + check_interval = 30 + + # + # Wait "check_timeout" seconds for a reply to a status check + # packet. + # + check_timeout = 4 + + # + # Configure the number of status checks in a row that the + # home server needs to respond to before it is marked alive. + # + # If you want to mark a home server as alive after a short + # time period of being responsive, it is best to use a small + # "check_interval", and a large value for + # "num_answers_to_alive". Using a long "check_interval" and + # a small number for "num_answers_to_alive" increases the + # probability of spurious fail-over and fallback attempts. + # + # Useful range of values: 3 to 10 + num_answers_to_alive = 3 + + # + # Limit the total number of outstanding packets to the home + # server. + # + # if ((#request sent) - (#requests received)) > max_outstanding + # then stop sending more packets to the home server + # + # This lets us gracefully fall over when the home server + # is overloaded. + max_outstanding = 65536 + + # + # The configuration items in the next sub-section are used ONLY + # when "type = coa". It is ignored for all other type of home + # servers. + # + # See RFC 5080 for the definitions of the following terms. + # RAND is a function (internal to FreeRADIUS) returning + # random numbers between -0.1 and +0.1 + # + # First Re-transmit occurs after: + # + # RT = IRT + RAND*IRT + # + # Subsequent Re-transmits occur after: + # + # RT = 2 * RTprev + RAND * RTprev + # + # Re-transmits are capped at: + # + # if (MRT && (RT > MRT)) RT = MRT + RAND * MRT + # + # For a maximum number of attempts: MRC + # + # For a maximum (total) period of time: MRD. + # + coa { + # Initial retransmit interval: 1..5 + irt = 2 + + # Maximum Retransmit Timeout: 1..30 (0 == no maximum) + mrt = 16 + + # Maximum Retransmit Count: 1..20 (0 == retransmit forever) + mrc = 5 + + # Maximum Retransmit Duration: 5..60 + mrd = 30 + } + + # + # Connection limiting for home servers with "proto = tcp". + # + # This section is ignored for other home servers. + # + limit { + # + # Limit the number of TCP connections to the home server. + # + # The default is 16. + # Setting this to 0 means "no limit" + max_connections = 16 + + # + # Limit the total number of requests sent over one + # TCP connection. After this number of requests, the + # connection will be closed. Any new packets that are + # proxied to the home server will result in a new TCP + # connection being made. + # + # Setting this to 0 means "no limit" + max_requests = 0 + + # + # The lifetime, in seconds, of a TCP connection. After + # this lifetime, the connection will be closed. + # + # Setting this to 0 means "forever". + lifetime = 0 + + # + # The idle timeout, in seconds, of a TCP connection. + # If no packets have been sent over the connection for + # this time, the connection will be closed. + # + # Setting this to 0 means "no timeout". + idle_timeout = 0 + } + +} + +# Sample virtual home server. +# +# +#home_server virtual.example.com { +# virtual_server = virtual.example.com +#} + +###################################################################### +# +# This section defines a pool of home servers that is used +# for fail-over and load-balancing. In earlier versions of +# FreeRADIUS, fail-over and load-balancing were defined per-realm. +# As a result, if a server had 5 home servers, each of which served +# the same 10 realms, you would need 50 "realm" entries. +# +# In version 2.0, you would need 5 "home_server" sections, +# 10 'realm" sections, and one "home_server_pool" section to tie the +# two together. +# +# You can proxy to a specific home server pool by doing: +# +# update control { +# Home-Server-Pool = "name of pool" +# } +# +home_server_pool my_auth_failover { + # + # The type of this pool controls how home servers are chosen. + # + # fail-over - the request is sent to the first live + # home server in the list. i.e. If the first home server + # is marked "dead", the second one is chosen, etc. + # + # load-balance - the least busy home server is chosen, + # where "least busy" is counted by taking the number of + # requests sent to that home server, and subtracting the + # number of responses received from that home server. + # + # If there are two or more servers with the same low + # load, then one of those servers is chosen at random. + # This configuration is most similar to the old + # "round-robin" method, though it is not exactly the same. + # + # Note that load balancing does not work well with EAP, + # as EAP requires packets for an EAP conversation to be + # sent to the same home server. The load balancing method + # does not keep state in between packets, meaning that + # EAP packets for the same conversation may be sent to + # different home servers. This will prevent EAP from + # working. + # + # For non-EAP authentication methods, and for accounting + # packets, we recommend using "load-balance". It will + # ensure the highest availability for your network. + # + # client-balance - the home server is chosen by hashing the + # source IP address of the packet. If that home server + # is down, the next one in the list is used, just as + # with "fail-over". + # + # There is no way of predicting which source IP will map + # to which home server. + # + # This configuration is most useful to do simple load + # balancing for EAP sessions, as the EAP session will + # always be sent to the same home server. + # + # client-port-balance - the home server is chosen by hashing + # the source IP address and source port of the packet. + # If that home server is down, the next one in the list + # is used, just as with "fail-over". + # + # This method provides slightly better load balancing + # for EAP sessions than "client-balance". However, it + # also means that authentication and accounting packets + # for the same session MAY go to different home servers. + # + # keyed-balance - the home server is chosen by hashing (FNV) + # the contents of the Load-Balance-Key attribute from the + # control items. The request is then sent to home server + # chosen by taking: + # + # server = (hash % num_servers_in_pool). + # + # If there is no Load-Balance-Key in the control items, + # the load balancing method is identical to "load-balance". + # + # For most non-EAP authentication methods, The User-Name + # attribute provides a good key. An "unlang" policy can + # be used to copy the User-Name to the Load-Balance-Key + # attribute. This method may not work for EAP sessions, + # as the User-Name outside of the TLS tunnel is often + # static, e.g. "anonymous@realm". + # + # + # The default type is fail-over. + type = fail-over + + # + # A virtual_server may be specified here. If so, the + # "pre-proxy" and "post-proxy" sections from the destination + # virtual server are run when the request is proxied and when + # a response is received. + # + # This lets you have one policy for all requests that are proxied + # to a home server. This policy is completely independent of + # any policies used to receive, or process the request. + # + #virtual_server = pre_post_proxy_for_pool + + # + # Next, a list of one or more home servers. The names + # of the home servers are NOT the hostnames, but the names + # of the sections. (e.g. home_server foo {...} has name "foo". + # + # Note that ALL home servers listed here have to be of the same + # type. i.e. they all have to be "auth", or they all have to + # be "acct", or the all have to be "auth+acct". + # + home_server = localhost + + # Additional home servers can be listed. + # There is NO LIMIT to the number of home servers that can + # be listed, though using more than 10 or so will become + # difficult to manage. + # + # home_server = foo.example.com + # home_server = bar.example.com + # home_server = baz.example.com + # home_server = ... + + + # + # If ALL home servers are dead, then this "fallback" home server + # is used. If set, it takes precedence over any realm-based + # fallback, such as the DEFAULT realm. + # + # For reasons of stability, this home server SHOULD be a virtual + # server. Otherwise, the fallback may itself be dead! + # + # Note: When packets are proxied to this fallback home server, + # if it is a virtual server, the pre-proxy and post-proxy sections + # from the destination virtual server will be run instead of those + # in the current virtual server. + # + #fallback = virtual.example.com +} + +###################################################################### +# +# +# This section defines a new-style "realm". Note the in version 2.0, +# there are many fewer configuration items than in 1.x for a realm. +# +# Automatic proxying is done via the "realms" module (see "man +# rlm_realm"). To manually proxy the request put this entry in the +# "users" file: + +# +# +#DEFAULT Proxy-To-Realm := "realm_name" +# +# +realm example.com { + # + # Realms point to pools of home servers. +# + # For authentication, the "auth_pool" configuration item + # should point to a "home_server_pool" that was previously + # defined. All of the home servers in the "auth_pool" must + # be of type "auth". + # + # For accounting, the "acct_pool" configuration item + # should point to a "home_server_pool" that was previously + # defined. All of the home servers in the "acct_pool" must + # be of type "acct". + # + # If you have a "home_server_pool" where all of the home servers + # are of type "auth+acct", you can just use the "pool" + # configuration item, instead of specifying both "auth_pool" + # and "acct_pool". + + auth_pool = my_auth_failover +# acct_pool = acct + + # The server can proxy CoA packets based on the Operator-Name + # attribute. This requires that the "suffix" module be + # listed in the "recv-coa" section. + # + # See raddb/sites-available/coa + # +# coa_pool = name_of_coa_pool + + # + # Normally, when an incoming User-Name is matched against the + # realm, the realm name is "stripped" off, and the "stripped" + # user name is used to perform matches. + # + # e.g. User-Name = "bob@example.com" will result in two new + # attributes being created by the "realms" module: + # + # Stripped-User-Name = "bob" + # Realm = "example.com" + # + # The Stripped-User-Name is then used as a key in the "users" + # file, for example. + # + # If you do not want this to happen, uncomment "nostrip" below. + # + # Note that if the system is doing EAP, you MUST set the "nostrip" + # option for realms used in EAP. Otherwise EAP will fail. + # + # nostrip + + # There are no more configuration entries for a realm. +} + + +# +# This is a sample entry for iPass. +# Note that you have to define "ipass_auth_pool" and +# "ipass_acct_pool", along with home_servers for them, too. +# +#realm IPASS { +# nostrip +# +# auth_pool = ipass_auth_pool +# acct_pool = ipass_acct_pool +#} + +# +# This realm is used mainly to cancel proxying. You can have +# the "realm suffix" module configured to proxy all requests for +# a realm, and then later cancel the proxying, based on other +# configuration. +# +# For example, you want to terminate PEAP or EAP-TTLS locally, +# you can add the following to the "users" file: +# +# DEFAULT EAP-Type == PEAP, Proxy-To-Realm := LOCAL +# +realm LOCAL { + # If we do not specify a server pool, the realm is LOCAL, and + # requests are not proxied to it. +} + +# +# This realm is for requests which don't have an explicit realm +# prefix or suffix. User names like "bob" will match this one. +# +#realm NULL { +# authhost = radius.example.com:1600 +# accthost = radius.example.com:1601 +# secret = testing123 +#} + +# +# This realm is for ALL OTHER requests. +# +#realm DEFAULT { +# authhost = radius.example.com:1600 +# accthost = radius.example.com:1601 +# secret = testing123 +#} + + +# This realm "proxies" requests internally to a virtual server. +# The pre-proxy and post-proxy sections from the destination +# virtual server are run rather than those in the current +# virtual server. The destination virtual server then receives +# the request, and replies, just as with any other packet. +# +# Once proxied internally like this, the request CANNOT be proxied +# internally or externally. +# +# Realms are almost always domain names, and therefore realm names +# are compared in a case-insensitive fashion. +# +#realm virtual.example.com { +# virtual_server = virtual.example.com +#} +# + +# +# Regular expressions may also be used as realm names. If these are used, +# then the "find matching realm" process is as follows: +# +# 1) Look for a non-regex realm with an *exact* match for the name. +# If found, it is used in preference to any regex matching realm. +# +# 2) Look for a regex realm, in the order that they are listed +# in the configuration files. Any regex match is performed in +# a case-insensitive fashion. +# +# 3) If no realm is found, return the DEFAULT realm, if any. +# +# The order of the realms matters in step (2). For example, defining +# two realms ".*\.example.net$" and ".*\.test\.example\.net$" will result in +# the second realm NEVER matching. This is because all of the realms +# which match the second regex also match the first one. Since the +# first regex matches, it is returned. +# +# The solution is to list the realms in the opposite order,. e.g. +# ".*\.test\.example.net$", followed by ".*\.example\.net$". +# +# +# Some helpful rules: +# +# - always place a '~' character at the start of the realm name. +# This signifies that it is a regex match, and not an exact match +# for the realm. +# +# - place the regex in double quotes. This helps the configuration +# file parser ignore any "special" characters in the regex. +# Yes, this rule is different than the normal "unlang" rules for +# regular expressions. That may be fixed in a future release. +# +# - If you are matching domain names, put a '$' at the end of the regex +# that matches the domain name. This tells the regex matching code +# that the realm ENDS with the domain name, so it does not match +# realms with the domain name in the middle. e.g. "~.*\.example\.net" +# will match "test.example.netFOO", which is likely not what you want. +# Using "~(.*\.)example\.net$" is better. +# +# The more regex realms that are defined, the more time it takes to +# process them. You should define as few regex realms as possible +# in order to maximize server performance. +# +#realm "~(.*\.)*example\.net$" { +# auth_pool = my_auth_failover +#} diff --git a/raddb/radiusd.conf.in b/raddb/radiusd.conf.in new file mode 100644 index 0000000..366dce4 --- /dev/null +++ b/raddb/radiusd.conf.in @@ -0,0 +1,902 @@ +# -*- text -*- +## +## radiusd.conf -- FreeRADIUS server configuration file - @RADIUSD_VERSION_STRING@ +## +## http://www.freeradius.org/ +## $Id$ +## + +###################################################################### +# +# The format of this (and other) configuration file is +# documented in "man unlang". There are also READMEs in many +# subdirectories: +# +# raddb/README.rst +# How to upgrade from v2. +# +# raddb/mods-available/README.rst +# How to use mods-available / mods-enabled. +# All of the modules are in individual files, +# along with configuration items and full documentation. +# +# raddb/sites-available/README +# virtual servers, "listen" sections, clients, etc. +# The "sites-available" directory contains many +# worked examples of common configurations. +# +# raddb/certs/README.md +# How to create certificates for EAP or RadSec. +# +# Every configuration item in the server is documented +# extensively in the comments in the example configuration +# files. +# +# Before editing this (or any other) configuration file, PLEASE +# read "man radiusd". See the section titled DEBUGGING. It +# outlines a method where you can quickly create the +# configuration you want, with minimal effort. +# +# Run the server in debugging mode, and READ the output. +# +# $ radiusd -X +# +# We cannot emphasize this point strongly enough. The vast +# majority of problems can be solved by carefully reading the +# debugging output, which includes warnings about common issues, +# and suggestions for how they may be fixed. +# +# There may be a lot of output, but look carefully for words like: +# "warning", "error", "reject", or "failure". The messages there +# will usually be enough to guide you to a solution. +# +# More documentation on "radiusd -X" is available on the wiki: +# https://wiki.freeradius.org/radiusd-X +# +# If you are going to ask a question on the mailing list, then +# explain what you are trying to do, and include the output from +# debugging mode (radiusd -X). Failure to do so means that all +# of the responses to your question will be people telling you +# to "post the output of radiusd -X". +# +# Guidelines for posting to the mailing list are on the wiki: +# https://wiki.freeradius.org/list-help +# +# Please read those guidelines before posting to the list. +# +# Further documentation is available in the "doc" directory +# of the server distribution, or on the wiki at: +# https://wiki.freeradius.org/ +# +# New users to RADIUS should read the Technical Guide. That guide +# explains how RADIUS works, how FreeRADIUS works, and what each +# part of a RADIUS system does. It is not just "configure FreeRADIUS"! +# https://networkradius.com/doc/FreeRADIUS-Technical-Guide.pdf +# +# More documentation on dictionaries, modules, unlang, etc. is also +# available on the Network RADIUS web site: +# https://networkradius.com/freeradius-documentation/ +# + +###################################################################### + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +sysconfdir = @sysconfdir@ +localstatedir = @localstatedir@ +sbindir = @sbindir@ +logdir = @logdir@ +raddbdir = @raddbdir@ +radacctdir = @radacctdir@ + +# +# name of the running server. See also the "-n" command-line option. +name = radiusd + +# Location of config and logfiles. +confdir = ${raddbdir} +modconfdir = ${confdir}/mods-config +certdir = ${confdir}/certs +cadir = ${confdir}/certs +run_dir = ${localstatedir}/run/${name} + +# Should likely be ${localstatedir}/lib/radiusd +db_dir = ${raddbdir} + +# +# libdir: Where to find the rlm_* modules. +# +# This should be automatically set at configuration time. +# +# If the server builds and installs, but fails at execution time +# with an 'undefined symbol' error, then you can use the libdir +# directive to work around the problem. +# +# The cause is usually that a library has been installed on your +# system in a place where the dynamic linker CANNOT find it. When +# executing as root (or another user), your personal environment MAY +# be set up to allow the dynamic linker to find the library. When +# executing as a daemon, FreeRADIUS MAY NOT have the same +# personalized configuration. +# +# To work around the problem, find out which library contains that symbol, +# and add the directory containing that library to the end of 'libdir', +# with a colon separating the directory names. NO spaces are allowed. +# +# e.g. libdir = /usr/local/lib:/opt/package/lib +# +# You can also try setting the LD_LIBRARY_PATH environment variable +# in a script which starts the server. +# +# If that does not work, then you can re-configure and re-build the +# server to NOT use shared libraries, via: +# +# ./configure --disable-shared +# make +# make install +# +libdir = @libdir@ + +# pidfile: Where to place the PID of the RADIUS server. +# +# The server may be signalled while it's running by using this +# file. +# +# This file is written when ONLY running in daemon mode. +# +# e.g.: kill -HUP `cat /var/run/radiusd/radiusd.pid` +# +pidfile = ${run_dir}/${name}.pid + +# panic_action: Command to execute if the server dies unexpectedly. +# +# FOR PRODUCTION SYSTEMS, ACTIONS SHOULD ALWAYS EXIT. +# AN INTERACTIVE ACTION MEANS THE SERVER IS NOT RESPONDING TO REQUESTS. +# AN INTERACTICE ACTION MEANS THE SERVER WILL NOT RESTART. +# +# THE SERVER MUST NOT BE ALLOWED EXECUTE UNTRUSTED PANIC ACTION CODE +# PATTACH CAN BE USED AS AN ATTACK VECTOR. +# +# The panic action is a command which will be executed if the server +# receives a fatal, non user generated signal, i.e. SIGSEGV, SIGBUS, +# SIGABRT or SIGFPE. +# +# This can be used to start an interactive debugging session so +# that information regarding the current state of the server can +# be acquired. +# +# The following string substitutions are available: +# - %e The currently executing program e.g. /sbin/radiusd +# - %p The PID of the currently executing program e.g. 12345 +# +# Standard ${} substitutions are also allowed. +# +# An example panic action for opening an interactive session in GDB would be: +# +#panic_action = "gdb %e %p" +# +# Again, don't use that on a production system. +# +# An example panic action for opening an automated session in GDB would be: +# +#panic_action = "gdb -silent -x ${raddbdir}/panic.gdb %e %p 2>&1 | tee ${logdir}/gdb-${name}-%p.log" +# +# That command can be used on a production system. +# + +# max_request_time: The maximum time (in seconds) to handle a request. +# +# Requests which take more time than this to process may be killed, and +# a REJECT message is returned. +# +# WARNING: If you notice that requests take a long time to be handled, +# then this MAY INDICATE a bug in the server, in one of the modules +# used to handle a request, OR in your local configuration. +# +# This problem is most often seen when using an SQL database. If it takes +# more than a second or two to receive an answer from the SQL database, +# then it probably means that you haven't indexed the database. See your +# SQL server documentation for more information. +# +# Useful range of values: 5 to 120 +# +max_request_time = 30 + +# cleanup_delay: The time to wait (in seconds) before cleaning up +# a reply which was sent to the NAS. +# +# The RADIUS request is normally cached internally for a short period +# of time, after the reply is sent to the NAS. The reply packet may be +# lost in the network, and the NAS will not see it. The NAS will then +# re-send the request, and the server will respond quickly with the +# cached reply. +# +# If this value is set too low, then duplicate requests from the NAS +# MAY NOT be detected, and will instead be handled as separate requests. +# +# If this value is set too high, then the server will cache too many +# requests, and some new requests may get blocked. (See 'max_requests'.) +# +# Useful range of values: 2 to 30 +# +cleanup_delay = 5 + +# max_requests: The maximum number of requests which the server keeps +# track of. This should be 256 multiplied by the number of clients. +# e.g. With 4 clients, this number should be 1024. +# +# If this number is too low, then when the server becomes busy, +# it will not respond to any new requests, until the 'cleanup_delay' +# time has passed, and it has removed the old requests. +# +# If this number is set too high, then the server will use a bit more +# memory for no real benefit. +# +# If you aren't sure what it should be set to, it's better to set it +# too high than too low. Setting it to 1000 per client is probably +# the highest it should be. +# +# Useful range of values: 256 to infinity +# +max_requests = 16384 + +# hostname_lookups: Log the names of clients or just their IP addresses +# e.g., www.freeradius.org (on) or 206.47.27.232 (off). +# +# The default is 'off' because it would be overall better for the net +# if people had to knowingly turn this feature on, since enabling it +# means that each client request will result in AT LEAST one lookup +# request to the nameserver. Enabling hostname_lookups will also +# mean that your server may stop randomly for 30 seconds from time +# to time, if the DNS requests take too long. +# +# Turning hostname lookups off also means that the server won't block +# for 30 seconds, if it sees an IP address which has no name associated +# with it. +# +# allowed values: {no, yes} +# +hostname_lookups = no + +# +# Run a "Post-Auth-Type Client-Lost" section. This ONLY happens when +# the server sends an Access-Challenge, and then client does not +# respond to it. The goal is to allow administrators to log +# something when the client does not respond. +# +# See sites-available/default, "Post-Auth-Type Client-Lost" for more +# information. +# +#postauth_client_lost = no + +# +# Logging section. The various "log_*" configuration items +# will eventually be moved here. +# +log { + # + # Destination for log messages. This can be one of: + # + # files - log to "file", as defined below. + # syslog - to syslog (see also the "syslog_facility", below. + # stdout - standard output + # stderr - standard error. + # + # The command-line option "-X" over-rides this option, and forces + # logging to go to stdout. + # + destination = files + + # + # Highlight important messages sent to stderr and stdout. + # + # Option will be ignored (disabled) if output if TERM is not + # an xterm or output is not to a TTY. + # + colourise = yes + + # + # The logging messages for the server are appended to the + # tail of this file if destination == "files" + # + # If the server is running in debugging mode, this file is + # NOT used. + # + file = ${logdir}/radius.log + + # + # Which syslog facility to use, if ${destination} == "syslog" + # + # The exact values permitted here are OS-dependent. You probably + # don't want to change this. + # + syslog_facility = daemon + + # Log the full User-Name attribute, as it was found in the request. + # + # allowed values: {no, yes} + # + stripped_names = no + + # Log all (accept and reject) authentication results to the log file. + # + # This is the same as setting "auth_accept = yes" and + # "auth_reject = yes" + # + # allowed values: {no, yes} + # + auth = no + + # Log Access-Accept results to the log file. + # + # This is only used if "auth = no" + # + # allowed values: {no, yes} + # +# auth_accept = no + + # Log Access-Reject results to the log file. + # + # This is only used if "auth = no" + # + # allowed values: {no, yes} + # +# auth_reject = no + + # Log passwords with the authentication requests. + # auth_badpass - logs password if it's rejected + # auth_goodpass - logs password if it's correct + # + # allowed values: {no, yes} + # + auth_badpass = no + auth_goodpass = no + + # Log additional text at the end of the "Login OK" messages. + # for these to work, the "auth" and "auth_goodpass" or "auth_badpass" + # configurations above have to be set to "yes". + # + # The strings below are dynamically expanded, which means that + # you can put anything you want in them. However, note that + # this expansion can be slow, and can negatively impact server + # performance. + # +# msg_goodpass = "" +# msg_badpass = "" + + # The message when the user exceeds the Simultaneous-Use limit. + # + msg_denied = "You are already logged in - access denied" + + # Suppress "secret" attributes when printing them in debug mode. + # + # Secrets are NOT tracked across xlat expansions. If your + # configuration puts secrets into other strings, they will + # still get printed. + # + # Setting this to "yes" means that the server prints + # + # <<< secret >>> + # + # instead of the value, for attriburtes which contain secret + # information. e.g. User-Name, Tunnel-Password, etc. + # + # This configuration is disabled by default. It is extremely + # important for administrators to be able to debug user logins + # by seeing what is actually being sent. + # +# suppress_secrets = no +} + +# The program to execute to do concurrency checks. +checkrad = ${sbindir}/checkrad + +# +# ENVIRONMENT VARIABLES +# +# You can reference environment variables using an expansion like +# `$ENV{PATH}`. However it is sometimes useful to be able to also set +# environment variables. This section lets you do that. +# +# The main purpose of this section is to allow administrators to keep +# RADIUS-specific configuration in the RADIUS configuration files. +# For example, if you need to set an environment variable which is +# used by a module. You could put that variable into a shell script, +# but that's awkward. Instead, just list it here. +# +# Note that these environment variables are set AFTER the +# configuration file is loaded. So you cannot set FOO here, and +# expect to reference it via `$ENV{FOO}` in another configuration file. +# You should instead just use a normal configuration variable for +# that. +# +ENV { + # + # Set environment varable `FOO` to value '/bar/baz'. + # + # NOTE: Note that you MUST use '='. You CANNOT use '+=' to append + # values. + # +# FOO = '/bar/baz' + + # + # Delete environment variable `BAR`. + # +# BAR + + # + # `LD_PRELOAD` is special. It is normally set before the + # application runs, and is interpreted by the dynamic linker. + # Which means you cannot set it inside of an application, and + # expect it to load libraries. + # + # Since this functionality is useful, we extend it here. + # + # You can set + # + # LD_PRELOAD = /path/to/library.so + # + # and the server will load the named libraries. Multiple + # libraries can be loaded by specificing multiple individual + # `LD_PRELOAD` entries. + # + # +# LD_PRELOAD = /path/to/library1.so +# LD_PRELOAD = /path/to/library2.so +} + +# SECURITY CONFIGURATION +# +# There may be multiple methods of attacking on the server. This +# section holds the configuration items which minimize the impact +# of those attacks +# +security { + # chroot: directory where the server does "chroot". + # + # The chroot is done very early in the process of starting + # the server. After the chroot has been performed it + # switches to the "user" listed below (which MUST be + # specified). If "group" is specified, it switches to that + # group, too. Any other groups listed for the specified + # "user" in "/etc/group" are also added as part of this + # process. + # + # The current working directory (chdir / cd) is left + # *outside* of the chroot until all of the modules have been + # initialized. This allows the "raddb" directory to be left + # outside of the chroot. Once the modules have been + # initialized, it does a "chdir" to ${logdir}. This means + # that it should be impossible to break out of the chroot. + # + # If you are worried about security issues related to this + # use of chdir, then simply ensure that the "raddb" directory + # is inside of the chroot, and be sure to do "cd raddb" + # BEFORE starting the server. + # + # If the server is statically linked, then the only files + # that have to exist in the chroot are ${run_dir} and + # ${logdir}. If you do the "cd raddb" as discussed above, + # then the "raddb" directory has to be inside of the chroot + # directory, too. + # +# chroot = /path/to/chroot/directory + + # user/group: The name (or #number) of the user/group to run radiusd as. + # + # If these are commented out, the server will run as the + # user/group that started it. In order to change to a + # different user/group, you MUST be root ( or have root + # privileges ) to start the server. + # + # We STRONGLY recommend that you run the server with as few + # permissions as possible. That is, if you're not using + # shadow passwords, the user and group items below should be + # set to radius'. + # + # NOTE that some kernels refuse to setgid(group) when the + # value of (unsigned)group is above 60000; don't use group + # "nobody" on these systems! + # + # On systems with shadow passwords, you might have to set + # 'group = shadow' for the server to be able to read the + # shadow password file. If you can authenticate users while + # in debug mode, but not in daemon mode, it may be that the + # debugging mode server is running as a user that can read + # the shadow info, and the user listed below can not. + # + # The server will also try to use "initgroups" to read + # /etc/groups. It will join all groups where "user" is a + # member. This can allow for some finer-grained access + # controls. + # +# user = radius +# group = radius + + # Core dumps are a bad thing. This should only be set to + # 'yes' if you're debugging a problem with the server. + # + # allowed values: {no, yes} + # + allow_core_dumps = no + + # + # max_attributes: The maximum number of attributes + # permitted in a RADIUS packet. Packets which have MORE + # than this number of attributes in them will be dropped. + # + # If this number is set too low, then no RADIUS packets + # will be accepted. + # + # If this number is set too high, then an attacker may be + # able to send a small number of packets which will cause + # the server to use all available memory on the machine. + # + # Setting this number to 0 means "allow any number of attributes" + max_attributes = 200 + + # + # reject_delay: When sending an Access-Reject, it can be + # delayed for a few seconds. This may help slow down a DoS + # attack. It also helps to slow down people trying to brute-force + # crack a users password. + # + # Setting this number to 0 means "send rejects immediately" + # + # If this number is set higher than 'cleanup_delay', then the + # rejects will be sent at 'cleanup_delay' time, when the request + # is deleted from the internal cache of requests. + # + # This number can be a decimal, e.g. 3.4 + # + # Useful ranges: 1 to 5 + reject_delay = 1 + + # + # status_server: Whether or not the server will respond + # to Status-Server requests. + # + # When sent a Status-Server message, the server responds with + # an Access-Accept or Accounting-Response packet. + # + # This is mainly useful for administrators who want to "ping" + # the server, without adding test users, or creating fake + # accounting packets. + # + # It's also useful when a NAS marks a RADIUS server "dead". + # The NAS can periodically "ping" the server with a Status-Server + # packet. If the server responds, it must be alive, and the + # NAS can start using it for real requests. + # + # See also raddb/sites-available/status + # + status_server = yes + +@openssl_version_check_config@ +} + +# PROXY CONFIGURATION +# +# proxy_requests: Turns proxying of RADIUS requests on or off. +# +# The server has proxying turned on by default. If your system is NOT +# set up to proxy requests to another server, then you can turn proxying +# off here. This will save a small amount of resources on the server. +# +# If you have proxying turned off, and your configuration files say +# to proxy a request, then an error message will be logged. +# +# To disable proxying, change the "yes" to "no", and comment the +# $INCLUDE line. +# +# allowed values: {no, yes} +# +proxy_requests = yes +$INCLUDE proxy.conf + + +# CLIENTS CONFIGURATION +# +# Client configuration is defined in "clients.conf". +# + +# The 'clients.conf' file contains all of the information from the old +# 'clients' and 'naslist' configuration files. We recommend that you +# do NOT use 'client's or 'naslist', although they are still +# supported. +# +# Anything listed in 'clients.conf' will take precedence over the +# information from the old-style configuration files. +# +$INCLUDE clients.conf + + +# THREAD POOL CONFIGURATION +# +# The thread pool is a long-lived group of threads which +# take turns (round-robin) handling any incoming requests. +# +# You probably want to have a few spare threads around, +# so that high-load situations can be handled immediately. If you +# don't have any spare threads, then the request handling will +# be delayed while a new thread is created, and added to the pool. +# +# You probably don't want too many spare threads around, +# otherwise they'll be sitting there taking up resources, and +# not doing anything productive. +# +# The numbers given below should be adequate for most situations. +# +thread pool { + # Number of servers to start initially --- should be a reasonable + # ballpark figure. + start_servers = 5 + + # Limit on the total number of servers running. + # + # If this limit is ever reached, clients will be LOCKED OUT, so it + # should NOT BE SET TOO LOW. It is intended mainly as a brake to + # keep a runaway server from taking the system with it as it spirals + # down... + # + # You may find that the server is regularly reaching the + # 'max_servers' number of threads, and that increasing + # 'max_servers' doesn't seem to make much difference. + # + # If this is the case, then the problem is MOST LIKELY that + # your back-end databases are taking too long to respond, and + # are preventing the server from responding in a timely manner. + # + # The solution is NOT do keep increasing the 'max_servers' + # value, but instead to fix the underlying cause of the + # problem: slow database, or 'hostname_lookups=yes'. + # + # For more information, see 'max_request_time', above. + # + max_servers = 32 + + # Server-pool size regulation. Rather than making you guess + # how many servers you need, FreeRADIUS dynamically adapts to + # the load it sees, that is, it tries to maintain enough + # servers to handle the current load, plus a few spare + # servers to handle transient load spikes. + # + # It does this by periodically checking how many servers are + # waiting for a request. If there are fewer than + # min_spare_servers, it creates a new spare. If there are + # more than max_spare_servers, some of the spares die off. + # The default values are probably OK for most sites. + # + min_spare_servers = 3 + max_spare_servers = 10 + + # When the server receives a packet, it places it onto an + # internal queue, where the worker threads (configured above) + # pick it up for processing. The maximum size of that queue + # is given here. + # + # When the queue is full, any new packets will be silently + # discarded. + # + # The most common cause of the queue being full is that the + # server is dependent on a slow database, and it has received + # a large "spike" of traffic. When that happens, there is + # very little you can do other than make sure the server + # receives less traffic, or make sure that the database can + # handle the load. + # +# max_queue_size = 65536 + + # Clean up old threads periodically. For no reason other than + # it might be useful. + # + # '0' is a special value meaning 'infinity', or 'the servers never + # exit' + max_requests_per_server = 0 + + # Automatically limit the number of accounting requests. + # This configuration item tracks how many requests per second + # the server can handle. It does this by tracking the + # packets/s received by the server for processing, and + # comparing that to the packets/s handled by the child + # threads. + # + + # If the received PPS is larger than the processed PPS, *and* + # the queue is more than half full, then new accounting + # requests are probabilistically discarded. This lowers the + # number of packets that the server needs to process. Over + # time, the server will "catch up" with the traffic. + # + # Throwing away accounting packets is usually safe and low + # impact. The NAS will retransmit them in a few seconds, or + # even a few minutes. Vendors should read RFC 5080 Section 2.2.1 + # to see how accounting packets should be retransmitted. Using + # any other method is likely to cause network meltdowns. + # + auto_limit_acct = no +} + +###################################################################### +# +# SNMP notifications. Uncomment the following line to enable +# snmptraps. Note that you MUST also configure the full path +# to the "snmptrap" command in the "trigger.conf" file. +# +#$INCLUDE trigger.conf + +# MODULE CONFIGURATION +# +# The names and configuration of each module is located in this section. +# +# After the modules are defined here, they may be referred to by name, +# in other sections of this configuration file. +# +modules { + # + # Each module has a configuration as follows: + # + # name [ instance ] { + # config_item = value + # ... + # } + # + # The 'name' is used to load the 'rlm_name' library + # which implements the functionality of the module. + # + # The 'instance' is optional. To have two different instances + # of a module, it first must be referred to by 'name'. + # The different copies of the module are then created by + # inventing two 'instance' names, e.g. 'instance1' and 'instance2' + # + # The instance names can then be used in later configuration + # INSTEAD of the original 'name'. See the 'radutmp' configuration + # for an example. + # + + # + # Some modules have ordering issues. e.g. "sqlippool" uses + # the configuration from "sql". In that case, the "sql" + # module must be read off of disk before the "sqlippool". + # However, the directory inclusion below just reads the + # directory from start to finish. Which means that the + # modules are read off of disk randomly. + # + # You can list individual modules *before* the directory + # inclusion. Those modules will be loaded first. Then, when + # the directory is read, those modules will be skipped and + # not read twice. + # +# $INCLUDE mods-enabled/sql + + # + # All modules are in ther mods-enabled/ directory. Files + # matching the regex /[a-zA-Z0-9_.]+/ are read. The + # modules are initialized ONLY if they are referenced in a + # processing section, such as authorize, authenticate, + # accounting, pre/post-proxy, etc. + # + $INCLUDE mods-enabled/ +} + +# Instantiation +# +# This section sets the instantiation order of the modules. listed +# here will get started up BEFORE the sections like authorize, +# authenticate, etc. get examined. +# +# This section is not strictly needed. When a section like authorize +# refers to a module, the module is automatically loaded and +# initialized. However, some modules may not be listed in any of the +# processing sections, so they should be listed here. +# +# Also, listing modules here ensures that you have control over +# the order in which they are initialized. If one module needs +# something defined by another module, you can list them in order +# here, and ensure that the configuration will be OK. +# +# After the modules listed here have been loaded, all of the modules +# in the "mods-enabled" directory will be loaded. Loading the +# "mods-enabled" directory means that unlike Version 2, you usually +# don't need to list modules here. +# +instantiate { + # + # We list the counter module here so that it registers + # the check_name attribute before any module which sets + # it +# daily + + # subsections here can be thought of as "virtual" modules. + # + # e.g. If you have two redundant SQL servers, and you want to + # use them in the authorize and accounting sections, you could + # place a "redundant" block in each section, containing the + # exact same text. Or, you could uncomment the following + # lines, and list "redundant_sql" in the authorize and + # accounting sections. + # + # The "virtual" module defined here can also be used with + # dynamic expansions, under a few conditions: + # + # * The section is "redundant", or "load-balance", or + # "redundant-load-balance" + # * The section contains modules ONLY, and no sub-sections + # * all modules in the section are using the same rlm_ + # driver, e.g. They are all sql, or all ldap, etc. + # + # When those conditions are satisfied, the server will + # automatically register a dynamic expansion, using the + # name of the "virtual" module. In the example below, + # it will be "redundant_sql". You can then use this expansion + # just like any other: + # + # update reply { + # Filter-Id := "%{redundant_sql: ... }" + # } + # + # In this example, the expansion is done via module "sql1", + # and if that expansion fails, using module "sql2". + # + # For best results, configure the "pool" subsection of the + # module so that "retry_delay" is non-zero. That will allow + # the redundant block to quickly ignore all "down" SQL + # databases. If instead we have "retry_delay = 0", then + # every time the redundant block is used, the server will try + # to open a connection to every "down" database, causing + # problems. + # + #redundant redundant_sql { + # sql1 + # sql2 + #} +} + +###################################################################### +# +# Policies are virtual modules, similar to those defined in the +# "instantiate" section above. +# +# Defining a policy in one of the policy.d files means that it can be +# referenced in multiple places as a *name*, rather than as a series of +# conditions to match, and actions to take. +# +# Policies are something like subroutines in a normal language, but +# they cannot be called recursively. They MUST be defined in order. +# If policy A calls policy B, then B MUST be defined before A. +# +###################################################################### +policy { + $INCLUDE policy.d/ +} + +###################################################################### +# +# Load virtual servers. +# +# This next $INCLUDE line loads files in the directory that +# match the regular expression: /[a-zA-Z0-9_.]+/ +# +# It allows you to define new virtual servers simply by placing +# a file into the raddb/sites-enabled/ directory. +# +$INCLUDE sites-enabled/ + +###################################################################### +# +# All of the other configuration sections like "authorize {}", +# "authenticate {}", "accounting {}", have been moved to the +# the file: +# +# raddb/sites-available/default +# +# This is the "default" virtual server that has the same +# configuration as in version 1.0.x and 1.1.x. The default +# installation enables this virtual server. You should +# edit it to create policies for your local site. +# +# For more documentation on virtual servers, see: +# +# raddb/sites-available/README +# +###################################################################### diff --git a/raddb/radrelay.conf.in b/raddb/radrelay.conf.in new file mode 100644 index 0000000..b707b34 --- /dev/null +++ b/raddb/radrelay.conf.in @@ -0,0 +1,170 @@ +# -*- text -*- +## +## radrelay.conf -- FreeRADIUS server configuration file. +## +## Use with: radiusd -n radrelay +## +## http://www.freeradius.org/ +## $Id$ +## + +###################################################################### +# +# This file is a sample configuration that replaces the old +# "radrelay" program. It is a *minimal* configuration that +# does little more than read the detail file, and proxy the +# packets to a home server. If you need it to do more than +# just replace radrelay, you will need to add additional +# configuration. +# +# See raddb/sites-available/copy-acct-to-home-server for a +# more complete example. That example is intended to be run +# as part of a larger radius configuration, where the server +# also listens on ports 1812, etc. The example given here +# is a minimal example that has ONLY radrelay functionality. +# +# See radiusd.conf for a complete description of the configuration +# parameters used here. +# +###################################################################### + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +sysconfdir = @sysconfdir@ +localstatedir = @localstatedir@ +sbindir = @sbindir@ +logdir = @logdir@ +raddbdir = @raddbdir@ +radacctdir = @radacctdir@ + +# +# name of the running server. See also the "-n" command-line option. +# +name = radrelay + +# +# Generic configuration +# +confdir = ${raddbdir} +run_dir = ${localstatedir}/run/${name} +libdir = @libdir@ +pidfile = ${run_dir}/${name}.pid + +# +# Request handling configuration +# +max_request_time = 30 +cleanup_delay = 5 +max_requests = 65536 + +# +# Logging section. +# +log { + destination = files + file = ${logdir}/${name}.log +} + +# +# Security configuration +# +security { + max_attributes = 200 + + # reject_delay && status_server don't apply when we are + # only reading accounting packets from the detail file + +@openssl_version_check_config@ +} + +# +# If you need more modules, add them here. +# +modules { + $INCLUDE ${confdir}/mods-enabled/always +} + +# +# If you need to instantiate modules, add them here. +# +instantiate { +} + +# +# Configuration of home servers, etc. +# +proxy_requests = yes + +# +# See proxy.conf for additional home server configuration. +# +home_server home1 { + type = acct + + # + # This directive replaces the "-r" command-line option + # in radrelay + # + ipaddr = 192.0.2.20 + + port = 1812 + + # + # This directive replaces the "-i" command-line option + # in radrelay + # +# src_ipaddr = 192.0.2.1 + + # + # This directive replaces the "-s", "-S", and "-n" command-line + # options in radrelay + # + secret = testing123 +} + +# +# List one or more home servers here for fail-over, load-balancing, etc. +# +home_server_pool radrelay { + type = fail-over + home_server = home1 +} + +# +# A dummy realm. +# +realm radrelay { + acct_pool = radrelay +} + +server radrelay { + # + # Read the detail file. + # + listen { + type = detail + + # + # The filename here should be the same as the one used by the + # main radiusd program. It writes the file using the "detail" + # module (see raddb/modules/detail). + # + filename = ${radacctdir}/detail + load_factor = 90 + } + + # + # See also raddb/sites-available/copy-acct-to-home-server + # for additional description. + # + preacct { + # + # Proxy the packet using the given realm. + # Note that we do not use the realm for anything else such + # as prefix/suffix stripping or comparisons. + # + update control { + Proxy-To-Realm := "radrelay" + } + } +} diff --git a/raddb/sites-available/README b/raddb/sites-available/README new file mode 100644 index 0000000..0805a75 --- /dev/null +++ b/raddb/sites-available/README @@ -0,0 +1,333 @@ +1. Virtual Servers. + + FreeRADIUS supports virtual servers. The virtual servers do NOT have +to be set up with the "sites-available" and "sites-enabled" +directories. You can still have one "radiusd.conf" file, and put the +server configuration there: + + ... + server { + authorize { + ... + } + authenticate { + ... + } + ... + } + ... + + The power of virtual servers lies in their ability to separate +policies. A policy can be placed into a virtual server, where it is +guaranteed to affect only the requests that are passed through that +virtual server. In 1.x, the policies were global, and it sometimes +took much effort to write a policy so that it only applied in certain +limited situations. + + +2. What do we mean by "virtual server"? + + + A virtual server is a (nearly complete) RADIUS server, just like a +configuration for FreeRADIUS 1.x. However, FreeRADIUS can now run +multiple virtual servers at the same time. The virtual servers can +even proxy requests to each other! + + The simplest way to create a virtual server is to take the all of +the request processing sections from radius.conf, ("authorize" , +"authenticate", etc.) and wrap them in a "server {}" block, as above. + + You can create another virtual server by: + + 1) defining a new "server foo {...}" section in radiusd.conf + 2) Putting the normal "authorize", etc. sections inside of it + 3) Adding a "listen" section *inside* of the "server" section. + + e.g. + + ... + server foo { + listen { + ipaddr = 127.0.0.1 + port = 2000 + type = auth + } + + authorize { + update control { + Cleartext-Password := "bob" + } + pap + } + + authenticate { + pap + } + } + ... + + With that text added to "radiusd.conf", run the server in debugging +mode (radiusd -X), and in another terminal window, type: + +$ radtest bob bob localhost:2000 0 testing123 + + You should see the server return an Access-Accept. + + +3. Capabilities and limitations + + + The only sub-sections that can appear in a virtual server section +are: + + listen + client + authorize + authenticate + post-auth + pre-proxy + post-proxy + preacct + accounting + session + + All other configuration parameters (modules, etc.) are global. + + Inside of a virtual server, the authorize, etc. sections have their +normal meaning, and can contain anything that an authorize section +could contain in 1.x. + + When a "listen" section is inside of a virtual server definition, it +means that all requests sent to that IP/port will be processed through +the virtual server. There cannot be two "listen" sections with the +same IP address and port number. + + When a "client" section is inside of a virtual server definition, it +means that that client is known only to the "listen" sections that are +also inside of that virtual server. Not only is this client +definition available only to this virtual server, but the details of +the client configuration is also available only to this virtual +server. + + i.e. Two virtual servers can listen on different IP address and +ports, but both can have a client with IP address 127.0.0.1. The +shared secret for that client can be different for each virtual +server. + + +4. More complex "listen" capabilities + + The "listen" sections have a few additional configuration items that +were not in 1.x, and were not mentioned above. These configuration +items enable almost any mapping of IP / port to clients to virtual +servers. + + The configuration items are: + + virtual_server = <name> + + If set, all requests sent to this IP / port are processed + through the named virtual server. + + This directive can be used only for "listen" sections + that are global. i.e. It CANNOT be used if the + "listen" section is inside of a virtual server. + + clients = <name> + + If set, the "listen" section looks for a "clients" section: + + clients <name> { + ... + } + + It looks inside of that named "clients" section for + "client" subsections, at least one of which must + exist. Each client in that section is added to the + list of known clients for this IP / port. No other + clients are known. + + If it is set, it over-rides the list of clients (if + any) in the same virtual server. Note that the + clients are NOT additive! + + If it is not set, then the clients from the current + virtual server (if any) are used. If there are no + clients in this virtual server, then the global + clients are used. + + i.e. The most specific directive is used: + * configuration in this "listen" section + * clients in the same virtual server + * global clients + + The directives are also *exclusive*, not *additive*. + If you have one client in a virtual server, and + another client referenced from a "listen" section, + then that "listen" section will ONLY use the second + client. It will NOT use both clients. + + +5. More complex "client" capabilities + + The "client" sections have a few additional configuration items that +were not in 1.x, and were not mentioned above. These configuration +items enable almost any mapping of IP / port to clients to virtual +servers. + + The configuration items are: + + virtual_server = <name> + + If set, all requests from this client are processed + through the named virtual server. + + This directive can be used only for "client" sections + that are global. i.e. It CANNOT be used if the + "client" section is inside of a virtual server. + + If the "listen" section has a "server" entry, and a matching +client is found ALSO with a "server" entry, then the clients server is +used for that request. + + +6. Worked examples + + + Listening on one socket, and mapping requests from two clients to +two different servers. + + listen { + ... + } + client one { + ... + virtual_server = server_one + } + client two { + ... + virtual_server = server_two + } + server server_one { + authorize { + ... + } + ... + } + server server_two { + authorize { + ... + } + ... + } + + This could also be done as: + + + listen { + ... + virtual_server = server_one + } + client one { + ... + } + client two { + ... + virtual_server = server_two + } + server server_one { + authorize { + ... + } + ... + } + server server_two { + authorize { + ... + } + ... + } + + In this case, the default server for the socket is "server_one", so +there is no need to set that in the client "one" configuration. The +"server_two" configuration for client "two" over-rides the default +setting for the socket. + + Note that the following configuration will NOT work: + + listen { + ... + virtual_server = server_one + } + client one { + ... + } + server server_one { + authorize { + ... + } + ... + } + server server_two { + client two { + ... + } + authorize { + ... + } + ... + } + + In this example, client "two" is hidden inside of the virtual +server, where the "listen" section cannot find it. + + +7. Outlined examples + + This section outlines a number of examples, with alternatives. + + One server, multiple sockets + - multiple "listen" sections in a "server" section + + one server per client + - define multiple servers + - have a global "listen" section + - have multiple global "clients", each with "virtual_server = X" + + two servers, each with their own sockets + - define multiple servers + - put "client" sections into each "server" + - put a "listen" section into each "server" + + Each server can list the same client IP, and the secret + can be different + + two sockets, sharing a list of clients, but pointing to different servers + - define global "listen" sections + - in each, set "virtual_server = X" + - in each, set "clients = Y" + - define "clients Y" section, containing multiple clients. + + This also means that you can have a third socket, which + doesn't share any of these clients. + + +8. How to decide what to do + + + If you want *completely* separate policies for a socket or a client, +then create a separate virtual server. Then, map the request to that +server by setting configuration entries in a "listen" section or in a +"client" section. + + Start off with the common cases first. If most of the clients +and/or sockets get a particular policy, make that policy the default. +Configure it without paying attention to the sockets or clients you +want to add later, and without adding a second virtual server. Once +it works, then add the second virtual server. + + If you want to re-use the previously defined sockets with the second +virtual server, then you will need one or more global "client" +sections. Those clients will contain a "virtual_server = ..." entry +that will direct requests from those clients to the appropriate +virtual server. diff --git a/raddb/sites-available/abfab-tls b/raddb/sites-available/abfab-tls new file mode 100644 index 0000000..b8d0626 --- /dev/null +++ b/raddb/sites-available/abfab-tls @@ -0,0 +1,118 @@ +# +# Example configuration for ABFAB listening on TLS. +# +# $Id$ +# +listen { + ipaddr = * + port = 2083 + type = auth + proto = tcp + + tls { + tls_min_version = "1.2" + private_key_password = whatever + + # Moonshot tends to distribute certs separate from keys + private_key_file = ${certdir}/server.key + certificate_file = ${certdir}/server.pem + ca_file = ${cadir}/ca.pem + dh_file = ${certdir}/dh + fragment_size = 8192 + ca_path = ${cadir} + cipher_list = "DEFAULT" + cache { + enable = no + lifetime = 24 # hours + name = "abfab-tls" + # persist_dir = ${logdir}/abfab-tls + } + require_client_cert = yes + verify { + } + + psk_query = "%{psksql:select hex(key) from psk_keys where keyid = '%{TLS-PSK-Identity}'}" + } + + virtual_server = abfab-idp + clients = radsec-abfab +} + +# There needs to be a separated "listen" section for IPv6. +# Typically it will be identical to the IPv4 one above, but there might be +# some differences (e.g. if a different certificate or port is desired) +listen { + ipaddr = :: + port = 2083 + type = auth + proto = tcp + + tls { + tls_min_version = "1.2" + private_key_password = whatever + + # Moonshot tends to distribute certs separate from keys + private_key_file = ${certdir}/server.key + certificate_file = ${certdir}/server.pem + ca_file = ${cadir}/ca.pem + dh_file = ${certdir}/dh + fragment_size = 8192 + ca_path = ${cadir} + cipher_list = "DEFAULT" + cache { + enable = no + lifetime = 24 # hours + name = "abfab-tls" + # persist_dir = ${logdir}/abfab-tls + } + require_client_cert = yes + verify { + } + + psk_query = "%{psksql:select hex(key) from psk_keys where keyid = '%{TLS-PSK-Identity}'}" + } + + virtual_server = abfab-idp + clients = radsec-abfab +} + +clients radsec-abfab { + # + # Allow all clients, but require TLS. + # This client stanza will match other RP proxies from other + # realms established via the trustrouter. In general + # additional client stanzas are also required for local services. + # + client default { + ipaddr = 0.0.0.0/0 + proto = tls + } + + client default_ip6 { + ipaddr = ::/0 + proto = tls + } + + # An example local service + # client service_1 { + # ipaddr = 192.0.2.20 + # # You should either set gss_acceptor_host_name below + # # or set up policy to confirm that a client claims + # # the right acceptor hostname when using ABFAB. If + # # set, the RADIUS server will confirm that all + # # requests have this value for the acceptor host name + # gss_acceptor_host_name = "server.example.com" + # # If set, this acceptor realm name will be included. + # # Foreign realms will typically reject a request if this is not + # # properly set. + # gss_acceptor_realm_name = "example.com" + # # Additionally, trust_router_coi can be set; if set + # # it will override the default_community in the realm + # # module + # trust_router_coi = "community1.example.net" + # # In production depployments it is important to set + # # up certificate verification so that even if + # # clients spoof IP addresses, one client cannot + # # impersonate another. + # } +} diff --git a/raddb/sites-available/abfab-tr-idp b/raddb/sites-available/abfab-tr-idp new file mode 100644 index 0000000..be98568 --- /dev/null +++ b/raddb/sites-available/abfab-tr-idp @@ -0,0 +1,198 @@ +# +# This file represents a server that is implementing an identity +# provider for GSS-EAP (RFC 7055) using the trust router +# protocol for dynamic realm discovery. Any ABFAB identity +# provider is also an ABFAB relying party proxy. +# +# This file does not include a TLS listener; see abfab-tls for a simple +# example of a RADSEC listener for ABFAB. +# +# $Id$ +# + +server abfab-idp { +authorize { + psk_authorize + abfab_client_check + filter_username + preprocess + + # If you intend to use CUI and you require that the Operator-Name + # be set for CUI generation and you want to generate CUI also + # for your local clients then uncomment the operator-name + # below and set the operator-name for your clients in clients.conf +# operator-name + + # + # If you want to generate CUI for some clients that do not + # send proper CUI requests, then uncomment the + # cui below and set "add_cui = yes" for these clients in clients.conf +# cui + + # + # Do RFC 7542 bang path routing. If you want to only do standard + # RADIUS NAI routing, comment out the below line. + rfc7542 + + # Standard RADIUS NAI routing + if (!updated) { + suffix { + updated = 1 + noop = reject + } + } + + eap { + ok = return + } + + expiration + logintime +} + +authenticate { + # + # Allow EAP authentication. + eap +} + +# Post-Authentication +# Once we KNOW that the user has been authenticated, there are +# additional steps we can take. +post-auth { + # + # For EAP-TTLS and PEAP, add the cached attributes to the reply. + # The "session-state" attributes are automatically cached when + # an Access-Challenge is sent, and automatically retrieved + # when an Access-Request is received. + # + # The session-state attributes are automatically deleted after + # an Access-Reject or Access-Accept is sent. + # + # If both session-state and reply contain a User-Name attribute, remove + # the one in the reply if it is just a copy of the one in the request, so + # we don't end up with two User-Name attributes. + + if (session-state:User-Name && reply:User-Name && request:User-Name && (reply:User-Name == request:User-Name)) { + update reply { + &User-Name !* ANY + } + } + update { + &reply: += &session-state: + } + + # Create the CUI value and add the attribute to Access-Accept. + # Uncomment the line below if *returning* the CUI. +# cui + + # + # If you want to have a log of authentication replies, + # un-comment the following line, and enable the + # 'detail reply_log' module. +# reply_log + + # + # After authenticating the user, do another SQL query. + # + # See "Authentication Logging Queries" in `mods-config/sql/main/$driver/queries.conf` + -sql + + # + # Un-comment the following if you want to modify the user's object + # in LDAP after a successful login. + # +# ldap + + # For Exec-Program and Exec-Program-Wait + exec + # Remove reply message if the response contains an EAP-Message + remove_reply_message_if_eap + # Access-Reject packets are sent through the REJECT sub-section of the + # post-auth section. + # + # Add the ldap module name (or instance) if you have set + # 'edir = yes' in the ldap module configuration + # + Post-Auth-Type REJECT { + # log failed authentications in SQL, too. + -sql + attr_filter.access_reject + + # Insert EAP-Failure message if the request was + # rejected by policy instead of because of an + # authentication failure And already has an EAP message + # For non-ABFAB, we insert the failure all the time, but for ABFAB + # It's more desirable to preserve reply-message when we can + if (&reply:Eap-Message) { + eap + } + + # Remove reply message if the response contains an EAP-Message + remove_reply_message_if_eap + } +} +# +# When the server decides to proxy a request to a home server, +# the proxied request is first passed through the pre-proxy +# stage. This stage can re-write the request, or decide to +# cancel the proxy. +# +# Only a few modules currently have this method. +# +pre-proxy { + # Before proxing the request add an Operator-Name attribute identifying + # if the operator-name is found for this client. + # No need to uncomment this if you have already enabled this in + # the authorize section. +# operator-name + + # The client requests the CUI by sending a CUI attribute + # containing one zero byte. + # Uncomment the line below if *requesting* the CUI. +# cui + + # Uncomment the following line if you want to change attributes + # as defined in the preproxy_users file. +# files + + # Uncomment the following line if you want to filter requests + # sent to remote servers based on the rules defined in the + # 'attrs.pre-proxy' file. +# attr_filter.pre-proxy + + # If you want to have a log of packets proxied to a home + # server, un-comment the following line, and the + # 'detail pre_proxy_log' section, above. +# pre_proxy_log +} +# +# When the server receives a reply to a request it proxied +# to a home server, the request may be massaged here, in the +# post-proxy stage. +# +post-proxy { + + # If you want to have a log of replies from a home server, + # un-comment the following line, and the 'detail post_proxy_log' + # section, above. +# post_proxy_log + + # Uncomment the following line if you want to filter replies from + # remote proxies based on the rules defined in the 'attrs' file. +# attr_filter.post-proxy + + # + # If you are proxying LEAP, you MUST configure the EAP + # module, and you MUST list it here, in the post-proxy + # stage. + # + # You MUST also use the 'nostrip' option in the 'realm' + # configuration. Otherwise, the User-Name attribute + # in the proxied request will not match the user name + # hidden inside of the EAP packet, and the end server will + # reject the EAP request. + # + eap +} +} diff --git a/raddb/sites-available/aws-nlb b/raddb/sites-available/aws-nlb new file mode 100644 index 0000000..acea81e --- /dev/null +++ b/raddb/sites-available/aws-nlb @@ -0,0 +1,46 @@ +# -*- text -*- +###################################################################### +# +# A sample virtual server which handles "health checks" from Amazon +# elastic load balancer. +# +# https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-healthchecks.html +# +# In the Amazon system, configure "proto" as "tcp", and "port" as the port +# given below in the "listen" section. +# +# $Id$ +# +server aws-nlb { + +# +# This should be the IP address of the Amazon load balancer. +# +# If TCP checks come from multiple IP addresses, just list each IP in a separate "client" section. +# +client aws-nlb { + ipaddr = 192.0.2.1 + proto = tcp + secret = "this-will-never-be-used" +} + +# +# Listen on a port. Don't use 80, as that requires root permissions, +# and you don't want to run radiusd as root. +# +listen { + type = status + proto = tcp + ipaddr = * + port = 8000 +} + +# +# This will never get used, but it's here just in case we actually +# get sent RADIUS packets. +# +authorize { + reject +} + +} diff --git a/raddb/sites-available/buffered-sql b/raddb/sites-available/buffered-sql new file mode 100644 index 0000000..74574ac --- /dev/null +++ b/raddb/sites-available/buffered-sql @@ -0,0 +1,161 @@ +# -*- text -*- +###################################################################### +# +# In 2.0.0, radrelay functionality is integrated into the +# server core. This virtual server gives an example of +# using radrelay functionality inside of the server. +# +# In this example, the detail file is read, and the data +# is put into SQL. This configuration is used when a RADIUS +# server on this machine is receiving accounting packets, +# and writing them to the detail file. +# +# The purpose of this virtual server is to de-couple the storage +# of long-term accounting data in SQL from "live" information +# needed by the RADIUS server as it is running. +# +# The benefit of this approach is that for a busy server, the +# overhead of performing SQL queries may be significant. Also, +# if the SQL databases are large (as is typical for ones storing +# months of data), the INSERTs and UPDATEs may take a relatively +# long time. Rather than slowing down the RADIUS server by +# having it interact with a database, you can just log the +# packets to a detail file, and then read that file later at a +# time when the RADIUS server is typically lightly loaded. +# +# If you use on virtual server to log to the detail file, +# and another virtual server (i.e. this one) to read from +# the detail file, then this process will happen automatically. +# A sudden spike of RADIUS traffic means that the detail file +# will grow in size, and the server will be able to handle +# large volumes of traffic quickly. When the traffic dies down, +# the server will have time to read the detail file, and insert +# the data into a long-term SQL database. +# +# $Id$ +# +###################################################################### + +server buffered-sql { + listen { + type = detail + + # The location where the detail file is located. + # This should be on local disk, and NOT on an NFS + # mounted location! + # + # On most systems, this should support file globbing + # e.g. "${radacctdir}/detail-*:*" + # This lets you write many smaller detail files as in + # the example in radiusd.conf: ".../detail-%Y%m%d:%H" + # Writing many small files is often better than writing + # one large file. File globbing also means that with + # a common naming scheme for detail files, then you can + # have many detail file writers, and only one reader. + # + filename = "${radacctdir}/detail-*" + + # + # The server can read accounting packets from the + # detail file much more quickly than those packets + # can be written to a database. If the database is + # overloaded, then bad things can happen. + # + # The server will keep track of how long it takes to + # process an entry from the detail file. It will + # then pause between handling entries. This pause + # allows databases to "catch up", and gives the + # server time to notice that other packets may have + # arrived. + # + # The pause is calculated dynamically, to ensure that + # the load due to reading the detail files is limited + # to a small percentage of CPU time. The + # "load_factor" configuration item is a number + # between 1 and 100. The server will try to keep the + # percentage of time taken by "detail" file entries + # to "load_factor" percentage of the CPU time. + # + # If the "load_factor" is set to 100, then the server + # will read packets as fast as it can, usually + # causing databases to go into overload. + # + load_factor = 10 + + # + # Set the interval for polling the detail file. + # If the detail file doesn't exist, the server will + # wake up, and poll for it every N seconds. + # + # Useful range of values: 1 to 60 + # + poll_interval = 1 + + # + # Set the retry interval for when the home server + # does not respond. The current packet will be + # sent repeatedly, at this interval, until the + # home server responds. + # + # Useful range of values: 5 to 30 + # + retry_interval = 30 + + # + # Track progress through the detail file. When the detail + # file is large, and the server is re-started, it will + # read from the START of the file. + # + # Setting "track = yes" means it will skip packets which + # have already been processed. The default is "no". + # + # track = yes + + # + # In some circumstances it may be desirable for the + # server to start up, process a detail file, and + # immediately quit. To do this enable the "one_shot" + # option below. + # + # Do not enable this for normal server operation. The + # default is "no". + # + # one_shot = no + } + + # + # Pre-accounting. Decide which accounting type to use. + # + preacct { + preprocess + + # + # Ensure that we have a semi-unique identifier for every + # request, and many NAS boxes are broken. + acct_unique + + # + # Read the 'acct_users' file. This isn't always + # necessary, and can be deleted if you do not use it. + files + } + + # + # Accounting. Log the accounting data. + # + accounting { + # + # Log traffic to an SQL database. + # + # See "Accounting queries" in mods-config/sql/main/$driver/queries.conf + # sql + + + # Cisco VoIP specific bulk accounting + # pgsql-voip + + } + + # The requests are not being proxied, so no pre/post-proxy + # sections are necessary. +} diff --git a/raddb/sites-available/challenge b/raddb/sites-available/challenge new file mode 100644 index 0000000..c3aeb08 --- /dev/null +++ b/raddb/sites-available/challenge @@ -0,0 +1,123 @@ +# +# This file gives an example of using Challenge-Response +# +# In this example, the user logs in with a password, which has +# to be "hello". The server will send them a challenge +# consisting of a random number 0..9. The user has to respond +# with that number. +# +# +# $Id$ +# +listen { + type = auth + ipaddr = * + port = 2000 + virtual_server = challenge +} + +server challenge { +authorize { + # + # OTP requires a password. + # + if (!User-Password) { + reject + } + + # + # If there's no State attribute, then this is the first + # request from the user. + # + if (!State) { + # + # Set the authentication to use step 1. + update control { + Auth-Type := Step1 + + # + # For testing we will just set the password to "hello". + # + # Normally the password comes from "ldap" or "sql". + # + Cleartext-Password := "hello" + +# ldap +# sql +# ... + } + } + else { + # + # Check that the password looks like an OTP + # + if (User-Password !~ /[0-9]{6}/) { + reject + } + + # + # Set the authentication to use step 2. + # Set the "known good" password to the number + # saved in the session-state list. + # + update control { + Auth-Type := Step2 + + # + # For testing, ensure that the user enters the same password. + # + # Normally this section should look up a TOTP-Secret, and + # + Cleartext-Password := &session-state:Tmp-Integer-0 + + # + # Normally this section should also set &control:TOTP-Secret, too. + # + TOTP-Password := &User-Password + } + } +} + +authenticate { + Auth-Type Step1 { + # If the password doesn't match, the user is rejected + # immediately. + pap + + # + # For testing, just use a 6 digit random OTP. + # + update session-state { + Tmp-Integer-0 := "%{randstr:nnnnnn}" + } + + # + # For testing, tell the user what OTP to enter. + # + # Don't do this in production... + # + update reply { + Reply-Message := "Please enter OTP %{session-state:Tmp-Integer-0}" + } + + # + # Send an Access-Challenge. + # See raddb/policy.d/control for the definition + # of "challenge" + # + challenge + } + + Auth-Type Step2 { + # + # For testing, do PAP authentication with the password. + # + pap + + # + # Normally you'd do TOTP checks via the TOTP module. + # +# totp + } +} +} diff --git a/raddb/sites-available/channel_bindings b/raddb/sites-available/channel_bindings new file mode 100644 index 0000000..b9f0ac7 --- /dev/null +++ b/raddb/sites-available/channel_bindings @@ -0,0 +1,17 @@ +# +# A virtual server which is used to validate channel-bindings. +# +# $Id$ +# +server channel_bindings { + # + # Only the "authorize" section is needed. + # + authorize { + # In general this section should include a policy for each type + # of channel binding that may be in use. For example each lower + # layer such as GSS-EAP (RFC 7055) or IEEE 802.11I is likely to + # need a separate channel binding policy. + abfab_channel_bindings + } +} diff --git a/raddb/sites-available/check-eap-tls b/raddb/sites-available/check-eap-tls new file mode 100644 index 0000000..d367463 --- /dev/null +++ b/raddb/sites-available/check-eap-tls @@ -0,0 +1,135 @@ +# This virtual server allows EAP-TLS to reject access requests +# based on some attributes of the certificates involved. +# +# To use this virtual server, you must enable it in the tls +# section of mods-enabled/eap as well as adding a link to this +# file in sites-enabled/. +# +# +# Value-pairs that are available for checking include: +# +# TLS-Client-Cert-Subject +# TLS-Client-Cert-Issuer +# TLS-Client-Cert-Common-Name +# TLS-Client-Cert-Subject-Alt-Name-Email +# +# To see a full list of attributes, run the server in debug mode +# with this virtual server configured, and look at the attributes +# passed in to this virtual server. +# +# +# This virtual server is also useful when using EAP-TLS as it is +# only called once, just before the final Accept is about to be +# returned from eap, whereas the outer authorize section is called +# multiple times for each challenge / response. For this reason, +# here may be a good location to put authentication logging, and +# modules that check for further authorization, especially if they +# hit external services such as sql or ldap. + + +server check-eap-tls { + + +# Authorize - this is the only section required. +# +# To accept the access request, set Auth-Type = Accept, otherwise +# set it to Reject. + +authorize { + + # + # By default, we just accept the request: + # + update config { + &Auth-Type := Accept + } + + + # + # Check the client certificate matches a string, and reject otherwise + # + +# if ("%{TLS-Client-Cert-Common-Name}" == 'client.example.com') { +# update config { +# &Auth-Type := Accept +# } +# } +# else { +# update config { +# &Auth-Type := Reject +# } +# update reply { +# &outer.Reply-Message := "Your certificate is not valid." +# } +# } + + + # + # Check the client certificate common name against the supplied User-Name + # +# if (&User-Name == "host/%{TLS-Client-Cert-Common-Name}") { +# update config { +# &Auth-Type := Accept +# } +# } +# else { +# update config { +# &Auth-Type := Reject +# } +# } + + + # + # This is a convenient place to call LDAP, for example, when using + # EAP-TLS, as it will only be called once, after all certificates as + # part of the EAP-TLS challenge process have been verified. + # + # An example could be to use LDAP to check that the connecting host, as + # well as presenting a valid certificate, is also in a group based on + # the User-Name (assuming this contains the service principal name). + # Settings such as the following could be used in the ldap module + # configuration: + # + # basedn = "dc=example, dc=com" + # filter = "(servicePrincipalName=%{User-Name})" + # base_filter = "(objectClass=computer)" + # groupname_attribute = cn + # groupmembership_filter = "(&(objectClass=group)(member=%{control:Ldap-UserDn}))" + +# ldap + + # Now let's test membership of an LDAP group (the ldap bind user will + # need permission to read this group membership): + +# if (!(Ldap-Group == "Permitted-Laptops")) { +# update config { +# &Auth-Type := Reject +# } +# } + + # or, to be more specific, you could use the group's full DN: + # if (!(Ldap-Group == "CN=Permitted-Laptops,OU=Groups,DC=example,DC=org")) { + + + # + # This may be a better place to call the files modules when using + # EAP-TLS, as it will only be called once, after the challenge-response + # iteration has completed. + # + +# files + + + # + # Log all request attributes, plus TLS certificate details, to the + # auth_log file. Again, this is just once per connection request, so + # may be preferable than in the outer authorize section. It is + # suggested that 'auth_log' also be in the outer post-auth and + # Post-Auth REJECT sections to log reply packet details, too. + # + + auth_log + +} +} + diff --git a/raddb/sites-available/coa b/raddb/sites-available/coa new file mode 100644 index 0000000..c10f88e --- /dev/null +++ b/raddb/sites-available/coa @@ -0,0 +1,49 @@ +# -*- text -*- +###################################################################### +# +# Sample virtual server for receiving a CoA or Disconnect-Request packet. +# + +# Listen on the CoA port. +# +# This uses the normal set of clients, with the same secret as for +# authentication and accounting. +# +listen { + type = coa + ipaddr = * + port = 3799 + virtual_server = coa +} + +server coa { + # When a packet is received, it is processed through the + # recv-coa section. This applies to *both* CoA-Request and + # Disconnect-Request packets. + recv-coa { + # CoA && Disconnect packets can be proxied in the same + # way as authentication or accounting packets. + # Just set Proxy-To-Realm, or Home-Server-Pool, and the + # packets will be proxied. + + # Do proxying based on realms here. You don't need + # "IPASS" or "ntdomain", as the proxying is based on + # the Operator-Name attribute. It contains the realm, + # and ONLY the realm (prefixed by a '1') + suffix + + # Insert your own policies here. + ok + } + + # When a packet is sent, it is processed through the + # send-coa section. This applies to *both* CoA-Request and + # Disconnect-Request packets. + send-coa { + # Sample module. + ok + } + + # You can use pre-proxy and post-proxy sections here, too. + # They will be processed for sending && receiving proxy packets. +} diff --git a/raddb/sites-available/coa-relay b/raddb/sites-available/coa-relay new file mode 100644 index 0000000..7dac7b1 --- /dev/null +++ b/raddb/sites-available/coa-relay @@ -0,0 +1,366 @@ +# -*- text -*- +###################################################################### +# +# This virtual server simplifies the process of sending CoA-Request or +# Disconnect-Request packets to a NAS. +# +# This virtual server will receive CoA-Request or Disconnect-Request +# packets that contain *minimal* identifying information. e.g. Just +# a User-Name, or maybe just an Acct-Session-Id attribute. It will +# look up that information in a database in order to find the rest of +# the session data. e.g. NAS-IP-Address, NAS-Identifier, NAS-Port, +# etc. That information will be added to the packet, which will then +# be sent to the NAS. +# +# This process is useful because NASes require the CoA packets to +# contain "session identification" attributes in order to to do CoA +# or Disconnect. If the attributes aren't in the packet, then the +# NAS will NAK the request. This NAK happens even if you ask to +# disconnect "User-Name = bob", and there is only one session with a +# "bob" active. +# +# Using this virtual server makes the CoA or Disconnect process +# easier. Just tell FreeRADIUS to disconnect "User-Name = bob", and +# FreeRADIUS will take care of adding the "session identification" +# attributes. +# +# The process is as follows: +# +# - A CoA/Disconnect-Request is received by FreeRADIUS. +# - The radacct table is searched for active sessions that match each of +# the provided identifier attributes: User-Name, Acct-Session-Id. The +# search returns the owning NAS and Acct-Unique-Id for the matching +# session/s. +# - The original CoA/Disconnect-Request content is written to a detail file +# with custom attributes representing the NAS and Acct-Session-Id. +# - A detail reader follows the file and originates CoA/Disconenct-Requests +# containing the original content, relayed to the corresponding NAS for +# each session using the custom attributes. +# +# This simplifies scripting directly against a set of NAS devices since a +# script need only send a single CoA/Disconnect to FreeRADIUS which will +# then: +# +# - Lookup all active sessions belonging to a user, in the case that only a +# User-Name attribute is provided in the request +# - Handle routing of the request to the correct NAS, in the case of a +# multi-NAS setup +# +# For example, to disconnect a specific session: +# +# $ echo 'Acct-Session-Id = "769df3 312343"' | \ +# radclient 127.0.0.1 disconnect testing123 +# +# To perform a CoA update of all active sessions belonging to a user: +# +# $ cat <<EOF | radclient 127.0.0.1 coa testing123 +# User-Name = bob +# Cisco-AVPair = "subscriber:sub-qos-policy-out=q_out_uncapped" +# EOF +# +# In addition to configuring and activating this site, a detail +# writer module must be configured in mods-enabled: +# +# detail detail_coa { +# filename = ${radacctdir}/detail_coa +# escape_filenames = no +# permissions = 0600 +# header = "%t" +# locking = yes +# } +# + + +# Listen on a local CoA port. +# +# This uses the normal set of clients, with the same secret as for +# authentication and accounting. +# +listen { + type = coa + ipaddr = 127.0.0.1 + port = 3799 + virtual_server = coa +} + +# +# Receive CoA/Disconnect, lookup sessions, write them to a detail file +# +server coa { + # + # When a packet is received, it is processed through the + # recv-coa section. This applies to *both* CoA-Request and + # Disconnect-Request packets. + # + recv-coa { + # + # Lookup all active sessions matching User-Name and/or + # Acct-Session-Id and write each session (which includes to + # owning NAS and session ID) to a detail file. + # + # Returns a single result in the format: + # + # NasIpAddress1#AcctSessionId1|NasIPAddress2#AcctSessionId2|... + # + # i.e. each session is separated by '|', and attributes + # within a session are separated by '#' + # + # You will likely have to update the SELECT to add in + # any other "session identification" attributes + # needed by the NAS. These may include NAS-Port, + # NAS-Identifier, etc. Only the NAS vendor knows + # what these attributes are unfortunately, so we + # cannot give more detailed advice here. + # + update control { + + # + # Example MySQL lookup + # +# Tmp-String-0 := "%{sql:SELECT IFNULL(GROUP_CONCAT(CONCAT(nasipaddress,'#',acctsessionid) separator '|'),'') FROM (SELECT * FROM radacct WHERE ('%{User-Name}'='' OR UserName='%{User-Name}') AND ('%{Acct-Session-Id}'='' OR acctsessionid = '%{Acct-Session-Id}') AND AcctStopTime IS NULL) a}" + + # + # Example PostgreSQL lookup + # +# Tmp-String-0 := "%{sql:SELECT STRING_AGG(CONCAT(nasipaddress,'#',acctsessionid),'|') FROM (SELECT * FROM radacct WHERE ('%{User-Name}'='' OR UserName='%{User-Name}') AND ('%{Acct-Session-Id}'='' OR acctsessionid = '%{Acct-Session-Id}') AND AcctStopTime IS NULL) a}" + + } + + # + # Split the string and split into pieces. + # + if (&control:Tmp-String-0 != "" && "%{explode:&control:Tmp-String-0 |}") { + foreach &control:Tmp-String-0 { + if ("%{Foreach-Variable-0}" =~ /([^#]*)#(.*)/) { + update request { + CoA-Packet-Type := "%{Packet-Type}" + + # + # Or use CoA-Packet-DST-IPv6-Address + # for IPv6 address. + # + CoA-Packet-DST-IP-Address := "%{1}" + + CoA-Acct-Session-Id := "%{2}" + + # + # Add any other attributes here. + # + + # Set the CoA/Disconnect port + CoA-Packet-DST-Port := 1700 + + # SQL-User-Name was left over + # from the xlat + SQL-User-Name !* ANY + + # Uncomment if the NAS does not + # expect User-Name + #User-Name !* ANY + + } + + # + # If we're sending a CoA packet, send it out. + # + if ((CoA-Packet-DST-IP-Address || CoA-Packet-DST-IPv6-Address) && \ + CoA-Acct-Session-Id != "") { + detail_coa.accounting + } + } + } + } else { + # No sessions found + reject + } + + } +} + +# +# Detail file reader that processes the queue of CoA/Disconnect requests +# +server coa-buffered-reader { + listen { + # + # See sites-available/buffered-sql for more details on + # all the options available for the detail reader. + # + type = detail + filename = "${radacctdir}/detail_coa" + load_factor = 90 + track = yes + } + + # + # For historical reasons packets from the detail file reader are + # processed through the "accounting" section. + # + accounting { + switch &CoA-Packet-Type { + case "Disconnect-Request" { + update { + # Include given attributes + disconnect: += request:[*] + + disconnect:Packet-DST-Port := \ + &CoA-Packet-DST-Port + + disconnect:Acct-Session-Id := \ + &CoA-Acct-Session-Id + + # Some NASs want these, others don't + disconnect:Event-Timestamp := "%l" + disconnect:Message-Authenticator := 0x00 + + # + # Remove attributes which will confuse the NAS + # + # The NAS will "helpfully" NAK the packet + # if it contains attributes which are NOT + # "session identification" attributes. + # + # Those attributes should be listed here. + # + disconnect:Acct-Delay-Time !* ANY + disconnect:Proxy-State !* ANY + } + + if (CoA-Packet-DST-IP-Address) { + update { + disconnect:Packet-DST-IP-Address := \ + &CoA-Packet-DST-IP-Address + } + } else { + update { + disconnect:Packet-DST-IPv6-Address := \ + &CoA-Packet-DST-IPv6-Address + } + } + } + + case "CoA-Request" { + update { + # Include given attributes + coa: += request:[*] + + coa:Packet-DST-Port := \ + &CoA-Packet-DST-Port + + coa:Acct-Session-Id := \ + &CoA-Acct-Session-Id + + # Some NASs want these, others don't + coa:Event-Timestamp := "%l" + coa:Message-Authenticator := 0x00 + + # + # Remove attributes which will confuse the NAS + # + # The NAS will "helpfully" NAK the packet + # if it contains attributes which are NOT + # "session identification" attributes. + # + # Those attributes should be listed here. + # + coa:Acct-Delay-Time !* ANY + coa:Proxy-State !* ANY + } + + if (CoA-Packet-DST-IP-Address) { + update { + coa:Packet-DST-IP-Address := \ + &CoA-Packet-DST-IP-Address + } + } else { + update { + coa:Packet-DST-IPv6-Address := \ + &CoA-Packet-DST-IPv6-Address + } + } + + } + } + + # + # ACK the CoA / Disconnect packet. + # + ok + } +} + + +# The CoA packet is in the "proxy-request" attribute list. +# The CoA reply (if any) is in the "proxy-reply" attribute list. +# +server originate-coa-relay { + # + # Handle the responses here. + # + post-proxy { + switch &proxy-reply:Packet-Type { + case CoA-ACK { + ok + } + + case CoA-NAK { + # the NAS didn't like the CoA request + ok + } + + case Disconnect-ACK { + ok + } + + case Disconnect-NAK { + # the NAS didn't like the Disconnect request + ok + } + + # Invalid packet type. This shouldn't happen. + case { + fail + } + } + + # + # These methods are run when there is NO response + # to the request. + # + Post-Proxy-Type Fail-CoA { + ok + } + + Post-Proxy-Type Fail-Disconnect { + ok + } + } +} + + +# +# Homeserver CoA / Disconnect endpoints +# +# See proxy.conf for more details on configuring a home_server and +# home_server_pool. +# +home_server coa-nas1 { + type = coa + + # Update these to match your NAS + ipaddr = 192.0.2.1 + port = 1700 + secret = testing1234 + + coa { + irt = 2 + mrt = 16 + mrc = 5 + mrd = 30 + } +} +home_server_pool coa-nas1 { + type = fail-over + home_server = coa-nas1 + virtual_server = originate-coa-relay +} diff --git a/raddb/sites-available/control-socket b/raddb/sites-available/control-socket new file mode 100644 index 0000000..97ba9ef --- /dev/null +++ b/raddb/sites-available/control-socket @@ -0,0 +1,92 @@ +# -*- text -*- +###################################################################### +# +# Control socket interface. +# +# In the future, we will add username/password checking for +# connections to the control socket. We will also add +# command authorization, where the commands entered by the +# administrator are run through a virtual server before +# they are executed. +# +# For now, anyone who has permission to connect to the socket +# has nearly complete control over the server. Be warned! +# +# This functionality is NOT enabled by default. +# +# See also the "radmin" program, which is used to communicate +# with the server over the control socket. +# +# $Id$ +# +###################################################################### +listen { + # + # Listen on the control socket. + # + type = control + + # + # Socket location. + # + # This file is created with the server's uid and gid. + # It's permissions are r/w for that user and group, and + # no permissions for "other" users. These permissions form + # minimal security, and should not be relied on. + # + socket = ${run_dir}/${name}.sock + + # + # Peercred auth + # + # By default the server users the peercred feature of unix + # sockets to get the UID and GID of the user connecting to + # the socket. You may choose to disable this functionality + # and rely on the file system for enforcing permissions. + # + # On most Unix systems, the permissions set on the socket + # are not enforced, but the ones on the directory containing + # the socket are. + # + # To use filesystem permissions you should create a new + # directory just to house the socket file, and set + # appropriate permissions on that. + # +# peercred = no +# socket = ${run_dir}/control/${name}.sock + + # + # The following two parameters perform authentication and + # authorization of connections to the control socket. + # + # If not set, then ANYONE can connect to the control socket, + # and have complete control over the server. This is likely + # not what you want. + # + # One, or both, of "uid" and "gid" should be set. If set, the + # corresponding value is checked. Unauthorized users result + # in an error message in the log file, and the connection is + # closed. + # + + # + # Name of user that is allowed to connect to the control socket. + # +# uid = radius + + # + # Name of group that is allowed to connect to the control socket. + # +# gid = radius + + # + # Access mode. + # + # This can be used to give *some* administrators access to + # monitor the system, but not to change it. + # + # ro = read only access (default) + # rw = read/write access. + # +# mode = rw +} diff --git a/raddb/sites-available/copy-acct-to-home-server b/raddb/sites-available/copy-acct-to-home-server new file mode 100644 index 0000000..cd085e0 --- /dev/null +++ b/raddb/sites-available/copy-acct-to-home-server @@ -0,0 +1,202 @@ +# -*- text -*- +###################################################################### +# +# In 2.0.0, radrelay functionality is integrated into the +# server core. This virtual server gives an example of +# using radrelay functionality inside of the server. +# +# In this example, the detail file is read, and the packets +# are proxied to a home server. You will have to configure +# realms, home_server_pool, and home_server in proxy.conf +# for this to work. +# +# The purpose of this virtual server is to enable duplication +# of information across a load-balanced, or fail-over set of +# servers. For example, if a group of clients lists two +# home servers (primary, secondary), then RADIUS accounting +# messages will go only to one server at a time. This file +# configures a server (primary, secondary) to send copies of +# the accounting information to each other. +# +# That way, each server has the same set of information, and +# can make the same decision about the user. +# +# $Id$ +# +###################################################################### + +server copy-acct-to-home-server { + listen { + type = detail + + # + # See sites-available/buffered-sql for more details on + # all the options available for the detail reader. + # + + ###################################################### + # + # !!!! WARNING !!!! + # + # The detail file reader acts just like a NAS. + # + # This means that if accounting fails, the packet + # is re-tried FOREVER. It is YOUR responsibility + # to write an accounting policy that returns "ok" + # if the packet was processed properly, "fail" on + # a database error, AND "ok" if you want to ignore + # the packet (e.g. no Acct-Status-Type). + # + # Neither the detail file write OR the detail file + # reader look at the contents of the packets. They + # just either dump the packet verbatim to the file, + # or read it verbatim from the file and pass it to + # the server. + # + ###################################################### + + + # The location where the detail file is located. + # This should be on local disk, and NOT on an NFS + # mounted location! + # + # On most systems, this should support file globbing + # e.g. "${radacctdir}/detail-*:*" + # This lets you write many smaller detail files as in + # the example in radiusd.conf: ".../detail-%Y%m%d:%H" + # Writing many small files is often better than writing + # one large file. File globbing also means that with + # a common naming scheme for detail files, then you can + # have many detail file writers, and only one reader. + # + # Do NOT copy the "filename" configuration from the + # "detail" module here. It won't work. Instead, use + # file globbing (or wildcards), such as: + # + # filename = ${radacctdir}/reader1/detail-* + # + filename = ${radacctdir}/detail + + # + # The server can read accounting packets from the + # detail file much more quickly than those packets + # can be written to a database. If the database is + # overloaded, then bad things can happen. + # + # The server will keep track of how long it takes to + # process an entry from the detail file. It will + # then pause between handling entries. This pause + # allows databases to "catch up", and gives the + # server time to notice that other packets may have + # arrived. + # + # The pause is calculated dynamically, to ensure that + # the load due to reading the detail files is limited + # to a small percentage of CPU time. The + # "load_factor" configuration item is a number + # between 1 and 100. The server will try to keep the + # percentage of time taken by "detail" file entries + # to "load_factor" percentage of the CPU time. + # + # If the "load_factor" is set to 100, then the server + # will read packets as fast as it can, usually + # causing databases to go into overload. + # + load_factor = 10 + + # + # Track progress through the detail file. When the detail + # file is large, and the server is re-started, it will + # read from the START of the file. + # + # Setting "track = yes" means it will skip packets which + # have already been processed. The default is "no". + # + # track = yes + + } + + # + # Pre-accounting. Decide which accounting type to use. + # + preacct { + preprocess + + # Since we're just proxying, we don't need acct_unique. + + # + # Look for IPASS-style 'realm/', and if not found, look for + # '@realm', and decide whether or not to proxy, based on + # that. + # + # Accounting requests are generally proxied to the same + # home server as authentication requests. +# IPASS +# suffix +# ntdomain + + # + # Edit proxy.conf to add a "home_server" section, + # which points to the other server. + # + # Then set that home_server name here. + # + update control { + Home-Server-Name := "name_of_home_server_from_proxy.conf" + } + + # + # Read the 'acct_users' file. This isn't always + # necessary, and can be deleted if you do not use it. + files + } + + # + # Accounting. Log the accounting data. + # + accounting { + # + # Since we're proxying, we don't log anything + # locally. Ensure that the accounting section + # "succeeds" by forcing an "ok" return. + ok + } + + + # + # When the server decides to proxy a request to a home server, + # the proxied request is first passed through the pre-proxy + # stage. This stage can re-write the request, or decide to + # cancel the proxy. + # + # Only a few modules currently have this method. + # + pre-proxy { + + # If you want to have a log of packets proxied to a home + # server, un-comment the following line, and the + # 'detail pre_proxy_log' section in radiusd.conf. + # pre_proxy_log + } + + # + # When the server receives a reply to a request it proxied + # to a home server, the request may be massaged here, in the + # post-proxy stage. + # + post-proxy { + # + + # If you want to have a log of replies from a home + # server, un-comment the following line, and the + # 'detail post_proxy_log' section in radiusd.conf. + # post_proxy_log + + + # Uncomment the following line if you want to filter + # replies from remote proxies based on the rules + # defined in the 'attrs' file. + + # attr_filter + } +} diff --git a/raddb/sites-available/decoupled-accounting b/raddb/sites-available/decoupled-accounting new file mode 100644 index 0000000..abf455c --- /dev/null +++ b/raddb/sites-available/decoupled-accounting @@ -0,0 +1,139 @@ +# -*- text -*- +###################################################################### +# +# This is a sample configuration for "decoupled" accounting. +# "Decoupled" accounting is where the accounting packets are +# NOT written "live" to the back-end database. This method +# can only be used if you are not interested in "live" +# accounting. i.e. Where you can tolerate delays that may be +# a few seconds, before accounting packets get written to +# the DB. +# +# Oddly enough, this method can speed up the processing of +# accounting packets, as all database activity is serialized. +# +# This file is NOT meant to be used as-is. It needs to be +# edited to match your local configuration. +# +# $Id$ +# +###################################################################### + +# Define a virtual server to write the accounting packets. +# Any "listen" section that listens on an accounting port should +# set "virtual_server = write-detail.example.com +server write_detail.example.com { + accounting { + # + # Write the "detail" files. + # + # See raddb/modules/detail.example.com for more info. + detail.example.com + } + + # That's it! +} + +# Define a virtual server to process the accounting packets. +server read-detail.example.com { + # Read accounting packets from the detail file(s) for + # the home server. + listen { + type = detail + filename = "${radacctdir}/detail.example.com/detail-*:*" + load_factor = 10 + track = yes + } + + # All packets read from the detail file are processed through + # the preacct && accounting sections. + # + # The following text is copied verbatim from sites-available/default. + # You should edit it for your own local configuration. + +# +# Pre-accounting. Decide which accounting type to use. +# +preacct { + preprocess + + # + # Ensure that we have a semi-unique identifier for every + # request, and many NAS boxes are broken. + acct_unique + + # + # Look for IPASS-style 'realm/', and if not found, look for + # '@realm', and decide whether or not to proxy, based on + # that. + # + # Accounting requests are generally proxied to the same + # home server as authentication requests. +# IPASS + suffix +# ntdomain + + # + # Read the 'acct_users' file + files +} + +# +# Accounting. Log the accounting data. +# +accounting { + # + # Create a 'detail'ed log of the packets. + # Note that accounting requests which are proxied + # are also logged in the detail file. + detail +# daily + + # Update the wtmp file + # + # If you don't use "radlast", you can delete this line. + unix + + # + # For Simultaneous-Use tracking. + # + # Due to packet losses in the network, the data here + # may be incorrect. There is little we can do about it. + radutmp +# sradutmp + + # + # Return an address to the IP Pool when we see a stop record. + # + # Ensure that &control:Pool-Name is set to determine which + # pool of IPs are used. +# sqlippool + + # + # Log traffic to an SQL database. + # + # NOTE! You will have to ensure that any accounting packets + # NOT handled by the SQL module (e.g. "stop with zero session length" + # result in the accounting section still returning "ok". + # + # Otherwise, the server will think that the accounting packet + # was NOT handled properly, and will keep trying to process it + # through this virtual server! + # + # See "Accounting queries" in `mods-config/sql/main/$driver/queries.conf` +# sql + + # Cisco VoIP specific bulk accounting +# pgsql-voip + + # Filter attributes from the accounting response. + attr_filter.accounting_response + + # + # See "Autz-Type Status-Server" for how this works. + # +# Acct-Type Status-Server { +# +# } +} +} diff --git a/raddb/sites-available/default b/raddb/sites-available/default new file mode 100644 index 0000000..78b7ae7 --- /dev/null +++ b/raddb/sites-available/default @@ -0,0 +1,1159 @@ +###################################################################### +# +# As of 2.0.0, FreeRADIUS supports virtual hosts using the +# "server" section, and configuration directives. +# +# Virtual hosts should be put into the "sites-available" +# directory. Soft links should be created in the "sites-enabled" +# directory to these files. This is done in a normal installation. +# +# If you are using 802.1X (EAP) authentication, please see also +# the "inner-tunnel" virtual server. You will likely have to edit +# that, too, for authentication to work. +# +# $Id$ +# +###################################################################### +# +# Read "man radiusd" before editing this file. See the section +# titled DEBUGGING. It outlines a method where you can quickly +# obtain the configuration you want, without running into +# trouble. See also "man unlang", which documents the format +# of this file. +# +# This configuration is designed to work in the widest possible +# set of circumstances, with the widest possible number of +# authentication methods. This means that in general, you should +# need to make very few changes to this file. +# +# The best way to configure the server for your local system +# is to CAREFULLY edit this file. Most attempts to make large +# edits to this file will BREAK THE SERVER. Any edits should +# be small, and tested by running the server with "radiusd -X". +# Once the edits have been verified to work, save a copy of these +# configuration files somewhere. (e.g. as a "tar" file). Then, +# make more edits, and test, as above. +# +# There are many "commented out" references to modules such +# as ldap, sql, etc. These references serve as place-holders. +# If you need the functionality of that module, then configure +# it in radiusd.conf, and un-comment the references to it in +# this file. In most cases, those small changes will result +# in the server being able to connect to the DB, and to +# authenticate users. +# +###################################################################### + +server default { +# +# If you want the server to listen on additional addresses, or on +# additional ports, you can use multiple "listen" sections. +# +# Each section make the server listen for only one type of packet, +# therefore authentication and accounting have to be configured in +# different sections. +# +# The server ignore all "listen" section if you are using '-i' and '-p' +# on the command line. +# +listen { + # Type of packets to listen for. + # Allowed values are: + # auth listen for authentication packets + # acct listen for accounting packets + # auth+acct listen for both authentication and accounting packets + # proxy IP to use for sending proxied packets + # detail Read from the detail file. For examples, see + # raddb/sites-available/copy-acct-to-home-server + # status listen for Status-Server packets. For examples, + # see raddb/sites-available/status + # coa listen for CoA-Request and Disconnect-Request + # packets. For examples, see the file + # raddb/sites-available/coa + # + type = auth + + # Note: "type = proxy" lets you control the source IP used for + # proxying packets, with some limitations: + # + # * A proxy listener CANNOT be used in a virtual server section. + # * You should probably set "port = 0". + # * Any "clients" configuration will be ignored. + # + # See also proxy.conf, and the "src_ipaddr" configuration entry + # in the sample "home_server" section. When you specify the + # source IP address for packets sent to a home server, the + # proxy listeners are automatically created. + + # ipaddr/ipv4addr/ipv6addr - IP address on which to listen. + # If multiple ones are listed, only the first one will + # be used, and the others will be ignored. + # + # The configuration options accept the following syntax: + # + # ipv4addr - IPv4 address (e.g.192.0.2.3) + # - wildcard (i.e. *) + # - hostname (radius.example.com) + # Only the A record for the host name is used. + # If there is no A record, an error is returned, + # and the server fails to start. + # + # ipv6addr - IPv6 address (e.g. 2001:db8::1) + # - wildcard (i.e. *) + # - hostname (radius.example.com) + # Only the AAAA record for the host name is used. + # If there is no AAAA record, an error is returned, + # and the server fails to start. + # + # ipaddr - IPv4 address as above + # - IPv6 address as above + # - wildcard (i.e. *), which means IPv4 wildcard. + # - hostname + # If there is only one A or AAAA record returned + # for the host name, it is used. + # If multiple A or AAAA records are returned + # for the host name, only the first one is used. + # If both A and AAAA records are returned + # for the host name, only the A record is used. + # + # ipv4addr = * + # ipv6addr = * + ipaddr = * + + # Port on which to listen. + # Allowed values are: + # integer port number (1812) + # 0 means "use /etc/services for the proper port" + port = 0 + + # Some systems support binding to an interface, in addition + # to the IP address. This feature isn't strictly necessary, + # but for sites with many IP addresses on one interface, + # it's useful to say "listen on all addresses for eth0". + # + # If your system does not support this feature, you will + # get an error if you try to use it. + # +# interface = eth0 + + # Per-socket lists of clients. This is a very useful feature. + # + # The name here is a reference to a section elsewhere in + # radiusd.conf, or clients.conf. Having the name as + # a reference allows multiple sockets to use the same + # set of clients. + # + # If this configuration is used, then the global list of clients + # is IGNORED for this "listen" section. Take care configuring + # this feature, to ensure you don't accidentally disable a + # client you need. + # + # See clients.conf for the configuration of "per_socket_clients". + # +# clients = per_socket_clients + + # + # Set the default UDP receive buffer size. In most cases, + # the default values set by the kernel are fine. However, in + # some cases the NASes will send large packets, and many of + # them at a time. It is then possible to overflow the + # buffer, causing the kernel to drop packets before they + # reach FreeRADIUS. Increasing the size of the buffer will + # avoid these packet drops. + # +# recv_buff = 65536 + + # + # Connection limiting for sockets with "proto = tcp". + # + # This section is ignored for other kinds of sockets. + # + limit { + # + # Limit the number of simultaneous TCP connections to the socket + # + # The default is 16. + # Setting this to 0 means "no limit" + max_connections = 16 + + # The per-socket "max_requests" option does not exist. + + # + # The lifetime, in seconds, of a TCP connection. After + # this lifetime, the connection will be closed. + # + # Setting this to 0 means "forever". + lifetime = 0 + + # + # The idle timeout, in seconds, of a TCP connection. + # If no packets have been received over the connection for + # this time, the connection will be closed. + # + # Setting this to 0 means "no timeout". + # + # We STRONGLY RECOMMEND that you set an idle timeout. + # + idle_timeout = 30 + } +} + +# +# This second "listen" section is for listening on the accounting +# port, too. +# +listen { + ipaddr = * +# ipv6addr = :: + port = 0 + type = acct +# interface = eth0 +# clients = per_socket_clients + + limit { + # The number of packets received can be rate limited via the + # "max_pps" configuration item. When it is set, the server + # tracks the total number of packets received in the previous + # second. If the count is greater than "max_pps", then the + # new packet is silently discarded. This helps the server + # deal with overload situations. + # + # The packets/s counter is tracked in a sliding window. This + # means that the pps calculation is done for the second + # before the current packet was received. NOT for the current + # wall-clock second, and NOT for the previous wall-clock second. + # + # Useful values are 0 (no limit), or 100 to 10000. + # Values lower than 100 will likely cause the server to ignore + # normal traffic. Few systems are capable of handling more than + # 10K packets/s. + # + # It is most useful for accounting systems. Set it to 50% + # more than the normal accounting load, and you can be sure that + # the server will never get overloaded + # +# max_pps = 0 + + # Only for "proto = tcp". These are ignored for "udp" sockets. + # +# idle_timeout = 0 +# lifetime = 0 +# max_connections = 0 + } +} + +# IPv6 versions of the above - read their full config to understand options +listen { + type = auth + ipv6addr = :: # any. ::1 == localhost + port = 0 +# interface = eth0 +# clients = per_socket_clients + limit { + max_connections = 16 + lifetime = 0 + idle_timeout = 30 + } +} + +listen { + ipv6addr = :: + port = 0 + type = acct +# interface = eth0 +# clients = per_socket_clients + + limit { +# max_pps = 0 +# idle_timeout = 0 +# lifetime = 0 +# max_connections = 0 + } +} + +# Authorization. First preprocess (hints and huntgroups files), +# then realms, and finally look in the "users" file. +# +# Any changes made here should also be made to the "inner-tunnel" +# virtual server. +# +# The order of the realm modules will determine the order that +# we try to find a matching realm. +# +# Make *sure* that 'preprocess' comes before any realm if you +# need to setup hints for the remote radius server +authorize { + # + # Take a User-Name, and perform some checks on it, for spaces and other + # invalid characters. If the User-Name appears invalid, reject the + # request. + # + # See policy.d/filter for the definition of the filter_username policy. + # + filter_username + + # + # Some broken equipment sends passwords with embedded zeros. + # i.e. the debug output will show + # + # User-Password = "password\000\000" + # + # This policy will fix it to just be "password". + # +# filter_password + + # + # The preprocess module takes care of sanitizing some bizarre + # attributes in the request, and turning them into attributes + # which are more standard. + # + # It takes care of processing the 'raddb/mods-config/preprocess/hints' + # and the 'raddb/mods-config/preprocess/huntgroups' files. + preprocess + + # If you intend to use CUI and you require that the Operator-Name + # be set for CUI generation and you want to generate CUI also + # for your local clients then uncomment the operator-name + # below and set the operator-name for your clients in clients.conf +# operator-name + + # + # If you want to generate CUI for some clients that do not + # send proper CUI requests, then uncomment the + # cui below and set "add_cui = yes" for these clients in clients.conf +# cui + + # + # If you want to have a log of authentication requests, + # un-comment the following line. +# auth_log + + # + # The chap module will set 'Auth-Type := CHAP' if we are + # handling a CHAP request and Auth-Type has not already been set + chap + + # + # If the users are logging in with an MS-CHAP-Challenge + # attribute for authentication, the mschap module will find + # the MS-CHAP-Challenge attribute, and add 'Auth-Type := MS-CHAP' + # to the request, which will cause the server to then use + # the mschap module for authentication. + mschap + + # + # If you have a Cisco SIP server authenticating against + # FreeRADIUS, uncomment the following line, and the 'digest' + # line in the 'authenticate' section. + digest + + # + # The WiMAX specification says that the Calling-Station-Id + # is 6 octets of the MAC. This definition conflicts with + # RFC 3580, and all common RADIUS practices. If you are using + # old style WiMAX (non LTE) the un-commenting the "wimax" module + # here means that it will fix the Calling-Station-Id attribute to + # the normal format as specified in RFC 3580 Section 3.21. + # + # If you are using WiMAX 2.1 (LTE) then un-commenting will allow + # the module to handle SQN resyncronisation. Prior to calling the + # module it is necessary to populate the following attributes + # with the relevant keys: + # control:WiMAX-SIM-Ki + # control:WiMAX-SIM-OPc + # + # If WiMAX-Re-synchronization-Info is found in the request then + # the module will attempt to extract SQN and store it in + # control:WiMAX-SIM-SQN. Also a copy of RAND is extracted to + # control:WiMAX-SIM-RAND. + # + # If the SIM cannot be authenticated using Ki and OPc then reject + # will be returned. +# wimax + + # + # Look for IPASS style 'realm/', and if not found, look for + # '@realm', and decide whether or not to proxy, based on + # that. +# IPASS + + # + # Look for realms in user@domain format + suffix +# ntdomain + + # + # This module takes care of EAP-MD5, EAP-TLS, and EAP-LEAP + # authentication. + # + # It also sets the EAP-Type attribute in the request + # attribute list to the EAP type from the packet. + # + # The EAP module returns "ok" or "updated" if it is not yet ready + # to authenticate the user. The configuration below checks for + # "ok", and stops processing the "authorize" section if so. + # + # Any LDAP and/or SQL servers will not be queried for the + # initial set of packets that go back and forth to set up + # TTLS or PEAP. + # + # The "updated" check is commented out for compatibility with + # previous versions of this configuration, but you may wish to + # uncomment it as well; this will further reduce the number of + # LDAP and/or SQL queries for TTLS or PEAP. + # + eap { + ok = return +# updated = return + } + + # + # Pull crypt'd passwords from /etc/passwd or /etc/shadow, + # using the system API's to get the password. If you want + # to read /etc/passwd or /etc/shadow directly, see the + # mods-available/passwd module. + # +# unix + + # + # Read the 'users' file. In v3, this is located in + # raddb/mods-config/files/authorize + files + + # + # Look in an SQL database. The schema of the database + # is meant to mirror the "users" file. + # + # See "Authorization Queries" in mods-available/sql + -sql + + # + # If you are using /etc/smbpasswd, and are also doing + # mschap authentication, the un-comment this line, and + # configure the 'smbpasswd' module. +# smbpasswd + + # + # The ldap module reads passwords from the LDAP database. + -ldap + + # + # If you're using Active Directory and PAP, then uncomment + # the following lines, and the "Auth-Type LDAP" section below. + # + # This will let you do PAP authentication to AD. + # +# if ((ok || updated) && User-Password && !control:Auth-Type) { +# update control { +# &Auth-Type := ldap +# } +# } + + # + # Enforce daily limits on time spent logged in. +# daily + + # + expiration + logintime + + # + # If no other module has claimed responsibility for + # authentication, then try to use PAP. This allows the + # other modules listed above to add a "known good" password + # to the request, and to do nothing else. The PAP module + # will then see that password, and use it to do PAP + # authentication. + # + # This module should be listed last, so that the other modules + # get a chance to set Auth-Type for themselves. + # + pap + + # + # If "status_server = yes", then Status-Server messages are passed + # through the following section, and ONLY the following section. + # This permits you to do DB queries, for example. If the modules + # listed here return "fail", then NO response is sent. + # +# Autz-Type Status-Server { +# +# } + + # + # RADIUS/TLS (or RadSec) connections are processed through + # this section. See sites-available/tls, and the configuration + # item "check_client_connections" for more information. + # + # The request contains TLS client certificate attributes, + # and nothing else. The debug output will print which + # attributes are available on your system. + # + # If the section returns "ok" or "updated", then the + # connection is accepted. Otherwise the connection is + # terminated. + # + Autz-Type New-TLS-Connection { + ok + } +} + + +# Authentication. +# +# +# This section lists which modules are available for authentication. +# Note that it does NOT mean 'try each module in order'. It means +# that a module from the 'authorize' section adds a configuration +# attribute 'Auth-Type := FOO'. That authentication type is then +# used to pick the appropriate module from the list below. +# + +# In general, you SHOULD NOT set the Auth-Type attribute. The server +# will figure it out on its own, and will do the right thing. The +# most common side effect of erroneously setting the Auth-Type +# attribute is that one authentication method will work, but the +# others will not. +# +# The common reasons to set the Auth-Type attribute by hand +# is to either forcibly reject the user (Auth-Type := Reject), +# or to or forcibly accept the user (Auth-Type := Accept). +# +# Note that Auth-Type := Accept will NOT work with EAP. +# +# Please do not put "unlang" configurations into the "authenticate" +# section. Put them in the "post-auth" section instead. That's what +# the post-auth section is for. +# +authenticate { + # + # PAP authentication, when a back-end database listed + # in the 'authorize' section supplies a password. The + # password can be clear-text, or encrypted. + Auth-Type PAP { + pap + } + + # + # Most people want CHAP authentication + # A back-end database listed in the 'authorize' section + # MUST supply a CLEAR TEXT password. Encrypted passwords + # won't work. + Auth-Type CHAP { + chap + } + + # + # MSCHAP authentication. + Auth-Type MS-CHAP { + mschap + } + + # + # For old names, too. + # + mschap + + # + # If you have a Cisco SIP server authenticating against + # FreeRADIUS, uncomment the following line, and the 'digest' + # line in the 'authorize' section. + digest + + # + # Pluggable Authentication Modules. +# pam + + # Uncomment it if you want to use ldap for authentication + # + # Note that this means "check plain-text password against + # the ldap database", which means that EAP won't work, + # as it does not supply a plain-text password. + # + # We do NOT recommend using this. LDAP servers are databases. + # They are NOT authentication servers. FreeRADIUS is an + # authentication server, and knows what to do with authentication. + # LDAP servers do not. + # + # However, it is necessary for Active Directory, because + # Active Directory won't give the passwords to FreeRADIUS. + # +# Auth-Type LDAP { +# ldap +# } + + # + # Allow EAP authentication. + eap + + # + # The older configurations sent a number of attributes in + # Access-Challenge packets, which wasn't strictly correct. + # If you want to filter out these attributes, uncomment + # the following lines. + # +# Auth-Type eap { +# eap { +# handled = 1 +# } +# if (handled && (Response-Packet-Type == Access-Challenge)) { +# attr_filter.access_challenge.post-auth +# handled # override the "updated" code from attr_filter +# } +# } +} + + +# +# Pre-accounting. Decide which accounting type to use. +# +preacct { + preprocess + + # + # Merge Acct-[Input|Output]-Gigawords and Acct-[Input-Output]-Octets + # into a single 64bit counter Acct-[Input|Output]-Octets64. + # +# acct_counters64 + + # + # Session start times are *implied* in RADIUS. + # The NAS never sends a "start time". Instead, it sends + # a start packet, *possibly* with an Acct-Delay-Time. + # The server is supposed to conclude that the start time + # was "Acct-Delay-Time" seconds in the past. + # + # The code below creates an explicit start time, which can + # then be used in other modules. It will be *mostly* correct. + # Any errors are due to the 1-second resolution of RADIUS, + # and the possibility that the time on the NAS may be off. + # + # The start time is: NOW - delay - session_length + # + +# update request { +# &FreeRADIUS-Acct-Session-Start-Time = "%{expr: %l - %{%{Acct-Session-Time}:-0} - %{%{Acct-Delay-Time}:-0}}" +# } + + + # + # Ensure that we have a semi-unique identifier for every + # request, and many NAS boxes are broken. + acct_unique + + # + # Look for IPASS-style 'realm/', and if not found, look for + # '@realm', and decide whether or not to proxy, based on + # that. + # + # Accounting requests are generally proxied to the same + # home server as authentication requests. +# IPASS + suffix +# ntdomain + + # + # Read the 'acct_users' file + files +} + +# +# Accounting. Log the accounting data. +# +accounting { + # Update accounting packet by adding the CUI attribute + # recorded from the corresponding Access-Accept + # use it only if your NAS boxes do not support CUI themselves +# cui + # + # Create a 'detail'ed log of the packets. + # Note that accounting requests which are proxied + # are also logged in the detail file. + detail +# daily + + # Update the wtmp file + # + # If you don't use "radlast", you can delete this line. + unix + + # + # For Simultaneous-Use tracking. + # + # Due to packet losses in the network, the data here + # may be incorrect. There is little we can do about it. +# radutmp +# sradutmp + + # + # Return an address to the IP Pool when we see a stop record. + # + # Ensure that &control:Pool-Name is set to determine which + # pool of IPs are used. +# sqlippool + + # + # Log traffic to an SQL database. + # + # See "Accounting queries" in mods-available/sql + -sql + + # + # If you receive stop packets with zero session length, + # they will NOT be logged in the database. The SQL module + # will print a message (only in debugging mode), and will + # return "noop". + # + # You can ignore these packets by uncommenting the following + # three lines. Otherwise, the server will not respond to the + # accounting request, and the NAS will retransmit. + # +# if (noop) { +# ok +# } + + # Cisco VoIP specific bulk accounting +# pgsql-voip + + # For Exec-Program and Exec-Program-Wait + exec + + # Filter attributes from the accounting response. + attr_filter.accounting_response + + # + # See "Autz-Type Status-Server" for how this works. + # +# Acct-Type Status-Server { +# +# } +} + + +# Session database, used for checking Simultaneous-Use. Either the radutmp +# or rlm_sql module can handle this. +# The rlm_sql module is *much* faster +session { +# radutmp + + # + # See "Simultaneous Use Checking Queries" in mods-available/sql +# sql +} + + +# Post-Authentication +# Once we KNOW that the user has been authenticated, there are +# additional steps we can take. +post-auth { + # + # If you need to have a State attribute, you can + # add it here. e.g. for later CoA-Request with + # State, and Service-Type = Authorize-Only. + # +# if (!&reply:State) { +# update reply { +# State := "0x%{randstr:16h}" +# } +# } + + # + # Reject packets where User-Name != TLS-Client-Cert-Common-Name + # There is no reason for users to lie about their names. + # + # In general, User-Name == EAP Identity == TLS-Client-Cert-Common-Name + # +# verify_tls_client_common_name + + # + # If there is no Stripped-User-Name in the request, AND we have a client cert, + # then create a Stripped-User-Name from the TLS client certificate information. + # + # Note that this policy MUST be edited for your local system! + # We do not know which fields exist in which certificate, as + # there is no standard here. There is no way for us to have + # a default configuration which "just works" everywhere. We + # can only make recommendations. + # + # The Stripped-User-Name is updated so that it is logged in + # the various "username" fields. This logging means that you + # can associate a particular session with a particular client + # certificate. + # +# if (&EAP-Message && !&Stripped-User-Name && &TLS-Client-Cert-Serial) { +# update request { +# &Stripped-User-Name := "%{%{TLS-Client-Cert-Subject-Alt-Name-Email}:-%{%{TLS-Client-Cert-Common-Name}:-%{TLS-Client-Cert-Serial}}}" +# } +# + # + # Create a Class attribute which is a hash of a bunch + # of information which we hope exists. This + # attribute should be echoed back in + # Accounting-Request packets, which will let the + # administrator correlate authentication and + # accounting. + # +# update reply { +# Class += "%{md5:%{Calling-Station-Id}%{Called-Station-Id}%{TLS-Client-Cert-Subject-Alt-Name-Email}%{TLS-Client-Cert-Common-Name}%{TLS-Client-Cert-Serial}%{NAS-IPv6-Address}%{NAS-IP-Address}%{NAS-Identifier}%{NAS-Port}" +# } +# +# } + + # + # For EAP-TTLS and PEAP, add the cached attributes to the reply. + # The "session-state" attributes are automatically cached when + # an Access-Challenge is sent, and automatically retrieved + # when an Access-Request is received. + # + # The session-state attributes are automatically deleted after + # an Access-Reject or Access-Accept is sent. + # + # If both session-state and reply contain a User-Name attribute, remove + # the one in the reply if it is just a copy of the one in the request, so + # we don't end up with two User-Name attributes. + + if (session-state:User-Name && reply:User-Name && request:User-Name && (reply:User-Name == request:User-Name)) { + update reply { + &User-Name !* ANY + } + } + update { + &reply: += &session-state: + } + + # + # Refresh leases when we see a start or alive. Return an address to + # the IP Pool when we see a stop record. + # + # Ensure that &control:Pool-Name is set to determine which + # pool of IPs are used. +# sqlippool + + + # Create the CUI value and add the attribute to Access-Accept. + # Uncomment the line below if *returning* the CUI. +# cui + + # Create empty accounting session to make simultaneous check + # more robust. See the accounting queries configuration in + # raddb/mods-config/sql/main/*/queries.conf for details. + # + # The "sql_session_start" policy is defined in + # raddb/policy.d/accounting. See that file for more details. +# sql_session_start + + # + # If you want to have a log of authentication replies, + # un-comment the following line, and enable the + # 'detail reply_log' module. +# reply_log + + # + # After authenticating the user, do another SQL query. + # + # See "Authentication Logging Queries" in mods-available/sql + -sql + + # + # Un-comment the following if you want to modify the user's object + # in LDAP after a successful login. + # +# ldap + + # For Exec-Program and Exec-Program-Wait + exec + + # + # In order to calcualate the various keys for old style WiMAX + # (non LTE) you will need to define the WiMAX NAI, usually via + # + # update request { + # &WiMAX-MN-NAI = "%{User-Name}" + # } + # + # If you want various keys to be calculated, you will need to + # update the reply with "template" values. The module will see + # this, and replace the template values with the correct ones + # taken from the cryptographic calculations. e.g. + # + # update reply { + # &WiMAX-FA-RK-Key = 0x00 + # &WiMAX-MSK = "%{reply:EAP-MSK}" + # } + # + # You may want to delete the MS-MPPE-*-Keys from the reply, + # as some WiMAX clients behave badly when those attributes + # are included. See "raddb/modules/wimax", configuration + # entry "delete_mppe_keys" for more information. + # + # For LTE style WiMAX you need to populate the following with the + # relevant values: + # control:WiMAX-SIM-Ki + # control:WiMAX-SIM-OPc + # control:WiMAX-SIM-AMF + # control:WiMAX-SIM-SQN + # +# wimax + + # If there is a client certificate (EAP-TLS, sometimes PEAP + # and TTLS), then some attributes are filled out after the + # certificate verification has been performed. These fields + # MAY be available during the authentication, or they may be + # available only in the "post-auth" section. + # + # The first set of attributes contains information about the + # issuing certificate which is being used. The second + # contains information about the client certificate (if + # available). +# +# update reply { +# Reply-Message += "%{TLS-Cert-Serial}" +# Reply-Message += "%{TLS-Cert-Expiration}" +# Reply-Message += "%{TLS-Cert-Subject}" +# Reply-Message += "%{TLS-Cert-Issuer}" +# Reply-Message += "%{TLS-Cert-Common-Name}" +# Reply-Message += "%{TLS-Cert-Subject-Alt-Name-Email}" +# +# Reply-Message += "%{TLS-Client-Cert-Serial}" +# Reply-Message += "%{TLS-Client-Cert-Expiration}" +# Reply-Message += "%{TLS-Client-Cert-Subject}" +# Reply-Message += "%{TLS-Client-Cert-Issuer}" +# Reply-Message += "%{TLS-Client-Cert-Common-Name}" +# Reply-Message += "%{TLS-Client-Cert-Subject-Alt-Name-Email}" +# } + + # Insert class attribute (with unique value) into response, + # aids matching auth and acct records, and protects against duplicate + # Acct-Session-Id. Note: Only works if the NAS has implemented + # RFC 2865 behaviour for the class attribute, AND if the NAS + # supports long Class attributes. Many older or cheap NASes + # only support 16-octet Class attributes. +# insert_acct_class + + # MacSEC requires the use of EAP-Key-Name. However, we don't + # want to send it for all EAP sessions. Therefore, the EAP + # modules put required data into the EAP-Session-Id attribute. + # This attribute is never put into a request or reply packet. + # + # Uncomment the next few lines to copy the required data into + # the EAP-Key-Name attribute +# if (&reply:EAP-Session-Id) { +# update reply { +# EAP-Key-Name := &reply:EAP-Session-Id +# } +# } + + # Remove reply message if the response contains an EAP-Message + remove_reply_message_if_eap + + # + # Access-Reject packets are sent through the REJECT sub-section of the + # post-auth section. + # + # Add the ldap module name (or instance) if you have set + # 'edir = yes' in the ldap module configuration + # + # The "session-state" attributes are not available here. + # + Post-Auth-Type REJECT { + # log failed authentications in SQL, too. + -sql + attr_filter.access_reject + + # Insert EAP-Failure message if the request was + # rejected by policy instead of because of an + # authentication failure + eap + + # Remove reply message if the response contains an EAP-Message + remove_reply_message_if_eap + } + + # + # Filter access challenges. + # + Post-Auth-Type Challenge { +# remove_reply_message_if_eap +# attr_filter.access_challenge.post-auth + } + + # + # The Client-Lost section will be run for a request when + # FreeRADIUS has given up waiting for an end-users client to + # respond. This is most useful for logging EAP sessions where + # the client stopped responding (likely because the + # certificate was not acceptable.) i.e. this is not for + # RADIUS clients, but for end-user systems. + # + # This will only be triggered by new packets arriving, + # and will be run at some point in the future *after* the + # original request has been discarded. + # + # Therefore the *ONLY* attributes that are available here + # are those in the session-state list. If you want data + # to log, make sure it is copied to &session-state: + # before the client stops responding. NONE of the other + # original attributes (request, reply, etc) will be + # available. + # + # This section will only be run if `postauth_client_lost` + # is enabled in the main configuration in `radiusd.conf`. + # + # Note that there are MANY reasons why an end users system + # might not respond: + # + # * it could not get the packet due to firewall issues + # * it could not get the packet due to a lossy network + # * the users system might not like the servers cert + # * the users system might not like something else... + # + # In some cases, the client is helpful enough to send us a + # TLS Alert message, saying what it doesn't like about the + # certificate. In other cases, no such message is available. + # + # All that we can know on the FreeRADIUS side is that we sent + # an Access-Challenge, and the client never sent anything + # else. The reasons WHY this happens are buried inside of + # the logs on the client system. No amount of looking at the + # FreeRADIUS logs, or poking the FreeRADIUS configuration + # will tell you why the client gave up. The answers are in + # the logs on the client side. And no, the FreeRADIUS team + # didn't write the client, so we don't know where those logs + # are, or how to get at them. + # + # Information about the TLS state changes is in the + # &session-state:TLS-Session-Information attribute. + # + Post-Auth-Type Client-Lost { + # + # Debug ALL of the TLS state changes done during the + # EAP negotiation. + # +# %{debug_attr:&session-state:TLS-Session-Information[*]} + + # + # Debug the LAST TLS state change done during the EAP + # negotiation. For errors, this is usually a TLS + # alert from the client saying something like + # "unknown CA". + # +# %{debug_attr:&session-state:TLS-Session-Information[n]} + + # + # Debug the last module failure message. This may be + # useful, or it may refer to a server-side failure + # which did not cause the client to stop talking to the server. + # +# %{debug_attr:&session-state:Module-Failure-Message} + } + + # + # If the client sends EAP-Key-Name in the request, + # then echo the real value back in the reply. + # + if (EAP-Key-Name && &reply:EAP-Session-Id) { + update reply { + &EAP-Key-Name := &reply:EAP-Session-Id + } + } +} + +# +# When the server decides to proxy a request to a home server, +# the proxied request is first passed through the pre-proxy +# stage. This stage can re-write the request, or decide to +# cancel the proxy. +# +# Only a few modules currently have this method. +# +pre-proxy { + # Before proxing the request add an Operator-Name attribute identifying + # if the operator-name is found for this client. + # No need to uncomment this if you have already enabled this in + # the authorize section. +# operator-name + + # The client requests the CUI by sending a CUI attribute + # containing one zero byte. + # Uncomment the line below if *requesting* the CUI. +# cui + + # Uncomment the following line if you want to change attributes + # as defined in the preproxy_users file. +# files + + # Uncomment the following line if you want to filter requests + # sent to remote servers based on the rules defined in the + # 'attrs.pre-proxy' file. +# attr_filter.pre-proxy + + # If you want to have a log of packets proxied to a home + # server, un-comment the following line, and the + # 'detail pre_proxy_log' section, above. +# pre_proxy_log +} + +# +# When the server receives a reply to a request it proxied +# to a home server, the request may be massaged here, in the +# post-proxy stage. +# +post-proxy { + + # If you want to have a log of replies from a home server, + # un-comment the following line, and the 'detail post_proxy_log' + # section, above. +# post_proxy_log + + # Uncomment the following line if you want to filter replies from + # remote proxies based on the rules defined in the 'attrs' file. +# attr_filter.post-proxy + + # + # If you are proxying LEAP, you MUST configure the EAP + # module, and you MUST list it here, in the post-proxy + # stage. + # + # You MUST also use the 'nostrip' option in the 'realm' + # configuration. Otherwise, the User-Name attribute + # in the proxied request will not match the user name + # hidden inside of the EAP packet, and the end server will + # reject the EAP request. + # + eap + + # + # If the server tries to proxy a request and fails, then the + # request is processed through the modules in this section. + # + # The main use of this section is to permit robust proxying + # of accounting packets. The server can be configured to + # proxy accounting packets as part of normal processing. + # Then, if the home server goes down, accounting packets can + # be logged to a local "detail" file, for processing with + # radrelay. When the home server comes back up, radrelay + # will read the detail file, and send the packets to the + # home server. + # + # See the "mods-available/detail.example.com" file for more + # details on writing a detail file specifically for one + # destination. + # + # See the "sites-available/robust-proxy-accounting" virtual + # server for more details on reading this "detail" file. + # + # With this configuration, the server always responds to + # Accounting-Requests from the NAS, but only writes + # accounting packets to disk if the home server is down. + # +# Post-Proxy-Type Fail-Accounting { +# detail.example.com + + # + # Ensure a response is sent to the NAS now that the + # packet has been written to a detail file. + # +# acct_response +# } +} +} diff --git a/raddb/sites-available/dhcp b/raddb/sites-available/dhcp new file mode 100644 index 0000000..696a395 --- /dev/null +++ b/raddb/sites-available/dhcp @@ -0,0 +1,595 @@ +# -*- text -*- +###################################################################### +# +# This is a virtual server that handles DHCP. +# +# See raddb/mods-available/dhcp_sqlippool for the IP Pool configuration. +# +# See raddb/policy.d/dhcp_sqlippool for the "glue" code that allows +# the RADIUS based "sqlippool" module to be used for DHCP. +# +# See raddb/mods-config/sql/ippool/ for the schemas. +# +# See raddb/sites-available/dhcp for instructions on how to configure +# the DHCP server. +# +# $Id$ +# +###################################################################### + +# +# The DHCP functionality goes into a virtual server. +# +server dhcp { + +# Define a DHCP socket. +# +# The default port below is 6700, so you don't break your network. +# If you want it to do real DHCP, change this to 67, and good luck! +# +# You can also bind the DHCP socket to an interface. +# See below, and raddb/radiusd.conf for examples. +# +# This lets you run *one* DHCP server instance and have it listen on +# multiple interfaces, each with a separate policy. +# +# If you have multiple interfaces, it is a good idea to bind the +# listen section to an interface. You will also need one listen +# section per interface. +# +# FreeBSD does *not* support binding sockets to interfaces. Therefore, +# if you have multiple interfaces, broadcasts may go out of the wrong +# one, or even all interfaces. The solution is to use the "setfib" command. +# If you have a network "10.10.0/24" on LAN1, you will need to do: +# +# Pick any IP on the 10.10.0/24 network +# $ setfib 1 route add default 10.10.0.1 +# +# Edit /etc/rc.local, and add a line: +# setfib 1 /path/to/radiusd +# +# The kern must be built with the following options: +# options ROUTETABLES=2 +# or any value larger than 2. +# +# The other only solution is to update FreeRADIUS to use BPF sockets. +# +listen { + # This is a dhcp socket. + type = dhcp + + # IP address to listen on. Will usually be the IP of the + # interface, or 0.0.0.0 + ipaddr = 127.0.0.1 + + # source IP address for unicast packets sent by the + # DHCP server. + # + # The source IP for unicast packets is chosen from the first + # one of the following items which returns a valid IP + # address: + # + # src_ipaddr + # ipaddr + # reply:DHCP-Server-IP-Address + # reply:DHCP-DHCP-Server-Identifier + # + src_ipaddr = 127.0.0.1 + + # The port should be 67 for a production network. Don't set + # it to 67 on a production network unless you really know + # what you're doing. Even if nothing is configured below, the + # server may still NAK legitimate responses from clients. + port = 6700 + + # Interface name we are listening on. See comments above. +# interface = lo0 + + # The DHCP server defaults to allowing broadcast packets. + # Set this to "no" only when the server receives *all* packets + # from a relay agent. i.e. when *no* clients are on the same + # LAN as the DHCP server. + # + # It's set to "no" here for testing. It will usually want to + # be "yes" in production, unless you are only dealing with + # relayed packets. + broadcast = no + + # On Linux if you're running the server as non-root, you + # will need to do: + # + # setcap cap_net_admin,cap_net_bind_service=eip /path/to/radiusd + # + # This will allow the server to set ARP table entries + # for newly allocated IPs, when run as the "radius" user. + # + # The above "setcap" command adds the capability to the program, + # usually so long as it is run by the "radius" user. Which means + # (oddly enough) that it no longer works when run as root! + # + # When running the server as root in debug mode, you can use: + # + # capsh --caps="cap_setpcap,cap_setuid,cap_setgid,cap_net_admin,cap_net_bind_service+eip" --keep=1 --user=radius --addamb=cap_net_admin,cap_net_bind_service -- -c "/path/to/radiusd -X" + # + # Or, simply "sudo" or "su" to the "radius" user, and then run + # the server in debug mode. + + # De-duplicate DHCP packets. If clients don't receive + # a reply within their timeout, most will re-transmit. + # A reply to either packet will satisfy, so de-duplicating + # helps manage load on a busy server + performance { + skip_duplicate_checks = no + } +} + +# Packets received on the socket will be processed through one +# of the following sections, named after the DHCP packet type. +# See dictionary.dhcp for the packet types. + +# Return packets will be sent to, in preference order: +# DHCP-Gateway-IP-Address +# DHCP-Client-IP-Address +# DHCP-Your-IP-Address +# At least one of these attributes should be set at the end of each +# section for a response to be sent. + +# An internal attribute of DHCP-Network-Subnet is set to provide +# a basis for determining the network that a client belongs to. This +# is a hierarchical assignment based on: +# +# - DHCP-Relay-Link-Selection +# - DHCP-Subnet-Selection-Option +# - DHCP-Gateway-IP-Address +# - DHCP-Client-IP-Address +# +# Except for cases where all IP allocation is performed using a mapping from +# the device MAC address to a fixed IP address the DHCP configuration will +# involve the use of one or more pools. +# +# Each pool should be composed of a set of equally valid IP addresses for the +# devices designated as users of the pool. During IP allocation the choice of +# pool is driven by setting the Pool-Name attribute which may either be +# specified directly or chosen (usually with the help of the dhcp_network +# module) based on the initial value of DHCP-Network-Subnet. +# +# DHCP-Network-Subnet indicates the network from which the request is +# originating. In cases where the originating network alone is insufficent to +# define the required IP allocated policy, DHCP-Network-Subnet may be +# overridden to force the selection of a particular pool. +# +# IP addresses belonging to a single pool that is designated for a Layer 2 +# network containing multiple subnets (a "shared-network" or "multinet" +# configuration as defined by some other DHCP servers), will by definition be +# members of distinct subnets that require their own DHCP reply parameters. In +# this case the dhcp_subnet policy can be used to set the correct +# DHCP-Subnet-Mask, DHCP-Router-Address and DHCP-Broadcast-Address options +# based on the allocated IP. + +dhcp DHCP-Discover { + + # The DHCP Server Identifier is set here since is returned in OFFERs + update control { + &DHCP-DHCP-Server-Identifier = 192.0.2.2 + } + + # Call a policy (defined in policy.d/dhcp) to set common reply attributes + dhcp_common + + # Use a "passwd" module to set group memberships in DHCP-Group-Name + # Enable mods-available/dhcp_passwd to use this + #dhcp_group_membership + + # If clients need to be assigned to a particular network based on + # an attribute in the packet rather than the calculated + # DHCP-Network-Subnet described above, then call a policy + # (defined in policy.d/dhcp) to perform the override + #dhcp_override_network + + # Use a "files" module to lookup global and subnet options + # For multiple subnets use this in place of dhcp_common + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_network + + # Do a simple mapping of MAC to assigned IP. + # + # See below for the definition of the "mac2ip" + # module. + # + #mac2ip + + # Or, allocate IPs from the DHCP pool in SQL. You may need to + # set the pool name here if you haven't set it elsewhere. + #update control { + # &Pool-Name := "local" + #} + #dhcp_sqlippool + + # If the IP address was not allocated, do something else. + # You could call a Perl, Python, or Java script here. + #if (notfound) { + # ... + #} + + # "Shared-networks" may have multiple IP subnets co-existing in a + # single Layer 2 network. If the pool for the network contains + # addresses from more that one subnet then the setting subnet-specific + # DHCP-Subnet-Mask, DHCP-Router-Address and DHCP-Broadcast-Address + # parameters must be performed after the allocation of the IP address. + # + # Set any subnet-specific parameters using this policy. + # + # Enable mods-available/dhcp_files AND uncomment dhcp_subnet in + # policy.d/dhcp to use this. + # + #dhcp_subnet + + # Use a "files" module to lookup options based on DHCP-Group-Name + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_group_options + + # Use a "files" module to lookup host specific options + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_hosts + + # As an alternative or complement to configuration files based lookup + # for options data you can instead use an SQL database. Example + # configuration is found in dhcp_policy_sql in policy.d/dhcp which + # will need to be adapted to your requirements. + #dhcp_policy_sql + + # Set the type of packet to send in reply. + # + # The server will look at the DHCP-Message-Type attribute to + # determine which type of packet to send in reply. Common + # values would be DHCP-Offer, DHCP-Ack or DHCP-NAK. See + # dictionary.dhcp for all the possible values. + # + # DHCP-Do-Not-Respond can be used to tell the server to not + # respond. + # + # In the event that DHCP-Message-Type is not set then the + # server will fall back to determining the type of reply + # based on the rcode of this section. + # + #update reply { + # DHCP-Message-Type = DHCP-Offer + #} + # + # If DHCP-Message-Type is not set, returning "ok" or + # "updated" from this section will respond with a DHCP-Offer + # message. + # + # Other rcodes will tell the server to not return any response. + # + #ok +} + +dhcp DHCP-Request { + + # You must set the DHCP Server Identifier here since this is returned + # in ACKs and is used to determine whether a request containing a + # "server-ip" field is intended for this server + update control { + &DHCP-DHCP-Server-Identifier = 192.0.2.2 + } + + # If the request is not for this server then silently discard it + if (&request:DHCP-DHCP-Server-Identifier && \ + &request:DHCP-DHCP-Server-Identifier != &control:DHCP-DHCP-Server-Identifier) { + do_not_respond + } + + # Response packet type. See DHCP-Discover section above. + #update reply { + # &DHCP-Message-Type = DHCP-Ack + #} + + # Call a policy (defined in policy.d/dhcp) to set common reply attributes + dhcp_common + + # Use a "passwd" module to set group memberships in DHCP-Group-Name + # Enable mods-available/dhcp_passwd to use this + #dhcp_group_membership + + # Optionally override the network address based on client attributes + # See Discover section + #dhcp_override_network + + # Use a "files" module to lookup global and subnet options + # For multiple subnets use this in place of dhcp_common + # Enable mods-available/dhcp_files AND uncomment dhcp_subnet in + # policy.d/dhcp to use this + # Options are set in mods-config/files/dhcp + #dhcp_network + + # Do a simple mapping of MAC to assigned IP. + # + # See below for the definition of the "mac2ip" + # module. + # + #mac2ip + + # Or, allocate IPs from the DHCP pool in SQL. You may need to + # set the pool name here if you haven't set it elsewhere. +# update control { +# &Pool-Name := "local" +# } +# dhcp_sqlippool_request + + # If the IP was not allocated, do something else. + # You could call a Perl, Python, or Java script here. + #if (notfound) { + # ... + #} + + # "Shared-networks" may have multiple IP subnets co-existing in a + # single Layer 2 network. If the pool for the network contains + # addresses from more that one subnet then the setting subnet-specific + # DHCP-Subnet-Mask, DHCP-Router-Address and DHCP-Broadcast-Address + # parameters must be performed after the allocation of the IP address. + # + # Set any subnet-specific parameters using this policy. + # + #dhcp_subnet + + # Use a "files" module to lookup options based on DHCP-Group-Name + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_group_options + + # Use a "files" module to lookup host specific options + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_hosts + + # As an alternative or complement to configuration files based lookup + # for options data you can instead use an SQL database. Example + # configuration is found in dhcp_policy_sql in policy.d/dhcp which + # will need to be adapted to your requirements. + #dhcp_policy_sql + + # If DHCP-Message-Type is not set, returning "ok" or + # "updated" from this section will respond with a DHCP-Ack + # packet. + # + # "handled" will not return a packet, all other rcodes will + # send back a DHCP-NAK. + # + #ok +} + +# +# Other DHCP packet types +# +# There should be a separate section for each DHCP message type. +# By default this configuration will ignore them all. Any packet type +# not defined here will be responded to with a DHCP-NAK. + +dhcp DHCP-Decline { + + # Use a "passwd" module to set group memberships in DHCP-Group-Name + # Enable mods-available/dhcp_passwd to use this + #dhcp_group_membership + + # Optionally override the network address based on client attributes + # See Discover section + #dhcp_override_network + + # Use a "files" module to lookup global and subnet options + # For multiple networks use this in place of dhcp_common + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_network + + # Use a policy that set options from data stored in an SQL database + #dhcp_policy_sql + + # If using IPs from a DHCP pool in SQL then you may need to set the + # pool name here if you haven't set it elsewhere and release the IP. +# update control { +# &Pool-Name := "local" +# } +# dhcp_sqlippool_decline + + update reply { + &DHCP-Message-Type = DHCP-Do-Not-Respond + } + reject +} + +# +# A dummy config for Inform packets - this should match the +# options set in the Request section above, except Inform replies +# must not set Your-IP-Address or IP-Address-Lease-Time +# +dhcp DHCP-Inform { + # Call a policy (defined in policy.d/dhcp) to set common reply attributes + dhcp_common + + # Use a "passwd" module to set group memberships in DHCP-Group-Name + # Enable mods-available/dhcp_passwd to use this + #dhcp_group_membership + + # Optionally override the network address based on client attributes + # See Discover section + #dhcp_override_network + + # Use a "files" module to lookup global and network options + # For multiple networks use this in place of dhcp_common + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_network + + # Use a policy with calls a "files" module of the same name to lookup + # subnet options + # Enable mods-available/dhcp_files AND uncomment dhcp_subnet in + # policy.d/dhcp to use this + # Options are set in mods-config/files/dhcp + #dhcp_subnet + + # Use a "files" module to lookup options based on DHCP-Group-Name + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_group_options + + # Use a "files" module to lookup host specific options + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_hosts + + # Use a policy that set options from data stored in an SQL database + #dhcp_policy_sql + + ok +} + +# +# For Windows 7 boxes +# +#dhcp DHCP-Inform { +# update reply { +# Packet-Dst-Port = 67 +# DHCP-Message-Type = DHCP-ACK +# DHCP-DHCP-Server-Identifier = "%{Packet-Dst-IP-Address}" +# DHCP-Site-specific-28 = 0x0a00 +# } +# ok +#} + +dhcp DHCP-Release { + + # Use a "passwd" module to set group memberships in DHCP-Group-Name + # Enable mods-available/dhcp_passwd to use this + #dhcp_group_membership + + # Optionally override the network address based on client attributes + # See Discover section + #dhcp_override_network + + # Use a "files" module to lookup global and subnet options + # For multiple subnets use this in place of dhcp_common + # Enable mods-available/dhcp_files to use this + # Options are set in mods-config/files/dhcp + #dhcp_network + + # If using IPs from a DHCP pool in SQL then you may need to set the + # pool name here if you haven't set it elsewhere and release the IP. +# update control { +# &Pool-Name := "local" +# } +# dhcp_sqlippool_release + + update reply { + &DHCP-Message-Type = DHCP-Do-Not-Respond + } + reject +} + + +dhcp DHCP-Lease-Query { + # The thing being queried for is implicit + # in the packets. + + # has MAC, asking for IP, etc. + if (&DHCP-Client-Hardware-Address) { + # look up MAC in database + } + + # has IP, asking for MAC, etc. + elsif (&DHCP-Your-IP-Address) { + # look up IP in database + } + + # has host name, asking for IP, MAC, etc. + elsif (&DHCP-Client-Identifier) { + # look up identifier in database + } + else { + update reply { + &DHCP-Message-Type = DHCP-Lease-Unknown + } + + ok + + # stop processing + return + } + + # + # We presume that the database lookup returns "notfound" + # if it can't find anything. + # + if (notfound) { + update reply { + &DHCP-Message-Type = DHCP-Lease-Unknown + } + ok + return + } + + # + # Add more logic here. Is the lease inactive? + # If so, respond with DHCP-Lease-Unassigned. + # + # Otherwise, respond with DHCP-Lease-Active + # + + # + # Also be sure to return ALL information about + # the lease. + # + + # + # The reply types are: + # + # DHCP-Lease-Unknown + # DHCP-Lease-Active + # DHCP-Lease-Unassigned + # + update reply { + &DHCP-Message-Type = DHCP-Lease-Unassigned + } + +} + +} + +###################################################################### +# +# This next section is a sample configuration for the "passwd" +# module, that reads flat-text files. It should go into +# radiusd.conf, in the "modules" section. +# +# The file is in the format <mac>,<ip> +# +# 00:01:02:03:04:05,192.0.2.100 +# 01:01:02:03:04:05,192.0.2.101 +# 02:01:02:03:04:05,192.0.2.102 +# +# This lets you perform simple static IP assignment. +# +# There is a preconfigured "mac2ip" module setup in +# mods-available/mac2ip. To use it do: +# +# # cd raddb/ +# # ln -s ../mods-available/mac2ip mods-enabled/mac2ip +# # mkdir mods-config/passwd +# +# Then create the file mods-config/passwd/mac2ip with the above +# format. +# +###################################################################### + + +# This is an example only - see mods-available/mac2ip instead; do +# not uncomment these lines here. +# +#passwd mac2ip { +# filename = ${confdir}/mac2ip +# format = "*DHCP-Client-Hardware-Address:=DHCP-Your-IP-Address" +# delimiter = "," +#} diff --git a/raddb/sites-available/dhcp.relay b/raddb/sites-available/dhcp.relay new file mode 100644 index 0000000..76d1e10 --- /dev/null +++ b/raddb/sites-available/dhcp.relay @@ -0,0 +1,44 @@ +# -*- text -*- +###################################################################### +# +# This is a virtual server that handles DHCP relaying +# +# Only one server can listen on a socket, so you cannot +# do DHCP relaying && run a DHCP server at the same time. +# +###################################################################### + +server dhcp.eth1 { + listen { + ipaddr = * + port = 67 + type = dhcp + interface = eth1 + } + + # Packets received on the socket will be processed through one + # of the following sections, named after the DHCP packet type. + # See dictionary.dhcp for the packet types. + dhcp DHCP-Discover { + update config { + # IP Address of the DHCP server + &DHCP-Relay-To-IP-Address := 192.0.2.2 + } + update request { + # IP Address of the DHCP relay (ourselves) + &DHCP-Gateway-IP-Address := 192.0.2.1 + } + ok + } + + dhcp DHCP-Request { + update config { + # IP Address of the DHCP server + &DHCP-Relay-To-IP-Address := 192.0.2.2 + } + update request { + &DHCP-Gateway-IP-Address := 192.0.2.2 + } + ok + } +} diff --git a/raddb/sites-available/dynamic-clients b/raddb/sites-available/dynamic-clients new file mode 100644 index 0000000..0459a7f --- /dev/null +++ b/raddb/sites-available/dynamic-clients @@ -0,0 +1,222 @@ +# -*- text -*- +###################################################################### +# +# Sample configuration file for dynamically updating the list +# of RADIUS clients at run time. +# +# Everything is keyed off of a client "network". (e.g. 192.0.2/24) +# This configuration lets the server know that clients within +# that network are defined dynamically. +# +# When the server receives a packet from an unknown IP address +# within that network, it tries to find a dynamic definition +# for that client. If the definition is found, the IP address +# (and other configuration) is added to the server's internal +# cache of "known clients", with a configurable lifetime. +# +# Further packets from that IP address result in the client +# definition being found in the cache. Once the lifetime is +# reached, the client definition is deleted, and any new requests +# from that client are looked up as above. +# +# If the dynamic definition is not found, then the request is +# treated as if it came from an unknown client. i.e. It is +# silently discarded. +# +# As part of protection from Denial of Service (DoS) attacks, +# the server will add only one new client per second. This CANNOT +# be changed, and is NOT configurable. +# +# $Id$ +# +###################################################################### + +# +# Define a network where clients may be dynamically defined. +client dynamic { + # + # You MUST specify a netmask! + # IPv4 /32 or IPv6 /128 are NOT allowed! + ipaddr = 192.0.2.0/24 + + # + # Any other configuration normally found in a "client" + # entry can be used here. + + # + # A shared secret does NOT have to be defined. It can + # be left out. + + # + # Define the virtual server used to discover dynamic clients. + dynamic_clients = dynamic_clients + + # + # The directory where client definitions are stored. This + # needs to be used ONLY if the client definitions are stored + # in flat-text files. Each file in that directory should be + # ONE and only one client definition. The name of the file + # should be the IP address of the client. + # + # If you are storing clients in SQL, this entry should not + # be used. +# directory = ${confdir}/dynamic-clients/ + + # + # Define the lifetime (in seconds) for dynamic clients. + # They will be cached for this lifetime, and deleted afterwards. + # + # If the lifetime is "0", then the dynamic client is never + # deleted. The only way to delete the client is to re-start + # the server. + lifetime = 3600 +} + +# +# This is the virtual server referenced above by "dynamic_clients". +server dynamic_clients { + + # + # The only contents of the virtual server is the "authorize" section. + authorize { + + # + # Put any modules you want here. SQL, LDAP, "exec", + # Perl, etc. The only requirements is that the + # attributes MUST go into the control item list. + # + # The request that is processed through this section + # is EMPTY. There are NO attributes. The request is fake, + # and is NOT the packet that triggered the lookup of + # the dynamic client. + # + # The ONLY piece of useful information is either + # + # Packet-Src-IP-Address (IPv4 clients) + # Packet-Src-IPv6-Address (IPv6 clients) + # + # The attributes used to define a dynamic client mirror + # the configuration items in the "client" structure. + # + + # + # Example 1: Hard-code a client IP. This example is + # useless, but it documents the attributes + # you need. + # + update control { + + # + # Echo the IP address of the client. + &FreeRADIUS-Client-IP-Address = "%{Packet-Src-IP-Address}" + + # require_message_authenticator + &FreeRADIUS-Client-Require-MA = no + + # secret + &FreeRADIUS-Client-Secret = "testing123" + + # shortname + &FreeRADIUS-Client-Shortname = "%{Packet-Src-IP-Address}" + + # nas_type + &FreeRADIUS-Client-NAS-Type = "other" + + # virtual_server + # + # This can ONLY be used if the network client + # definition (e.g. "client dynamic" above) has + # NO virtual_server defined. + # + # If the network client definition does have a + # virtual_server defined, then that is used, + # and there is no need to define this attribute. + # + &FreeRADIUS-Client-Virtual-Server = "something" + + } + + # + # Example 2: Read the clients from "clients" files + # in a directory. + # + + # This requires you to uncomment the + # "directory" configuration in the + # "client dynamic" configuration above, + # and then put one file per IP address in + # that directory. + # + dynamic_clients + + # + # Example 3: Look the clients up in SQL. + # + # This requires the SQL module to be configured, of course. + if ("%{sql: SELECT nasname FROM nas WHERE nasname = '%{Packet-Src-IP-Address}'}") { + update control { + # + # Echo the IP. + &FreeRADIUS-Client-IP-Address = "%{Packet-Src-IP-Address}" + + # + # Do multiple SELECT statements to grab + # the various definitions. + &FreeRADIUS-Client-Shortname = "%{sql: SELECT shortname FROM nas WHERE nasname = '%{Packet-Src-IP-Address}'}" + + &FreeRADIUS-Client-Secret = "%{sql: SELECT secret FROM nas WHERE nasname = '%{Packet-Src-IP-Address}'}" + + &FreeRADIUS-Client-NAS-Type = "%{sql: SELECT type FROM nas WHERE nasname = '%{Packet-Src-IP-Address}'}" + + &FreeRADIUS-Client-Virtual-Server = "%{sql: SELECT server FROM nas WHERE nasname = '%{Packet-Src-IP-Address}'}" + } + + } + + # Do an LDAP lookup in the elements OU, check to see if + # the Packet-Src-IP-Address object has a "ou" + # attribute, if it does continue. Change "ACME.COM" to + # the real OU of your organization. + # + # Assuming the following schema: + # + # OU=Elements,OU=Radius,DC=ACME,DC=COM + # + # Elements will hold a record of every NAS in your + # Network. Create Group objects based on the IP + # Address of the NAS and set the "Location" or "l" + # attribute to the NAS Huntgroup the NAS belongs to + # allow them to be centrally managed in LDAP. + # + # e.g. CN=10.1.2.3,OU=Elements,OU=Radius,DC=ACME,DC=COM + # + # With a "l" value of "CiscoRTR" for a Cisco Router + # that has a NAS-IP-Address or Source-IP-Address of + # 10.1.2.3. + # + # And with a "ou" value of the shared secret password + # for the NAS element. ie "password" + if ("%{ldap:ldap:///OU=Elements,OU=Radius,DC=ACME,DC=COM?ou?sub?cn=%{Packet-Src-IP-Address}}") { + update control { + &FreeRADIUS-Client-IP-Address = "%{Packet-Src-IP-Address}" + + # Set the Client-Shortname to be the Location + # "l" just like in the Huntgroups, but this + # time to the shortname. + + &FreeRADIUS-Client-Shortname = "%{ldap:ldap:///OU=Elements,OU=Radius,DC=ACME,DC=COM?l?sub?cn=%{Packet-Src-IP-Address}}" + + # Lookup and set the Shared Secret based on + # the "ou" attribute. + &FreeRADIUS-Client-Secret = "%{ldap:ldap:///OU=Elements,OU=Radius,DC=ACME,DC=COM?ou?sub?cn=%{Packet-Src-IP-Address}}" + } + } + + # + # Tell the caller that the client was defined properly. + # + # If the authorize section does NOT return "ok", then + # the new client is ignored. + ok + } +} diff --git a/raddb/sites-available/example b/raddb/sites-available/example new file mode 100644 index 0000000..5f204aa --- /dev/null +++ b/raddb/sites-available/example @@ -0,0 +1,122 @@ +###################################################################### +# +# An example virtual server configuration. +# +# $Id$ +# +###################################################################### + + +# +# This client will be available to any "listen" section that +# are defined outside of a virtual server section. However, +# when the server receives a packet from this client, the +# request will be processed through the "example" virtual +# server, as the "client" section contains a configuration item +# to that effect. +# +# Note that this client will be able to send requests to any +# port defined in a global "listen" section. It will NOT, +# however, be able to send requests to a port defined in a +# "listen" section that is contained in a "server" section. +# +# With careful matching of configurations, you should be able +# to: +# +# - Define one authentication port, but process each client +# through a separate virtual server. +# +# - define multiple authentication ports, each with a private +# list of clients. +# +# - define multiple authentication ports, each of which may +# have the same client listed, but with different shared +# secrets +# +# FYI: We use an address in the 192.0.2.* space for this example, +# as RFC 3330 says that that /24 range is used for documentation +# and examples, and should not appear on the net. You shouldn't +# use it for anything, either. +# +client 192.0.2.10 { + shortname = example-client + secret = testing123 + virtual_server = example +} + +###################################################################### +# +# An example virtual server. It starts off with "server name {" +# The "name" is used to reference this server from a "listen" +# or "client" section. +# +###################################################################### +server example { + # + # Listen on 192.0.2.1:1812 for Access-Requests + # + # When the server receives a packet, it is processed + # through the "authorize", etc. sections listed here, + # NOT the global ones the "default" site. + # + listen { + ipaddr = 192.0.2.1 + port = 1821 + type = auth + } + + # + # This client is listed within the "server" section, + # and is therefore known ONLY to the socket defined + # in the "listen" section above. If the client IP + # sends a request to a different socket, the server + # will treat it as an unknown client, and will not + # respond. + # + # In contrast, the client listed at the top of this file + # is outside of any "server" section, and is therefore + # global in scope. It can send packets to any port + # defined in a global "listen" section. It CANNOT send + # packets to the listen section defined above, though. + # + # Note that you don't have to have a "virtual_server = example" + # line here, as the client is encapsulated within + # the "server" section. + # + client 192.0.2.9 { + shortname = example-client + secret = testing123 + } + + authorize { + # + # Some example policies. See "man unlang" for more. + # + if (&User-Name == 'bob') { + update control { + &Cleartext-Password := 'bob' + } + } + + # + # And then reject the user. The next line requires + # that the "always reject {}" section is defined in + # the "modules" section of radiusd.conf. + # + reject + } + + authenticate { + + } + + post-auth { + + Post-Auth-Type Reject { + update reply { + &Reply-Message = 'This is only an example.' + } + } + } + +} diff --git a/raddb/sites-available/google-ldap-auth b/raddb/sites-available/google-ldap-auth new file mode 100644 index 0000000..3be530f --- /dev/null +++ b/raddb/sites-available/google-ldap-auth @@ -0,0 +1,225 @@ +# -*- text -*- +######################################################################### +# +# The file contains a sample virtual server which uses Google +# Secure LDAP for authentication +# +# This file is designed to be used as an inner tunnel virtual +# server for EAP-TTLS-PAP authentication. +# +# Use this virtual server in conjunction with the sample Google +# Secure LDAP module configuration, which is in +# mods-available/ldap_google. +# +# Due to the poor performance of Google Secure LDAP, this +# configuration also caches information around accepts, rejects, +# and LDAP qeuries. See mods-available/cache_auth for the +# configuration of the various "cache" modules used here. +# +# The TTL on these caches should be tuned to match site policies +# - e.g. how long should a user be re-authenticated from a cache +# without performing an LDAP bind. +# +# Typically the caches are beneficial when performing +# authentication for 802.1x wifi where repeated authentications +# occur as users roam. We also recommend enabling the "cache" +# subsection of mods-available/eap. Both kinds of caching can +# be done at the same time, and both kinds of caching will help +# improve system performance and stability. +# +# $Id$ +# +######################################################################### + +server google-ldap { + +# +# This is only for testing, and not needed in general operation. +# +listen { + ipaddr = 127.0.0.1 + port = 18123 + type = auth +} + +authorize { + # + # Perform sanity checks on the supplied user name + # + filter_username + + # + # Perform sanity checks comparing inner and outer user name + # + filter_inner_identity + + # + # Split up user names in the form user@domain + # + split_username_nai + + # + # Check the authentication cache to see if this user + # recently sucessfully authenticated + # + update control { + &Cache-Status-Only := 'yes' + } + cache_auth_accept + + # + # If there's a cached User-Name / User-Password which matches + # what the user sent here, then the user has been + # authenticated. We can then avoid interacting with Google's + # LDAP server, which significantly improves the performance + # of user authentication. + # + if (ok) { + update { + &control:Auth-Type := Accept + } + return + } + + # + # Check the reject cache to see if this user was + # recently rejected + # + update control { + &Cache-Status-Only := 'yes' + } + cache_auth_reject + + # + # If there's a cached User-Name / User-Password which matches + # what the user sent here, then the user has been rejected. + # As with authentication above, we don't need to check + # Google's LDAP server, and can improve performance. + # + # Note that in may cases rejected users will try over and + # over again. This increased load can significantly affect + # performance, and can even prevent other users from + # authenticating! The solution is to just tell the bad users + # to "go away" as quickly as possible, while using minimal + # resources. + # + if (ok) { + update { + &Module-Failure-Message := "Rejected by cache entry" + } + reject + } + + # + # If group membership checks are required, then ensure that + # the relevant "cacheable_" option is set against the ldap + # instance, and call the ldap module here. + # + # If group membership is irrelevant, do not call ldap here + # to improve performance + # + # ldap_google + + # + # As Google LDAP does not return user passwords, + # authentication is only possible by LDAP "bind as user". So + # only PAP and TTLS+PAP will work. + # + # If the request contains a password, then force LDAP "bind + # as user". + # + if (&User-Password && !control:Auth-Type) { + update { + &control:Auth-Type := ldap + } + + # + # Look up a user's DN in the cache. + # + # The standard ldap auth mechanism is 3 steps + # - bind as admin user + # - lookup the user's DN + # - bind as the user + # + # Caching the DN removes the first two steps + # during the lifetime of the cache entry. + # + # If the ldap module is called above, then this cache + # call can be commented out; the DN will have been + # retrieved above by the "ldap_google" module. + # + update control { + &Cache-Read-Only := "yes" + } + cache_ldap_user_dn + + } +} + +authenticate { + # + # Use an LDAP "bind as user" to authenticate. Google will + # check the users' password, and will return success / fail. + # + Auth-Type LDAP { + ldap_google + } + +} + +# +# Google LDAP has no specific session section configuration +# +session { + +} + +# +# In post-auth the various caches get updated. +# +# Add in any additional policy required to set reply attributes +# +post-auth { + # + # Cache the user's DN. See the authorize section for + # how and why this would be used + # + cache_ldap_user_dn + + # + # If a user was authenticated by ldap, add the users name / + # password to the cache of successful authentications. + # + # Otherwise the user was authenticated via the + # cache_auth_accept call above, in the "authorize" section. + # + if (&control:Auth-Type == ldap) { + cache_auth_accept + } + + Post-Auth-Type REJECT { + attr_filter.access_reject + + # + # Record rejects in a cache, as a protection against + # repeated attempts from mis-configured clients. + # + if (&control:Auth-Type == ldap) { + cache_auth_reject + } + + # + # Clear the DN cache entry if it exists. + # If the DN cache is in use, retaining an incorrect + # DN entry could cause issues if the user's DN + # has changed. + # + update control { + &Cache-TTL := 0 + } + cache_ldap_user_dn + + } +} + +} diff --git a/raddb/sites-available/inner-tunnel b/raddb/sites-available/inner-tunnel new file mode 100644 index 0000000..c178baa --- /dev/null +++ b/raddb/sites-available/inner-tunnel @@ -0,0 +1,468 @@ +# -*- text -*- +###################################################################### +# +# This is a virtual server that handles *only* inner tunnel +# requests for EAP-TTLS and PEAP types. +# +# $Id$ +# +###################################################################### + +server inner-tunnel { + +# +# This next section is here to allow testing of the "inner-tunnel" +# authentication methods, independently from the "default" server. +# It is listening on "localhost", so that it can only be used from +# the same machine. +# +# $ radtest USER PASSWORD 127.0.0.1:18120 0 testing123 +# +# If it works, you have configured the inner tunnel correctly. To check +# if PEAP will work, use: +# +# $ radtest -t mschap USER PASSWORD 127.0.0.1:18120 0 testing123 +# +# If that works, PEAP should work. If that command doesn't work, then +# +# FIX THE INNER TUNNEL CONFIGURATION SO THAT IT WORKS. +# +# Do NOT do any PEAP tests. It won't help. Instead, concentrate +# on fixing the inner tunnel configuration. DO NOTHING ELSE. +# +listen { + ipaddr = 127.0.0.1 + port = 18120 + type = auth +} + + +# Authorization. First preprocess (hints and huntgroups files), +# then realms, and finally look in the "users" file. +# +# The order of the realm modules will determine the order that +# we try to find a matching realm. +# +# Make *sure* that 'preprocess' comes before any realm if you +# need to setup hints for the remote radius server +authorize { + # + # Take a User-Name, and perform some checks on it, for spaces and other + # invalid characters. If the User-Name appears invalid, reject the + # request. + # + # See policy.d/filter for the definition of the filter_username policy. + # + filter_username + + # + # Do checks on outer / inner User-Name, so that users + # can't spoof us by using incompatible identities + # +# filter_inner_identity + + # + # The chap module will set 'Auth-Type := CHAP' if we are + # handling a CHAP request and Auth-Type has not already been set + chap + + # + # If the users are logging in with an MS-CHAP-Challenge + # attribute for authentication, the mschap module will find + # the MS-CHAP-Challenge attribute, and add 'Auth-Type := MS-CHAP' + # to the request, which will cause the server to then use + # the mschap module for authentication. + mschap + + # + # Pull crypt'd passwords from /etc/passwd or /etc/shadow, + # using the system API's to get the password. If you want + # to read /etc/passwd or /etc/shadow directly, see the + # passwd module, above. + # +# unix + + # + # Look for IPASS style 'realm/', and if not found, look for + # '@realm', and decide whether or not to proxy, based on + # that. +# IPASS + + # + # Look for realms in user@domain format + # + # Note that proxying the inner tunnel authentication means + # that the user MAY use one identity in the outer session + # (e.g. "anonymous", and a different one here + # (e.g. "user@example.com"). The inner session will then be + # proxied elsewhere for authentication. If you are not + # careful, this means that the user can cause you to forward + # the authentication to another RADIUS server, and have the + # accounting logs *not* sent to the other server. This makes + # it difficult to bill people for their network activity. + # + suffix +# ntdomain + + # + # The "suffix" module takes care of stripping the domain + # (e.g. "@example.com") from the User-Name attribute, and the + # next few lines ensure that the request is not proxied. + # + # If you want the inner tunnel request to be proxied, delete + # the next few lines. + # + update control { + &Proxy-To-Realm := LOCAL + } + + # + # This module takes care of EAP-MSCHAPv2 authentication. + # + # It also sets the EAP-Type attribute in the request + # attribute list to the EAP type from the packet. + # + # The example below uses module failover to avoid querying all + # of the following modules if the EAP module returns "ok". + # Therefore, your LDAP and/or SQL servers will not be queried + # for the many packets that go back and forth to set up TTLS + # or PEAP. The load on those servers will therefore be reduced. + # + eap { + ok = return + } + + # + # Read the 'users' file + files + + # + # Look in an SQL database. The schema of the database + # is meant to mirror the "users" file. + # + # See "Authorization Queries" in `mods-config/sql/main/$driver/queries.conf` + -sql + + # + # If you are using /etc/smbpasswd, and are also doing + # mschap authentication, the un-comment this line, and + # enable the "smbpasswd" module. +# smbpasswd + + # + # The ldap module reads passwords from the LDAP database. + -ldap + + # + # Enforce daily limits on time spent logged in. +# daily + + expiration + logintime + + # + # If no other module has claimed responsibility for + # authentication, then try to use PAP. This allows the + # other modules listed above to add a "known good" password + # to the request, and to do nothing else. The PAP module + # will then see that password, and use it to do PAP + # authentication. + # + # This module should be listed last, so that the other modules + # get a chance to set Auth-Type for themselves. + # + pap + + # Uncomment this section if you want to use ldap for + # authentication. The "Auth-Type ldap { ...}" configuration + # section below also has to be uncommented. + # + # Note that this means "check plain-text password against + # the ldap database", which means that EAP won't work, + # as it does not supply a plain-text password. + # + # We do NOT recommend using this, unless you have no other + # choice. LDAP servers are databases. They are NOT + # authentication servers. FreeRADIUS is an authentication + # server, and knows what to do with authentication. LDAP + # servers do not. + # + # Note that we force "Auth-Type := LDAP" ONLY if nothing else + # is authenticating the user, AND ONLY if the request contains + # a plain-text password. + # + # LDAP servers can only do PAP. They cannot do CHAP, MS-CHAP, + # or EAP. + # +# if (!&control.Auth-Type && &User-Password) { +# update control { +# &Auth-Type := LDAP +# } +# } +} + + +# Authentication. +# +# +# This section lists which modules are available for authentication. +# Note that it does NOT mean 'try each module in order'. It means +# that a module from the 'authorize' section adds a configuration +# attribute 'Auth-Type := FOO'. That authentication type is then +# used to pick the appropriate module from the list below. +# + +# In general, you SHOULD NOT set the Auth-Type attribute. The server +# will figure it out on its own, and will do the right thing. The +# most common side effect of erroneously setting the Auth-Type +# attribute is that one authentication method will work, but the +# others will not. +# +# The common reasons to set the Auth-Type attribute by hand +# is to either forcibly reject the user, or forcibly accept him. +# +authenticate { + # + # PAP authentication, when a back-end database listed + # in the 'authorize' section supplies a password. The + # password can be clear-text, or encrypted. + Auth-Type PAP { + pap + } + + # + # Most people want CHAP authentication + # A back-end database listed in the 'authorize' section + # MUST supply a CLEAR TEXT password. Encrypted passwords + # won't work. + Auth-Type CHAP { + chap + } + + # + # MSCHAP authentication. + Auth-Type MS-CHAP { + mschap + } + + # + # For old names, too. + # + mschap + + # + # Pluggable Authentication Modules. +# pam + + # Uncomment this section if you want to use ldap for + # authentication. The "Auth-Type := LDAP" configuration + # at the end of the "authorize" section also has to be + # uncommented. + # + # Note that this means "check plain-text password against + # the ldap database", which means that EAP won't work, + # as it does not supply a plain-text password. + # + # We do NOT recommend using this. LDAP servers are databases. + # They are NOT authentication servers. FreeRADIUS is an + # authentication server, and knows what to do with authentication. + # LDAP servers do not. + # +# Auth-Type LDAP { +# ldap +# } + + # + # Allow EAP authentication. + eap +} + +###################################################################### +# +# There are no accounting requests inside of EAP-TTLS or PEAP +# tunnels. +# +###################################################################### + + +# Session database, used for checking Simultaneous-Use. Either the radutmp +# or rlm_sql module can handle this. +# The rlm_sql module is *much* faster +session { + radutmp + + # + # See "Simultaneous Use Checking Queries" in `mods-config/sql/main/$driver/queries.conf` +# sql +} + + +# Post-Authentication +# Once we KNOW that the user has been authenticated, there are +# additional steps we can take. +# +# Note that the last packet of the inner-tunnel authentication +# MAY NOT BE the last packet of the outer session. So updating +# the outer reply MIGHT work, and sometimes MIGHT NOT. The +# exact functionality depends on both the inner and outer +# authentication methods. +# +# If you need to send a reply attribute in the outer session, +# the ONLY safe way is to set "use_tunneled_reply = yes", and +# then update the inner-tunnel reply. +post-auth { + # If you want privacy to remain, see the + # Chargeable-User-Identity attribute from RFC 4372. + # If you want to use it just uncomment the line below. +# cui-inner + + # + # If you want the Access-Accept to contain the inner + # User-Name, uncomment the following lines. + # +# update outer.session-state { +# User-Name := &User-Name +# } + + # + # If you want to have a log of authentication replies, + # un-comment the following line, and enable the + # 'detail reply_log' module. +# reply_log + + # + # After authenticating the user, do another SQL query. + # + # See "Authentication Logging Queries" in `mods-config/sql/main/$driver/queries.conf` + -sql + + # + # Un-comment the following if you have set + # 'edir = yes' in the ldap module sub-section of + # the 'modules' section. + # +# ldap + + + # + # Un-comment the following if you want to generate Moonshot (ABFAB) TargetedIds + # + # IMPORTANT: This requires the UUID package to be installed, and a targeted_id_salt + # to be configured. + # + # This functionality also supports SQL backing. To use this functionality, enable + # and configure the moonshot-targeted-ids SQL module in the mods-enabled directory. + # Then remove the comments from the appropriate lines in each of the below + # policies in the policy.d/moonshot-targeted-ids file. + # +# moonshot_host_tid +# moonshot_realm_tid +# moonshot_coi_tid + + # + # Instead of "use_tunneled_reply", change this "if (0)" to an + # "if (1)". + # + if (0) { + # + # These attributes are for the inner-tunnel only, + # and MUST NOT be copied to the outer reply. + # + update reply { + User-Name !* ANY + Message-Authenticator !* ANY + EAP-Message !* ANY + Proxy-State !* ANY + MS-MPPE-Encryption-Types !* ANY + MS-MPPE-Encryption-Policy !* ANY + MS-MPPE-Send-Key !* ANY + MS-MPPE-Recv-Key !* ANY + } + + # + # Copy the inner reply attributes to the outer + # session-state list. The post-auth policy will take + # care of copying the outer session-state list to the + # outer reply. + # + update { + &outer.session-state: += &reply: + } + } + + # + # Access-Reject packets are sent through the REJECT sub-section of the + # post-auth section. + # + # Add the ldap module name (or instance) if you have set + # 'edir = yes' in the ldap module configuration + # + Post-Auth-Type REJECT { + # log failed authentications in SQL, too. + -sql + attr_filter.access_reject + + # + # Let the outer session know which module failed, and why. + # + update outer.session-state { + &Module-Failure-Message := &request:Module-Failure-Message + } + } +} + +# +# When the server decides to proxy a request to a home server, +# the proxied request is first passed through the pre-proxy +# stage. This stage can re-write the request, or decide to +# cancel the proxy. +# +# Only a few modules currently have this method. +# +pre-proxy { + # Uncomment the following line if you want to change attributes + # as defined in the preproxy_users file. +# files + + # Uncomment the following line if you want to filter requests + # sent to remote servers based on the rules defined in the + # 'attrs.pre-proxy' file. +# attr_filter.pre-proxy + + # If you want to have a log of packets proxied to a home + # server, un-comment the following line, and the + # 'detail pre_proxy_log' section, above. +# pre_proxy_log +} + +# +# When the server receives a reply to a request it proxied +# to a home server, the request may be massaged here, in the +# post-proxy stage. +# +post-proxy { + + # If you want to have a log of replies from a home server, + # un-comment the following line, and the 'detail post_proxy_log' + # section, above. +# post_proxy_log + + # Uncomment the following line if you want to filter replies from + # remote proxies based on the rules defined in the 'attrs' file. +# attr_filter.post-proxy + + # + # If you are proxying LEAP, you MUST configure the EAP + # module, and you MUST list it here, in the post-proxy + # stage. + # + # You MUST also use the 'nostrip' option in the 'realm' + # configuration. Otherwise, the User-Name attribute + # in the proxied request will not match the user name + # hidden inside of the EAP packet, and the end server will + # reject the EAP request. + # + eap +} + +} # inner-tunnel server block diff --git a/raddb/sites-available/originate-coa b/raddb/sites-available/originate-coa new file mode 100644 index 0000000..3325b88 --- /dev/null +++ b/raddb/sites-available/originate-coa @@ -0,0 +1,185 @@ +# -*- text -*- +###################################################################### +# +# The server can originate Change of Authorization (CoA) or +# Disconnect request packets. These packets are used to dynamically +# change the parameters of a users session (bandwidth, etc.), or +# to forcibly disconnect the user. +# +# There are some caveats. Not all NAS vendors support this +# functionality. Even for the ones that do, it may be difficult to +# find out what needs to go into a CoA-Request or Disconnect-Request +# packet. All we can suggest is to read the NAS documentation +# available from the vendor. That documentation SHOULD describe +# what information their equipment needs to see in a CoA packet. +# +# This information is usually a list of attributes such as: +# +# NAS-IP-Address (or NAS-IPv6 address) +# NAS-Identifier +# User-Name +# Acct-Session-Id +# +# CoA packets can be originated when a normal Access-Request or +# Accounting-Request packet is received. Simply update the +# "coa" list: +# +# update coa { +# &User-Name = "%{User-Name}" +# &Acct-Session-Id = "%{Acct-Session-Id}" +# &NAS-IP-Address = "%{NAS-IP-Address}" +# } +# +# And the CoA packet will be sent. You can also send Disconnect +# packets by using "update disconnect { ...". +# +# This "update coa" entry can be placed in any section (authorize, +# preacct, etc.), EXCEPT for pre-proxy and post-proxy. The CoA +# packets CANNOT be sent if the original request has been proxied. +# +# The CoA functionality works best when the RADIUS server and +# the NAS receiving CoA packets are on the same network. +# +# If "update coa { ... " is used, and then later it becomes necessary +# to not send a CoA request, the following example can suppress the +# CoA packet: +# +# update control { +# &Send-CoA-Request = No +# } +# +# The default destination of a CoA packet is the NAS (or client) +# the sent the original Access-Request or Accounting-Request. See +# raddb/clients.conf for a "coa_server" configuration that ties +# a client to a specific home server, or to a home server pool. +# +# If you need to send the packet to a different destination, update +# the "coa" list with one of: +# +# Packet-Dst-IP-Address = ... +# Packet-Dst-IPv6-Address = ... +# Home-Server-Pool = ... +# +# That specifies an Ipv4 or IPv6 address, or a home server pool +# (such as the "coa" pool example below). This use is not +# recommended, however, It is much better to point the client +# configuration directly at the CoA server/pool, as outlined +# earlier. +# +# If the CoA port is non-standard, you can also set: +# +# Packet-Dst-Port +# +# to have the value of the port. +# +###################################################################### + +# +# When CoA packets are sent to a NAS, the NAS is acting as a +# server (see RFC 5176). i.e. it has a type (accepts CoA and/or +# Disconnect packets), an IP address (or IPv6 address), a +# destination port, and a shared secret. +# +home_server example-coa { + type = coa + + # + # Note that a home server of type "coa" MUST be a real NAS, + # with an ipaddr or ipv6addr. It CANNOT point to a virtual + # server. + # + # Change this IP address to the IP address of the NAS. + # + ipaddr = 192.0.2.42 + port = 3799 + + # This secret SHOULD NOT be the same as the shared + # secret in a "client" section. + secret = testing1234 + + # CoA specific parameters. See raddb/proxy.conf for details. + coa { + irt = 2 + mrt = 16 + mrc = 5 + mrd = 30 + } +} + +# +# CoA servers can be put into pools, just like normal servers. +# +home_server_pool coa { + type = fail-over + + # Point to the CoA server above. + home_server = example-coa + + # CoA requests are run through the pre-proxy section. + # CoA responses are run through the post-proxy section. + virtual_server = originate-coa.example.com + + # + # Home server pools of type "coa" cannot (currently) have + # a "fallback" configuration. + # +} + +# +# When this virtual server is run, the original request has FINISHED +# processing. i.e. the reply has already been sent to the NAS. +# You can access the attributes in the original packet, reply, and +# control items, but changing them will have NO EFFECT. +# +# The CoA packet is in the "proxy-request" attribute list. +# The CoA reply (if any) is in the "proxy-reply" attribute list. +# +server originate-coa.example.com { + pre-proxy { + update proxy-request { + NAS-IP-Address = 192.0.2.42 + } + } + + # + # Handle the responses here. + # + post-proxy { + switch &proxy-reply:Packet-Type { + case CoA-ACK { + ok + } + + case CoA-NAK { + # the NAS didn't like the CoA request + ok + } + + case Disconnect-ACK { + ok + } + + case Disconnect-NAK { + # the NAS didn't like the Disconnect request + ok + } + + # Invalid packet type. This shouldn't happen. + case { + fail + } + } + + # + # These methods are run when there is NO response + # to the request. + # + Post-Proxy-Type Fail-CoA { + ok + } + + Post-Proxy-Type Fail-Disconnect { + ok + } + } +} diff --git a/raddb/sites-available/proxy-inner-tunnel b/raddb/sites-available/proxy-inner-tunnel new file mode 100644 index 0000000..938d954 --- /dev/null +++ b/raddb/sites-available/proxy-inner-tunnel @@ -0,0 +1,47 @@ +# -*- text -*- +###################################################################### +# +# This is a virtual server that handles *only* inner tunnel +# requests for EAP-TTLS and PEAP types. +# +# $Id$ +# +###################################################################### + +server proxy-inner-tunnel { + +# +# This example is very simple. All inner tunnel requests get +# proxied to another RADIUS server. +# +authorize { + # + # Do other things here, as necessary. + # + # e.g. run the "realms" module, to decide how to proxy + # the inner tunnel request. + # + + update control { + # You should update this to be one of your realms. + &Proxy-To-Realm := "example.com" + } +} + +authenticate { + # + # This is necessary so that the inner tunnel EAP-MSCHAPv2 + # method can be called. That method takes care of turning + # EAP-MSCHAPv2 into plain MS-CHAPv2, if necessary. + eap +} + +post-proxy { + # + # This is necessary for LEAP, or if you set: + # + # proxy_tunneled_request_as_eap = no + # + eap +} +} diff --git a/raddb/sites-available/resource-check b/raddb/sites-available/resource-check new file mode 100644 index 0000000..486c3b8 --- /dev/null +++ b/raddb/sites-available/resource-check @@ -0,0 +1,140 @@ +# -*- text -*- +###################################################################### +# +# This virtual server provides an example of how to dynamically amend the +# control flow within some virtual server's policy on the basis of the status +# of some resource, such as an external database. +# +# This resource-check virtual server receives periodic dummy server-status +# requests that trigger an arbitrary set of checks. On the basis of those +# checks the status of an instance of the rlm_always module, that we refer to +# as the "control module", is updated to reflect the system status. +# +# Elsewhere, some other virtual server (the "controlled virtual server") uses +# the control module to make decisions during the processing of incoming +# requests. By amending the status of the control module in response to the +# system status this virtual server is able to manipulate the outcome of the +# controlled virtual server. +# +# Firstly, the authorize section of this virtual server will need to be +# amended to check the status of the external resources and to set the status +# of the control module appropriately, as described in the inline comments +# below... +# +# In addition to configuring and activating this virtual server, a control +# module must be configured as an instance of rlm_always in mods-enabled, for +# example: +# +# always db_online { +# # Default to online +# rcode = ok +# } +# +# Now trigger the resource checks by sending a server-status request to this +# virtual server, as follows: +# +# echo "Message-Authenticator = 0x00" | \ +# radclient -r 1 -t 3 -q 127.0.0.1:18122 status testing123 +# +# The trigger could be invoked by a cron job or if more frequent checks than +# once per minute are required a systemd timer might be used. +# +# The current status of the control module can be examined at any time using +# radmin: +# +# radmin -e 'show module status db_online' +# +# For radmin to work requires that the control-socket virtual server is +# configured and enabled. +# +# The controlled virtual server will contain some control flow decision that +# uses the control module, for example: +# +# server default { +# +# ... +# +# authorize { +# +# # If the database is not healthy then remain silent to trigger +# # NAS failover +# # +# db_online { +# fail = 1 +# } +# if (fail) { +# do_not_respond +# } +# +# sql +# +# pap +# } +# +# ... +# +# +# The configuration for this virtual server follows and should be amended as +# required... +# + + +# +# Listen on a local port for Server-Status requests that trigger the resource +# checks. +# +# This uses the normal set of clients, with the same secret as for +# authentication and accounting. +# +listen { + type = status + ipaddr = 127.0.0.1 + port = 18122 + virtual_server = resource_check +} + + +# +# Within this virual server we provide only an Autz-Type Status-Server section +# whose task is to perform the resource checks and sets the status of the +# "control module" +# +server resource-check { + +authorize { + +Autz-Type Status-Server { + + # + # In this example we check whether a PostgreSQL database is in + # recovery (or inaccessible) and when this is the case we fail the + # db_online control module. + # + # Other modules could be used here. + # + # You can even invoke synchronous checks using the %{exec:...} xlat in + # which case timeout should be set to less than the check trigger + # interval to avoid buildup of checks when resources do not respond. + # See rlm_exec for details. + # + if ("%{sql:SELECT pg_is_in_recovery()}" != "f") { + + # Fail the db_online module, if it isn't already + if ("%{db_online:}" != "fail") { + %{db_online:fail} + } + + } else { + + # Set the db_online module status to alive, if it isn't already + if ("%{db_online:}" != "alive") { + %{db_online:alive} + } + + } + +} + +} + +} diff --git a/raddb/sites-available/robust-proxy-accounting b/raddb/sites-available/robust-proxy-accounting new file mode 100644 index 0000000..7371643 --- /dev/null +++ b/raddb/sites-available/robust-proxy-accounting @@ -0,0 +1,177 @@ +# -*- text -*- +###################################################################### +# +# This is a sample configuration for robust proxy accounting. +# accounting packets are proxied, OR logged locally if all +# home servers are down. When the home servers come back up, +# the accounting packets are forwarded. +# +# This method enables the server to proxy all packets to the +# home servers when they're up, AND to avoid writing to the +# detail file in most situations. +# +# In most situations, proxying of accounting messages is done +# in a "pass-through" fashion. If the home server does not +# respond, then the proxy server does not respond to the NAS. +# That means that the NAS must retransmit packets, sometimes +# forever. This example shows how the proxy server can still +# respond to the NAS, even if all home servers are down. +# +# This configuration could be done MUCH more simply if ALL +# packets were written to the detail file. But that would +# involve a lot more disk writes, which may not be a good idea. +# +# This file is NOT meant to be used as-is. It needs to be +# edited to match your local configuration. +# +# Please see the sites-available/default file, in the +# "Post-Proxy-Type Fail-Accounting" section. That should be +# configured to write packets to the "detail.example.com" +# file when proxying fails. The "listen" section below will +# then read packets from that file, and proxy them. +# +# See also mods-available/detail.example.com, which is the +# module that writes the "detail.example.com" file. +# +# +# $Id$ +# +###################################################################### + +# (1) Define two home servers. +home_server home1.example.com { + type = acct + ipaddr = 192.0.2.10 + port = 1813 + secret = testing123 + + # Mark this home server alive ONLY when it starts being responsive + status_check = request + username = "test_user_status_check" + + # Set the response timeout aggressively low. + # You MAY have to increase this, depending on tests with + # your local installation. + response_window = 6 +} + +home_server home2.example.com { + type = acct + ipaddr = 192.0.2.20 + port = 1813 + secret = testing123 + + # Mark this home server alive ONLY when it starts being responsive + status_check = request + username = "test_user_status_check" + + # Set the response timeout aggressively low. + # You MAY have to increase this, depending on tests with + # your local installation. + response_window = 6 +} + +# (2) Put all of the servers into a pool. +home_server_pool acct_pool.example.com { + type = load-balance # other types are OK, too. + + home_server = home1.example.com + home_server = home2.example.com + # add more home_server's here. + + # for pre/post-proxy policies + virtual_server = home.example.com +} + +# (3) Define a realm for these home servers. +# It should NOT be used as part of normal proxying decisions! +realm acct_realm.example.com { + acct_pool = acct_pool.example.com +} + +# (4) Define a detail file writer. +# See raddb/modules/detail.example.com + +# (5) Define a virtual server to handle pre/post-proxy re-writing +server home.example.com { + pre-proxy { + # Insert pre-proxy rules here + } + + post-proxy { + # Insert post-proxy rules here + + # This will be called when the CURRENT packet failed + # to be proxied. This may happen when one home server + # suddenly goes down, even though another home server + # may be alive. + # + # i.e. the current request has run out of time, so it + # cannot fail over to another (possibly) alive server. + # + # We want to respond to the NAS, so that it can stop + # re-sending the packet. We write the packet to the + # "detail" file, where it will be read, and sent to + # another home server. + # + Post-Proxy-Type Fail-Accounting { + detail.example.com + + # Now that the packet has been stored + # mark that we can respond to the NAS + acct_response + } + + # + # This section is run when there are problems + # proxying Access-Request packets + # + Post-Proxy-Type Fail-Authentication { + # add policies here + } + + } + + + # Read accounting packets from the detail file(s) for + # the home server. + # + # Note that you can have only ONE "listen" section reading + # detail files from a particular directory. That is why the + # destination host name is used as part of the directory name + # below. Having two "listen" sections reading detail files + # from the same directory WILL cause problems. The packets + # may be read by one, the other, or both "listen" sections. + listen { + type = detail + filename = "${radacctdir}/detail.example.com/detail-*:*" + + # + # See sites-available/buffered-sql for documentation + # on the configuration items for a detail listener. + # + load_factor = 90 + track = yes + } + + # All packets read from the detail file are proxied back to + # the home servers. + # + # The normal pre/post-proxy rules are applied to them, too. + # + # If the home servers are STILL down, then the server stops + # reading the detail file, and queues the packets for a later + # retransmission. The Post-Proxy-Type "Fail" handler is NOT + # called. + # + # When the home servers come back up, the packets are forwarded, + # and the detail file processed as normal. + accounting { + # You may want accounting policies here... + + update control { + &Proxy-To-Realm := 'acct_realm.example.com' + } + } + +} diff --git a/raddb/sites-available/soh b/raddb/sites-available/soh new file mode 100644 index 0000000..86748b6 --- /dev/null +++ b/raddb/sites-available/soh @@ -0,0 +1,34 @@ +# This is a simple server for the MS SoH requests generated by the +# peap module - see "eap.conf" for more info + +# Requests are ONLY passed through the authorize section, and cannot +# current be proxied (in any event, the radius attributes used are +# internal). + +server soh-server { + authorize { + if (&SoH-Supported == no) { + # client NAKed our request for SoH - not supported, or turned off + update config { + &Auth-Type = Accept + } + } + else { + # client replied; check something - this is a local policy issue! + if (&SoH-MS-Windows-Health-Status =~ /antivirus (warn|error) /) { + update config { + &Auth-Type = Reject + } + update reply { + &Reply-Message = 'You must have antivirus enabled & installed!' + } + } + else { + update config { + &Auth-Type = Accept + } + } + } + } +} + diff --git a/raddb/sites-available/status b/raddb/sites-available/status new file mode 100644 index 0000000..e7d4346 --- /dev/null +++ b/raddb/sites-available/status @@ -0,0 +1,127 @@ +# -*- text -*- +###################################################################### +# +# A virtual server to handle ONLY Status-Server packets. +# +# Server statistics can be queried with a properly formatted +# Status-Server request. See dictionary.freeradius for comments. +# +# If radiusd.conf has "status_server = yes", then any client +# will be able to send a Status-Server packet to any port +# (listen section type "auth", "acct", or "status"), and the +# server will respond. +# +# If radiusd.conf has "status_server = no", then the server will +# ignore Status-Server packets to "auth" and "acct" ports. It +# will respond only if the Status-Server packet is sent to a +# "status" port. +# +# The server statistics are available ONLY on socket of type +# "status". Queries for statistics sent to any other port +# are ignored. +# +# Similarly, a socket of type "status" will not process +# authentication or accounting packets. This is for security. +# +# $Id$ +# +###################################################################### + +server status { + listen { + # ONLY Status-Server is allowed to this port. + # ALL other packets are ignored. + type = status + + ipaddr = 127.0.0.1 + port = 18121 + } + + # + # We recommend that you list ONLY management clients here. + # i.e. NOT your NASes or Access Points, and for an ISP, + # DEFINITELY not any RADIUS servers that are proxying packets + # to you. + # + # If you do NOT list a client here, then any client that is + # globally defined (i.e. all of them) will be able to query + # these statistics. + # + # Do you really want your partners seeing the internal details + # of what your RADIUS server is doing? + # + client admin { + ipaddr = 127.0.0.1 + secret = adminsecret + } + + # + # Simple authorize section. The "Autz-Type Status-Server" + # section will work here, too. See "raddb/sites-available/default". + authorize { + ok + + # respond to the Status-Server request. + Autz-Type Status-Server { + ok + } + } +} + +# Statistics can be queried via a number of methods: +# +# All packets received/sent by the server (1 = auth, 2 = acct) +# FreeRADIUS-Statistics-Type = 3 +# +# All packets proxied by the server (4 = proxy-auth, 8 = proxy-acct) +# FreeRADIUS-Statistics-Type = 12 +# +# All packets sent && received: +# FreeRADIUS-Statistics-Type = 15 +# +# Internal server statistics: +# FreeRADIUS-Statistics-Type = 16 +# +# All packets for a particular client (globally defined) +# FreeRADIUS-Statistics-Type = 35 +# FreeRADIUS-Stats-Client-IP-Address = 192.0.2.1 +# +# All packets for a client attached to a "listen" ip/port +# FreeRADIUS-Statistics-Type = 35 +# FreeRADIUS-Stats-Client-IP-Address = 192.0.2.1 +# FreeRADIUS-Stats-Server-IP-Address = 127.0.0.1 +# FreeRADIUS-Stats-Server-Port = 1812 +# +# All packets for a "listen" IP/port +# FreeRADIUS-Statistics-Type = 67 +# FreeRADIUS-Stats-Server-IP-Address = 127.0.0.1 +# FreeRADIUS-Stats-Server-Port = 1812 +# +# All packets for a home server IP / port +# FreeRADIUS-Statistics-Type = 131 +# FreeRADIUS-Stats-Server-IP-Address = 192.0.2.2 +# FreeRADIUS-Stats-Server-Port = 1812 + +# +# You can also get exponentially weighted moving averages of +# response times (in usec) of home servers. Just set the config +# item "historic_average_window" in a home_server section. +# +# By default it is zero (don't calculate it). Useful values +# are between 100, and 10,000. The server will calculate and +# remember the moving average for this window, and for 10 times +# that window. +# + +# +# Some of this could have been simplified. e.g. the proxy-auth and +# proxy-acct bits aren't completely necessary. But using them permits +# the server to be queried for ALL inbound && outbound packets at once. +# This gives a good snapshot of what the server is doing. +# +# Due to internal limitations, the statistics might not be exactly up +# to date. Do not expect all of the numbers to add up perfectly. +# The Status-Server packets are also counted in the total requests && +# responses. The responses are counted only AFTER the response has +# been sent. +# diff --git a/raddb/sites-available/tls b/raddb/sites-available/tls new file mode 100644 index 0000000..137fcbc --- /dev/null +++ b/raddb/sites-available/tls @@ -0,0 +1,696 @@ +###################################################################### +# +# RADIUS over TLS (radsec) +# +# When a new client connects, the various TLS parameters for the +# connection are available as dynamic expansions, e.g. +# +# %{listen:TLS-Client-Cert-Common-Name} +# +# Along with other TLS-Client-Cert-... attributes. +# These expansions will only exist if the relevant fields +# are in the client certificate. Read the debug output to see +# which fields are available. Look for output like the following: +# +# (0) TLS - Creating attributes from certificate OIDs +# (0) TLS-Client-Cert-Subject-Alt-Name-Dns := "one.example.org" +# (0) TLS-Client-Cert-Subject-Alt-Name-Dns := "two.example.org" +# ... +# +# It is also possible to distinguish between connections which have +# TLS enables, and ones which do not. The expansion: +# +# %{listen:tls} +# +# Will return "yes" if the connection has TLS enabled. It will +# return "no" if TLS is not enabled for a particular listen section. +# +# A number of TLS-Client-Cert-.. attributes holds X509v3 extensions +# data, attributes named the way OpenSSL names them. It is possible +# to extract data for an extension not known to OpenSSL by defining +# a custom string attribute which contains extension OID in it's +# name after 'TLS-Client-Cert-' prefix. E.g.: +# +# ATTRIBUTE TLS-Client-Cert-1.3.6.1.4.1.311.21.7 3002 string +# +# which will yield something simmilar to: +# +# (0) eap_tls: TLS - Creating attributes from certificate OIDs +# (0) eap_tls: TLS-Client-Cert-1.3.6.1.4.1.311.21.7 += "0x302e06" +# ... +# +###################################################################### + +listen { + ipaddr = * + port = 2083 + + # + # TCP and TLS sockets can accept Access-Request and + # Accounting-Request on the same socket. + # + # auth = only Access-Request + # acct = only Accounting-Request + # auth+acct = both + # coa = only CoA / Disconnect requests + # + type = auth+acct + + # For now, only TCP transport is allowed. + proto = tcp + + # Send packets to the default virtual server + virtual_server = default + + clients = radsec + + # + # Use the haproxy "PROXY protocol". + # + # This configuration allows for many FreeRADIUS servers to be + # behind a haproxy server. The "PROXY protocol" allows + # haproxy to send the actual client IP to FreeRADIUS. + # + # This will work ONLY for RadSec (TLS). Both the haproxy AND + # the RadSec client MUST be listed as allowed RADIUS clients. + # + # haproxy needs to have "send-proxy" configured for this server. + # Health checks should be turned off, as haproxy does not + # support RADIUS health checks. + # + # The main use of this feature is for scalability. There is no + # longer any need to have a RADIUS proxy as a load balancer. + # haproxy is fast, stable, and supports dynamic reloads! + # + # The only problem is that many RADIUS clients do not support + # RadSec. That situation will hopefully change over time. + # +# proxy_protocol = no + + # + # When this is set to "yes", new TLS connections + # are processed through a section called + # + # Autz-Type New-TLS-Connection { + # ... + # } + # + # The request contains TLS client certificate attributes, + # and nothing else. The debug output will print which + # attributes are available on your system. + # + # If the section returns "ok" or "updated", then the + # connection is accepted. Otherwise the connection is + # terminated. + # +# check_client_connections = yes + + # + # Connection limiting for sockets with "proto = tcp". + # + limit { + # + # Limit the number of simultaneous TCP connections to the socket + # + # The default is 16. + # Setting this to 0 means "no limit" + max_connections = 16 + + # The per-socket "max_requests" option does not exist. + + # + # The lifetime, in seconds, of a TCP connection. After + # this lifetime, the connection will be closed. + # + # Setting this to 0 means "forever". + lifetime = 0 + + # + # The idle timeout, in seconds, of a TCP connection. + # If no packets have been received over the connection for + # this time, the connection will be closed. + # + # Setting this to 0 means "no timeout". + # + # We STRONGLY RECOMMEND that you set an idle timeout. + # + idle_timeout = 30 + } + + # This is *exactly* the same configuration as used by the EAP-TLS + # module. It's OK for testing, but for production use it's a good + # idea to use different server certificates for EAP and for RADIUS + # transport. + # + # If you want only one TLS configuration for multiple sockets, + # then we suggest putting "tls { ...}" into radiusd.conf. + # The subsection below can then be changed into a reference: + # + # tls = ${tls} + # + # Which means "the tls sub-section is not here, but instead is in + # the top-level section called 'tls'". + # + # If you have multiple tls configurations, you can put them into + # sub-sections of a top-level "tls" section. There's no need to + # call them all "tls". You can then use: + # + # tls = ${tls.site1} + # + # to refer to the "site1" sub-section of the "tls" section. + # + tls { + private_key_password = whatever + private_key_file = ${certdir}/server.pem + + # Accept an expired Certificate Revocation List + # + # allow_expired_crl = no + + # If Private key & Certificate are located in + # the same file, then private_key_file & + # certificate_file must contain the same file + # name. + # + # If ca_file (below) is not used, then the + # certificate_file below MUST include not + # only the server certificate, but ALSO all + # of the CA certificates used to sign the + # server certificate. + certificate_file = ${certdir}/server.pem + + # Trusted Root CA list + # + # ALL of the CA's in this list will be trusted + # to issue client certificates for authentication. + # + # In general, you should use self-signed + # certificates for 802.1x (EAP) authentication. + # In that case, this CA file should contain + # *one* CA certificate. + # + # This parameter is used only for EAP-TLS, + # when you issue client certificates. If you do + # not use client certificates, and you do not want + # to permit EAP-TLS authentication, then delete + # this configuration item. + ca_file = ${cadir}/ca.pem + + # For DH cipher suites to work in OpenSSL < 1.1.0, + # you have to run OpenSSL to create the DH file + # first: + # + # openssl dhparam -out certs/dh 2048 + # + # For OpenSSL >= 1.1.0, just leave this commented + # out, and OpenSSL will do the right thing. + # +# dh_file = ${certdir}/dh + + # + # If your system doesn't have /dev/urandom, + # you will need to create this file, and + # periodically change its contents. + # + # For security reasons, FreeRADIUS doesn't + # write to files in its configuration + # directory. + # +# random_file = /dev/urandom + + # + # The default fragment size is 1K. + # However, it's possible to send much more data than + # that over a TCP connection. The upper limit is 64K. + # Setting the fragment size to more than 1K means that + # there are fewer round trips when setting up a TLS + # connection. But only if the certificates are large. + # + fragment_size = 8192 + + # include_length is a flag which is + # by default set to yes If set to + # yes, Total Length of the message is + # included in EVERY packet we send. + # If set to no, Total Length of the + # message is included ONLY in the + # First packet of a fragment series. + # + # include_length = yes + + # Check the Certificate Revocation List + # + # 1) Copy CA certificates and CRLs to same directory. + # 2) Execute 'c_rehash <CA certs&CRLs Directory>'. + # 'c_rehash' is OpenSSL's command. + # 3) uncomment the line below. + # 5) Restart radiusd + # check_crl = yes + ca_path = ${cadir} + + # OpenSSL does not reload contents of ca_path dir over time. + # That means that if check_crl is enabled and CRLs are loaded + # from ca_path dir, at some point CRLs will expire and + # RADIUSd will stop authenticating NASes. + # If ca_path_reload_interval is non-zero, it will force OpenSSL + # to reload all data from ca_path periodically + # + # Flush ca_path each hour + ca_path_reload_interval = 3600 + + # + # If check_cert_issuer is set, the value will + # be checked against the DN of the issuer in + # the client certificate. If the values do not + # match, the certificate verification will fail, + # rejecting the user. + # + # This check can be done more generally by checking + # the value of the TLS-Client-Cert-Issuer attribute. + # This check can be done via any mechanism you choose. + # + # check_cert_issuer = "/C=GB/ST=Berkshire/L=Newbury/O=My Company Ltd" + + # + # If check_cert_cn is set, the value will + # be xlat'ed and checked against the CN + # in the client certificate. If the values + # do not match, the certificate verification + # will fail rejecting the user. + # + # This check is done only if the previous + # "check_cert_issuer" is not set, or if + # the check succeeds. + # + # In 2.1.10 and later, this check can be done + # more generally by checking the value of the + # TLS-Client-Cert-Common-Name attribute. This check + # can be done via any mechanism you choose. + # + # check_cert_cn = %{User-Name} + # + # Set this option to specify the allowed + # TLS cipher suites. The format is listed + # in "man 1 ciphers". + cipher_list = "DEFAULT" + + # If enabled, OpenSSL will use server cipher list + # (possibly defined by cipher_list option above) + # for choosing right cipher suite rather than + # using client-specified list which is OpenSSl default + # behavior. Having it set to yes is a current best practice + # for TLS + cipher_server_preference = no + + # + # Older TLS versions are deprecated. But for RadSec, + # we CAN allow TLS 1.3. + # + tls_min_version = "1.2" + tls_max_version = "1.3" + + # + # Session resumption / fast reauthentication + # cache. + # + # The cache contains the following information: + # + # session Id - unique identifier, managed by SSL + # User-Name - from the Access-Accept + # Stripped-User-Name - from the Access-Request + # Cached-Session-Policy - from the Access-Accept + # + # The "Cached-Session-Policy" is the name of a + # policy which should be applied to the cached + # session. This policy can be used to assign + # VLANs, IP addresses, etc. It serves as a useful + # way to re-apply the policy from the original + # Access-Accept to the subsequent Access-Accept + # for the cached session. + # + # On session resumption, these attributes are + # copied from the cache, and placed into the + # reply list. + # + # You probably also want "use_tunneled_reply = yes" + # when using fast session resumption. + # + cache { + # + # Enable it. The default is "no". + # Deleting the entire "cache" subsection + # Also disables caching. + # + # + # The session cache requires the use + # of the "name" and "persist_dir" configuration items, below. + # + # The internal OpenSSL session cache has been permanently + # disabled. + # + # You can disallow resumption for a + # particular user by adding the following + # attribute to the control item list: + # + # Allow-Session-Resumption = No + # + # If "enable = no" below, you CANNOT + # enable resumption for just one user + # by setting the above attribute to "yes". + # + enable = no + + # + # Lifetime of the cached entries, in hours. + # The sessions will be deleted after this + # time. + # + lifetime = 24 # hours + + # + # Internal "name" of the session cache. + # Used to distinguish which TLS context + # sessions belong to. + # + # The server will generate a random value + # if unset. This will change across server + # restart so you MUST set the "name" if you + # want to persist sessions (see below). + # + # If you use IPv6, change the "ipaddr" below + # to "ipv6addr" + # + #name = "TLS ${..ipaddr} ${..port} ${..proto}" + + # + # Simple directory-based storage of sessions. + # Two files per session will be written, the SSL + # state and the cached VPs. This will persist session + # across server restarts. + # + # The server will need write perms, and the directory + # should be secured from anyone else. You might want + # a script to remove old files from here periodically: + # + # find ${logdir}/tlscache -mtime +2 -exec rm -f {} \; + # + # This feature REQUIRES "name" option be set above. + # + #persist_dir = "${logdir}/tlscache" + } + + # + # Require a client certificate. + # + require_client_cert = yes + + # + # As of version 2.1.10, client certificates can be + # validated via an external command. This allows + # dynamic CRLs or OCSP to be used. + # + # This configuration is commented out in the + # default configuration. Uncomment it, and configure + # the correct paths below to enable it. + # + verify { + # A temporary directory where the client + # certificates are stored. This directory + # MUST be owned by the UID of the server, + # and MUST not be accessible by any other + # users. When the server starts, it will do + # "chmod go-rwx" on the directory, for + # security reasons. The directory MUST + # exist when the server starts. + # + # You should also delete all of the files + # in the directory when the server starts. + # tmpdir = /tmp/radiusd + + # The command used to verify the client cert. + # We recommend using the OpenSSL command-line + # tool. + # + # The ${..ca_path} text is a reference to + # the ca_path variable defined above. + # + # The %{TLS-Client-Cert-Filename} is the name + # of the temporary file containing the cert + # in PEM format. This file is automatically + # deleted by the server when the command + # returns. + # client = "/path/to/openssl verify -CApath ${..ca_path} %{TLS-Client-Cert-Filename}" + } + + # + # When the RadSec clients use SNI, the server will + # automatically choose the correct certificate from + # "realm_dir". See raddb/certs/realms/README.md for + # more information. + # + # Note that the default is to use the same set of + # realm certificates for both EAP and RadSec! If + # this is not what you want, you should use different + # subdirectories or each, e.g. ${certdir}/realms/radsec/, + # and ${certdir}/realms/eap/ + # + # realm_dir = ${certdir}/realms/ + } +} + +clients radsec { + client 127.0.0.1 { + ipaddr = 127.0.0.1 + + # + # Ensure that this client is TLS *only*. + # + proto = tls + + # + # TCP clients can have any shared secret. + # + # TLS clients MUST have the shared secret + # set to "radsec". Or, for "proto = tls", + # you can omit the secret, and it will + # automatically be set to "radsec". + # + secret = radsec + + # + # You can also use a "limit" section here. + # See raddb/clients.conf for examples. + # + # Note that BOTH limits are applied. You + # should therefore set the "listen" limits + # higher than the ones for each individual + # client. + # + } +} + +# +# When a request is proxied to a TLS-enabled home server, +# the TLS parameters are available via the expansion: +# +# %{proxy_listen: ... } +# +# The contents of the expansion are the same as described +# above with the %{listen: ... } expansion, and have similar +# meanings. "client" in this case is the proxy (this system) +# and "server" is the remote system (home server). +# +# Note that the %{proxy_listen: ... } parameters are available +# only AFTER the connection has been made to the home server. +# +home_server tls { + ipaddr = 127.0.0.1 + port = 2083 + + # type can be the same types as for the "listen" section/ + # e.g. auth, acct, auth+acct, coa + type = auth + secret = radsec + proto = tcp + status_check = none + + tls { + # + # Similarly to HTTP, the client can use Server Name + # Indication to inform the RadSec server as to which + # domain it is requesting. This selection allows + # multiple sites to exist at the same IP address. + # + # For example, an identity provider could host + # multiple sites, but present itself with one public + # IP address. If the RadSec clients do not use SNI, + # then they must be configured with the certificate + # of the identity provider. + # + # When SNI is used, the clients can be configured + # with the certificate of the hosted system that + # they're connecting to. This ability means that + # there is no need to change certificates when + # changing providers. In addition, there is no need + # to change the configuration of all RadSec clients + # when the hosting system changes its certifiates. + # Because the hosting system certificates are never used. + # + # Instead, each hosted company is responsible for its + # own certificates, and for its own clients. + # + # SNI also permits the use of a load balancer such as + # haproxy. That load balancer can terminate the TLS + # connection, and then use SNI to route the + # underlying RADIUS TCP traffic to a particular host. + # + # Note that "hostname" here is only for SNI, and is NOT + # the hostname or IP address we connect to. For that, + # see "ipaddr", above. + # + # hostname = "example.com" + + private_key_password = whatever + private_key_file = ${certdir}/client.pem + + # If Private key & Certificate are located in + # the same file, then private_key_file & + # certificate_file must contain the same file + # name. + # + # If ca_file (below) is not used, then the + # certificate_file below MUST include not + # only the server certificate, but ALSO all + # of the CA certificates used to sign the + # server certificate. + certificate_file = ${certdir}/client.pem + + # Trusted Root CA list + # + # ALL of the CA's in this list will be trusted + # to issue client certificates for authentication. + # + # In general, you should use self-signed + # certificates for 802.1x (EAP) authentication. + # In that case, this CA file should contain + # *one* CA certificate. + # + # This parameter is used only for EAP-TLS, + # when you issue client certificates. If you do + # not use client certificates, and you do not want + # to permit EAP-TLS authentication, then delete + # this configuration item. + ca_file = ${cadir}/ca.pem + + # + # Before version 3.2.1, outbound RadSec connections + # would put the home server certificate into the + # TLS-Client-Cert* attributes. Set this configuration + # item to "yes" in order to have the home server + # certificates placed into the "TLS-Cert-*" attributes. + # +# fix_cert_order = yes + + # + # For TLS-PSK, the key should be specified + # dynamically, instead of using a hard-coded + # psk_identity and psk_hexphrase. + # + # The input to the dynamic expansion will be the PSK + # identity supplied by the client, in the + # TLS-PSK-Identity attribute. The output of the + # expansion should be a hex string, of no more than + # 512 characters. The string should not be prefixed + # with "0x". e.g. "abcdef" is OK. "0xabcdef" is not. + # + # psk_query = "%{psksql:select hex(key) from psk_keys where keyid = '%{TLS-PSK-Identity}'}" + + # + # For DH cipher suites to work, you have to + # run OpenSSL to create the DH file first: + # + # openssl dhparam -out certs/dh 1024 + # +# dh_file = ${certdir}/dh +# random_file = /dev/urandom + + # + # The default fragment size is 1K. + # However, TLS can send 64K of data at once. + # It can be useful to set it higher. + # + fragment_size = 8192 + + # include_length is a flag which is + # by default set to yes If set to + # yes, Total Length of the message is + # included in EVERY packet we send. + # If set to no, Total Length of the + # message is included ONLY in the + # First packet of a fragment series. + # + # include_length = yes + + # Check the Certificate Revocation List + # + # 1) Copy CA certificates and CRLs to same directory. + # 2) Execute 'c_rehash <CA certs&CRLs Directory>'. + # 'c_rehash' is OpenSSL's command. + # 3) uncomment the line below. + # 5) Restart radiusd + # check_crl = yes + ca_path = ${cadir} + + # + # If check_cert_issuer is set, the value will + # be checked against the DN of the issuer in + # the client certificate. If the values do not + # match, the certificate verification will fail, + # rejecting the user. + # + # In 2.1.10 and later, this check can be done + # more generally by checking the value of the + # TLS-Client-Cert-Issuer attribute. This check + # can be done via any mechanism you choose. + # + # check_cert_issuer = "/C=GB/ST=Berkshire/L=Newbury/O=My Company Ltd" + + # + # If check_cert_cn is set, the value will + # be xlat'ed and checked against the CN + # in the client certificate. If the values + # do not match, the certificate verification + # will fail rejecting the user. + # + # This check is done only if the previous + # "check_cert_issuer" is not set, or if + # the check succeeds. + # + # In 2.1.10 and later, this check can be done + # more generally by checking the value of the + # TLS-Client-Cert-Common-Name attribute. This check + # can be done via any mechanism you choose. + # + # check_cert_cn = %{User-Name} + # + # Set this option to specify the allowed + # TLS cipher suites. The format is listed + # in "man 1 ciphers". + cipher_list = "DEFAULT" + + # + # Connection timeout for outgoing TLS connections. + # Values are 1..30. + # + connect_timeout = 30 + } +} + +home_server_pool tls { + type = fail-over + home_server = tls +} + +realm tls { + auth_pool = tls +} diff --git a/raddb/sites-available/tls-cache b/raddb/sites-available/tls-cache new file mode 100644 index 0000000..e6451c5 --- /dev/null +++ b/raddb/sites-available/tls-cache @@ -0,0 +1,144 @@ +# -*- text -*- +###################################################################### +# +# This is a virtual server which handles TLS session caching. +# +# $Id$ +# +###################################################################### +# +# In mods-enabled/eap, "cache" subsection +# +# comment out +# +# persist_dir +# +# add +# +# virtual_server = tls-cache +# +# and set +# +# enable = yes +# +# In order to enable caching. +# + +# +# This virtual server SHOULD NOT have any "listen" sections. +# +# +# All of the cache sections key off of &request:TLS-Session-Id +# +# The cache sections also run the "post-auth" section of any +# module which they use. +# +# These sections do not need to return any specific codes (e.g. ok / +# fail /etc.). The cache functionality depends only on which +# attributes are saved / loaded. +# +# For example, if the "cache save" process fails, there is nothing +# that the server can do about that. The users authentication +# session will still succeed. The only difference from a successful +# "cache save" is that the user will be unable to resume their +# session. Instead, they will need to do a full re-authentication +# process. +# +# Similarly for "cache load". If the session (and/or) the VPs are +# not loaded from the cache, then the user will do a full +# re-authentication. +# +# Whilst any store can be used for tls session caching, whatever is +# chosen should be faster than performing a full re-authentication +server tls-cache { + +cache clear { + # clear the cache entry by keying off of &request:TLS-Session-Id + + # An example using redis +# "%{redis:DEL %{request:TLS-Session-ID}}" + + # An example using SQL +# "%{sql:DELETE FROM tls_cache WHERE session_id = '%{request:TLS-Session-ID}'}" +} + +cache save { + # use the key &request:TLS-Session-ID + # save &session-state:TLS-Session-Data + # save &reply:... + + # The &reply: list is initialized to the attributes + # which should be saved. This includes attributes + # mentioned in the "store" subsection of the "cache" + # section configuration. This is the same set of + # attributes which is saved when the 'persist_dir' + # configuration is used. + # + # Note the "store" subsection will only copy matching + # attributes from the &reply: list at the time that + # eap authentication succeeds. + # + # Other attributes can be saved by referring to them + # e.g. &outer.request:... + + # An example using redis +# update { +# &Tmp-String-0 := "%{session-state:TLS-Session-Data}|%{escape:%{reply:Tunnel-Private-Group-ID}}" +# } +# "%{redis: SET %{request:TLS-Session-ID} \"%{Tmp-String-0}\" EX 86400}" + + # An example using SQL +# "%{sql: INSERT INTO tls_cache (session_id, session_data, vlan, expiry) VALUES ('%{request:TLS-Session-ID}', '%{session-state:TLS-Session-Data}', '%{escape:%{reply:Tunnel-Private-Group-ID}}', DATE_ADD(NOW(), INTERVAL 24 HOUR))}" +} + +cache load { + # use the key &request:TLS-Session-ID + # load &session-state:TLS-Session-Data + # load &reply:... + + # Attributes returned in &reply: which are listed + # in the "store" subsection of the "cache" section + # configuration will be copied to &session-state: + # + # Certificate attributes returned in &reply: are added + # to &request: if they do not already exist and if + # EAP-Type is returned it is added to &control: + # + # Any other attributes returned are added to &reply: + + # An example using redis +# update { +# &Tmp-String-0 := "%{redis:GET %{request:TLS-Session-ID}}" +# } +# if (!&Tmp-String-0 || &Tmp-String-0 !~ /^([^|]+)\|([^|]+)$/) { +# return +# } +# update { +# &session-state:TLS-Session-Data := "%{1}" +# &reply:Tunnel-Private-Group-ID := "%{unescape:%{2}}" +# } + + # An example using SQL +# update { +# &Tmp-String-0 := "%{sql:SELECT CONCAT(session_data, '|', vlan) FROM session_cache WHERE session_id = '%{request:TLS-Session-ID}'}" +# } +# if (!&Tmp-String-0 || &Tmp-String-0 !~ /^([^|]+)\|([^|]+)$/) { +# return +# } +# update { +# &session-state:TLS-Session-Data := "%{1}" +# &reply:Tunnel-Private-Group-ID := "%{unescape:%{2}}" +# } +} + +cache refresh { + # refresh the cache entry by keying off of &request:TLS-Session-ID + + # An example using redis +# "%{redis:EXPIRE %{request:TLS-Session-ID} 86400}" + + # An example using SQL +# "%{sql:UPDATE tls_cache SET expiry = DATE_ADD(NOW(), INTERVAL 24 HOUR) WHERE session_id = '%{request:TLS-Session-ID}'}" +} + +} diff --git a/raddb/sites-available/totp b/raddb/sites-available/totp new file mode 100644 index 0000000..e42bf05 --- /dev/null +++ b/raddb/sites-available/totp @@ -0,0 +1,85 @@ +###################################################################### +# +# $Id$ +# +###################################################################### +# +# Simple server to do TOTP and not much else. +# +server totp { +authorize { + # + # TOTP only works for PAP + # + if (!&User-Password) { + reject + } + + # + # The 6-digit TOTP password should be at the end of the + # User-Password attribute. It can be at the beginning or at + # the end, it doesn't really make any difference. Just + # update the regular expression for whatever you want. + # + # If the password doesn't have 6 digits at the end, reject. + # + if (User-Password !~ /^(.*)([0-9]{6})$/) { + reject + } + + # + # Separate the two fields + # + update request { + User-Password := "%{1}" + TOTP-Password := "%{2}" + } + + # + # Get the users' real password and authorization credentials + # from somewhere, such as a database. This should also set + # + # &control:TOTP-Secret + # + -ldap + -sql + + # + # As an example, fake out the TOTP secret + # + # The value should be the base-32 version of the TOTP secret. + # + # Note that the TOTP secret is effectively a password, and + # should be kept secret! At this time, there is no way to + # "hide" or "encrypt" the TOTP secret for a user. Even if it + # was encrypted, the server would still need a key to decrypt + # it. So encrypying this field does not offer much benefit. + # + if (&User-Name == "bob") { + &control:TOTP-Secret := 12345678901234567890 + } + + # + # Verify the 6-digit TOTP password. If the module does not + # return "ok", then the TOTP password is wrong. + # + totp.authenticate + if (!ok) { + reject + } + + # + # Set Auth-Type = PAP + # + pap +} + +authenticate { + # + # Check the User-Password against whatever we found in LDAP + # or SQL. + # + pap +} + +} diff --git a/raddb/sites-available/virtual.example.com b/raddb/sites-available/virtual.example.com new file mode 100644 index 0000000..3c4aea7 --- /dev/null +++ b/raddb/sites-available/virtual.example.com @@ -0,0 +1,32 @@ +# -*- text -*- +###################################################################### +# +# Sample virtual server for internally proxied requests. +# +# See the "realm virtual.example.com" example in "proxy.conf". +# +# $Id$ +# +###################################################################### + +# +# You will want to edit this to your local needs. We suggest copying +# the text from the "default" file here, and then editing the text. +# That way, any changes to the "default" file will not affect this +# virtual server, and vice-versa. +# +# When this virtual server receives the request, the original +# attributes can be accessed as "outer.request", "outer.control", etc. +# See "man unlang" for more details. +# +server virtual.example.com { +authorize { + # insert policies here +} + +authenticate { + # insert policies here +} + +# etc. +} diff --git a/raddb/sites-available/vmps b/raddb/sites-available/vmps new file mode 100644 index 0000000..c5c5078 --- /dev/null +++ b/raddb/sites-available/vmps @@ -0,0 +1,98 @@ +# -*- text -*- +###################################################################### +# +# As of version 2.0.0, the server also supports the VMPS +# protocol. +# +# $Id$ +# +###################################################################### + +server vmps { + listen { + # VMPS sockets only support IPv4 addresses. + ipaddr = * + + # Port on which to listen. + # Allowed values are: + # integer port number + # 1589 is the default VMPS port. + port = 1589 + + # Type of packets to listen for. Here, it is VMPS. + type = vmps + + # Some systems support binding to an interface, in addition + # to the IP address. This feature isn't strictly necessary, + # but for sites with many IP addresses on one interface, + # it's useful to say "listen on all addresses for + # eth0". + # + # If your system does not support this feature, you will + # get an error if you try to use it. + # + # interface = eth0 + } + + # If you have switches that are allowed to send VMPS, but NOT + # RADIUS packets, then list them here as "client" sections. + # + # Note that for compatibility with RADIUS, you still have to + # list a "secret" for each client, though that secret will not + # be used for anything. + + + # And the REAL contents. This section is just like the + # "post-auth" section of radiusd.conf. In fact, it calls the + # "post-auth" component of the modules that are listed here. + # But it's called "vmps" to highlight that it's for VMPS. + # + vmps { + # + # Some requests may not have a MAC address. Try to + # create one using other attributes. + if (!&VMPS-Mac) { + if (&VMPS-Ethernet-Frame =~ /0x.{12}(..)(..)(..)(..)(..)(..).*/) { + update request { + &VMPS-Mac = "%{1}:%{2}:%{3}:%{4}:%{5}:%{6}" + } + } + else { + update request { + &VMPS-Mac = &VMPS-Cookie + } + } + } + + # Do a simple mapping of MAC to VLAN. + # + # See radiusd.conf for the definition of the "mac2vlan" + # module. + # + #mac2vlan + + # required VMPS reply attributes + update reply { + &VMPS-Packet-Type = VMPS-Join-Response + &VMPS-Cookie = &VMPS-Mac + + &VMPS-VLAN-Name = "please_use_real_vlan_here" + + # + # If you have VLAN's in a database, you can select + # the VLAN name based on the MAC address. + # + #&VMPS-VLAN-Name = "%{sql:select ... where mac='%{VMPS-Mac}'}" + } + + # correct reply packet type for reconfirmation requests + # + if (&VMPS-Packet-Type == VMPS-Reconfirm-Request){ + update reply { + &VMPS-Packet-Type := VMPS-Reconfirm-Response + } + } + } + + # Proxying of VMPS requests is NOT supported. +} diff --git a/raddb/templates.conf b/raddb/templates.conf new file mode 100644 index 0000000..7b8b44e --- /dev/null +++ b/raddb/templates.conf @@ -0,0 +1,108 @@ +# -*- text -*- +## +## templates.conf -- configurations to be used in multiple places +## +## $Id$ + +###################################################################### +# +# Version 2.0 has a useful new feature called "templates". +# +# Use templates by adding a line in radiusd.conf: +# +# $INCLUDE templates.conf +# +# The goal of the templates is to have common configuration located +# in this file, and to list only the *differences* in the individual +# sections. This feature is most useful for sections like "clients" +# or "home_servers", where many may be defined, and each one has +# similar repeated configuration. +# +# Something similar to templates can be done by putting common +# configuration into separate files, and using "$INCLUDE file...", +# but this is more flexible, and simpler to understand. It's also +# cheaper for the server, because "$INCLUDE" makes a copy of the +# configuration for inclusion, and templates are simply referenced. +# +# The templates are defined in the "templates" section, so that they +# do not affect the rest of the server configuration. +# +# A section can reference a template by using "$template name" +# +templates { + # + # The contents of the templates section are other + # configuration sections that would normally go into + # the configuration files. + # + + # + # This is a default template for the "home_server" section. + # Note that there is no name for the section. + # + # Any configuration item that is valid for a "home_server" + # section is also valid here. When a "home_server" section + # is defined in proxy.conf, this section is referenced as + # the template. + # + # Configuration items that are explicitly listed in a + # "home_server" section of proxy.conf are used in + # preference to the configuration items listed here. + # + # However, if a configuration item is NOT listed in a + # "home_server" section of proxy.conf, then the value here + # is used. + # + # This functionality lets you put common configuration into + # a template, and to put only the unique configuration + # items in "proxy.conf". Each section in proxy.conf can + # then contain a line "$template home_server", which will + # cause it to reference this template. + # + home_server { + response_window = 20 + zombie_period = 40 + revive_interval = 120 + # + # Etc. + } + + # + # You can also have named templates. For example, if you + # are proxying to 3 different home servers all at the same + # site, with identical configurations (other than IP + # addresses), you can use this named template. + # + + # Then, each "home_server" section in "proxy.conf" would + # only list the IP address of that home server, and a + # line saying + # + # $template example_com + # + # That would tell FreeRADIUS to look in the section below + # for the rest of the configuration items. + # + # For various reasons, you shouldn't have a "." in the template + # name. Doing so means that the server will be unable to find + # the template. + # + example_com { + type = auth + port = 1812 + secret = testing123 + response_window = 20 + # + # Etc... + } + + # + # You can have templates for other sections, too, but they + # seem to be most useful for home_servers. + # + # For now, you can use templates only for sections in + # radiusd.conf, not sub-sections. So you still have to use + # the "$INCLUDE file.." method for things like defining + # multiple "sql" modules, each with similar configuration. + # +} diff --git a/raddb/trigger.conf b/raddb/trigger.conf new file mode 100644 index 0000000..413a182 --- /dev/null +++ b/raddb/trigger.conf @@ -0,0 +1,281 @@ +# -*- text -*- +## +## trigger.conf -- Events in the server can trigger a hook to be executed. +## +## $Id$ + +# +# The triggers are named as "type.subtype.value". These names refer +# to subsections and then configuration items in the "trigger" +# section below. When an event occurs, the trigger is executed. The +# trigger is simply a program that is run, with optional arguments. +# +# The server does not wait when a trigger is executed. It is simply +# a "one-shot" event that is sent. +# +# The trigger names should be self-explanatory. +# + +# +# SNMP configuration. +# +# For now, this is only for SNMP traps. +# +# They are enabled by uncommenting (or adding) "$INCLUDE trigger.conf" +# in the main "radiusd.conf" file. +# +# The traps *REQUIRE* that the files in the "mibs" directory be copied +# to the global mibs directory, usually /usr/share/snmp/mibs/. +# If this is not done, the "snmptrap" program has no idea what information +# to send, and will not work. The MIB installation is *NOT* done as +# part of the default installation, so that step *MUST* be done manually. +# +# The global MIB directory can be found by running the following command: +# +# snmptranslate -Dinit_mib .1.3 2>&1 | grep MIBDIR | sed "s/' .*//;s/.* '//;s/.*://" +# +# Or maybe just: +# +# snmptranslate -Dinit_mib .1.3 2>&1 | grep MIBDIR +# +# If you have copied the MIBs to that directory, you can test the +# FreeRADIUS MIBs by running the following command: +# +# snmptranslate -m +FREERADIUS-NOTIFICATION-MIB -IR -On serverStart +# +# It should print out: +# +# .1.3.6.1.4.1.11344.4.1.1 +# +# As always, run the server in debugging mode after enabling the +# traps. You will see the "snmptrap" command being run, and it will +# print out any errors or issues that it encounters. Those need to +# be fixed before running the server in daemon mode. +# +# We also suggest running in debugging mode as the "radiusd" user, if +# you have "user/group" set in radiusd.conf. The "snmptrap" program +# may behave differently when run as "root" or as the "radiusd" user. +# +snmp { + # + # Configuration for SNMP traps / notifications + # + # To disable traps, edit "radiusd.conf", and delete the line + # which says "$INCUDE trigger.conf" + # + trap { + # + # Absolute path for the "snmptrap" command, and + # default command-line arguments. + # + # You can disable traps by changing the command to + # "/bin/echo". + # + cmd = "/usr/bin/snmptrap -v2c" + + # + # Community string + # + community = "public" + + # + # Agent configuration. + # + agent = "localhost ''" + } +} + +# +# The "snmptrap" configuration defines the full command used to run the traps. +# +# This entry should not be edited. Instead, edit the "trap" section above. +# +snmptrap = "${snmp.trap.cmd} -c ${snmp.trap.community} ${snmp.trap.agent} FREERADIUS-NOTIFICATION-MIB" + +# +# The individual triggers are defined here. You can disable one by +# deleting it, or by commenting it out. You can disable an entire +# section of traps by deleting the section. +# +# The entries below should not be edited. For example, the double colons +# *must* immediately follow the ${snmptrap} reference. Adding a space +# before the double colons will break all SNMP traps. +# +# However... the traps are just programs which are run when +# particular events occur. If you want to replace a trap with +# another program, you can. Just edit the definitions below, so that +# they run a program of your choice. +# +# For example, you can leverage the "start/stop" triggers to run a +# program when the server starts, or when it stops. But that will +# prevent the start/stop SNMP traps from working, of course. +# +trigger { + # + # Events in the server core + # + server { + # the server has just started + start = "${snmptrap}::serverStart" + + # the server is about to stop + stop = "${snmptrap}::serverStop" + + # The "max_requests" condition has been reached. + # This will trigger only once per 60 seconds. + max_requests = "${snmptrap}::serverMaxRequests" + + # For events related to clients + client { + # Added a new dynamic client + add = "/path/to/file %{Packet-Src-IP-Address}" + + # There is no event for when dynamic clients expire + } + + # Events related to signals received. + signal { + # a HUP signal + hup = "${snmptrap}::signalHup" + + # a TERM signal + term = "${snmptrap}::signalTerm" + } + + + # Events related to the thread pool + thread { + # A new thread has been started + start = "${snmptrap}::threadStart" + + # an existing thread has been stopped + stop = "${snmptrap}::threadStop" + + # an existing thread is unresponsive + unresponsive = "${snmptrap}::threadUnresponsive" + + # the "max_threads" limit has been reached + max_threads = "${snmptrap}::threadMaxThreads" + } + } + + # When a home server changes state. + # These traps are edge triggered. + home_server { + # common arguments: IP, port, identifier + args = "radiusAuthServerAddress a %{proxy-request:Packet-Dst-IP-Address} radiusAuthClientServerPortNumber i %{proxy-request:Packet-Dst-Port} radiusAuthServIdent s '%{home_server:instance}'" + + # The home server has been marked "alive" + alive = "${snmptrap}::homeServerAlive ${args}" + + # The home server has been marked "zombie" + zombie = "${snmptrap}::homeServerZombie ${args}" + + # The home server has been marked "dead" + dead = "${snmptrap}::homeServerDead ${args}" + } + + # When a pool of home servers changes state. + home_server_pool { + # common arguments + args = "radiusdConfigName s %{home_server:instance}" + + # It has reverted to "normal" mode, where at least one + # home server is alive. + normal = "${snmptrap}::homeServerPoolNormal ${args}" + + # It is in "fallback" mode, with all home servers "dead" + fallback = "${snmptrap}::homeServerPoolFallback ${args}" + } + + # Triggers for specific modules. These are NOT in the module + # configuration because they are global to all instances of the + # module. You can have module-specific triggers, by placing a + # "trigger" subsection in the module configuration. + modules { + # Common arguments + args = "radiusdModuleInstance s ''" + + # The files module + files { + # Common arguments + args = "radiusdModuleName s files ${..args}" + + # The module has been HUP'd via radmin + hup = "${snmptrap}::serverModuleHup ${args}" + + # Note that "hup" can be used for every module + # which can be HUP'd via radmin + } + + # The LDAP module + # If the server does "bind as user", it will open and close + # an LDAP connection ofr every "bind as user". Be aware that + # this will likely produce a lot of triggers. + ldap { + # Common arguments + args = "radiusdModuleName s ldap ${..args}" + + # A new connection to the DB has been opened + open = "${snmptrap}::serverModuleConnectionUp ${args}" + + # A connection to the DB has been closed + close = "${snmptrap}::serverModuleConnectionDown ${args}" + + # The module has been HUP'd via radmin + hup = "${snmptrap}::serverModuleHup ${args}" + } + + # The SQL module + sql { + # Common arguments + args = "radiusdModuleName s sql ${..args}" + + # A new connection to the DB has been opened + open = "${snmptrap}::serverModuleConnectionUp ${args}" + + # A connection to the DB has been closed + close = "${snmptrap}::serverModuleConnectionDown ${args}" + + # Failed to open a new connection to the DB + fail = "${snmptrap}::serverModuleConnectionFail ${args}" + + # The module has been HUP'd via radmin + hup = "${snmptrap}::serverModuleHup ${args}" + } + + # You can also use connection pool's start/stop/open/close triggers + # for any module which uses the "pool" section, here and under + # pool.trigger in module configuration. + } +} + +# +# The complete list of triggers as generated from the source code is below. +# +# These are the ONLY traps which are generated. You CANNOT add new traps +# by defining them in one of the sections above. New traps can be created +# only by edited both the source code to the server, *and* the MIBs. +# If you are not an expert in C and SNMP, then adding new traps will be +# difficult to create. +# +# home_server.alive +# home_server.dead +# home_server.zombie +# home_server_pool.fallback +# home_server_pool.normal +# modules.*.hup +# modules.ldap.timeout +# modules.sql.close +# modules.sql.fail +# modules.sql.open +# server.client.add +# server.max_requests +# server.signal.hup +# server.signal.term +# server.start +# server.stop +# server.thread.max_threads +# server.thread.start +# server.thread.stop +# server.thread.unresponsive diff --git a/raddb/vmpsd.conf.in b/raddb/vmpsd.conf.in new file mode 100644 index 0000000..d68ef01 --- /dev/null +++ b/raddb/vmpsd.conf.in @@ -0,0 +1,405 @@ +## +## vmpsd.conf -- FreeRADIUS VMPS server configuration file. +## +## http://www.freeradius.org/ +## $Id$ +## + +# +# This configuration file is for a stand-alone VMPS server that +# does not do RADIUS. For an integrated radius + vmps server, +# edit "radiusd.conf", and add two sections to it: +# +# listen { +# type = vmps +# ... +# } +# +# vmps { +# ... +# } +# +# +# See the text below for additional documentation on those two +# sections. +# + +# The location of other config files and +# logfiles are declared in this file +# +# Also general configuration for modules can be done +# in this file, it is exported through the API to +# modules that ask for it. +# +# The configuration variables defined here are of the form ${foo} +# They are local to this file, and do not change from request to +# request. +# +# The per-request variables are of the form %{Attribute-Name}, and +# are taken from the values of the attribute in the incoming +# request. See 'doc/configuration/variables.rst' for more information. + +# +# Standard includes, etc. +# +# FIXME: to make this work: prefix, etc. See radiusd.conf... +# +prefix = @prefix@ +exec_prefix = @exec_prefix@ +sysconfdir = @sysconfdir@ +localstatedir = @localstatedir@ +sbindir = @sbindir@ +logdir = @logdir@ +raddbdir = @raddbdir@ +radacctdir = @radacctdir@ + +# +# The logging messages for the server are appended to the +# tail of this file. +# +log_file = ${logdir}/vmpsd.log + +# +# Destination for log messages. This can be one of: +# +# files - log to ${log_file}, as defined above. +# syslog - to syslog (see also the log{} section, below) +# stdout - standard output +# stderr - standard error. +# +# The command-line option "-X" over-rides this option, and forces +# logging to go to stdout. +# +log_destination = files + +# +# libdir: Where to find the rlm_* modules. +# +# This should be automatically set at configuration time. +# +# If the server builds and installs, but fails at execution time +# with an 'undefined symbol' error, then you can use the libdir +# directive to work around the problem. +# +# The cause is usually that a library has been installed on your +# system in a place where the dynamic linker CANNOT find it. When +# executing as root (or another user), your personal environment MAY +# be set up to allow the dynamic linker to find the library. When +# executing as a daemon, FreeRADIUS MAY NOT have the same +# personalized configuration. +# +# To work around the problem, find out which library contains that symbol, +# and add the directory containing that library to the end of 'libdir', +# with a colon separating the directory names. NO spaces are allowed. +# +# e.g. libdir = /usr/local/lib:/opt/package/lib +# +# You can also try setting the LD_LIBRARY_PATH environment variable +# in a script which starts the server. +# +# If that does not work, then you can re-configure and re-build the +# server to NOT use shared libraries, via: +# +# ./configure --disable-shared +# make +# make install +# +libdir = @libdir@ + +# pidfile: Where to place the PID of the RADIUS server. +# +# The server may be signalled while it's running by using this +# file. +# +# This file is written when ONLY running in daemon mode. +# +# e.g.: kill -HUP `cat /var/run/radiusd/radiusd.pid` +# +pidfile = ${run_dir}/vmpsd.pid + + +# user/group: The name (or #number) of the user/group to run vmpsd as. +# +# If these are commented out, the server will run as the user/group +# that started it. In order to change to a different user/group, you +# MUST be root ( or have root privileges ) to start the server. +# +# We STRONGLY recommend that you run the server with as few permissions +# as possible. That is, if you're not using shadow passwords, the +# user and group items below should be set to 'nobody'. +# +# On SCO (ODT 3) use "user = nouser" and "group = nogroup". +# +# NOTE that some kernels refuse to setgid(group) when the value of +# (unsigned)group is above 60000; don't use group nobody on these systems! +# +# On systems with shadow passwords, you might have to set 'group = shadow' +# for the server to be able to read the shadow password file. If you can +# authenticate users while in debug mode, but not in daemon mode, it may be +# that the debugging mode server is running as a user that can read the +# shadow info, and the user listed below can not. +# +#user = nobody +#group = nobody + +# max_request_time: The maximum time (in seconds) to handle a request. +# +# Requests which take more time than this to process may be killed, and +# a REJECT message is returned. +# +# WARNING: If you notice that requests take a long time to be handled, +# then this MAY INDICATE a bug in the server, in one of the modules +# used to handle a request, OR in your local configuration. +# +# This problem is most often seen when using an SQL database. If it takes +# more than a second or two to receive an answer from the SQL database, +# then it probably means that you haven't indexed the database. See your +# SQL server documentation for more information. +# +# Useful range of values: 5 to 120 +# +max_request_time = 30 + +# cleanup_delay: The time to wait (in seconds) before cleaning up +# a reply which was sent to the NAS. +# +# The VMPS request is normally cached internally for a short period +# of time, after the reply is sent to the NAS. The reply packet may be +# lost in the network, and the NAS will not see it. The NAS will then +# re-send the request, and the server will respond quickly with the +# cached reply. +# +# If this value is set too low, then duplicate requests from the NAS +# MAY NOT be detected, and will instead be handled as separate requests. +# +# If this value is set too high, then the server will cache too many +# requests, and some new requests may get blocked. (See 'max_requests'.) +# +# Useful range of values: 2 to 10 +# +cleanup_delay = 5 + +# listen: Make the server listen on a particular IP address, and send +# replies out from that address. This directive is most useful for +# hosts with multiple IP addresses on one interface. +# +# If you want the server to listen on additional addresses, or on +# additional ports, you can use multiple "listen" sections. +# +# Each section make the server listen for only one type of packet, +# therefore authentication and accounting have to be configured in +# different sections. +# +# The server ignore all "listen" section if you are using '-i' and '-p' +# on the command line. +# +listen { + # IP address on which to listen. + # Allowed values are: + # dotted quad (1.2.3.4) + # hostname (radius.example.com) + # wildcard (*) + ipaddr = * + + # OR, you can use an IPv6 address, but not both + # at the same time. +# ipv6addr = :: # any. ::1 == localhost + + # Port on which to listen. + # Allowed values are: + # integer port number + # 1589 is the default VMPS port. + port = 1589 + + # Type of packets to listen for. Use "vmps" for VMPSd. + # + type = vmps + + # Some systems support binding to an interface, in addition + # to the IP address. This feature isn't strictly necessary, + # but for sites with many IP addresses on one interface, + # it's useful to say "listen on all addresses for eth0". + # + # If your system does not support this feature, you will + # get an error if you try to use it. + # +# interface = eth0 + + # Per-socket lists of clients. This is a very useful feature. + # + # The name here is a reference to a section elsewhere in + # radiusd.conf, or clients.conf. Having the name as + # a reference allows multiple sockets to use the same + # set of clients. + # + # If this configuration is used, then the global list of clients + # is IGNORED for this "listen" section. Take care configuring + # this feature, to ensure you don't accidentally disable a + # client you need. + # + # See clients.conf for the configuration of "per_socket_clients". + # +# clients = per_socket_clients +} + +# hostname_lookups: Log the names of clients or just their IP addresses +# e.g., www.freeradius.org (on) or 206.47.27.232 (off). +# +# The default is 'off' because it would be overall better for the net +# if people had to knowingly turn this feature on, since enabling it +# means that each client request will result in AT LEAST one lookup +# request to the nameserver. Enabling hostname_lookups will also +# mean that your server may stop randomly for 30 seconds from time +# to time, if the DNS requests take too long. +# +# Turning hostname lookups off also means that the server won't block +# for 30 seconds, if it sees an IP address which has no name associated +# with it. +# +# allowed values: {no, yes} +# +hostname_lookups = no + +# Core dumps are a bad thing. This should only be set to 'yes' +# if you're debugging a problem with the server. +# +# allowed values: {no, yes} +# +allow_core_dumps = no + +# +# Logging section. The various "log_*" configuration items +# will eventually be moved here. +# +log { + # + # Which syslog facility to use, if ${log_destination} == "syslog" + # + # The exact values permitted here are OS-dependent. You probably + # don't want to change this. + # + syslog_facility = daemon +} + +# THREAD POOL CONFIGURATION +# +# The thread pool is a long-lived group of threads which +# take turns (round-robin) handling any incoming requests. +# +# You probably want to have a few spare threads around, +# so that high-load situations can be handled immediately. If you +# don't have any spare threads, then the request handling will +# be delayed while a new thread is created, and added to the pool. +# +# You probably don't want too many spare threads around, +# otherwise they'll be sitting there taking up resources, and +# not doing anything productive. +# +# The numbers given below should be adequate for most situations. +# +thread pool { + # Number of servers to start initially --- should be a reasonable + # ballpark figure. + start_servers = 5 + + # Limit on the total number of servers running. + # + # If this limit is ever reached, clients will be LOCKED OUT, so it + # should NOT BE SET TOO LOW. It is intended mainly as a brake to + # keep a runaway server from taking the system with it as it spirals + # down... + # + # You may find that the server is regularly reaching the + # 'max_servers' number of threads, and that increasing + # 'max_servers' doesn't seem to make much difference. + # + # If this is the case, then the problem is MOST LIKELY that + # your back-end databases are taking too long to respond, and + # are preventing the server from responding in a timely manner. + # + # The solution is NOT do keep increasing the 'max_servers' + # value, but instead to fix the underlying cause of the + # problem: slow database, or 'hostname_lookups=yes'. + # + # For more information, see 'max_request_time', above. + # + max_servers = 32 + + # Server-pool size regulation. Rather than making you guess + # how many servers you need, The server dynamically adapts to + # the load it sees, that is, it tries to maintain enough + # servers to handle the current load, plus a few spare + # servers to handle transient load spikes. + # + # It does this by periodically checking how many servers are + # waiting for a request. If there are fewer than + # min_spare_servers, it creates a new spare. If there are + # more than max_spare_servers, some of the spares die off. + # The default values are probably OK for most sites. + # + min_spare_servers = 3 + max_spare_servers = 10 + + # There may be memory leaks or resource allocation problems with + # the server. If so, set this value to 300 or so, so that the + # resources will be cleaned up periodically. + # + # This should only be necessary if there are serious bugs in the + # server which have not yet been fixed. + # + # '0' is a special value meaning 'infinity', or 'the servers never + # exit' + max_requests_per_server = 0 +} + +# MODULE CONFIGURATION +# +# The names and configuration of each module is located in this section. +# +# After the modules are defined here, they may be referred to by name, +# in other sections of this configuration file. +# +modules { + # + # Add modules here. See "radiusd.conf" for examples. + # +} + +# Instantiation +# +# This section orders the loading of the modules. Modules +# listed here will get loaded BEFORE the later sections like +# authorize, authenticate, etc. get examined. +# +# This section is not strictly needed. When a section like +# authorize refers to a module, it's automatically loaded and +# initialized. However, some modules may not be listed in any +# of the following sections, so they can be listed here. +# +# Also, listing modules here ensures that you have control over +# the order in which they are initialized. If one module needs +# something defined by another module, you can list them in order +# here, and ensure that the configuration will be OK. +# +instantiate { + # + # Add modules here. See "radiusd.conf" for examples. + # +} + +# +# And the REAL contents. This section is just like the "post-auth" +# section of radiusd.conf. In fact, it calls the "post-auth" component +# of the modules that are listed here. But it's called "vmps" for +# simplicity. +# +vmps { + # + # This is a hack for testing + # + update reply { + VMPS-Packet-Type = VMPS-Join-Response + VMPS-VLAN-Name = "foo" + VMPS-Cookie = "%{VMPS-Mac}" + } +} |