Files

4389 lines
109 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 code handles the actual transfer of data between the serial
ports and the TCP ports. */
#include <sys/time.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <errno.h>
#include <syslog.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#include <fcntl.h>
#include "ser2net.h"
#include "dataxfer.h"
#include "selector.h"
#include "utils.h"
#include "telnet.h"
#include "devio.h"
#include "buffer.h"
#include "locking.h"
#include "led.h"
#define SERIAL "term"
#define NET "tcp "
/** BASED ON sshd.c FROM openssh.com */
#ifdef HAVE_TCPD_H
#include <tcpd.h>
static char *progname = "ser2net";
#endif /* HAVE_TCPD_H */
/* States for the net_to_dev_state and dev_to_net_state. */
#define PORT_UNCONNECTED 0 /* The TCP port is not connected
to anything right now. */
#define PORT_WAITING_INPUT 1 /* Waiting for input from the
input side. */
#define PORT_WAITING_OUTPUT_CLEAR 2 /* Waiting for output to clear
so I can send data. */
#define PORT_CLOSING 3 /* Waiting for output close
string to be sent. */
char *state_str[] = { "unconnected", "waiting input", "waiting output",
"closing" };
#define PORT_DISABLED 0 /* The port is not open. */
#define PORT_RAW 1 /* Port will not do telnet negotiation. */
#define PORT_RAWLP 2 /* Port will not do telnet negotiation and
termios setting, open for output only. */
#define PORT_TELNET 3 /* Port will do telnet negotiation. */
char *enabled_str[] = { "off", "raw", "rawlp", "telnet" };
typedef struct trace_info_s
{
int hexdump; /* output each block as a hexdump */
int timestamp; /* preceed each line with a timestamp */
char *filename; /* open file. NULL if not used */
int fd; /* open file. -1 if not used */
} trace_info_t;
typedef struct port_info port_info_t;
typedef struct net_info net_info_t;
struct net_info {
port_info_t *port; /* My port. */
bool closing; /* Is the connection in the process
of closing? */
int fd; /* When connected, the file
descriptor for the network
port used for I/O. */
bool remote_fixed; /* Tells if the remote address was
set in the configuration, and
cannot be changed. */
struct sockaddr_storage remote; /* The socket address of who
is connected to this port. */
struct sockaddr *raddr; /* Points to remote, for convenience. */
socklen_t raddrlen;
struct sockaddr *udpraddr; /* Points to remote, only for UDP,
so sendto's addr will be NULL on
TCP. */
socklen_t udpraddrlen;
unsigned int bytes_received; /* Number of bytes read from the
network port. */
unsigned int bytes_sent; /* Number of bytes written to the
network port. */
struct sbuf *banner; /* Outgoing banner */
unsigned int write_pos; /* Our current position in the
output buffer where we need
to start writing next. */
bool data_to_write; /* There is data to write on
this network port. */
void (*write_handler)(port_info_t *, net_info_t *);
/* Data for the telnet processing */
telnet_data_t tn_data;
bool sending_tn_data; /* Are we sending tn data at the moment? */
int timeout_left; /* The amount of time left (in
seconds) before the timeout
goes off. */
sel_runner_t *runshutdown; /* Used to run things at the
base context. This way we
don't have to worry that we
are running inside a
handler context that needs
to be waited for exit. */
/*
* If a user gets kicked, store the information for the new user
* here since we have already accepted the connection or received
* the packet, we have to store it someplace.
*/
int new_fd;
struct sockaddr_storage new_remote;
socklen_t new_raddrlen;
unsigned char *new_buf;
int new_buf_len;
};
struct port_remaddr
{
char *name;
struct sockaddr_storage addr;
socklen_t addrlen;
bool is_port_set;
struct port_remaddr *next;
};
struct port_info
{
DEFINE_LOCK(, lock)
int enabled; /* If PORT_DISABLED, the port
is disabled and the TCP
accept port is not
operational. If PORT_RAW,
the port is enabled and
will not do any telnet
negotiations. If
PORT_RAWLP, the port is enabled
only for output without any
termios setting - it allows
to redirect /dev/lpX devices If
PORT_TELNET, the port is
enabled and it will do
telnet negotiations. */
int timeout; /* The number of seconds to
wait without any I/O before
we shut the port down. */
sel_timer_t *timer; /* Used to timeout when the no
I/O has been seen for a
certain period of time. */
sel_timer_t *send_timer; /* Used to delay a bit when
waiting for characters to
batch up as many characters
as possible. */
bool send_timer_running;
sel_runner_t *runshutdown; /* Used to run things at the
base context. This way we
don't have to worry that we
are running inside a
handler context that needs
to be waited for exit. */
int chardelay; /* The amount of time to wait after
receiving a character before
sending it, unless we receive
another character. Based on
bit rate. */
int bps; /* Bits per second rate. */
int bpc; /* Bits per character. */
bool enable_chardelay;
int chardelay_scale; /* The number of character
periods to wait for the
next character, in tenths of
a character period. */
int chardelay_min; /* The minimum chardelay, in
microseconds. */
int chardelay_max; /* Maximum amount of time to
wait before sending the data. */
struct timeval send_time; /* When using chardelay, the
time when we will send the
data, no matter what, set
by chardelay_max. */
int (*netread)(int fd, port_info_t *port, int *readerr,
net_info_t **netcon);
struct port_remaddr *remaddrs;
bool remaddr_set;
/* Information about the network port. */
char *portname; /* The name given for the port. */
int is_stdio; /* Do stdio on the port? */
struct addrinfo *ai; /* The address list for the portname. */
bool dgram; /* Is this a datagram (UDP) port? */
struct opensocks *acceptfds; /* The file descriptor used to
accept connections on the
TCP port. */
unsigned int nr_acceptfds;
waiter_t *accept_waiter; /* Wait for accept changes. */
unsigned int max_connections; /* Maximum number of TCP connections
we can accept at a time for this
port. */
net_info_t *netcons;
unsigned int dev_bytes_received; /* Number of bytes read from the
device. */
unsigned int dev_bytes_sent; /* Number of bytes written to the
device. */
/* Information use when transferring information from the network port
to the terminal device. */
int net_to_dev_state; /* State of transferring
data from the network port
to the device. */
int net_to_dev_bufsize;
struct sbuf net_to_dev; /* Buffer struct for
network to device
transfers. */
struct controller_info *net_monitor; /* If non-null, send any input
received from the network port
to this controller port. */
struct sbuf *devstr; /* Outgoing string */
/* Information use when transferring information from the terminal
device to the network port. */
int dev_to_net_state; /* State of transferring
data from the device to
the network port. */
int dev_to_net_bufsize;
struct sbuf dev_to_net; /* Buffer struct for
device to network
transfers. */
struct controller_info *dev_monitor; /* If non-null, send any input
received from the device
to this controller port. */
struct port_info *next; /* Used to keep a linked list
of these. */
int config_num; /* Keep track of what configuration this was last
updated under. Setting to -1 means to delete
the port when the current session is done. */
struct port_info *new_config; /* If the port is reconfigged while
open, this will hold the new
configuration that should be
loaded when the current session
is done. */
/* Is RFC 2217 mode enabled? */
int is_2217;
/* Masks for RFC 2217 */
unsigned char linestate_mask;
unsigned char modemstate_mask;
unsigned char last_modemstate;
/* Allow RFC 2217 mode */
int allow_2217;
/* Send a break if we get a sync command? */
int telnet_brk_on_sync;
/* kickolduser mode */
int kickolduser_mode;
/* Banner to display at startup, or NULL if none. */
char *bannerstr;
/* RFC 2217 signature. */
char *signaturestr;
/* String to send to device at startup, or NULL if none. */
char *openstr;
/* String to send to device at close, or NULL if none. */
char *closestr;
/*
* Close on string to shutdown connection when received from
* serial side, or NULL if none.
*/
char *closeon;
unsigned int closeon_pos;
unsigned int closeon_len;
/*
* Close the session when all the output has been written to the
* network port.
*/
bool close_on_output_done;
/*
* File to read/write trace, NULL if none. If the same, then
* trace information is in the same file, only one open is done.
*/
trace_info_t trace_read;
trace_info_t trace_write;
trace_info_t trace_both;
/*
* Pointers to the above, that way if two are the same file we can just
* set up one and point both to it.
*/
trace_info_t *tr;
trace_info_t *tw;
trace_info_t *tb;
struct devio io; /* For handling I/O operation to the device */
void (*dev_write_handler)(port_info_t *);
/*
* devname as specified on the line, not the substituted version. Only
*non-null if devname was substituted.
*/
char *orig_devname;
#if HAVE_DECL_TIOCSRS485
struct serial_rs485 *rs485conf;
#endif
/*
* LED to flash for serial traffic
*/
struct led_s *led_tx;
struct led_s *led_rx;
};
#define for_each_connection(port, netcon) \
for (netcon = port->netcons; \
netcon < &(port->netcons[port->max_connections]); \
netcon++)
DEFINE_LOCK_INIT(static, ports_lock)
port_info_t *ports = NULL; /* Linked list of ports. */
static void shutdown_one_netcon(net_info_t *netcon, char *reason);
static void shutdown_port(port_info_t *port, char *reason);
static void disable_accept_ports(port_info_t *port);
static void enable_accept_ports(port_info_t *port);
/* The init sequence we use. */
static unsigned char telnet_init_seq[] = {
TN_IAC, TN_WILL, TN_OPT_SUPPRESS_GO_AHEAD,
TN_IAC, TN_WILL, TN_OPT_ECHO,
TN_IAC, TN_DONT, TN_OPT_ECHO,
TN_IAC, TN_DO, TN_OPT_BINARY_TRANSMISSION,
TN_IAC, TN_WILL, TN_OPT_BINARY_TRANSMISSION,
};
/* Our telnet command table. */
static void com_port_handler(void *cb_data, unsigned char *option, int len);
static int com_port_will(void *cb_data);
static struct telnet_cmd telnet_cmds[] =
{
/* I will, I do, sent will, sent do */
{ TN_OPT_SUPPRESS_GO_AHEAD, 0, 1, 1, 0, },
{ TN_OPT_ECHO, 0, 1, 1, 1, },
{ TN_OPT_BINARY_TRANSMISSION, 1, 1, 1, 1, },
{ TN_OPT_COM_PORT, 1, 1, 0, 0, 0, 0,
com_port_handler, com_port_will },
{ TELNET_CMD_END_OPTION }
};
static int
syslog_eprint(struct absout *e, const char *str, ...)
{
va_list ap;
va_start(ap, str);
vsyslog(LOG_ERR, str, ap);
va_end(ap);
return 0;
}
static struct absout syslog_eout = {
.out = syslog_eprint,
};
static void
add_usec_to_timeval(struct timeval *tv, int usec)
{
tv->tv_usec += usec;
while (usec >= 1000000) {
usec -= 1000000;
tv->tv_sec += 1;
}
}
static int
sub_timeval_us(struct timeval *left,
struct timeval *right)
{
struct timeval dest;
dest.tv_sec = left->tv_sec - right->tv_sec;
dest.tv_usec = left->tv_usec - right->tv_usec;
while (dest.tv_usec < 0) {
dest.tv_usec += 1000000;
dest.tv_sec--;
}
return (dest.tv_sec * 1000000) + dest.tv_usec;
}
/*
* Generic output function for using a controller output for
* abstract I/O.
*/
static int
cntrl_absout(struct absout *o, const char *str, ...)
{
va_list ap;
int rv;
va_start(ap, str);
rv = controller_voutputf(o->data, str, ap);
va_end(ap);
return rv;
}
/*
* Like above but does a new line at the end of the output, generally
* for error output.
*/
static int
cntrl_abserrout(struct absout *o, const char *str, ...)
{
va_list ap;
int rv;
va_start(ap, str);
rv = controller_voutputf(o->data, str, ap);
va_end(ap);
rv += controller_outputf(o->data, "\r\n");
return rv;
}
static int
num_connected_net(port_info_t *port, bool include_closing)
{
net_info_t *netcon;
int count = 0;
for_each_connection(port, netcon) {
if (!include_closing && netcon->closing)
continue;
if (netcon->fd != -1)
count++;
}
return count;
}
static net_info_t *
first_live_net_con(port_info_t *port)
{
net_info_t *netcon;
for_each_connection(port, netcon) {
if (netcon->fd != -1)
return netcon;
}
return NULL;
}
static int
init_port_data(port_info_t *port)
{
port->enabled = PORT_DISABLED;
port->net_to_dev_state = PORT_UNCONNECTED;
port->dev_to_net_state = PORT_UNCONNECTED;
port->trace_read.fd = -1;
port->trace_write.fd = -1;
port->trace_both.fd = -1;
#if HAVE_DECL_TIOCSRS485
port->rs485conf = NULL;
#endif
port->allow_2217 = find_default_int("remctl");
port->telnet_brk_on_sync = find_default_int("telnet_brk_on_sync");
port->kickolduser_mode = find_default_int("kickolduser");
port->enable_chardelay = find_default_int("chardelay");
port->chardelay_scale = find_default_int("chardelay-scale");
port->chardelay_min = find_default_int("chardelay-min");
port->chardelay_max = find_default_int("chardelay-max");
port->dev_to_net_bufsize = find_default_int("dev-to-net-bufsize");
port->net_to_dev_bufsize = find_default_int("net-to-dev-bufsize");
port->max_connections = find_default_int("max-connections");
if (buffer_init(&port->net_to_dev, NULL, port->net_to_dev_bufsize))
return ENOMEM;
if (buffer_init(&port->dev_to_net, NULL, port->dev_to_net_bufsize))
return ENOMEM;
port->led_tx = NULL;
port->led_rx = NULL;
return 0;
}
static void
reset_timer(net_info_t *netcon)
{
netcon->timeout_left = netcon->port->timeout;
}
static int
timestamp(trace_info_t *t, char *buf, int size)
{
time_t result;
if (!t->timestamp)
return 0;
result = time(NULL);
return strftime(buf, size, "%Y/%m/%d %H:%M:%S ", localtime(&result));
}
static int
trace_write_end(char *out, int size, unsigned char *start, int col)
{
int pos = 0, w;
strncat(out, " |", size - pos);
pos += 2;
for(w = 0; w < col; w++) {
pos += snprintf(out + pos, size - pos, "%c",
isprint(start[w]) ? start[w] : '.');
}
strncat(out + pos, "|\n", size - pos);
pos += 2;
return pos;
}
int
trace_write(port_info_t *port, trace_info_t *t, unsigned char *buf,
unsigned int buf_len, char *prefix)
{
int rv = 0, w, col = 0, pos, file = t->fd;
unsigned int q;
static char out[1024];
unsigned char *start;
if (buf_len == 0)
return 0;
if (!t->hexdump)
return write(file, buf, buf_len);
pos = timestamp(t, out, sizeof(out));
pos += snprintf(out + pos, sizeof(out) - pos, "%s ", prefix);
start = buf;
for (q = 0; q < buf_len; q++) {
pos += snprintf(out + pos, sizeof(out) - pos, "%02x ", buf[q]);
col++;
if (col >= 8) {
trace_write_end(out + pos, sizeof(out) - pos, start, col);
rv = write(file, out, strlen(out));
if (rv < 0)
return rv;
pos = timestamp(t, out, sizeof(out));
pos += snprintf(out + pos, sizeof(out) - pos, "%s ", prefix);
col = 0;
start = buf + q + 1;
}
}
if (col > 0) {
for (w = 8; w > col; w--) {
strncat(out + pos, " ", sizeof(out) - pos);
pos += 3;
}
trace_write_end(out + pos, sizeof(out) - pos, start, col);
rv = write(file, out, strlen(out));
if (rv < 0)
return rv;
}
return buf_len;
}
static void
do_trace(port_info_t *port, trace_info_t *t, unsigned char *buf,
unsigned int buf_len, char *prefix)
{
int rv;
while (buf_len > 0) {
retry_write:
rv = trace_write(port, t, buf, buf_len, prefix);
if (rv == -1) {
char errbuf[128];
int err = errno;
if (err == EINTR)
goto retry_write;
/* Fatal error writing to the file, log it and close the file. */
if (strerror_r(err, errbuf, sizeof(errbuf)) == -1)
syslog(LOG_ERR, "Unable write to trace file on port %s: %d",
port->portname, err);
else
syslog(LOG_ERR, "Unable to write to trace file on port %s: %s",
port->portname, errbuf);
close(t->fd);
t->fd = -1;
return;
}
/* Handle a partial write */
buf_len -= rv;
buf += rv;
}
}
static void
hf_out(port_info_t *port, char *buf, int len)
{
if (port->tr && port->tr->timestamp)
write_ignore_fail(port->tr->fd, buf, len);
/* don't output to write file if it's the same as read file */
if (port->tw && port->tw != port->tr && port->tw->timestamp)
write_ignore_fail(port->tw->fd, buf, len);
/* don't output to both file if it's the same as read or write file */
if (port->tb && port->tb != port->tr && port->tb != port->tw
&& port->tb->timestamp)
write_ignore_fail(port->tb->fd, buf, len);
}
static void
header_trace(port_info_t *port, net_info_t *netcon)
{
static char buf[1024];
static trace_info_t tr = { 1, 1, NULL, -1 };
int len = 0, err;
char portstr[NI_MAXSERV];
len += timestamp(&tr, buf, sizeof(buf));
len += snprintf(buf + len, sizeof(buf) - len, "OPEN (");
err = getnameinfo(netcon->raddr, netcon->raddrlen,
buf + len, sizeof(buf) - len,
portstr, sizeof(portstr), NI_NUMERICHOST);
if (err) {
len += snprintf(buf + len, sizeof(buf) - len,
"unknown:%s\n", gai_strerror(err));
} else {
len += strlen(buf + len);
if ((sizeof(buf) - len) > 2) {
buf[len] = ':';
len++;
}
strncpy(buf + len, portstr, sizeof(buf) - len);
len += strlen(buf + len);
}
len += snprintf(buf + len, sizeof(buf) - len, ")\n");
hf_out(port, buf, len);
}
static void
footer_trace(port_info_t *port, char *type, char *reason)
{
static char buf[1024];
static trace_info_t tr = { 1, 1, NULL, -1 };
int len = 0;
len += timestamp(&tr, buf, sizeof(buf));
len += snprintf(buf + len, sizeof(buf), "CLOSE %s (%s)\n", type, reason);
hf_out(port, buf, len);
}
static bool
any_net_data_to_write(port_info_t *port)
{
net_info_t *netcon;
for_each_connection(port, netcon) {
if (netcon->data_to_write)
return true;
}
return false;
}
static void
handle_net_send_one(port_info_t *port, net_info_t *netcon)
{
int count = 0;
if (netcon->sending_tn_data || netcon->banner)
/* We are sending telnet or banner data, stop the reader for now. */
goto no_send;
retry_write:
count = net_write(netcon->fd,
port->dev_to_net.buf, port->dev_to_net.cursize,
0, netcon->udpraddr, netcon->udpraddrlen);
if (count == -1) {
if (errno == EINTR) {
/* EINTR means we were interrupted, just retry. */
goto retry_write;
}
if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* This was due to O_NONBLOCK, we need to shut off the reader
and start the writer monitor. */
count = 0;
goto no_send;
} else if (errno == EPIPE) {
shutdown_one_netcon(netcon, "EPIPE");
} else {
/* Some other bad error. */
syslog(LOG_ERR, "The network write for port %s had error: %m",
port->portname);
shutdown_one_netcon(netcon, "network write error");
}
} else {
netcon->bytes_sent += count;
if (port->dev_to_net.cursize != count) {
/* We didn't write all the data, shut off the reader and
start the write monitor. */
no_send:
netcon->write_pos = count;
netcon->data_to_write = true;
port->io.f->read_handler_enable(&port->io, 0);
sel_set_fd_write_handler(ser2net_sel, netcon->fd,
SEL_FD_HANDLER_ENABLED);
port->dev_to_net_state = PORT_WAITING_OUTPUT_CLEAR;
} else if (port->close_on_output_done) {
shutdown_one_netcon(netcon, "closeon sequence found");
}
}
reset_timer(netcon);
}
static void
handle_net_send(port_info_t *port)
{
net_info_t *netcon;
for_each_connection(port, netcon) {
if (netcon->fd == -1)
continue;
handle_net_send_one(port, netcon);
}
if (!any_net_data_to_write(port))
port->dev_to_net.cursize = 0;
}
void
send_timeout(struct selector_s *sel,
sel_timer_t *timer,
void *data)
{
port_info_t *port = (port_info_t *) data;
LOCK(port->lock);
if (port->dev_to_net_state == PORT_CLOSING) {
UNLOCK(port->lock);
return;
}
port->send_timer_running = false;
if (port->dev_to_net.cursize > 0)
handle_net_send(port);
UNLOCK(port->lock);
}
static void
disable_all_net_read(port_info_t *port)
{
net_info_t *netcon;
for_each_connection(port, netcon) {
if (netcon->fd != -1)
sel_set_fd_read_handler(ser2net_sel, netcon->fd,
SEL_FD_HANDLER_DISABLED);
}
if (port->dgram)
disable_accept_ports(port);
}
static void
enable_all_net_read(port_info_t *port)
{
net_info_t *netcon;
for_each_connection(port, netcon) {
if (netcon->fd != -1)
sel_set_fd_read_handler(ser2net_sel, netcon->fd,
SEL_FD_HANDLER_ENABLED);
}
if (port->dgram)
enable_accept_ports(port);
}
/* Data is ready to read on the serial port. */
static void
handle_dev_fd_read(struct devio *io)
{
port_info_t *port = (port_info_t *) io->user_data;
int count;
int curend;
bool send_now = false;
LOCK(port->lock);
curend = port->dev_to_net.cursize;
if (port->enabled == PORT_TELNET) {
/* Leave room for IACs. */
count = port->io.f->read(&port->io, port->dev_to_net.buf + curend,
(port->dev_to_net.maxsize - curend) / 2);
} else {
count = port->io.f->read(&port->io, port->dev_to_net.buf + curend,
port->dev_to_net.maxsize - curend);
}
if (count <= 0) {
if (curend != 0) {
/* We still have data to send. */
send_now = true;
count = 0;
goto do_send;
}
if (count < 0) {
if (errno == EAGAIN || errno == EWOULDBLOCK)
/* Nothing to read, just return. */
goto out_unlock;
/* Got an error on the read, shut down the port. */
syslog(LOG_ERR, "dev read error for device %s: %m", port->portname);
shutdown_port(port, "dev read error");
} else if (count == 0) {
/* The port got closed somehow, shut it down. */
shutdown_port(port, "closed port");
}
goto out_unlock;
}
if (port->dev_monitor != NULL && count > 0) {
controller_write(port->dev_monitor,
(char *) port->dev_to_net.buf + curend,
count);
}
do_send:
if (port->closeon) {
int i;
for (i = curend; i < curend + count; i++) {
if (port->dev_to_net.buf[i] == port->closeon[port->closeon_pos]) {
port->closeon_pos++;
if (port->closeon_pos >= port->closeon_len) {
port->close_on_output_done = true;
/* Ignore everything after the closeon string */
count = i - curend + 1;
break;
}
} else {
port->closeon_pos = 0;
}
}
}
if (port->tr)
/* Do read tracing, ignore errors. */
do_trace(port, port->tr, port->dev_to_net.buf + curend, count, SERIAL);
if (port->tb)
/* Do both tracing, ignore errors. */
do_trace(port, port->tb, port->dev_to_net.buf + curend, count, SERIAL);
if (port->led_rx) {
led_flash(port->led_rx);
}
port->dev_bytes_received += count;
if (port->enabled == PORT_TELNET) {
int i, j;
/* Double the IACs on a telnet stream. This will always fit because
we only use half the buffer for telnet connections. */
for (i = curend; i < curend + count; i++) {
if (port->dev_to_net.buf[i] == TN_IAC) {
for (j = curend + count; j > i; j--)
port->dev_to_net.buf[j] = port->dev_to_net.buf[j - 1];
/* Last copy above will duplicate the IAC */
count++;
i++;
}
}
}
port->dev_to_net.cursize += count;
if (send_now || port->dev_to_net.cursize == port->dev_to_net.maxsize ||
port->chardelay == 0) {
send_it:
handle_net_send(port);
} else {
struct timeval then;
int delay;
sel_get_monotonic_time(&then);
if (port->send_timer_running) {
sel_stop_timer(port->send_timer);
} else {
port->send_time = then;
add_usec_to_timeval(&port->send_time, port->chardelay_max);
}
delay = sub_timeval_us(&port->send_time, &then);
if (delay > port->chardelay)
delay = port->chardelay;
else if (delay < 0) {
port->send_timer_running = false;
goto send_it;
}
add_usec_to_timeval(&then, delay);
sel_start_timer(port->send_timer, &then);
port->send_timer_running = true;
}
out_unlock:
UNLOCK(port->lock);
}
/* The serial port has room to write some data. This is only activated
if a write fails to complete, it is deactivated as soon as writing
is available again. */
static void
dev_fd_write(port_info_t *port, struct sbuf *buf)
{
int reterr, buferr;
reterr = buffer_io_write(&port->io, buf, &buferr);
if (reterr == -1) {
syslog(LOG_ERR, "The dev write for port %s had error: %m",
port->portname);
shutdown_port(port, "dev write error");
return;
}
if (buffer_cursize(buf) == 0) {
/* We are done writing, turn the reader back on. */
enable_all_net_read(port);
port->io.f->write_handler_enable(&port->io, 0);
port->net_to_dev_state = PORT_WAITING_INPUT;
}
}
static void
handle_dev_fd_normal_write(port_info_t *port)
{
dev_fd_write(port, &port->net_to_dev);
}
static void
handle_dev_fd_write(struct devio *io)
{
port_info_t *port = (port_info_t *) io->user_data;
LOCK(port->lock);
port->dev_write_handler(port);
UNLOCK(port->lock);
}
/* Handle an exception from the serial port. */
static void
handle_dev_fd_except(struct devio *io)
{
port_info_t *port = (port_info_t *) io->user_data;
LOCK(port->lock);
syslog(LOG_ERR, "Select exception on device for port %s",
port->portname);
shutdown_port(port, "fd exception");
UNLOCK(port->lock);
}
/* Output the devstr buffer */
static void
handle_dev_fd_devstr_write(port_info_t *port)
{
dev_fd_write(port, port->devstr);
if (buffer_cursize(port->devstr) == 0) {
port->dev_write_handler = handle_dev_fd_normal_write;
free(port->devstr->buf);
free(port->devstr);
port->devstr = NULL;
/* Send out any data we got on the TCP port. */
handle_dev_fd_normal_write(port);
}
}
static void
net_fd_read2(port_info_t *port, net_info_t *netcon, int count)
{
port->net_to_dev.cursize = count;
netcon->bytes_received += count;
if (port->enabled == PORT_TELNET) {
port->net_to_dev.cursize = process_telnet_data(port->net_to_dev.buf,
count,
&netcon->tn_data);
if (netcon->tn_data.error) {
shutdown_one_netcon(netcon, "telnet output error");
return;
}
if (port->net_to_dev.cursize == 0) {
/* We are out of characters; they were all processed. We
don't want to continue with 0, because that will mess
up the other processing and it's not necessary. */
return;
}
}
if (port->net_monitor != NULL) {
controller_write(port->net_monitor,
(char *) port->net_to_dev.buf,
port->net_to_dev.cursize);
}
if (port->tw)
/* Do write tracing, ignore errors. */
do_trace(port, port->tw,
port->net_to_dev.buf, port->net_to_dev.cursize, NET);
if (port->tb)
/* Do both tracing, ignore errors. */
do_trace(port, port->tb,
port->net_to_dev.buf, port->net_to_dev.cursize, NET);
retry_write:
/*
* Don't write anything to the device until devstr is written.
* This can happen on UDP ports, we get the first packet before
* the port is enabled, so there will be data in the output buffer
* but there will also possibly be devstr data. We want the
* devstr data to go out first.
*/
if (port->devstr)
goto stop_write;
count = port->io.f->write(&port->io, port->net_to_dev.buf,
port->net_to_dev.cursize);
if (count == -1) {
if (errno == EINTR) {
/* EINTR means we were interrupted, just retry. */
goto retry_write;
}
if (errno == EAGAIN || errno == EWOULDBLOCK) {
/* This was due to O_NONBLOCK, we need to shut off the reader
and start the writer monitor. */
stop_write:
disable_all_net_read(port);
port->io.f->write_handler_enable(&port->io, 1);
port->net_to_dev_state = PORT_WAITING_OUTPUT_CLEAR;
} else {
/* Some other bad error. */
syslog(LOG_ERR, "The dev write for port %s had error: %m",
port->portname);
shutdown_port(port, "dev write error");
return;
}
} else {
if (port->led_tx) {
led_flash(port->led_tx);
}
port->dev_bytes_sent += count;
port->net_to_dev.cursize -= count;
if (port->net_to_dev.cursize != 0) {
/* We didn't write all the data, shut off the reader and
start the write monitor. */
port->net_to_dev.pos = count;
disable_all_net_read(port);
port->io.f->write_handler_enable(&port->io, 1);
port->net_to_dev_state = PORT_WAITING_OUTPUT_CLEAR;
}
}
reset_timer(netcon);
}
/* Data is ready to read on the network port. */
static void
handle_net_fd_read(int fd, void *data)
{
net_info_t *netcon = data;
port_info_t *port = netcon->port;
int count, readerr = 0;
char *reason;
LOCK(port->lock);
if (port->net_to_dev_state == PORT_WAITING_OUTPUT_CLEAR)
/* Catch a race here. */
goto out_unlock;
port->net_to_dev.pos = 0;
/*
* Note that netcon->fd can be -1 in this case, if it's an
* unconnected UDP port. That's why we read from "fd" here. If
* it's UDP, the fd will be duplicated.
*/
count = port->netread(fd, port, &readerr, &netcon);
if (count < 0) {
if (readerr == EAGAIN || readerr == EWOULDBLOCK)
/* Nothing to read, just return. */
goto out_unlock;
/* Got an error on the read, shut down the port. */
syslog(LOG_ERR, "read error for port %s: %s", port->portname,
strerror(readerr));
reason = "network read error";
goto out_shutdown;
} else if (count == 0 && !port->dgram) {
/* The other end closed the port, shut it down. */
reason = "network read close";
goto out_shutdown;
}
net_fd_read2(port, netcon, count);
out_unlock:
UNLOCK(port->lock);
return;
out_shutdown:
shutdown_one_netcon(netcon, reason);
goto out_unlock;
}
void io_enable_read_handler(port_info_t *port)
{
port->io.f->read_handler_enable(&port->io,
port->enabled != PORT_RAWLP);
}
/*
* The network fd has room to write some data. This is only activated
* if a write fails to complete, it is deactivated as soon as writing
* is available again.
*/
static void
net_fd_write(port_info_t *port, net_info_t *netcon,
struct sbuf *buf, unsigned int *pos)
{
telnet_data_t *td = &netcon->tn_data;
int buferr, reterr, to_send;
if (netcon->sending_tn_data) {
send_tn_data:
reterr = buffer_sendto(netcon->fd,
netcon->udpraddr, netcon->udpraddrlen,
&td->out_telnet_cmd, &buferr);
if (reterr == -1) {
if (buferr == EPIPE) {
shutdown_one_netcon(netcon, "EPIPE");
return;
} else {
/* Some other bad error. */
syslog(LOG_ERR, "The network write for port %s had error: %m",
port->portname);
shutdown_one_netcon(netcon, "network write error");
return;
}
}
if (buffer_cursize(&td->out_telnet_cmd) > 0) {
/* If we have more telnet command data to send, don't
send any real data. */
return;
}
netcon->sending_tn_data = false;
if (!any_net_data_to_write(port))
io_enable_read_handler(port);
}
to_send = buf->cursize - *pos;
if (to_send <= 0)
/* Don't send empty packets, that can confuse UDP clients. */
return;
reterr = net_write(netcon->fd, buf->buf + *pos, to_send,
0, netcon->udpraddr, netcon->udpraddrlen);
if (reterr == -1) {
if (errno == EPIPE) {
shutdown_one_netcon(netcon, "EPIPE");
return;
} else {
/* Some other bad error. */
syslog(LOG_ERR, "The network write for port %s had error: %m",
port->portname);
shutdown_one_netcon(netcon, "network write error");
return;
}
}
*pos += reterr;
if (*pos >= buf->cursize) {
netcon->data_to_write = false;
/* Start telnet data write when the data write is done. */
if (buffer_cursize(&td->out_telnet_cmd) > 0) {
netcon->sending_tn_data = true;
goto send_tn_data;
}
if (any_net_data_to_write(port))
goto out;
port->dev_to_net.cursize = 0;
/* We are done writing, turn the reader back on. */
io_enable_read_handler(port);
sel_set_fd_write_handler(ser2net_sel, netcon->fd,
SEL_FD_HANDLER_DISABLED);
port->dev_to_net_state = PORT_WAITING_INPUT;
if (port->close_on_output_done) {
shutdown_one_netcon(netcon, "closeon sequence found");
return;
}
}
out:
reset_timer(netcon);
}
/* The network fd has room to write some data. This is only activated
if a write fails to complete, it is deactivated as soon as writing
is available again. */
static void
handle_net_fd_write(port_info_t *port, net_info_t *netcon)
{
net_fd_write(port, netcon,
&port->dev_to_net, &netcon->write_pos);
}
static void
handle_net_fd_write_mux(int fd, void *data)
{
net_info_t *netcon = data;
port_info_t *port = netcon->port;
LOCK(port->lock);
netcon->write_handler(port, netcon);
UNLOCK(port->lock);
}
/* Handle an exception from the network port. */
static void
handle_net_fd_except(int fd, void *data)
{
net_info_t *netcon = data;
port_info_t *port = netcon->port;
int rv, val;
unsigned char c;
int cmd_pos;
/* We should have urgent data, a DATA MARK in the stream. Read
then urgent data (whose contents are irrelevant) then discard
user data until we find the DATA_MARK command. */
LOCK(port->lock);
while ((rv = recv(fd, &c, 1, MSG_OOB)) > 0)
;
/* Ignore any errors, they are irrelevant. */
if (port->enabled != PORT_TELNET)
goto out;
/* Flush the data in the local and device queue. */
port->net_to_dev.cursize = 0;
val = 0;
port->io.f->flush(&port->io, &val);
/* Store it if we last got an IAC, and abort any current
telnet processing. */
cmd_pos = netcon->tn_data.telnet_cmd_pos;
if (cmd_pos != 1)
cmd_pos = 0;
netcon->tn_data.telnet_cmd_pos = 0;
netcon->tn_data.suboption_iac = 0;
while ((rv = read(fd, &c, 1)) > 0) {
if (cmd_pos == 1) {
if (c == TN_DATA_MARK) {
/* Found it. */
if (port->telnet_brk_on_sync)
port->io.f->send_break(&port->io);
break;
}
cmd_pos = 0;
} else if (c == TN_IAC) {
cmd_pos = 1;
}
}
out:
UNLOCK(port->lock);
}
static void port_net_fd_cleared(int fd, void *cb_data);
static void
handle_net_fd_banner_write(port_info_t *port, net_info_t *netcon)
{
net_fd_write(port, netcon, netcon->banner, &netcon->banner->pos);
if (netcon->banner->pos >= netcon->banner->cursize) {
netcon->write_handler = handle_net_fd_write;
free(netcon->banner->buf);
free(netcon->banner);
netcon->banner = NULL;
handle_net_fd_write(port, netcon);
}
}
static void
telnet_cmd_handler(void *cb_data, unsigned char cmd)
{
net_info_t *netcon = cb_data;
port_info_t *port = netcon->port;
if ((cmd == TN_BREAK) || (port->telnet_brk_on_sync && cmd == TN_DATA_MARK))
port->io.f->send_break(&port->io);
}
/* Called when the telnet code has output ready. */
static void
telnet_output_ready(void *cb_data)
{
net_info_t *netcon = cb_data;
port_info_t *port = netcon->port;
/* If we are currently sending some data, wait until it is done.
It might have IACs in it, and we don't want to split those. */
if (buffer_cursize(&port->dev_to_net) != 0)
return;
netcon->sending_tn_data = true;
sel_set_fd_write_handler(ser2net_sel, netcon->fd,
SEL_FD_HANDLER_ENABLED);
}
/* Checks to see if some other port has the same device in use. Must
be called with ports_lock held. */
static int
is_device_already_inuse(port_info_t *check_port)
{
port_info_t *port = ports;
while (port != NULL) {
if (port != check_port) {
if ((strcmp(port->io.devname, check_port->io.devname) == 0)
&& (port->net_to_dev_state != PORT_UNCONNECTED))
{
return 1;
}
}
port = port->next;
}
return 0;
}
static int
from_hex_digit(char c)
{
if ((c >= '0') && (c <= '9'))
return c - '0';
if ((c >= 'A') && (c <= 'F'))
return c - 'A' + 10;
if ((c >= 'a') && (c <= 'f'))
return c - 'a' + 10;
return 0;
}
static char *smonths[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
static char *sdays[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
static void
process_str(port_info_t *port, net_info_t *netcon,
struct tm *time, struct timeval *tv,
const char *s,
void (*op)(void *data, char val), void *data, int isfilename)
{
char val;
char *t, *s2;
while (*s) {
if (*s == '\\') {
s++;
if (!*s)
return;
switch (*s) {
/* Standard "C" characters. */
case 'a': op(data, 7); break;
case 'b': op(data, 8); break;
case 'f': op(data, 12); break;
case 'n': op(data, 10); break;
case 'r': op(data, 13); break;
case 't': op(data, 9); break;
case 'v': op(data, 11); break;
case '\\': op(data, '\\'); break;
case '?': op(data, '?'); break;
case '\'': op(data, '\''); break;
case '"': op(data, '"'); break;
case 'd': /* Actual device name */
case 'o': /* Device name on config line */
/* ser2net device name. */
if (*s == 'o' && port->orig_devname)
s2 = port->orig_devname;
else
s2 = port->io.devname;
if (isfilename) {
/* Can't have '/' in a filename. */
t = strrchr(s2, '/');
if (t)
t++;
else
t = s2;
} else
t = s2;
for (; *t; t++)
op(data, *t);
break;
case 'p':
/* ser2net network port. */
for (t = port->portname; *t; t++)
op(data, *t);
break;
case 's':
if (isfilename)
goto seconds;
goto serparms;
case 'B':
serparms:
/* ser2net serial parms. */
{
char str[15];
port->io.f->serparm_to_str(&port->io, str, sizeof(str));
for (t = str; *t; t++)
op(data, *t);
}
break;
case '0': case '1': case '2': case '3': case '4': case '5':
case '6': case '7':
/* Octal digit */
val = (*s) - '0';
s++;
if (!*s) {
op(data, val);
return;
}
if (!isdigit(*s)) {
continue;
}
val = (val * 8) + (*s) - '0';
s++;
if (!*s) {
op(data, val);
return;
}
if (!isdigit(*s)) {
continue;
}
val = (val * 8) + (*s) - '0';
op(data, val);
break;
case 'x':
/* Hex digit */
s++;
if (!*s)
return;
if (!isxdigit(*s))
continue;
val = from_hex_digit(*s);
s++;
if (!*s) {
op(data, val);
return;
}
if (!isdigit(*s))
continue;
val = (val * 16) + from_hex_digit(*s);
op(data, val);
break;
/* \Y -> year */
case 'Y':
{
char d[10], *dp;
snprintf(d, sizeof(d), "%d", time->tm_year + 1900);
for (dp = d; *dp; dp++)
op(data, *dp);
break;
}
/* \y -> day of the year (days since Jan 1) */
case 'y':
{
char d[10], *dp;
snprintf(d, sizeof(d), "%d", time->tm_yday);
for (dp = d; *dp; dp++)
op(data, *dp);
break;
}
/* \M -> month (Jan, Feb, Mar, etc.) */
case 'M':
if (time->tm_mon >= 12)
op(data, '?');
else {
char *dp = smonths[time->tm_mon];
for (; *dp; dp++)
op(data, *dp);
}
break;
/* \m -> month (as a number) */
case 'm':
{
char d[10], *dp;
snprintf(d, sizeof(d), "%d", time->tm_mon);
for (dp = d; *dp; dp++)
op(data, *dp);
break;
}
/* \A -> day of the week (Mon, Tue, etc.) */
case 'A':
if (time->tm_wday >= 7)
op(data, '?');
else {
char *dp = sdays[time->tm_wday];
for (; *dp; dp++)
op(data, *dp);
}
break;
/* \D -> day of the month */
case 'D':
{
char d[10], *dp;
snprintf(d, sizeof(d), "%d", time->tm_mday);
for (dp = d; *dp; dp++)
op(data, *dp);
break;
}
/* \H -> hour (24-hour time) */
case 'H':
{
char d[10], *dp;
snprintf(d, sizeof(d), "%2.2d", time->tm_hour);
for (dp = d; *dp; dp++)
op(data, *dp);
break;
}
/* \h -> hour (12-hour time) */
case 'h':
{
char d[10], *dp;
int v;
v = time->tm_hour;
if (v == 0)
v = 12;
else if (v > 12)
v -= 12;
snprintf(d, sizeof(d), "%2.2d", v);
for (dp = d; *dp; dp++)
op(data, *dp);
break;
}
/* \i -> minute */
case 'i':
{
char d[10], *dp;
snprintf(d, sizeof(d), "%2.2d", time->tm_min);
for (dp = d; *dp; dp++)
op(data, *dp);
break;
}
/* \S -> second */
case 'S':
seconds:
{
char d[10], *dp;
snprintf(d, sizeof(d), "%2.2d", time->tm_sec);
for (dp = d; *dp; dp++)
op(data, *dp);
break;
}
/* \q -> am/pm */
case 'q':
if (time->tm_hour < 12) {
op(data, 'a');
} else {
op(data, 'p');
}
op(data, 'm');
break;
/* \P -> AM/PM */
case 'P':
if (time->tm_hour < 12) {
op(data, 'A');
} else {
op(data, 'P');
}
op(data, 'M');
break;
/* \T -> time (HH:MM:SS) */
case 'T':
{
char d[10], *dp;
snprintf(d, sizeof(d), "%2.2d:%2.2d:%2.2d",
time->tm_hour, time->tm_min, time->tm_sec);
for (dp = d; *dp; dp++)
op(data, *dp);
break;
}
/* \e -> epoc (seconds since Jan 1, 1970) */
case 'e':
{
char d[30], *dp;
snprintf(d, sizeof(d), "%ld", tv->tv_sec);
for (dp = d; *dp; dp++)
op(data, *dp);
break;
}
/* \U -> microseconds in the current second */
case 'U':
{
char d[10], *dp;
snprintf(d, sizeof(d), "%6.6ld", tv->tv_usec);
for (dp = d; *dp; dp++)
op(data, *dp);
break;
}
/* \I -> remote IP address (in dot format) */
case 'I':
{
char ip[100], *ipp;
if (!netcon)
netcon = first_live_net_con(port);
if (!netcon)
break;
if (!getnameinfo(netcon->raddr, netcon->raddrlen,
ip, sizeof(ip), NULL, 0, NI_NUMERICHOST))
break;
for (ipp = ip; *ipp; ipp++)
op(data, *ipp);
break;
}
default:
op(data, *s);
}
} else
op(data, *s);
s++;
}
}
static void
count_op(void *data, char c)
{
unsigned int *idata = data;
(*idata)++;
}
struct bufop_data {
unsigned int pos;
char *str;
};
static void
buffer_op(void *data, char c)
{
struct bufop_data *bufop = data;
bufop->str[bufop->pos] = c;
(bufop->pos)++;
}
static char *
process_str_to_str(port_info_t *port, net_info_t *netcon,
const char *str, struct timeval *tv,
unsigned int *lenrv, int isfilename)
{
unsigned int len = 0;
struct tm now;
struct bufop_data bufop;
localtime_r(&tv->tv_sec, &now);
process_str(port, netcon, &now, tv, str, count_op, &len, isfilename);
if (!lenrv)
/* If we don't return a length, append a nil char. */
len++;
bufop.pos = 0;
if (len == 0)
/* malloc(0) sometimes return NULL */
bufop.str = malloc(1);
else
bufop.str = malloc(len);
if (!bufop.str) {
syslog(LOG_ERR, "Out of memory processing string: %s", port->portname);
return NULL;
}
process_str(port, netcon, &now, tv, str, buffer_op, &bufop, isfilename);
if (lenrv)
*lenrv = len;
else
bufop.str[bufop.pos] = '\0';
return bufop.str;
}
static struct sbuf *
process_str_to_buf(port_info_t *port, net_info_t *netcon, const char *str)
{
const char *bstr;
struct sbuf *buf;
unsigned int len;
struct timeval tv;
if (!str || *str == '\0')
return NULL;
gettimeofday(&tv, NULL);
buf = malloc(sizeof(*buf));
if (!buf) {
syslog(LOG_ERR, "Out of memory processing string: %s", port->portname);
return NULL;
}
bstr = process_str_to_str(port, netcon, str, &tv, &len, 0);
if (!bstr) {
free(buf);
syslog(LOG_ERR, "Error processing string: %s", port->portname);
return NULL;
}
buffer_init(buf, (unsigned char *) bstr, len);
buf->cursize = len;
return buf;
}
static void
open_trace_file(port_info_t *port,
trace_info_t *t,
struct timeval *tv,
trace_info_t **out)
{
int rv;
char *trfile;
trfile = process_str_to_str(port, NULL, t->filename, tv, NULL, 1);
if (!trfile) {
syslog(LOG_ERR, "Unable to translate trace file %s", t->filename);
t->fd = -1;
return;
}
rv = open(trfile, O_WRONLY | O_CREAT | O_APPEND, 0600);
if (rv == -1) {
char errbuf[128];
int err = errno;
if (strerror_r(err, errbuf, sizeof(errbuf)) == -1)
syslog(LOG_ERR, "Unable to open trace file %s: %d",
trfile, err);
else
syslog(LOG_ERR, "Unable to open trace file %s: %s",
trfile, errbuf);
}
free(trfile);
t->fd = rv;
*out = t;
}
static void
setup_trace(port_info_t *port)
{
struct timeval tv;
/* Only get the time once so all trace files have consistent times. */
gettimeofday(&tv, NULL);
port->tw = NULL;
if (port->trace_write.filename)
open_trace_file(port, &port->trace_write, &tv, &port->tw);
port->tr = NULL;
if (port->trace_read.filename) {
trace_info_t *np = &port->trace_read;
if (port->tw && (strcmp(np->filename, port->tw->filename) == 0))
port->tr = port->tw;
else
open_trace_file(port, np, &tv, &port->tr);
}
port->tb = NULL;
if (port->trace_both.filename) {
trace_info_t *np = &port->trace_both;
if (port->tw && (strcmp(np->filename, port->tw->filename) == 0))
port->tb = port->tw;
else if (port->tr && (strcmp(np->filename, port->tr->filename) == 0))
port->tb = port->tr;
else
open_trace_file(port, np, &tv, &port->tb);
}
return;
}
static void
recalc_port_chardelay(port_info_t *port)
{
/* delay is (((1 / bps) * bpc) * scale) seconds */
if (!port->enable_chardelay) {
port->chardelay = 0;
return;
}
/* We are working in microseconds here. */
port->chardelay = (port->bpc * 100000 * port->chardelay_scale) / port->bps;
if (port->chardelay < port->chardelay_min)
port->chardelay = port->chardelay_min;
}
/* Called to set up a new connection's file descriptor. */
static int
setup_port(port_info_t *port, net_info_t *netcon, bool is_reconfig)
{
int options;
struct timeval then;
bool i_am_first = false;
if (fcntl(netcon->fd, F_SETFL, O_NONBLOCK) == -1) {
close(netcon->fd);
netcon->fd = -1;
syslog(LOG_ERR, "Could not fcntl the tcp port %s: %m", port->portname);
return -1;
}
if (!port->dgram) {
options = 1;
if (setsockopt(netcon->fd, IPPROTO_TCP, TCP_NODELAY,
(char *) &options, sizeof(options)) == -1) {
if (port->is_stdio)
/* Ignore this error on stdio ports. */
goto end_net_config;
close(netcon->fd);
netcon->fd = -1;
syslog(LOG_ERR, "Could not enable TCP_NODELAY tcp port %s: %m",
port->portname);
return -1;
}
}
end_net_config:
if (!is_reconfig) {
if (netcon->banner) {
free(netcon->banner->buf);
free(netcon->banner);
}
netcon->banner = process_str_to_buf(port, netcon, port->bannerstr);
}
if (netcon->banner)
netcon->write_handler = handle_net_fd_banner_write;
else
netcon->write_handler = handle_net_fd_write;
if (num_connected_net(port, true) == 1) {
/* We are first, set things up on the device. */
const char *errstr = NULL;
if (port->io.f->setup(&port->io, port->portname, &errstr,
&port->bps, &port->bpc) == -1) {
if (errstr)
write_ignore_fail(netcon->fd, errstr, strlen(errstr));
close(netcon->fd);
netcon->fd = -1;
return -1;
}
recalc_port_chardelay(port);
port->is_2217 = 0;
if (!is_reconfig) {
if (port->devstr) {
free(port->devstr->buf);
free(port->devstr);
}
port->devstr = process_str_to_buf(port, netcon, port->openstr);
}
if (port->devstr)
port->dev_write_handler = handle_dev_fd_devstr_write;
else
port->dev_write_handler = handle_dev_fd_normal_write;
port->io.read_handler = (port->enabled == PORT_RAWLP
? NULL
: handle_dev_fd_read);
port->io.write_handler = handle_dev_fd_write;
port->io.except_handler = handle_dev_fd_except;
port->io.f->except_handler_enable(&port->io, 1);
if (port->devstr)
port->io.f->write_handler_enable(&port->io, 1);
port->dev_to_net_state = PORT_WAITING_INPUT;
i_am_first = true;
}
sel_set_fd_handlers(ser2net_sel,
netcon->fd,
netcon,
handle_net_fd_read,
handle_net_fd_write_mux,
handle_net_fd_except,
port_net_fd_cleared);
sel_set_fd_read_handler(ser2net_sel, netcon->fd,
SEL_FD_HANDLER_ENABLED);
sel_set_fd_except_handler(ser2net_sel, netcon->fd,
SEL_FD_HANDLER_ENABLED);
port->net_to_dev_state = PORT_WAITING_INPUT;
if (port->enabled == PORT_TELNET) {
telnet_init(&netcon->tn_data, netcon, telnet_output_ready,
telnet_cmd_handler,
telnet_cmds,
telnet_init_seq, sizeof(telnet_init_seq));
sel_set_fd_write_handler(ser2net_sel, netcon->fd,
SEL_FD_HANDLER_ENABLED);
} else {
buffer_init(&netcon->tn_data.out_telnet_cmd,
netcon->tn_data.out_telnet_cmdbuf, 0);
if (netcon->banner)
sel_set_fd_write_handler(ser2net_sel, netcon->fd,
SEL_FD_HANDLER_ENABLED);
else if (i_am_first)
io_enable_read_handler(port);
}
if (i_am_first)
setup_trace(port);
header_trace(port, netcon);
if (i_am_first) {
sel_get_monotonic_time(&then);
then.tv_sec += 1;
sel_start_timer(port->timer, &then);
}
reset_timer(netcon);
return 0;
}
static bool
port_remaddr_ok(port_info_t *port, struct sockaddr *addr, socklen_t addrlen)
{
struct port_remaddr *r = port->remaddrs;
if (!r)
return true;
while (r) {
if (sockaddr_equal(addr, addrlen,
(struct sockaddr *) &r->addr, r->addrlen,
r->is_port_set))
break;
r = r->next;
}
return r != NULL;
}
/* Returns with the port locked, if non-NULL. */
static port_info_t *
find_rotator_port(char *portname, struct sockaddr *addr, socklen_t addrlen)
{
port_info_t *port = ports;
while (port) {
if (strcmp(port->portname, portname) == 0) {
LOCK(port->lock);
if (port->dev_to_net_state != PORT_DISABLED &&
port->dev_to_net_state != PORT_CLOSING &&
port->net_to_dev_state == PORT_UNCONNECTED &&
port_remaddr_ok(port, addr, addrlen) &&
!is_device_already_inuse(port))
return port;
UNLOCK(port->lock);
}
port = port->next;
}
return NULL;
}
static void
handle_port_accept(port_info_t *port, int new_fd, net_info_t *netcon)
{
int optval;
netcon->fd = new_fd;
optval = 1;
if (setsockopt(netcon->fd, SOL_SOCKET, SO_KEEPALIVE,
(void *)&optval, sizeof(optval)) == -1) {
close(netcon->fd);
syslog(LOG_ERR, "Could not enable SO_KEEPALIVE on tcp port %s: %m",
port->portname);
netcon->fd = -1;
return;
}
/* XXX log netcon->remote */
setup_port(port, netcon, false);
}
static const char *
check_tcpd_ok(int new_fd)
{
#ifdef HAVE_TCPD_H
struct request_info req;
request_init(&req, RQ_DAEMON, progname, RQ_FILE, new_fd, NULL);
fromhost(&req);
if (!hosts_access(&req))
return "Access denied\r\n";
#endif
return NULL;
}
typedef struct rotator
{
/* Rotators use the ports_lock for mutex. */
int curr_port;
char **portv;
int portc;
char *portname;
struct addrinfo *ai; /* The address list for the portname. */
struct opensocks *acceptfds; /* The file descriptor used to
accept connections on the
TCP port. */
unsigned int nr_acceptfds;
waiter_t *accept_waiter;
struct rotator *next;
} rotator_t;
rotator_t *rotators = NULL;
/* A connection request has come in on a port. */
static void
handle_rot_port_read(int fd, void *data)
{
rotator_t *rot = (rotator_t *) data;
int i, new_fd;
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
const char *err;
/* FIXME - handle remote address interactions? */
new_fd = accept(fd, (struct sockaddr *) &addr, &addrlen);
if (new_fd == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
syslog(LOG_ERR, "Could not accept on rotator %s: %m",
rot->portname);
return;
}
err = check_tcpd_ok(new_fd);
if (err)
goto out_err;
LOCK(ports_lock);
i = rot->curr_port;
do {
port_info_t *port = find_rotator_port(rot->portv[i],
(struct sockaddr *) &addr, addrlen);
if (++i >= rot->portc)
i = 0;
if (port) {
rot->curr_port = i;
UNLOCK(ports_lock);
handle_port_accept(port, new_fd, &(port->netcons[0]));
UNLOCK(port->lock);
return;
}
} while (i != rot->curr_port);
UNLOCK(ports_lock);
err = "No free port found\r\n";
out_err:
write_ignore_fail(new_fd, err, strlen(err));
close(new_fd);
}
static void
free_rotator(rotator_t *rot)
{
int i;
for (i = 0; i < rot->nr_acceptfds; i++) {
sel_set_fd_read_handler(ser2net_sel,
rot->acceptfds[i].fd,
SEL_FD_HANDLER_DISABLED);
sel_clear_fd_handlers(ser2net_sel, rot->acceptfds[i].fd);
wait_for_waiter(rot->accept_waiter);
close(rot->acceptfds[i].fd);
}
if (rot->accept_waiter)
free_waiter(rot->accept_waiter);
if (rot->portname)
free(rot->portname);
if (rot->ai)
freeaddrinfo(rot->ai);
if (rot->acceptfds)
free(rot->acceptfds);
if (rot->portv)
str_to_argv_free(rot->portc, rot->portv);
free(rot);
}
void
free_rotators(void)
{
rotator_t *rot, *next;
rot = rotators;
while (rot) {
next = rot->next;
free_rotator(rot);
rot = next;
}
rotators = NULL;
}
static void
rotator_fd_cleared(int fd, void *cb_data)
{
rotator_t *rot = cb_data;
wake_waiter(rot->accept_waiter);
}
int
add_rotator(char *portname, char *ports, int lineno)
{
rotator_t *rot;
int rv;
bool is_port_set;
rot = malloc(sizeof(*rot));
if (!rot)
return ENOMEM;
memset(rot, 0, sizeof(*rot));
rot->accept_waiter = alloc_waiter();
if (!rot->accept_waiter) {
free_rotator(rot);
return ENOMEM;
}
rot->portname = strdup(portname);
if (!rot->portname) {
free_waiter(rot->accept_waiter);
free_rotator(rot);
return ENOMEM;
}
rv = str_to_argv(ports, &rot->portc, &rot->portv, NULL);
if (rv)
goto out;
rv = scan_network_port(rot->portname, &rot->ai, NULL, &is_port_set);
if (rv) {
syslog(LOG_ERR, "port number was invalid on line %d", lineno);
goto out;
}
if (!is_port_set) {
syslog(LOG_ERR, "port number was zero on line %d", lineno);
goto out;
}
rot->acceptfds = open_socket(rot->ai, handle_rot_port_read, NULL, rot,
&rot->nr_acceptfds, rotator_fd_cleared);
if (rot->acceptfds == NULL) {
syslog(LOG_ERR, "Unable to create TCP socket on line %d", lineno);
rv = ENOMEM;
goto out;
}
rot->next = rotators;
rotators = rot;
out:
if (rv)
free_rotator(rot);
return rv;
}
static void
disable_accept_ports(port_info_t *port)
{
int i;
for (i = 0; i < port->nr_acceptfds; i++)
sel_set_fd_read_handler(ser2net_sel, port->acceptfds[i].fd,
SEL_FD_HANDLER_DISABLED);
}
static void
enable_accept_ports(port_info_t *port)
{
int i;
for (i = 0; i < port->nr_acceptfds; i++)
sel_set_fd_read_handler(ser2net_sel, port->acceptfds[i].fd,
SEL_FD_HANDLER_ENABLED);
}
static void
kick_old_user(port_info_t *port, net_info_t *netcon, int new_fd, int buflen,
struct sockaddr_storage *remaddr, socklen_t remaddrlen)
{
char *err = "kicked off, new user is coming\r\n";
/* If another user is waiting for a kick, kick that user. */
if (netcon->new_fd) {
net_write(netcon->new_fd, err, strlen(err), 0,
(struct sockaddr *) &netcon->new_remote,
netcon->new_raddrlen);
close(netcon->new_fd);
}
/* Wait it to be unconnected and clean, restart the process. */
netcon->new_fd = new_fd;
memcpy(&netcon->new_remote, remaddr, remaddrlen);
netcon->new_raddrlen = remaddrlen;
if (port->dgram) {
memcpy(netcon->new_buf, port->net_to_dev.buf, buflen);
netcon->new_buf_len = buflen;
}
shutdown_one_netcon(netcon, err);
}
static void
check_port_new_fd(port_info_t *port, net_info_t *netcon)
{
int fd;
if (netcon->new_fd == -1)
return;
if (netcon->fd != -1) {
/* Something snuck in before, kick this one out. */
char *err = "kicked off, new user is coming\r\n";
net_write(netcon->new_fd, err, strlen(err), 0,
(struct sockaddr *) &netcon->new_remote,
netcon->new_raddrlen);
close(netcon->new_fd);
netcon->new_fd = -1;
return;
}
fd = netcon->new_fd;
netcon->new_fd = -1;
memcpy(netcon->raddr, &netcon->new_remote, netcon->new_raddrlen);
netcon->raddrlen = netcon->new_raddrlen;
if (port->dgram) {
netcon->fd = fd;
netcon->udpraddrlen = netcon->new_raddrlen;
if (!setup_port(port, netcon, false)) {
memcpy(port->net_to_dev.buf, netcon->new_buf, netcon->new_buf_len);
net_fd_read2(port, netcon, netcon->new_buf_len);
}
} else {
handle_port_accept(port, fd, netcon);
}
}
/* A connection request has come in on a port. */
static void
handle_accept_port_read(int fd, void *data)
{
port_info_t *port = (port_info_t *) data;
const char *err = NULL;
int i, new_fd;
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);
LOCK(port->lock);
if (port->enabled == PORT_DISABLED)
goto out;
/* We raced, the shutdown should disable the accept read
until the shutdown is complete. */
if (port->dev_to_net_state == PORT_CLOSING)
goto out;
new_fd = accept(fd, (struct sockaddr *) &addr, &addrlen);
if (new_fd == -1) {
if (errno != EAGAIN && errno != EWOULDBLOCK)
syslog(LOG_ERR, "Could not accept on port %s: %m", port->portname);
goto out;
}
err = check_tcpd_ok(new_fd);
if (err)
goto out_err;
if (!port_remaddr_ok(port, (struct sockaddr *) &addr, addrlen)) {
err = "Access denied\r\n";
goto out_err;
}
for (i = 0; i < port->max_connections; i++) {
if (port->netcons[i].fd == -1)
break;
}
if (i == port->max_connections) {
if (port->kickolduser_mode) {
/* Kick off user 0. */
kick_old_user(port, &port->netcons[0], new_fd, 0,
&addr, addrlen);
UNLOCK(port->lock);
return;
}
err = "Port already in use\r\n";
}
if (!err && is_device_already_inuse(port))
err = "Port's device already in use\r\n";
if (err != NULL) {
out_err:
UNLOCK(port->lock);
write_ignore_fail(new_fd, err, strlen(err));
close(new_fd);
return;
}
memcpy(port->netcons[i].raddr, &addr, addrlen);
port->netcons[i].raddrlen = addrlen;
if (port->dgram)
port->netcons[i].udpraddrlen = addrlen;
/* We have to hold the ports_lock until after this call so the
device won't get used (from is_device_already_inuse()). */
handle_port_accept(port, new_fd, &(port->netcons[i]));
out:
UNLOCK(port->lock);
}
static void
port_accept_fd_cleared(int fd, void *cb_data)
{
port_info_t *port = cb_data;
wake_waiter(port->accept_waiter);
}
static int
tcp_port_read(int fd, port_info_t *port, int *readerr, net_info_t **rnetcon)
{
int rv;
rv = read(fd, port->net_to_dev.buf, port->net_to_dev.maxsize);
if (rv < 0)
*readerr = errno;
return rv;
}
static int
udp_port_read(int fd, port_info_t *port, int *readerr, net_info_t **rnetcon)
{
struct sockaddr_storage remaddr;
socklen_t remaddrlen;
int i, rv;
net_info_t *netcon;
char *err = NULL;
remaddrlen = sizeof(remaddr);
rv = recvfrom(fd, port->net_to_dev.buf, port->net_to_dev.maxsize, 0,
(struct sockaddr *) &remaddr, &remaddrlen);
if (rv < 0) {
*readerr = errno;
return rv;
}
for (i = 0; i < port->max_connections; i++) {
if (port->netcons[i].fd == -1)
continue;
if (!sockaddr_equal((struct sockaddr *) &remaddr, remaddrlen,
port->netcons[i].raddr, port->netcons[i].raddrlen,
true))
continue;
/* We found a matching port. */
*rnetcon = &(port->netcons[i]);
goto out;
}
/* No matching port, try a new connection. */
if (port->remaddrs) {
struct port_remaddr *r = port->remaddrs;
while (r) {
if (sockaddr_equal((struct sockaddr *) &remaddr, remaddrlen,
(struct sockaddr *) &r->addr, r->addrlen,
r->is_port_set))
break;
r = r->next;
}
if (!r) {
err = "Access denied\r\n";
goto out_err;
}
}
if (i == port->max_connections) {
for (i = 0; i < port->max_connections; i++) {
if (port->netcons[i].fd == -1)
break;
}
}
if (i == port->max_connections && port->kickolduser_mode) {
for (i = 0; i < port->max_connections; i++) {
if (!port->netcons[i].remote_fixed)
break;
}
}
if (i == port->max_connections)
err = "Port already in use\r\n";
if (!err && is_device_already_inuse(port))
err = "Port's device already in use\r\n";
if (!err) {
int new_fd = dup(fd);
if (new_fd == -1)
err = "Unable to dup port fd\r\n";
netcon = &(port->netcons[i]);
if (netcon->fd == -1) {
netcon->fd = new_fd;
} else {
kick_old_user(port, netcon, new_fd, rv, &remaddr, remaddrlen);
goto out_ignore;
}
}
if (err) {
out_err:
net_write(fd, err, strlen(err), 0,
(struct sockaddr *) &remaddr, remaddrlen);
goto out_ignore;
}
memcpy(netcon->raddr, &remaddr, remaddrlen);
netcon->raddrlen = remaddrlen;
if (port->dgram)
netcon->udpraddrlen = remaddrlen;
if (setup_port(port, netcon, false))
goto out_ignore;
*rnetcon = netcon;
out:
if (rv == 0)
/* zero-length packet. */
goto out_ignore;
return rv;
out_ignore:
*readerr = EAGAIN;
return -1;
}
static void
handle_udp_net_fd_read(int fd, void *data)
{
port_info_t *port = data;
handle_net_fd_read(fd, &(port->netcons[0]));
}
static void
process_remaddr(struct absout *eout, port_info_t *port, struct port_remaddr *r,
bool is_reconfig)
{
net_info_t *netcon;
if (!r->is_port_set || !port->dgram)
return;
for_each_connection(port, netcon) {
int i = 0;
if (netcon->remote_fixed)
continue;
/* Search for a UDP port that matches the remote address family. */
for (i = 0; i < port->nr_acceptfds; i++) {
if (port->acceptfds[i].family == r->addr.ss_family)
break;
}
if (i == port->nr_acceptfds) {
eout->out(eout, "remote address '%s' had no socket with"
" a matching family", r->name);
return;
}
netcon->remote_fixed = true;
memcpy(netcon->raddr, &r->addr, r->addrlen);
netcon->raddrlen = r->addrlen;
if (port->dgram)
netcon->udpraddrlen = r->addrlen;
netcon->fd = dup(port->acceptfds[i].fd);
if (netcon->fd == -1) {
eout->out(eout,
"Unable to duplicate fd for remote address '%s'",
r->name);
return;
}
if (setup_port(port, netcon, is_reconfig)) {
netcon->remote_fixed = false;
close(netcon->fd);
netcon->fd = -1;
eout->out(eout, "Unable to set up port for remote address '%s'",
r->name);
}
return;
}
eout->out(eout, "Too many fixed UDP remote addresses specified for the"
" max-connections given");
}
/* Start monitoring for connections on a specific port. */
static int
startup_port(struct absout *eout, port_info_t *port, bool is_reconfig)
{
void (*readhandler)(int, void *) = handle_accept_port_read;
struct port_remaddr *r;
if (port->is_stdio) {
if (is_device_already_inuse(port)) {
if (eout)
eout->out(eout, "Port's device already in use");
return -1;
} else {
port->acceptfds = NULL;
port->netcons[0].fd = 0; /* stdin */
if (setup_port(port, &(port->netcons[0]), false) == -1)
return -1;
}
return 0;
}
if (port->dgram)
readhandler = handle_udp_net_fd_read;
port->acceptfds = open_socket(port->ai, readhandler, NULL, port,
&port->nr_acceptfds, port_accept_fd_cleared);
if (port->acceptfds == NULL) {
if (eout)
eout->out(eout, "Unable to create network socket(s)");
else
syslog(LOG_ERR, "Unable to create network socket for port %s: %s",
port->portname, strerror(errno));
return -1;
}
for (r = port->remaddrs; r; r = r->next)
process_remaddr(eout, port, r, is_reconfig);
return 0;
}
static void
redo_port_handlers(port_info_t *port)
{
unsigned int i;
void (*readhandler)(int, void *) = handle_accept_port_read;
if (port->dgram)
readhandler = handle_udp_net_fd_read;
for (i = 0; i < port->nr_acceptfds; i++) {
sel_set_fd_handlers(ser2net_sel, port->acceptfds[i].fd, port,
readhandler, NULL, NULL,
port_accept_fd_cleared);
wait_for_waiter(port->accept_waiter);
}
}
int
change_port_state(struct absout *eout, port_info_t *port, int state,
bool is_reconfig)
{
int rv = 0;
if (port->enabled == state) {
UNLOCK(port->lock);
return 0;
}
if (state == PORT_DISABLED) {
port->enabled = PORT_DISABLED; /* Stop accepts */
UNLOCK(port->lock);
if (port->acceptfds != NULL) {
unsigned int i;
for (i = 0; i < port->nr_acceptfds; i++) {
sel_set_fd_read_handler(ser2net_sel,
port->acceptfds[i].fd,
SEL_FD_HANDLER_DISABLED);
sel_clear_fd_handlers(ser2net_sel, port->acceptfds[i].fd);
wait_for_waiter(port->accept_waiter);
close(port->acceptfds[i].fd);
}
free(port->acceptfds);
port->acceptfds = NULL;
port->nr_acceptfds = 0;
}
} else {
if (port->enabled == PORT_DISABLED) {
if (state == PORT_RAWLP)
port->io.read_disabled = 1;
else
port->io.read_disabled = 0;
rv = startup_port(eout, port, is_reconfig);
port->enabled = state;
}
UNLOCK(port->lock);
}
return rv;
}
static void
free_port(port_info_t *port)
{
net_info_t *netcon;
for_each_connection(port, netcon) {
char *err = "Port was deleted\n\r";
if (netcon->new_fd != -1) {
net_write(netcon->new_fd, err, strlen(err), 0,
(struct sockaddr *) &netcon->new_remote,
netcon->new_raddrlen);
close(netcon->new_fd);
}
if (netcon->runshutdown)
sel_free_runner(netcon->runshutdown);
if (netcon->new_buf)
free(netcon->new_buf);
}
while (port->remaddrs) {
struct port_remaddr *r = port->remaddrs;
port->remaddrs = r->next;
free(r->name);
free(r);
}
FREE_LOCK(port->lock);
if (port->dev_to_net.buf)
free(port->dev_to_net.buf);
if (port->net_to_dev.buf)
free(port->net_to_dev.buf);
if (port->timer)
sel_free_timer(port->timer);
if (port->send_timer)
sel_free_timer(port->send_timer);
if (port->runshutdown)
sel_free_runner(port->runshutdown);
if (port->accept_waiter)
free_waiter(port->accept_waiter);
if (port->io.f)
port->io.f->free(&port->io);
if (port->trace_read.filename)
free(port->trace_read.filename);
if (port->trace_write.filename)
free(port->trace_write.filename);
if (port->trace_both.filename)
free(port->trace_both.filename);
if (port->io.devname)
free(port->io.devname);
if (port->portname)
free(port->portname);
if (port->new_config)
free_port(port->new_config);
if (port->ai)
freeaddrinfo(port->ai);
if (port->acceptfds)
free(port->acceptfds);
if (port->bannerstr)
free(port->bannerstr);
if (port->signaturestr)
free(port->signaturestr);
if (port->openstr)
free(port->openstr);
if (port->closestr)
free(port->closestr);
if (port->closeon)
free(port->closeon);
if (port->netcons)
free(port->netcons);
if (port->orig_devname)
free(port->orig_devname);
free(port);
}
static void
switchout_port(struct absout *eout, port_info_t *new_port,
port_info_t *curr, port_info_t *prev)
{
int new_state = new_port->enabled;
waiter_t *tmp_waiter;
int i;
/* Keep the same accept ports as the old port. */
new_port->enabled = curr->enabled;
new_port->acceptfds = curr->acceptfds;
new_port->nr_acceptfds = curr->nr_acceptfds;
/*
* The strange switcharoo with the waiter keep the waiter the same
* on the old and new data, otherwise the wakeup and the wait
* would be on different waiters.
*/
tmp_waiter = new_port->accept_waiter;
new_port->accept_waiter = curr->accept_waiter;
curr->acceptfds = NULL;
redo_port_handlers(new_port);
curr->accept_waiter = tmp_waiter;
for (i = 0; i < new_port->max_connections; i++) {
if (i >= curr->max_connections)
break;
if (curr->netcons[i].new_fd == -1)
continue;
new_port->netcons[i].new_fd = curr->netcons[i].new_fd;
new_port->netcons[i].new_remote = curr->netcons[i].new_remote;
new_port->netcons[i].new_raddrlen = curr->netcons[i].new_raddrlen;
if (new_port->net_to_dev_bufsize < curr->netcons[i].new_buf_len)
/* Buffer shrank and data won't fit, just drop the old data. */
new_port->netcons[i].new_buf_len = new_port->net_to_dev_bufsize;
else
new_port->netcons[i].new_buf_len = curr->netcons[i].new_buf_len;
memcpy(new_port->netcons[i].new_buf, curr->netcons[i].new_buf,
new_port->netcons[i].new_buf_len);
}
if (prev == NULL) {
ports = new_port;
} else {
prev->next = new_port;
}
new_port->next = curr->next;
UNLOCK(curr->lock);
free_port(curr);
change_port_state(eout, new_port, new_state, true); /* releases lock */
}
static void
finish_shutdown_port(port_info_t *port)
{
net_info_t *netcon;
struct port_remaddr *r;
/* At this point nothing can happen on the port, so no need for a lock */
port->net_to_dev_state = PORT_UNCONNECTED;
buffer_reset(&port->net_to_dev);
if (port->devstr) {
free(port->devstr->buf);
free(port->devstr);
port->devstr = NULL;
}
buffer_reset(&port->dev_to_net);
port->dev_bytes_received = 0;
port->dev_bytes_sent = 0;
if (port->is_stdio)
/* This was a zero port (for stdin/stdout), this is only
allowed with one port at a time, and we shut down when it
closes. */
exit(0);
/* If the port has been disabled, then delete it. Check this before
the new config so the port will be deleted properly and not
reconfigured on a reconfig. */
if (port->config_num == -1) {
port_info_t *curr, *prev;
prev = NULL;
LOCK(ports_lock);
curr = ports;
while ((curr != NULL) && (curr != port)) {
prev = curr;
curr = curr->next;
}
if (curr != NULL) {
if (prev == NULL)
ports = curr->next;
else
prev->next = curr->next;
}
UNLOCK(ports_lock);
free_port(port);
return; /* We have to return here because we no longer have a port. */
}
/*
* The configuration for this port has changed, install it now that
* the user has closed the connection.
*/
if (port->new_config != NULL) {
port_info_t *curr, *prev;
prev = NULL;
LOCK(ports_lock);
curr = ports;
while ((curr != NULL) && (curr != port)) {
prev = curr;
curr = curr->next;
}
if (curr != NULL) {
port = curr->new_config;
curr->new_config = NULL;
LOCK(curr->lock);
LOCK(port->lock);
switchout_port(NULL, port, curr, prev); /* Releases locks */
}
UNLOCK(ports_lock);
}
/* Wait until here to let anything start on the port. */
LOCK(port->lock);
port->dev_to_net_state = PORT_UNCONNECTED;
for (r = port->remaddrs; r; r = r->next)
process_remaddr(&syslog_eout, port, r, true);
enable_accept_ports(port);
for_each_connection(port, netcon)
check_port_new_fd(port, netcon);
UNLOCK(port->lock);
}
static void
io_shutdown_done(struct devio *io)
{
port_info_t *port = io->user_data;
finish_shutdown_port(port);
}
static void
shutdown_port_io(sel_runner_t *runner, void *cb_data)
{
port_info_t *port = cb_data;
if (port->io.f)
port->io.f->shutdown(&port->io, io_shutdown_done);
else
finish_shutdown_port(port);
}
/* Output the devstr buffer */
static void
handle_dev_fd_close_write(port_info_t *port)
{
int reterr, buferr;
reterr = buffer_io_write(&port->io, port->devstr, &buferr);
if (reterr == -1) {
syslog(LOG_ERR, "The dev write for port %s had error: %m",
port->portname);
goto closeit;
}
if (buffer_cursize(port->devstr) != 0)
return;
closeit:
sel_run(port->runshutdown, shutdown_port_io, port);
}
static void
timer_shutdown_done(struct selector_s *sel, sel_timer_t *timer, void *cb_data)
{
port_info_t *port = cb_data;
LOCK(port->lock);
if (port->devstr) {
free(port->devstr->buf);
free(port->devstr);
}
port->devstr = process_str_to_buf(port, NULL, port->closestr);
if (port->devstr && (port->net_to_dev_state != PORT_UNCONNECTED)) {
port->io.f->read_handler_enable(&port->io, 0);
port->io.f->except_handler_enable(&port->io, 0);
port->dev_write_handler = handle_dev_fd_close_write;
port->io.f->write_handler_enable(&port->io, 1);
UNLOCK(port->lock);
} else {
UNLOCK(port->lock);
shutdown_port_io(NULL, port);
}
}
static void shutdown_port_timer(sel_runner_t *runner, void *cb_data)
{
port_info_t *port = cb_data;
sel_stop_timer_with_done(port->timer, timer_shutdown_done, port);
}
static void
start_shutdown_port(port_info_t *port, char *reason)
{
if (port->dev_to_net_state == PORT_CLOSING)
return;
port->close_on_output_done = false;
disable_accept_ports(port);
footer_trace(port, "port", reason);
if (port->trace_write.fd != -1) {
close(port->trace_write.fd);
port->trace_write.fd = -1;
}
if (port->trace_read.fd != -1) {
close(port->trace_read.fd);
port->trace_read.fd = -1;
}
if (port->trace_both.fd != -1) {
close(port->trace_both.fd);
port->trace_both.fd = -1;
}
port->tw = port->tr = port->tb = NULL;
port->dev_to_net_state = PORT_CLOSING;
}
static void
netcon_finish_shutdown(net_info_t *netcon)
{
port_info_t *port = netcon->port;
LOCK(port->lock);
netcon->closing = false;
netcon->bytes_received = 0;
netcon->bytes_sent = 0;
netcon->sending_tn_data = false;
netcon->write_pos = 0;
netcon->data_to_write = false;
if (netcon->banner) {
free(netcon->banner->buf);
free(netcon->banner);
netcon->banner = NULL;
}
if (num_connected_net(port, true) == 0) {
start_shutdown_port(port, "All network connections free");
sel_run(port->runshutdown, shutdown_port_timer, port);
} else {
check_port_new_fd(port, netcon);
}
UNLOCK(port->lock);
}
static void
port_net_fd_cleared(int fd, void *cb_data)
{
net_info_t *netcon = cb_data;
close(netcon->fd);
netcon->fd = -1;
netcon_finish_shutdown(netcon);
}
static void shutdown_netcon_clear(sel_runner_t *runner, void *cb_data)
{
net_info_t *netcon = cb_data;
if (netcon->fd != -1)
sel_clear_fd_handlers(ser2net_sel, netcon->fd);
else
netcon_finish_shutdown(netcon);
}
static void
shutdown_port(port_info_t *port, char *reason)
{
net_info_t *netcon;
bool some_to_close = false;
start_shutdown_port(port, reason);
for_each_connection(port, netcon) {
if (netcon->fd != -1) {
some_to_close = true;
shutdown_one_netcon(netcon, "Port closing");
}
}
if (!some_to_close)
sel_run(port->runshutdown, shutdown_port_timer, port);
}
static void
shutdown_one_netcon(net_info_t *netcon, char *reason)
{
if (netcon->closing)
return;
footer_trace(netcon->port, "netcon", reason);
netcon->closing = true;
/* shutdown_netcon_clear() may clain the port lock, run it elsewhere. */
sel_run(netcon->runshutdown, shutdown_netcon_clear, netcon);
}
void
got_timeout(struct selector_s *sel,
sel_timer_t *timer,
void *data)
{
port_info_t *port = (port_info_t *) data;
struct timeval then;
unsigned char modemstate;
net_info_t *netcon;
LOCK(port->lock);
if (port->dev_to_net_state == PORT_CLOSING) {
UNLOCK(port->lock);
return;
}
if (port->timeout) {
for_each_connection(port, netcon) {
if (netcon->fd == -1 || netcon->remote_fixed)
continue;
netcon->timeout_left--;
if (netcon->timeout_left < 0)
shutdown_one_netcon(netcon, "timeout");
}
}
if (port->is_2217 &&
(port->io.f->get_modem_state(&port->io, &modemstate) != -1)) {
modemstate &= port->modemstate_mask;
if (modemstate != port->last_modemstate) {
unsigned char data[3];
data[0] = TN_OPT_COM_PORT;
data[1] = 107; /* Notify modemstate */
data[2] = modemstate;
port->last_modemstate = modemstate;
for_each_connection(port, netcon) {
if (netcon->fd == -1)
continue;
telnet_send_option(&netcon->tn_data, data, 3);
}
}
}
sel_get_monotonic_time(&then);
then.tv_sec += 1;
sel_start_timer(port->timer, &then);
UNLOCK(port->lock);
}
static int cmpstrval(const char *s, const char *prefix, unsigned int *end)
{
int len = strlen(prefix);
if (strncmp(s, prefix, len))
return 0;
*end = len;
return 1;
}
static int cmpstrint(const char *s, const char *prefix, int *val,
struct absout *eout)
{
unsigned int end;
char *endpos;
if (!cmpstrval(s, prefix, &end))
return 0;
*val = strtoul(s + end, &endpos, 10);
if (endpos == s + end || *endpos != '\0') {
eout->out(eout, "Invalid number for %s: %s\n", prefix, s + end);
return -1;
}
return 1;
}
static void
port_add_one_remaddr(struct absout *eout, port_info_t *port, char *str)
{
struct port_remaddr *r, *r2;
struct addrinfo *ai = NULL;
bool is_dgram, is_port_set;
int rv;
rv = scan_network_port(str, &ai, &is_dgram, &is_port_set);
if (rv) {
eout->out(eout, "Invalid remote address '%s'", str);
goto out;
}
if (port->dgram != is_dgram) {
eout->out(eout, "Remote address '%s' does not match the port"
" type, one cannot be UDP while the other is UDP.", str);
goto out;
}
r = malloc(sizeof(*r));
if (!r) {
eout->out(eout, "Out of memory allocation remote address");
goto out;
}
r->name = strdup(str);
if (!r->name) {
eout->out(eout, "Out of memory allocation remote address string");
free(r);
goto out;
}
memcpy(&r->addr, ai->ai_addr, ai->ai_addrlen);
r->addrlen = ai->ai_addrlen;
r->is_port_set = is_port_set;
r->next = NULL;
r2 = port->remaddrs;
if (!r2) {
port->remaddrs = r;
} else {
while (r2->next)
r2 = r2->next;
r2->next = r;
}
out:
if (ai)
freeaddrinfo(ai);
}
static void
port_add_remaddr(struct absout *eout, port_info_t *port, const char *istr)
{
char *str;
char *strtok_data;
char *remstr;
str = strdup(istr);
if (!str) {
eout->out(eout, "Out of memory handling remote address '%s'", istr);
return;
}
remstr = strtok_r(str, ";", &strtok_data);
/* Note that we ignore an empty remaddr. */
while (remstr && *remstr) {
port_add_one_remaddr(eout, port, remstr);
remstr = strtok_r(NULL, ";", &strtok_data);
}
free(str);
}
static int
myconfig(void *data, struct absout *eout, const char *pos)
{
port_info_t *port = data;
enum str_type stype;
char *s;
unsigned int len, end;
int rv, val;
if (strcmp(pos, "remctl") == 0) {
port->allow_2217 = 1;
} else if (strcmp(pos, "-remctl") == 0) {
port->allow_2217 = 0;
} else if (strcmp(pos, "kickolduser") == 0) {
port->kickolduser_mode = 1;
} else if (strcmp(pos, "-kickolduser") == 0) {
port->kickolduser_mode = 0;
} else if (strcmp(pos, "hexdump") == 0 ||
strcmp(pos, "-hexdump") == 0) {
port->trace_read.hexdump = (*pos != '-');
port->trace_write.hexdump = (*pos != '-');
port->trace_both.hexdump = (*pos != '-');
} else if (strcmp(pos, "timestamp") == 0 ||
strcmp(pos, "-timestamp") == 0) {
port->trace_read.timestamp = (*pos != '-');
port->trace_write.timestamp = (*pos != '-');
port->trace_both.timestamp = (*pos != '-');
} else if (strcmp(pos, "tr-hexdump") == 0 ||
strcmp(pos, "-tr-hexdump") == 0) {
port->trace_read.hexdump = (*pos != '-');
} else if (strcmp(pos, "tr-timestamp") == 0 ||
strcmp(pos, "-tr-timestamp") == 0) {
port->trace_read.timestamp = (*pos != '-');
} else if (strcmp(pos, "tw-hexdump") == 0 ||
strcmp(pos, "-tw-hexdump") == 0) {
port->trace_write.hexdump = (*pos != '-');
} else if (strcmp(pos, "tw-timestamp") == 0 ||
strcmp(pos, "-tw-timestamp") == 0) {
port->trace_write.timestamp = (*pos != '-');
} else if (strcmp(pos, "tb-hexdump") == 0 ||
strcmp(pos, "-tb-hexdump") == 0) {
port->trace_both.hexdump = (*pos != '-');
} else if (strcmp(pos, "tb-timestamp") == 0 ||
strcmp(pos, "-tb-timestamp") == 0) {
port->trace_both.timestamp = (*pos != '-');
} else if (cmpstrval(pos, "tr=", &end)) {
/* trace read, data from the port to the socket */
port->trace_read.filename = find_tracefile(pos + end);
} else if (cmpstrval(pos, "tw=", &end)) {
/* trace write, data from the socket to the port */
port->trace_write.filename = find_tracefile(pos + end);
} else if (cmpstrval(pos, "tb=", &end)) {
/* trace both directions. */
port->trace_both.filename = find_tracefile(pos + end);
} else if (cmpstrval(pos, "led-rx=", &end)) {
/* LED for UART RX traffic */
port->led_rx = find_led(pos + end);
} else if (cmpstrval(pos, "led-tx=", &end)) {
/* LED for UART TX traffic */
port->led_tx = find_led(pos + end);
#if HAVE_DECL_TIOCSRS485
} else if (cmpstrval(pos, "rs485=", &end)) {
/* get RS485 configuration. */
port->rs485conf = find_rs485conf(pos + end);
#endif
} else if (strcmp(pos, "telnet_brk_on_sync") == 0) {
port->telnet_brk_on_sync = 1;
} else if (strcmp(pos, "-telnet_brk_on_sync") == 0) {
port->telnet_brk_on_sync = 0;
} else if (strcmp(pos, "chardelay") == 0) {
port->enable_chardelay = true;
} else if (strcmp(pos, "-chardelay") == 0) {
port->enable_chardelay = false;
} else if ((rv = cmpstrint(pos, "chardelay-scale=", &val, eout))) {
if (rv == -1)
return -1;
port->chardelay_scale = val;
} else if ((rv = cmpstrint(pos, "chardelay-min=", &val, eout))) {
if (rv == -1)
return -1;
port->chardelay_min = val;
} else if ((rv = cmpstrint(pos, "chardelay-max=", &val, eout))) {
if (rv == -1)
return -1;
port->chardelay_max = val;
} else if ((rv = cmpstrint(pos, "dev-to-net-bufsize=", &val, eout))) {
if (rv == -1)
return -1;
port->dev_to_net_bufsize = val;
} else if ((rv = cmpstrint(pos, "net-to-dev-bufsize=", &val, eout))) {
if (rv == -1)
return -1;
port->net_to_dev_bufsize = val;
} else if ((rv = cmpstrint(pos, "dev-to-tcp-bufsize=", &val, eout))) {
/* deprecated */
if (rv == -1)
return -1;
port->dev_to_net_bufsize = val;
} else if ((rv = cmpstrint(pos, "tcp-to-dev-bufsize=", &val, eout))) {
/* deprecated */
if (rv == -1)
return -1;
port->net_to_dev_bufsize = val;
} else if ((rv = cmpstrint(pos, "max-connections=", &val, eout))) {
if (rv == -1)
return -1;
if (val < 1)
val = 1;
port->max_connections = val;
} else if (cmpstrval(pos, "remaddr=", &end)) {
port_add_remaddr(eout, port, pos + end);
port->remaddr_set = true;
} else if ((s = find_str(pos, &stype, &len))) {
/* It's a startup banner, signature or open/close string, it's
already set. */
switch (stype) {
case BANNER: port->bannerstr = s; break;
case SIGNATURE: port->signaturestr = s; break;
case OPENSTR: port->openstr = s; break;
case CLOSESTR: port->closestr = s; break;
case CLOSEON: port->closeon = s; port->closeon_len = len; break;
default: free(s); goto unknown;
}
} else {
unknown:
eout->out(eout, "Unknown config item: %s", pos);
return -1;
}
return 0;
}
/* Create a port based on a set of parameters passed in. */
int
portconfig(struct absout *eout,
char *portnum,
char *state,
char *timeout,
char *devname,
char *devcfg,
int config_num)
{
port_info_t *new_port, *curr, *prev;
net_info_t *netcon;
enum str_type str_type;
bool is_port_set;
new_port = malloc(sizeof(port_info_t));
if (new_port == NULL) {
eout->out(eout, "Could not allocate a port data structure");
return -1;
}
memset(new_port, 0, sizeof(*new_port));
INIT_LOCK(new_port->lock);
new_port->accept_waiter = alloc_waiter();
if (!new_port->accept_waiter) {
eout->out(eout, "Could not allocate accept waiter data");
goto errout;
}
if (sel_alloc_timer(ser2net_sel,
got_timeout, new_port,
&new_port->timer))
{
eout->out(eout, "Could not allocate timer data");
goto errout;
}
if (sel_alloc_timer(ser2net_sel,
send_timeout, new_port,
&new_port->send_timer))
{
eout->out(eout, "Could not allocate timer data");
goto errout;
}
if (sel_alloc_runner(ser2net_sel, &new_port->runshutdown)) {
goto errout;
}
new_port->io.devname = find_str(devname, &str_type, NULL);
if (new_port->io.devname) {
if (str_type != DEVNAME) {
free(new_port->io.devname);
new_port->io.devname = NULL;
} else {
new_port->orig_devname = strdup(devname);
if (!new_port->orig_devname) {
eout->out(eout, "unable to allocate original device name");
goto errout;
}
}
}
if (!new_port->io.devname)
new_port->io.devname = strdup(devname);
if (!new_port->io.devname) {
eout->out(eout, "unable to allocate device name");
goto errout;
}
/* Errors from here on out must goto errout. */
init_port_data(new_port);
if (!new_port->portname) {
new_port->portname = strdup(portnum);
if (!new_port->portname)
goto errout;
}
if (strisallzero(new_port->portname)) {
new_port->is_stdio = 1;
} else if (scan_network_port(new_port->portname, &new_port->ai,
&new_port->dgram, &is_port_set)) {
eout->out(eout, "port number was invalid");
goto errout;
} else if (!is_port_set) {
eout->out(eout, "port number was zero");
goto errout;
}
if (new_port->dgram)
new_port->netread = udp_port_read;
else
new_port->netread = tcp_port_read;
if (strcmp(state, "raw") == 0) {
new_port->enabled = PORT_RAW;
} else if (strcmp(state, "rawlp") == 0) {
new_port->enabled = PORT_RAWLP;
new_port->io.read_disabled = 1;
} else if (strcmp(state, "telnet") == 0) {
new_port->enabled = PORT_TELNET;
} else if (strcmp(state, "off") == 0) {
new_port->enabled = PORT_DISABLED;
} else {
eout->out(eout, "state was invalid");
goto errout;
}
new_port->timeout = scan_int(timeout);
if (new_port->timeout == -1) {
eout->out(eout, "timeout was invalid");
goto errout;
}
new_port->io.user_data = new_port;
if (strncmp(new_port->io.devname, "sol.", 4) == 0) {
if (solcfg_init(&new_port->io, eout, devcfg, myconfig,
new_port) == -1) {
eout->out(eout, "device configuration invalid");
goto errout;
}
} else {
if (devcfg_init(&new_port->io, eout, devcfg, myconfig,
new_port) == -1) {
eout->out(eout, "device configuration invalid");
goto errout;
}
}
/*
* Don't handle the remaddr default until here, we don't want to
* mess with it if the user has set it, because the user may set
* it to an empty string.
*/
if (!new_port->remaddr_set) {
char *remaddr = find_default_str("remaddr");
if (!remaddr) {
eout->out(eout, "Out of memory processing default remote address");
} else {
port_add_remaddr(eout, new_port, remaddr);
free(remaddr);
}
}
new_port->netcons = malloc(sizeof(*(new_port->netcons)) *
new_port->max_connections);
if (new_port->netcons == NULL) {
eout->out(eout, "Could not allocate a port data structure");
goto errout;
}
memset(new_port->netcons, 0,
sizeof(*(new_port->netcons)) * new_port->max_connections);
for_each_connection(new_port, netcon) {
if (sel_alloc_runner(ser2net_sel, &netcon->runshutdown)) {
eout->out(eout, "Could not allocate a netcon shutdown handler");
goto errout;
}
netcon->port = new_port;
netcon->fd = -1;
netcon->new_fd = -1;
netcon->raddr = ((struct sockaddr *) &netcon->remote);
if (new_port->dgram) {
netcon->udpraddr = netcon->raddr;
netcon->new_buf = malloc(new_port->net_to_dev_bufsize);
if (!netcon->new_buf) {
eout->out(eout, "Could not allocate a temp buf");
goto errout;
}
}
}
new_port->config_num = config_num;
/* See if the port already exists, and reconfigure it if so. */
prev = NULL;
LOCK(ports_lock);
curr = ports;
while (curr != NULL) {
if (strcmp(curr->portname, new_port->portname) == 0) {
/* We are reconfiguring this port. */
LOCK(curr->lock);
if (curr->dev_to_net_state == PORT_UNCONNECTED) {
/* Port is disconnected, switch it now. */
LOCK(new_port->lock);
switchout_port(eout, new_port, curr, prev); /* releases locks */
} else {
/* Mark it to be replaced later. */
if (curr->new_config != NULL)
free_port(curr->new_config);
curr->config_num = config_num;
curr->new_config = new_port;
if (curr->dgram)
shutdown_port(curr, "UDP reconfig"); /* releases lock */
UNLOCK(curr->lock);
}
goto out;
} else {
prev = curr;
curr = curr->next;
}
}
/* If we get here, the port is brand new, so don't do anything that
would affect a port replacement here. */
if (new_port->enabled != PORT_DISABLED) {
int rv;
LOCK(new_port->lock);
rv = startup_port(eout, new_port, false);
UNLOCK(new_port->lock);
if (rv == -1)
goto errout_unlock;
}
/* Tack it on to the end of the list of ports. */
new_port->next = NULL;
if (ports == NULL) {
ports = new_port;
} else {
curr = ports;
while (curr->next != NULL) {
curr = curr->next;
}
curr->next = new_port;
}
out:
UNLOCK(ports_lock);
return 0;
errout_unlock:
UNLOCK(ports_lock);
errout:
free_port(new_port);
return -1;
}
void
clear_old_port_config(int curr_config)
{
port_info_t *curr, *prev;
prev = NULL;
curr = ports;
LOCK(ports_lock);
while (curr != NULL) {
if (curr->config_num != curr_config) {
/* The port was removed, remove it. */
LOCK(curr->lock);
if (curr->dev_to_net_state == PORT_UNCONNECTED) {
/* releases lock */
change_port_state(NULL, curr, PORT_DISABLED, false);
if (prev == NULL) {
ports = curr->next;
free_port(curr);
curr = ports;
} else {
prev->next = curr->next;
free_port(curr);
curr = prev->next;
}
} else {
curr->config_num = -1;
/* releases lock */
change_port_state(NULL, curr, PORT_DISABLED, false);
prev = curr;
curr = curr->next;
if (curr->dgram) {
/*
* UDP sockets have to shut down immediately as they
* don't have a separate accept and data socket.
*/
LOCK(curr->lock);
shutdown_port(curr, "UDP delete");
UNLOCK(curr->lock);
}
}
} else {
prev = curr;
curr = curr->next;
}
}
UNLOCK(ports_lock);
}
#define REMOTEADDR_COLUMN_WIDTH \
(INET6_ADDRSTRLEN - 1 /* terminating NUL */ + 1 /* comma */ + 5 /* strlen("65535") */)
/* Print information about a port to the control port given in cntlr. */
static void
showshortport(struct controller_info *cntlr, port_info_t *port)
{
char buffer[NI_MAXHOST], portbuff[NI_MAXSERV];
int count;
int need_space = 0;
int err;
struct absout out = { .out = cntrl_absout, .data = cntlr };
net_info_t *netcon = NULL;
unsigned int bytes_recv = 0, bytes_sent = 0;
controller_outputf(cntlr, "%-22s ", port->portname);
if (port->config_num == -1)
controller_outputf(cntlr, "%-6s ", "DEL");
else
controller_outputf(cntlr, "%-6s ", enabled_str[port->enabled]);
controller_outputf(cntlr, "%7d ", port->timeout);
netcon = first_live_net_con(port);
if (!netcon)
netcon = &(port->netcons[0]);
if (port->net_to_dev_state != PORT_UNCONNECTED) {
err = getnameinfo(netcon->raddr, netcon->raddrlen,
buffer, sizeof(buffer),
portbuff, sizeof(portbuff),
NI_NUMERICHOST | NI_NUMERICSERV);
if (err) {
snprintf(buffer, sizeof(buffer), "*err*,%s", gai_strerror(err));
/* gai_strerror could return an elongated string which could break
our pretty formatted output below, so truncate the string nicely */
if (strlen(buffer) > REMOTEADDR_COLUMN_WIDTH)
strcpy(&buffer[REMOTEADDR_COLUMN_WIDTH - 3], "...");
count = controller_outputf(cntlr, "%s", buffer);
} else {
count = controller_outputf(cntlr, "%s,%s", buffer, portbuff);
}
} else {
count = controller_outputf(cntlr, "unconnected");
}
while (count < REMOTEADDR_COLUMN_WIDTH + 1) {
controller_outs(cntlr, " ");
count++;
}
bytes_recv = netcon->bytes_received;
bytes_sent = netcon->bytes_sent;
controller_outputf(cntlr, "%-22s ", port->io.devname);
controller_outputf(cntlr, "%-14s ", state_str[port->net_to_dev_state]);
controller_outputf(cntlr, "%-14s ", state_str[port->dev_to_net_state]);
controller_outputf(cntlr, "%9d ", bytes_recv);
controller_outputf(cntlr, "%9d ", bytes_sent);
controller_outputf(cntlr, "%9d ", port->dev_bytes_received);
controller_outputf(cntlr, "%9d ", port->dev_bytes_sent);
if (port->enabled != PORT_RAWLP) {
port->io.f->show_devcfg(&port->io, &out);
need_space = 1;
}
if (port->net_to_dev_state != PORT_UNCONNECTED) {
if (need_space) {
controller_outs(cntlr, " ");
}
port->io.f->show_devcontrol(&port->io, &out);
}
controller_outs(cntlr, "\r\n");
}
/* Print information about a port to the control port given in cntlr. */
static void
showport(struct controller_info *cntlr, port_info_t *port)
{
char buffer[NI_MAXHOST], portbuff[NI_MAXSERV];
struct absout out = { .out = cntrl_absout, .data = cntlr };
int err;
net_info_t *netcon;
controller_outputf(cntlr, "TCP Port %s\r\n", port->portname);
controller_outputf(cntlr, " enable state: %s\r\n",
enabled_str[port->enabled]);
controller_outputf(cntlr, " timeout: %d\r\n", port->timeout);
for_each_connection(port, netcon) {
if (port->net_to_dev_state != PORT_UNCONNECTED) {
err = getnameinfo(netcon->raddr, netcon->raddrlen,
buffer, sizeof(buffer),
portbuff, sizeof(portbuff),
NI_NUMERICHOST | NI_NUMERICSERV);
if (err) {
snprintf(buffer, sizeof(buffer), "*err*,%s", gai_strerror(err));
controller_outputf(cntlr, " connected to: %s\r\n", buffer);
} else {
controller_outputf(cntlr, " connected to: %s,%s\r\n",
buffer, portbuff);
}
controller_outputf(cntlr, " bytes read from TCP: %d\r\n",
netcon->bytes_received);
controller_outputf(cntlr, " bytes written to TCP: %d\r\n",
netcon->bytes_sent);
} else {
controller_outputf(cntlr, " unconnected\r\n");
}
}
if (port->orig_devname)
controller_outputf(cntlr, " device: %s (%s)\r\n", port->io.devname,
port->orig_devname);
else
controller_outputf(cntlr, " device: %s\r\n", port->io.devname);
controller_outputf(cntlr, " device config: ");
if (port->enabled == PORT_RAWLP) {
controller_outputf(cntlr, "none\r\n");
} else {
port->io.f->show_devcfg(&port->io, &out);
controller_outputf(cntlr, "\r\n");
}
controller_outputf(cntlr, " device controls: ");
if (port->net_to_dev_state == PORT_UNCONNECTED) {
controller_outputf(cntlr, "not currently connected\r\n");
} else {
port->io.f->show_devcontrol(&port->io, &out);
controller_outputf(cntlr, "\r\n");
}
controller_outputf(cntlr, " tcp to device state: %s\r\n",
state_str[port->net_to_dev_state]);
controller_outputf(cntlr, " device to tcp state: %s\r\n",
state_str[port->dev_to_net_state]);
controller_outputf(cntlr, " bytes read from device: %d\r\n",
port->dev_bytes_received);
controller_outputf(cntlr, " bytes written to device: %d\r\n",
port->dev_bytes_sent);
if (port->config_num == -1) {
controller_outputf(cntlr, " Port will be deleted when current"
" session closes.\r\n");
} else if (port->new_config != NULL) {
controller_outputf(cntlr, " Port will be reconfigured when current"
" session closes.\r\n");
}
}
/*
* Find a port data structure given a port number. Returns with port->lock
* held, if it returns a non-NULL port.
*/
static port_info_t *
find_port_by_num(char *portstr, bool allow_deleted)
{
port_info_t *port;
LOCK(ports_lock);
port = ports;
while (port != NULL) {
if (strcmp(portstr, port->portname) == 0) {
LOCK(port->lock);
UNLOCK(ports_lock);
if (port->config_num == -1 && !allow_deleted) {
UNLOCK(port->lock);
return NULL;
}
return port;
}
port = port->next;
}
UNLOCK(ports_lock);
return NULL;
}
/* Handle a showport command from the control port. */
void
showports(struct controller_info *cntlr, char *portspec)
{
port_info_t *port;
if (portspec == NULL) {
LOCK(ports_lock);
/* Dump everything. */
port = ports;
while (port != NULL) {
LOCK(port->lock);
showport(cntlr, port);
UNLOCK(port->lock);
port = port->next;
}
UNLOCK(ports_lock);
} else {
port = find_port_by_num(portspec, true);
if (port == NULL) {
controller_outputf(cntlr, "Invalid port number: %s\r\n", portspec);
} else {
showport(cntlr, port);
UNLOCK(port->lock);
}
}
}
/* Handle a showport command from the control port. */
void
showshortports(struct controller_info *cntlr, char *portspec)
{
port_info_t *port;
controller_outputf(cntlr,
"%-22s %-6s %7s %-*s %-22s %-14s %-14s %9s %9s %9s %9s %s\r\n",
"Port name",
"Type",
"Timeout",
REMOTEADDR_COLUMN_WIDTH,
"Remote address",
"Device",
"TCP to device",
"Device to TCP",
"TCP in",
"TCP out",
"Dev in",
"Dev out",
"State");
if (portspec == NULL) {
LOCK(ports_lock);
/* Dump everything. */
port = ports;
while (port != NULL) {
LOCK(port->lock);
showshortport(cntlr, port);
UNLOCK(port->lock);
port = port->next;
}
UNLOCK(ports_lock);
} else {
port = find_port_by_num(portspec, true);
if (port == NULL) {
controller_outputf(cntlr, "Invalid port number: %s\r\n", portspec);
} else {
showshortport(cntlr, port);
UNLOCK(port->lock);
}
}
}
/* Set the timeout on a port. The port number and timeout are passed
in as strings, this code will convert them, return any errors, and
perform the operation. */
void
setporttimeout(struct controller_info *cntlr, char *portspec, char *timeout)
{
port_info_t *port;
net_info_t *netcon;
port = find_port_by_num(portspec, true);
if (port == NULL) {
controller_outputf(cntlr, "Invalid port number: %s\r\n", portspec);
} else {
int timeout_num = scan_int(timeout);
if (timeout_num == -1) {
controller_outputf(cntlr, "Invalid timeout: %s\r\n", timeout);
} else {
port->timeout = timeout_num;
for_each_connection(port, netcon) {
if (netcon->fd != -1)
reset_timer(netcon);
}
}
UNLOCK(port->lock);
}
}
/* Configure a port. The port number and configuration are passed in
as strings, this code will get the port and then call the code to
configure the device. */
void
setportdevcfg(struct controller_info *cntlr, char *portspec, char *devcfg)
{
port_info_t *port;
struct absout out = { .out = cntrl_abserrout, .data = cntlr };
port = find_port_by_num(portspec, false);
if (port == NULL) {
controller_outputf(cntlr, "Invalid port number: %s\r\n", portspec);
} else {
if (port->io.f->reconfig(&port->io, &out, devcfg, myconfig, port) == -1)
{
controller_outputf(cntlr, "Invalid device config\r\n");
}
UNLOCK(port->lock);
}
}
/* Modify the controls of a port. The port number and configuration
are passed in as strings, this code will get the port and then call
the code to control the device. */
void
setportcontrol(struct controller_info *cntlr, char *portspec, char *controls)
{
port_info_t *port;
port = find_port_by_num(portspec, false);
if (port == NULL) {
controller_outputf(cntlr, "Invalid port number: %s\r\n", portspec);
goto out;
} else if (port->net_to_dev_state == PORT_UNCONNECTED) {
controller_outputf(cntlr, "Port is not currently connected: %s\r\n",
portspec);
} else {
if (port->io.f->set_devcontrol(&port->io, controls) == -1) {
controller_outputf(cntlr, "Invalid device controls\r\n");
}
}
UNLOCK(port->lock);
out:
return;
}
/* Set the enable state of a port. */
void
setportenable(struct controller_info *cntlr, char *portspec, char *enable)
{
port_info_t *port;
int new_enable;
struct absout eout = { .out = cntrl_abserrout, .data = cntlr };
port = find_port_by_num(portspec, false);
if (port == NULL) {
controller_outputf(cntlr, "Invalid port number: %s\r\n", portspec);
goto out;
}
if (strcmp(enable, "off") == 0) {
new_enable = PORT_DISABLED;
} else if (strcmp(enable, "raw") == 0) {
new_enable = PORT_RAW;
} else if (strcmp(enable, "rawlp") == 0) {
new_enable = PORT_RAWLP;
} else if (strcmp(enable, "telnet") == 0) {
new_enable = PORT_TELNET;
} else {
controller_outputf(cntlr, "Invalid enable: %s\r\n", enable);
goto out_unlock;
}
change_port_state(&eout, port, new_enable, false); /* releases lock */
out:
return;
out_unlock:
UNLOCK(port->lock);
}
#if HAVE_DECL_TIOCSRS485
struct serial_rs485 *get_rs485_conf(void *data)
{
port_info_t *port = data;
return port->rs485conf;
}
#endif
/* Start data monitoring on the given port, type may be either "tcp" or
"term" and only one direction may be monitored. This return NULL if
the monitor fails. The monitor output will go to "fd". */
void *
data_monitor_start(struct controller_info *cntlr,
char *type,
char *portspec)
{
port_info_t *port;
port = find_port_by_num(portspec, true);
if (port == NULL) {
char *err = "Invalid port number: ";
controller_outs(cntlr, err);
controller_outs(cntlr, portspec);
controller_outs(cntlr, "\r\n");
goto out;
}
if ((port->net_monitor != NULL) || (port->dev_monitor != NULL)) {
char *err = "Port is already being monitored";
controller_outs(cntlr, err);
controller_outs(cntlr, "\r\n");
goto out_unlock;
}
if (strcmp(type, "tcp") == 0) {
port->net_monitor = cntlr;
} else if (strcmp(type, "term") == 0) {
port->dev_monitor = cntlr;
} else {
char *err = "invalid monitor type: ";
controller_outs(cntlr, err);
controller_outs(cntlr, type);
controller_outs(cntlr, "\r\n");
UNLOCK(port->lock);
port = NULL;
goto out;
}
out_unlock:
UNLOCK(port->lock);
out:
return port;
}
/* Stop monitoring the given id. */
void
data_monitor_stop(struct controller_info *cntlr,
void *monitor_id)
{
port_info_t *port = (port_info_t *) monitor_id;
port_info_t *curr;
LOCK(ports_lock);
curr = ports;
while (curr) {
if (curr == port) {
LOCK(port->lock);
port->net_monitor = NULL;
port->dev_monitor = NULL;
UNLOCK(port->lock);
break;
}
curr = curr->next;
}
UNLOCK(ports_lock);
}
void
disconnect_port(struct controller_info *cntlr,
char *portspec)
{
port_info_t *port;
port = find_port_by_num(portspec, true);
if (port == NULL) {
char *err = "Invalid port number: ";
controller_outs(cntlr, err);
controller_outs(cntlr, portspec);
controller_outs(cntlr, "\r\n");
goto out;
} else if (port->net_to_dev_state == PORT_UNCONNECTED) {
char *err = "Port not connected: ";
controller_outs(cntlr, err);
controller_outs(cntlr, portspec);
controller_outs(cntlr, "\r\n");
goto out_unlock;
}
shutdown_port(port, "disconnect");
out_unlock:
UNLOCK(port->lock);
out:
return;
}
static int
com_port_will(void *cb_data)
{
net_info_t *netcon = cb_data;
port_info_t *port = netcon->port;
unsigned char data[3];
if (! port->allow_2217)
return 0;
/* The remote end turned on RFC2217 handling. */
port->is_2217 = 1;
port->linestate_mask = 0;
port->modemstate_mask = 255;
port->last_modemstate = 0;
/* send a modemstate notify */
data[0] = TN_OPT_COM_PORT;
data[1] = 107; /* Notify modemstate */
data[2] = 0;
if (port->io.f->get_modem_state(&port->io, data + 2) != -1) {
port->last_modemstate = data[2];
}
telnet_send_option(&netcon->tn_data, data, 3);
return 1;
}
static void
com_port_handler(void *cb_data, unsigned char *option, int len)
{
net_info_t *netcon = cb_data;
port_info_t *port = netcon->port;
unsigned char outopt[MAX_TELNET_CMD_XMIT_BUF];
int val;
unsigned char ucval;
int cisco_ios_baud_rates = 0;
if (len < 2)
return;
switch (option[1]) {
case 0: /* SIGNATURE? */
{
/* truncate signature, if it exceeds buffer size */
char *sig = port->signaturestr;
int sign_len;
if (!sig)
/* If not set, use a default. */
sig = rfc2217_signature;
sign_len = strlen(sig);
if (sign_len > (MAX_TELNET_CMD_XMIT_BUF - 2))
sign_len = MAX_TELNET_CMD_XMIT_BUF - 2;
outopt[0] = 44;
outopt[1] = 100;
strncpy((char *) outopt + 2, sig, sign_len);
telnet_send_option(&netcon->tn_data, outopt, 2 + sign_len);
break;
}
case 1: /* SET-BAUDRATE */
if (len == 3) {
cisco_ios_baud_rates = 1;
val = option[2];
} else {
if (len < 6)
return;
/* Basically the same as:
* val = ntohl(*((uint32_t *) (option + 2)));
* but handled unaligned cases */
val = option[2] << 24;
val |= option[3] << 16;
val |= option[4] << 8;
val |= option[5];
}
port->io.f->baud_rate(&port->io, &val, cisco_ios_baud_rates,
&port->bps);
recalc_port_chardelay(port);
outopt[0] = 44;
outopt[1] = 101;
if (cisco_ios_baud_rates) {
outopt[2] = val;
telnet_send_option(&netcon->tn_data, outopt, 3);
} else {
/* Basically the same as:
* *((uint32_t *) (outopt + 2)) = htonl(val);
* but handles unaligned cases */
outopt[2] = val >> 24;
outopt[3] = val >> 16;
outopt[4] = val >> 8;
outopt[5] = val;
telnet_send_option(&netcon->tn_data, outopt, 6);
}
break;
case 2: /* SET-DATASIZE */
if (len < 3)
return;
ucval = option[2];
port->io.f->data_size(&port->io, &ucval, &port->bpc);
recalc_port_chardelay(port);
outopt[0] = 44;
outopt[1] = 102;
outopt[2] = ucval;
telnet_send_option(&netcon->tn_data, outopt, 3);
break;
case 3: /* SET-PARITY */
if (len < 3)
return;
ucval = option[2];
port->io.f->parity(&port->io, &ucval, &port->bpc);
recalc_port_chardelay(port);
outopt[0] = 44;
outopt[1] = 103;
outopt[2] = ucval;
telnet_send_option(&netcon->tn_data, outopt, 3);
break;
case 4: /* SET-STOPSIZE */
if (len < 3)
return;
ucval = option[2];
port->io.f->stop_size(&port->io, &ucval, &port->bpc);
recalc_port_chardelay(port);
outopt[0] = 44;
outopt[1] = 104;
outopt[2] = ucval;
telnet_send_option(&netcon->tn_data, outopt, 3);
break;
case 5: /* SET-CONTROL */
if (len < 3)
return;
ucval = option[2];
port->io.f->control(&port->io, &ucval);
outopt[0] = 44;
outopt[1] = 105;
outopt[2] = ucval;
telnet_send_option(&netcon->tn_data, outopt, 3);
break;
case 8: /* FLOWCONTROL-SUSPEND */
port->io.f->flow_control(&port->io, 1);
outopt[0] = 44;
outopt[1] = 108;
telnet_send_option(&netcon->tn_data, outopt, 2);
break;
case 9: /* FLOWCONTROL-RESUME */
port->io.f->flow_control(&port->io, 0);
outopt[0] = 44;
outopt[1] = 109;
telnet_send_option(&netcon->tn_data, outopt, 2);
break;
case 10: /* SET-LINESTATE-MASK */
if (len < 3)
return;
port->linestate_mask = option[2];
outopt[0] = 44;
outopt[1] = 110;
outopt[2] = port->linestate_mask;
telnet_send_option(&netcon->tn_data, outopt, 3);
break;
case 11: /* SET-MODEMSTATE-MASK */
if (len < 3)
return;
port->modemstate_mask = option[2];
outopt[0] = 44;
outopt[1] = 111;
outopt[2] = port->modemstate_mask;
telnet_send_option(&netcon->tn_data, outopt, 3);
break;
case 12: /* PURGE_DATA */
if (len < 3)
return;
val = option[2];
port->io.f->flush(&port->io, &val);
outopt[0] = 44;
outopt[1] = 112;
outopt[2] = val;
telnet_send_option(&netcon->tn_data, outopt, 3);
break;
case 6: /* NOTIFY-LINESTATE */
case 7: /* NOTIFY-MODEMSTATE */
default:
break;
}
}
void
shutdown_ports(void)
{
port_info_t *port = ports, *next;
/* No need for a lock here, nothing can reconfigure the port list at
this point. */
while (port != NULL) {
port->config_num = -1;
next = port->next;
LOCK(port->lock);
change_port_state(NULL, port, PORT_DISABLED, false);
LOCK(port->lock);
shutdown_port(port, "program shutdown");
UNLOCK(port->lock);
port = next;
}
}
int
check_ports_shutdown(void)
{
return ports == NULL;
}