1 /*****************************************************************************
2 * Contributed by Levi Pearson <lpearson@lnxi.com>.
3 *
4 * Written by Chris Dunlap <cdunlap@llnl.gov>.
5 * Copyright (C) 2007-2018 Lawrence Livermore National Security, LLC.
6 * Copyright (C) 2001-2007 The Regents of the University of California.
7 * UCRL-CODE-2002-009.
8 *
9 * This file is part of ConMan: The Console Manager.
10 * For details, see <https://dun.github.io/conman/>.
11 *
12 * ConMan is free software: you can redistribute it and/or modify it under
13 * the terms of the GNU General Public License as published by the Free
14 * Software Foundation, either version 3 of the License, or (at your option)
15 * any later version.
16 *
17 * ConMan is distributed in the hope that it will be useful, but WITHOUT
18 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
19 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
20 * for more details.
21 *
22 * You should have received a copy of the GNU General Public License along
23 * with ConMan. If not, see <http://www.gnu.org/licenses/>.
24 *****************************************************************************/
25
26
27 #if HAVE_CONFIG_H
28 # include <config.h>
29 #endif /* HAVE_CONFIG_H */
30
31 #if HAVE_IPMICONSOLE_H
32 # include <ipmiconsole.h>
33 #endif /* HAVE_IPMICONSOLE_H */
34
35 #include <assert.h>
36 #include <ctype.h>
37 #include <errno.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <unistd.h>
42 #include "common.h"
43 #include "list.h"
44 #include "log.h"
45 #include "server.h"
46 #include "tpoll.h"
47 #include "util.h"
48 #include "util-file.h"
49 #include "util-str.h"
50 #include "wrapper.h"
51
52
53 static int parse_ipmi_opts_v1(
54 ipmiopt_t *iopts, char *str, char *errbuf, int errlen);
55 static int process_ipmi_opt(
56 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen);
57 static int is_ipmi_opt_tag(const char *str);
58 static int process_ipmi_opt_username(
59 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen);
60 static int process_ipmi_opt_password(
61 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen);
62 static int process_ipmi_opt_k_g(
63 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen);
64 static int process_ipmi_opt_privilege(
65 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen);
66 static int process_ipmi_opt_cipher(
67 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen);
68 static int process_ipmi_opt_workaround(
69 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen);
70 static int parse_key(char *dst, const char *src, size_t dstlen);
71 static void disconnect_ipmi_obj(obj_t *ipmi);
72 static int connect_ipmi_obj(obj_t *ipmi);
73 static int initiate_ipmi_connect(obj_t *ipmi);
74 static int create_ipmi_ctx(obj_t *ipmi);
75 static int complete_ipmi_connect(obj_t *ipmi);
76 static void fail_ipmi_connect(obj_t *ipmi);
77 static void reset_ipmi_delay(obj_t *ipmi);
78
79 extern tpoll_t tp_global; /* defined in server.c */
80 static int is_ipmi_engine_started = 0;
81
82
ipmi_init(int num_consoles)83 void ipmi_init(int num_consoles)
84 {
85 /* Starts the ipmiconsole engine to handle 'num_consoles' IPMI SOL consoles.
86 */
87 int num_threads;
88
89 if (num_consoles <= 0) {
90 return;
91 }
92 if (is_ipmi_engine_started) {
93 return;
94 }
95 num_threads = ((num_consoles - 1) / IPMI_ENGINE_CONSOLES_PER_THREAD) + 1;
96 num_threads = MIN(num_threads, IPMICONSOLE_THREAD_COUNT_MAX);
97
98 if (ipmiconsole_engine_init(num_threads, 0) < 0) {
99 log_err(0, "Unable to start IPMI SOL engine");
100 }
101 else {
102 log_msg(LOG_INFO,
103 "IPMI SOL engine started with %d thread%s for %d console%s",
104 num_threads, (num_threads == 1) ? "" : "s",
105 num_consoles, (num_consoles == 1) ? "" : "s");
106 }
107 is_ipmi_engine_started = 1;
108 return;
109 }
110
111
ipmi_fini(void)112 void ipmi_fini(void)
113 {
114 /* Stops the ipmiconsole engine.
115 */
116 /* Setting do_sol_session_cleanup to nonzero will cause
117 * ipmiconsole_engine_teardown() to block until all active
118 * IPMI SOL sessions have been cleanly closed or timed-out.
119 */
120 int do_sol_session_cleanup = 1;
121
122 if (!is_ipmi_engine_started) {
123 return;
124 }
125 ipmiconsole_engine_teardown(do_sol_session_cleanup);
126 is_ipmi_engine_started = 0;
127 return;
128 }
129
130
is_ipmi_dev(const char * dev,char ** host_ref)131 int is_ipmi_dev(const char *dev, char **host_ref)
132 {
133 /* Returns 1 if 'dev' appears to be a valid IPMI device name
134 * (ie, contains the "ipmi" tag), storing a new string containing
135 * the hostname in the reference parm 'host_ref'; o/w, returns 0.
136 */
137 const char * const prefix = "ipmi:";
138
139 if (dev == NULL) {
140 return(0);
141 }
142 if (strncasecmp(dev, prefix, strlen(prefix)) != 0) {
143 return(0);
144 }
145 dev += strlen(prefix);
146 if (dev[0] == '\0') {
147 return(0);
148 }
149 if (host_ref) {
150 *host_ref = create_string(dev);
151 }
152 return(1);
153 }
154
155
init_ipmi_opts(ipmiopt_t * iopts)156 int init_ipmi_opts(ipmiopt_t *iopts)
157 {
158 /* Initializes 'iopts' to the default values.
159 * Returns 0 on success, -1 on error.
160 */
161 if (iopts == NULL) {
162 return(-1);
163 }
164 memset(iopts, 0, sizeof(ipmiopt_t));
165 iopts->privilegeLevel = -1;
166 iopts->cipherSuite = -1;
167 iopts->workaroundFlags = IPMICONSOLE_WORKAROUND_DEFAULT;
168 return(0);
169 }
170
171
parse_ipmi_opts(ipmiopt_t * iopts,const char * str,char * errbuf,int errlen)172 int parse_ipmi_opts(
173 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen)
174 {
175 /* Parses string 'str' for IPMI device options 'iopts'.
176 * The string 'str' is broken up into comma-delimited tokens; as such,
177 * token values for a given IPMI device option cannot contain commas.
178 * The 'iopts' should be initialized to a default value beforehand.
179 * Returns 0 and updates the 'iopts' struct on success; o/w, returns -1
180 * (writing an error message into buffer 'errbuf' of length 'errlen').
181 */
182 ipmiopt_t ioptsTmp;
183 char buf[MAX_LINE];
184 char *tok;
185 const char * const separators = ",";
186
187 if (iopts == NULL) {
188 log_err(0, "parse_ipmi_opts: iopts ptr is NULL");
189 }
190 ioptsTmp = *iopts;
191
192 if (strlcpy(buf, str, sizeof(buf)) >= sizeof(buf)) {
193 if ((errbuf != NULL) && (errlen > 0)) {
194 snprintf(errbuf, errlen,
195 "ipmiopts string exceeds %lu-byte maximum",
196 (unsigned long) sizeof(buf) - 1);
197 }
198 return(-1);
199 }
200 /* Support previous ipmiopts format for backwards-compatibility.
201 * This behavior is considered deprecated and may be removed at any time.
202 */
203 if ((buf[0] != '\0') && (buf[1] != ':')) {
204 if (parse_ipmi_opts_v1(&ioptsTmp, buf, errbuf, errlen) < 0) {
205 return(-1);
206 }
207 }
208 else {
209 tok = strtok(buf, separators);
210 while (tok != NULL) {
211 if (process_ipmi_opt(&ioptsTmp, tok, errbuf, errlen) < 0) {
212 return(-1);
213 }
214 tok = strtok(NULL, separators);
215 }
216 }
217 *iopts = ioptsTmp;
218 return(0);
219 }
220
221
parse_ipmi_opts_v1(ipmiopt_t * iopts,char * str,char * errbuf,int errlen)222 static int parse_ipmi_opts_v1(
223 ipmiopt_t *iopts, char *str, char *errbuf, int errlen)
224 {
225 /* Parses/modifies string 'str' for IPMI device options.
226 * The string 'str' is of the form "[<user>[,<pswd[,<K_g>[,<w-flag>]*]]]".
227 * An empty 'str' is valid and denotes specifying the default behavior.
228 * A "-" may be used to specify the IPMI default for a given option.
229 * Returns 0 and updates the 'iopts' struct on success; o/w, returns -1
230 * (writing an error message into buffer 'errbuf' of length 'errlen').
231 * This behavior is considered deprecated and may be removed at any time.
232 */
233 char *tok;
234 const char * const separators = ",";
235
236 assert(iopts != NULL);
237 assert(str != NULL);
238
239 if ((tok = strtok(str, separators))) {
240 if ((tok[0] == '-') && (tok[1] == '\0')) {
241 tok++;
242 }
243 if (process_ipmi_opt_username(iopts, tok, errbuf, errlen) < 0) {
244 return(-1);
245 }
246 }
247 if ((tok = strtok(NULL, separators))) {
248 if ((tok[0] == '-') && (tok[1] == '\0')) {
249 tok++;
250 }
251 if (process_ipmi_opt_password(iopts, tok, errbuf, errlen) < 0) {
252 return(-1);
253 }
254 }
255 if ((tok = strtok(NULL, separators))) {
256 if ((tok[0] == '-') && (tok[1] == '\0')) {
257 tok++;
258 }
259 if (process_ipmi_opt_k_g(iopts, tok, errbuf, errlen) < 0) {
260 return(-1);
261 }
262 }
263 while ((tok = strtok(NULL, separators))) {
264 if ((tok[0] == '-') && (tok[1] == '\0')) {
265 tok++;
266 }
267 if (process_ipmi_opt_workaround(iopts, tok, errbuf, errlen) < 0) {
268 return(-1);
269 }
270 }
271 return(0);
272 }
273
274
process_ipmi_opt(ipmiopt_t * iopts,const char * str,char * errbuf,int errlen)275 static int process_ipmi_opt(
276 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen)
277 {
278 /* Parses string 'str' for a single IPMI device option.
279 * The string 'str' is of the form "X:VALUE", where "X" is a single-char key
280 * tag specifying the option type and "VALUE" is its corresponding value.
281 * Returns 0 and updates the 'iopts' struct on success; o/w, returns -1
282 * (writing an error message into buffer 'errbuf' of length 'errlen').
283 */
284 char c;
285 const char *p;
286 int rv = -1;
287
288 assert(iopts != NULL);
289 assert(str != NULL);
290
291 if (!is_ipmi_opt_tag(str)) {
292 if ((errbuf != NULL) && (errlen > 0)) {
293 snprintf(errbuf, errlen, "invalid ipmiopts string \"%s\"", str);
294 }
295 return(-1);
296 }
297 c = toupper((int) str[0]);
298 p = str + 2;
299 switch (c) {
300 case 'U':
301 rv = process_ipmi_opt_username(iopts, p, errbuf, errlen);
302 break;
303 case 'P':
304 rv = process_ipmi_opt_password(iopts, p, errbuf, errlen);
305 break;
306 case 'K':
307 rv = process_ipmi_opt_k_g(iopts, p, errbuf, errlen);
308 break;
309 case 'L':
310 rv = process_ipmi_opt_privilege(iopts, p, errbuf, errlen);
311 break;
312 case 'C':
313 rv = process_ipmi_opt_cipher(iopts, p, errbuf, errlen);
314 break;
315 case 'W':
316 rv = process_ipmi_opt_workaround(iopts, p, errbuf, errlen);
317 break;
318 default:
319 /* This case should never happen since is_ipmi_opt_tag() above
320 * has already validated the option tag.
321 */
322 log_err(0, "invalid ipmiopts tag '%c'", c);
323 break;
324 }
325 return((rv == 0) ? 0 : -1);
326 }
327
328
is_ipmi_opt_tag(const char * str)329 static int is_ipmi_opt_tag(const char *str)
330 {
331 /* Returns 1 if string 'str' is a recognized ipmiopts tag; o/w, returns 0.
332 */
333 if ((str == NULL) || (str[0] == '\0') || (str[1] != ':')) {
334 return(0);
335 }
336 switch (toupper((int) str[0])) {
337 case 'U': case 'P': case 'K': case 'L': case 'C': case 'W':
338 return(1);
339 }
340 return(0);
341 }
342
343
process_ipmi_opt_username(ipmiopt_t * iopts,const char * str,char * errbuf,int errlen)344 static int process_ipmi_opt_username(
345 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen)
346 {
347 /* Parses string 'str' for the IPMI device username.
348 * If the option value is the empty string, the IPMI default will be used.
349 * Returns 0 and updates the 'iopts' struct on success; o/w, returns -1
350 * (writing an error message into buffer 'errbuf' of length 'errlen').
351 */
352 assert(iopts != NULL);
353 assert(str != NULL);
354
355 if (str[0] == '\0') {
356 iopts->username[0] = '\0';
357 }
358 else {
359 int n;
360
361 n = strlcpy(iopts->username, str, sizeof(iopts->username));
362
363 if ((size_t) n >= sizeof(iopts->username)) {
364 if ((errbuf != NULL) && (errlen > 0)) {
365 snprintf(errbuf, errlen,
366 "IPMI username exceeds %d-byte maximum",
367 IPMI_MAX_USER_LEN);
368 }
369 return(-1);
370 }
371 if (!ipmiconsole_username_is_valid(iopts->username)) {
372 if ((errbuf != NULL) && (errlen > 0)) {
373 snprintf(errbuf, errlen, "invalid IPMI username");
374 }
375 return(-1);
376 }
377 }
378 return(0);
379 }
380
381
process_ipmi_opt_password(ipmiopt_t * iopts,const char * str,char * errbuf,int errlen)382 static int process_ipmi_opt_password(
383 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen)
384 {
385 /* Parses string 'str' for the IPMI device password.
386 * If the option value is the empty string, the IPMI default will be used.
387 * Returns 0 and updates the 'iopts' struct on success; o/w, returns -1
388 * (writing an error message into buffer 'errbuf' of length 'errlen').
389 */
390 assert(iopts != NULL);
391 assert(str != NULL);
392
393 if (str[0] == '\0') {
394 iopts->password[0] = '\0';
395 }
396 else {
397 int n;
398
399 n = parse_key(iopts->password, str, sizeof(iopts->password));
400
401 if (n < 0) {
402 if ((errbuf != NULL) && (errlen > 0)) {
403 snprintf(errbuf, errlen,
404 "IPMI password exceeds %d-byte maximum",
405 IPMI_MAX_PSWD_LEN);
406 }
407 return(-1);
408 }
409 if (!ipmiconsole_password_is_valid(iopts->password)) {
410 if ((errbuf != NULL) && (errlen > 0)) {
411 snprintf(errbuf, errlen, "invalid IPMI password");
412 }
413 return(-1);
414 }
415 }
416 return(0);
417 }
418
419
process_ipmi_opt_k_g(ipmiopt_t * iopts,const char * str,char * errbuf,int errlen)420 static int process_ipmi_opt_k_g(
421 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen)
422 {
423 /* Parses string 'str' for the IPMI device K_g key.
424 * If the option value is the empty string, the IPMI default will be used.
425 * Returns 0 and updates the 'iopts' struct on success; o/w, returns -1
426 * (writing an error message into buffer 'errbuf' of length 'errlen').
427 */
428 assert(iopts != NULL);
429 assert(str != NULL);
430
431 if (str[0] == '\0') {
432 iopts->kg[0] = '\0';
433 iopts->kgLen = 0;
434 }
435 else {
436 int n;
437
438 n = parse_key((char *) iopts->kg, str, sizeof(iopts->kg));
439
440 if (n < 0) {
441 if ((errbuf != NULL) && (errlen > 0)) {
442 snprintf(errbuf, errlen,
443 "IPMI K_g exceeds %d-byte maximum",
444 IPMI_MAX_KG_LEN);
445 }
446 return(-1);
447 }
448 if (!ipmiconsole_k_g_is_valid(iopts->kg, n)) {
449 if ((errbuf != NULL) && (errlen > 0)) {
450 snprintf(errbuf, errlen, "invalid IPMI K_g");
451 }
452 return(-1);
453 }
454 iopts->kgLen = n;
455 }
456 return(0);
457 }
458
459
process_ipmi_opt_privilege(ipmiopt_t * iopts,const char * str,char * errbuf,int errlen)460 static int process_ipmi_opt_privilege(
461 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen)
462 {
463 /* Parses string 'str' for the IPMI device privilege level.
464 * If the option value is the empty string, the IPMI default will be used.
465 * Returns 0 and updates the 'iopts' struct on success; o/w, returns -1
466 * (writing an error message into buffer 'errbuf' of length 'errlen').
467 */
468 assert(iopts != NULL);
469 assert(str != NULL);
470
471 if (str[0] == '\0') {
472 iopts->privilegeLevel = -1;
473 }
474 else if (!strcasecmp(str, "user")) {
475 iopts->privilegeLevel = IPMICONSOLE_PRIVILEGE_USER;
476 }
477 else if (!strcasecmp(str, "op") || !strcasecmp(str, "operator")) {
478 iopts->privilegeLevel = IPMICONSOLE_PRIVILEGE_OPERATOR;
479 }
480 else if (!strcasecmp(str, "admin") || !strcasecmp(str, "administrator")) {
481 iopts->privilegeLevel = IPMICONSOLE_PRIVILEGE_ADMIN;
482 }
483 else {
484 long int n;
485 char *p;
486
487 errno = 0;
488 n = strtol(str, &p, 0);
489
490 if ((*p != '\0') || (errno == ERANGE)) {
491 if ((errbuf != NULL) && (errlen > 0)) {
492 snprintf(errbuf, errlen,
493 "invalid IPMI privilege level \"%s\"", str);
494 }
495 return(-1);
496 }
497 if (!ipmiconsole_privilege_level_is_valid(n)) {
498 if ((errbuf != NULL) && (errlen > 0)) {
499 snprintf(errbuf, errlen,
500 "invalid IPMI privilege level %ld", n);
501 }
502 return(-1);
503 }
504 iopts->privilegeLevel = n;
505 }
506 return(0);
507 }
508
509
process_ipmi_opt_cipher(ipmiopt_t * iopts,const char * str,char * errbuf,int errlen)510 static int process_ipmi_opt_cipher(
511 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen)
512 {
513 /* Parses string 'str' for the IPMI device cipher suite.
514 * If the option value is the empty string, the IPMI default will be used.
515 * Returns 0 and updates the 'iopts' struct on success; o/w, returns -1
516 * (writing an error message into buffer 'errbuf' of length 'errlen').
517 */
518 assert(iopts != NULL);
519 assert(str != NULL);
520
521 if (str[0] == '\0') {
522 iopts->cipherSuite = -1;
523 }
524 else {
525 long int n;
526 char *p;
527
528 errno = 0;
529 n = strtol(str, &p, 0);
530
531 if ((*p != '\0') || (errno == ERANGE)) {
532 if ((errbuf != NULL) && (errlen > 0)) {
533 snprintf(errbuf, errlen,
534 "invalid IPMI cipher suite \"%s\"", str);
535 }
536 return(-1);
537 }
538 if (!ipmiconsole_cipher_suite_id_is_valid(n)) {
539 if ((errbuf != NULL) && (errlen > 0)) {
540 snprintf(errbuf, errlen,
541 "invalid IPMI cipher suite %ld", n);
542 }
543 return(-1);
544 }
545 iopts->cipherSuite = n;
546 }
547 return(0);
548 }
549
550
process_ipmi_opt_workaround(ipmiopt_t * iopts,const char * str,char * errbuf,int errlen)551 static int process_ipmi_opt_workaround(
552 ipmiopt_t *iopts, const char *str, char *errbuf, int errlen)
553 {
554 /* Parses string 'str' for the IPMI device workaround flag.
555 * If the option value is the empty string, the IPMI default will be used.
556 * Returns 0 and updates the 'iopts' struct on success; o/w, returns -1
557 * (writing an error message into buffer 'errbuf' of length 'errlen').
558 */
559 unsigned int flag;
560
561 assert(iopts != NULL);
562 assert(str != NULL);
563
564 if ((str[0] == '\0') || !strcasecmp(str, "default")) {
565 flag = IPMICONSOLE_WORKAROUND_DEFAULT;
566 }
567 else if (!strcasecmp(str, "authcap")) {
568 flag = IPMICONSOLE_WORKAROUND_AUTHENTICATION_CAPABILITIES;
569 }
570 else if (!strcasecmp(str, "intel20")) {
571 flag = IPMICONSOLE_WORKAROUND_INTEL_2_0_SESSION;
572 }
573 else if (!strcasecmp(str, "supermicro20")) {
574 flag = IPMICONSOLE_WORKAROUND_SUPERMICRO_2_0_SESSION;
575 }
576 else if (!strcasecmp(str, "sun20")) {
577 flag = IPMICONSOLE_WORKAROUND_SUN_2_0_SESSION;
578 }
579 else if (!strcasecmp(str, "opensesspriv")) {
580 flag = IPMICONSOLE_WORKAROUND_OPEN_SESSION_PRIVILEGE;
581 }
582 else if (!strcasecmp(str, "integritycheckvalue")) {
583 flag = IPMICONSOLE_WORKAROUND_NON_EMPTY_INTEGRITY_CHECK_VALUE;
584 }
585 #ifdef IPMICONSOLE_WORKAROUND_NO_CHECKSUM_CHECK
586 else if (!strcasecmp(str, "nochecksumcheck")) {
587 flag = IPMICONSOLE_WORKAROUND_NO_CHECKSUM_CHECK;
588 }
589 #endif /* IPMICONSOLE_WORKAROUND_NO_CHECKSUM_CHECK */
590 #ifdef IPMICONSOLE_WORKAROUND_SERIAL_ALERTS_DEFERRED
591 else if (!strcasecmp(str, "serialalertsdeferred")) {
592 flag = IPMICONSOLE_WORKAROUND_SERIAL_ALERTS_DEFERRED;
593 }
594 #endif /* IPMICONSOLE_WORKAROUND_SERIAL_ALERTS_DEFERRED */
595 #ifdef IPMICONSOLE_WORKAROUND_INCREMENT_SOL_PACKET_SEQUENCE
596 else if (!strcasecmp(str, "solpacketseq")) {
597 flag = IPMICONSOLE_WORKAROUND_INCREMENT_SOL_PACKET_SEQUENCE;
598 }
599 #endif /* IPMICONSOLE_WORKAROUND_INCREMENT_SOL_PACKET_SEQUENCE */
600 else if (!strcasecmp(str, "solpayloadsize")) {
601 flag = IPMICONSOLE_WORKAROUND_IGNORE_SOL_PAYLOAD_SIZE;
602 }
603 else if (!strcasecmp(str, "solport")) {
604 flag = IPMICONSOLE_WORKAROUND_IGNORE_SOL_PORT;
605 }
606 else if (!strcasecmp(str, "solstatus")) {
607 flag = IPMICONSOLE_WORKAROUND_SKIP_SOL_ACTIVATION_STATUS;
608 }
609 #ifdef IPMICONSOLE_WORKAROUND_SKIP_CHANNEL_PAYLOAD_SUPPORT
610 else if (!strcasecmp(str, "solchannelsupport")) {
611 flag = IPMICONSOLE_WORKAROUND_SKIP_CHANNEL_PAYLOAD_SUPPORT;
612 }
613 #endif /* IPMICONSOLE_WORKAROUND_SKIP_CHANNEL_PAYLOAD_SUPPORT */
614 else if (!strcasecmp(str, "none")) {
615 flag = 0;
616 }
617 else {
618 unsigned int u;
619 char *p;
620
621 errno = 0;
622 u = strtoul(str, &p, 0);
623
624 if ((*p != '\0') || (errno == ERANGE)) {
625 if ((errbuf != NULL) && (errlen > 0)) {
626 snprintf(errbuf, errlen,
627 "invalid IPMI workaround flag \"%s\"", str);
628 }
629 return(-1);
630 }
631 else if (!ipmiconsole_workaround_flags_is_valid(u)) {
632 if ((errbuf != NULL) && (errlen > 0)) {
633 snprintf(errbuf, errlen,
634 "invalid IPMI workaround flag 0x%X", u);
635 }
636 return(-1);
637 }
638 else {
639 flag = u;
640 }
641 }
642
643 if ((flag == 0) ||
644 (flag == IPMICONSOLE_WORKAROUND_DEFAULT) ||
645 (iopts->workaroundFlags == IPMICONSOLE_WORKAROUND_DEFAULT)) {
646 iopts->workaroundFlags = flag;
647 }
648 else {
649 iopts->workaroundFlags |= flag;
650 }
651 return(0);
652 }
653
654
parse_key(char * dst,const char * src,size_t dstlen)655 static int parse_key(char *dst, const char *src, size_t dstlen)
656 {
657 /* Parses the NUL-terminated key string 'src', writing the result into buffer
658 * 'dst' of length 'dstlen'. The 'dst' buffer will be NUL-terminated if
659 * 'dstlen' > 0.
660 * The 'src' is interpreted as ASCII text unless it is prefixed with
661 * "0x" or "0X" and contains only hexadecimal digits (ie, [0-9A-Fa-f]).
662 * A hexadecimal string will be converted to binary and may contain
663 * embedded NUL characters.
664 * Returns the length of the key (in bytes) written to 'dst'
665 * (not including the terminating null character),
666 * or -1 if truncation occurred.
667 */
668 const char *hexdigits = "0123456789ABCDEFabcdef";
669 char *dstend;
670 char *p;
671 char *q;
672 int n;
673
674 assert(dst != NULL);
675 assert(src != NULL);
676
677 if (dstlen == 0) {
678 return(-1);
679 }
680 if ((src[0] == '0') && (src[1] == 'x' || src[1] == 'X')
681 && (strspn(src + 2, hexdigits) == strlen(src + 2))) {
682 dstend = dst + dstlen - 1; /* reserve space for terminating NUL */
683 p = (char *) src + 2;
684 q = dst;
685 n = 0;
686 while (*p && (q < dstend)) {
687 if (((p - src) & 0x01) == 0) {
688 *q = (toint(*p++) << 4) & 0xf0;
689 n++;
690 }
691 else {
692 *q++ |= (toint(*p++)) & 0x0f;
693 }
694 }
695 dst[n] = '\0';
696 if (*p) {
697 return(-1);
698 }
699 }
700 else {
701 n = strlcpy(dst, src, dstlen);
702 if ((size_t) n >= dstlen) {
703 return(-1);
704 }
705 }
706 assert((n >= 0) && ((size_t) n < dstlen));
707 return(n);
708 }
709
710
create_ipmi_obj(server_conf_t * conf,char * name,ipmiopt_t * iconf,char * host,char * errbuf,int errlen)711 obj_t * create_ipmi_obj(server_conf_t *conf, char *name,
712 ipmiopt_t *iconf, char *host, char *errbuf, int errlen)
713 {
714 /* Creates a new IPMI device object and adds it to the master objs list.
715 * Returns the new object, or NULL on error.
716 */
717 ListIterator i;
718 obj_t *ipmi;
719
720 assert(conf != NULL);
721 assert((name != NULL) && (name[0] != '\0'));
722 assert(iconf != NULL);
723
724 /* Check for duplicate console names.
725 */
726 i = list_iterator_create(conf->objs);
727 while ((ipmi = list_next(i))) {
728 if (is_console_obj(ipmi) && !strcmp(ipmi->name, name)) {
729 if ((errbuf != NULL) && (errlen > 0)) {
730 snprintf(errbuf, errlen,
731 "console [%s] specifies duplicate console name", name);
732 }
733 break;
734 }
735 if (is_ipmi_obj(ipmi) && !strcmp(ipmi->aux.ipmi.host, host)) {
736 if ((errbuf != NULL) && (errlen > 0)) {
737 snprintf(errbuf, errlen,
738 "console [%s] specifies duplicate hostname \"%s\"",
739 name, host);
740 }
741 break;
742 }
743 }
744 list_iterator_destroy(i);
745 if (ipmi != NULL) {
746 return(NULL);
747 }
748 ipmi = create_obj(conf, name, -1, CONMAN_OBJ_IPMI);
749 ipmi->aux.ipmi.host = create_string(host);
750 ipmi->aux.ipmi.iconf = *iconf;
751 ipmi->aux.ipmi.ctx = NULL;
752 ipmi->aux.ipmi.logfile = NULL;
753 ipmi->aux.ipmi.state = CONMAN_IPMI_DOWN;
754 ipmi->aux.ipmi.timer = -1;
755 ipmi->aux.ipmi.delay = IPMI_MIN_TIMEOUT;
756 x_pthread_mutex_init(&ipmi->aux.ipmi.mutex, NULL);
757 conf->numIpmiObjs++;
758 /*
759 * Add obj to the master conf->objs list.
760 */
761 list_append(conf->objs, ipmi);
762
763 DPRINTF((10,
764 " IPMI [%s] H:%s U:%s P:%s K:%s L:%d C:%d W:0x%X\n",
765 ipmi->name, ipmi->aux.ipmi.host, ipmi->aux.ipmi.iconf.username,
766 ipmi->aux.ipmi.iconf.password, ipmi->aux.ipmi.iconf.kg,
767 ipmi->aux.ipmi.iconf.privilegeLevel, ipmi->aux.ipmi.iconf.cipherSuite,
768 ipmi->aux.ipmi.iconf.workaroundFlags));
769 return(ipmi);
770 }
771
772
open_ipmi_obj(obj_t * ipmi)773 int open_ipmi_obj(obj_t *ipmi)
774 {
775 /* (Re)opens the specified 'ipmi' obj.
776 * A no-op is performed if the connection is already in the pending state.
777 * Returns 0 if the IPMI console is successfully opened; o/w, returns -1.
778 */
779 int rc = 0;
780 ipmi_state_t state;
781
782 assert(ipmi != NULL);
783 assert(is_ipmi_obj(ipmi));
784
785 x_pthread_mutex_lock(&ipmi->aux.ipmi.mutex);
786 state = ipmi->aux.ipmi.state;
787 x_pthread_mutex_unlock(&ipmi->aux.ipmi.mutex);
788
789 if (state == CONMAN_IPMI_UP) {
790 disconnect_ipmi_obj(ipmi);
791 rc = connect_ipmi_obj(ipmi);
792 }
793 else if (state == CONMAN_IPMI_DOWN) {
794 rc = connect_ipmi_obj(ipmi);
795 }
796 DPRINTF((9, "Opened [%s] via IPMI: fd=%d host=%s state=%d.\n",
797 ipmi->name, ipmi->fd, ipmi->aux.ipmi.host,
798 (int) ipmi->aux.ipmi.state));
799 return(rc);
800 }
801
802
disconnect_ipmi_obj(obj_t * ipmi)803 static void disconnect_ipmi_obj(obj_t *ipmi)
804 {
805 /* Closes the existing connection with the specified 'ipmi' obj.
806 */
807 DPRINTF((10, "Disconnecting from <%s> via IPMI for [%s].\n",
808 ipmi->aux.ipmi.host, ipmi->name));
809
810 x_pthread_mutex_lock(&ipmi->aux.ipmi.mutex);
811
812 if (ipmi->aux.ipmi.timer >= 0) {
813 (void) tpoll_timeout_cancel(tp_global, ipmi->aux.ipmi.timer);
814 ipmi->aux.ipmi.timer = -1;
815 }
816 if (ipmi->fd >= 0) {
817 tpoll_clear(tp_global, ipmi->fd, POLLIN | POLLOUT);
818 if (close(ipmi->fd) < 0) {
819 log_msg(LOG_WARNING,
820 "Unable to close connection to <%s> for console [%s]: %s",
821 ipmi->aux.ipmi.host, ipmi->name, strerror(errno));
822 }
823 ipmi->fd = -1;
824 }
825 /* Notify linked objs when transitioning from an UP state.
826 */
827 if (ipmi->aux.ipmi.state == CONMAN_IPMI_UP) {
828 write_notify_msg(ipmi, LOG_INFO,
829 "Console [%s] disconnected from <%s>",
830 ipmi->name, ipmi->aux.ipmi.host);
831 }
832 ipmi->aux.ipmi.state = CONMAN_IPMI_DOWN;
833
834 x_pthread_mutex_unlock(&ipmi->aux.ipmi.mutex);
835
836 return;
837 }
838
839
connect_ipmi_obj(obj_t * ipmi)840 static int connect_ipmi_obj(obj_t *ipmi)
841 {
842 /* Establishes a non-blocking connect with the specified (ipmi) obj.
843 * Returns 0 if the connection is successfully completed; o/w, returns -1.
844 *
845 * Note that this routine can be invoked from both the main thread and the
846 * ipmiconsole engine thread.
847 */
848 int rc = 0;
849
850 x_pthread_mutex_lock(&ipmi->aux.ipmi.mutex);
851
852 /* The if-guard for a !UP state is to protect against a race-condition
853 * where both the main thread and the ipmiconsole engine thread call
854 * this routine at the same time. If the first thread successfully
855 * completes the connection and transitions to an UP state, it will
856 * set a reset_ipmi_delay timer. The if-guard protects against the
857 * subsequent thread cancelling this timer.
858 * If the first thread fails to complete the connection, the subsequent
859 * thread will just retry a bit sooner than planned.
860 */
861 if (ipmi->aux.ipmi.state != CONMAN_IPMI_UP) {
862
863 if (ipmi->aux.ipmi.timer >= 0) {
864 (void) tpoll_timeout_cancel(tp_global, ipmi->aux.ipmi.timer);
865 ipmi->aux.ipmi.timer = -1;
866 }
867 if (ipmi->aux.ipmi.state == CONMAN_IPMI_DOWN) {
868 rc = initiate_ipmi_connect(ipmi);
869 }
870 else if (ipmi->aux.ipmi.state == CONMAN_IPMI_PENDING) {
871 rc = complete_ipmi_connect(ipmi);
872 }
873 else {
874 log_err(0, "Console [%s] in unexpected IPMI state=%d",
875 ipmi->name, (int) ipmi->aux.ipmi.state);
876 }
877 if (rc < 0) {
878 fail_ipmi_connect(ipmi);
879 }
880 }
881 x_pthread_mutex_unlock(&ipmi->aux.ipmi.mutex);
882 return(rc);
883 }
884
885
initiate_ipmi_connect(obj_t * ipmi)886 static int initiate_ipmi_connect(obj_t *ipmi)
887 {
888 /* Initiates an IPMI connection attempt.
889 * Returns 0 if the connection initiation is successful, or -1 on error.
890 *
891 * XXX: This routine assumes the ipmi obj mutex is already locked.
892 */
893 int rc;
894
895 assert(ipmi->aux.ipmi.state == CONMAN_IPMI_DOWN);
896
897 if (create_ipmi_ctx(ipmi) < 0) {
898 return(-1);
899 }
900 DPRINTF((10, "Connecting to <%s> via IPMI for [%s].\n",
901 ipmi->aux.ipmi.host, ipmi->name));
902
903 rc = ipmiconsole_engine_submit(ipmi->aux.ipmi.ctx,
904 (Ipmiconsole_callback) connect_ipmi_obj, ipmi);
905 if (rc < 0) {
906 return(-1);
907 }
908 ipmi->aux.ipmi.state = CONMAN_IPMI_PENDING;
909 /*
910 * ipmiconsole_engine_submit() should always call its callback function,
911 * at which point the connection will be established or retried.
912 * This timer is simply an additional safety measure in case it doesn't.
913 * Any existing timer should have already been cancelled at the start of
914 * connect_ipmi_obj().
915 */
916 assert(ipmi->aux.ipmi.timer == -1);
917 ipmi->aux.ipmi.timer = tpoll_timeout_relative(tp_global,
918 (callback_f) connect_ipmi_obj, ipmi,
919 IPMI_CONNECT_TIMEOUT * 1000);
920
921 return(0);
922 }
923
924
create_ipmi_ctx(obj_t * ipmi)925 static int create_ipmi_ctx(obj_t *ipmi)
926 {
927 /* Creates a new IPMI context 'ipmi'.
928 * Returns 0 if the context is successfully created; o/w, returns -1.
929 *
930 * XXX: This routine assumes the ipmi obj mutex is already locked.
931 */
932 struct ipmiconsole_ipmi_config ipmi_config;
933 struct ipmiconsole_protocol_config protocol_config;
934 struct ipmiconsole_engine_config engine_config;
935
936 ipmi_config.username = ipmi->aux.ipmi.iconf.username;
937 ipmi_config.password = ipmi->aux.ipmi.iconf.password;
938 ipmi_config.k_g = ipmi->aux.ipmi.iconf.kg;
939 ipmi_config.k_g_len = ipmi->aux.ipmi.iconf.kgLen;
940 ipmi_config.privilege_level = ipmi->aux.ipmi.iconf.privilegeLevel;
941 ipmi_config.cipher_suite_id = ipmi->aux.ipmi.iconf.cipherSuite;
942 ipmi_config.workaround_flags = ipmi->aux.ipmi.iconf.workaroundFlags;
943
944 protocol_config.session_timeout_len = -1;
945 protocol_config.retransmission_timeout_len = -1;
946 protocol_config.retransmission_backoff_count = -1;
947 protocol_config.keepalive_timeout_len = -1;
948 protocol_config.retransmission_keepalive_timeout_len = -1;
949 protocol_config.acceptable_packet_errors_count = -1;
950 protocol_config.maximum_retransmission_count = -1;
951
952 engine_config.engine_flags = IPMICONSOLE_ENGINE_DEFAULT;
953 engine_config.behavior_flags = IPMICONSOLE_BEHAVIOR_DEFAULT;
954 engine_config.debug_flags = IPMICONSOLE_DEBUG_DEFAULT;
955
956 /* A context cannot be submitted to the ipmiconsole engine more than once,
957 * so create a new context if one already exists.
958 */
959 if (ipmi->aux.ipmi.ctx) {
960 ipmiconsole_ctx_destroy(ipmi->aux.ipmi.ctx);
961 }
962 ipmi->aux.ipmi.ctx = ipmiconsole_ctx_create(
963 ipmi->aux.ipmi.host, &ipmi_config, &protocol_config, &engine_config);
964
965 if (!ipmi->aux.ipmi.ctx) {
966 return(-1);
967 }
968 return(0);
969 }
970
971
complete_ipmi_connect(obj_t * ipmi)972 static int complete_ipmi_connect(obj_t *ipmi)
973 {
974 /* Completes an IPMI connection attempt.
975 * Returns 0 if the connection is successfully established, or -1 on error.
976 *
977 * XXX: This routine assumes the ipmi obj mutex is already locked.
978 */
979 ipmiconsole_ctx_status_t status;
980
981 assert(ipmi->aux.ipmi.state == CONMAN_IPMI_PENDING);
982
983 status = ipmiconsole_ctx_status(ipmi->aux.ipmi.ctx);
984 if (status != IPMICONSOLE_CTX_STATUS_SOL_ESTABLISHED) {
985 return(-1);
986 }
987 if ((ipmi->fd = ipmiconsole_ctx_fd(ipmi->aux.ipmi.ctx)) < 0) {
988 return(-1);
989 }
990 set_fd_nonblocking(ipmi->fd);
991 set_fd_closed_on_exec(ipmi->fd);
992
993 ipmi->gotEOF = 0;
994 ipmi->aux.ipmi.state = CONMAN_IPMI_UP;
995 tpoll_set(tp_global, ipmi->fd, POLLIN);
996
997 /* Require the connection to be up for a minimum length of time
998 * before resetting the reconnect delay back to the minimum.
999 * Any existing timer should have already been cancelled at the start of
1000 * connect_ipmi_obj().
1001 */
1002 assert(ipmi->aux.ipmi.timer == -1);
1003 ipmi->aux.ipmi.timer = tpoll_timeout_relative(tp_global,
1004 (callback_f) reset_ipmi_delay, ipmi, IPMI_MIN_TIMEOUT * 1000);
1005
1006 /* Notify linked objs when transitioning into an UP state.
1007 */
1008 write_notify_msg(ipmi, LOG_INFO, "Console [%s] connected to <%s>",
1009 ipmi->name, ipmi->aux.ipmi.host);
1010 DPRINTF((15, "Connection established to <%s> via IPMI for [%s].\n",
1011 ipmi->aux.ipmi.host, ipmi->name));
1012 return (0);
1013 }
1014
1015
fail_ipmi_connect(obj_t * ipmi)1016 static void fail_ipmi_connect(obj_t *ipmi)
1017 {
1018 /* Logs an error message for the connection failure and sets a timer to
1019 * establish a new connection attempt.
1020 *
1021 * XXX: This routine assumes the ipmi obj mutex is already locked.
1022 */
1023 ipmi->aux.ipmi.state = CONMAN_IPMI_DOWN;
1024
1025 if (!ipmi->aux.ipmi.ctx) {
1026 log_msg(LOG_INFO,
1027 "Unable to create IPMI context for [%s]", ipmi->name);
1028 }
1029 else {
1030 int e = ipmiconsole_ctx_errnum(ipmi->aux.ipmi.ctx);
1031 log_msg(LOG_INFO,
1032 "Unable to connect to <%s> via IPMI for [%s]%s%s",
1033 ipmi->aux.ipmi.host, ipmi->name, (e ? ": " : ""),
1034 (e ? ipmiconsole_ctx_strerror(e) : ""));
1035 }
1036 /* Set timer for establishing new connection attempt.
1037 * Any existing timer should have already been cancelled at the start of
1038 * connect_ipmi_obj().
1039 */
1040 assert(ipmi->aux.ipmi.delay >= IPMI_MIN_TIMEOUT);
1041 assert(ipmi->aux.ipmi.delay <= IPMI_MAX_TIMEOUT);
1042 DPRINTF((15, "Reconnect attempt to <%s> via IPMI for [%s] in %ds.\n",
1043 ipmi->aux.ipmi.host, ipmi->name, ipmi->aux.ipmi.delay));
1044 assert(ipmi->aux.ipmi.timer == -1);
1045 ipmi->aux.ipmi.timer = tpoll_timeout_relative(tp_global,
1046 (callback_f) connect_ipmi_obj, ipmi,
1047 ipmi->aux.ipmi.delay * 1000);
1048
1049 /* Update timer delay via exponential backoff.
1050 */
1051 if (ipmi->aux.ipmi.delay < IPMI_MAX_TIMEOUT) {
1052 ipmi->aux.ipmi.delay = MIN(ipmi->aux.ipmi.delay * 2, IPMI_MAX_TIMEOUT);
1053 }
1054 return;
1055 }
1056
1057
reset_ipmi_delay(obj_t * ipmi)1058 static void reset_ipmi_delay(obj_t *ipmi)
1059 {
1060 /* Resets the 'ipmi' obj's delay between reconnect attempts.
1061 */
1062 assert(is_ipmi_obj(ipmi));
1063
1064 x_pthread_mutex_lock(&ipmi->aux.ipmi.mutex);
1065
1066 ipmi->aux.ipmi.delay = IPMI_MIN_TIMEOUT;
1067
1068 /* Also reset the timer ID since this routine is only invoked via a timer.
1069 */
1070 ipmi->aux.ipmi.timer = -1;
1071
1072 x_pthread_mutex_unlock(&ipmi->aux.ipmi.mutex);
1073 return;
1074 }
1075
1076
send_ipmi_break(obj_t * ipmi)1077 int send_ipmi_break(obj_t *ipmi)
1078 {
1079 /* Generates a serial-break for the specified 'ipmi' obj.
1080 * Returns 0 on success; o/w, returns -1.
1081 */
1082 int rc;
1083
1084 assert(ipmi != NULL);
1085 assert(is_ipmi_obj(ipmi));
1086
1087 x_pthread_mutex_lock(&ipmi->aux.ipmi.mutex);
1088 if (!ipmi->aux.ipmi.ctx) {
1089 log_msg(LOG_ERR,
1090 "Unable to send serial-break to [%s]: NULL IPMI context",
1091 ipmi->name);
1092 rc = -1;
1093 }
1094 else {
1095 rc = ipmiconsole_ctx_generate_break(ipmi->aux.ipmi.ctx);
1096 }
1097 x_pthread_mutex_unlock(&ipmi->aux.ipmi.mutex);
1098 return(rc);
1099 }
1100