Line data Source code
1 : // Copyright (c) 2016-2025 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/cluck
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : // self
20 : //
21 : #include "computer.h"
22 :
23 :
24 : // cluck
25 : //
26 : #include <cluck/exception.h>
27 :
28 :
29 : // cppthread
30 : //
31 : #include <cppthread/thread.h>
32 :
33 :
34 : // snaplogger
35 : //
36 : #include <snaplogger/message.h>
37 :
38 :
39 : // libaddr
40 : //
41 : #include <libaddr/addr_parser.h>
42 : #include <libaddr/exception.h>
43 :
44 :
45 : // advgetopt
46 : //
47 : #include <advgetopt/validator_integer.h>
48 :
49 :
50 : // snapdev
51 : //
52 : #include <snapdev/tokenize_string.h>
53 :
54 :
55 : // C++
56 : //
57 : #include <iomanip>
58 :
59 :
60 : // openssl
61 : //
62 : #include <openssl/rand.h>
63 :
64 :
65 : // last include
66 : //
67 : #include <snapdev/poison.h>
68 :
69 :
70 :
71 : namespace cluck_daemon
72 : {
73 :
74 :
75 :
76 110 : computer::computer()
77 : {
78 : // used for a remote computer, we'll eventually get a set_id() which
79 : // defines the necessary computer parameters
80 110 : }
81 :
82 :
83 44 : computer::computer(std::string const & name, priority_t priority, addr::addr ip_address)
84 44 : : f_self(true)
85 44 : , f_priority(priority)
86 44 : , f_ip_address(ip_address)
87 44 : , f_pid(getpid())
88 88 : , f_name(name)
89 : {
90 : // verify the name becaue it can cause issues in the serializer if it
91 : // includes invalid characters are is empty
92 : //
93 44 : if(f_name.empty())
94 : {
95 3 : throw cluck::invalid_parameter("the computer name cannot be an empty string.");
96 : }
97 129 : std::string invalid_name_characters("|");
98 43 : invalid_name_characters += '\0';
99 43 : if(f_name.find_first_of(invalid_name_characters) != std::string::npos)
100 : {
101 6 : throw cluck::invalid_parameter("a computer name cannot include the '|' or null characters.");
102 : }
103 :
104 41 : if(f_priority < PRIORITY_USER_MIN
105 40 : || f_priority > PRIORITY_MAX)
106 : {
107 : throw cluck::invalid_parameter(
108 : "priority is limited to a number between "
109 4 : + std::to_string(static_cast<int>(PRIORITY_USER_MIN))
110 8 : + " and "
111 8 : + std::to_string(static_cast<int>(PRIORITY_MAX))
112 6 : + " inclusive.");
113 : }
114 :
115 39 : RAND_bytes(reinterpret_cast<unsigned char *>(&f_random_id), sizeof(f_random_id));
116 97 : }
117 :
118 :
119 904 : bool computer::is_self() const
120 : {
121 904 : return f_self;
122 : }
123 :
124 :
125 28 : void computer::set_connected(bool connected)
126 : {
127 28 : f_connected = connected;
128 28 : }
129 :
130 :
131 788 : bool computer::get_connected() const
132 : {
133 788 : return f_connected;
134 : }
135 :
136 :
137 : /** \brief Initialize this computer object from \p id.
138 : *
139 : * \note
140 : * At the moment, the function is expected to work each time. This is
141 : * used internally to the cluckd, so the errors are due primarily to
142 : * a spurious message from a hacker. Otherwise, we would have to keep
143 : * all the new parameter on the stack and save them in the object
144 : * fields only if all are considered valid. (i.e. If the input string
145 : * is invalid, the computer object is likely left in an invalid state.)
146 : *
147 : * \exception logic_error
148 : * If the function was already called once successfully, then this
149 : * exception is raised when trying to do call it again.
150 : *
151 : * \return true if the id was considered 100% valid, false otherwise.
152 : */
153 124 : bool computer::set_id(std::string const & id)
154 : {
155 124 : if(f_priority != PRIORITY_UNDEFINED)
156 : {
157 45 : throw cluck::logic_error("computer::set_id() cannot be called more than once.");
158 : }
159 :
160 109 : std::vector<std::string> parts;
161 327 : snapdev::tokenize_string(parts, id, "|");
162 109 : if(parts.size() != 5)
163 : {
164 : // do not throw in case something changes we do not want snaplock to
165 : // "crash" over and over again
166 : //
167 4 : SNAP_LOG_ERROR
168 : << "received a computer id which does not have exactly 5 parts: \""
169 : << id
170 : << "\"."
171 : << SNAP_LOG_SEND;
172 2 : return false;
173 : }
174 :
175 : // base is VERY IMPORTANT for this one as we save priorities below ten
176 : // as 0n (01 to 09) so the sort works as expected
177 : //
178 107 : std::int64_t value(0);
179 107 : bool valid(advgetopt::validator_integer::convert_string(parts[0], value));
180 107 : if(!valid
181 106 : || value < PRIORITY_USER_MIN
182 105 : || value > PRIORITY_MAX)
183 : {
184 9 : SNAP_LOG_ERROR
185 3 : << "priority is limited to a number between "
186 : << static_cast<int>(PRIORITY_USER_MIN)
187 3 : << " and "
188 : << static_cast<int>(PRIORITY_MAX)
189 : << " inclusive."
190 : << SNAP_LOG_SEND;
191 3 : return false;
192 : }
193 104 : f_priority = value;
194 :
195 104 : if(!advgetopt::validator_integer::convert_string(parts[1], value))
196 : {
197 3 : SNAP_LOG_ERROR
198 : << "random value is expected to be a valid integer, not "
199 1 : << parts[1]
200 : << "."
201 : << SNAP_LOG_SEND;
202 1 : return false;
203 : }
204 103 : f_random_id = value;
205 :
206 103 : if(parts[2].empty())
207 : {
208 2 : SNAP_LOG_ERROR
209 : << "the process IP cannot be an empty string."
210 : << SNAP_LOG_SEND;
211 1 : return false;
212 : }
213 : try
214 : {
215 106 : f_ip_address = addr::string_to_addr(parts[2]);
216 101 : if(f_ip_address.is_default())
217 : {
218 2 : SNAP_LOG_ERROR
219 : << "the IP address cannot be the default IP (0.0.0.0)."
220 : << SNAP_LOG_SEND;
221 1 : return false;
222 : }
223 : }
224 1 : catch(addr::addr_invalid_argument const & e)
225 : {
226 : // we want to avoid "crashing" because any hacker could send us an
227 : // invalid message and get the service to stop on this one
228 : //
229 4 : SNAP_LOG_ERROR
230 : << "the process IP, \""
231 1 : << parts[2]
232 : << "\", is not valid: "
233 1 : << e.what()
234 : << SNAP_LOG_SEND;
235 1 : return false;
236 1 : }
237 :
238 100 : valid = advgetopt::validator_integer::convert_string(parts[3], value);
239 200 : if(!valid
240 99 : || value < 1
241 199 : || value > cppthread::get_pid_max())
242 : {
243 12 : SNAP_LOG_ERROR
244 : << "process identifier "
245 4 : << parts[3]
246 4 : << " is invalid ("
247 4 : << std::boolalpha << valid
248 4 : << ") or out of bounds: [1.."
249 : << cppthread::get_pid_max()
250 : << "]."
251 : << SNAP_LOG_SEND;
252 4 : return false;
253 : }
254 96 : f_pid = value;
255 :
256 96 : if(parts[4].empty())
257 : {
258 2 : SNAP_LOG_ERROR
259 : << "the server name in the lock identifier cannot be empty."
260 : << SNAP_LOG_SEND;
261 1 : return false;
262 : }
263 95 : f_name = parts[4];
264 :
265 95 : f_id = id;
266 :
267 95 : return true;
268 109 : }
269 :
270 :
271 125 : computer::priority_t computer::get_priority() const
272 : {
273 125 : return f_priority;
274 : }
275 :
276 :
277 75 : void computer::set_start_time(snapdev::timespec_ex const & start_time)
278 : {
279 75 : f_start_time = start_time;
280 75 : }
281 :
282 :
283 50 : snapdev::timespec_ex const & computer::get_start_time() const
284 : {
285 50 : return f_start_time;
286 : }
287 :
288 :
289 1138 : std::string const & computer::get_name() const
290 : {
291 1138 : return f_name;
292 : }
293 :
294 :
295 2515 : std::string const & computer::get_id() const
296 : {
297 2515 : if(f_id.empty())
298 : {
299 42 : if(f_priority == PRIORITY_UNDEFINED)
300 : {
301 3 : throw cluck::invalid_parameter("computer::get_id() can't be called when the priority is not defined.");
302 : }
303 41 : if(f_ip_address.is_default())
304 : {
305 3 : throw cluck::invalid_parameter("computer::get_id() can't be called when the address is the default address.");
306 : }
307 40 : if(f_pid == 0)
308 : {
309 3 : throw cluck::invalid_parameter("computer::get_id() can't be called when the pid is not defined.");
310 : }
311 :
312 39 : std::stringstream ss;
313 : ss
314 39 : << std::setfill('0') << std::setw(2) << static_cast<int>(f_priority)
315 39 : << '|'
316 39 : << f_random_id
317 : << '|'
318 78 : << f_ip_address.to_ipv4or6_string(addr::STRING_IP_ADDRESS | addr::STRING_IP_BRACKET_ADDRESS)
319 78 : << '|'
320 39 : << f_pid
321 : << '|'
322 78 : << f_name;
323 39 : f_id = ss.str();
324 39 : }
325 :
326 2512 : return f_id;
327 : }
328 :
329 :
330 231 : addr::addr const & computer::get_ip_address() const
331 : {
332 231 : return f_ip_address;
333 : }
334 :
335 :
336 : } // namespace cluck_daemon
337 : // vim: ts=4 sw=4 et
|