nuttx/drivers/net/telnet.c
Huang Qi c7a935dc1a drivers/telnet: Implement part of termios
Implement local mode control (ECHO only) for telnet, this allow application to disable ECHO of telnet, it's useful to input password during shell login and other case.

Signed-off-by: Huang Qi <huangqi3@xiaomi.com>
2023-04-22 01:03:51 +08:00

1233 lines
33 KiB
C

/****************************************************************************
* drivers/net/telnet.c
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <nuttx/config.h>
#include <assert.h>
#include <stdio.h>
#include <fcntl.h>
#include <poll.h>
#include <errno.h>
#include <debug.h>
#include <termios.h>
#include <nuttx/kmalloc.h>
#include <nuttx/kthread.h>
#include <nuttx/signal.h>
#include <nuttx/mutex.h>
#include <nuttx/fs/fs.h>
#include <nuttx/net/net.h>
#include <nuttx/net/telnet.h>
#ifdef CONFIG_NETDEV_TELNET
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
/* Configuration ************************************************************/
#ifndef CONFIG_TELNET_RXBUFFER_SIZE
# define CONFIG_TELNET_RXBUFFER_SIZE 256
#endif
#ifndef CONFIG_TELNET_TXBUFFER_SIZE
# define CONFIG_TELNET_TXBUFFER_SIZE 256
#endif
#ifndef CONFIG_TELNET_MAXLCLIENTS
# define CONFIG_TELNET_MAXLCLIENTS 8
#endif
#undef HAVE_SIGNALS
#if defined(CONFIG_TTY_SIGINT) || defined(CONFIG_TTY_SIGTSTP)
# define HAVE_SIGNALS
#endif
/* Telnet protocol stuff ****************************************************/
#define TELNET_NL 0x0a
#define TELNET_CR 0x0d
/* Telnet commands */
#define TELNET_ECHO 1
#define TELNET_SGA 3 /* Suppress Go Ahead */
#define TELNET_NAWS 31 /* Negotiate about window size */
/* Telnet control */
#define TELNET_IAC 255
#define TELNET_WILL 251
#define TELNET_WONT 252
#define TELNET_DO 253
#define TELNET_DONT 254
#define TELNET_SB 250
#define TELNET_SE 240
/* Device stuff *************************************************************/
#define TELNET_DEVFMT "/dev/telnet%d"
/****************************************************************************
* Private Types
****************************************************************************/
/* The state of the telnet parser */
enum telnet_state_e
{
STATE_NORMAL = 0,
STATE_IAC,
STATE_WILL,
STATE_WONT,
STATE_DO,
STATE_DONT,
STATE_SB,
STATE_SB_NAWS,
STATE_SE
};
/* This structure describes the internal state of the driver */
struct telnet_dev_s
{
uint8_t td_state; /* (See telnet_state_e) */
uint8_t td_crefs; /* The number of open references to the session */
uint8_t td_minor; /* Minor device number */
uint16_t td_offset; /* Offset to the valid, pending bytes in the rxbuffer */
uint16_t td_pending; /* Number of valid, pending bytes in the rxbuffer */
#ifdef CONFIG_TELNET_SUPPORT_NAWS
uint16_t td_rows; /* Number of NAWS rows */
uint16_t td_cols; /* Number of NAWS cols */
int td_sb_count; /* Count of TELNET_SB bytes received */
#endif
#ifdef HAVE_SIGNALS
pid_t td_pid;
#endif
tcflag_t td_lflag; /* Local modes */
FAR struct socket td_psock; /* A clone of the internal socket structure */
char td_rxbuffer[CONFIG_TELNET_RXBUFFER_SIZE];
char td_txbuffer[CONFIG_TELNET_TXBUFFER_SIZE];
};
/****************************************************************************
* Private Function Prototypes
****************************************************************************/
/* Support functions */
#ifdef CONFIG_TELNET_DUMPBUFFER
static inline void telnet_dumpbuffer(FAR const char *msg,
FAR const char *buffer, unsigned int nbytes);
#else
# define telnet_dumpbuffer(msg,buffer,nbytes)
#endif
static void telnet_getchar(FAR struct telnet_dev_s *priv, uint8_t ch,
FAR char *dest, int *nread);
static ssize_t telnet_receive(FAR struct telnet_dev_s *priv,
FAR const char *src, size_t srclen, FAR char *dest,
size_t destlen);
static bool telnet_putchar(FAR struct telnet_dev_s *priv, uint8_t ch,
int *nwritten);
static void telnet_sendopt(FAR struct telnet_dev_s *priv, uint8_t option,
uint8_t value);
/* Telnet character driver methods */
static int telnet_open(FAR struct file *filep);
static int telnet_close(FAR struct file *filep);
static ssize_t telnet_read(FAR struct file *filep, FAR char *buffer,
size_t len);
static ssize_t telnet_write(FAR struct file *filep, FAR const char *buffer,
size_t len);
static int telnet_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
static int telnet_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup);
/* Telnet session creation */
static int telnet_session(FAR struct telnet_session_s *session);
/* Telnet factory driver methods */
static ssize_t factory_read(FAR struct file *filep, FAR char *buffer,
size_t buflen);
static ssize_t factory_write(FAR struct file *filep, FAR const char *buffer,
size_t buflen);
static int factory_ioctl(FAR struct file *filep, int cmd,
unsigned long arg);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct file_operations g_telnet_fops =
{
telnet_open, /* open */
telnet_close, /* close */
telnet_read, /* read */
telnet_write, /* write */
NULL, /* seek */
telnet_ioctl, /* ioctl */
NULL, /* mmap */
NULL, /* truncate */
telnet_poll /* poll */
};
static const struct file_operations g_factory_fops =
{
NULL, /* open */
NULL, /* close */
factory_read, /* read */
factory_write, /* write */
NULL, /* seek */
factory_ioctl, /* ioctl */
};
/* This is an global data set of all of all active Telnet drivers. This
* additional logic in included to handle killing of task via control
* characters received via Telenet (via Ctrl-C SIGINT, in particular).
*/
static struct telnet_dev_s *g_telnet_clients[CONFIG_TELNET_MAXLCLIENTS];
static mutex_t g_clients_lock = NXMUTEX_INITIALIZER;
/****************************************************************************
* Private Functions
****************************************************************************/
/****************************************************************************
* Name: telnet_dumpbuffer
*
* Description:
* Dump a buffer of data (debug only)
*
****************************************************************************/
#ifdef CONFIG_TELNET_DUMPBUFFER
static inline void telnet_dumpbuffer(FAR const char *msg,
FAR const char *buffer,
unsigned int nbytes)
{
/* CONFIG_DEBUG_FEATURES, CONFIG_DEBUG_INFO, and CONFIG_DEBUG_NET have to
* be defined or the following does nothing.
*/
ninfodumpbuffer(msg, (FAR const uint8_t *)buffer, nbytes);
}
#endif
/****************************************************************************
* Name: telnet_check_ctrlchar
*
* Description:
* Check if an incoming control character should generate a signal.
*
****************************************************************************/
#ifdef HAVE_SIGNALS
static void telnet_check_ctrlchar(FAR struct telnet_dev_s *priv,
FAR char *buffer, size_t len)
{
int signo = 0;
for (; priv->td_pid >= 0 && len > 0; buffer++, len--)
{
#ifdef CONFIG_TTY_SIGINT
/* Is this the special character that will generate the SIGINT
* signal?
*/
if (*buffer == CONFIG_TTY_SIGINT_CHAR)
{
/* Yes.. note that the kill is needed and do not put the character
* into the Rx buffer. It should not be read as normal data.
*/
signo = SIGINT;
break;
}
#endif
#ifdef CONFIG_TTY_SIGTSTP
/* Is this the special character that will generate the SIGTSTP
* signal?
*/
if (*buffer == CONFIG_TTY_SIGTSTP_CHAR)
{
/* Note that the kill is needed and do not put the character
* into the Rx buffer. It should not be read as normal data.
*/
signo = SIGTSTP;
#ifndef CONFIG_TTY_SIGINT
break;
#endif
}
#endif
}
/* Send the signal if necessary */
if (signo != 0)
{
nxsig_kill(priv->td_pid, signo);
}
}
#endif
/****************************************************************************
* Name: telnet_getchar
*
* Description:
* Get another character for the user received buffer from the RX buffer
*
****************************************************************************/
static void telnet_getchar(FAR struct telnet_dev_s *priv, uint8_t ch,
FAR char *dest, int *nread)
{
register int index;
/* Add all characters to the destination buffer */
index = *nread;
dest[index++] = ch;
*nread = index;
}
/****************************************************************************
* Name: telnet_receive
*
* Description:
* Process a received Telnet buffer
*
****************************************************************************/
static ssize_t telnet_receive(FAR struct telnet_dev_s *priv,
FAR const char *src, size_t srclen,
FAR char *dest, size_t destlen)
{
int nread;
uint8_t ch;
ninfo("srclen: %zd destlen: %zd\n", srclen, destlen);
for (nread = 0; srclen > 0 && nread < destlen; srclen--)
{
ch = *src++;
ninfo("ch=%02x state=%d\n", ch, priv->td_state);
switch (priv->td_state)
{
case STATE_IAC:
if (ch == TELNET_IAC)
{
telnet_getchar(priv, ch, dest, &nread);
priv->td_state = STATE_NORMAL;
}
else
{
switch (ch)
{
case TELNET_WILL:
priv->td_state = STATE_WILL;
break;
case TELNET_WONT:
priv->td_state = STATE_WONT;
break;
case TELNET_DO:
priv->td_state = STATE_DO;
break;
case TELNET_DONT:
priv->td_state = STATE_DONT;
break;
#ifdef CONFIG_TELNET_SUPPORT_NAWS
case TELNET_SB:
priv->td_state = STATE_SB;
priv->td_sb_count = 0;
break;
case TELNET_SE:
priv->td_state = STATE_NORMAL;
break;
#endif
default:
priv->td_state = STATE_NORMAL;
break;
}
}
break;
case STATE_WILL:
#ifdef CONFIG_TELNET_SUPPORT_NAWS
/* For NAWS, Reply with a DO */
if (ch == TELNET_NAWS)
{
telnet_sendopt(priv, TELNET_DO, ch);
}
/* Reply with a DON'T */
else
#endif
{
telnet_sendopt(priv, TELNET_DONT, ch);
ninfo("Suppress: 0x%02X (%d)\n", ch, ch);
}
priv->td_state = STATE_NORMAL;
break;
case STATE_WONT:
telnet_sendopt(priv, TELNET_DONT, ch);
priv->td_state = STATE_NORMAL;
break;
case STATE_DO:
if ((priv->td_lflag & ECHO) != 0 && ch == TELNET_ECHO)
{
telnet_sendopt(priv, TELNET_WONT, ch);
}
else
{
telnet_sendopt(priv, TELNET_WILL, ch);
}
priv->td_state = STATE_NORMAL;
break;
case STATE_DONT:
/* Reply with a WONT */
telnet_sendopt(priv, TELNET_WONT, ch);
priv->td_state = STATE_NORMAL;
break;
case STATE_NORMAL:
if (ch == TELNET_IAC)
{
priv->td_state = STATE_IAC;
}
else
{
telnet_getchar(priv, ch, dest, &nread);
}
break;
#ifdef CONFIG_TELNET_SUPPORT_NAWS
/* Handle Telnet Sub negotiation request */
case STATE_SB:
switch (ch)
{
case TELNET_NAWS:
priv->td_state = STATE_SB_NAWS;
break;
default:
priv->td_state = STATE_NORMAL;
break;
}
break;
/* Handle NAWS sub-option negotiation */
case STATE_SB_NAWS:
/* Update cols / rows based on received byte count */
switch (priv->td_sb_count)
{
case 0:
priv->td_cols = (priv->td_cols & 0x00ff) | (ch << 8);
break;
case 1:
priv->td_cols = (priv->td_cols & 0xff00) | ch;
break;
case 2:
priv->td_rows = (priv->td_rows & 0x00ff) | (ch << 8);
break;
case 3:
priv->td_rows = (priv->td_rows & 0xff00) | ch;
ninfo("NAWS: %d,%d", priv->td_cols, priv->td_rows);
break;
}
/* Increment SB count and switch to NORMAL when complete */
if (++priv->td_sb_count == 4)
{
priv->td_state = STATE_NORMAL;
}
break;
#endif
}
}
/* We get here if (1) all of the received bytes have been processed, or
* (2) if the user's buffer has become full.
*/
if (srclen > 0)
{
/* Remember where we left off. These bytes will be returned the next
* time that telnet_read() is called.
*/
priv->td_pending = srclen;
priv->td_offset = (src - priv->td_rxbuffer);
}
else
{
/* All of the received bytes were consumed */
priv->td_pending = 0;
priv->td_offset = 0;
}
return nread;
}
/****************************************************************************
* Name: telnet_putchar
*
* Description:
* Put another character from the user buffer to the TX buffer.
*
****************************************************************************/
static bool telnet_putchar(FAR struct telnet_dev_s *priv, uint8_t ch,
int *nread)
{
register int index;
bool ret = false;
/* Ignore carriage returns (we will put these in automatically as
* necessary).
*/
if (ch != TELNET_CR)
{
/* Add all other characters to the destination buffer */
index = *nread;
priv->td_txbuffer[index++] = ch;
/* Check for line feeds */
if (ch == TELNET_NL)
{
/* Now add the carriage return */
priv->td_txbuffer[index++] = TELNET_CR;
/* End of line */
ret = true;
}
*nread = index;
}
return ret;
}
/****************************************************************************
* Name: telnet_sendopt
*
* Description:
* Send the telnet option bytes
*
****************************************************************************/
static void telnet_sendopt(FAR struct telnet_dev_s *priv, uint8_t option,
uint8_t value)
{
uint8_t optbuf[3];
int ret;
optbuf[0] = TELNET_IAC;
optbuf[1] = option;
optbuf[2] = value;
telnet_dumpbuffer("Send optbuf", optbuf, 3);
ret = psock_send(&priv->td_psock, optbuf, 3, 0);
if (ret < 0)
{
nerr("ERROR: Failed to send TELNET_IAC: %d\n", ret);
}
}
/****************************************************************************
* Name: telnet_open
****************************************************************************/
static int telnet_open(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct telnet_dev_s *priv = inode->i_private;
int tmp;
int ret;
ninfo("td_crefs: %d\n", priv->td_crefs);
/* Get exclusive access to the device structures */
ret = nxmutex_lock(&g_clients_lock);
if (ret < 0)
{
nerr("ERROR: nxmutex_lock failed: %d\n", ret);
goto errout;
}
/* Increment the count of references to the device. If this the first
* time that the driver has been opened for this device, then initialize
* the device.
*/
tmp = priv->td_crefs + 1;
if (tmp > 255)
{
/* More than 255 opens; uint8_t would overflow to zero */
ret = -EMFILE;
goto errout_with_lock;
}
/* Save the new open count on success */
priv->td_crefs = tmp;
ret = OK;
errout_with_lock:
nxmutex_unlock(&g_clients_lock);
errout:
return ret;
}
/****************************************************************************
* Name: telnet_close
****************************************************************************/
static int telnet_close(FAR struct file *filep)
{
FAR struct inode *inode = filep->f_inode;
FAR struct telnet_dev_s *priv = inode->i_private;
FAR char *devpath;
int ret;
int i;
ninfo("td_crefs: %d\n", priv->td_crefs);
/* Get exclusive access to the device structures */
ret = nxmutex_lock(&g_clients_lock);
if (ret < 0)
{
nerr("ERROR: nxmutex_lock failed: %d\n", ret);
return ret;
}
/* Decrement the references to the driver. If the reference count will
* decrement to 0, then uninitialize the driver.
*/
if (priv->td_crefs > 1)
{
/* Just decrement the reference count */
priv->td_crefs--;
}
else
{
/* Re-create the path to the driver. */
ret = asprintf(&devpath, TELNET_DEVFMT, priv->td_minor);
if (ret < 0)
{
nerr("ERROR: Failed to allocate the driver path\n");
}
else
{
/* Un-register the character driver */
ret = unregister_driver(devpath);
if (ret < 0)
{
/* NOTE: a return value of -EBUSY is not an error, it simply
* means that the Telnet driver is busy now and cannot be
* registered now because there are other sessions using the
* connection. The driver will be properly unregistered when
* the final session terminates.
*/
if (ret != -EBUSY)
{
nerr("ERROR: Failed to unregister the driver %s: %d\n",
devpath, ret);
}
else
{
ret = OK;
}
}
lib_free(devpath);
}
for (i = 0; i < CONFIG_TELNET_MAXLCLIENTS; i++)
{
if (g_telnet_clients[i] == priv)
{
g_telnet_clients[i] = NULL;
break;
}
}
/* Close the socket */
psock_close(&priv->td_psock);
kmm_free(priv);
}
nxmutex_unlock(&g_clients_lock);
return ret;
}
/****************************************************************************
* Name: telnet_read
****************************************************************************/
static ssize_t telnet_read(FAR struct file *filep, FAR char *buffer,
size_t len)
{
FAR struct inode *inode = filep->f_inode;
FAR struct telnet_dev_s *priv = inode->i_private;
ssize_t nread = 0;
ninfo("len: %zd\n", len);
/* First, handle the case where there are still valid bytes left in the
* I/O buffer from the last time that read was called. NOTE: Much of
* what we read may be protocol stuff and may not correspond to user
* data. Hence we need the loop and we need may need to wait for data
* multiple times in order to get data that the client is interested in.
*/
do
{
FAR const char *src;
if (priv->td_pending == 0)
{
nread = psock_recv(&priv->td_psock,
priv->td_rxbuffer,
CONFIG_TELNET_RXBUFFER_SIZE,
0);
if (nread <= 0)
{
return nread;
}
priv->td_pending = nread;
}
/* Process the buffered telnet data */
src = &priv->td_rxbuffer[priv->td_offset];
nread = telnet_receive(priv, src, priv->td_pending, buffer, len);
}
while (nread == 0);
#ifdef HAVE_SIGNALS
/* Check if any of the received characters is a
* control that should generate a signal.
*/
telnet_check_ctrlchar(priv, buffer, nread);
#endif
/* Returned Value:
*
* nread > 0: The number of characters copied into the user buffer by
* telnet_receive().
* nread <= 0: Loss of connection or error events reported by recv().
*/
return nread;
}
/****************************************************************************
* Name: telnet_write
****************************************************************************/
static ssize_t telnet_write(FAR struct file *filep, FAR const char *buffer,
size_t len)
{
FAR struct inode *inode = filep->f_inode;
FAR struct telnet_dev_s *priv = inode->i_private;
FAR const char *src = buffer;
ssize_t ret = 0;
ssize_t nsent;
int ncopied;
char ch;
bool eol;
ninfo("len: %zd\n", len);
/* Process each character from the user buffer */
for (nsent = 0, ncopied = 0; nsent < len; nsent++)
{
/* Get the next character from the user buffer */
ch = *src++;
/* Add the character to the TX buffer */
eol = telnet_putchar(priv, ch, &ncopied);
/* Was that the end of a line? Or is the buffer too full to hold the
* next largest character sequence ("\r\n")?
*/
if (eol || ncopied > CONFIG_TELNET_TXBUFFER_SIZE - 2)
{
/* Yes... send the data now */
ret = psock_send(&priv->td_psock, priv->td_txbuffer, ncopied, 0);
if (ret < 0)
{
nerr("ERROR: psock_send failed '%s': %zd\n",
priv->td_txbuffer, ret);
goto out;
}
/* Reset the index to the beginning of the TX buffer. */
ncopied = 0;
}
}
/* Send anything remaining in the TX buffer */
if (ncopied > 0)
{
ret = psock_send(&priv->td_psock, priv->td_txbuffer, ncopied, 0);
if (ret < 0)
{
nerr("ERROR: psock_send failed '%s': %zd\n",
priv->td_txbuffer, ret);
goto out;
}
}
/* Notice that we don't actually return the number of bytes sent, but
* rather, the number of bytes that the caller asked us to send. We may
* have sent more bytes (because of CR-LF expansion). But it confuses
* some logic if you report that you sent more than you were requested to.
*/
out:
return nsent ? nsent : ret;
}
/****************************************************************************
* Name: telnet_session
*
* Description:
* Create a character driver to "wrap" the telnet session. This function
* will select and return a unique path for the new telnet device.
*
* Input Parameters:
* session - On input, contains the socket descriptor that represents the
* new telnet connection. On output, it holds the path to the new Telnet
* driver.
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
static int telnet_session(FAR struct telnet_session_s *session)
{
FAR struct telnet_dev_s *priv;
FAR struct socket *psock;
int ret;
/* Allocate instance data for this driver */
priv = (FAR struct telnet_dev_s *)kmm_zalloc(sizeof(struct telnet_dev_s));
if (!priv)
{
nerr("ERROR: Failed to allocate the driver data structure\n");
return -ENOMEM;
}
priv->td_state = STATE_NORMAL;
priv->td_crefs = 0;
priv->td_minor = 0;
priv->td_pending = 0;
priv->td_offset = 0;
#ifdef HAVE_SIGNALS
priv->td_pid = INVALID_PROCESS_ID;
#endif
#ifdef CONFIG_TELNET_SUPPORT_NAWS
priv->td_rows = 25;
priv->td_cols = 80;
priv->td_sb_count = 0;
#endif
/* Clone the internal socket structure. We do this so that it will be
* independent of threads and of socket descriptors (the original socket
* instance resided in the daemon's task group`).
*/
ret = sockfd_socket(session->ts_sd, &psock);
if (ret != OK)
{
nerr("ERROR: Failed to convert sd=%d to a socket structure\n",
session->ts_sd);
goto errout_with_dev;
}
ret = psock_dup2(psock, &priv->td_psock);
if (ret < 0)
{
nerr("ERROR: psock_dup2 failed: %d\n", ret);
goto errout_with_dev;
}
/* Allocate a unique minor device number of the telnet driver.
* Get exclusive access to the minor counter.
*/
ret = nxmutex_lock(&g_clients_lock);
if (ret < 0)
{
nerr("ERROR: nxmutex_lock failed: %d\n", ret);
goto errout_with_clone;
}
/* Loop until the device name is verified to be unique. */
while (priv->td_minor < CONFIG_TELNET_MAXLCLIENTS)
{
if (g_telnet_clients[priv->td_minor] == NULL)
{
snprintf(session->ts_devpath, TELNET_DEVPATH_MAX,
TELNET_DEVFMT, priv->td_minor);
break;
}
priv->td_minor++;
}
if (priv->td_minor >= CONFIG_TELNET_MAXLCLIENTS)
{
nerr("ERROR: Too many sessions\n");
ret = -ENFILE;
goto errout_with_lock;
}
/* Setting terminal attributes */
priv->td_lflag = ECHO;
/* Register the driver */
ret = register_driver(session->ts_devpath, &g_telnet_fops, 0666, priv);
if (ret < 0)
{
nerr("ERROR: Failed to register the driver %s: %d\n",
session->ts_devpath, ret);
goto errout_with_lock;
}
/* Close the original psock (keeping the clone) */
nx_close(session->ts_sd);
#ifdef CONFIG_TELNET_SUPPORT_NAWS
telnet_sendopt(priv, TELNET_DO, TELNET_NAWS);
#endif
/* Save ourself in the list of Telnet client threads */
g_telnet_clients[priv->td_minor] = priv;
nxmutex_unlock(&g_clients_lock);
return OK;
errout_with_lock:
nxmutex_unlock(&g_clients_lock);
errout_with_clone:
psock_close(&priv->td_psock);
errout_with_dev:
kmm_free(priv);
return ret;
}
/****************************************************************************
* Name: factory_read
****************************************************************************/
static ssize_t factory_read(FAR struct file *filep, FAR char *buffer,
size_t len)
{
return 0; /* Return EOF */
}
/****************************************************************************
* Name: factory_write
****************************************************************************/
static ssize_t factory_write(FAR struct file *filep, FAR const char *buffer,
size_t len)
{
return len; /* Say that everything was written */
}
/****************************************************************************
* Name: telnet_ioctl
****************************************************************************/
static int telnet_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
FAR struct inode *inode = filep->f_inode;
FAR struct telnet_dev_s *priv = inode->i_private;
FAR struct termios *termiosp;
int ret = OK;
switch (cmd)
{
#ifdef HAVE_SIGNALS
/* Make the given terminal the controlling terminal of the calling
* process.
*/
case TIOCSCTTY:
{
/* Check if the ISIG flag is set in the termios c_lflag to enable
* this feature. This flag is set automatically for a serial console
* device.
*/
/* Save the PID of the recipient of the SIGINT signal. */
priv->td_pid = (pid_t)arg;
DEBUGASSERT((unsigned long)(priv->td_pid) == arg);
}
break;
#endif
#ifdef CONFIG_TELNET_SUPPORT_NAWS
case TIOCGWINSZ:
{
FAR struct winsize *pw = (FAR struct winsize *)((uintptr_t)arg);
/* Get row/col from the private data */
pw->ws_row = priv->td_rows;
pw->ws_col = priv->td_cols;
}
break;
#endif
/* Handle TERMIOS command */
case TCGETS:
{
termiosp = (FAR struct termios *)((uintptr_t)arg);
DEBUGASSERT(termiosp != NULL);
cfmakeraw(termiosp);
termiosp->c_lflag = priv->td_lflag;
}
break;
case TCSETS:
{
termiosp = (FAR struct termios *)((uintptr_t)arg);
DEBUGASSERT(termiosp != NULL);
/* Save the termios settings */
priv->td_lflag = termiosp->c_lflag;
if ((priv->td_lflag & ECHO) != 0)
{
/* If ECHO is set, then we need to send the won't echo option
* to the client, let the client do echo to emulate
* the behavior of a real terminal.
*/
telnet_sendopt(priv, TELNET_WONT, TELNET_ECHO);
}
else
{
/* Otherwise, we need to send the will echo option to the
* client, let the client don't echo to disable the echo.
*/
telnet_sendopt(priv, TELNET_WILL, TELNET_ECHO);
}
}
break;
default:
ret = psock_ioctl(&priv->td_psock, cmd, arg);
break;
}
return ret;
}
/****************************************************************************
* Name: telnet_poll
*
* Description:
* The standard poll() operation redirects operations on socket descriptors
* to this function.
*
* Input Parameters:
* fd - The socket descriptor of interest
* fds - The structure describing the events to be monitored, OR NULL if
* this is a request to stop monitoring events.
* setup - true: Setup up the poll; false: Teardown the poll
*
* Returned Value:
* 0: Success; Negated errno on failure
*
****************************************************************************/
static int telnet_poll(FAR struct file *filep, FAR struct pollfd *fds,
bool setup)
{
FAR struct inode *inode = filep->f_inode;
FAR struct telnet_dev_s *priv = inode->i_private;
DEBUGASSERT(fds != NULL);
/* Test if we have cached data waiting to be read */
if (priv->td_pending > 0)
{
/* Yes.. then signal the poll logic */
poll_notify(&fds, 1, POLLRDNORM);
}
/* Then let psock_poll() do the heavy lifting */
return psock_poll(&priv->td_psock, fds, setup);
}
/****************************************************************************
* Name: factory_ioctl
****************************************************************************/
static int factory_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
{
int ret = OK;
switch (cmd)
{
/* Command: SIOCTELNET
* Description: Create a Telnet sessions.
* Argument: A pointer to a write-able instance of struct
* telnet_session_s.
* Dependencies: CONFIG_NETDEV_TELNET
*/
case SIOCTELNET:
{
FAR struct telnet_session_s *session =
(FAR struct telnet_session_s *)((uintptr_t)arg);
if (session == NULL)
{
ret = -EINVAL;
}
else
{
ret = telnet_session(session);
}
}
break;
default:
ret = -ENOTTY;
break;
}
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: telnet_initialize
*
* Description:
* Create the Telnet factory at /dev/telnet.
*
* Input Parameters:
* None
*
* Returned Value:
* Zero (OK) on success; a negated errno value on failure.
*
****************************************************************************/
int telnet_initialize(void)
{
return register_driver("/dev/telnet", &g_factory_fops, 0666, NULL);
}
#endif /* CONFIG_NETDEV_TELNET */