summaryrefslogtreecommitdiff
path: root/snmpcfg
diff options
context:
space:
mode:
authorAndrey V.Kosteltsev <AKosteltsev@IBS.RU>2022-07-15 10:36:51 +0300
committerAndrey V.Kosteltsev <AKosteltsev@IBS.RU>2022-07-15 10:36:51 +0300
commit0f4556fc2343dc0ade0bb1e0d1fc6f85770d77af (patch)
treee26b6cb64f8ac4625c45baa40834fe51dc25c4f8 /snmpcfg
downloadobmc-sila-snmp-0f4556fc2343dc0ade0bb1e0d1fc6f85770d77af.tar.xz
First commit: Sila SNMP Sub Agent and configuration manager
Diffstat (limited to 'snmpcfg')
-rw-r--r--snmpcfg/Makefile.am41
-rw-r--r--snmpcfg/sila-snmp-cfg-manager.service.in13
-rw-r--r--snmpcfg/snmpcfg-server.cpp291
-rw-r--r--snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml13
4 files changed, 358 insertions, 0 deletions
diff --git a/snmpcfg/Makefile.am b/snmpcfg/Makefile.am
new file mode 100644
index 0000000..5741fd0
--- /dev/null
+++ b/snmpcfg/Makefile.am
@@ -0,0 +1,41 @@
+bin_PROGRAMS = sila-snmpcfg
+
+nobase_nodist_include_HEADERS = \
+ xyz/openbmc_project/SNMPCfg/server.hpp
+
+sila_snmpcfg_SOURCES = \
+ snmpcfg-server.cpp \
+ xyz/openbmc_project/SNMPCfg/server.cpp
+
+sila_snmpcfg_CXXFLAGS = \
+ $(SDBUSPLUS_CFLAGS) \
+ $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \
+ $(PHOSPHOR_LOGGING_CFLAGS)
+sila_snmpcfg_LDADD = \
+ $(SDBUSPLUS_LIBS) \
+ $(PHOSPHOR_DBUS_INTERFACES_LIBS) \
+ $(PHOSPHOR_LOGGING_LIBS)
+
+# Be sure to build needed files before compiling
+BUILT_SOURCES = \
+ xyz/openbmc_project/SNMPCfg/server.cpp \
+ xyz/openbmc_project/SNMPCfg/server.hpp
+
+CLEANFILES=${BUILT_SOURCES}
+
+xyz/openbmc_project/SNMPCfg/server.cpp: \
+xyz/openbmc_project/SNMPCfg.interface.yaml \
+xyz/openbmc_project/SNMPCfg/server.hpp
+ @mkdir -p $(@D)
+ $(SDBUSPLUSPLUS) -r $(srcdir) interface server-cpp \
+xyz.openbmc_project.SNMPCfg > $@
+
+xyz/openbmc_project/SNMPCfg/server.hpp: \
+xyz/openbmc_project/SNMPCfg.interface.yaml
+ @mkdir -p $(@D)
+ $(SDBUSPLUSPLUS) -r $(srcdir) interface server-header \
+xyz.openbmc_project.SNMPCfg > $@
+
+if HAVE_SYSTEMD
+systemdsystemunit_DATA = sila-snmp-cfg-manager.service
+endif
diff --git a/snmpcfg/sila-snmp-cfg-manager.service.in b/snmpcfg/sila-snmp-cfg-manager.service.in
new file mode 100644
index 0000000..d80b9a6
--- /dev/null
+++ b/snmpcfg/sila-snmp-cfg-manager.service.in
@@ -0,0 +1,13 @@
+[Unit]
+Description=Sila SNMP configuration manager
+After=snmpd.service
+
+[Service]
+ExecStart=@bindir@/sila-snmpcfg
+SyslogIdentifier=sila-snmpcfg
+Restart=always
+Type=dbus
+BusName=xyz.openbmc_project.SNMPCfg
+
+[Install]
+WantedBy=@SYSTEMD_TARGET@
diff --git a/snmpcfg/snmpcfg-server.cpp b/snmpcfg/snmpcfg-server.cpp
new file mode 100644
index 0000000..da76555
--- /dev/null
+++ b/snmpcfg/snmpcfg-server.cpp
@@ -0,0 +1,291 @@
+/**
+ * @brief SNMP Configuration manager
+ *
+ * This file is part of sila-snmp project.
+ *
+ * Copyright (c) 2022 SILA
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+#include <string>
+#include <fstream>
+#include <streambuf>
+
+#include <sdbusplus/server.hpp>
+#include <xyz/openbmc_project/SNMPCfg/server.hpp>
+#include <phosphor-logging/log.hpp>
+#include <phosphor-logging/elog.hpp>
+#include <phosphor-logging/elog-errors.hpp>
+#include <xyz/openbmc_project/Common/error.hpp>
+
+using namespace phosphor::logging;
+using SNMPCfg_inherit = sdbusplus::server::object_t<
+ sdbusplus::xyz::openbmc_project::server::SNMPCfg>;
+
+using InternalFailure =
+ sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure;
+using InvalidArgument =
+ sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument;
+using Argument = xyz::openbmc_project::Common::InvalidArgument;
+
+static constexpr auto snmpdConf = "/etc/snmp/snmpd.conf";
+static constexpr auto whitespace = " \t";
+
+/**
+ * @brief Check if the string contains the token at the position or after
+ * several spaces.
+ *
+ * @param[in] str - string to be checked
+ * @param[in] token - expected token
+ * @param[in/out] pos - start position for checking, will keep the last
+ * checked position.
+ *
+ * @return - true if token found.
+ */
+static bool getToken(const std::string& str, const std::string& token,
+ size_t& pos)
+{
+ pos = str.find_first_not_of(whitespace, pos);
+ if (pos != std::string::npos && 0 == str.compare(pos, token.size(), token))
+ {
+ auto endPos = str.find_first_of(whitespace, pos);
+ if (endPos == std::string::npos || pos + token.size() == endPos)
+ {
+ pos = endPos;
+ return true;
+ }
+ }
+ return false;
+}
+
+class Configurator : public SNMPCfg_inherit
+{
+ public:
+ /**
+ * @brief Configurator object constructor
+ *
+ * @param bus - DBus connection object reference
+ * @param path - DBus object path
+ */
+ Configurator(sdbusplus::bus::bus& bus, const char* path) :
+ SNMPCfg_inherit(bus, path), bus(bus)
+ {
+ readConfig();
+ }
+
+ std::string community(std::string value) override
+ {
+ static constexpr auto communityMaxLen = 256;
+ static constexpr auto communityMinLen = 1;
+
+ auto communityLen = value.length();
+ if (communityLen < communityMinLen || communityLen > communityMaxLen)
+ {
+ log<level::ERR>("Invalid community name length");
+ elog<InvalidArgument>(Argument::ARGUMENT_NAME("Community"),
+ Argument::ARGUMENT_VALUE(value.c_str()));
+ }
+
+ SNMPCfg_inherit::community(value);
+ writeConfig();
+ return value;
+ }
+
+ private:
+ /**
+ * @brief Parse line and get community name
+ *
+ * @param line - the line from configuration file
+ *
+ * @return community name if corresponding statement found
+ * and nullopt otherwise
+ */
+ std::optional<std::string> getCommunityName(std::string line) const
+ {
+ // Required line should be in format:
+ // 'com2sec readonly default <communityName>'
+ size_t pos = 0;
+
+ for (const auto& token : {"com2sec", "readonly", "default"})
+ {
+ if (!getToken(line, token, pos) || pos == std::string::npos)
+ {
+ return std::nullopt;
+ }
+ }
+
+ pos = line.find_first_not_of(whitespace, pos);
+ if (pos == std::string::npos)
+ {
+ return std::nullopt;
+ }
+
+ auto endPos = line.find_last_not_of(whitespace);
+ if (pos < endPos && line[pos] == '"' && line[endPos] == '"')
+ {
+ pos++;
+ endPos--;
+ }
+
+ return (pos <= endPos ? line.substr(pos, endPos - pos + 1)
+ : std::string{});
+ }
+
+ /**
+ * @brief Read actual settings from configuration file
+ */
+ void readConfig()
+ {
+ std::ifstream fileToRead(snmpdConf, std::ios::in);
+ if (!fileToRead.is_open())
+ {
+ log<level::ERR>("Failed to open SNMP daemon configuration file",
+ entry("FILE_NAME=%s", snmpdConf));
+ return;
+ }
+
+ std::string line;
+ while (std::getline(fileToRead, line))
+ {
+ auto communityName = getCommunityName(line);
+ if (communityName)
+ {
+ SNMPCfg_inherit::community(*communityName);
+ return;
+ }
+ }
+ log<level::ERR>("Community not found",
+ entry("FILE_NAME=%s", snmpdConf));
+ }
+
+ /**
+ * @brief Write actual settings to the configuration file.
+ */
+ void writeConfig()
+ {
+ std::string tmpFileName{snmpdConf};
+ tmpFileName += ".tmp";
+
+ std::ifstream fileToRead(snmpdConf, std::ios::in);
+ std::ofstream fileToWrite(tmpFileName, std::ios::out);
+ if (!fileToRead.is_open())
+ {
+ log<level::ERR>("Failed to open SNMP daemon configuration file",
+ entry("FILE_NAME=%s", snmpdConf));
+ return;
+ }
+
+ if (!fileToWrite.is_open())
+ {
+ log<level::ERR>("Failed to create new configuration file",
+ entry("FILE_NAME=%s", tmpFileName.c_str()));
+ return;
+ }
+
+ auto value = SNMPCfg_inherit::community();
+ bool isQuoteRequired =
+ (value.find_first_of(whitespace) != std::string::npos);
+ bool updated = false;
+ std::string line;
+ while (std::getline(fileToRead, line))
+ {
+ auto communityName = getCommunityName(line);
+ if (communityName && *communityName != value)
+ {
+ fileToWrite << "com2sec readonly default\t "
+ << (isQuoteRequired ? "\"" : "") << value
+ << (isQuoteRequired ? "\"" : "") << std::endl;
+ updated = true;
+ }
+ else
+ {
+ fileToWrite << line << std::endl;
+ }
+ }
+ fileToWrite.close();
+ fileToRead.close();
+
+ if (updated)
+ {
+ if (0 != std::rename(tmpFileName.c_str(), snmpdConf))
+ {
+ int error = errno;
+ log<level::ERR>("Failed to update SNMP daemon configuarion",
+ entry("WHAT=%s", strerror(error)),
+ entry("FILE_NAME=%s", snmpdConf));
+ elog<InternalFailure>();
+ }
+
+ reloadSnmpDaemon();
+ }
+ else
+ {
+ if (0 != std::remove(tmpFileName.c_str()))
+ {
+ int error = errno;
+ log<level::ERR>("Failed to remove temporary file",
+ entry("WHAT=%s", strerror(error)),
+ entry("FILE_NAME=%s", tmpFileName.c_str()));
+ }
+ }
+ }
+
+ sdbusplus::bus::bus& bus;
+
+ /**
+ * @brief Ask SNMP daemon to reread configuration.
+ */
+ void reloadSnmpDaemon()
+ {
+ auto m = bus.new_method_call(
+ "org.freedesktop.systemd1", "/org/freedesktop/systemd1",
+ "org.freedesktop.systemd1.Manager", "ReloadUnit");
+ m.append("snmpd.service", "replace");
+ try
+ {
+ bus.call_noreply(m);
+ }
+ catch (const sdbusplus::exception::SdBusError& e)
+ {
+ log<level::ERR>("Failed to reload SNMP daemon",
+ entry("WHATE=%s", e.what()));
+ elog<InternalFailure>();
+ }
+ }
+};
+
+/**
+ * @brief Application entry point
+ *
+ * @return exit status
+ */
+int main()
+{
+ constexpr auto path = "/xyz/openbmc_project/snmpcfg";
+
+ auto b = sdbusplus::bus::new_default();
+ sdbusplus::server::manager_t m{b, path};
+
+ b.request_name("xyz.openbmc_project.SNMPCfg");
+ Configurator cfg{b, path};
+
+ while (1)
+ {
+ b.process_discard();
+ b.wait();
+ }
+
+ return 0;
+}
diff --git a/snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml b/snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml
new file mode 100644
index 0000000..5017f7b
--- /dev/null
+++ b/snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml
@@ -0,0 +1,13 @@
+description: >
+ SNMP daemon configuration manager.
+
+properties:
+ - name: Community
+ type: string
+ description: >
+ SNMPv1 and SNMPv2 community that allows read-only (GET and GETNEXT) access
+ to the data providing by SNMP daemon.
+ errors:
+ - xyz.openbmc_project.Common.Error.InternalFailure
+ - xyz.openbmc_project.Common.Error.InvalidArgument
+