735 lines
16 KiB
C
735 lines
16 KiB
C
/*
|
|
* ser2net - A program for allowing telnet connection to serial ports
|
|
* Copyright (C) 2001 Corey Minyard <minyard@acm.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
|
|
/* This is the entry point for the ser2net program. It reads
|
|
parameters, initializes everything, then starts the select loop. */
|
|
|
|
/* TODO
|
|
*
|
|
* Add some type of security
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <syslog.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
#include <errno.h>
|
|
|
|
#include "ser2net.h"
|
|
#include "readconfig.h"
|
|
#include "controller.h"
|
|
#include "utils.h"
|
|
#include "selector.h"
|
|
#include "dataxfer.h"
|
|
#include "locking.h"
|
|
#include "led.h"
|
|
|
|
static char *config_file = "/etc/ser2net.conf";
|
|
int config_port_from_cmdline = 0;
|
|
char *config_port = NULL; /* Can be set from readconfig, too. */
|
|
static char *pid_file = NULL;
|
|
static int detach = 1;
|
|
int ser2net_debug = 0;
|
|
int ser2net_debug_level = 0;
|
|
volatile int in_shutdown = 0;
|
|
#ifdef USE_UUCP_LOCKING
|
|
int uucp_locking_enabled = 1;
|
|
#endif
|
|
#ifdef USE_PTHREADS
|
|
int num_threads = 1;
|
|
struct thread_info {
|
|
pthread_t id;
|
|
};
|
|
struct thread_info *threads;
|
|
#endif
|
|
|
|
|
|
struct selector_s *ser2net_sel;
|
|
char *rfc2217_signature = "ser2net";
|
|
|
|
static char *help_string =
|
|
"%s: Valid parameters are:\n"
|
|
" -c <config file> - use a config file besides /etc/ser2net.conf\n"
|
|
" -C <config line> - Handle a single configuration line. This may be\n"
|
|
" specified multiple times for multiple lines. This is just like a\n"
|
|
" line in the config file. This disables the default config file,\n"
|
|
" you must specify a -c after the last -C to have it read a config\n"
|
|
" file, too.\n"
|
|
" -p <controller port> - Start a controller session on the given TCP port\n"
|
|
" -P <file> - set location of pid file\n"
|
|
" -n - Don't detach from the controlling terminal\n"
|
|
" -d - Don't detach and send debug I/O to standard output\n"
|
|
" -l - Increate the debugging level\n"
|
|
#ifdef USE_UUCP_LOCKING
|
|
" -u - Disable UUCP locking\n"
|
|
#endif
|
|
#ifdef USE_PTHREADS
|
|
" -t <num threads> - Use the given number of threads, default 1\n"
|
|
#endif
|
|
" -b - unused (was Do CISCO IOS baud-rate negotiation, instead of RFC2217)\n"
|
|
" -v - print the program's version and exit\n"
|
|
" -s - specify a default signature for RFC2217 protocol\n";
|
|
|
|
static void
|
|
reread_config_file(void)
|
|
{
|
|
if (config_file) {
|
|
char *prev_config_port = config_port;
|
|
config_port = NULL;
|
|
syslog(LOG_INFO, "Got SIGHUP, re-reading configuration");
|
|
readconfig_init();
|
|
readconfig(config_file);
|
|
if (config_port_from_cmdline) {
|
|
/* Never override the config port from the command line. */
|
|
free(config_port);
|
|
config_port = prev_config_port;
|
|
goto config_port_unchanged;
|
|
}
|
|
if (config_port && prev_config_port
|
|
&& (strcmp(config_port, prev_config_port) == 0)) {
|
|
free(prev_config_port);
|
|
goto config_port_unchanged;
|
|
}
|
|
|
|
if (prev_config_port) {
|
|
controller_shutdown();
|
|
free(prev_config_port);
|
|
}
|
|
|
|
if (config_port) {
|
|
int rv = controller_init(config_port);
|
|
if (rv == CONTROLLER_INVALID_TCP_SPEC)
|
|
syslog(LOG_ERR, "Invalid control port specified: %s",
|
|
config_port);
|
|
else if (rv == CONTROLLER_OUT_OF_MEMORY)
|
|
syslog(LOG_ERR, "Out of memory opening control port: %s",
|
|
config_port);
|
|
else if (rv == CONTROLLER_CANT_OPEN_PORT)
|
|
syslog(LOG_ERR, "Can't open control port: %s",
|
|
config_port);
|
|
if (rv) {
|
|
syslog(LOG_ERR, "Control port is disabled");
|
|
free(config_port);
|
|
config_port = NULL;
|
|
}
|
|
}
|
|
}
|
|
config_port_unchanged:
|
|
return;
|
|
}
|
|
|
|
static void
|
|
arg_error(char *name)
|
|
{
|
|
fprintf(stderr, help_string, name);
|
|
exit(1);
|
|
}
|
|
|
|
static void
|
|
make_pidfile(void)
|
|
{
|
|
FILE *fpidfile;
|
|
if (!pid_file)
|
|
return;
|
|
fpidfile = fopen(pid_file, "w");
|
|
if (!fpidfile) {
|
|
syslog(LOG_WARNING,
|
|
"Error opening pidfile '%s': %m, pidfile not created",
|
|
pid_file);
|
|
pid_file = NULL;
|
|
return;
|
|
}
|
|
fprintf(fpidfile, "%d\n", getpid());
|
|
fclose(fpidfile);
|
|
}
|
|
|
|
static int dummyrv; /* Used to ignore return values of read() and write(). */
|
|
|
|
/* Used to reliably deliver signals to a thread. This is a pipe that
|
|
we write to from a signal handler to make sure it wakes up. */
|
|
static int sig_fd_alert = -1;
|
|
static int sig_fd_watch = -1;
|
|
static volatile int reread_config = 0; /* Did I get a HUP signal? */
|
|
static volatile int term_prog = 0; /* Did I get an INT signal? */
|
|
|
|
static void
|
|
sig_wake_selector(void)
|
|
{
|
|
char dummy = 0;
|
|
|
|
dummyrv = write(sig_fd_alert, &dummy, 1);
|
|
}
|
|
|
|
static void
|
|
sighup_handler(int sig)
|
|
{
|
|
reread_config = 1;
|
|
sig_wake_selector();
|
|
}
|
|
|
|
static void
|
|
sigint_handler(int sig)
|
|
{
|
|
term_prog = 1;
|
|
sig_wake_selector();
|
|
}
|
|
|
|
#if USE_PTHREADS
|
|
DEFINE_LOCK_INIT(static, config_lock)
|
|
static int in_config_read = 0;
|
|
|
|
DEFINE_LOCK_INIT(static, maint_lock)
|
|
|
|
int ser2net_wake_sig = SIGUSR1;
|
|
void (*finish_shutdown)(void);
|
|
|
|
void
|
|
start_maint_op(void)
|
|
{
|
|
LOCK(maint_lock);
|
|
}
|
|
|
|
void
|
|
end_maint_op(void)
|
|
{
|
|
UNLOCK(maint_lock);
|
|
}
|
|
|
|
static void *
|
|
config_reread_thread(void *dummy)
|
|
{
|
|
pthread_detach(pthread_self());
|
|
start_maint_op();
|
|
reread_config_file();
|
|
end_maint_op();
|
|
LOCK(config_lock);
|
|
in_config_read = 0;
|
|
UNLOCK(config_lock);
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
thread_reread_config_file(void)
|
|
{
|
|
int rv;
|
|
pthread_t thread;
|
|
|
|
LOCK(config_lock);
|
|
if (in_config_read) {
|
|
UNLOCK(config_lock);
|
|
return;
|
|
}
|
|
in_config_read = 1;
|
|
UNLOCK(config_lock);
|
|
|
|
rv = pthread_create(&thread, NULL, config_reread_thread, NULL);
|
|
if (rv) {
|
|
syslog(LOG_ERR,
|
|
"Unable to start thread to reread config file: %s",
|
|
strerror(rv));
|
|
LOCK(config_lock);
|
|
in_config_read = 0;
|
|
UNLOCK(config_lock);
|
|
}
|
|
}
|
|
|
|
static void
|
|
wake_thread_sighandler(int sig)
|
|
{
|
|
/* Nothing to do, sending the sig just wakes up select(). */
|
|
}
|
|
|
|
static void
|
|
wake_thread_send_sig(long thread_id, void *cb_data)
|
|
{
|
|
pthread_t *id = (void *) thread_id;
|
|
|
|
pthread_kill(*id, ser2net_wake_sig);
|
|
}
|
|
|
|
static void *
|
|
op_loop(void *dummy)
|
|
{
|
|
pthread_t self = pthread_self();
|
|
|
|
while (!in_shutdown)
|
|
sel_select(ser2net_sel, wake_thread_send_sig, (long) &self, NULL, NULL);
|
|
|
|
/* Join the threads only in the first thread. You cannot join the
|
|
first thread. Finish thw shutdown in the first thread. */
|
|
if (self == threads[0].id) {
|
|
int i;
|
|
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (threads[i].id == self)
|
|
continue;
|
|
pthread_join(threads[i].id, NULL);
|
|
}
|
|
free(threads);
|
|
|
|
finish_shutdown();
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
start_threads(void)
|
|
{
|
|
int i, rv;
|
|
struct sigaction act;
|
|
|
|
act.sa_handler = wake_thread_sighandler;
|
|
sigemptyset(&act.sa_mask);
|
|
act.sa_flags = 0;
|
|
rv = sigaction(ser2net_wake_sig, &act, NULL);
|
|
if (rv) {
|
|
syslog(LOG_ERR, "Unable to set sigaction: %s", strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
threads = malloc(sizeof(*threads) * num_threads);
|
|
if (!threads) {
|
|
syslog(LOG_ERR, "Unable to allocate thread info");
|
|
exit(1);
|
|
}
|
|
|
|
threads[0].id = pthread_self();
|
|
|
|
for (i = 1; i < num_threads; i++) {
|
|
rv = pthread_create(&threads[i].id, NULL, op_loop, NULL);
|
|
if (rv) {
|
|
syslog(LOG_ERR, "Unable to start thread: %s", strerror(rv));
|
|
exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void
|
|
stop_threads(void (*finish)(void))
|
|
{
|
|
int i;
|
|
pthread_t self = pthread_self();
|
|
|
|
in_shutdown = 1;
|
|
finish_shutdown = finish;
|
|
start_maint_op();
|
|
/* Make sure we aren't in a reconfig. */
|
|
end_maint_op();
|
|
|
|
for (i = 0; i < num_threads; i++) {
|
|
if (threads[i].id == self)
|
|
continue;
|
|
pthread_kill(threads[i].id, ser2net_wake_sig);
|
|
}
|
|
}
|
|
|
|
struct sel_lock_s
|
|
{
|
|
pthread_mutex_t lock;
|
|
};
|
|
|
|
static sel_lock_t *
|
|
slock_alloc(void *cb_data)
|
|
{
|
|
sel_lock_t *l;
|
|
|
|
l = malloc(sizeof(*l));
|
|
if (!l)
|
|
return NULL;
|
|
pthread_mutex_init(&l->lock, NULL);
|
|
return l;
|
|
}
|
|
|
|
static void
|
|
slock_free(sel_lock_t *l)
|
|
{
|
|
pthread_mutex_destroy(&l->lock);
|
|
free(l);
|
|
}
|
|
|
|
static void
|
|
slock_lock(sel_lock_t *l)
|
|
{
|
|
pthread_mutex_lock(&l->lock);
|
|
}
|
|
|
|
static void
|
|
slock_unlock(sel_lock_t *l)
|
|
{
|
|
pthread_mutex_unlock(&l->lock);
|
|
}
|
|
|
|
#else
|
|
int ser2net_wake_sig = 0;
|
|
void start_maint_op(void) { }
|
|
void end_maint_op(void) { }
|
|
static void start_threads(void) { }
|
|
static void stop_threads(void (*finish)(void)) { finish(); }
|
|
#define slock_alloc NULL
|
|
#define slock_free NULL
|
|
#define slock_lock NULL
|
|
#define slock_unlock NULL
|
|
static void *
|
|
op_loop(void *dummy)
|
|
{
|
|
sel_select_loop(ser2net_sel, NULL, 0, NULL);
|
|
return NULL;
|
|
}
|
|
#endif /* USE_PTHREADS */
|
|
|
|
static void
|
|
finish_shutdown_cleanly(void)
|
|
{
|
|
struct timeval tv;
|
|
|
|
sel_clear_fd_handlers(ser2net_sel, sig_fd_watch);
|
|
free_rotators();
|
|
free_controllers();
|
|
shutdown_ports();
|
|
do {
|
|
if (check_ports_shutdown())
|
|
break;
|
|
tv.tv_sec = 1;
|
|
tv.tv_usec = 0;
|
|
sel_select(ser2net_sel, NULL, 0, NULL, &tv);
|
|
} while(1);
|
|
|
|
sol_shutdown(); /* Free's the selector. */
|
|
|
|
free_longstrs();
|
|
free_tracefiles();
|
|
free_rs485confs();
|
|
|
|
if (pid_file)
|
|
unlink(pid_file);
|
|
|
|
exit(1);
|
|
}
|
|
|
|
static void
|
|
shutdown_cleanly(void)
|
|
{
|
|
stop_threads(finish_shutdown_cleanly);
|
|
}
|
|
|
|
static void
|
|
sig_fd_read_handler(int fd, void *cb_data)
|
|
{
|
|
char dummy[10];
|
|
|
|
dummyrv = read(fd, dummy, sizeof(dummy));
|
|
|
|
if (term_prog)
|
|
shutdown_cleanly();
|
|
|
|
if (reread_config && !in_shutdown) {
|
|
#if USE_PTHREADS
|
|
thread_reread_config_file();
|
|
#else
|
|
reread_config_file();
|
|
#endif
|
|
}
|
|
}
|
|
|
|
static void
|
|
setup_signals(void)
|
|
{
|
|
struct sigaction act;
|
|
int err;
|
|
int pipefds[2];
|
|
|
|
/* Ignore SIGPIPEs so they don't kill us. */
|
|
signal(SIGPIPE, SIG_IGN);
|
|
|
|
err = pipe(pipefds);
|
|
if (err)
|
|
goto out;
|
|
|
|
sig_fd_alert = pipefds[1];
|
|
sig_fd_watch = pipefds[0];
|
|
|
|
act.sa_handler = sighup_handler;
|
|
sigemptyset(&act.sa_mask);
|
|
act.sa_flags = SA_RESTART;
|
|
err = sigaction(SIGHUP, &act, NULL);
|
|
if (err)
|
|
goto out;
|
|
|
|
act.sa_handler = sigint_handler;
|
|
/* Only handle SIGINT once. */
|
|
act.sa_flags |= SA_RESETHAND;
|
|
err = sigaction(SIGINT, &act, NULL);
|
|
if (!err)
|
|
err = sigaction(SIGQUIT, &act, NULL);
|
|
if (!err)
|
|
err = sigaction(SIGTERM, &act, NULL);
|
|
if (err)
|
|
goto out;
|
|
|
|
sel_set_fd_handlers(ser2net_sel, sig_fd_watch, NULL, sig_fd_read_handler,
|
|
NULL, NULL, NULL);
|
|
sel_set_fd_read_handler(ser2net_sel, sig_fd_watch, SEL_FD_HANDLER_ENABLED);
|
|
|
|
out:
|
|
if (err) {
|
|
fprintf(stderr, "Error setting up signals: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
int
|
|
main(int argc, char *argv[])
|
|
{
|
|
int i;
|
|
int err;
|
|
#ifdef USE_PTHREADS
|
|
char *end;
|
|
#endif
|
|
char **config_lines;
|
|
int num_config_lines = 0;
|
|
|
|
config_lines = malloc(sizeof(*config_lines));
|
|
if (!config_lines) {
|
|
fprintf(stderr, "Out of memory\n");
|
|
exit(1);
|
|
}
|
|
*config_lines = NULL;
|
|
|
|
if (led_driver_init() < 0) {
|
|
fprintf(stderr, "Error while initializing LED drivers\n");
|
|
exit(1);
|
|
}
|
|
|
|
for (i = 1; i < argc; i++) {
|
|
if ((argv[i][0] != '-') || (strlen(argv[i]) != 2)) {
|
|
fprintf(stderr, "Invalid argument: '%s'\n", argv[i]);
|
|
arg_error(argv[0]);
|
|
}
|
|
|
|
switch (argv[i][1]) {
|
|
case 'n':
|
|
detach = 0;
|
|
break;
|
|
|
|
case 'd':
|
|
detach = 0;
|
|
ser2net_debug = 1;
|
|
break;
|
|
|
|
case 'l':
|
|
ser2net_debug_level++;
|
|
break;
|
|
|
|
case 'b':
|
|
break;
|
|
|
|
case 'C':
|
|
/* Get a config line. */
|
|
i++;
|
|
if (i == argc) {
|
|
fprintf(stderr, "No config line specified with -C\n");
|
|
arg_error(argv[0]);
|
|
}
|
|
num_config_lines++;
|
|
config_lines = realloc(config_lines, sizeof(*config_lines) *
|
|
(num_config_lines + 1));
|
|
if (!config_lines) {
|
|
fprintf(stderr, "Out of memory handling config line\n");
|
|
exit(1);
|
|
}
|
|
config_lines[num_config_lines - 1] = argv[i];
|
|
config_file = NULL;
|
|
break;
|
|
|
|
case 'c':
|
|
/* Get a config file. */
|
|
i++;
|
|
if (i == argc) {
|
|
fprintf(stderr, "No config file specified with -c\n");
|
|
arg_error(argv[0]);
|
|
}
|
|
config_file = argv[i];
|
|
break;
|
|
|
|
case 'p':
|
|
/* Get the control port. */
|
|
i++;
|
|
if (i == argc) {
|
|
fprintf(stderr, "No control port specified with -p\n");
|
|
arg_error(argv[0]);
|
|
}
|
|
config_port = strdup(argv[i]);
|
|
if (!config_port) {
|
|
fprintf(stderr, "Could not allocate memory for -p\n");
|
|
exit(1);
|
|
}
|
|
config_port_from_cmdline = 1;
|
|
break;
|
|
|
|
case 'P':
|
|
i++;
|
|
if (i == argc) {
|
|
fprintf(stderr, "No pid file specified with -P\n");
|
|
arg_error(argv[0]);
|
|
}
|
|
pid_file = argv[i];
|
|
break;
|
|
|
|
#ifdef USE_UUCP_LOCKING
|
|
case 'u':
|
|
uucp_locking_enabled = 0;
|
|
break;
|
|
#endif
|
|
|
|
case 'v':
|
|
printf("%s version %s\n", argv[0], VERSION);
|
|
exit(0);
|
|
|
|
case 's':
|
|
i++;
|
|
if (i == argc) {
|
|
fprintf(stderr, "No signature specified\n");
|
|
exit(1);
|
|
}
|
|
rfc2217_signature = argv[i];
|
|
break;
|
|
|
|
#ifdef USE_PTHREADS
|
|
case 't':
|
|
i++;
|
|
if (i == argc) {
|
|
fprintf(stderr, "No thread count specified\n");
|
|
exit(1);
|
|
}
|
|
num_threads = strtoul(argv[i], &end, 10);
|
|
if (end == argv[i] || *end != '\0') {
|
|
fprintf(stderr, "Invalid thread count specified: %s\n",
|
|
argv[i]);
|
|
exit(1);
|
|
}
|
|
break;
|
|
#endif
|
|
|
|
default:
|
|
fprintf(stderr, "Invalid option: '%s'\n", argv[i]);
|
|
arg_error(argv[0]);
|
|
}
|
|
}
|
|
|
|
#ifdef USE_PTHREADS
|
|
if (num_threads > 1)
|
|
err = sel_alloc_selector_thread(&ser2net_sel, ser2net_wake_sig,
|
|
slock_alloc, slock_free,
|
|
slock_lock, slock_unlock, NULL);
|
|
else
|
|
#endif
|
|
err = sel_alloc_selector_nothread(&ser2net_sel);
|
|
|
|
if (err) {
|
|
fprintf(stderr,
|
|
"Could not initialize ser2net selector: '%s'\n",
|
|
strerror(err));
|
|
exit(1);
|
|
}
|
|
|
|
setup_signals();
|
|
|
|
err = sol_init();
|
|
if (err) {
|
|
fprintf(stderr,
|
|
"Could not initialize IPMI SOL: '%s'\n",
|
|
strerror(-err));
|
|
exit(1);
|
|
}
|
|
|
|
if (ser2net_debug && !detach)
|
|
openlog("ser2net", LOG_PID | LOG_CONS | LOG_PERROR, LOG_DAEMON);
|
|
|
|
readconfig_init();
|
|
for (i = 0; i < num_config_lines; i++)
|
|
handle_config_line(config_lines[i], strlen(config_lines[i]));
|
|
free(config_lines);
|
|
if (config_file) {
|
|
if (readconfig(config_file) == -1)
|
|
exit(1);
|
|
}
|
|
|
|
if (config_port != NULL) {
|
|
int rv;
|
|
rv = controller_init(config_port);
|
|
if (rv == CONTROLLER_INVALID_TCP_SPEC) {
|
|
fprintf(stderr, "Invalid control port specified: %s\n",
|
|
config_port);
|
|
arg_error(argv[0]);
|
|
}
|
|
if (rv == CONTROLLER_CANT_OPEN_PORT) {
|
|
fprintf(stderr, "Unable to open control port, see syslog: %s\n",
|
|
config_port);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (detach) {
|
|
int pid;
|
|
|
|
/* Detach from the calling terminal. */
|
|
openlog("ser2net", LOG_PID | LOG_CONS, LOG_DAEMON);
|
|
syslog(LOG_NOTICE, "ser2net startup");
|
|
if ((pid = fork()) > 0) {
|
|
exit(0);
|
|
} else if (pid < 0) {
|
|
syslog(LOG_ERR, "Error forking first fork: %s", strerror(errno));
|
|
exit(1);
|
|
} else {
|
|
/* setsid() is necessary if we really want to demonize */
|
|
setsid();
|
|
/* Second fork to really deamonize me. */
|
|
if ((pid = fork()) > 0) {
|
|
exit(0);
|
|
} else if (pid < 0) {
|
|
syslog(LOG_ERR, "Error forking second fork: %s",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
/* Close all my standard I/O. */
|
|
if (chdir("/") < 0) {
|
|
syslog(LOG_ERR, "unable to chdir to '/': %s", strerror(errno));
|
|
exit(1);
|
|
}
|
|
close(0);
|
|
close(1);
|
|
close(2);
|
|
}
|
|
|
|
/* write pid file */
|
|
make_pidfile();
|
|
|
|
start_threads();
|
|
op_loop(NULL);
|
|
|
|
sel_free_selector(ser2net_sel);
|
|
|
|
return 0;
|
|
}
|