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