static int perform_monitor_cmd(req_t *req, server_conf_t *conf) { /* Performs the MONITOR command, placing the client in a * "read-only" session with a single console. * Returns 0 if the command succeeds, or -1 on error. */ obj_t *client; obj_t *console; assert(req->sd >= 0); assert(req->command == CONMAN_CMD_MONITOR); assert(list_count(req->consoles) == 1); log_msg(LOG_INFO, "Client <%s@%s:%d> issued monitor command", req->user, req->fqdn, req->port); if (send_rsp(req, CONMAN_ERR_NONE, NULL) < 0) return(-1); client = create_client_obj(conf, req); console = list_peek(req->consoles); assert(is_console_obj(console)); link_objs(console, client); check_console_state(console, client); return(0); }
static int query_consoles_via_regex( server_conf_t *conf, req_t *req, List matches) { /* Match request patterns against console names using regular expressions. */ char *p; ListIterator i; char buf[MAX_SOCK_LINE]; int rc; regex_t rex; regmatch_t match; obj_t *obj; /* An empty list for the QUERY command matches all consoles. */ if (list_is_empty(req->consoles)) { p = create_string(".*"); list_append(req->consoles, p); } /* Combine console patterns via alternation to create single regex. */ i = list_iterator_create(req->consoles); strlcpy(buf, list_next(i), sizeof(buf)); while ((p = list_next(i))) { strlcat(buf, "|", sizeof(buf)); strlcat(buf, p, sizeof(buf)); } list_iterator_destroy(i); /* Initialize 'rex' to silence "uninitialized use" warnings. */ memset(&rex, 0, sizeof(rex)); /* Compile regex for searching server's console objs. */ rc = regcomp(&rex, buf, REG_EXTENDED | REG_ICASE); if (rc != 0) { if (regerror(rc, &rex, buf, sizeof(buf)) > sizeof(buf)) log_msg(LOG_WARNING, "Got regerror() buffer overrun"); regfree(&rex); send_rsp(req, CONMAN_ERR_BAD_REGEX, buf); return(-1); } /* Search objs for console names matching console patterns in the request. */ i = list_iterator_create(conf->objs); while ((obj = list_next(i))) { if (!is_console_obj(obj)) continue; if (!regexec(&rex, obj->name, 1, &match, 0) && (match.rm_so == 0) && (match.rm_eo == strlen(obj->name))) list_append(matches, obj); } list_iterator_destroy(i); regfree(&rex); return(0); }
static int perform_connect_cmd(req_t *req, server_conf_t *conf) { /* Performs the CONNECT command. If a single console is specified, * the client is placed in a "read-write" session with that console. * Otherwise, the client is placed in a "write-only" broadcast session * affecting multiple consoles. * Returns 0 if the command succeeds, or -1 on error. */ obj_t *client; obj_t *console; ListIterator i; assert(req->sd >= 0); assert(req->command == CONMAN_CMD_CONNECT); log_msg(LOG_INFO, "Client <%s@%s:%d> issued connect command", req->user, req->fqdn, req->port); if (send_rsp(req, CONMAN_ERR_NONE, NULL) < 0) return(-1); client = create_client_obj(conf, req); if (list_count(req->consoles) == 1) { /* * Unicast connection (R/W). */ console = list_peek(req->consoles); assert(is_console_obj(console)); link_objs(client, console); link_objs(console, client); check_console_state(console, client); } else { /* * Broadcast connection (W/O). */ i = list_iterator_create(req->consoles); while ((console = list_next(i))) { assert(is_console_obj(console)); link_objs(client, console); check_console_state(console, client); } list_iterator_destroy(i); } return(0); }
obj_t * create_serial_obj(server_conf_t *conf, char *name, char *dev, seropt_t *opts, char *errbuf, int errlen) { /* Creates a new serial device object and adds it to the master objs list. * Note: the console is open and set for non-blocking I/O. * Returns the new object, or NULL on error. */ ListIterator i; obj_t *serial; assert(conf != NULL); assert((name != NULL) && (name[0] != '\0')); assert((dev != NULL) && (dev[0] != '\0')); assert(opts != NULL); /* Check for duplicate console and device names. * While the write-lock will protect against two separate daemons * using the same device, it will not protect against two console * objects within the same daemon process using the same device. * So that check is performed here. */ i = list_iterator_create(conf->objs); while ((serial = list_next(i))) { if (is_console_obj(serial) && !strcmp(serial->name, name)) { snprintf(errbuf, errlen, "console [%s] specifies duplicate console name", name); break; } if (is_serial_obj(serial) && !strcmp(serial->aux.serial.dev, dev)) { snprintf(errbuf, errlen, "console [%s] specifies duplicate device \"%s\"", name, dev); break; } } list_iterator_destroy(i); if (serial != NULL) { return(NULL); } serial = create_obj(conf, name, -1, CONMAN_OBJ_SERIAL); serial->aux.serial.dev = create_string(dev); serial->aux.serial.opts = *opts; serial->aux.serial.logfile = NULL; /* * Add obj to the master conf->objs list. */ list_append(conf->objs, serial); return(serial); }
static int query_consoles_via_globbing( server_conf_t *conf, req_t *req, List matches) { /* Match request patterns against console names using shell-style globbing. * This is less efficient than matching via regular expressions * since the console list must be traversed for each pattern, and the * matches list must be traversed for each match to prevent duplicates. */ char *p; ListIterator i, j; char *pat; obj_t *obj; /* An empty list for the QUERY command matches all consoles. */ if (list_is_empty(req->consoles)) { p = create_string("*"); list_append(req->consoles, p); } /* Search objs for console names matching console patterns in the request. */ i = list_iterator_create(req->consoles); j = list_iterator_create(conf->objs); while ((pat = list_next(i))) { list_iterator_reset(j); while ((obj = list_next(j))) { if (!is_console_obj(obj)) continue; if (!fnmatch(pat, obj->name, 0) && !list_find_first(matches, (ListFindF) find_obj, obj)) list_append(matches, obj); } } list_iterator_destroy(i); list_iterator_destroy(j); return(0); }
obj_t * get_console_logfile_obj(obj_t *console) { /* Returns a ptr to the logfile obj associated with 'console' * if one exists and is currently active; o/w, returns NULL. */ obj_t *logfile = NULL; assert(console != NULL); assert(is_console_obj(console)); if (is_process_obj(console)) { logfile = console->aux.process.logfile; } else if (is_serial_obj(console)) { logfile = console->aux.serial.logfile; } else if (is_telnet_obj(console)) { logfile = console->aux.telnet.logfile; } else if (is_unixsock_obj(console)) { logfile = console->aux.unixsock.logfile; } #if WITH_FREEIPMI else if (is_ipmi_obj(console)) { logfile = console->aux.ipmi.logfile; } #endif /* WITH_FREEIPMI */ else { log_err(0, "INTERNAL: Unrecognized console [%s] type=%d", console->name, console->type); } if (!logfile || (logfile->fd < 0)) { return(NULL); } assert(is_logfile_obj(logfile)); return(logfile); }
static void display_configuration(server_conf_t *conf) { /* Displays a summary of the server's configuration. */ ListIterator i; obj_t *obj; int n = 0; int gotOptions = 0; i = list_iterator_create(conf->objs); while ((obj = list_next(i))) { if (is_console_obj(obj)) { n++; } } list_iterator_destroy(i); fprintf(stderr, "\nStarting ConMan daemon %s (pid %d)\n", VERSION, (int) getpid()); fprintf(stderr, "Configuration: %s\n", conf->confFileName); fprintf(stderr, "Options:"); if (conf->enableCoreDump) { fprintf(stderr, " CoreDump"); gotOptions++; } if (conf->enableKeepAlive) { fprintf(stderr, " KeepAlive"); gotOptions++; } if (conf->logFileName) { fprintf(stderr, " LogFile"); gotOptions++; } if (conf->enableLoopBack) { fprintf(stderr, " LoopBack"); gotOptions++; } if (conf->resetCmd) { fprintf(stderr, " ResetCmd"); gotOptions++; } if (conf->syslogFacility >= 0) { fprintf(stderr, " SysLog"); gotOptions++; } if (conf->enableTCPWrap) { fprintf(stderr, " TCP-Wrappers"); gotOptions++; } if (conf->tStampMinutes > 0) { fprintf(stderr, " TimeStamp=%dm", conf->tStampMinutes); gotOptions++; } if (conf->enableZeroLogs) { fprintf(stderr, " ZeroLogs"); gotOptions++; } if (!gotOptions) { fprintf(stderr, " None"); } fprintf(stderr, "\n"); fprintf(stderr, "Listening on port %d\n", conf->port); fprintf(stderr, "Monitoring %d console%s\n", n, ((n == 1) ? "" : "s")); fprintf(stderr, "\n"); return; }
static void reset_console(obj_t *console, const char *cmd) { /* Resets the 'console' obj by performing the reset 'cmd' in a subshell. */ char buf[MAX_LINE]; pid_t pid; pid_t *arg; assert(is_console_obj(console)); assert(console->gotReset); assert(cmd != NULL); console->gotReset = 0; if (format_obj_string(buf, sizeof(buf), console, cmd) < 0) { log_msg(LOG_NOTICE, "Unable to reset console [%s]: command too long", console->name); return; } if ((pid = fork()) < 0) { log_msg(LOG_NOTICE, "Unable to reset console [%s]: %s", console->name, strerror(errno)); return; } else if (pid == 0) { setpgid(pid, 0); close(STDIN_FILENO); /* ignore errors on close() */ close(STDOUT_FILENO); close(STDERR_FILENO); execl("/bin/sh", "sh", "-c", buf, (char *) NULL); _exit(127); /* execl() error */ } /* Both parent and child call setpgid() to make the child a process * group leader. One of these calls is redundant, but by doing * both we avoid a race condition. (cf. APUE 9.4 p244) */ setpgid(pid, 0); /* FIXME: Have perform_reset() store the client info instead of a bool * for gotReset. Then remove the notify_objs msg from perform_reset() * and replace it with the following: */ log_msg(LOG_INFO, "Reset console [%s] (pid %d)", console->name, pid); #if 0 snprintf(buf, sizeof(buf), "%sConsole [%s] reset (pid %d)%s", CONMAN_MSG_PREFIX, console->name, (int) pid, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); notify_console_objs(console, buf); #endif /* Set a timer to ensure the reset cmd does not exceed its time limit. * The callback function's arg must be allocated on the heap since * local vars on the stack will be lost once this routine returns. */ if (!(arg = malloc(sizeof *arg))) { out_of_memory(); } *arg = pid; if (tpoll_timeout_relative (tp_global, (callback_f) kill_console_reset, arg, RESET_CMD_TIMEOUT * 1000) < 0) { log_msg(LOG_ERR, "Unable to create timer for resetting console [%s]", console->name); } return; }
obj_t * create_unixsock_obj(server_conf_t *conf, char *name, char *dev, char *errbuf, int errlen) { /* Creates a new unix domain object and adds it to the master objs list. * Returns the new objects, or NULL on error. */ int n; ListIterator i; obj_t *unixsock; int rv; assert(conf != NULL); assert((name != NULL) && (name[0] != '\0')); assert((dev != NULL) && (dev[0] != '\0')); assert(errbuf != NULL); assert(errlen > 0); /* Check length of device string. */ n = max_unixsock_dev_strlen(); if (strlen(dev) > n) { snprintf(errbuf, errlen, "console [%s] exceeds maximum device length of %d bytes", name, n); return(NULL); } /* Check for duplicate console and device names. */ i = list_iterator_create(conf->objs); while ((unixsock = list_next(i))) { if (is_console_obj(unixsock) && !strcmp(unixsock->name, name)) { snprintf(errbuf, errlen, "console [%s] specifies duplicate console name", name); break; } if (is_unixsock_obj(unixsock) && !strcmp(unixsock->aux.unixsock.dev, dev)) { snprintf(errbuf, errlen, "console [%s] specifies duplicate device \"%s\"", name, dev); break; } } list_iterator_destroy(i); if (unixsock != NULL) { return(NULL); } unixsock = create_obj(conf, name, -1, CONMAN_OBJ_UNIXSOCK); unixsock->aux.unixsock.dev = create_string(dev); unixsock->aux.unixsock.logfile = NULL; unixsock->aux.unixsock.timer = -1; unixsock->aux.unixsock.state = CONMAN_UNIXSOCK_DOWN; /* * Add obj to the master conf->objs list. */ list_append(conf->objs, unixsock); rv = inevent_add(unixsock->aux.unixsock.dev, (inevent_cb_f) open_unixsock_obj, unixsock); if (rv < 0) { log_msg(LOG_INFO, "Console [%s] unable to register device \"%s\" for inotify events", unixsock->name, unixsock->aux.unixsock.dev); } return(unixsock); }
static void check_console_state(obj_t *console, obj_t *client) { /* Checks the state of the console and warns the client if needed. * Informs the newly-connected client if strange things are afoot. * Attempts an immediate reconnect if the console connection is down. */ char buf[MAX_LINE]; assert(is_console_obj(console)); assert(is_client_obj(client)); if (is_process_obj(console) && (console->fd < 0)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from \"%s\"%s", CONMAN_MSG_PREFIX, console->name, console->aux.process.prog, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); open_process_obj(console); } else if (is_serial_obj(console) && (console->fd < 0)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from \"%s\"%s", CONMAN_MSG_PREFIX, console->name, console->aux.serial.dev, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); open_serial_obj(console); } else if (is_telnet_obj(console) && (console->aux.telnet.state != CONMAN_TELNET_UP)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from <%s:%d>%s", CONMAN_MSG_PREFIX, console->name, console->aux.telnet.host, console->aux.telnet.port, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); console->aux.telnet.delay = TELNET_MIN_TIMEOUT; /* * Do not call connect_telnet_obj() while in the PENDING state since * it would be misinterpreted as the completion of the non-blocking * connect(). */ if (console->aux.telnet.state == CONMAN_TELNET_DOWN) { open_telnet_obj(console); } } else if (is_unixsock_obj(console) && (console->fd < 0)) { assert(console->aux.unixsock.state == CONMAN_UNIXSOCK_DOWN); snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from \"%s\"%s", CONMAN_MSG_PREFIX, console->name, console->aux.unixsock.dev, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); open_unixsock_obj(console); } #if WITH_FREEIPMI else if (is_ipmi_obj(console) && (console->aux.ipmi.state != CONMAN_IPMI_UP)) { snprintf(buf, sizeof(buf), "%sConsole [%s] is currently disconnected from <%s>%s", CONMAN_MSG_PREFIX, console->name, console->aux.ipmi.host, CONMAN_MSG_SUFFIX); strcpy(&buf[sizeof(buf) - 3], "\r\n"); write_obj_data(client, buf, strlen(buf), 1); if (console->aux.ipmi.state == CONMAN_IPMI_DOWN) { open_ipmi_obj(console); } } #endif /* WITH_FREEIPMI */ return; }
static int check_busy_consoles(req_t *req) { /* Checks to see if a "writable" request affects any consoles * that are currently busy (unless the force or join option is enabled). * Returns 0 if the request is valid, or -1 on error. */ List busy; ListIterator i; obj_t *console; obj_t *writer; int gotBcast; char *tty; time_t t; char *delta; char buf[MAX_LINE]; assert(!list_is_empty(req->consoles)); if ((req->command == CONMAN_CMD_QUERY) || (req->command == CONMAN_CMD_MONITOR)) return(0); if (req->enableForce || req->enableJoin) return(0); busy = list_create(NULL); i = list_iterator_create(req->consoles); while ((console = list_next(i))) { assert(is_console_obj(console)); if (!list_is_empty(console->writers)) list_append(busy, console); } list_iterator_destroy(i); if (list_is_empty(busy)) { list_destroy(busy); return(0); } if (list_count(busy) == 1) { snprintf(buf, sizeof(buf), "Found console already in use"); } else { snprintf(buf, sizeof(buf), "Found %d consoles already in use", list_count(busy)); } send_rsp(req, CONMAN_ERR_BUSY_CONSOLES, buf); /* Note: the "busy" list contains object references, * so they DO NOT get destroyed here when removed from the list. */ while ((console = list_pop(busy))) { i = list_iterator_create(console->writers); while ((writer = list_next(i))) { assert(is_client_obj(writer)); x_pthread_mutex_lock(&writer->bufLock); t = writer->aux.client.timeLastRead; gotBcast = list_is_empty(writer->writers); tty = writer->aux.client.req->tty; x_pthread_mutex_unlock(&writer->bufLock); delta = create_time_delta_string(t, -1); snprintf(buf, sizeof(buf), "Console [%s] open %s by <%s@%s>%s%s (idle %s).\n", console->name, (gotBcast ? "B/C" : "R/W"), writer->aux.client.req->user, writer->aux.client.req->host, (tty ? " on " : ""), (tty ? tty : ""), (delta ? delta : "???")); buf[sizeof(buf) - 2] = '\n'; buf[sizeof(buf) - 1] = '\0'; if (delta) free(delta); if (write_n(req->sd, buf, strlen(buf)) < 0) { log_msg(LOG_NOTICE, "Unable to write to <%s:%d>: %s", req->fqdn, req->port, strerror(errno)); break; } } list_iterator_destroy(i); } list_destroy(busy); return(-1); }