📄 dataxfer.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 <termios.h>#include <sys/types.h>#include <sys/ioctl.h>#include <sys/time.h>#include <sys/socket.h>#include <sys/stat.h>#include <arpa/inet.h>#include <stdlib.h>#include <unistd.h>#include <stdio.h>#include <fcntl.h>#include <netinet/in.h>#include <errno.h>#include <syslog.h>#include <string.h>#include <signal.h>#include <ctype.h>#include "dataxfer.h"#include "selector.h"#include "devcfg.h"#include "utils.h"#include "telnet.h"extern selector_t *ser2net_sel;/** BASED ON sshd.c FROM openssh.com */#ifdef HAVE_TCPD_H#include <tcpd.h>static char *progname = "ser2net";#endif /* HAVE_TCPD_H */#ifdef USE_UUCP_LOCKINGstatic char *uucp_lck_dir = "/var/lock";#ifndef HAVE_TCPD_Hstatic char *progname = "ser2net";#endif#endif /* USE_UUCP_LOCKING *//* States for the tcp_to_dev_state and dev_to_tcp_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. */char *state_str[] = { "unconnected", "waiting input", "waiting output" };#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" };#define PORT_BUFSIZE 1024typedef struct port_info{ 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. */ int timeout_left; /* The amount of time left (in seconds) before the timeout goes off. */ sel_timer_t *timer; /* Used to timeout when the no I/O has been seen for a certain period of time. */ /* Information about the TCP port. */ char *portname; /* The name given for the port. */ struct sockaddr_in tcpport; /* The TCP port to listen on for connections to this terminal device. */ int acceptfd; /* The file descriptor used to accept connections on the TCP port. */ int tcpfd; /* When connected, the file descriptor for the TCP port used for I/O. */ struct sockaddr_in remote; /* The socket address of who is connected to this port. */ unsigned int tcp_bytes_received; /* Number of bytes read from the TCP port. */ unsigned int tcp_bytes_sent; /* Number of bytes written to the TCP port. */ /* Information about the terminal device. */ char *devname; /* The full path to the device */ int devfd; /* The file descriptor for the device, only valid if the TCP port is open. */ struct termios termctl; /* The termios information to set for the device. */ 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 TCP port to the terminal device. */ int tcp_to_dev_state; /* State of transferring data from the TCP port to the device. */ unsigned char tcp_to_dev_buf[PORT_BUFSIZE]; /* Buffer used for TCP to device transfers. */ int tcp_to_dev_buf_start; /* The first byte in the buffer that is ready to send. */ int tcp_to_dev_buf_count; /* The number of bytes in the buffer to send. */ struct controller_info *tcp_monitor; /* If non-null, send any input received from the TCP port to this controller port. */ /* Information use when transferring information from the terminal device to the TCP port. */ int dev_to_tcp_state; /* State of transferring data from the device to the TCP port. */ char dev_to_tcp_buf[PORT_BUFSIZE]; /* Buffer used for device to TCP transfers. */ int dev_to_tcp_buf_start; /* The first byte in the buffer that is ready to send. */ int dev_to_tcp_buf_count; /* The number of bytes in the buffer to send. */ 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. */ /* Banner to display at startup, or NULL if none. */ char *banner; /* Data for the telnet processing */ telnet_data_t tn_data; /* Allow RFC 2217 mode */ int allow_2217; /* Is RFC 2217 mode enabled? */ int is_2217; /* Holds whether break is on or not. */ int break_set; /* Masks for RFC 2217 */ unsigned char linestate_mask; unsigned char modemstate_mask; unsigned char last_modemstate;} port_info_t;port_info_t *ports = NULL; /* Linked list of ports. */static void shutdown_port(port_info_t *port, char *reason);/* 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,};/* 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, 0, 1, }, { TN_OPT_COM_PORT, 1, 0, 0, 0, 0, 0, com_port_handler, com_port_will }, { 255 }};#ifdef USE_UUCP_LOCKINGstatic intuucp_fname_lock_size(char *devname){ char *ptr; (ptr = strrchr(devname, '/')); if (ptr == NULL) { ptr = devname; } else { ptr = ptr + 1; } return 7 + strlen(uucp_lck_dir) + strlen(ptr);}static voiduucp_fname_lock(char *buf, char *devname){ char *ptr; (ptr = strrchr(devname, '/')); if (ptr == NULL) { ptr = devname; } else { ptr = ptr + 1; } sprintf(buf, "%s/LCK..%s", uucp_lck_dir, ptr);}static voiduucp_rm_lock(char *devname){ char *lck_file; if (!uucp_locking_enabled) return; lck_file = malloc(uucp_fname_lock_size(devname)); if (lck_file == NULL) { return; } uucp_fname_lock(lck_file, devname); unlink(lck_file); free(lck_file);}/* return 0=OK, -1=error, 1=locked by other proces */static intuucp_mk_lock(char *devname){ struct stat stt; int pid=-1; if (!uucp_locking_enabled) return 0; if( stat(uucp_lck_dir, &stt) == 0 ) { /* is lock file directory present? */ char *lck_file, buf[64]; int fd; lck_file = malloc(uucp_fname_lock_size(devname)); if (lck_file == NULL) { return -1; } uucp_fname_lock(lck_file, devname); pid = 0; if( (fd = open(lck_file, O_RDONLY)) >= 0 ) { int n; n = read(fd, buf, sizeof(buf)); close(fd); if( n == 4 ) /* Kermit-style lockfile. */ pid = *(int *)buf; else if( n > 0 ) { /* Ascii lockfile. */ buf[n] = 0; sscanf(buf, "%d", &pid); } if( pid > 0 && kill((pid_t)pid, 0) < 0 && errno == ESRCH ) { /* death lockfile - remove it */ unlink(lck_file); sleep(1); pid = 0; } else pid = 1; } if( pid == 0 ) { int mask; mask = umask(022); fd = open(lck_file, O_WRONLY | O_CREAT | O_EXCL, 0666); umask(mask); if( fd >= 0 ) { snprintf( buf, sizeof(buf), "%10ld\t%s\n", (long)getpid(), progname ); write( fd, buf, strlen(buf) ); close(fd); } else { pid = 1; } } free(lck_file); } return pid;}#endif /* USE_UUCP_LOCKING */static voidinit_port_data(port_info_t *port){ port->enabled = PORT_DISABLED; port->portname = NULL; memset(&(port->tcpport), 0, sizeof(port->tcpport)); port->acceptfd = -1; port->tcpfd = -1; port->timeout = 0; port->next = NULL; port->new_config = NULL; port->tcp_monitor = NULL; port->devname = NULL; port->devfd = 0; memset(&(port->remote), 0, sizeof(port->remote)); memset(&(port->termctl), 0, sizeof(port->termctl)); port->tcp_to_dev_state = PORT_UNCONNECTED; port->tcp_to_dev_buf_start = 0; port->tcp_to_dev_buf_count = 0; port->tcp_bytes_received = 0; port->tcp_bytes_sent = 0; port->dev_to_tcp_state = PORT_UNCONNECTED; port->dev_to_tcp_buf_start = 0; port->dev_to_tcp_buf_count = 0; port->dev_bytes_received = 0; port->dev_bytes_sent = 0; port->is_2217 = 0; port->break_set = 0;}voiddelete_tcp_to_dev_char(port_info_t *port, int pos){ int j; for (j=pos; j<port->tcp_to_dev_buf_count-1; j++) { port->tcp_to_dev_buf[j] = port->tcp_to_dev_buf[j+1]; } port->tcp_to_dev_buf_count--;}static voidreset_timer(port_info_t *port){ port->timeout_left = port->timeout;}/* Data is ready to read on the serial port. */static voidhandle_dev_fd_read(int fd, void *data){ port_info_t *port = (port_info_t *) data; int write_count; port->dev_to_tcp_buf_start = 0; port->dev_to_tcp_buf_count = read(fd, port->dev_to_tcp_buf, PORT_BUFSIZE); if (port->dev_to_tcp_buf_count < 0) { /* Got an error on the read, shut down the port. */ syslog(LOG_ERR, "dev read error for port %s: %m", port->portname); shutdown_port(port, "dev read error"); return; } else if (port->dev_to_tcp_buf_count == 0) { /* The port got closed somehow, shut it down. */ shutdown_port(port, "closed port"); return; } port->dev_bytes_received += port->dev_to_tcp_buf_count; write_count = write(port->tcpfd, port->dev_to_tcp_buf, port->dev_to_tcp_buf_count); if (port->dev_monitor != NULL) { controller_write(port->dev_monitor, port->dev_to_tcp_buf, port->dev_to_tcp_buf_count); } if (write_count == -1) { if (errno == EINTR) { /* EINTR means we were interrupted, just retry by returning. */ return; } if (errno == EAGAIN) { /* This was due to O_NONBLOCK, we need to shut off the reader and start the writer monitor. */ sel_set_fd_read_handler(ser2net_sel, port->devfd, SEL_FD_HANDLER_DISABLED); sel_set_fd_write_handler(ser2net_sel, port->tcpfd, SEL_FD_HANDLER_ENABLED); port->dev_to_tcp_state = PORT_WAITING_OUTPUT_CLEAR; } else if (errno == EPIPE) { shutdown_port(port, "EPIPE"); } else { /* Some other bad error. */ syslog(LOG_ERR, "The tcp write for port %s had error: %m", port->portname); shutdown_port(port, "tcp write error"); } } else { port->tcp_bytes_sent += write_count; port->dev_to_tcp_buf_count -= write_count; if (port->dev_to_tcp_buf_count != 0) { /* We didn't write all the data, shut off the reader and start the write monitor. */ port->dev_to_tcp_buf_start += write_count; sel_set_fd_read_handler(ser2net_sel, port->devfd, SEL_FD_HANDLER_DISABLED); sel_set_fd_write_handler(ser2net_sel, port->tcpfd, SEL_FD_HANDLER_ENABLED); port->dev_to_tcp_state = PORT_WAITING_OUTPUT_CLEAR; } } reset_timer(port);}/* 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 voidhandle_dev_fd_write(int fd, void *data){ port_info_t *port = (port_info_t *) data; int write_count; write_count = write(port->devfd, &(port->tcp_to_dev_buf[port->tcp_to_dev_buf_start]), port->tcp_to_dev_buf_count); if (write_count == -1) { if (errno == EINTR) { /* EINTR means we were interrupted, just retry by returning. */ return; } if (errno == EAGAIN) { /* This again was due to O_NONBLOCK, just ignore it. */ } 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"); } } else { port->dev_bytes_sent += write_count; port->tcp_to_dev_buf_count -= write_count; if (port->tcp_to_dev_buf_count != 0) { /* We didn't write all the data, continue writing. */ port->tcp_to_dev_buf_start += write_count; } else { /* We are done writing, turn the reader back on. */ sel_set_fd_read_handler(ser2net_sel, port->tcpfd, SEL_FD_HANDLER_ENABLED); sel_set_fd_write_handler(ser2net_sel, port->devfd, SEL_FD_HANDLER_DISABLED); port->tcp_to_dev_state = PORT_WAITING_INPUT; } } reset_timer(port);}/* Handle an exception from the serial port. */static voidhandle_dev_fd_except(int fd, void *data){ port_info_t *port = (port_info_t *) data; syslog(LOG_ERR, "Select exception on device for port %s", port->portname); shutdown_port(port, "fd exception");}/* Data is ready to read on the TCP port. */static voidhandle_tcp_fd_read(int fd, void *data){ port_info_t *port = (port_info_t *) data; int write_count; port->tcp_to_dev_buf_start = 0; port->tcp_to_dev_buf_count = read(fd, port->tcp_to_dev_buf, PORT_BUFSIZE); if (port->tcp_to_dev_buf_count < 0) { /* Got an error on the read, shut down the port. */ syslog(LOG_ERR, "read error for port %s: %m", port->portname); shutdown_port(port, "tcp read error"); return; } else if (port->tcp_to_dev_buf_count == 0) { /* The other end closed the port, shut it down. */ shutdown_port(port, "tcp read close"); return; } port->tcp_bytes_received += port->tcp_to_dev_buf_count; if (port->enabled == PORT_TELNET) { port->tcp_to_dev_buf_count = process_telnet_data (port->tcp_to_dev_buf, port->tcp_to_dev_buf_count, &port->tn_data); if (port->tn_data.error) { shutdown_port(port, "telnet output error"); return; } if (port->tcp_to_dev_buf_count == 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; } } write_count = write(port->devfd, port->tcp_to_dev_buf, port->tcp_to_dev_buf_count); if (port->tcp_monitor != NULL) { controller_write(port->tcp_monitor, port->tcp_to_dev_buf, port->tcp_to_dev_buf_count); } if (write_count == -1) { if (errno == EINTR) {
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -