1 #include "server-conf.hpp"
2 
3 #include "utils.hpp"
4 #include "xyz/openbmc_project/Common/error.hpp"
5 
6 #include <fstream>
7 #include <phosphor-logging/elog.hpp>
8 #if __has_include("../../usr/include/phosphor-logging/elog-errors.hpp")
9 #include "../../usr/include/phosphor-logging/elog-errors.hpp"
10 #else
11 #include <phosphor-logging/elog-errors.hpp>
12 #endif
13 #include <arpa/inet.h>
14 #include <netdb.h>
15 
16 #include <optional>
17 #include <string>
18 
19 namespace phosphor
20 {
21 namespace rsyslog_config
22 {
23 
24 namespace utils = phosphor::rsyslog_utils;
25 using namespace phosphor::logging;
26 using namespace sdbusplus::xyz::openbmc_project::Common::Error;
27 
28 namespace internal
29 {
30 
31 bool isIPv6Address(const std::string& addr)
32 {
33     struct in6_addr result;
34     return inet_pton(AF_INET6, addr.c_str(), &result) == 1;
35 }
36 
37 std::optional<std::pair<std::string, uint32_t>> parseConfig(std::istream& ss)
38 {
39     std::string line;
40     std::getline(ss, line);
41 
42     //"*.* @@<address>:<port>" or
43     //"*.* @@[<ipv6-address>:<port>"
44     constexpr auto start = 6; // Skip "*.* @@"
45     std::string serverAddress;
46     std::string serverPort;
47 
48     // Ignore if line is commented
49     if (!line.empty() && '#' != line.at(0))
50     {
51         // Check if there is "[]", and make IPv6 address from it
52         auto posColonLeft = line.find('[');
53         auto posColonRight = line.find(']');
54         if (posColonLeft != std::string::npos ||
55             posColonRight != std::string::npos)
56         {
57             // It contains [ or ], so it should be an IPv6 address
58             if (posColonLeft == std::string::npos ||
59                 posColonRight == std::string::npos)
60             {
61                 // There either '[' or ']', invalid config
62                 return {};
63             }
64             if (line.size() < posColonRight + 2 ||
65                 line.at(posColonRight + 1) != ':')
66             {
67                 // There is no ':', or no more content after ':', invalid config
68                 return {};
69             }
70             serverAddress =
71                 line.substr(posColonLeft + 1, posColonRight - posColonLeft - 1);
72             serverPort = line.substr(posColonRight + 2);
73         }
74         else
75         {
76             auto pos = line.find(':');
77             if (pos == std::string::npos)
78             {
79                 // There is no ':', invalid config
80                 return {};
81             }
82             serverAddress = line.substr(start, pos - start);
83             serverPort = line.substr(pos + 1);
84         }
85     }
86     if (serverAddress.empty() || serverPort.empty())
87     {
88         return {};
89     }
90     try
91     {
92         uint32_t port = std::stoul(serverPort);
93         return std::make_pair(std::move(serverAddress), port);
94     }
95     catch (const std::exception& ex)
96     {
97         log<level::ERR>("Invalid config", entry("ERR=%s", ex.what()));
98         return {};
99     }
100 }
101 
102 } // namespace internal
103 
104 std::string Server::address(std::string value)
105 {
106     using Argument = xyz::openbmc_project::Common::InvalidArgument;
107     std::string result{};
108 
109     try
110     {
111         auto serverAddress = address();
112         if (serverAddress == value)
113         {
114             return serverAddress;
115         }
116 
117         if (!value.empty() && !addressValid(value))
118         {
119             elog<InvalidArgument>(Argument::ARGUMENT_NAME("Address"),
120                                   Argument::ARGUMENT_VALUE(value.c_str()));
121         }
122 
123         writeConfig(value, port(), configFilePath.c_str());
124         result = std::move(NetworkClient::address(value));
125     }
126     catch (const InvalidArgument& e)
127     {
128         throw;
129     }
130     catch (const InternalFailure& e)
131     {
132         throw;
133     }
134     catch (const std::exception& e)
135     {
136         log<level::ERR>(e.what());
137         elog<InternalFailure>();
138     }
139 
140     return result;
141 }
142 
143 uint16_t Server::port(uint16_t value)
144 {
145     uint16_t result{};
146 
147     try
148     {
149         auto serverPort = port();
150         if (serverPort == value)
151         {
152             return serverPort;
153         }
154 
155         writeConfig(address(), value, configFilePath.c_str());
156         result = NetworkClient::port(value);
157     }
158     catch (const InternalFailure& e)
159     {
160         throw;
161     }
162     catch (const std::exception& e)
163     {
164         log<level::ERR>(e.what());
165         elog<InternalFailure>();
166     }
167 
168     return result;
169 }
170 
171 void Server::writeConfig(const std::string& serverAddress, uint16_t serverPort,
172                          const char* filePath)
173 {
174     std::fstream stream(filePath, std::fstream::out);
175 
176     if (serverPort && !serverAddress.empty())
177     {
178         // write '*.* @@<remote-host>:<port>'
179         if (internal::isIPv6Address(serverAddress))
180         {
181             stream << "*.* @@[" << serverAddress << "]:" << serverPort;
182         }
183         else
184         {
185             stream << "*.* @@" << serverAddress << ":" << serverPort;
186         }
187     }
188     else // this is a disable request
189     {
190         // write '*.* ~' - this causes rsyslog to discard all messages
191         stream << "*.* ~";
192     }
193 
194     restart();
195 }
196 
197 bool Server::addressValid(const std::string& address)
198 {
199     addrinfo hints{};
200     addrinfo* res = nullptr;
201     hints.ai_family = AF_UNSPEC;
202     hints.ai_socktype = SOCK_STREAM;
203     hints.ai_flags |= AI_CANONNAME;
204 
205     auto result = getaddrinfo(address.c_str(), nullptr, &hints, &res);
206     if (result)
207     {
208         log<level::ERR>("bad address", entry("ADDRESS=%s", address.c_str()),
209                         entry("ERRNO=%d", result));
210         return false;
211     }
212 
213     freeaddrinfo(res);
214     return true;
215 }
216 
217 void Server::restore(const char* filePath)
218 {
219     std::fstream stream(filePath, std::fstream::in);
220 
221     auto ret = internal::parseConfig(stream);
222     if (ret)
223     {
224         NetworkClient::address(ret->first);
225         NetworkClient::port(ret->second);
226     }
227 }
228 
229 void Server::restart()
230 {
231     utils::restart();
232 }
233 
234 } // namespace rsyslog_config
235 } // namespace phosphor
236