From 1dc79cebc6d27a8f9d2f9ca9c2e0f2fd0809d940 Mon Sep 17 00:00:00 2001
From: Christian Breunig <christian@breunig.cc>
Date: Mon, 8 May 2023 21:27:14 +0200
Subject: syslog: T2778: migrate to get_config_dict()

---
 data/templates/rsyslog/logrotate.j2                |  16 ++
 data/templates/rsyslog/rsyslog.conf                |  59 ----
 data/templates/rsyslog/rsyslog.conf.j2             |  71 +++++
 data/templates/syslog/logrotate.j2                 |  11 -
 data/templates/syslog/rsyslog.conf.j2              |  58 ----
 debian/vyos-1x.install                             |   2 +-
 debian/vyos-1x.preinst                             |   1 +
 .../include/protocol-tcp-udp.xml.i                 |  22 ++
 interface-definitions/include/snmp/protocol.xml.i  |  22 --
 .../include/syslog-facility.xml.i                  |  21 +-
 .../include/version/system-version.xml.i           |   2 +-
 interface-definitions/snmp.xml.in                  |   4 +-
 interface-definitions/system-syslog.xml.in         |  13 +-
 smoketest/configs/basic-vyos                       |  34 ++-
 src/conf_mode/system-syslog.py                     | 312 ++++++---------------
 src/etc/rsyslog.conf                               |  61 ++++
 src/etc/rsyslog.d/01-auth.conf                     |  14 -
 src/migration-scripts/system/25-to-26              |  82 ++++++
 18 files changed, 389 insertions(+), 416 deletions(-)
 create mode 100644 data/templates/rsyslog/logrotate.j2
 delete mode 100644 data/templates/rsyslog/rsyslog.conf
 create mode 100644 data/templates/rsyslog/rsyslog.conf.j2
 delete mode 100644 data/templates/syslog/logrotate.j2
 delete mode 100644 data/templates/syslog/rsyslog.conf.j2
 create mode 100644 interface-definitions/include/protocol-tcp-udp.xml.i
 delete mode 100644 interface-definitions/include/snmp/protocol.xml.i
 create mode 100644 src/etc/rsyslog.conf
 delete mode 100644 src/etc/rsyslog.d/01-auth.conf
 create mode 100755 src/migration-scripts/system/25-to-26

diff --git a/data/templates/rsyslog/logrotate.j2 b/data/templates/rsyslog/logrotate.j2
new file mode 100644
index 000000000..89d1a8a50
--- /dev/null
+++ b/data/templates/rsyslog/logrotate.j2
@@ -0,0 +1,16 @@
+### Autogenerated by system-syslog.py ###
+{% if file is vyos_defined %}
+{%     for file_name, file_options in file.items() %}
+/var/log/user/{{ file_name }} {
+  missingok
+  notifempty
+  create
+  rotate {{ file_options.archive.file }}
+  size={{ file_options.archive.size | int // 1024 }}k
+  postrotate
+    invoke-rc.d rsyslog rotate > /dev/null
+  endscript
+}
+
+{%     endfor %}
+{% endif %}
diff --git a/data/templates/rsyslog/rsyslog.conf b/data/templates/rsyslog/rsyslog.conf
deleted file mode 100644
index ab60fc0f0..000000000
--- a/data/templates/rsyslog/rsyslog.conf
+++ /dev/null
@@ -1,59 +0,0 @@
-#  /etc/rsyslog.conf    Configuration file for rsyslog.
-#
-
-#################
-#### MODULES ####
-#################
-
-$ModLoad imuxsock # provides support for local system logging
-$ModLoad imklog   # provides kernel logging support (previously done by rklogd)
-#$ModLoad immark  # provides --MARK-- message capability
-
-$OmitLocalLogging off
-$SystemLogSocketName /run/systemd/journal/syslog
-
-$KLogPath /proc/kmsg
-
-# provides UDP syslog reception
-#$ModLoad imudp
-#$UDPServerRun 514
-
-# provides TCP syslog reception
-#$ModLoad imtcp
-#$InputTCPServerRun 514
-
-###########################
-#### GLOBAL DIRECTIVES ####
-###########################
-
-#
-# Use traditional timestamp format.
-# To enable high precision timestamps, comment out the following line.
-#
-$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
-
-# Filter duplicated messages
-$RepeatedMsgReduction on
-
-#
-# Set the default permissions for all log files.
-#
-$FileOwner root
-$FileGroup adm
-$FileCreateMode 0640
-$DirCreateMode 0755
-$Umask 0022
-
-
-#
-# Include all config files in /etc/rsyslog.d/
-#
-$IncludeConfig /etc/rsyslog.d/*.conf
-
-###############
-#### RULES ####
-###############
-# Emergencies are sent to everybody logged in.
-
-*.emerg                         :omusrmsg:*
-
diff --git a/data/templates/rsyslog/rsyslog.conf.j2 b/data/templates/rsyslog/rsyslog.conf.j2
new file mode 100644
index 000000000..0460ae5f0
--- /dev/null
+++ b/data/templates/rsyslog/rsyslog.conf.j2
@@ -0,0 +1,71 @@
+### Autogenerated by system-syslog.py ###
+
+{% if global.marker is vyos_defined %}
+$ModLoad immark
+{%     if global.marker.interval is vyos_defined %}
+$MarkMessagePeriod {{ global.marker.interval }}
+{%     endif %}
+{% endif %}
+{% if global.preserve_fqdn is vyos_defined %}
+$PreserveFQDN on
+{% endif %}
+
+# We always log to /var/log/messages
+$outchannel global,/var/log/messages,262144,/usr/sbin/logrotate {{ logrotate }}
+{% if global.facility is vyos_defined %}
+{%     set tmp = [] %}
+{%     for facility, facility_options in global.facility.items() %}
+{%         set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level) %}
+{%     endfor %}
+{{ tmp | join(';') }} :omfile:$global
+{% endif %}
+
+{% if file is vyos_defined %}
+# File based configuration section
+{%     for file_name, file_options in file.items() %}
+$outchannel {{ file_name }},/var/log/user/{{ file_name }},{{ file_options.archive.size }},/usr/sbin/logrotate {{ logrotate }}
+{%         set tmp = [] %}
+{%         for facility, facility_options in file_options.facility.items() %}
+{%             set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level) %}
+{%         endfor %}
+{{ tmp | join(';') }} :omfile:${{ file }}
+{%     endfor %}
+{% endif %}
+
+{% if console.facility is vyos_defined %}
+# Console logging
+{%     set tmp = [] %}
+{%     for facility, facility_options in console.facility.items() %}
+{%         set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level) %}
+{%     endfor %}
+{{ tmp | join(';') }} /dev/console
+{% endif %}
+
+{% if host is vyos_defined %}
+# Remote logging
+{%     for host_name, host_options in host.items() %}
+{%         set tmp = [] %}
+{%         for facility, facility_options in host_options.facility.items() %}
+{%             set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level) %}
+{%         endfor %}
+{%         if host_options.protocol is vyos_defined('tcp') %}
+{%             if host_options.oct_count is vyos_defined %}
+{{ tmp | join(';') }} @@(o){{ host_name | bracketize_ipv6 }}:{{ host_options.port }};RSYSLOG_SyslogProtocol23Format
+{%             else %}
+{{ tmp | join(';') }} @@{{ host_name | bracketize_ipv6 }}:{{ host_options.port }}
+{%             endif %}
+{%         else %}
+{{ tmp | join(';') }} @{{ host_name | bracketize_ipv6 }}:{{ host_options.port }}{{ ';RSYSLOG_SyslogProtocol23Format' if host_options.format.octet_counted is vyos_defined }}
+{%         endif %}
+{%     endfor %}
+{% endif %}
+
+{% if user is defined and user is not none %}
+# Log to user terminal
+{%     for username, user_options in user.items() %}
+{%         for facility, facility_options in user_options.facility.items() %}
+{%             set _ = tmp.append(facility.replace('all', '*') + '.' + facility_options.level) %}
+{%         endfor %}
+{{ tmp | join(';') }} :omusrmsg:{{ username }}
+{%     endfor %}
+{% endif %}
diff --git a/data/templates/syslog/logrotate.j2 b/data/templates/syslog/logrotate.j2
deleted file mode 100644
index c1b951e8b..000000000
--- a/data/templates/syslog/logrotate.j2
+++ /dev/null
@@ -1,11 +0,0 @@
-{{ config_render['log-file'] }} {
-  missingok
-  notifempty
-  create
-  rotate {{ config_render['max-files'] }}
-  size={{ config_render['max-size'] // 1024 }}k
-  postrotate
-    invoke-rc.d rsyslog rotate > /dev/null
-  endscript
-}
-
diff --git a/data/templates/syslog/rsyslog.conf.j2 b/data/templates/syslog/rsyslog.conf.j2
deleted file mode 100644
index abe880283..000000000
--- a/data/templates/syslog/rsyslog.conf.j2
+++ /dev/null
@@ -1,58 +0,0 @@
-## generated by syslog.py ##
-## file based logging
-{% if files['global']['marker'] %}
-$ModLoad immark
-{%     if files['global']['marker-interval'] %}
-$MarkMessagePeriod {{ files['global']['marker-interval'] }}
-{%     endif %}
-{% endif %}
-{% if files['global']['preserver_fqdn'] %}
-$PreserveFQDN on
-{% endif %}
-{% for file, file_options in files.items() %}
-{%     if file_options['max-size'] is vyos_defined %}
-$outchannel {{ file }},{{ file_options['log-file'] }},{{ file_options['max-size'] }},{{ file_options['action-on-max-size'] }}
-{%     else %}
-$outchannel {{ file }},{{ file_options['log-file'] }}
-{%     endif %}
-{{ file_options['selectors'] }} :omfile:${{ file }}
-{% endfor %}
-{% if console is defined and console is not none %}
-## console logging
-{%     for con, con_options in console.items() %}
-{{ con_options['selectors'] }} /dev/console
-{%     endfor %}
-{% endif %}
-{% if hosts is defined and hosts is not none %}
-## remote logging
-{%     for host, host_options in hosts.items() %}
-{%         if host_options.proto == 'tcp' %}
-{%             if host_options.port is defined %}
-{%                 if host_options.oct_count is defined %}
-{{ host_options.selectors }} @@(o){{ host | bracketize_ipv6 }}:{{ host_options.port }};RSYSLOG_SyslogProtocol23Format
-{%                 else %}
-{{ host_options.selectors }} @@{{ host | bracketize_ipv6 }}:{{ host_options.port }}
-{%                 endif %}
-{%             else %}
-{{ host_options.selectors }} @@{{ host | bracketize_ipv6 }}
-{%             endif %}
-{%         elif host_options.proto == 'udp' %}
-{%             if host_options.port is defined %}
-{{ host_options.selectors }} @{{ host | bracketize_ipv6 }}:{{ host_options.port }}{{ ';RSYSLOG_SyslogProtocol23Format' if host_options.oct_count is sameas true }}
-{%             else %}
-{{ host_options.selectors }} @{{ host | bracketize_ipv6 }}
-{%             endif %}
-{%         else %}
-{%             if host_options['port'] %}
-{{ host_options.selectors }} @{{ host | bracketize_ipv6 }}:{{ host_options.port }}
-{%             else %}
-{{ host_options.selectors }} @{{ host | bracketize_ipv6 }}
-{%             endif %}
-{%         endif %}
-{%     endfor %}
-{% endif %}
-{% if user is defined and user is not none %}
-{%     for username, user_options in user.items() %}
-{{ user_options.selectors }} :omusrmsg:{{ username }}
-{%     endfor %}
-{% endif %}
diff --git a/debian/vyos-1x.install b/debian/vyos-1x.install
index 98d1bc0cd..2b04f173b 100644
--- a/debian/vyos-1x.install
+++ b/debian/vyos-1x.install
@@ -6,7 +6,7 @@ etc/netplug
 etc/opennhrp
 etc/modprobe.d
 etc/ppp
-etc/rsyslog.d
+etc/rsyslog.conf
 etc/securetty
 etc/security
 etc/sudoers.d
diff --git a/debian/vyos-1x.preinst b/debian/vyos-1x.preinst
index 213a23d9e..58f24cb5a 100644
--- a/debian/vyos-1x.preinst
+++ b/debian/vyos-1x.preinst
@@ -3,3 +3,4 @@ dpkg-divert --package vyos-1x --add --rename /etc/security/capability.conf
 dpkg-divert --package vyos-1x --add --rename /lib/systemd/system/lcdproc.service
 dpkg-divert --package vyos-1x --add --rename /etc/logrotate.d/conntrackd
 dpkg-divert --package vyos-1x --add --rename /usr/share/pam-configs/radius
+dpkg-divert --package vyos-1x --add --rename /etc/rsyslog.conf
diff --git a/interface-definitions/include/protocol-tcp-udp.xml.i b/interface-definitions/include/protocol-tcp-udp.xml.i
new file mode 100644
index 000000000..d7e6752ad
--- /dev/null
+++ b/interface-definitions/include/protocol-tcp-udp.xml.i
@@ -0,0 +1,22 @@
+<!-- include start from snmp/protocol.xml.i -->
+<leafNode name="protocol">
+  <properties>
+    <help>Protocol to be used (TCP/UDP)</help>
+    <completionHelp>
+      <list>udp tcp</list>
+    </completionHelp>
+    <valueHelp>
+      <format>udp</format>
+      <description>Listen protocol UDP</description>
+    </valueHelp>
+    <valueHelp>
+      <format>tcp</format>
+      <description>Listen protocol TCP</description>
+    </valueHelp>
+    <constraint>
+      <regex>(udp|tcp)</regex>
+    </constraint>
+  </properties>
+  <defaultValue>udp</defaultValue>
+</leafNode>
+<!-- include end -->
diff --git a/interface-definitions/include/snmp/protocol.xml.i b/interface-definitions/include/snmp/protocol.xml.i
deleted file mode 100644
index d7e6752ad..000000000
--- a/interface-definitions/include/snmp/protocol.xml.i
+++ /dev/null
@@ -1,22 +0,0 @@
-<!-- include start from snmp/protocol.xml.i -->
-<leafNode name="protocol">
-  <properties>
-    <help>Protocol to be used (TCP/UDP)</help>
-    <completionHelp>
-      <list>udp tcp</list>
-    </completionHelp>
-    <valueHelp>
-      <format>udp</format>
-      <description>Listen protocol UDP</description>
-    </valueHelp>
-    <valueHelp>
-      <format>tcp</format>
-      <description>Listen protocol TCP</description>
-    </valueHelp>
-    <constraint>
-      <regex>(udp|tcp)</regex>
-    </constraint>
-  </properties>
-  <defaultValue>udp</defaultValue>
-</leafNode>
-<!-- include end -->
diff --git a/interface-definitions/include/syslog-facility.xml.i b/interface-definitions/include/syslog-facility.xml.i
index 57067ece2..e6138a122 100644
--- a/interface-definitions/include/syslog-facility.xml.i
+++ b/interface-definitions/include/syslog-facility.xml.i
@@ -3,10 +3,10 @@
   <properties>
     <help>Facility for logging</help>
     <completionHelp>
-      <list>auth authpriv cron daemon kern lpr mail mark news protocols security syslog user uucp local0 local1 local2 local3 local4 local5 local6 local7 all</list>
+      <list>auth authpriv cron daemon kern lpr mail mark news syslog user uucp local0 local1 local2 local3 local4 local5 local6 local7 all</list>
     </completionHelp>
     <constraint>
-      <regex>(auth|authpriv|cron|daemon|kern|lpr|mail|mark|news|protocols|security|syslog|user|uucp|local0|local1|local2|local3|local4|local5|local6|local7|all)</regex>
+      <regex>(auth|authpriv|cron|daemon|kern|lpr|mail|mark|news|syslog|user|uucp|local0|local1|local2|local3|local4|local5|local6|local7|all)</regex>
     </constraint>
     <constraintErrorMessage>Invalid facility type</constraintErrorMessage>
     <valueHelp>
@@ -49,14 +49,6 @@
       <format>news</format>
       <description>USENET subsystem</description>
     </valueHelp>
-    <valueHelp>
-      <format>protocols</format>
-      <description>depricated will be set to local7</description>
-    </valueHelp>
-    <valueHelp>
-      <format>security</format>
-      <description>depricated will be set to auth</description>
-    </valueHelp>
     <valueHelp>
       <format>syslog</format>
       <description>Authentication and authorization</description>
@@ -109,10 +101,6 @@
         <completionHelp>
           <list>emerg alert crit err warning notice info debug all</list>
         </completionHelp>
-        <constraint>
-          <regex>(emerg|alert|crit|err|warning|notice|info|debug|all)</regex>
-        </constraint>
-        <constraintErrorMessage>Invalid loglevel</constraintErrorMessage>
         <valueHelp>
           <format>emerg</format>
           <description>Emergency messages</description>
@@ -149,7 +137,12 @@
           <format>all</format>
           <description>Log everything</description>
         </valueHelp>
+        <constraint>
+          <regex>(emerg|alert|crit|err|warning|notice|info|debug|all)</regex>
+        </constraint>
+        <constraintErrorMessage>Invalid loglevel</constraintErrorMessage>
       </properties>
+      <defaultValue>err</defaultValue>
     </leafNode>
   </children>
 </tagNode>
diff --git a/interface-definitions/include/version/system-version.xml.i b/interface-definitions/include/version/system-version.xml.i
index b7650c782..73df8bd8e 100644
--- a/interface-definitions/include/version/system-version.xml.i
+++ b/interface-definitions/include/version/system-version.xml.i
@@ -1,3 +1,3 @@
 <!-- include start from include/version/system-version.xml.i -->
-<syntaxVersion component='system' version='25'></syntaxVersion>
+<syntaxVersion component='system' version='26'></syntaxVersion>
 <!-- include end -->
diff --git a/interface-definitions/snmp.xml.in b/interface-definitions/snmp.xml.in
index 559e09388..6527cabd6 100644
--- a/interface-definitions/snmp.xml.in
+++ b/interface-definitions/snmp.xml.in
@@ -143,7 +143,7 @@
               <multi/>
             </properties>
           </leafNode>
-          #include <include/snmp/protocol.xml.i>
+          #include <include/protocol-tcp-udp.xml.i>
           <leafNode name="smux-peer">
             <properties>
               <help>Register a subtree for SMUX-based processing</help>
@@ -327,7 +327,7 @@
                       #include <include/snmp/privacy-type.xml.i>
                     </children>
                   </node>
-                  #include <include/snmp/protocol.xml.i>
+                  #include <include/protocol-tcp-udp.xml.i>
                   <leafNode name="type">
                     <properties>
                       <help>Specifies the type of notification between inform and trap</help>
diff --git a/interface-definitions/system-syslog.xml.in b/interface-definitions/system-syslog.xml.in
index 4a2adfd5f..17aa85706 100644
--- a/interface-definitions/system-syslog.xml.in
+++ b/interface-definitions/system-syslog.xml.in
@@ -50,6 +50,10 @@
             </properties>
             <children>
               #include <include/port-number.xml.i>
+              <leafNode name="port">
+                <defaultValue>514</defaultValue>
+              </leafNode>
+              #include <include/protocol-tcp-udp.xml.i>
               #include <include/syslog-facility.xml.i>
               <node name="format">
                 <properties>
@@ -79,11 +83,12 @@
                 <children>
                   <leafNode name="interval">
                     <properties>
-                      <help>time interval how often a mark message is being sent in seconds (default: 1200)</help>
+                      <help>time interval how often a mark message is being sent in seconds</help>
                       <constraint>
                         <validator name="numeric" argument="--positive"/>
                       </constraint>
                     </properties>
+                    <defaultValue>1200</defaultValue>
                   </leafNode>
                 </children>
               </node>
@@ -111,21 +116,23 @@
                 <children>
                   <leafNode name="file">
                     <properties>
-                      <help>Number of saved files (default is 5)</help>
+                      <help>Number of saved files</help>
                       <constraint>
                         <regex>[0-9]+</regex>
                       </constraint>
                       <constraintErrorMessage>illegal characters in number of files</constraintErrorMessage>
                     </properties>
+                    <defaultValue>5</defaultValue>
                   </leafNode>
                   <leafNode name="size">
                     <properties>
-                      <help>Size of log files (in kbytes, default is 256)</help>
+                      <help>Size of log files in kbytes</help>
                       <constraint>
                         <regex>[0-9]+</regex>
                       </constraint>
                       <constraintErrorMessage>illegal characters in size</constraintErrorMessage>
                     </properties>
+                    <defaultValue>256</defaultValue>
                   </leafNode>
                 </children>
               </node>
diff --git a/smoketest/configs/basic-vyos b/smoketest/configs/basic-vyos
index 23186b9b8..033c1a518 100644
--- a/smoketest/configs/basic-vyos
+++ b/smoketest/configs/basic-vyos
@@ -127,14 +127,40 @@ system {
     }
     name-server 192.168.0.1
     syslog {
-        global {
-            archive {
-                file 5
-                size 512
+        console {
+            facility all {
+                level emerg
+            }
+            facility mail {
+                level info
             }
+        }
+        global {
             facility all {
                 level info
             }
+            facility protocols {
+                level debug
+            }
+            facility security {
+                level info
+            }
+            preserve-fqdn
+        }
+        host syslog.vyos.net {
+            facility local7 {
+                level notice
+            }
+            facility protocols {
+                level alert
+            }
+            facility security {
+                level warning
+            }
+            format {
+                octet-counted
+            }
+            port 8000
         }
     }
     time-zone Europe/Berlin
diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py
index 20132456c..dba29d152 100755
--- a/src/conf_mode/system-syslog.py
+++ b/src/conf_mode/system-syslog.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 #
-# Copyright (C) 2018-2020 VyOS maintainers and contributors
+# Copyright (C) 2018-2023 VyOS maintainers and contributors
 #
 # This program is free software; you can redistribute it and/or modify
 # it under the terms of the GNU General Public License version 2 or later as
@@ -15,253 +15,111 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import os
-import re
 
-from pathlib import Path
 from sys import exit
 
 from vyos.config import Config
-from vyos import ConfigError
-from vyos.util import run
+from vyos.configdict import dict_merge
+from vyos.util import call
 from vyos.template import render
-
+from vyos.xml import defaults
+from vyos import ConfigError
 from vyos import airbag
 airbag.enable()
 
+rsyslog_conf = '/etc/rsyslog.d/00-vyos.conf'
+logrotate_conf = '/etc/logrotate.d/vyos-rsyslog'
+
 def get_config(config=None):
     if config:
-        c = config
+        conf = config
     else:
-        c = Config()
-    if not c.exists('system syslog'):
+        conf = Config()
+    base = ['system', 'syslog']
+    if not conf.exists(base):
         return None
-    c.set_level('system syslog')
-
-    config_data = {
-        'files': {},
-      'console': {},
-      'hosts': {},
-      'user': {}
-    }
-
-    #
-    # /etc/rsyslog.d/vyos-rsyslog.conf
-    # 'set system syslog global'
-    #
-    config_data['files'].update(
-        {
-            'global': {
-                'log-file': '/var/log/messages',
-                'selectors': '*.notice;local7.debug',
-                'max-files': '5',
-                'preserver_fqdn': False
-            }
-        }
-    )
-
-    if c.exists('global marker'):
-        config_data['files']['global']['marker'] = True
-        if c.exists('global marker interval'):
-            config_data['files']['global'][
-                'marker-interval'] = c.return_value('global marker interval')
-    if c.exists('global facility'):
-        config_data['files']['global'][
-            'selectors'] = generate_selectors(c, 'global facility')
-    if c.exists('global archive size'):
-        config_data['files']['global']['max-size'] = int(
-            c.return_value('global archive size')) * 1024
-    if c.exists('global archive file'):
-        config_data['files']['global'][
-            'max-files'] = c.return_value('global archive file')
-    if c.exists('global preserve-fqdn'):
-        config_data['files']['global']['preserver_fqdn'] = True
-
-    #
-    # set system syslog file
-    #
-
-    if c.exists('file'):
-        filenames = c.list_nodes('file')
-        for filename in filenames:
-            config_data['files'].update(
-                {
-                    filename: {
-                        'log-file': '/var/log/user/' + filename,
-                        'max-files': '5',
-                        'action-on-max-size': '/usr/sbin/logrotate /etc/logrotate.d/vyos-rsyslog-generated-' + filename,
-                        'selectors': '*.err',
-                        'max-size': 262144
-                    }
-                }
-            )
-
-            if c.exists('file ' + filename + ' facility'):
-                config_data['files'][filename]['selectors'] = generate_selectors(
-                    c, 'file ' + filename + ' facility')
-            if c.exists('file ' + filename + ' archive size'):
-                config_data['files'][filename]['max-size'] = int(
-                    c.return_value('file ' + filename + ' archive size')) * 1024
-            if c.exists('file ' + filename + ' archive files'):
-                config_data['files'][filename]['max-files'] = c.return_value(
-                    'file ' + filename + ' archive files')
-
-    # set system syslog console
-    if c.exists('console'):
-        config_data['console'] = {
-            '/dev/console': {
-                'selectors': '*.err'
-            }
-        }
-
-    for f in c.list_nodes('console facility'):
-        if c.exists('console facility ' + f + ' level'):
-            config_data['console'] = {
-                '/dev/console': {
-                    'selectors': generate_selectors(c, 'console facility')
-                }
-            }
-
-    # set system syslog host
-    if c.exists('host'):
-        rhosts = c.list_nodes('host')
-        proto = 'udp'
-        for rhost in rhosts:
-            for fac in c.list_nodes('host ' + rhost + ' facility'):
-                if c.exists('host ' + rhost + ' facility ' + fac + ' protocol'):
-                    proto = c.return_value(
-                        'host ' + rhost + ' facility ' + fac + ' protocol')
-                else:
-                    proto = 'udp'
-
-            config_data['hosts'].update(
-                {
-                    rhost: {
-                        'selectors': generate_selectors(c, 'host ' + rhost + ' facility'),
-                        'proto': proto
-                    }
-                }
-            )
-            if c.exists('host ' + rhost + ' port'):
-                config_data['hosts'][rhost][
-                    'port'] = c.return_value(['host', rhost, 'port'])
 
-            # set system syslog host x.x.x.x format octet-counted
-            if c.exists('host ' + rhost + ' format octet-counted'):
-                config_data['hosts'][rhost]['oct_count'] = True
-            else:
-                config_data['hosts'][rhost]['oct_count'] = False
-
-    # set system syslog user
-    if c.exists('user'):
-        usrs = c.list_nodes('user')
-        for usr in usrs:
-            config_data['user'].update(
-                {
-                    usr: {
-                        'selectors': generate_selectors(c, 'user ' + usr + ' facility')
-                    }
-                }
-            )
-
-    return config_data
-
-
-def generate_selectors(c, config_node):
-# protocols and security are being mapped here
-# for backward compatibility with old configs
-# security and protocol mappings can be removed later
-    nodes = c.list_nodes(config_node)
-    selectors = ""
-    for node in nodes:
-        lvl = c.return_value(config_node + ' ' + node + ' level')
-        if lvl == None:
-            lvl = "err"
-        if lvl == 'all':
-            lvl = '*'
-        if node == 'all' and node != nodes[-1]:
-            selectors += "*." + lvl + ";"
-        elif node == 'all':
-            selectors += "*." + lvl
-        elif node != nodes[-1]:
-            if node == 'protocols':
-                node = 'local7'
-            if node == 'security':
-                node = 'auth'
-            selectors += node + "." + lvl + ";"
-        else:
-            if node == 'protocols':
-                node = 'local7'
-            if node == 'security':
-                node = 'auth'
-            selectors += node + "." + lvl
-    return selectors
-
-
-def generate(c):
-    if c == None:
+    syslog = conf.get_config_dict(base, key_mangling=('-', '_'),
+                                  get_first_key=True, no_tag_node_value_mangle=True)
+
+    syslog.update({ 'logrotate' : logrotate_conf })
+
+    # We have gathered the dict representation of the CLI, but there are default
+    # options which we need to update into the dictionary retrived.
+    default_values = defaults(base)
+    # XXX: some syslog default values can not be merged here (originating from
+    # a tagNode - remove and add them later per individual tagNode instance
+    if 'console' in default_values:
+        del default_values['console']
+    for entity in ['global', 'user', 'host', 'file']:
+        if entity in default_values:
+            del default_values[entity]
+
+    syslog = dict_merge(default_values, syslog)
+
+    # XXX: add defaults for "console" tree
+    if 'console' in syslog and 'facility' in syslog['console']:
+        default_values = defaults(base + ['console', 'facility'])
+        for facility in syslog['console']['facility']:
+            syslog['console']['facility'][facility] = dict_merge(default_values,
+                                                                syslog['console']['facility'][facility])
+
+    # XXX: add defaults for "host" tree
+    if 'host' in syslog:
+        default_values_host = defaults(base + ['host'])
+        if 'facility' in default_values_host:
+            del default_values_host['facility']
+        default_values_facility = defaults(base + ['host', 'facility'])
+
+        for host, host_config in syslog['host'].items():
+            syslog['host'][host] = dict_merge(default_values_host, syslog['host'][host])
+            if 'facility' in host_config:
+                for facility in host_config['facility']:
+                    syslog['host'][host]['facility'][facility] = dict_merge(default_values_facility,
+                                                                        syslog['host'][host]['facility'][facility])
+
+    # XXX: add defaults for "user" tree
+    if 'user' in syslog:
+        default_values = defaults(base + ['user', 'facility'])
+        for user, user_config in syslog['user'].items():
+            if 'facility' in user_config:
+                for facility in user_config['facility']:
+                    syslog['user'][user]['facility'][facility] = dict_merge(default_values,
+                                                                        syslog['user'][user]['facility'][facility])
+
+    # XXX: add defaults for "file" tree
+    if 'file' in syslog:
+        default_values = defaults(base + ['file'])
+        for file, file_config in syslog['file'].items():
+            for facility in file_config['facility']:
+                syslog['file'][file]['facility'][facility] = dict_merge(default_values,
+                                                                        syslog['file'][file]['facility'][facility])
+
+    return syslog
+
+def verify(syslog):
+    if not syslog:
         return None
 
-    conf = '/etc/rsyslog.d/vyos-rsyslog.conf'
-    render(conf, 'syslog/rsyslog.conf.j2', c)
-
-    # cleanup current logrotate config files
-    logrotate_files = Path('/etc/logrotate.d/').glob('vyos-rsyslog-generated-*')
-    for file in logrotate_files:
-        file.unlink()
+def generate(syslog):
+    if not syslog:
+        if os.path.exists(rsyslog_conf):
+            os.path.unlink(rsyslog_conf)
+        if os.path.exists(logrotate_conf):
+            os.path.unlink(logrotate_conf)
 
-    # eventually write for each file its own logrotate file, since size is
-    # defined it shouldn't matter
-    for filename, fileconfig in c.get('files', {}).items():
-        if fileconfig['log-file'].startswith('/var/log/user/'):
-            conf = '/etc/logrotate.d/vyos-rsyslog-generated-' + filename
-            render(conf, 'syslog/logrotate.j2', { 'config_render': fileconfig })
-
-
-def verify(c):
-    if c == None:
         return None
 
-    # may be obsolete
-    # /etc/rsyslog.conf is generated somewhere and copied over the original (exists in /opt/vyatta/etc/rsyslog.conf)
-    # it interferes with the global logging, to make sure we are using a single base, template is enforced here
-    #
-    if not os.path.islink('/etc/rsyslog.conf'):
-        os.remove('/etc/rsyslog.conf')
-        os.symlink(
-            '/usr/share/vyos/templates/rsyslog/rsyslog.conf', '/etc/rsyslog.conf')
-
-    # /var/log/vyos-rsyslog were the old files, we may want to clean those up, but currently there
-    # is a chance that someone still needs it, so I don't automatically remove
-    # them
-    #
+    render(rsyslog_conf, 'rsyslog/rsyslog.conf.j2', syslog)
+    render(logrotate_conf, 'rsyslog/logrotate.j2', syslog)
 
-    if c == None:
+def apply(syslog):
+    systemd_service = 'syslog.service'
+    if not syslog:
+        call(f'systemctl stop {systemd_service}')
         return None
 
-    fac = [
-        '*', 'auth', 'authpriv', 'cron', 'daemon', 'kern', 'lpr', 'mail', 'mark', 'news', 'protocols', 'security',
-          'syslog', 'user', 'uucp', 'local0', 'local1', 'local2', 'local3', 'local4', 'local5', 'local6', 'local7']
-    lvl = ['emerg', 'alert', 'crit', 'err',
-           'warning', 'notice', 'info', 'debug', '*']
-
-    for conf in c:
-        if c[conf]:
-            for item in c[conf]:
-                for s in c[conf][item]['selectors'].split(";"):
-                    f = re.sub("\..*$", "", s)
-                    if f not in fac:
-                        raise ConfigError(
-                            'Invalid facility ' + s + ' set in ' + conf + ' ' + item)
-                    l = re.sub("^.+\.", "", s)
-                    if l not in lvl:
-                        raise ConfigError(
-                            'Invalid logging level ' + s + ' set in ' + conf + ' ' + item)
-
-
-def apply(c):
-    if not c:
-        return run('systemctl stop syslog.service')
-    return run('systemctl restart syslog.service')
+    call(f'systemctl reload-or-restart {systemd_service}')
 
 if __name__ == '__main__':
     try:
diff --git a/src/etc/rsyslog.conf b/src/etc/rsyslog.conf
new file mode 100644
index 000000000..706ebb60d
--- /dev/null
+++ b/src/etc/rsyslog.conf
@@ -0,0 +1,61 @@
+#################
+#### MODULES ####
+#################
+
+$ModLoad imuxsock # provides support for local system logging
+$ModLoad imklog   # provides kernel logging support (previously done by rklogd)
+#$ModLoad immark  # provides --MARK-- message capability
+
+$OmitLocalLogging off
+$SystemLogSocketName /run/systemd/journal/syslog
+
+$KLogPath /proc/kmsg
+
+###########################
+#### GLOBAL DIRECTIVES ####
+###########################
+
+# The lines below cause all listed daemons/processes to be logged into
+# /var/log/auth.log, then drops the message so it does not also go to the
+# regular syslog so that messages are not duplicated
+
+$outchannel auth_log,/var/log/auth.log
+if  $programname == 'CRON' or
+    $programname == 'sudo' or
+    $programname == 'su'
+    then :omfile:$auth_log
+
+if $programname == 'CRON' or
+    $programname == 'sudo' or
+    $programname == 'su'
+    then stop
+
+# Use traditional timestamp format.
+# To enable high precision timestamps, comment out the following line.
+# A modern-style logfile format similar to TraditionalFileFormat, buth with high-precision timestamps and timezone information
+#$ActionFileDefaultTemplate RSYSLOG_FileFormat
+# The "old style" default log file format with low-precision timestamps
+$ActionFileDefaultTemplate RSYSLOG_TraditionalFileFormat
+
+# Filter duplicated messages
+$RepeatedMsgReduction on
+
+#
+# Set the default permissions for all log files.
+#
+$FileOwner root
+$FileGroup adm
+$FileCreateMode 0640
+$DirCreateMode 0755
+$Umask 0022
+
+#
+# Include all config files in /etc/rsyslog.d/
+#
+$IncludeConfig /etc/rsyslog.d/*.conf
+
+###############
+#### RULES ####
+###############
+# Emergencies are sent to everybody logged in.
+*.emerg                         :omusrmsg:*
\ No newline at end of file
diff --git a/src/etc/rsyslog.d/01-auth.conf b/src/etc/rsyslog.d/01-auth.conf
deleted file mode 100644
index cc64099d6..000000000
--- a/src/etc/rsyslog.d/01-auth.conf
+++ /dev/null
@@ -1,14 +0,0 @@
-# The lines below cause all listed daemons/processes to be logged into
-# /var/log/auth.log, then drops the message so it does not also go to the
-# regular syslog so that messages are not duplicated
-
-$outchannel auth_log,/var/log/auth.log
-if  $programname == 'CRON' or
-    $programname == 'sudo' or
-    $programname == 'su'
-    then :omfile:$auth_log
-
-if $programname == 'CRON' or
-    $programname == 'sudo' or
-    $programname == 'su'
-    then stop
diff --git a/src/migration-scripts/system/25-to-26 b/src/migration-scripts/system/25-to-26
new file mode 100755
index 000000000..615274430
--- /dev/null
+++ b/src/migration-scripts/system/25-to-26
@@ -0,0 +1,82 @@
+#!/usr/bin/env python3
+#
+# Copyright (C) 2023 VyOS maintainers and contributors
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License version 2 or later as
+# published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# syslog: migrate deprecated CLI options
+#         - protocols -> local7
+#         - security -> auth
+
+from sys import exit, argv
+from vyos.configtree import ConfigTree
+
+if (len(argv) < 1):
+    print("Must specify file name!")
+    exit(1)
+
+file_name = argv[1]
+with open(file_name, 'r') as f:
+    config_file = f.read()
+
+base = ['system', 'syslog']
+config = ConfigTree(config_file)
+
+if not config.exists(base):
+    exit(0)
+
+def rename_facilities(config, base_tree, facility, facility_new) -> None:
+    if config.exists(base + [base_tree, 'facility', facility]):
+        # do not overwrite already existing replacement facility
+        if not config.exists(base + [base_tree, 'facility', facility_new]):
+            config.rename(base + [base_tree, 'facility', facility], facility_new)
+        else:
+            # delete old duplicate facility config
+            config.delete(base + [base_tree, 'facility', facility])
+
+#
+# Rename protocols and securityy facility to common ones
+#
+replace = {
+    'protocols' : 'local7',
+    'security' : 'auth'
+}
+for facility, facility_new in replace.items():
+    rename_facilities(config, 'console', facility, facility_new)
+    rename_facilities(config, 'global', facility, facility_new)
+
+    if config.exists(base + ['host']):
+        for host in config.list_nodes(base + ['host']):
+            rename_facilities(config, f'host {host}', facility, facility_new)
+
+#
+# It makes no sense to configure udp/tcp transport per individual facility
+#
+if config.exists(base + ['host']):
+    for host in config.list_nodes(base + ['host']):
+        protocol = None
+        for facility in config.list_nodes(base + ['host', host, 'facility']):
+            tmp_path = base + ['host', host, 'facility', facility, 'protocol']
+            if config.exists(tmp_path):
+                # We can only change the first one
+                if protocol == None:
+                    protocol = config.return_value(tmp_path)
+                    config.set(base + ['host', host, 'protocol'], value=protocol)
+                config.delete(tmp_path)
+
+try:
+    with open(file_name, 'w') as f:
+        f.write(config.to_string())
+except OSError as e:
+    print(f'Failed to save the modified config: {e}')
+    exit(1)
-- 
cgit v1.2.3


From 46d2bcdb0b500b4d1b9d973ab5b9ca3c6cf44e51 Mon Sep 17 00:00:00 2001
From: Christian Breunig <christian@breunig.cc>
Date: Mon, 8 May 2023 22:34:22 +0200
Subject: syslog: T2769: add VRF support

Allow syslog messages to be sent through a VRF (e.g. management).
---
 data/templates/rsyslog/override.conf.j2    | 11 +++++++++++
 interface-definitions/system-syslog.xml.in |  1 +
 src/conf_mode/system-syslog.py             | 20 +++++++++++++++++++-
 src/etc/rsyslog.conf                       |  6 ++++++
 4 files changed, 37 insertions(+), 1 deletion(-)
 create mode 100644 data/templates/rsyslog/override.conf.j2

diff --git a/data/templates/rsyslog/override.conf.j2 b/data/templates/rsyslog/override.conf.j2
new file mode 100644
index 000000000..5f6a87edf
--- /dev/null
+++ b/data/templates/rsyslog/override.conf.j2
@@ -0,0 +1,11 @@
+{% set vrf_command = 'ip vrf exec ' ~ vrf ~ ' ' if vrf is vyos_defined else '' %}
+[Unit]
+StartLimitIntervalSec=0
+
+[Service]
+ExecStart=
+ExecStart={{ vrf_command }}/usr/sbin/rsyslogd -n -iNONE
+Restart=always
+RestartPreventExitStatus=
+RestartSec=10
+RuntimeDirectoryPreserve=yes
diff --git a/interface-definitions/system-syslog.xml.in b/interface-definitions/system-syslog.xml.in
index 17aa85706..cd5c514a8 100644
--- a/interface-definitions/system-syslog.xml.in
+++ b/interface-definitions/system-syslog.xml.in
@@ -147,6 +147,7 @@
               #include <include/syslog-facility.xml.i>
             </children>
           </node>
+          #include <include/interface/vrf.xml.i>
         </children>
       </node>
     </children>
diff --git a/src/conf_mode/system-syslog.py b/src/conf_mode/system-syslog.py
index dba29d152..e646fb0ae 100755
--- a/src/conf_mode/system-syslog.py
+++ b/src/conf_mode/system-syslog.py
@@ -20,6 +20,8 @@ from sys import exit
 
 from vyos.config import Config
 from vyos.configdict import dict_merge
+from vyos.configdict import is_node_changed
+from vyos.configverify import verify_vrf
 from vyos.util import call
 from vyos.template import render
 from vyos.xml import defaults
@@ -29,6 +31,7 @@ airbag.enable()
 
 rsyslog_conf = '/etc/rsyslog.d/00-vyos.conf'
 logrotate_conf = '/etc/logrotate.d/vyos-rsyslog'
+systemd_override = r'/run/systemd/system/rsyslog.service.d/override.conf'
 
 def get_config(config=None):
     if config:
@@ -43,6 +46,8 @@ def get_config(config=None):
                                   get_first_key=True, no_tag_node_value_mangle=True)
 
     syslog.update({ 'logrotate' : logrotate_conf })
+    tmp = is_node_changed(conf, base + ['vrf'])
+    if tmp: syslog.update({'restart_required': {}})
 
     # We have gathered the dict representation of the CLI, but there are default
     # options which we need to update into the dictionary retrived.
@@ -101,6 +106,8 @@ def verify(syslog):
     if not syslog:
         return None
 
+    verify_vrf(syslog)
+
 def generate(syslog):
     if not syslog:
         if os.path.exists(rsyslog_conf):
@@ -111,15 +118,26 @@ def generate(syslog):
         return None
 
     render(rsyslog_conf, 'rsyslog/rsyslog.conf.j2', syslog)
+    render(systemd_override, 'rsyslog/override.conf.j2', syslog)
     render(logrotate_conf, 'rsyslog/logrotate.j2', syslog)
 
+    # Reload systemd manager configuration
+    call('systemctl daemon-reload')
+    return None
+
 def apply(syslog):
     systemd_service = 'syslog.service'
     if not syslog:
         call(f'systemctl stop {systemd_service}')
         return None
 
-    call(f'systemctl reload-or-restart {systemd_service}')
+    # we need to restart the service if e.g. the VRF name changed
+    systemd_action = 'reload-or-restart'
+    if 'restart_required' in syslog:
+        systemd_action = 'restart'
+
+    call(f'systemctl {systemd_action} {systemd_service}')
+    return None
 
 if __name__ == '__main__':
     try:
diff --git a/src/etc/rsyslog.conf b/src/etc/rsyslog.conf
index 706ebb60d..c28e9b537 100644
--- a/src/etc/rsyslog.conf
+++ b/src/etc/rsyslog.conf
@@ -49,6 +49,12 @@ $FileCreateMode 0640
 $DirCreateMode 0755
 $Umask 0022
 
+#
+# Stop excessive logging of sudo
+#
+:msg, contains, " pam_unix(sudo:session): session opened for user root(uid=0) by" ~
+:msg, contains, "pam_unix(sudo:session): session closed for user root" ~
+
 #
 # Include all config files in /etc/rsyslog.d/
 #
-- 
cgit v1.2.3