From b1333c03dc9047449f6974cfef529166aa565de3 Mon Sep 17 00:00:00 2001
From: Ajay Tripathi <ajay39in@gmail.com>
Date: Sun, 29 Nov 2020 21:29:26 +0530
Subject: [PATCH 01/34] [feature] Added openwisp_radius installation

---
 README.md                             | 127 +++++++++-----------------
 defaults/main.yml                     |  34 +++++++
 molecule/resources/converge.yml       |   5 +-
 tasks/apt.yml                         |  11 +++
 tasks/django.yml                      |   3 +
 tasks/freeradius-mysql.yml            |  38 ++++++++
 tasks/freeradius-postgresql.yml       |  43 +++++++++
 tasks/freeradius.yml                  | 122 +++++++++++++++++++++++++
 tasks/main.yml                        |   3 +
 tasks/nginx.yml                       |  11 +++
 tasks/pip.yml                         |  21 +++++
 templates/freeradius/clients.conf.j2  |   7 ++
 templates/freeradius/openwisp_site.j2 |  53 +++++++++++
 templates/freeradius/radiusd.conf.j2  |  63 +++++++++++++
 templates/freeradius/rest.j2          |  31 +++++++
 templates/freeradius/sql.j2           |  33 +++++++
 templates/freeradius/sql_counter.j2   |  41 +++++++++
 templates/openwisp2/settings.py       |  81 +++++++++++++---
 templates/openwisp2/urls.py           |   5 +
 19 files changed, 633 insertions(+), 99 deletions(-)
 create mode 100644 tasks/freeradius-mysql.yml
 create mode 100644 tasks/freeradius-postgresql.yml
 create mode 100644 tasks/freeradius.yml
 create mode 100644 templates/freeradius/clients.conf.j2
 create mode 100644 templates/freeradius/openwisp_site.j2
 create mode 100644 templates/freeradius/radiusd.conf.j2
 create mode 100644 templates/freeradius/rest.j2
 create mode 100644 templates/freeradius/sql.j2
 create mode 100644 templates/freeradius/sql_counter.j2

diff --git a/README.md b/README.md
index 9193e313..94e8964a 100644
--- a/README.md
+++ b/README.md
@@ -385,6 +385,10 @@ create an empty file named `playbook.yml` which contains the following:
   # the following line is needed only when an IP address is used as the inventory hostname
   vars:
       postfix_myhostname: localhost
+      # Enable the modules you want to use
+      openwisp2_network_topology: true
+      openwisp2_firmware_upgrader: true
+      openwisp2_radius: true
 ```
 
 **Step 6**: Run the playbook
@@ -401,88 +405,6 @@ username: admin
 password: admin
 ```
 
-Enabling the network topology module
-------------------------------------
-
-To enable the network topology module you need to set `openwisp2_network_topology` to `true` in
-your `playbook.yml` file. Here's a short summary of how to do this:
-
-**Step 1**: [Install ansible](#install-ansible)
-
-**Step 2**: [Install this role](#install-this-role)
-
-**Step 3**: [Create inventory file](#create-inventory-file)
-
-**Step 4**: Create a playbook file with following contents:
-
-```yaml
-- hosts: openwisp2
-  become: "{{ become | default('yes') }}"
-  roles:
-    - openwisp.openwisp2
-  vars:
-    openwisp2_network_topology: true
-```
-
-**Step 5**: [Run the playbook](#run-the-playbook)
-
-When the playbook is done running, if you got no errors you can login at:
-
-    https://openwisp2.mydomain.com/admin
-    username: admin
-    password: admin
-
-Enabling the firmware upgrader module
--------------------------------------
-
-**Note**: It is encouraged that you read the [quick-start guide of openwisp-firmware-upgrader](https://github.com/openwisp/openwisp-firmware-upgrader#quickstart)
-before going ahead.
-
-To enable the firmware upgrader module you need to set `openwisp2_firmware_upgrader` to `true` in
-your `playbook.yml` file. Here's a short summary of how to do this:
-
-**Step 1**: [Install ansible](#install-ansible)
-
-**Step 2**: [Install this role](#install-this-role)
-
-**Step 3**: [Create inventory file](#create-inventory-file)
-
-**Step 4**: Create a playbook file with following contents:
-
-```yaml
-- hosts: openwisp2
-  become: "{{ become | default('yes') }}"
-  roles:
-    - openwisp.openwisp2
-  vars:
-    openwisp2_firmware_upgrader: true
-```
-
-**Step 5**: [Run the playbook](#run-the-playbook)
-
-When the playbook is done running, if you got no errors you can login at:
-
-    https://openwisp2.mydomain.com/admin
-    username: admin
-    password: admin
-
-**Note**: You can configure [openwisp-firmware-upgrader specific settings](https://github.com/openwisp/openwisp-firmware-upgrader#settings)
-using `openwisp2_extra_django_settings` variable of this ansible role.
-For example if you want to enable the [APIs of openwisp-firmware-upgrader](https://github.com/openwisp/openwisp-firmware-upgrader#rest-api),
-you will update the above playbook as follows:
-
-```yaml
-- hosts: openwisp2
-  become: "{{ become | default('yes') }}"
-  roles:
-    - openwisp.openwisp2
-  vars:
-    openwisp2_firmware_upgrader: true
-    openwisp2_extra_django_settings:
-      OPENWISP_USERS_AUTH_API: true
-      OPENWISP_FIRMWARE_UPGRADER_API: true
-```
-
 Troubleshooting
 ===============
 
@@ -579,12 +501,17 @@ Below are listed all the variables you can customize (you may also want to take
     - openwisp.openwisp2
   vars:
     # openwisp-controler version
-    openwisp2_controller_version: "0.4"
+    openwisp2_controller_version: "0.8.1"
     # optional openwisp2 modules
     openwisp2_network_topology: false
-    openwisp2_network_topology_version: "0.4"
+    openwisp2_network_topology_version: "0.5.1"
     openwisp2_firmware_upgrader: false
     openwisp2_firmware_upgrader_version: "0.1"
+    openwisp2_radius_version: "0.1"
+    # Enable the modules you want to use
+    openwisp2_network_topology: true
+    openwisp2_firmware_upgrader: true
+    openwisp2_radius: true
     # you may replace the values of these variables with any URL
     # supported by pip (the python package installer)
     # use these to install forks, branches or development versions
@@ -598,6 +525,7 @@ Below are listed all the variables you can customize (you may also want to take
     openwisp2_netjsonconfig_pip: false
     openwisp2_network_topology_pip: false
     openwisp2_firmware_upgrader_pip: false
+    openwisp2_radius_pip: false
     # customize the app_path
     openwisp2_path: /opt/openwisp2
     # It is recommended that you change the value of this variable if you intend to use
@@ -761,6 +689,37 @@ Below are listed all the variables you can customize (you may also want to take
     postfix_smtpd_relay_restrictions_override: permit_mynetworks
     # allows overriding the default duration for keeping notifications
     openwisp2_notifications_delete_old_notifications: 10
+    openwisp2_users_auth_api: true
+    openwisp2_radius_sms_backend: "sendsms.backends.console.SmsBackend"
+    openwisp2_radius_sms_token_max_ip_daily: 25
+    openwisp2_radius_delete_old_users: 365
+    openwisp2_radius_cleanup_stale_radacct: 365
+    openwisp2_radius_delete_old_postauth: 365
+    openwisp2_radius_delete_old_radacct: 365
+    openwisp2_radius_allowed_hosts: ["127.0.0.1"]
+    freeradius_dir: /etc/freeradius/3.0
+    freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available"
+    freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled"
+    freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available"
+    freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled"
+    freeradius_sql:
+        driver: rlm_sql_sqlite
+        dialect: sqlite
+        host: ""
+        port: ""
+        dbname: ""
+        user: ""
+        password: ""
+    freeradius_rest:
+        url: "https://{{ inventory_hostname }}/api/v1/freeradius"
+    freeradius_clients_ip: "0.0.0.0/0"
+    freeradius_clients_key: "admin"
+    cron_delete_old_notifications: "'hour': 0, 'minute': 0"
+    cron_deactivate_expired_users: "'hour': 0, 'minute': 0"
+    cron_delete_old_users: "'hour': 0, 'minute': 10"
+    cron_cleanup_stale_radacct: "'hour': 0, 'minute': 20"
+    cron_delete_old_postauth: "'hour': 0, 'minute': 30"
+    cron_delete_old_radacct: "'hour': 0, 'minute': 40"
 ```
 
 Support
diff --git a/defaults/main.yml b/defaults/main.yml
index 49bfb38e..99648f66 100755
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -4,9 +4,11 @@ openwisp2_python: python3
 ansible_python_interpreter: /usr/bin/python3
 openwisp2_network_topology: false
 openwisp2_firmware_upgrader: false
+openwisp2_radius: false
 openwisp2_controller_version: "0.8.2"
 openwisp2_network_topology_version: "0.5.1"
 openwisp2_firmware_upgrader_version: "0.1"
+openwisp2_radius_version: "0.2.1"
 openwisp2_controller_pip: false
 openwisp2_notifications_pip: false
 openwisp2_users_pip: false
@@ -16,6 +18,7 @@ openwisp2_django_loci_pip: false
 openwisp2_netjsonconfig_pip: false
 openwisp2_network_topology_pip: false
 openwisp2_firmware_upgrader_pip: false
+openwisp2_radius_pip: false
 openwisp2_extra_python_packages: [bpython]
 openwisp2_extra_django_apps: []
 openwisp2_extra_django_settings: {}
@@ -106,3 +109,34 @@ openwisp2_celery_broker_max_tries: 10
 openwisp2_django_celery_logging: false
 openwisp2_postfix_install: true
 postfix_smtpd_relay_restrictions_override: "permit_sasl_authenticated, permit_mynetworks, check_relay_domains, reject_unauth_destination, reject"
+openwisp2_users_auth_api: true
+openwisp2_radius_sms_backend: "sendsms.backends.console.SmsBackend"
+openwisp2_radius_sms_token_max_ip_daily: 25
+openwisp2_radius_delete_old_users: 365
+openwisp2_radius_cleanup_stale_radacct: 365
+openwisp2_radius_delete_old_postauth: 365
+openwisp2_radius_delete_old_radacct: 365
+openwisp2_radius_allowed_hosts: ["127.0.0.1"]
+freeradius_dir: /etc/freeradius/3.0
+freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available"
+freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled"
+freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available"
+freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled"
+freeradius_sql:
+    driver: rlm_sql_sqlite
+    dialect: sqlite
+    host: ""
+    port: ""
+    dbname: ""
+    user: ""
+    password: ""
+freeradius_rest:
+    url: "https://{{ inventory_hostname }}/api/v1/freeradius"
+freeradius_clients_ip: "0.0.0.0/0"
+freeradius_clients_key: "admin"
+cron_delete_old_notifications: "'hour': 0, 'minute': 0"
+cron_deactivate_expired_users: "'hour': 0, 'minute': 0"
+cron_delete_old_users: "'hour': 0, 'minute': 10"
+cron_cleanup_stale_radacct: "'hour': 0, 'minute': 20"
+cron_delete_old_postauth: "'hour': 0, 'minute': 30"
+cron_delete_old_radacct: "'hour': 0, 'minute': 40"
diff --git a/molecule/resources/converge.yml b/molecule/resources/converge.yml
index e8ff2c84..fa80c09e 100644
--- a/molecule/resources/converge.yml
+++ b/molecule/resources/converge.yml
@@ -7,10 +7,13 @@
   vars:
     openwisp2_network_topology: true
     openwisp2_firmware_upgrader: true
+    openwisp2_radius: true
 
   pre_tasks:
     - name: Update apt cache
-      apt: update_cache=true cache_valid_time=600
+      apt:
+        update_cache: true
+        cache_valid_time: 600
       when: ansible_os_family == 'Debian'
 
     - name: Remove the .dockerenv file
diff --git a/tasks/apt.yml b/tasks/apt.yml
index 91755c4e..275963e2 100644
--- a/tasks/apt.yml
+++ b/tasks/apt.yml
@@ -92,6 +92,17 @@
   until: result is success
   notify: reload systemd
 
+- name: Install cairo
+  when: openwisp2_radius
+  apt:
+    name:
+      - libcairo2
+      - libpango-1.0-0
+      - libpangocairo-1.0-0
+      - libgdk-pixbuf2.0-0
+      - shared-mime-info
+  tags: [openwisp2, radius]
+
 - name: Install mod-spatialite (may fail on older linux distros)
   when: openwisp2_database.engine == "django.contrib.gis.db.backends.spatialite"
   apt: name=libsqlite3-mod-spatialite
diff --git a/tasks/django.yml b/tasks/django.yml
index b146f0a2..21aab597 100644
--- a/tasks/django.yml
+++ b/tasks/django.yml
@@ -98,6 +98,9 @@
     group: "{{ www_group }}"
     mode: 0640
 
+- name: start redis for migration
+  meta: flush_handlers
+
 - name: migrate
   notify: reload supervisor
   become: true
diff --git a/tasks/freeradius-mysql.yml b/tasks/freeradius-mysql.yml
new file mode 100644
index 00000000..367a0a18
--- /dev/null
+++ b/tasks/freeradius-mysql.yml
@@ -0,0 +1,38 @@
+---
+- name: Freeradius additional mysql system packages
+  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
+  apt:
+    name:
+      - freeradius-mysql
+      - mariadb-server
+    state: latest
+  notify: start mysql
+
+- name: Install pymysql
+  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
+  pip:
+    name: pymysql
+    state: latest
+  retries: 5
+  delay: 10
+  register: result
+  until: result is success
+  notify: start mysql
+
+- name: start mysql for migration
+  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
+  meta: flush_handlers
+
+- name: Create freeradius database
+  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
+  mysql_db:
+    login_unix_socket: /var/run/mysqld/mysqld.sock
+    db: "{{ freeradius_sql.dbname }}"
+    state: present
+
+- name: Create freeradius database user
+  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
+  mysql_user:
+    login_unix_socket: /var/run/mysqld/mysqld.sock
+    name: "{{ freeradius_sql.user }}"
+    password: "{{ freeradius_sql.password }}"
diff --git a/tasks/freeradius-postgresql.yml b/tasks/freeradius-postgresql.yml
new file mode 100644
index 00000000..868b2604
--- /dev/null
+++ b/tasks/freeradius-postgresql.yml
@@ -0,0 +1,43 @@
+---
+- name: Freeradius additional postgresql system packages
+  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
+  apt:
+    name:
+      - postgresql
+      - freeradius-postgresql
+      - libpq-dev
+    state: latest
+  notify: start postgresql
+
+- name: Install psycopg2
+  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
+  pip:
+    name: psycopg2
+    state: latest
+  retries: 5
+  delay: 10
+  register: result
+  until: result is success
+  notify: start postgresql
+
+- name: start postgresql for migration
+  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
+  meta: flush_handlers
+
+- name: Create freeradius database
+  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
+  become_user: postgres
+  become: true
+  postgresql_db:
+    name: "{{ freeradius_sql.dbname }}"
+    state: present
+
+- name: Create freeradius database user
+  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
+  become_user: postgres
+  become: true
+  postgresql_user:
+    db: "{{ freeradius_sql.dbname }}"
+    name: "{{ freeradius_sql.user }}"
+    password: "{{ freeradius_sql.password }}"
+    priv: ALL
diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
new file mode 100644
index 00000000..ae7af881
--- /dev/null
+++ b/tasks/freeradius.yml
@@ -0,0 +1,122 @@
+---
+- name: Freeradius system packages
+  when: openwisp2_radius
+  apt:
+    name:
+      - freeradius
+      - freeradius-rest
+    state: latest
+  notify: restart freeradius
+
+- import_tasks: freeradius-mysql.yml
+  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
+
+- import_tasks: freeradius-postgresql.yml
+  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
+
+- name: Radius configurations
+  when: openwisp2_radius
+  template:
+    src: freeradius/radiusd.conf.j2
+    dest: "{{ freeradius_dir }}/radiusd.conf"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
+
+- name: Clients configuration
+  when: openwisp2_radius
+  template:
+    src: freeradius/clients.conf.j2
+    dest: "{{ freeradius_dir }}/site"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
+
+- name: Remove unnecessary modules
+  when: openwisp2_radius
+  file:
+    dest: "{{ item }}"
+    state: absent
+  with_items:
+    - "{{ freeradius_mods_enabled_dir }}/eap"
+
+- name: SQL configuration
+  when: openwisp2_radius
+  template:
+    src: freeradius/sql.j2
+    dest: "{{ freeradius_mods_available_dir }}/sql"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
+
+- name: Enable SQL module
+  when: openwisp2_radius
+  file:
+    src: "{{ freeradius_mods_available_dir }}/sql"
+    dest: "{{ freeradius_mods_enabled_dir }}/sql"
+    state: link
+    mode: 0640
+    owner: freerad
+    group: freerad
+
+- name: SQL Counter module
+  when: openwisp2_radius
+  template:
+    src: freeradius/sql_counter.j2
+    dest: "{{ freeradius_mods_available_dir }}/sql_counter"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
+
+- name: Enable SQL Counter module
+  when: openwisp2_radius
+  file:
+    src: "{{ freeradius_mods_available_dir }}/sql_counter"
+    dest: "{{ freeradius_mods_enabled_dir }}/sql_counter"
+    state: link
+    mode: 0640
+    owner: freerad
+    group: freerad
+
+- name: REST configuration
+  when: openwisp2_radius
+  template:
+    src: freeradius/rest.j2
+    dest: "{{ freeradius_mods_available_dir }}/rest"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
+
+- name: Enable REST module
+  when: openwisp2_radius
+  file:
+    src: "{{ freeradius_mods_available_dir }}/rest"
+    dest: "{{ freeradius_mods_enabled_dir }}/rest"
+    state: link
+    mode: 0640
+    owner: freerad
+    group: freerad
+
+- name: Remove default site
+  when: openwisp2_radius
+  file:
+    dest: "{{ item }}"
+    state: absent
+  with_items:
+    - "{{ freeradius_sites_enabled_dir }}/default"
+    - "{{ freeradius_sites_enabled_dir }}/inner-tunnel"
+
+- name: Site configuration
+  when: openwisp2_radius
+  template:
+    src: freeradius/openwisp_site.j2
+    dest: "{{ freeradius_sites_enabled_dir }}/openwisp_site"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
diff --git a/tasks/main.yml b/tasks/main.yml
index ea6de4c7..18f34fbb 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -19,6 +19,9 @@
 - import_tasks: django.yml
   tags: [openwisp2, django]
 
+- import_tasks: freeradius.yml
+  tags: [openwisp2, freeradius]
+
 - import_tasks: supervisor.yml
   tags: [openwisp2, supervisor]
 
diff --git a/tasks/nginx.yml b/tasks/nginx.yml
index ec8a8dc1..94029bb2 100644
--- a/tasks/nginx.yml
+++ b/tasks/nginx.yml
@@ -26,6 +26,17 @@
     warn: false
   notify: restart nginx
 
+- name: Copy SSL cert to be added to trusted Cert (for freeradius)
+  copy:
+    src: "{{ openwisp2_ssl_cert }}"
+    dest: /usr/local/share/ca-certificates/openwisp-ssl-server.crt
+    remote_src: yes
+    owner: "root"
+    group: "root"
+    mode: "0644"
+  when: ansible_os_family == 'Debian'
+  notify: update-ca-certificates
+
 - name: disable default nginx configuration
   file:
     path: "/etc/nginx/sites-enabled/default"
diff --git a/tasks/pip.yml b/tasks/pip.yml
index f1892b1c..0a08b8b6 100644
--- a/tasks/pip.yml
+++ b/tasks/pip.yml
@@ -54,6 +54,13 @@
     - "{{ openwisp2_network_topology_pip }}"
   when: item is defined and item is string and openwisp2_network_topology
 
+- name: Add openwisp_radius to custom package list if set and enabled
+  set_fact:
+    openwisp2_python_packages: "{{ openwisp2_python_packages + [item] }}"
+  with_items:
+    - "{{ openwisp2_radius_pip }}"
+  when: item is defined and item is string and openwisp2_radius
+
 - name: Add firmware_upgrader to custom package list if set and enabled
   set_fact:
     openwisp2_python_packages: "{{ openwisp2_python_packages + [item] }}"
@@ -136,6 +143,20 @@
   register: result
   until: result is success
 
+- name: Install openwisp2_radius and its dependencies
+  when: openwisp2_radius
+  pip:
+    name: "openwisp-radius~={{ openwisp2_radius_version }}"
+    state: latest
+    virtualenv: "{{ virtualenv_path }}"
+    virtualenv_python: "{{ openwisp2_python }}"
+    virtualenv_site_packages: yes
+  notify: reload supervisor
+  retries: 5
+  delay: 10
+  register: result
+  until: result is success
+
 - name: Install extra python packages
   pip:
     name: "{{ openwisp2_extra_python_packages }}"
diff --git a/templates/freeradius/clients.conf.j2 b/templates/freeradius/clients.conf.j2
new file mode 100644
index 00000000..91d48f41
--- /dev/null
+++ b/templates/freeradius/clients.conf.j2
@@ -0,0 +1,7 @@
+# Freeradius Clients
+
+client radius_clients {
+	ipaddr     = {{ freeradius_clients_ip }}
+	secret     = {{ freeradius_clients_key }}
+	nas_type   = other
+}
diff --git a/templates/freeradius/openwisp_site.j2 b/templates/freeradius/openwisp_site.j2
new file mode 100644
index 00000000..0e8f50d4
--- /dev/null
+++ b/templates/freeradius/openwisp_site.j2
@@ -0,0 +1,53 @@
+server default {
+    listen {
+        type = auth
+        ipaddr = *
+        port = 0
+        limit {
+            max_connections = 16
+            lifetime = 0
+            idle_timeout = 30
+        }
+    }
+
+    listen {
+        ipaddr = *
+        port = 0
+        type = acct
+        limit {}
+    }
+
+    authorize {
+        rest
+        sql
+        dailycounter
+        noresetcounter
+        dailybandwidthcounter
+    }
+
+    authenticate {}
+
+    preacct {
+        preprocess
+        acct_unique
+        suffix
+        files
+    }
+
+    accounting {
+        rest
+    }
+
+    session {}
+
+    post-auth {
+        rest
+
+        Post-Auth-Type REJECT {
+            rest
+        }
+    }
+
+    pre-proxy {}
+    post-proxy {}
+}
diff --git a/templates/freeradius/radiusd.conf.j2 b/templates/freeradius/radiusd.conf.j2
new file mode 100644
index 00000000..8db7f031
--- /dev/null
+++ b/templates/freeradius/radiusd.conf.j2
@@ -0,0 +1,63 @@
+prefix = /usr
+exec_prefix = ${prefix}
+sysconfdir = /etc
+localstatedir = /var
+sbindir = ${exec_prefix}/sbin
+logdir = /var/log/radius
+raddbdir = ${sysconfdir}/raddb
+radacctdir = /var/log/radius/radacct
+name = radiusd
+confdir = ${raddbdir}
+modconfdir = ${confdir}/mods-config
+certdir = ${confdir}/certs
+cadir   = ${confdir}/certs
+run_dir = ${localstatedir}/run/${name}
+db_dir = ${raddbdir}
+libdir = /usr/lib/freeradius
+pidfile = ${run_dir}/${name}.pid
+correct_escapes = true
+max_request_time = 30
+cleanup_delay = 5
+max_requests = 16384
+hostname_lookups = no
+
+log {
+    destination = stdout
+    auth = yes
+    auth_badpass = yes
+    auth_goodpass = yes
+}
+
+checkrad = ${sbindir}/checkrad
+security {
+	user = root
+	group = root
+	allow_core_dumps = no
+	max_attributes = 200
+	reject_delay = 1
+	status_server = yes
+	allow_vulnerable_openssl = no
+}
+
+proxy_requests  = yes
+$INCLUDE proxy.conf
+$INCLUDE clients.conf
+thread pool {
+	start_servers = 5
+	max_servers = 32
+	min_spare_servers = 3
+	max_spare_servers = 10
+	max_requests_per_server = 0
+	auto_limit_acct = no
+}
+
+modules {
+	$INCLUDE mods-enabled/
+}
+
+instantiate {}
+
+policy {
+	$INCLUDE policy.d/
+}
+$INCLUDE sites-enabled/
diff --git a/templates/freeradius/rest.j2 b/templates/freeradius/rest.j2
new file mode 100644
index 00000000..94af8f73
--- /dev/null
+++ b/templates/freeradius/rest.j2
@@ -0,0 +1,31 @@
+rest {
+    tls = {}
+    connect_uri = "{{ freeradius_rest.url }}"
+
+    authorize {
+        uri = "${..connect_uri}/authorize/"
+        method = 'post'
+        body = 'json'
+        data = '{"username": "%{User-Name}", "password": "%{User-Password}"}'
+        tls = ${..tls}
+    }
+
+    # this section can be left empty
+    authenticate {}
+
+    post-auth {
+        uri = "${..connect_uri}/postauth/"
+        method = 'post'
+        body = 'json'
+        data = '{"username": "%{User-Name}", "password": "%{User-Password}", "reply": "%{reply:Packet-Type}", "called_station_id": "%{Called-Station-ID}", "calling_station_id": "%{Calling-Station-ID}"}'
+        tls = ${..tls}
+    }
+
+    accounting {
+        uri = "${..connect_uri}/accounting/"
+        method = 'post'
+        body = 'json'
+        data = '{"status_type": "%{Acct-Status-Type}", "session_id": "%{Acct-Session-Id}", "unique_id": "%{Acct-Unique-Session-Id}", "username": "%{User-Name}", "realm": "%{Realm}", "nas_ip_address": "%{NAS-IP-Address}", "nas_port_id": "%{NAS-Port}", "nas_port_type": "%{NAS-Port-Type}", "session_time": "%{Acct-Session-Time}", "authentication": "%{Acct-Authentic}", "input_octets": "%{Acct-Input-Octets}", "output_octets": "%{Acct-Output-Octets}", "called_station_id": "%{Called-Station-Id}", "calling_station_id": "%{Calling-Station-Id}", "terminate_cause": "%{Acct-Terminate-Cause}", "service_type": "%{Service-Type}", "framed_protocol": "%{Framed-Protocol}", "framed_ip_address": "%{Framed-IP-Address}"}'
+        tls = ${..tls}
+    }
+}
diff --git a/templates/freeradius/sql.j2 b/templates/freeradius/sql.j2
new file mode 100644
index 00000000..d949ab60
--- /dev/null
+++ b/templates/freeradius/sql.j2
@@ -0,0 +1,33 @@
+sql {
+    driver = "{{ freeradius_sql.driver }}"
+    dialect = "{{ freeradius_sql.dialect }}"
+{% if 'postgresql' == freeradius_sql.dialect %}
+	radius_db = "host={{ freeradius_sql.host }} port={{ freeradius_sql.port }} dbname={{ freeradius_sql.dbname }}  user={{ freeradius_sql.user }}  password={{ freeradius_sql.password }}"
+{% elif 'sqlite' == freeradius_sql.dialect %}
+	sqlite {
+		filename = "{{ openwisp2_database.name }}"
+	}
+{% endif %}
+	acct_table1 = "radacct"
+	acct_table2 = "radacct"
+	postauth_table = "radpostauth"
+	authcheck_table = "radcheck"
+	groupcheck_table = "radgroupcheck"
+	authreply_table = "radreply"
+	groupreply_table = "radgroupreply"
+	usergroup_table = "radusergroup"
+	delete_stale_sessions = yes
+	client_table = "nas"
+	group_attribute = "SQL-Group"
+	$INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf
+	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/templates/freeradius/sql_counter.j2 b/templates/freeradius/sql_counter.j2
new file mode 100644
index 00000000..e1900013
--- /dev/null
+++ b/templates/freeradius/sql_counter.j2
@@ -0,0 +1,41 @@
+# The dailycounter is included by default in the freeradius conf
+
+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
+}
+
+# The noresetcounter is included by default in the freeradius 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
+}
+
+# The dailybandwidthcounter is added for django-freeradius
+sqlcounter dailybandwidthcounter {
+   counter_name = Max-Daily-Session-Traffic
+   check_name = Max-Daily-Session-Traffic
+   sql_module_instance = sql
+   key = 'User-Name'
+   reset = daily
+   query = "SELECT SUM(acctinputoctets + acctoutputoctets) \
+            FROM radacct \
+            WHERE UserName='%{${key}}' \
+            AND UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%%b'"
+}
diff --git a/templates/openwisp2/settings.py b/templates/openwisp2/settings.py
index afeee256..95f72333 100644
--- a/templates/openwisp2/settings.py
+++ b/templates/openwisp2/settings.py
@@ -1,10 +1,9 @@
 import os
 import sys
+from celery.schedules import crontab
 
 TESTING = 'test' in sys.argv
 
-from datetime import timedelta
-
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
@@ -52,7 +51,6 @@
 {% endif %}
 {% if openwisp2_firmware_upgrader %}
     'openwisp_firmware_upgrader',
-    'private_storage',
 {% endif %}
     # openwisp2 admin theme
     # (must be loaded here)
@@ -68,6 +66,14 @@
     'rest_framework_gis',
     'rest_framework.authtoken',
     'django_filters',
+{% if openwisp2_radius %}
+    'dj_rest_auth',
+    'dj_rest_auth.registration',
+    'openwisp_radius',
+{% endif %}
+{% if openwisp2_firmware_upgrader or openwisp2_radius %}
+    'private_storage',
+{% endif %}
     'drf_yasg',
     'channels',
     'pipeline',
@@ -110,11 +116,27 @@
     'pipeline.middleware.MinifyHTMLMiddleware'
 ]
 
+{% if openwisp2_radius %}
+OPENWISP_RADIUS_FREERADIUS_ALLOWED_HOSTS = {{ openwisp2_radius_allowed_hosts }}
+
+# SMS
+REST_AUTH_SERIALIZERS = {
+    'PASSWORD_RESET_SERIALIZER': 'openwisp_radius.api.serializers.PasswordResetSerializer',
+}
+REST_AUTH_REGISTER_SERIALIZERS = {
+    'REGISTER_SERIALIZER': 'openwisp_radius.api.serializers.RegisterSerializer',
+}
+OPENWISP_RADIUS_SMS_TOKEN_MAX_IP_DAILY = {{ openwisp2_radius_sms_token_max_ip_daily }}
+SENDSMS_BACKEND = '{{ openwisp2_radius_sms_backend }}'
+
+OPENWISP_USERS_AUTH_API = {{ openwisp2_users_auth_api }}
+{% endif %}
+
 ROOT_URLCONF = 'openwisp2.urls'
 
 CHANNEL_LAYERS = {
     'default': {
-        "BACKEND": "channels_redis.core.RedisChannelLayer",
+        'BACKEND': 'channels_redis.core.RedisChannelLayer',
         'CONFIG': {'hosts': [('{{ openwisp2_redis_host }}', {{ openwisp2_redis_port }})]},
     },
 }
@@ -162,9 +184,39 @@
 CELERY_BEAT_SCHEDULE = {
     'delete_old_notifications': {
         'task': 'openwisp_notifications.tasks.delete_old_notifications',
-        'schedule': timedelta(days=1),
+        'schedule': crontab(**{ {{ cron_delete_old_notifications }} }),
         'args': ({{ openwisp2_notifications_delete_old_notifications }},),
     },
+    'deactivate_expired_users': {
+        'task': 'openwisp_radius.tasks.cleanup_stale_radacct',
+        'schedule': crontab(**{ {{ cron_deactivate_expired_users }} }),
+        'args': None,
+        'relative': True,
+    },
+    'delete_old_users': {
+        'task': 'openwisp_radius.tasks.delete_old_users',
+        'schedule': crontab(**{ {{ cron_delete_old_users }} }),
+        'args': [{{ openwisp2_radius_delete_old_users }}],
+        'relative': True,
+    },
+    'cleanup_stale_radacct': {
+        'task': 'openwisp_radius.tasks.cleanup_stale_radacct',
+        'schedule': crontab(**{ {{ cron_cleanup_stale_radacct }} }),
+        'args': [{{ openwisp2_radius_cleanup_stale_radacct }}],
+        'relative': True,
+    },
+    'delete_old_postauth': {
+        'task': 'openwisp_radius.tasks.delete_old_postauth',
+        'schedule': crontab(**{ {{ cron_delete_old_postauth }} }),
+        'args': [{{ openwisp2_radius_delete_old_postauth }}],
+        'relative': True,
+    },
+    'delete_old_radacct': {
+        'task': 'openwisp_radius.tasks.delete_old_radacct',
+        'schedule': crontab(**{ {{ cron_delete_old_radacct }} }),
+        'args': [{{ openwisp2_radius_delete_old_radacct }}],
+        'relative': True,
+    },
 }
 
 {% if openwisp2_celery_task_routes_defaults %}
@@ -182,17 +234,17 @@
 # FOR DJANGO REDIS
 
 CACHES = {
-    "default": {
-        "BACKEND": "django_redis.cache.RedisCache",
-        "LOCATION": "{{ openwisp2_redis_cache_url }}",
-        "OPTIONS": {
-            "CLIENT_CLASS": "django_redis.client.DefaultClient",
+    'default': {
+        'BACKEND': 'django_redis.cache.RedisCache',
+        'LOCATION': '{{ openwisp2_redis_cache_url }}',
+        'OPTIONS': {
+            'CLIENT_CLASS': 'django_redis.client.DefaultClient',
         }
     }
 }
 
-SESSION_ENGINE = "django.contrib.sessions.backends.cache"
-SESSION_CACHE_ALIAS = "default"
+SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
+SESSION_CACHE_ALIAS = 'default'
 SESSION_COOKIE_SECURE = True
 CSRF_COOKIE_SECURE = True
 
@@ -251,8 +303,9 @@
 # Static files (CSS, JavaScript, Images)
 # https://docs.djangoproject.com/en/1.9/howto/static-files/
 
-STATIC_ROOT = '%s/static' % BASE_DIR
-MEDIA_ROOT = '%s/media' % BASE_DIR
+STATIC_ROOT = os.path.join(BASE_DIR, 'static')
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
+PRIVATE_STORAGE_ROOT = os.path.join(MEDIA_ROOT, 'private')
 STATIC_URL = '/static/'
 MEDIA_URL = '/media/'
 
diff --git a/templates/openwisp2/urls.py b/templates/openwisp2/urls.py
index 505be80b..728a8e58 100644
--- a/templates/openwisp2/urls.py
+++ b/templates/openwisp2/urls.py
@@ -23,6 +23,11 @@
     {% if openwisp2_firmware_upgrader %}
     url(r'^', include('openwisp_firmware_upgrader.urls')),
     {% endif %}
+    {% if openwisp2_radius %}
+    url(r'^', include('openwisp_radius.urls')),
+    url(r'^api/v1/', include('openwisp_users.api.urls')),
+    url(r'^api/v1/', include('openwisp_utils.api.urls')),
+    {% endif %}
     {% for extra_url in openwisp2_extra_urls %}
     {{ extra_url }},
     {% endfor %}

From 4640bdc7648b62f61ddf4805e07c4f67e5c05f2b Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 12:20:08 -0500
Subject: [PATCH 02/34] Commented out mysql and postgres install.

Installing and configuring a relational database is better
done with another dedicated ansible role, we may suggest
a couple of roles in the README.

EG, for postgres I use ANXS.postgresql.
---
 tasks/freeradius.yml | 9 +++++----
 1 file changed, 5 insertions(+), 4 deletions(-)

diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index ae7af881..b17d204d 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -8,11 +8,12 @@
     state: latest
   notify: restart freeradius
 
-- import_tasks: freeradius-mysql.yml
-  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
+# - import_tasks: freeradius-mysql.yml
+#   when: openwisp2_radius and freeradius_sql.dialect == "mysql"
+#
+# - import_tasks: freeradius-postgresql.yml
+#   when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
 
-- import_tasks: freeradius-postgresql.yml
-  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
 
 - name: Radius configurations
   when: openwisp2_radius

From c36f1c8d391acda73f6acbe2fc8e1143081f5034 Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 12:20:20 -0500
Subject: [PATCH 03/34] Do not remove eap

---
 tasks/freeradius.yml | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index b17d204d..683bb683 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -35,13 +35,13 @@
     group: freerad
   notify: restart freeradius
 
-- name: Remove unnecessary modules
-  when: openwisp2_radius
-  file:
-    dest: "{{ item }}"
-    state: absent
-  with_items:
-    - "{{ freeradius_mods_enabled_dir }}/eap"
+# - name: Remove unnecessary modules
+#   when: openwisp2_radius
+#   file:
+#     dest: "{{ item }}"
+#     state: absent
+#   with_items:
+#     - "{{ freeradius_mods_enabled_dir }}/eap"
 
 - name: SQL configuration
   when: openwisp2_radius

From e989b1d6a63615e06c7682746bf9dbee69f6ae9d Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 12:20:39 -0500
Subject: [PATCH 04/34] Do not modify clients (we'll configure sql module to
 read from DB)

---
 tasks/freeradius.yml | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index 683bb683..05dea8ed 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -25,15 +25,15 @@
     group: freerad
   notify: restart freeradius
 
-- name: Clients configuration
-  when: openwisp2_radius
-  template:
-    src: freeradius/clients.conf.j2
-    dest: "{{ freeradius_dir }}/site"
-    mode: 0640
-    owner: freerad
-    group: freerad
-  notify: restart freeradius
+# - name: Clients configuration
+#   when: openwisp2_radius
+#   template:
+#     src: freeradius/clients.conf.j2
+#     dest: "{{ freeradius_dir }}/site"
+#     mode: 0640
+#     owner: freerad
+#     group: freerad
+#   notify: restart freeradius
 
 # - name: Remove unnecessary modules
 #   when: openwisp2_radius

From 761a0063b50239cbd9eefdd9305fda598ee98fca Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 12:24:01 -0500
Subject: [PATCH 05/34] Avoid modifying the main radius config file since it
 shouldn't be needed

---
 tasks/freeradius.yml | 19 +++++++++----------
 1 file changed, 9 insertions(+), 10 deletions(-)

diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index 05dea8ed..754ecae9 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -14,16 +14,15 @@
 # - import_tasks: freeradius-postgresql.yml
 #   when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
 
-
-- name: Radius configurations
-  when: openwisp2_radius
-  template:
-    src: freeradius/radiusd.conf.j2
-    dest: "{{ freeradius_dir }}/radiusd.conf"
-    mode: 0640
-    owner: freerad
-    group: freerad
-  notify: restart freeradius
+# - name: Radius configurations
+#   when: openwisp2_radius
+#   template:
+#     src: freeradius/radiusd.conf.j2
+#     dest: "{{ freeradius_dir }}/radiusd.conf"
+#     mode: 0640
+#     owner: freerad
+#     group: freerad
+#   notify: restart freeradius
 
 # - name: Clients configuration
 #   when: openwisp2_radius

From 055e443e562b1eb27d0c9ab425d344ec4e4416c7 Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 18:02:15 -0500
Subject: [PATCH 06/34] Added possibility of turning off installation of
 freeradius

Needed if freeradius is deployed on a different VM.
---
 README.md         | 4 ++++
 defaults/main.yml | 1 +
 tasks/main.yml    | 1 +
 3 files changed, 6 insertions(+)

diff --git a/README.md b/README.md
index 94e8964a..85dfc1e5 100644
--- a/README.md
+++ b/README.md
@@ -697,6 +697,10 @@ Below are listed all the variables you can customize (you may also want to take
     openwisp2_radius_delete_old_postauth: 365
     openwisp2_radius_delete_old_radacct: 365
     openwisp2_radius_allowed_hosts: ["127.0.0.1"]
+    # this role provides a default configuration of freeradius
+    # if you manage freeradius on a different machine or you need different configurations
+    # you can disable this default behavior
+    openwisp2_freeradius_install: true
     freeradius_dir: /etc/freeradius/3.0
     freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available"
     freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled"
diff --git a/defaults/main.yml b/defaults/main.yml
index 99648f66..9b005168 100755
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -117,6 +117,7 @@ openwisp2_radius_cleanup_stale_radacct: 365
 openwisp2_radius_delete_old_postauth: 365
 openwisp2_radius_delete_old_radacct: 365
 openwisp2_radius_allowed_hosts: ["127.0.0.1"]
+openwisp2_freeradius_install: true
 freeradius_dir: /etc/freeradius/3.0
 freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available"
 freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled"
diff --git a/tasks/main.yml b/tasks/main.yml
index 18f34fbb..21bcd999 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -20,6 +20,7 @@
   tags: [openwisp2, django]
 
 - import_tasks: freeradius.yml
+  when: openwisp2_freeradius_install
   tags: [openwisp2, freeradius]
 
 - import_tasks: supervisor.yml

From ad131521126366e24846e761d42256c207203c77 Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 18:36:47 -0500
Subject: [PATCH 07/34] Add way to disable radius URLs if deployd on a
 different VM

---
 README.md                   | 1 +
 defaults/main.yml           | 1 +
 templates/openwisp2/urls.py | 2 +-
 3 files changed, 3 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 85dfc1e5..c19821d9 100644
--- a/README.md
+++ b/README.md
@@ -512,6 +512,7 @@ Below are listed all the variables you can customize (you may also want to take
     openwisp2_network_topology: true
     openwisp2_firmware_upgrader: true
     openwisp2_radius: true
+    openwisp2_radius_urls: true
     # you may replace the values of these variables with any URL
     # supported by pip (the python package installer)
     # use these to install forks, branches or development versions
diff --git a/defaults/main.yml b/defaults/main.yml
index 9b005168..d0f88d01 100755
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -5,6 +5,7 @@ ansible_python_interpreter: /usr/bin/python3
 openwisp2_network_topology: false
 openwisp2_firmware_upgrader: false
 openwisp2_radius: false
+openwisp2_radius_urls: "{{ openwisp2_radius }}"
 openwisp2_controller_version: "0.8.2"
 openwisp2_network_topology_version: "0.5.1"
 openwisp2_firmware_upgrader_version: "0.1"
diff --git a/templates/openwisp2/urls.py b/templates/openwisp2/urls.py
index 728a8e58..0b165b18 100644
--- a/templates/openwisp2/urls.py
+++ b/templates/openwisp2/urls.py
@@ -23,7 +23,7 @@
     {% if openwisp2_firmware_upgrader %}
     url(r'^', include('openwisp_firmware_upgrader.urls')),
     {% endif %}
-    {% if openwisp2_radius %}
+    {% if openwisp2_radius and openwisp2_radius_urls %}
     url(r'^', include('openwisp_radius.urls')),
     url(r'^api/v1/', include('openwisp_users.api.urls')),
     url(r'^api/v1/', include('openwisp_utils.api.urls')),

From 761327e3bd3a3bdaa896894a54c580493eb48df0 Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 18:39:10 -0500
Subject: [PATCH 08/34] Simplified sql configuration

The freeradius database is the same database of OpenWISP.
Hence the freeradius DB settings can be derived from the
settings we already have.
---
 defaults/main.yml           | 17 +++++++++++------
 templates/freeradius/sql.j2 | 17 ++++++++++++-----
 2 files changed, 23 insertions(+), 11 deletions(-)

diff --git a/defaults/main.yml b/defaults/main.yml
index d0f88d01..61d2900e 100755
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -124,14 +124,19 @@ freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available"
 freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled"
 freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available"
 freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled"
-freeradius_sql:
+freeradius_db_map:
+  django.contrib.gis.db.backends.spatialite:
     driver: rlm_sql_sqlite
     dialect: sqlite
-    host: ""
-    port: ""
-    dbname: ""
-    user: ""
-    password: ""
+  django.contrib.gis.db.backends.postgis:
+    driver: rlm_sql_postgresql
+    dialect: postgresql
+  django.contrib.gis.db.backends.mysql:
+    driver: rlm_sql_mysql
+    dialect: mysql
+freeradius_sql:
+    driver: "{{ freeradius_db_map[openwisp2_database.engine].driver }}"
+    dialect: "{{ freeradius_db_map[openwisp2_database.engine].dialect }}"
 freeradius_rest:
     url: "https://{{ inventory_hostname }}/api/v1/freeradius"
 freeradius_clients_ip: "0.0.0.0/0"
diff --git a/templates/freeradius/sql.j2 b/templates/freeradius/sql.j2
index d949ab60..9eefd027 100644
--- a/templates/freeradius/sql.j2
+++ b/templates/freeradius/sql.j2
@@ -1,13 +1,18 @@
 sql {
-    driver = "{{ freeradius_sql.driver }}"
-    dialect = "{{ freeradius_sql.dialect }}"
-{% if 'postgresql' == freeradius_sql.dialect %}
-	radius_db = "host={{ freeradius_sql.host }} port={{ freeradius_sql.port }} dbname={{ freeradius_sql.dbname }}  user={{ freeradius_sql.user }}  password={{ freeradius_sql.password }}"
+  driver = "{{ freeradius_sql.driver }}"
+  dialect = "{{ freeradius_sql.dialect }}"
+{% if freeradius_sql.dialect in ['postgresql', 'mysql'] %}
+	radius_db = "{{ openwisp2_database.name }}"
+  host = "{{ openwisp2_database.host }}"
+  port = "{{ openwisp2_database.port }}"
+  user = "{{ openwisp2_database.user }}"
+  password = "{{ openwisp2_database.password }}"
 {% elif 'sqlite' == freeradius_sql.dialect %}
 	sqlite {
 		filename = "{{ openwisp2_database.name }}"
 	}
 {% endif %}
+
 	acct_table1 = "radacct"
 	acct_table2 = "radacct"
 	postauth_table = "radpostauth"
@@ -19,7 +24,9 @@ sql {
 	delete_stale_sessions = yes
 	client_table = "nas"
 	group_attribute = "SQL-Group"
-	$INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf
+
+  $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf
+
 	pool {
 		start = ${thread[pool].start_servers}
 		min = ${thread[pool].min_spare_servers}

From 31940d3c88da5d07da27972aa1716307525c99b3 Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 18:39:26 -0500
Subject: [PATCH 09/34] SQL: added read_clients = yes

---
 templates/freeradius/sql.j2 | 1 +
 1 file changed, 1 insertion(+)

diff --git a/templates/freeradius/sql.j2 b/templates/freeradius/sql.j2
index 9eefd027..91d4b7b3 100644
--- a/templates/freeradius/sql.j2
+++ b/templates/freeradius/sql.j2
@@ -23,6 +23,7 @@ sql {
 	usergroup_table = "radusergroup"
 	delete_stale_sessions = yes
 	client_table = "nas"
+	read_clients = yes
 	group_attribute = "SQL-Group"
 
   $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf

From b6f9b3d8ce055daa7f7ba9c3049780faa6baeddc Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 18:39:38 -0500
Subject: [PATCH 10/34] django-freeradius > openwisp-radius

---
 templates/freeradius/sql_counter.j2 | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/templates/freeradius/sql_counter.j2 b/templates/freeradius/sql_counter.j2
index e1900013..ab535a0e 100644
--- a/templates/freeradius/sql_counter.j2
+++ b/templates/freeradius/sql_counter.j2
@@ -27,7 +27,7 @@ sqlcounter noresetcounter {
     $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf
 }
 
-# The dailybandwidthcounter is added for django-freeradius
+# The dailybandwidthcounter is added for openwisp-radius
 sqlcounter dailybandwidthcounter {
    counter_name = Max-Daily-Session-Traffic
    check_name = Max-Daily-Session-Traffic

From 03156ab03fef35e6b6a7f3ff115530ce10b6f7da Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 18:44:44 -0500
Subject: [PATCH 11/34] Moved OPENWISP_USERS_AUTH_API out of the if
 openwisp_radius block

Since it can be used by other modules as well.
---
 templates/openwisp2/settings.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/templates/openwisp2/settings.py b/templates/openwisp2/settings.py
index 95f72333..246a591b 100644
--- a/templates/openwisp2/settings.py
+++ b/templates/openwisp2/settings.py
@@ -129,10 +129,10 @@
 OPENWISP_RADIUS_SMS_TOKEN_MAX_IP_DAILY = {{ openwisp2_radius_sms_token_max_ip_daily }}
 SENDSMS_BACKEND = '{{ openwisp2_radius_sms_backend }}'
 
-OPENWISP_USERS_AUTH_API = {{ openwisp2_users_auth_api }}
 {% endif %}
 
 ROOT_URLCONF = 'openwisp2.urls'
+OPENWISP_USERS_AUTH_API = {{ openwisp2_users_auth_api }}
 
 CHANNEL_LAYERS = {
     'default': {

From a1e154ea2c36b0849d681feadc4326ef1d4ee356 Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 18:44:51 -0500
Subject: [PATCH 12/34] Removed redundant urls

---
 templates/openwisp2/urls.py | 2 --
 1 file changed, 2 deletions(-)

diff --git a/templates/openwisp2/urls.py b/templates/openwisp2/urls.py
index 0b165b18..bf4f187e 100644
--- a/templates/openwisp2/urls.py
+++ b/templates/openwisp2/urls.py
@@ -25,8 +25,6 @@
     {% endif %}
     {% if openwisp2_radius and openwisp2_radius_urls %}
     url(r'^', include('openwisp_radius.urls')),
-    url(r'^api/v1/', include('openwisp_users.api.urls')),
-    url(r'^api/v1/', include('openwisp_utils.api.urls')),
     {% endif %}
     {% for extra_url in openwisp2_extra_urls %}
     {{ extra_url }},

From c4b956b77fc5c24c54eed6f484b1291f7317c57a Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 20:05:41 -0500
Subject: [PATCH 13/34] Fixed redundant PRIVATE_STORAGE_ROOT definition

---
 templates/openwisp2/settings.py | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/templates/openwisp2/settings.py b/templates/openwisp2/settings.py
index 246a591b..66289796 100644
--- a/templates/openwisp2/settings.py
+++ b/templates/openwisp2/settings.py
@@ -90,7 +90,7 @@
     'django_loci',
 ]
 
-{% if openwisp2_firmware_upgrader %}
+{% if openwisp2_firmware_upgrader or openwisp2_radius %}
 PRIVATE_STORAGE_ROOT = os.path.join(BASE_DIR, 'private')
 {% endif %}
 
@@ -305,7 +305,6 @@
 
 STATIC_ROOT = os.path.join(BASE_DIR, 'static')
 MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
-PRIVATE_STORAGE_ROOT = os.path.join(MEDIA_ROOT, 'private')
 STATIC_URL = '/static/'
 MEDIA_URL = '/media/'
 

From 21b272007192beafa164c066b9300fe613fa199f Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 23 Dec 2020 20:06:20 -0500
Subject: [PATCH 14/34] [chores] Install libpq-dev if using postgres

---
 tasks/apt.yml | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/tasks/apt.yml b/tasks/apt.yml
index 275963e2..bce14932 100644
--- a/tasks/apt.yml
+++ b/tasks/apt.yml
@@ -92,6 +92,15 @@
   until: result is success
   notify: reload systemd
 
+- name: Install postgresql client drivers
+  when: openwisp2_database.engine == "django.contrib.gis.db.backends.postgis"
+  apt:
+    name: libpq-dev
+  retries: 5
+  delay: 10
+  register: result
+  until: result is success
+
 - name: Install cairo
   when: openwisp2_radius
   apt:

From c65d2bd1a777fe300746085ff94fd71d18c31bc1 Mon Sep 17 00:00:00 2001
From: Ajay Tripathi <ajay39in@gmail.com>
Date: Sun, 29 Nov 2020 21:29:26 +0530
Subject: [PATCH 15/34] [feature] Added openwisp_radius installation

---
 templates/freeradius/sql.j2 |  1 -
 templates/openwisp2/urls.py | 16 ++++++++--------
 2 files changed, 8 insertions(+), 9 deletions(-)

diff --git a/templates/freeradius/sql.j2 b/templates/freeradius/sql.j2
index 91d4b7b3..362bfaa8 100644
--- a/templates/freeradius/sql.j2
+++ b/templates/freeradius/sql.j2
@@ -12,7 +12,6 @@ sql {
 		filename = "{{ openwisp2_database.name }}"
 	}
 {% endif %}
-
 	acct_table1 = "radacct"
 	acct_table2 = "radacct"
 	postauth_table = "radpostauth"
diff --git a/templates/openwisp2/urls.py b/templates/openwisp2/urls.py
index bf4f187e..e59e7d60 100644
--- a/templates/openwisp2/urls.py
+++ b/templates/openwisp2/urls.py
@@ -17,18 +17,18 @@
     {% endif %}
     url(r'^api/v1/', include('openwisp_utils.api.urls')),
     url(r'^api/v1/', include('openwisp_users.api.urls')),
-    {% if openwisp2_network_topology %}
+{% if openwisp2_network_topology %}
     url(r'^', include('openwisp_network_topology.urls')),
-    {% endif %}
-    {% if openwisp2_firmware_upgrader %}
+{% endif %}
+{% if openwisp2_firmware_upgrader %}
     url(r'^', include('openwisp_firmware_upgrader.urls')),
-    {% endif %}
-    {% if openwisp2_radius and openwisp2_radius_urls %}
+{% endif %}
+{% if openwisp2_radius and openwisp2_radius_urls %}
     url(r'^', include('openwisp_radius.urls')),
-    {% endif %}
-    {% for extra_url in openwisp2_extra_urls %}
+{% endif %}
+{% for extra_url in openwisp2_extra_urls %}
     {{ extra_url }},
-    {% endfor %}
+{% endfor %}
     url(r'^$', redirect_view, name='index'),
 ]
 

From fed2967f7e242671188eb2f5f59bb458509993e8 Mon Sep 17 00:00:00 2001
From: Ajay Tripathi <ajay39in@gmail.com>
Date: Fri, 1 Jan 2021 16:59:39 +0530
Subject: [PATCH 16/34] [radius] Removed database setup + minor

---
 README.md                            | 147 +++++++++++++++++++++++----
 defaults/main.yml                    |  21 ++--
 handlers/main.yml                    |   9 ++
 tasks/freeradius-mysql.yml           |  38 -------
 tasks/freeradius-postgresql.yml      |  43 --------
 tasks/freeradius.yml                 |  48 +--------
 tasks/nginx.yml                      |   2 +-
 tasks/pip.yml                        |   2 +-
 templates/freeradius/clients.conf.j2 |   7 --
 templates/freeradius/radiusd.conf.j2 |  63 ------------
 templates/freeradius/sql_counter.j2  |  25 +++++
 11 files changed, 180 insertions(+), 225 deletions(-)
 delete mode 100644 tasks/freeradius-mysql.yml
 delete mode 100644 tasks/freeradius-postgresql.yml
 delete mode 100644 templates/freeradius/clients.conf.j2
 delete mode 100644 templates/freeradius/radiusd.conf.j2

diff --git a/README.md b/README.md
index c19821d9..11c2d4ba 100644
--- a/README.md
+++ b/README.md
@@ -385,10 +385,6 @@ create an empty file named `playbook.yml` which contains the following:
   # the following line is needed only when an IP address is used as the inventory hostname
   vars:
       postfix_myhostname: localhost
-      # Enable the modules you want to use
-      openwisp2_network_topology: true
-      openwisp2_firmware_upgrader: true
-      openwisp2_radius: true
 ```
 
 **Step 6**: Run the playbook
@@ -405,6 +401,127 @@ username: admin
 password: admin
 ```
 
+Enabling the network topology module
+------------------------------------
+
+To enable the network topology module you need to set `openwisp2_network_topology` to `true` in
+your `playbook.yml` file. Here's a short summary of how to do this:
+
+**Step 1**: [Install ansible](#install-ansible)
+
+**Step 2**: [Install this role](#install-this-role)
+
+**Step 3**: [Create inventory file](#create-inventory-file)
+
+**Step 4**: Create a playbook file with following contents:
+
+```yaml
+- hosts: openwisp2
+  become: "{{ become | default('yes') }}"
+  roles:
+    - openwisp.openwisp2
+  vars:
+    openwisp2_network_topology: true
+```
+
+**Step 5**: [Run the playbook](#run-the-playbook)
+
+When the playbook is done running, if you got no errors you can login at:
+
+    https://openwisp2.mydomain.com/admin
+    username: admin
+    password: admin
+
+Enabling the firmware upgrader module
+-------------------------------------
+
+**Note**: It is encouraged that you read the [quick-start guide of openwisp-firmware-upgrader](https://github.com/openwisp/openwisp-firmware-upgrader#quickstart)
+before going ahead.
+
+To enable the firmware upgrader module you need to set `openwisp2_firmware_upgrader` to `true` in
+your `playbook.yml` file. Here's a short summary of how to do this:
+
+**Step 1**: [Install ansible](#install-ansible)
+
+**Step 2**: [Install this role](#install-this-role)
+
+**Step 3**: [Create inventory file](#create-inventory-file)
+
+**Step 4**: Create a playbook file with following contents:
+
+```yaml
+- hosts: openwisp2
+  become: "{{ become | default('yes') }}"
+  roles:
+    - openwisp.openwisp2
+  vars:
+    openwisp2_firmware_upgrader: true
+```
+
+**Step 5**: [Run the playbook](#run-the-playbook)
+
+When the playbook is done running, if you got no errors you can login at:
+
+    https://openwisp2.mydomain.com/admin
+    username: admin
+    password: admin
+
+**Note**: You can configure [openwisp-firmware-upgrader specific settings](https://github.com/openwisp/openwisp-firmware-upgrader#settings)
+using `openwisp2_extra_django_settings` variable of this ansible role.
+For example if you want to enable the [APIs of openwisp-firmware-upgrader](https://github.com/openwisp/openwisp-firmware-upgrader#rest-api),
+you will update the above playbook as follows:
+
+```yaml
+- hosts: openwisp2
+  become: "{{ become | default('yes') }}"
+  roles:
+    - openwisp.openwisp2
+  vars:
+    openwisp2_firmware_upgrader: true
+    openwisp2_extra_django_settings:
+      OPENWISP_USERS_AUTH_API: true
+      OPENWISP_FIRMWARE_UPGRADER_API: true
+```
+
+Enabling the radius module
+--------------------------
+
+To enable the radius module you need to set `openwisp2_radius` to `true` in
+your `playbook.yml` file. Here's a short summary of how to do this:
+
+**Step 1**: [Install ansible](#install-ansible)
+
+**Step 2**: [Install this role](#install-this-role)
+
+**Step 3**: [Create inventory file](#create-inventory-file)
+
+**Step 4**: Create a playbook file with following contents:
+
+```yaml
+- hosts: openwisp2
+  become: "{{ become | default('yes') }}"
+  roles:
+    - openwisp.openwisp2
+  vars:
+    openwisp2_radius: true
+    openwisp2_freeradius_install: true
+    # set to false when you don't want to register openwisp-radius
+    # API endpoints.
+    openwisp2_radius_urls: true
+```
+
+**Note:** `openwisp2_freeradius_install` option provides a basic configuration of freeradius for openwisp,
+it sets up the [radius user token mechanism](https://openwisp-radius.readthedocs.io/en/latest/user/api.html#radius-user-token-recommended) if you want to use another mechanism or manage your freeradius separately,
+please disable this option by setting it to `false`.
+
+**Step 5**: [Run the playbook](#run-the-playbook)
+
+When the playbook is done running, if you got no errors you can login at:
+
+    https://openwisp2.mydomain.com/admin
+    username: admin
+    password: admin
+
 Troubleshooting
 ===============
 
@@ -509,10 +626,14 @@ Below are listed all the variables you can customize (you may also want to take
     openwisp2_firmware_upgrader_version: "0.1"
     openwisp2_radius_version: "0.1"
     # Enable the modules you want to use
-    openwisp2_network_topology: true
-    openwisp2_firmware_upgrader: true
-    openwisp2_radius: true
-    openwisp2_radius_urls: true
+    openwisp2_network_topology: false
+    openwisp2_firmware_upgrader: false
+    openwisp2_radius: false
+    # when openwisp2_radius_urls is set to false, the radius module
+    # is setup but it's urls are not added, which means API and social
+    # views cannot be used, this is helpful if you have an external
+    # radius instance.
+    openwisp2_radius_urls: "{{ openwisp2_radius }}"
     # you may replace the values of these variables with any URL
     # supported by pip (the python package installer)
     # use these to install forks, branches or development versions
@@ -707,18 +828,8 @@ Below are listed all the variables you can customize (you may also want to take
     freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled"
     freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available"
     freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled"
-    freeradius_sql:
-        driver: rlm_sql_sqlite
-        dialect: sqlite
-        host: ""
-        port: ""
-        dbname: ""
-        user: ""
-        password: ""
     freeradius_rest:
         url: "https://{{ inventory_hostname }}/api/v1/freeradius"
-    freeradius_clients_ip: "0.0.0.0/0"
-    freeradius_clients_key: "admin"
     cron_delete_old_notifications: "'hour': 0, 'minute': 0"
     cron_deactivate_expired_users: "'hour': 0, 'minute': 0"
     cron_delete_old_users: "'hour': 0, 'minute': 10"
diff --git a/defaults/main.yml b/defaults/main.yml
index 61d2900e..d74a6888 100755
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -125,22 +125,21 @@ freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled"
 freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available"
 freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled"
 freeradius_db_map:
-  django.contrib.gis.db.backends.spatialite:
-    driver: rlm_sql_sqlite
-    dialect: sqlite
-  django.contrib.gis.db.backends.postgis:
-    driver: rlm_sql_postgresql
-    dialect: postgresql
-  django.contrib.gis.db.backends.mysql:
-    driver: rlm_sql_mysql
-    dialect: mysql
+    django.contrib.gis.db.backends.spatialite:
+        driver: rlm_sql_sqlite
+        dialect: sqlite
+    django.contrib.gis.db.backends.postgis:
+        driver: rlm_sql_postgresql
+        dialect: postgresql
+    django.contrib.gis.db.backends.mysql:
+        driver: rlm_sql_mysql
+        dialect: mysql
 freeradius_sql:
     driver: "{{ freeradius_db_map[openwisp2_database.engine].driver }}"
     dialect: "{{ freeradius_db_map[openwisp2_database.engine].dialect }}"
 freeradius_rest:
     url: "https://{{ inventory_hostname }}/api/v1/freeradius"
-freeradius_clients_ip: "0.0.0.0/0"
-freeradius_clients_key: "admin"
+freeradius_expire_attr_after_seconds: 86400
 cron_delete_old_notifications: "'hour': 0, 'minute': 0"
 cron_deactivate_expired_users: "'hour': 0, 'minute': 0"
 cron_delete_old_users: "'hour': 0, 'minute': 10"
diff --git a/handlers/main.yml b/handlers/main.yml
index 8a712e2d..08aa3d52 100644
--- a/handlers/main.yml
+++ b/handlers/main.yml
@@ -18,3 +18,12 @@
   service:
     name: redis
     state: started
+
+- name: update-ca-certificates
+  command: /usr/sbin/update-ca-certificates
+  when: ansible_os_family == "Debian"
+
+- name: restart freeradius
+  service:
+    name: freeradius
+    state: started
diff --git a/tasks/freeradius-mysql.yml b/tasks/freeradius-mysql.yml
deleted file mode 100644
index 367a0a18..00000000
--- a/tasks/freeradius-mysql.yml
+++ /dev/null
@@ -1,38 +0,0 @@
----
-- name: Freeradius additional mysql system packages
-  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
-  apt:
-    name:
-      - freeradius-mysql
-      - mariadb-server
-    state: latest
-  notify: start mysql
-
-- name: Install pymysql
-  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
-  pip:
-    name: pymysql
-    state: latest
-  retries: 5
-  delay: 10
-  register: result
-  until: result is success
-  notify: start mysql
-
-- name: start mysql for migration
-  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
-  meta: flush_handlers
-
-- name: Create freeradius database
-  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
-  mysql_db:
-    login_unix_socket: /var/run/mysqld/mysqld.sock
-    db: "{{ freeradius_sql.dbname }}"
-    state: present
-
-- name: Create freeradius database user
-  when: openwisp2_radius and freeradius_sql.dialect == "mysql"
-  mysql_user:
-    login_unix_socket: /var/run/mysqld/mysqld.sock
-    name: "{{ freeradius_sql.user }}"
-    password: "{{ freeradius_sql.password }}"
diff --git a/tasks/freeradius-postgresql.yml b/tasks/freeradius-postgresql.yml
deleted file mode 100644
index 868b2604..00000000
--- a/tasks/freeradius-postgresql.yml
+++ /dev/null
@@ -1,43 +0,0 @@
----
-- name: Freeradius additional postgresql system packages
-  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
-  apt:
-    name:
-      - postgresql
-      - freeradius-postgresql
-      - libpq-dev
-    state: latest
-  notify: start postgresql
-
-- name: Install psycopg2
-  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
-  pip:
-    name: psycopg2
-    state: latest
-  retries: 5
-  delay: 10
-  register: result
-  until: result is success
-  notify: start postgresql
-
-- name: start postgresql for migration
-  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
-  meta: flush_handlers
-
-- name: Create freeradius database
-  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
-  become_user: postgres
-  become: true
-  postgresql_db:
-    name: "{{ freeradius_sql.dbname }}"
-    state: present
-
-- name: Create freeradius database user
-  when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
-  become_user: postgres
-  become: true
-  postgresql_user:
-    db: "{{ freeradius_sql.dbname }}"
-    name: "{{ freeradius_sql.user }}"
-    password: "{{ freeradius_sql.password }}"
-    priv: ALL
diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index 754ecae9..f9f6c0f6 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -1,6 +1,5 @@
 ---
 - name: Freeradius system packages
-  when: openwisp2_radius
   apt:
     name:
       - freeradius
@@ -8,42 +7,7 @@
     state: latest
   notify: restart freeradius
 
-# - import_tasks: freeradius-mysql.yml
-#   when: openwisp2_radius and freeradius_sql.dialect == "mysql"
-#
-# - import_tasks: freeradius-postgresql.yml
-#   when: openwisp2_radius and freeradius_sql.dialect == "postgresql"
-
-# - name: Radius configurations
-#   when: openwisp2_radius
-#   template:
-#     src: freeradius/radiusd.conf.j2
-#     dest: "{{ freeradius_dir }}/radiusd.conf"
-#     mode: 0640
-#     owner: freerad
-#     group: freerad
-#   notify: restart freeradius
-
-# - name: Clients configuration
-#   when: openwisp2_radius
-#   template:
-#     src: freeradius/clients.conf.j2
-#     dest: "{{ freeradius_dir }}/site"
-#     mode: 0640
-#     owner: freerad
-#     group: freerad
-#   notify: restart freeradius
-
-# - name: Remove unnecessary modules
-#   when: openwisp2_radius
-#   file:
-#     dest: "{{ item }}"
-#     state: absent
-#   with_items:
-#     - "{{ freeradius_mods_enabled_dir }}/eap"
-
 - name: SQL configuration
-  when: openwisp2_radius
   template:
     src: freeradius/sql.j2
     dest: "{{ freeradius_mods_available_dir }}/sql"
@@ -53,7 +17,6 @@
   notify: restart freeradius
 
 - name: Enable SQL module
-  when: openwisp2_radius
   file:
     src: "{{ freeradius_mods_available_dir }}/sql"
     dest: "{{ freeradius_mods_enabled_dir }}/sql"
@@ -63,7 +26,6 @@
     group: freerad
 
 - name: SQL Counter module
-  when: openwisp2_radius
   template:
     src: freeradius/sql_counter.j2
     dest: "{{ freeradius_mods_available_dir }}/sql_counter"
@@ -73,7 +35,6 @@
   notify: restart freeradius
 
 - name: Enable SQL Counter module
-  when: openwisp2_radius
   file:
     src: "{{ freeradius_mods_available_dir }}/sql_counter"
     dest: "{{ freeradius_mods_enabled_dir }}/sql_counter"
@@ -82,8 +43,12 @@
     owner: freerad
     group: freerad
 
+- name: Add Attributes to freeradius dictionary
+  lineinfile:
+    path: "{{ freeradius_dir }}/dictionary"
+    line: "ATTRIBUTE      Expire-After         {{ freeradius_expire_attr_after_seconds }}    integer"
+
 - name: REST configuration
-  when: openwisp2_radius
   template:
     src: freeradius/rest.j2
     dest: "{{ freeradius_mods_available_dir }}/rest"
@@ -93,7 +58,6 @@
   notify: restart freeradius
 
 - name: Enable REST module
-  when: openwisp2_radius
   file:
     src: "{{ freeradius_mods_available_dir }}/rest"
     dest: "{{ freeradius_mods_enabled_dir }}/rest"
@@ -103,7 +67,6 @@
     group: freerad
 
 - name: Remove default site
-  when: openwisp2_radius
   file:
     dest: "{{ item }}"
     state: absent
@@ -112,7 +75,6 @@
     - "{{ freeradius_sites_enabled_dir }}/inner-tunnel"
 
 - name: Site configuration
-  when: openwisp2_radius
   template:
     src: freeradius/openwisp_site.j2
     dest: "{{ freeradius_sites_enabled_dir }}/openwisp_site"
diff --git a/tasks/nginx.yml b/tasks/nginx.yml
index 94029bb2..d0418752 100644
--- a/tasks/nginx.yml
+++ b/tasks/nginx.yml
@@ -30,7 +30,7 @@
   copy:
     src: "{{ openwisp2_ssl_cert }}"
     dest: /usr/local/share/ca-certificates/openwisp-ssl-server.crt
-    remote_src: yes
+    remote_src: true
     owner: "root"
     group: "root"
     mode: "0644"
diff --git a/tasks/pip.yml b/tasks/pip.yml
index 0a08b8b6..a23c6bbc 100644
--- a/tasks/pip.yml
+++ b/tasks/pip.yml
@@ -150,7 +150,7 @@
     state: latest
     virtualenv: "{{ virtualenv_path }}"
     virtualenv_python: "{{ openwisp2_python }}"
-    virtualenv_site_packages: yes
+    virtualenv_site_packages: true
   notify: reload supervisor
   retries: 5
   delay: 10
diff --git a/templates/freeradius/clients.conf.j2 b/templates/freeradius/clients.conf.j2
deleted file mode 100644
index 91d48f41..00000000
--- a/templates/freeradius/clients.conf.j2
+++ /dev/null
@@ -1,7 +0,0 @@
-# Freeradius Clients
-
-client radius_clients {
-	ipaddr     = {{ freeradius_clients_ip }}
-	secret     = {{ freeradius_clients_key }}
-	nas_type   = other
-}
diff --git a/templates/freeradius/radiusd.conf.j2 b/templates/freeradius/radiusd.conf.j2
deleted file mode 100644
index 8db7f031..00000000
--- a/templates/freeradius/radiusd.conf.j2
+++ /dev/null
@@ -1,63 +0,0 @@
-prefix = /usr
-exec_prefix = ${prefix}
-sysconfdir = /etc
-localstatedir = /var
-sbindir = ${exec_prefix}/sbin
-logdir = /var/log/radius
-raddbdir = ${sysconfdir}/raddb
-radacctdir = /var/log/radius/radacct
-name = radiusd
-confdir = ${raddbdir}
-modconfdir = ${confdir}/mods-config
-certdir = ${confdir}/certs
-cadir   = ${confdir}/certs
-run_dir = ${localstatedir}/run/${name}
-db_dir = ${raddbdir}
-libdir = /usr/lib/freeradius
-pidfile = ${run_dir}/${name}.pid
-correct_escapes = true
-max_request_time = 30
-cleanup_delay = 5
-max_requests = 16384
-hostname_lookups = no
-
-log {
-    destination = stdout
-    auth = yes
-    auth_badpass = yes
-    auth_goodpass = yes
-}
-
-checkrad = ${sbindir}/checkrad
-security {
-	user = root
-	group = root
-	allow_core_dumps = no
-	max_attributes = 200
-	reject_delay = 1
-	status_server = yes
-	allow_vulnerable_openssl = no
-}
-
-proxy_requests  = yes
-$INCLUDE proxy.conf
-$INCLUDE clients.conf
-thread pool {
-	start_servers = 5
-	max_servers = 32
-	min_spare_servers = 3
-	max_spare_servers = 10
-	max_requests_per_server = 0
-	auto_limit_acct = no
-}
-
-modules {
-	$INCLUDE mods-enabled/
-}
-
-instantiate {}
-
-policy {
-	$INCLUDE policy.d/
-}
-$INCLUDE sites-enabled/
diff --git a/templates/freeradius/sql_counter.j2 b/templates/freeradius/sql_counter.j2
index ab535a0e..b9e53c8c 100644
--- a/templates/freeradius/sql_counter.j2
+++ b/templates/freeradius/sql_counter.j2
@@ -39,3 +39,28 @@ sqlcounter dailybandwidthcounter {
             WHERE UserName='%{${key}}' \
             AND UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%%b'"
 }
+
+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
+
+  $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf
+}
+
+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
+}

From 6136f07b4bcbdd9918cf070f0af30ba5e73c436d Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Wed, 6 Jan 2021 19:10:01 -0500
Subject: [PATCH 17/34] [chores] Import freeradius.yml only when
 openwisp2_radius is true

---
 tasks/main.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/tasks/main.yml b/tasks/main.yml
index 21bcd999..cad6ec49 100644
--- a/tasks/main.yml
+++ b/tasks/main.yml
@@ -20,7 +20,7 @@
   tags: [openwisp2, django]
 
 - import_tasks: freeradius.yml
-  when: openwisp2_freeradius_install
+  when: openwisp2_radius and openwisp2_freeradius_install
   tags: [openwisp2, freeradius]
 
 - import_tasks: supervisor.yml

From 6ea3bbe6940d8bfc3c3e97246557218b1016eba4 Mon Sep 17 00:00:00 2001
From: Federico Capoano <federico.capoano@gmail.com>
Date: Thu, 7 Jan 2021 19:55:38 -0500
Subject: [PATCH 18/34] [fix] Schedule openwisp-radius tasks only if radius
 enabled

---
 templates/openwisp2/settings.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/templates/openwisp2/settings.py b/templates/openwisp2/settings.py
index 66289796..80eee547 100644
--- a/templates/openwisp2/settings.py
+++ b/templates/openwisp2/settings.py
@@ -187,6 +187,7 @@
         'schedule': crontab(**{ {{ cron_delete_old_notifications }} }),
         'args': ({{ openwisp2_notifications_delete_old_notifications }},),
     },
+    {% if openwisp2_radius %}
     'deactivate_expired_users': {
         'task': 'openwisp_radius.tasks.cleanup_stale_radacct',
         'schedule': crontab(**{ {{ cron_deactivate_expired_users }} }),
@@ -217,6 +218,7 @@
         'args': [{{ openwisp2_radius_delete_old_radacct }}],
         'relative': True,
     },
+    {% endif %}
 }
 
 {% if openwisp2_celery_task_routes_defaults %}

From 797e8682aacef575d478246a33df5a949339d1db Mon Sep 17 00:00:00 2001
From: Ajay Tripathi <ajay39in@gmail.com>
Date: Fri, 1 Jan 2021 16:59:39 +0530
Subject: [PATCH 19/34] [radius] Removed database setup + minor

---
 README.md                       |  3 +++
 tasks/freeradius.yml            | 21 +++++++++++++++++++++
 templates/openwisp2/settings.py |  3 +--
 3 files changed, 25 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 11c2d4ba..51003f1a 100644
--- a/README.md
+++ b/README.md
@@ -823,6 +823,9 @@ Below are listed all the variables you can customize (you may also want to take
     # if you manage freeradius on a different machine or you need different configurations
     # you can disable this default behavior
     openwisp2_freeradius_install: true
+    # Set an account to expire T seconds after first login.
+    # This variable sets the value of T.
+    freeradius_expire_attr_after_seconds: 86400
     freeradius_dir: /etc/freeradius/3.0
     freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available"
     freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled"
diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index f9f6c0f6..29e67854 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -7,6 +7,20 @@
     state: latest
   notify: restart freeradius
 
+- name: Freeradius postgres packages
+  when: openwisp2_database.engine == "django.contrib.gis.db.backends.postgis"
+  apt:
+    name: freeradius-postgresql
+    state: latest
+  notify: restart freeradius
+
+- name: Freeradius mysql packages
+  when: openwisp2_database.engine == "django.contrib.gis.db.backends.mysql"
+  apt:
+    name: freeradius-mysql
+    state: latest
+  notify: restart freeradius
+
 - name: SQL configuration
   template:
     src: freeradius/sql.j2
@@ -25,6 +39,13 @@
     owner: freerad
     group: freerad
 
+- name: adding user 'freerad' to www-data group for database access
+  when: openwisp2_database.engine == "django.contrib.gis.db.backends.spatialite"
+  user:
+    name: freerad
+    groups: www-data
+    append: yes
+
 - name: SQL Counter module
   template:
     src: freeradius/sql_counter.j2
diff --git a/templates/openwisp2/settings.py b/templates/openwisp2/settings.py
index 80eee547..3568e096 100644
--- a/templates/openwisp2/settings.py
+++ b/templates/openwisp2/settings.py
@@ -367,8 +367,7 @@
             'formatter': 'verbose',
             'filename': os.path.join(BASE_DIR, 'log/openwisp2.log'),
             'maxBytes': 15728640,
-            'backupCount': 3,
-            'formatter': 'verbose'
+            'backupCount': 3
         },
         'null': {
             'level': 'DEBUG',

From 69fad3e0312fa05f6779c57cbaadaabfe65eb61b Mon Sep 17 00:00:00 2001
From: Ajay Tripathi <ajay39in@gmail.com>
Date: Sun, 10 Jan 2021 02:00:37 +0530
Subject: [PATCH 20/34] [freeradius] Fix eap error

---
 README.md                             |  4 ++--
 handlers/main.yml                     |  1 -
 molecule/resources/converge.yml       |  1 -
 tasks/freeradius.yml                  |  2 +-
 tasks/nginx.yml                       |  1 -
 templates/freeradius/openwisp_site.j2 | 18 +++++++++++++++++-
 templates/freeradius/sql.j2           |  2 +-
 templates/openwisp2/urls.py           |  4 ++--
 8 files changed, 23 insertions(+), 10 deletions(-)

diff --git a/README.md b/README.md
index 51003f1a..0bd796c4 100644
--- a/README.md
+++ b/README.md
@@ -618,13 +618,13 @@ Below are listed all the variables you can customize (you may also want to take
     - openwisp.openwisp2
   vars:
     # openwisp-controler version
-    openwisp2_controller_version: "0.8.1"
+    openwisp2_controller_version: "0.8.2"
     # optional openwisp2 modules
     openwisp2_network_topology: false
     openwisp2_network_topology_version: "0.5.1"
     openwisp2_firmware_upgrader: false
     openwisp2_firmware_upgrader_version: "0.1"
-    openwisp2_radius_version: "0.1"
+    openwisp2_radius_version: "0.2.1"
     # Enable the modules you want to use
     openwisp2_network_topology: false
     openwisp2_firmware_upgrader: false
diff --git a/handlers/main.yml b/handlers/main.yml
index 08aa3d52..978a8c18 100644
--- a/handlers/main.yml
+++ b/handlers/main.yml
@@ -21,7 +21,6 @@
 
 - name: update-ca-certificates
   command: /usr/sbin/update-ca-certificates
-  when: ansible_os_family == "Debian"
 
 - name: restart freeradius
   service:
diff --git a/molecule/resources/converge.yml b/molecule/resources/converge.yml
index fa80c09e..c95e1caa 100644
--- a/molecule/resources/converge.yml
+++ b/molecule/resources/converge.yml
@@ -14,7 +14,6 @@
       apt:
         update_cache: true
         cache_valid_time: 600
-      when: ansible_os_family == 'Debian'
 
     - name: Remove the .dockerenv file
       file:
diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index 29e67854..99d4d3b8 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -44,7 +44,7 @@
   user:
     name: freerad
     groups: www-data
-    append: yes
+    append: true
 
 - name: SQL Counter module
   template:
diff --git a/tasks/nginx.yml b/tasks/nginx.yml
index d0418752..6c278f74 100644
--- a/tasks/nginx.yml
+++ b/tasks/nginx.yml
@@ -34,7 +34,6 @@
     owner: "root"
     group: "root"
     mode: "0644"
-  when: ansible_os_family == 'Debian'
   notify: update-ca-certificates
 
 - name: disable default nginx configuration
diff --git a/templates/freeradius/openwisp_site.j2 b/templates/freeradius/openwisp_site.j2
index 0e8f50d4..dfc75cf8 100644
--- a/templates/freeradius/openwisp_site.j2
+++ b/templates/freeradius/openwisp_site.j2
@@ -25,7 +25,23 @@ server default {
         dailybandwidthcounter
     }
 
-    authenticate {}
+    authenticate {
+        Auth-Type PAP {
+            pap
+        }
+
+        Auth-Type CHAP {
+            chap
+        }
+
+        Auth-Type MS-CHAP {
+            mschap
+        }
+
+        Auth-Type EAP {
+            eap
+        }
+    }
 
     preacct {
         preprocess
diff --git a/templates/freeradius/sql.j2 b/templates/freeradius/sql.j2
index 362bfaa8..a13f59ca 100644
--- a/templates/freeradius/sql.j2
+++ b/templates/freeradius/sql.j2
@@ -2,7 +2,7 @@ sql {
   driver = "{{ freeradius_sql.driver }}"
   dialect = "{{ freeradius_sql.dialect }}"
 {% if freeradius_sql.dialect in ['postgresql', 'mysql'] %}
-	radius_db = "{{ openwisp2_database.name }}"
+  radius_db = "{{ openwisp2_database.name }}"
   host = "{{ openwisp2_database.host }}"
   port = "{{ openwisp2_database.port }}"
   user = "{{ openwisp2_database.user }}"
diff --git a/templates/openwisp2/urls.py b/templates/openwisp2/urls.py
index e59e7d60..1d5b6080 100644
--- a/templates/openwisp2/urls.py
+++ b/templates/openwisp2/urls.py
@@ -12,9 +12,9 @@
 
 urlpatterns = [
     url(r'^admin/', admin.site.urls),
-    {% if openwisp2_controller_urls %}
+{% if openwisp2_controller_urls %}
     url(r'', include('openwisp_controller.urls')),
-    {% endif %}
+{% endif %}
     url(r'^api/v1/', include('openwisp_utils.api.urls')),
     url(r'^api/v1/', include('openwisp_users.api.urls')),
 {% if openwisp2_network_topology %}

From 37703ebd06eeca2d37b99cb933261551633a3174 Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Wed, 20 Jan 2021 18:02:50 -0500
Subject: [PATCH 21/34] [fix] Fixed SQL parameters: host > server, user > login

---
 templates/freeradius/sql.j2 | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/templates/freeradius/sql.j2 b/templates/freeradius/sql.j2
index a13f59ca..7d686d56 100644
--- a/templates/freeradius/sql.j2
+++ b/templates/freeradius/sql.j2
@@ -3,10 +3,10 @@ sql {
   dialect = "{{ freeradius_sql.dialect }}"
 {% if freeradius_sql.dialect in ['postgresql', 'mysql'] %}
   radius_db = "{{ openwisp2_database.name }}"
-  host = "{{ openwisp2_database.host }}"
   port = "{{ openwisp2_database.port }}"
-  user = "{{ openwisp2_database.user }}"
   password = "{{ openwisp2_database.password }}"
+    server = "{{ openwisp2_database.host }}"
+    login = "{{ openwisp2_database.user }}"
 {% elif 'sqlite' == freeradius_sql.dialect %}
 	sqlite {
 		filename = "{{ openwisp2_database.name }}"

From 1332b1d98790c995e6b7b1260388958535abeb3d Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Wed, 20 Jan 2021 18:03:10 -0500
Subject: [PATCH 22/34] [chores] Added missing modules in authorize section

---
 templates/freeradius/openwisp_site.j2 | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/templates/freeradius/openwisp_site.j2 b/templates/freeradius/openwisp_site.j2
index dfc75cf8..29fb8503 100644
--- a/templates/freeradius/openwisp_site.j2
+++ b/templates/freeradius/openwisp_site.j2
@@ -18,11 +18,15 @@ server default {
     }
 
     authorize {
+        filter_username
         rest
         sql
         dailycounter
         noresetcounter
         dailybandwidthcounter
+        noresetcounter
+        expiration
+        logintime
     }
 
     authenticate {

From c98e6c10ab2c0c2738fa3c9889014cbb2fc920e7 Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Wed, 20 Jan 2021 18:03:34 -0500
Subject: [PATCH 23/34] [chores] Fixed indentation of sql module

---
 templates/freeradius/sql.j2 | 64 +++++++++++++++++++------------------
 1 file changed, 33 insertions(+), 31 deletions(-)

diff --git a/templates/freeradius/sql.j2 b/templates/freeradius/sql.j2
index 7d686d56..54d5d834 100644
--- a/templates/freeradius/sql.j2
+++ b/templates/freeradius/sql.j2
@@ -1,40 +1,42 @@
 sql {
-  driver = "{{ freeradius_sql.driver }}"
-  dialect = "{{ freeradius_sql.dialect }}"
+    driver = "{{ freeradius_sql.driver }}"
+    dialect = "{{ freeradius_sql.dialect }}"
 {% if freeradius_sql.dialect in ['postgresql', 'mysql'] %}
-  radius_db = "{{ openwisp2_database.name }}"
-  port = "{{ openwisp2_database.port }}"
-  password = "{{ openwisp2_database.password }}"
+    radius_db = "{{ openwisp2_database.name }}"
     server = "{{ openwisp2_database.host }}"
+    port = "{{ openwisp2_database.port }}"
     login = "{{ openwisp2_database.user }}"
+    password = "{{ openwisp2_database.password }}"
+
 {% elif 'sqlite' == freeradius_sql.dialect %}
-	sqlite {
-		filename = "{{ openwisp2_database.name }}"
-	}
+    sqlite {
+        filename = "{{ openwisp2_database.name }}"
+    }
+
 {% endif %}
-	acct_table1 = "radacct"
-	acct_table2 = "radacct"
-	postauth_table = "radpostauth"
-	authcheck_table = "radcheck"
-	groupcheck_table = "radgroupcheck"
-	authreply_table = "radreply"
-	groupreply_table = "radgroupreply"
-	usergroup_table = "radusergroup"
-	delete_stale_sessions = yes
-	client_table = "nas"
-	read_clients = yes
-	group_attribute = "SQL-Group"
+    acct_table1 = "radacct"
+    acct_table2 = "radacct"
+    postauth_table = "radpostauth"
+    authcheck_table = "radcheck"
+    groupcheck_table = "radgroupcheck"
+    authreply_table = "radreply"
+    groupreply_table = "radgroupreply"
+    usergroup_table = "radusergroup"
+    delete_stale_sessions = yes
+    client_table = "nas"
+    read_clients = yes
+    group_attribute = "SQL-Group"
 
-  $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf
+    $INCLUDE ${modconfdir}/${.:name}/main/${dialect}/queries.conf
 
-	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
-	}
+    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
+    }
 }

From b63c3379911eab803f8a5c8fb47dda470740cc83 Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Wed, 20 Jan 2021 18:04:08 -0500
Subject: [PATCH 24/34] [fix] Fixed sql counter

---
 defaults/main.yml                             |  1 +
 tasks/freeradius.yml                          | 39 +++++++++--
 .../daily-bandwidth-counter/mysql.j2          |  5 ++
 .../daily-bandwidth-counter/postgresql.j2     |  5 ++
 .../daily-bandwidth-counter/sqlite.j2         |  5 ++
 templates/freeradius/sql_counter.j2           | 66 -------------------
 templates/freeradius/sqlcounter.j2            | 65 ++++++++++++++++++
 7 files changed, 115 insertions(+), 71 deletions(-)
 create mode 100644 templates/freeradius/daily-bandwidth-counter/mysql.j2
 create mode 100644 templates/freeradius/daily-bandwidth-counter/postgresql.j2
 create mode 100644 templates/freeradius/daily-bandwidth-counter/sqlite.j2
 delete mode 100644 templates/freeradius/sql_counter.j2
 create mode 100644 templates/freeradius/sqlcounter.j2

diff --git a/defaults/main.yml b/defaults/main.yml
index d74a6888..faf81090 100755
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -122,6 +122,7 @@ openwisp2_freeradius_install: true
 freeradius_dir: /etc/freeradius/3.0
 freeradius_mods_available_dir: "{{ freeradius_dir }}/mods-available"
 freeradius_mods_enabled_dir: "{{ freeradius_dir }}/mods-enabled"
+freeradius_mods_config_dir: "{{ freeradius_dir }}/mods-config"
 freeradius_sites_available_dir: "{{ freeradius_dir }}/sites-available"
 freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled"
 freeradius_db_map:
diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index 99d4d3b8..9c4c6e64 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -33,7 +33,9 @@
 - name: Enable SQL module
   file:
     src: "{{ freeradius_mods_available_dir }}/sql"
-    dest: "{{ freeradius_mods_enabled_dir }}/sql"
+    # the "1_" prefix is put on purpose to force loading the sql module
+    # before the sqlcounter module to avoid an annoying startup error
+    dest: "{{ freeradius_mods_enabled_dir }}/1_sql"
     state: link
     mode: 0640
     owner: freerad
@@ -48,8 +50,35 @@
 
 - name: SQL Counter module
   template:
-    src: freeradius/sql_counter.j2
-    dest: "{{ freeradius_mods_available_dir }}/sql_counter"
+    src: freeradius/sqlcounter.j2
+    dest: "{{ freeradius_mods_available_dir }}/sqlcounter"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
+
+- name: Upload Daily Bandwidth Counter config file (sqlite)
+  template:
+    src: freeradius/daily-bandwidth-counter/sqlite.j2
+    dest: "{{ freeradius_mods_config_dir }}/sql/counter/sqlite/dailybandwidthcounter.conf"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
+
+- name: Upload Daily Bandwidth Counter config file (mysql)
+  template:
+    src: freeradius/daily-bandwidth-counter/mysql.j2
+    dest: "{{ freeradius_mods_config_dir }}/sql/counter/mysql/dailybandwidthcounter.conf"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
+
+- name: Upload Daily Bandwidth Counter config file (postgresql)
+  template:
+    src: freeradius/daily-bandwidth-counter/postgresql.j2
+    dest: "{{ freeradius_mods_config_dir }}/sql/counter/postgresql/dailybandwidthcounter.conf"
     mode: 0640
     owner: freerad
     group: freerad
@@ -57,8 +86,8 @@
 
 - name: Enable SQL Counter module
   file:
-    src: "{{ freeradius_mods_available_dir }}/sql_counter"
-    dest: "{{ freeradius_mods_enabled_dir }}/sql_counter"
+    src: "{{ freeradius_mods_available_dir }}/sqlcounter"
+    dest: "{{ freeradius_mods_enabled_dir }}/sqlcounter"
     state: link
     mode: 0640
     owner: freerad
diff --git a/templates/freeradius/daily-bandwidth-counter/mysql.j2 b/templates/freeradius/daily-bandwidth-counter/mysql.j2
new file mode 100644
index 00000000..e3d8b170
--- /dev/null
+++ b/templates/freeradius/daily-bandwidth-counter/mysql.j2
@@ -0,0 +1,5 @@
+query = "\
+        SELECT SUM(acctinputoctets + acctoutputoctets) \
+        FROM radacct \
+        WHERE UserName='%{${key}}' \
+        AND UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%%b'"
diff --git a/templates/freeradius/daily-bandwidth-counter/postgresql.j2 b/templates/freeradius/daily-bandwidth-counter/postgresql.j2
new file mode 100644
index 00000000..b58737e7
--- /dev/null
+++ b/templates/freeradius/daily-bandwidth-counter/postgresql.j2
@@ -0,0 +1,5 @@
+query = "\
+        SELECT sum(AcctOutputOctets) + sum(AcctInputOctets) \
+        FROM radacct WHERE \
+        UserName = '%{${key}}' AND \
+        AcctStartTime::ABSTIME::INT4 + AcctSessionTime > '%%b'"
diff --git a/templates/freeradius/daily-bandwidth-counter/sqlite.j2 b/templates/freeradius/daily-bandwidth-counter/sqlite.j2
new file mode 100644
index 00000000..79d900fd
--- /dev/null
+++ b/templates/freeradius/daily-bandwidth-counter/sqlite.j2
@@ -0,0 +1,5 @@
+query = "\
+        SELECT SUM(acctinputoctets + acctoutputoctets) \
+        FROM radacct \
+        WHERE UserName='%{${key}}' \
+        AND strftime('%%s', acctstarttime) + acctsessiontime > '%%b'"
diff --git a/templates/freeradius/sql_counter.j2 b/templates/freeradius/sql_counter.j2
deleted file mode 100644
index b9e53c8c..00000000
--- a/templates/freeradius/sql_counter.j2
+++ /dev/null
@@ -1,66 +0,0 @@
-# The dailycounter is included by default in the freeradius conf
-
-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
-}
-
-# The noresetcounter is included by default in the freeradius 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
-}
-
-# The dailybandwidthcounter is added for openwisp-radius
-sqlcounter dailybandwidthcounter {
-   counter_name = Max-Daily-Session-Traffic
-   check_name = Max-Daily-Session-Traffic
-   sql_module_instance = sql
-   key = 'User-Name'
-   reset = daily
-   query = "SELECT SUM(acctinputoctets + acctoutputoctets) \
-            FROM radacct \
-            WHERE UserName='%{${key}}' \
-            AND UNIX_TIMESTAMP(acctstarttime) + acctsessiontime > '%%b'"
-}
-
-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
-
-  $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf
-}
-
-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/templates/freeradius/sqlcounter.j2 b/templates/freeradius/sqlcounter.j2
new file mode 100644
index 00000000..963b1de6
--- /dev/null
+++ b/templates/freeradius/sqlcounter.j2
@@ -0,0 +1,65 @@
+# The dailycounter is included by default in the freeradius conf
+
+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
+}
+
+# The dailybandwidthcounter is added by OpenWISP
+sqlcounter dailybandwidthcounter {
+    sql_module_instance = sql
+    dialect = ${modules.sql.dialect}
+
+    counter_name = Max-Daily-Session-Traffic
+    check_name = Max-Daily-Session-Traffic
+    key = User-Name
+    reset = daily
+
+    $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
+}
+
+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
+
+    $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf
+}
+
+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
+}

From 325ac68fc6a0c0615e6515abc4dffd7de090a9ba Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Thu, 21 Jan 2021 17:53:51 -0500
Subject: [PATCH 25/34] [fix] Added patched postgresql counters

---
 tasks/freeradius.yml                          | 20 +++++++++++
 .../daily-bandwidth-counter/postgresql.j2     |  2 +-
 .../freeradius/daily-counter/postgresql.j2    | 34 +++++++++++++++++++
 .../freeradius/monthly-counter/postgresql.j2  | 31 +++++++++++++++++
 4 files changed, 86 insertions(+), 1 deletion(-)
 create mode 100644 templates/freeradius/daily-counter/postgresql.j2
 create mode 100644 templates/freeradius/monthly-counter/postgresql.j2

diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index 9c4c6e64..1a3de948 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -84,6 +84,26 @@
     group: freerad
   notify: restart freeradius
 
+- name: Fix dailycounter postgresql query
+  when: freeradius_sql.dialect == "postgresql"
+  template:
+    src: freeradius/daily-counter/postgresql.j2
+    dest: "{{ freeradius_mods_config_dir }}/sql/counter/postgresql/dailycounter.conf"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
+
+- name: Fix monthlycounter postgresql query
+  when: freeradius_sql.dialect == "postgresql"
+  template:
+    src: freeradius/monthly-counter/postgresql.j2
+    dest: "{{ freeradius_mods_config_dir }}/sql/counter/postgresql/monthlycounter.conf"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
+
 - name: Enable SQL Counter module
   file:
     src: "{{ freeradius_mods_available_dir }}/sqlcounter"
diff --git a/templates/freeradius/daily-bandwidth-counter/postgresql.j2 b/templates/freeradius/daily-bandwidth-counter/postgresql.j2
index b58737e7..87ffb6c1 100644
--- a/templates/freeradius/daily-bandwidth-counter/postgresql.j2
+++ b/templates/freeradius/daily-bandwidth-counter/postgresql.j2
@@ -2,4 +2,4 @@ query = "\
         SELECT sum(AcctOutputOctets) + sum(AcctInputOctets) \
         FROM radacct WHERE \
         UserName = '%{${key}}' AND \
-        AcctStartTime::ABSTIME::INT4 + AcctSessionTime > '%%b'"
+        EXTRACT(epoch FROM AcctStartTime) + AcctSessionTime > '%%b'"
diff --git a/templates/freeradius/daily-counter/postgresql.j2 b/templates/freeradius/daily-counter/postgresql.j2
new file mode 100644
index 00000000..a26785db
--- /dev/null
+++ b/templates/freeradius/daily-counter/postgresql.j2
@@ -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/templates/freeradius/monthly-counter/postgresql.j2 b/templates/freeradius/monthly-counter/postgresql.j2
new file mode 100644
index 00000000..cdaf83a5
--- /dev/null
+++ b/templates/freeradius/monthly-counter/postgresql.j2
@@ -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'"

From 7db00abf738347db0da67ecb98aa37f964e4b340 Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Thu, 21 Jan 2021 17:54:13 -0500
Subject: [PATCH 26/34] [chores] Minor improvements

---
 tasks/freeradius.yml            | 8 +++++---
 templates/freeradius/sql.j2     | 2 +-
 templates/openwisp2/settings.py | 3 +--
 3 files changed, 7 insertions(+), 6 deletions(-)

diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index 1a3de948..53b99120 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -8,14 +8,14 @@
   notify: restart freeradius
 
 - name: Freeradius postgres packages
-  when: openwisp2_database.engine == "django.contrib.gis.db.backends.postgis"
+  when: freeradius_sql.dialect == "postgresql"
   apt:
     name: freeradius-postgresql
     state: latest
   notify: restart freeradius
 
 - name: Freeradius mysql packages
-  when: openwisp2_database.engine == "django.contrib.gis.db.backends.mysql"
+  when: freeradius_sql.dialect == "mysql"
   apt:
     name: freeradius-mysql
     state: latest
@@ -42,7 +42,7 @@
     group: freerad
 
 - name: adding user 'freerad' to www-data group for database access
-  when: openwisp2_database.engine == "django.contrib.gis.db.backends.spatialite"
+  when: freeradius_sql.dialect == "sqlite"
   user:
     name: freerad
     groups: www-data
@@ -67,6 +67,7 @@
   notify: restart freeradius
 
 - name: Upload Daily Bandwidth Counter config file (mysql)
+  when: freeradius_sql.dialect == "mysql"
   template:
     src: freeradius/daily-bandwidth-counter/mysql.j2
     dest: "{{ freeradius_mods_config_dir }}/sql/counter/mysql/dailybandwidthcounter.conf"
@@ -76,6 +77,7 @@
   notify: restart freeradius
 
 - name: Upload Daily Bandwidth Counter config file (postgresql)
+  when: freeradius_sql.dialect == "postgresql"
   template:
     src: freeradius/daily-bandwidth-counter/postgresql.j2
     dest: "{{ freeradius_mods_config_dir }}/sql/counter/postgresql/dailybandwidthcounter.conf"
diff --git a/templates/freeradius/sql.j2 b/templates/freeradius/sql.j2
index 54d5d834..22882984 100644
--- a/templates/freeradius/sql.j2
+++ b/templates/freeradius/sql.j2
@@ -8,7 +8,7 @@ sql {
     login = "{{ openwisp2_database.user }}"
     password = "{{ openwisp2_database.password }}"
 
-{% elif 'sqlite' == freeradius_sql.dialect %}
+{% elif freeradius_sql.dialect == 'sqlite' %}
     sqlite {
         filename = "{{ openwisp2_database.name }}"
     }
diff --git a/templates/openwisp2/settings.py b/templates/openwisp2/settings.py
index 3568e096..a9d7a268 100644
--- a/templates/openwisp2/settings.py
+++ b/templates/openwisp2/settings.py
@@ -118,14 +118,13 @@
 
 {% if openwisp2_radius %}
 OPENWISP_RADIUS_FREERADIUS_ALLOWED_HOSTS = {{ openwisp2_radius_allowed_hosts }}
-
-# SMS
 REST_AUTH_SERIALIZERS = {
     'PASSWORD_RESET_SERIALIZER': 'openwisp_radius.api.serializers.PasswordResetSerializer',
 }
 REST_AUTH_REGISTER_SERIALIZERS = {
     'REGISTER_SERIALIZER': 'openwisp_radius.api.serializers.RegisterSerializer',
 }
+# SMS settings
 OPENWISP_RADIUS_SMS_TOKEN_MAX_IP_DAILY = {{ openwisp2_radius_sms_token_max_ip_daily }}
 SENDSMS_BACKEND = '{{ openwisp2_radius_sms_backend }}'
 

From f7f3468882de75ff273c879483d4d31aa03812b9 Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Thu, 21 Jan 2021 19:05:21 -0500
Subject: [PATCH 27/34] [chores] Removed duplicated noresetcounter

---
 templates/freeradius/openwisp_site.j2 | 1 -
 1 file changed, 1 deletion(-)

diff --git a/templates/freeradius/openwisp_site.j2 b/templates/freeradius/openwisp_site.j2
index 29fb8503..a6c2d168 100644
--- a/templates/freeradius/openwisp_site.j2
+++ b/templates/freeradius/openwisp_site.j2
@@ -22,7 +22,6 @@ server default {
         rest
         sql
         dailycounter
-        noresetcounter
         dailybandwidthcounter
         noresetcounter
         expiration

From 9c6ff64ca76c03468d3874b1cd27e59e18a3391f Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Fri, 5 Feb 2021 11:54:42 -0500
Subject: [PATCH 28/34] [change] Adjusted interval of cron tasks

---
 README.md         | 4 ++--
 defaults/main.yml | 4 ++--
 2 files changed, 4 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index 0bd796c4..c0d20015 100644
--- a/README.md
+++ b/README.md
@@ -834,11 +834,11 @@ Below are listed all the variables you can customize (you may also want to take
     freeradius_rest:
         url: "https://{{ inventory_hostname }}/api/v1/freeradius"
     cron_delete_old_notifications: "'hour': 0, 'minute': 0"
-    cron_deactivate_expired_users: "'hour': 0, 'minute': 0"
+    cron_deactivate_expired_users: "'hour': 0, 'minute': 5"
     cron_delete_old_users: "'hour': 0, 'minute': 10"
     cron_cleanup_stale_radacct: "'hour': 0, 'minute': 20"
     cron_delete_old_postauth: "'hour': 0, 'minute': 30"
-    cron_delete_old_radacct: "'hour': 0, 'minute': 40"
+    cron_delete_old_radacct: "'hour': 1, 'minute': 30"
 ```
 
 Support
diff --git a/defaults/main.yml b/defaults/main.yml
index faf81090..1817024d 100755
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -142,8 +142,8 @@ freeradius_rest:
     url: "https://{{ inventory_hostname }}/api/v1/freeradius"
 freeradius_expire_attr_after_seconds: 86400
 cron_delete_old_notifications: "'hour': 0, 'minute': 0"
-cron_deactivate_expired_users: "'hour': 0, 'minute': 0"
+cron_deactivate_expired_users: "'hour': 0, 'minute': 5"
 cron_delete_old_users: "'hour': 0, 'minute': 10"
 cron_cleanup_stale_radacct: "'hour': 0, 'minute': 20"
 cron_delete_old_postauth: "'hour': 0, 'minute': 30"
-cron_delete_old_radacct: "'hour': 0, 'minute': 40"
+cron_delete_old_radacct: "'hour': 1, 'minute': 30"

From 3b035c54c39f5c20d77a9f3b60eac172c5870c0d Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Fri, 5 Feb 2021 12:01:52 -0500
Subject: [PATCH 29/34] [chores] Allow disabling
 openwisp_radius.tasks.delete_old_radacct

---
 templates/openwisp2/settings.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/templates/openwisp2/settings.py b/templates/openwisp2/settings.py
index a9d7a268..12a027cb 100644
--- a/templates/openwisp2/settings.py
+++ b/templates/openwisp2/settings.py
@@ -186,7 +186,8 @@
         'schedule': crontab(**{ {{ cron_delete_old_notifications }} }),
         'args': ({{ openwisp2_notifications_delete_old_notifications }},),
     },
-    {% if openwisp2_radius %}
+{% if openwisp2_radius %}
+
     'deactivate_expired_users': {
         'task': 'openwisp_radius.tasks.cleanup_stale_radacct',
         'schedule': crontab(**{ {{ cron_deactivate_expired_users }} }),
@@ -211,6 +212,7 @@
         'args': [{{ openwisp2_radius_delete_old_postauth }}],
         'relative': True,
     },
+    {% if openwisp2_radius_delete_old_radacct %}
     'delete_old_radacct': {
         'task': 'openwisp_radius.tasks.delete_old_radacct',
         'schedule': crontab(**{ {{ cron_delete_old_radacct }} }),
@@ -218,6 +220,8 @@
         'relative': True,
     },
     {% endif %}
+{% endif %}
+
 }
 
 {% if openwisp2_celery_task_routes_defaults %}

From 1255dafaf8513761b05e809d20371253ffb5f9ae Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Fri, 5 Feb 2021 12:02:04 -0500
Subject: [PATCH 30/34] [chores] Added more notes to variables

---
 README.md | 13 +++++++++++++
 1 file changed, 13 insertions(+)

diff --git a/README.md b/README.md
index c0d20015..3fd32562 100644
--- a/README.md
+++ b/README.md
@@ -522,6 +522,10 @@ When the playbook is done running, if you got no errors you can login at:
     username: admin
     password: admin
 
+**Note:** for more information regarding radius configuration options,
+look for the word "radius" in the
+[Role variables](#role-variables) section of this document.
+
 Troubleshooting
 ===============
 
@@ -812,11 +816,20 @@ Below are listed all the variables you can customize (you may also want to take
     # allows overriding the default duration for keeping notifications
     openwisp2_notifications_delete_old_notifications: 10
     openwisp2_users_auth_api: true
+    # used for SMS verification, the default is a dummy SMS backend
+    # which prints to standard output and hence does nothing
+    # one of the available providers from django-sendsms can be
+    # used or alternatively, you can write a backend class for your
+    # favorite SMS API gateway
     openwisp2_radius_sms_backend: "sendsms.backends.console.SmsBackend"
     openwisp2_radius_sms_token_max_ip_daily: 25
     openwisp2_radius_delete_old_users: 365
     openwisp2_radius_cleanup_stale_radacct: 365
     openwisp2_radius_delete_old_postauth: 365
+    # days for which the radius accounting sessions (radacct) are retained,
+    # 0 means sessions are kept forever.
+    # we highly suggest to set this number according
+    # to the privacy regulation of your jurisdiction
     openwisp2_radius_delete_old_radacct: 365
     openwisp2_radius_allowed_hosts: ["127.0.0.1"]
     # this role provides a default configuration of freeradius

From 6680a1847438937a31d775ee4189bc9355160091 Mon Sep 17 00:00:00 2001
From: Ajay Tripathi <ajay39in@gmail.com>
Date: Mon, 8 Feb 2021 22:41:36 +0530
Subject: [PATCH 31/34] [openwisp-radius] Add test to ensure freeradius is
 working

---
 .github/workflows/ci.yml                      |  8 +++-----
 molecule/resources/converge.yml               |  3 +++
 molecule/resources/verify.yml                 | 19 +++++++++++++++++++
 tasks/freeradius.yml                          | 12 +++++-------
 .../{sqlcounter.j2 => sql_counter.j2}         |  0
 templates/openwisp2/settings.py               |  2 +-
 6 files changed, 31 insertions(+), 13 deletions(-)
 rename templates/freeradius/{sqlcounter.j2 => sql_counter.j2} (100%)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5b3ecd23..96c54df0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -28,14 +28,12 @@ jobs:
         with:
           ref: ${{ github.event.pull_request.head.sha }}
 
-      - name: Set up Python
-        uses: actions/setup-python@v2
-        with:
-          python-version: '3.x'
+      - name: Set default python version
+        run: sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 2
 
       - name: Install python dependencies
         run: |
-          pip install molecule[docker,ansible] yamllint ansible-lint docker openwisp-utils[qa]
+          sudo python3 -m pip install molecule[docker] yamllint ansible ansible-lint docker openwisp-utils[qa]
 
       - name: QA checks
         run: |
diff --git a/molecule/resources/converge.yml b/molecule/resources/converge.yml
index c95e1caa..7ff83ec8 100644
--- a/molecule/resources/converge.yml
+++ b/molecule/resources/converge.yml
@@ -8,12 +8,15 @@
     openwisp2_network_topology: true
     openwisp2_firmware_upgrader: true
     openwisp2_radius: true
+    # TODO: test help when openwisp-radius 0.3.0 is released!
+    # openwisp2_radius_allowed_hosts: ["0.0.0.0/0"]
 
   pre_tasks:
     - name: Update apt cache
       apt:
         update_cache: true
         cache_valid_time: 600
+      when: ansible_os_family == 'Debian'
 
     - name: Remove the .dockerenv file
       file:
diff --git a/molecule/resources/verify.yml b/molecule/resources/verify.yml
index 5feb8c9c..d0e6be45 100644
--- a/molecule/resources/verify.yml
+++ b/molecule/resources/verify.yml
@@ -64,3 +64,22 @@
         - name: Show OpenWisp log
           debug:
             var: openwisp_log
+
+    - name: Check Freeradius
+    # TODO: This test should work when openwisp-radius 0.3.0 is released!
+    #   block:
+    #     - name: Get radius-token for admin user
+    #       uri:
+    #         url: "https://{{ inventory_hostname }}/api/v1/default/account/token/"
+    #         validate_certs: false
+    #         method: POST
+    #         body_format: form-urlencoded
+    #         body:
+    #           username: admin
+    #           password: admin
+    #     - name: Check if freeradius is running
+    #       command: radtest admin admin localhost 0 testing123
+    #       register: freeradius_status
+    #       failed_when: '"Received Access-Accept" not in freeradius_status.stdout'
+      debug:
+        msg: "This test should work when openwisp-radius 0.3.0 is released!"
diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index 53b99120..0b80b5f9 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -33,9 +33,7 @@
 - name: Enable SQL module
   file:
     src: "{{ freeradius_mods_available_dir }}/sql"
-    # the "1_" prefix is put on purpose to force loading the sql module
-    # before the sqlcounter module to avoid an annoying startup error
-    dest: "{{ freeradius_mods_enabled_dir }}/1_sql"
+    dest: "{{ freeradius_mods_enabled_dir }}/sql"
     state: link
     mode: 0640
     owner: freerad
@@ -50,8 +48,8 @@
 
 - name: SQL Counter module
   template:
-    src: freeradius/sqlcounter.j2
-    dest: "{{ freeradius_mods_available_dir }}/sqlcounter"
+    src: freeradius/sql_counter.j2
+    dest: "{{ freeradius_mods_available_dir }}/sql_counter"
     mode: 0640
     owner: freerad
     group: freerad
@@ -108,8 +106,8 @@
 
 - name: Enable SQL Counter module
   file:
-    src: "{{ freeradius_mods_available_dir }}/sqlcounter"
-    dest: "{{ freeradius_mods_enabled_dir }}/sqlcounter"
+    src: "{{ freeradius_mods_available_dir }}/sql_counter"
+    dest: "{{ freeradius_mods_enabled_dir }}/sql_counter"
     state: link
     mode: 0640
     owner: freerad
diff --git a/templates/freeradius/sqlcounter.j2 b/templates/freeradius/sql_counter.j2
similarity index 100%
rename from templates/freeradius/sqlcounter.j2
rename to templates/freeradius/sql_counter.j2
diff --git a/templates/openwisp2/settings.py b/templates/openwisp2/settings.py
index 12a027cb..927d72b9 100644
--- a/templates/openwisp2/settings.py
+++ b/templates/openwisp2/settings.py
@@ -189,7 +189,7 @@
 {% if openwisp2_radius %}
 
     'deactivate_expired_users': {
-        'task': 'openwisp_radius.tasks.cleanup_stale_radacct',
+        'task': 'openwisp_radius.tasks.deactivate_expired_users',
         'schedule': crontab(**{ {{ cron_deactivate_expired_users }} }),
         'args': None,
         'relative': True,

From ef1479684f691cb9e6eadbe355aa90a137996c11 Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Fri, 23 Apr 2021 19:06:21 -0500
Subject: [PATCH 32/34] [chores] Added basic inner-tunnel

---
 tasks/freeradius.yml                 | 10 +++-
 templates/freeradius/inner-tunnel.j2 | 81 ++++++++++++++++++++++++++++
 2 files changed, 90 insertions(+), 1 deletion(-)
 create mode 100644 templates/freeradius/inner-tunnel.j2

diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index 0b80b5f9..ce9755c0 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -142,7 +142,6 @@
     state: absent
   with_items:
     - "{{ freeradius_sites_enabled_dir }}/default"
-    - "{{ freeradius_sites_enabled_dir }}/inner-tunnel"
 
 - name: Site configuration
   template:
@@ -152,3 +151,12 @@
     owner: freerad
     group: freerad
   notify: restart freeradius
+
+- name: Inner tunnel
+  template:
+    src: freeradius/openwisp_site.j2
+    dest: "{{ freeradius_sites_enabled_dir }}/inner-tunnel"
+    mode: 0640
+    owner: freerad
+    group: freerad
+  notify: restart freeradius
diff --git a/templates/freeradius/inner-tunnel.j2 b/templates/freeradius/inner-tunnel.j2
new file mode 100644
index 00000000..686ca489
--- /dev/null
+++ b/templates/freeradius/inner-tunnel.j2
@@ -0,0 +1,81 @@
+server inner-tunnel {
+    listen {
+        ipaddr = 127.0.0.1
+        port = 18120
+        type = auth
+    }
+
+    authorize {
+        filter_username
+        rest
+
+        chap
+        mschap
+        suffix
+
+        update control {
+            &Proxy-To-Realm := LOCAL
+        }
+
+        eap {
+            ok = return
+        }
+
+        -ldap
+
+        pap
+
+        dailycounter
+        dailybandwidthcounter
+        noresetcounter
+        expiration
+        logintime
+    }
+
+    authenticate {
+        Auth-Type PAP {
+            pap
+        }
+
+        Auth-Type CHAP {
+            chap
+        }
+
+        Auth-Type MS-CHAP {
+            mschap
+        }
+        eap
+    }
+
+    session {}
+
+    post-auth {
+        if (0) {
+            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
+            }
+            update {
+                &outer.session-state: += &reply:
+            }
+        }
+
+        Post-Auth-Type REJECT {
+            attr_filter.access_reject
+            update outer.session-state {
+                &Module-Failure-Message := &request:Module-Failure-Message
+            }
+        }
+    }
+
+    pre-proxy {}
+    post-proxy {
+        eap
+    }
+}

From 8c0bbc88269be5278828a7505ca4a530bba2dd6b Mon Sep 17 00:00:00 2001
From: Ajay Tripathi <ajay39in@gmail.com>
Date: Wed, 28 Apr 2021 21:12:00 +0530
Subject: [PATCH 33/34] [freeradius] Fixed problems prohibiting start

---
 .github/workflows/ci.yml                              |  5 +++--
 tasks/freeradius.yml                                  |  2 +-
 .../freeradius/{inner-tunnel.j2 => inner_tunnel.j2}   |  2 +-
 templates/freeradius/sql_counter.j2                   | 11 +++++------
 4 files changed, 10 insertions(+), 10 deletions(-)
 rename templates/freeradius/{inner-tunnel.j2 => inner_tunnel.j2} (98%)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 96c54df0..b44e7555 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,11 +29,12 @@ jobs:
           ref: ${{ github.event.pull_request.head.sha }}
 
       - name: Set default python version
-        run: sudo update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 2
+        run: sudo update-alternatives --install /usr/bin/python python ${pythonLocation}/bin/python3.8 2
+
 
       - name: Install python dependencies
         run: |
-          sudo python3 -m pip install molecule[docker] yamllint ansible ansible-lint docker openwisp-utils[qa]
+          python -m pip install molecule[docker] yamllint ansible ansible-lint docker openwisp-utils[qa]
 
       - name: QA checks
         run: |
diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index ce9755c0..afa174ed 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -154,7 +154,7 @@
 
 - name: Inner tunnel
   template:
-    src: freeradius/openwisp_site.j2
+    src: freeradius/inner_tunnel.j2
     dest: "{{ freeradius_sites_enabled_dir }}/inner-tunnel"
     mode: 0640
     owner: freerad
diff --git a/templates/freeradius/inner-tunnel.j2 b/templates/freeradius/inner_tunnel.j2
similarity index 98%
rename from templates/freeradius/inner-tunnel.j2
rename to templates/freeradius/inner_tunnel.j2
index 686ca489..9bec6c31 100644
--- a/templates/freeradius/inner-tunnel.j2
+++ b/templates/freeradius/inner_tunnel.j2
@@ -1,4 +1,4 @@
-server inner-tunnel {
+server inner_tunnel {
     listen {
         ipaddr = 127.0.0.1
         port = 18120
diff --git a/templates/freeradius/sql_counter.j2 b/templates/freeradius/sql_counter.j2
index 963b1de6..1ae67cfa 100644
--- a/templates/freeradius/sql_counter.j2
+++ b/templates/freeradius/sql_counter.j2
@@ -2,7 +2,7 @@
 
 sqlcounter dailycounter {
     sql_module_instance = sql
-    dialect = ${modules.sql.dialect}
+    dialect = "{{ freeradius_sql.dialect }}"
 
     counter_name = Daily-Session-Time
     check_name = Max-Daily-Session
@@ -13,11 +13,10 @@ sqlcounter dailycounter {
 
     $INCLUDE ${modconfdir}/sql/counter/${dialect}/${.:instance}.conf
 }
-
 # The dailybandwidthcounter is added by OpenWISP
 sqlcounter dailybandwidthcounter {
     sql_module_instance = sql
-    dialect = ${modules.sql.dialect}
+    dialect = "{{ freeradius_sql.dialect }}"
 
     counter_name = Max-Daily-Session-Traffic
     check_name = Max-Daily-Session-Traffic
@@ -29,7 +28,7 @@ sqlcounter dailybandwidthcounter {
 
 sqlcounter noresetcounter {
     sql_module_instance = sql
-    dialect = ${modules.sql.dialect}
+    dialect = "{{ freeradius_sql.dialect }}"
 
     counter_name = Max-All-Session-Time
     check_name = Max-All-Session
@@ -41,7 +40,7 @@ sqlcounter noresetcounter {
 
 sqlcounter monthlycounter {
     sql_module_instance = sql
-    dialect = ${modules.sql.dialect}
+    dialect = "{{ freeradius_sql.dialect }}"
 
     counter_name = Monthly-Session-Time
     check_name = Max-Monthly-Session
@@ -54,7 +53,7 @@ sqlcounter monthlycounter {
 
 sqlcounter expire_on_login {
     sql_module_instance = sql
-    dialect = ${modules.sql.dialect}
+    dialect = "{{ freeradius_sql.dialect }}"
 
     counter_name = Expire-After-Initial-Login
     check_name = Expire-After

From 8bebc284ef7d44ab7e10277c4e34a4cbcd3cb654 Mon Sep 17 00:00:00 2001
From: Federico Capoano <f.capoano@openwisp.io>
Date: Fri, 13 Aug 2021 18:05:17 -0500
Subject: [PATCH 34/34] [fix] Added safe_characters to freeradius SQL conf

---
 README.md            | 1 +
 defaults/main.yml    | 3 ++-
 tasks/freeradius.yml | 8 ++++++++
 3 files changed, 11 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index b4d59b83..62842b16 100644
--- a/README.md
+++ b/README.md
@@ -854,6 +854,7 @@ Below are listed all the variables you can customize (you may also want to take
     freeradius_sites_enabled_dir: "{{ freeradius_dir }}/sites-enabled"
     freeradius_rest:
         url: "https://{{ inventory_hostname }}/api/v1/freeradius"
+    freeradius_safe_characters: "+@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"
     cron_delete_old_notifications: "'hour': 0, 'minute': 0"
     cron_deactivate_expired_users: "'hour': 0, 'minute': 5"
     cron_delete_old_users: "'hour': 0, 'minute': 10"
diff --git a/defaults/main.yml b/defaults/main.yml
index 8e55e8c0..9ef95753 100755
--- a/defaults/main.yml
+++ b/defaults/main.yml
@@ -144,9 +144,10 @@ freeradius_sql:
 freeradius_rest:
     url: "https://{{ inventory_hostname }}/api/v1/freeradius"
 freeradius_expire_attr_after_seconds: 86400
+freeradius_safe_characters: "+@abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_: /"
 cron_delete_old_notifications: "'hour': 0, 'minute': 0"
 cron_deactivate_expired_users: "'hour': 0, 'minute': 5"
 cron_delete_old_users: "'hour': 0, 'minute': 10"
 cron_cleanup_stale_radacct: "'hour': 0, 'minute': 20"
 cron_delete_old_postauth: "'hour': 0, 'minute': 30"
-cron_delete_old_radacct: "'hour': 1, 'minute': 30"
\ No newline at end of file
+cron_delete_old_radacct: "'hour': 1, 'minute': 30"
diff --git a/tasks/freeradius.yml b/tasks/freeradius.yml
index afa174ed..64bb9dee 100644
--- a/tasks/freeradius.yml
+++ b/tasks/freeradius.yml
@@ -39,6 +39,14 @@
     owner: freerad
     group: freerad
 
+- name: Add plus sign to safe characters
+  lineinfile:
+    path: "{{ freeradius_mods_config_dir }}/sql/main/{{ freeradius_sql.dialect }}/queries.conf"
+    regexp: "^(.*)safe_characters =(.*)$"
+    line: "safe_characters = \"{{ freeradius_safe_characters }}\""
+    state: present
+  notify: restart freeradius
+
 - name: adding user 'freerad' to www-data group for database access
   when: freeradius_sql.dialect == "sqlite"
   user: