summaryrefslogtreecommitdiff
path: root/apps/netutils/telnetd/telnetd_driver.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/netutils/telnetd/telnetd_driver.c')
-rw-r--r--apps/netutils/telnetd/telnetd_driver.c735
1 files changed, 735 insertions, 0 deletions
diff --git a/apps/netutils/telnetd/telnetd_driver.c b/apps/netutils/telnetd/telnetd_driver.c
new file mode 100644
index 000000000..efa693cdb
--- /dev/null
+++ b/apps/netutils/telnetd/telnetd_driver.c
@@ -0,0 +1,735 @@
+/****************************************************************************
+ * apps/netutils/telnetd_driver.c
+ *
+ * Copyright (C) 2008-2 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <gnutt@nuttx.org>
+ *
+ * Copyright (C) 2007, 2009, 2011-2012 Gregory Nutt. All rights reserved.
+ * Author: Gregory Nutt <gnutt@nuttx.org>
+ *
+ * This is a leverage of similar logic from uIP which has a compatible BSD
+ * license:
+ *
+ * Author: Adam Dunkels <adam@sics.se>
+ * Copyright (c) 2003, Adam Dunkels.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. Neither the name of the Institute, NuttX nor the names of its contributors
+ * may be used to endorse or promote products derived from this software
+ * without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ ****************************************************************************/
+
+/****************************************************************************
+ * Included Files
+ ****************************************************************************/
+
+#include <nuttx/config.h>
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <fcntl.h>
+#include <string.h>
+#include <poll.h>
+#include <errno.h>
+#include <nuttx/fs.h>
+#include <debug.h>
+
+#include <net/psock.h>
+
+#include <apps/netutils/telnetd.h>
+#include <apps/netutils/uiplib.h>
+
+#include "telnetd.h"
+
+/****************************************************************************
+ * Pre-processor Definitions
+ ****************************************************************************/
+
+#define ISO_nl 0x0a
+#define ISO_cr 0x0d
+
+#define TELNET_IAC 255
+#define TELNET_WILL 251
+#define TELNET_WONT 252
+#define TELNET_DO 253
+#define TELNET_DONT 254
+
+/****************************************************************************
+ * Private Types
+ ****************************************************************************/
+/* The state of the telnet parser */
+
+enum telnetd_state_e
+{
+ STATE_NORMAL = 0,
+ STATE_IAC,
+ STATE_WILL,
+ STATE_WONT,
+ STATE_DO,
+ STATE_DONT
+};
+
+/* This structure describes the internal state of the driver */
+
+struct telnetd_dev_s
+{
+ sem_t td_exclsem; /* Enforces mutually exclusive access */
+ uint8_t td_state; /* (See telnetd_state_e) */
+ uint8_t td_pending; /* Number of valid, pending bytes in the rxbuffer */
+ uint8_t td_offset; /* Offset to the valid, pending bytes in the rxbuffer */
+ uint8_t td_crefs; /* The number of open references to the session */
+ FAR struct socket *td_psock; /* A reference to the internal socket structure */
+ char td_rxbuffer[CONFIG_TELNETD_IOBUFFER_SIZE];
+ char td_txbuffer[CONFIG_TELNETD_IOBUFFER_SIZE];
+};
+
+/****************************************************************************
+ * Private Function Prototypes
+ ****************************************************************************/
+/* Support functions */
+
+#ifdef CONFIG_TELNETD_DUMPBUFFER
+static inline void telnetd_dumpbuffer(FAR const char *msg,
+ FAR const char *buffer, unsigned int nbytes)
+#else
+# define telnetd_dumpbuffer(msg,buffer,nbytes)
+#endif
+static void telnetd_getchar(struct telnetd_dev_s *priv, uint8_t ch,
+ FAR char *dest, int *nread);
+static ssize_t telnetd_receive(struct telnetd_dev_s *priv, FAR const char *src,
+ size_t srclen, FAR char *dest, size_t destlen);
+static bool telnetd_putchar(struct telnetd_dev_s *priv, uint8_t ch,
+ int *nwritten);
+static void telnetd_sendopt(struct telnetd_dev_s *priv, uint8_t option,
+ uint8_t value);
+
+/* Character driver methods */
+
+static int telnetd_open(FAR struct file *filep);
+static int telnetd_close(FAR struct file *filep);
+static ssize_t telnetd_read(FAR struct file *, FAR char *, size_t);
+static ssize_t telnetd_write(FAR struct file *, FAR const char *, size_t);
+static int telnetd_ioctl(FAR struct file *filep, int cmd,
+ unsigned long arg);
+
+/****************************************************************************
+ * Private Data
+ ****************************************************************************/
+
+static const struct file_operations g_telnetdfops =
+{
+ telnetd_open, /* open */
+ telnetd_close, /* close */
+ telnetd_read, /* read */
+ telnetd_write, /* write */
+ 0, /* seek */
+ telnetd_ioctl /* ioctl */
+#ifndef CONFIG_DISABLE_POLL
+ , 0 /* poll */
+#endif
+};
+
+/****************************************************************************
+ * Private Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: telnetd_dumpbuffer
+ *
+ * Description:
+ * Dump a buffer of data (debug only)
+ *
+ ****************************************************************************/
+
+#ifdef CONFIG_TELNETD_DUMPBUFFER
+static inline void telnetd_dumpbuffer(FAR const char *msg,
+ FAR const char *buffer,
+ unsigned int nbytes)
+{
+ /* CONFIG_DEBUG, CONFIG_DEBUG_VERBOSE, and CONFIG_DEBUG_NET have to be
+ * defined or the following does nothing.
+ */
+
+ nvdbgdumpbuffer(msg, (FAR const uint8_t*)buffer, nbytes);
+}
+#endif
+
+/****************************************************************************
+ * Name: telnetd_getchar
+ *
+ * Description:
+ * Get another character for the user received buffer from the RX buffer
+ *
+ ****************************************************************************/
+
+static void telnetd_getchar(struct telnetd_dev_s *priv, uint8_t ch,
+ FAR char *dest, int *nread)
+{
+ register int index;
+
+ /* Ignore carriage returns */
+
+ if (ch != ISO_cr)
+ {
+ /* Add all other characters to the destination buffer */
+
+ index = *nread;
+ dest[index++] = ch;
+ *nread = index;
+ }
+}
+
+/****************************************************************************
+ * Name: telnetd_receive
+ *
+ * Description:
+ * Process a received telenet buffer
+ *
+ ****************************************************************************/
+
+static ssize_t telnetd_receive(struct telnetd_dev_s *priv, FAR const char *src,
+ size_t srclen, FAR char *dest, size_t destlen)
+{
+ int nread;
+ uint8_t ch;
+
+ for (nread = 0; srclen > 0 && nread < destlen; srclen--)
+ {
+ ch = *src++;
+ nvdbg("ch=%02x state=%d\n", ch, priv->td_state);
+
+ switch (priv->td_state)
+ {
+ case STATE_IAC:
+ if (ch == TELNET_IAC)
+ {
+ telnetd_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;
+
+ default:
+ priv->td_state = STATE_NORMAL;
+ break;
+ }
+ }
+ break;
+
+ case STATE_WILL:
+ /* Reply with a DONT */
+
+ telnetd_sendopt(priv, TELNET_DONT, ch);
+ priv->td_state = STATE_NORMAL;
+ break;
+
+ case STATE_WONT:
+ /* Reply with a DONT */
+
+ telnetd_sendopt(priv, TELNET_DONT, ch);
+ priv->td_state = STATE_NORMAL;
+ break;
+
+ case STATE_DO:
+ /* Reply with a WONT */
+
+ telnetd_sendopt(priv, TELNET_WONT, ch);
+ priv->td_state = STATE_NORMAL;
+ break;
+
+ case STATE_DONT:
+ /* Reply with a WONT */
+
+ telnetd_sendopt(priv, TELNET_WONT, ch);
+ priv->td_state = STATE_NORMAL;
+ break;
+
+ case STATE_NORMAL:
+ if (ch == TELNET_IAC)
+ {
+ priv->td_state = STATE_IAC;
+ }
+ else
+ {
+ telnetd_getchar(priv, ch, dest, &nread);
+ }
+ break;
+ }
+ }
+
+ /* 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 telnetd_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: telnetd_putchar
+ *
+ * Description:
+ * Put another character from the user buffer to the TX buffer.
+ *
+ ****************************************************************************/
+
+static bool telnetd_putchar(struct telnetd_dev_s *priv, uint8_t ch,
+ int *nread)
+{
+ register int index;
+ bool ret = false;
+
+ /* Ignore carriage returns (we will put these in automatically as necesary) */
+
+ if (ch != ISO_cr)
+ {
+ /* Add all other characters to the destination buffer */
+
+ index = *nread;
+ priv->td_txbuffer[index++] = ch;
+
+ /* Check for line feeds */
+
+ if (ch == ISO_nl)
+ {
+ /* Now add the carriage return */
+
+ priv->td_txbuffer[index++] = ISO_cr;
+ priv->td_txbuffer[index++] = '\0';
+
+ /* End of line */
+
+ ret = true;
+ }
+
+ *nread = index;
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ * Name: telnetd_sendopt
+ *
+ * Description:
+ * Send the telnet option bytes
+ *
+ ****************************************************************************/
+
+static void telnetd_sendopt(struct telnetd_dev_s *priv, uint8_t option,
+ uint8_t value)
+{
+ uint8_t optbuf[4];
+ optbuf[0] = TELNET_IAC;
+ optbuf[1] = option;
+ optbuf[2] = value;
+ optbuf[3] = 0;
+
+ telnetd_dumpbuffer("Send optbuf", optbuf, 4);
+ if (psock_send(priv->td_psock, optbuf, 4, 0) < 0)
+ {
+ ndbg("Failed to send TELNET_IAC\n");
+ }
+}
+
+/****************************************************************************
+ * Name: telnetd_open
+ ****************************************************************************/
+
+static int telnetd_open(FAR struct file *filep)
+{
+ FAR struct inode *inode = filep->f_inode;
+ FAR struct telnetd_dev_s *priv = inode->i_private;
+ int tmp;
+ int ret;
+
+ nvdbg("td_crefs: %d\n", priv->td_crefs);
+
+ /* O_NONBLOCK is not supported */
+
+ if (filep->f_oflags & O_NONBLOCK)
+ {
+ ret = -ENOSYS;
+ goto errout;
+ }
+
+ /* Get exclusive access to the device structures */
+
+ ret = sem_wait(&priv->td_exclsem);
+ if (ret < 0)
+ {
+ ret = -errno;
+ 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_sem;
+ }
+
+ /* Save the new open count on success */
+
+ priv->td_crefs = tmp;
+ ret = OK;
+
+errout_with_sem:
+ sem_post(&priv->td_exclsem);
+
+errout:
+ return ret;
+}
+
+/****************************************************************************
+ * Name: telnetd_close
+ ****************************************************************************/
+
+static int telnetd_close(FAR struct file *filep)
+{
+ FAR struct inode *inode = filep->f_inode;
+ FAR struct telnetd_dev_s *priv = inode->i_private;
+ int ret;
+
+ nvdbg("td_crefs: %d\n", priv->td_crefs);
+
+ /* Get exclusive access to the device structures */
+
+ ret = sem_wait(&priv->td_exclsem);
+ if (ret < 0)
+ {
+ ret = -errno;
+ goto errout;
+ }
+
+ /* Decrement the references to the driver. If the reference count will
+ * decrement to 0, then uninitialize the driver.
+ */
+
+ if (priv->td_crefs > 1)
+ {
+ priv->td_crefs--;
+ sem_post(&priv->td_exclsem);
+ }
+ else
+ {
+ psock_close(priv->td_psock);
+ sem_post(&priv->td_exclsem);
+ sem_destroy(&priv->td_exclsem);
+ free(priv);
+ }
+
+ ret = OK;
+
+errout:
+ return ret;
+}
+
+/****************************************************************************
+ * Name: telnetd_read
+ ****************************************************************************/
+
+static ssize_t telnetd_read(FAR struct file *filep, FAR char *buffer, size_t len)
+{
+ FAR struct inode *inode = filep->f_inode;
+ FAR struct telnetd_dev_s *priv = inode->i_private;
+ ssize_t ret;
+
+ /* First, handle the case where there are still valid bytes left in the
+ * I/O buffer from the last time that read was called.
+ */
+
+ if (priv->td_pending > 0)
+ {
+ FAR const char *src = &priv->td_rxbuffer[priv->td_offset];
+ ret = telnetd_receive(priv, src, priv->td_pending, buffer, len);
+ }
+
+ /* Read a buffer of data from the telnet client */
+
+ else
+ {
+ ret = psock_recv(priv->td_psock, priv->td_rxbuffer,
+ CONFIG_TELNETD_IOBUFFER_SIZE, 0);
+ if (ret > 0)
+ {
+ /* Process the received telnet data */
+
+ telnetd_dumpbuffer("Received buffer", priv->td_rxbuffer, ret);
+ ret = telnetd_receive(priv, priv->td_rxbuffer, ret, buffer, len);
+ }
+ }
+
+ return ret;
+}
+
+/****************************************************************************
+ * Name: telnetd_write
+ ****************************************************************************/
+
+static ssize_t telnetd_write(FAR struct file *filep, FAR const char *buffer, size_t len)
+{
+ FAR struct inode *inode = filep->f_inode;
+ FAR struct telnetd_dev_s *priv = inode->i_private;
+ FAR const char *src = buffer;
+ ssize_t nsent;
+ ssize_t ret;
+ int ncopied;
+ char ch;
+ bool eol;
+
+ /* Process each character from the user buffer */
+
+ for (nsent = 0, ncopied = 0; len > 0; len--)
+ {
+ /* Get the next character from the user buffer */
+
+ ch = *src++;
+
+ /* Add the character to the TX buffer */
+
+ eol = telnetd_putchar(priv, ch, &ncopied);
+
+ /* Was that the end of a line? */
+
+ if (eol)
+ {
+ /* Yes... send the data now */
+
+ ret = psock_send(priv->td_psock, priv->td_txbuffer, ncopied, 0);
+ if (ret < 0)
+ {
+ ndbg("Failed to send response: %s\n", priv->td_txbuffer);
+ }
+
+ /* Reset the index to the beginning of the TX buffer. Remember
+ * the total number of bytes sent;
+ */
+
+ nsent += ret;
+ 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)
+ {
+ ndbg("Failed to send response: %s\n", priv->td_txbuffer);
+ }
+ nsent += ret;
+ }
+
+ return nsent;
+}
+
+/****************************************************************************
+ * Name: telnetd_poll
+ ****************************************************************************/
+
+static int telnetd_ioctl(FAR struct file *filep, int cmd, unsigned long arg)
+{
+#if 0 /* No ioctl commands are yet supported */
+ struct inode *inode = filep->f_inode;
+ struct cdcacm_dev_s *priv = inode->i_private;
+ int ret = OK;
+
+ switch (cmd)
+ {
+ /* Add ioctl commands here */
+
+ default:
+ ret = -ENOTTY;
+ break;
+ }
+
+ return ret;
+#else
+ return -ENOTTY;
+#endif
+}
+
+/****************************************************************************
+ * Name: telnetd_poll
+ ****************************************************************************/
+
+#if 0 /* Not used by this driver */
+static int telnetd_poll(FAR struct file *filep, FAR struct pollfd *fds,
+ bool setup)
+{
+ FAR struct inode *inode = filep->f_inode;
+ FAR struct telnetd_dev_s *priv = inode->i_private;
+}
+#endif
+
+/****************************************************************************
+ * Public Functions
+ ****************************************************************************/
+
+/****************************************************************************
+ * Name: telnetd_driver
+ *
+ * Description:
+ * Create a character driver to "wrap" the telnet session. This function
+ * will select and return a unique path for the new telnet device.
+ *
+ * Parameters:
+ * sd - The socket descriptor that represents the new telnet connection.
+ * daemon - A pointer to the structure representing the overall state of
+ * this instance of the telnet daemon.
+ *
+ * Return:
+ * An allocated string represent the full path to the created driver. The
+ * receiver of the string must de-allocate this memory when it is no longer
+ * needed. NULL is returned on a failure.
+ *
+ ****************************************************************************/
+
+FAR char *telnetd_driver(int sd, FAR struct telnetd_s *daemon)
+{
+ FAR struct telnetd_dev_s *priv;
+ FAR char *devpath = NULL;
+ int minor;
+ int ret;
+
+ /* Allocate instance data for this driver */
+
+ priv = (FAR struct telnetd_dev_s*)malloc(sizeof(struct telnetd_dev_s));
+ if (!priv)
+ {
+ ndbg("Failed to allocate the driver data structure\n");
+ return NULL;
+ }
+
+ /* Initialize the allocated driver instance */
+
+ priv->td_psock = sockfd_socket(sd);
+ priv->td_state = STATE_NORMAL;
+ priv->td_crefs = 0;
+ priv->td_pending = 0;
+ priv->td_offset = 0;
+
+ if (!priv->td_psock)
+ {
+ ndbg("Failed to convert sd=%d to a socket structure\n");
+ goto errout_with_dev;
+ }
+
+ /* Allocation a unique minor device number of the telnet drvier */
+
+ do
+ {
+ ret = sem_wait(&g_telnetdcommon.exclsem);
+ if (ret < 0 && errno != -EINTR)
+ {
+ goto errout_with_dev;
+ }
+ }
+ while (ret < 0);
+
+ minor = g_telnetdcommon.minor;
+ g_telnetdcommon.minor++;
+ sem_post(&g_telnetdcommon.exclsem);
+
+ /* Create a path and name for the driver. */
+
+ ret = asprintf(&devpath, "/dev/telnetd%d", minor);
+ if (ret < 0)
+ {
+ ndbg("Failed to allocate the driver path\n");
+ goto errout_with_dev;
+ }
+
+ /* Register the driver */
+
+ ret = register_driver(devpath, &g_telnetdfops, 0666, priv);
+ if (ret < 0)
+ {
+ ndbg("Failed to register the driver %s: %d\n", ret);
+ goto errout_with_devpath;
+ }
+
+ /* Return the path to the new telnet driver */
+
+ return devpath;
+
+errout_with_devpath:
+ free(devpath);
+errout_with_dev:
+ free(priv);
+ return NULL;
+}
+
+
+