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