From b5bf8cd163de664f558a5b2b02b01cc8a7722456 Mon Sep 17 00:00:00 2001 From: Joel Sherrill Date: Mon, 24 Sep 2007 21:35:10 +0000 Subject: 2007-09-24 Joel Sherrill PR 1262/filesystem * libcsupport/Makefile.am, libnetworking/libc/herror.c, libnetworking/libc/res_send.c, libnetworking/sys/uio.h, telnetd/Makefile.am, telnetd/README, telnetd/preinstall.am, telnetd/pty.c, telnetd/telnetd.c: Add support for readv() and writev() including documentation and test case. * libcsupport/src/readv.c, libcsupport/src/writev.c: New files. --- cpukit/ChangeLog | 10 + cpukit/libcsupport/Makefile.am | 6 + cpukit/libcsupport/src/readv.c | 118 +++++++ cpukit/libcsupport/src/writev.c | 126 +++++++ cpukit/libnetworking/libc/herror.c | 6 +- cpukit/libnetworking/libc/res_send.c | 7 +- cpukit/libnetworking/sys/uio.h | 7 +- cpukit/telnetd/Makefile.am | 4 +- cpukit/telnetd/README | 2 +- cpukit/telnetd/preinstall.am | 4 + cpukit/telnetd/pty.c | 657 +++++++++++++++++++++++++---------- cpukit/telnetd/telnetd.c | 474 +++++++++++++++++++++---- 12 files changed, 1160 insertions(+), 261 deletions(-) create mode 100644 cpukit/libcsupport/src/readv.c create mode 100644 cpukit/libcsupport/src/writev.c (limited to 'cpukit') diff --git a/cpukit/ChangeLog b/cpukit/ChangeLog index ef7ef19a91..0e9bb1b28d 100644 --- a/cpukit/ChangeLog +++ b/cpukit/ChangeLog @@ -1,3 +1,13 @@ +2007-09-24 Joel Sherrill + + PR 1262/filesystem + * libcsupport/Makefile.am, libnetworking/libc/herror.c, + libnetworking/libc/res_send.c, libnetworking/sys/uio.h, + telnetd/Makefile.am, telnetd/README, telnetd/preinstall.am, + telnetd/pty.c, telnetd/telnetd.c: Add support for readv() and + writev() including documentation and test case. + * libcsupport/src/readv.c, libcsupport/src/writev.c: New files. + 2007-09-21 Joel Sherrill * libnetworking/Makefile.am: Add dummy socketpair() implementation to diff --git a/cpukit/libcsupport/Makefile.am b/cpukit/libcsupport/Makefile.am index fdbeb48994..10ac75bb31 100644 --- a/cpukit/libcsupport/Makefile.am +++ b/cpukit/libcsupport/Makefile.am @@ -63,6 +63,12 @@ SYSTEM_CALL_C_FILES = src/open.c src/close.c src/read.c src/write.c \ src/pipe.c src/dup.c src/dup2.c src/symlink.c src/readlink.c src/creat.c \ src/chroot.c src/sync.c +## Until sys/uio.h is moved to libcsupport, we have to have networking +## enabled to compile these. Hopefully this is a temporary situation. +if LIBNETWORKING +SYSTEM_CALL_C_FILES += src/readv.c src/writev.c +endif + DIRECTORY_SCAN_C_FILES = src/opendir.c src/closedir.c src/readdir.c \ src/readdir_r.c src/rewinddir.c src/scandir.c src/seekdir.c \ src/telldir.c src/getcwd.c diff --git a/cpukit/libcsupport/src/readv.c b/cpukit/libcsupport/src/readv.c new file mode 100644 index 0000000000..08f0b9c52e --- /dev/null +++ b/cpukit/libcsupport/src/readv.c @@ -0,0 +1,118 @@ +/* + * readv() - POSIX 1003.1 - Read a Vector + * + * OpenGroup URL: + * + * http://www.opengroup.org/onlinepubs/009695399/functions/readv.html + * + * COPYRIGHT (c) 1989-2007. + * On-Line Applications Research Corporation (OAR). + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.com/license/LICENSE. + * + * $Id$ + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +ssize_t readv( + int fd, + const struct iovec *iov, + int iovcnt +) +{ + ssize_t total; + int v; + int bytes; + rtems_libio_t *iop; + boolean all_zeros; + + rtems_libio_check_fd( fd ); + iop = rtems_libio_iop( fd ); + rtems_libio_check_is_open( iop ); + rtems_libio_check_permissions( iop, LIBIO_FLAGS_READ ); + + /* + * Argument validation on IO vector + */ + if ( !iov ) + rtems_set_errno_and_return_minus_one( EINVAL ); + + if ( iovcnt <= 0 ) + rtems_set_errno_and_return_minus_one( EINVAL ); + + if ( iovcnt > IOV_MAX ) + rtems_set_errno_and_return_minus_one( EINVAL ); + + if ( !iop->handlers->read_h ) + rtems_set_errno_and_return_minus_one( ENOTSUP ); + + /* + * OpenGroup says that you are supposed to return EINVAL if the + * sum of the iov_len values in the iov array would overflow a + * ssize_t. + * + * Also we would like to ensure that no IO is performed if there + * are obvious errors in the iovec. So this extra loop ensures + * that we do not do anything if there is an argument error. + */ + + all_zeros = TRUE; + for ( total=0, v=0 ; v < iovcnt ; v++ ) { + ssize_t old; + + if ( !iov[v].iov_base ) + rtems_set_errno_and_return_minus_one( EINVAL ); + + if ( iov[v].iov_len <= 0 ) + rtems_set_errno_and_return_minus_one( EINVAL ); + + /* check for wrap */ + old = total; + total += iov[v].iov_len; + if ( total < old ) + rtems_set_errno_and_return_minus_one( EINVAL ); + + if ( iov[v].iov_len ) + all_zeros = FALSE; + } + + /* + * A readv with all zeros logically has no effect. Even though + * OpenGroup didn't address this case as they did with writev(), + * we will handle it the same way for symmetry. + */ + if ( all_zeros == TRUE ) { + return 0; + } + + /* + * Now process the readv(). + */ + for ( total=0, v=0 ; v < iovcnt ; v++ ) { + bytes = (*iop->handlers->read_h)( iop, iov[v].iov_base, iov[v].iov_len ); + + if ( bytes < 0 ) + return -1; + + if ( bytes > 0 ) { + iop->offset += bytes; + total += bytes; + } + + if (bytes != iov[ v ].iov_len) + break; + } + + return total; +} diff --git a/cpukit/libcsupport/src/writev.c b/cpukit/libcsupport/src/writev.c new file mode 100644 index 0000000000..c032f9f6bc --- /dev/null +++ b/cpukit/libcsupport/src/writev.c @@ -0,0 +1,126 @@ +/* + * writev() - POSIX 1003.1 - Read a Vector + * + * OpenGroup URL: + * + * http://www.opengroup.org/onlinepubs/009695399/functions/writev.html + * + * COPYRIGHT (c) 1989-2007. + * On-Line Applications Research Corporation (OAR). + * + * The license and distribution terms for this file may be + * found in the file LICENSE in this distribution or at + * http://www.rtems.com/license/LICENSE. + * + * $Id$ + */ + +#if HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include + +#include +#include + +ssize_t writev( + int fd, + const struct iovec *iov, + int iovcnt +) +{ + ssize_t total; + int v; + int bytes; + rtems_libio_t *iop; + ssize_t old; + boolean all_zeros; + + rtems_libio_check_fd( fd ); + iop = rtems_libio_iop( fd ); + rtems_libio_check_is_open( iop ); + rtems_libio_check_permissions( iop, LIBIO_FLAGS_WRITE ); + + /* + * Argument validation on IO vector + */ + if ( !iov ) + rtems_set_errno_and_return_minus_one( EINVAL ); + + if ( iovcnt <= 0 ) + rtems_set_errno_and_return_minus_one( EINVAL ); + + if ( iovcnt > IOV_MAX ) + rtems_set_errno_and_return_minus_one( EINVAL ); + + if ( !iop->handlers->write_h ) + rtems_set_errno_and_return_minus_one( ENOTSUP ); + + /* + * OpenGroup says that you are supposed to return EINVAL if the + * sum of the iov_len values in the iov array would overflow a + * ssize_t. + * + * Also we would like to ensure that no IO is performed if there + * are obvious errors in the iovec. So this extra loop ensures + * that we do not do anything if there is an argument error. + * + * In addition,the OpenGroup specification says that if all the + * iov_len entries are zero, then the call has no effect. So + * this loop does that check as well and sets "all-zero" appropriately. + * The variable "all_zero" is used as an early exit point before + * entering the write loop. + */ + all_zeros = TRUE; + for ( old=0, total=0, v=0 ; v < iovcnt ; v++ ) { + + if ( !iov[v].iov_base ) + rtems_set_errno_and_return_minus_one( EINVAL ); + + if ( iov[v].iov_len < 0 ) + rtems_set_errno_and_return_minus_one( EINVAL ); + + if ( iov[v].iov_len ) + all_zeros = FALSE; + + /* check for wrap */ + old = total; + total += iov[v].iov_len; + if ( total < old || total > SSIZE_MAX ) + rtems_set_errno_and_return_minus_one( EINVAL ); + } + + /* + * A writev with all zeros is supposed to have no effect per OpenGroup. + */ + if ( all_zeros == TRUE ) { + return 0; + } + + /* + * Now process the writev(). + */ + for ( total=0, v=0 ; v < iovcnt ; v++ ) { + /* all zero lengths has no effect */ + if ( iov[v].iov_len == 0 ) + continue; + + bytes = (*iop->handlers->write_h)( iop, iov[v].iov_base, iov[v].iov_len ); + + if ( bytes < 0 ) + return -1; + + if ( bytes > 0 ) { + iop->offset += bytes; + total += bytes; + } + + if (bytes != iov[ v ].iov_len) + break; + } + + return total; +} + diff --git a/cpukit/libnetworking/libc/herror.c b/cpukit/libnetworking/libc/herror.c index 45ce588569..621da3ec1d 100644 --- a/cpukit/libnetworking/libc/herror.c +++ b/cpukit/libnetworking/libc/herror.c @@ -69,6 +69,8 @@ int h_nerr = { sizeof h_errlist / sizeof h_errlist[0] }; int h_errno; +#define HERROR_USE_WRITEV + /* * herror -- * print the error indicated by the h_errno value. @@ -77,7 +79,7 @@ void herror(s) const char *s; { -#if 0 +#if defined(HERROR_USE_WRITEV) struct iovec iov[4]; register struct iovec *v = iov; @@ -97,7 +99,7 @@ herror(s) writev(STDERR_FILENO, iov, (v - iov) + 1); #else /* - * RTEMS: no writev yet + * no writev implementation available */ if (s && *s) { write (2, s, strlen (s)); diff --git a/cpukit/libnetworking/libc/res_send.c b/cpukit/libnetworking/libc/res_send.c index fa04d545b3..5f675a46e4 100644 --- a/cpukit/libnetworking/libc/res_send.c +++ b/cpukit/libnetworking/libc/res_send.c @@ -97,6 +97,9 @@ #include #endif +/* RTEMS now has writev */ +#define USE_WRITEV + #include "res_config.h" #if !defined(__rtems__) @@ -378,7 +381,7 @@ res_send(buf, buflen, ans, anssiz) if (v_circuit) { int truncated; -#if !defined(__rtems__) +#if defined(USE_WRITEV) struct iovec iov[2]; #endif u_short len; @@ -416,7 +419,7 @@ res_send(buf, buflen, ans, anssiz) * Send length & message */ putshort((u_short)buflen, (u_char*)&len); -#if !defined(__rtems__) +#if defined(USE_WRITEV) iov[0].iov_base = (caddr_t)&len; iov[0].iov_len = INT16SZ; iov[1].iov_base = (caddr_t)buf; diff --git a/cpukit/libnetworking/sys/uio.h b/cpukit/libnetworking/sys/uio.h index 4a27faa8fa..5aec197848 100644 --- a/cpukit/libnetworking/sys/uio.h +++ b/cpukit/libnetworking/sys/uio.h @@ -40,12 +40,11 @@ #include /* - * XXX - * iov_base should be a void *. + * POSIX compliant iovec definition */ struct iovec { - char *iov_base; /* Base address. */ - size_t iov_len; /* Length. */ + void *iov_base; /* pointer to data to be written */ + size_t iov_len; /* length of this data block */ }; #if __BSD_VISIBLE diff --git a/cpukit/telnetd/Makefile.am b/cpukit/telnetd/Makefile.am index 4e0ab4bc2e..a2b581a700 100644 --- a/cpukit/telnetd/Makefile.am +++ b/cpukit/telnetd/Makefile.am @@ -7,10 +7,10 @@ include $(top_srcdir)/automake/compile.am if LIBNETWORKING if LIBSHELL include_rtemsdir = $(includedir)/rtems -include_rtems_HEADERS = pty.h telnetd.h +include_rtems_HEADERS = pty.h telnetd.h passwd.h project_lib_LIBRARIES = libtelnetd.a -libtelnetd_a_SOURCES = pty.c pty.h telnetd.c telnetd.h icmds.c +libtelnetd_a_SOURCES = check_passwd.c des.c icmds.c pty.c telnetd.c libtelnetd_a_CPPFLAGS = $(AM_CPPFLAGS) endif endif diff --git a/cpukit/telnetd/README b/cpukit/telnetd/README index 0c964c412d..1c84eb769f 100644 --- a/cpukit/telnetd/README +++ b/cpukit/telnetd/README @@ -19,7 +19,7 @@ NOTES: 1. OOB not yet implemented. Only a reduced negotiation is implemented. -2. If you have tcp/ip inited you can start telnetd daemon. +2. If you have tcp/ip initialied you can start telnetd daemon. You need register pseudo-terminals driver into device drivers table. 16 ptyX termios device terminales are created into /dev/. Calling rtems_initialize_telnetd() starts the daemon. diff --git a/cpukit/telnetd/preinstall.am b/cpukit/telnetd/preinstall.am index 12e0421226..68bf81f457 100644 --- a/cpukit/telnetd/preinstall.am +++ b/cpukit/telnetd/preinstall.am @@ -38,6 +38,10 @@ $(PROJECT_INCLUDE)/rtems/telnetd.h: telnetd.h $(PROJECT_INCLUDE)/rtems/$(dirstam $(INSTALL_DATA) $< $(PROJECT_INCLUDE)/rtems/telnetd.h PREINSTALL_FILES += $(PROJECT_INCLUDE)/rtems/telnetd.h +$(PROJECT_INCLUDE)/rtems/passwd.h: passwd.h $(PROJECT_INCLUDE)/rtems/$(dirstamp) + $(INSTALL_DATA) $< $(PROJECT_INCLUDE)/rtems/passwd.h +PREINSTALL_FILES += $(PROJECT_INCLUDE)/rtems/passwd.h + $(PROJECT_LIB)/libtelnetd.a: libtelnetd.a $(PROJECT_LIB)/$(dirstamp) $(INSTALL_DATA) $< $(PROJECT_LIB)/libtelnetd.a TMPINSTALL_FILES += $(PROJECT_LIB)/libtelnetd.a diff --git a/cpukit/telnetd/pty.c b/cpukit/telnetd/pty.c index a650833cde..9ee05231b7 100644 --- a/cpukit/telnetd/pty.c +++ b/cpukit/telnetd/pty.c @@ -7,20 +7,74 @@ * 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. + * + * Till Straumann + * + * - converted into a loadable module + * - NAWS support / ioctls for querying/setting the window + * size added. + * - don't delete the running task when the connection + * is closed. Rather let 'read()' return a 0 count so + * they may cleanup. Some magic hack works around termios + * limitation. * * $Id$ */ +/* + LICENSE INFORMATION + +RTEMS is free software; you can redistribute it and/or modify it under +terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. RTEMS 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 RTEMS; see +file COPYING. If not, write to the Free Software Foundation, 675 +Mass Ave, Cambridge, MA 02139, USA. + +As a special exception, including RTEMS header files in a file, +instantiating RTEMS generics or templates, or linking other files +with RTEMS objects to produce an executable application, does not +by itself cause the resulting executable application to be covered +by the GNU General Public License. This exception does not +however invalidate any other reasons why the executable file might be +covered by the GNU Public License. + +*/ + #ifdef HAVE_CONFIG_H #include "config.h" #endif +#define DEBUG_WH (1<<0) +#define DEBUG_DETAIL (1<<1) + +/* #define DEBUG DEBUG_WH */ + +#ifdef __cplusplus +extern "C" { +#endif /*-----------------------------------------*/ #include +#include +#include #include #include -#include +#include #include +#if 0 +#include +#else +#define MAX_PTYS 8 +#endif +#include +#include +#ifdef __cplusplus +}; +#endif /*-----------------------------------------*/ #include #include @@ -45,37 +99,65 @@ #define IAC_SE 240 #define IAC_EOR 239 -typedef struct { +#define SB_MAX 16 + +struct pty_tt; +typedef struct pty_tt pty_t; + +struct pty_tt { char *devname; struct rtems_termios_tty *ttyp; tcflag_t c_cflag; int opened; int socket; - int last_cr; - int iac_mode; -} pty_t; + unsigned iac_mode; + unsigned char sb_buf[SB_MAX]; + int sb_ind; + int width; + int height; +}; -static int ptys_initted=FALSE; -static pty_t *ptys; +#ifdef __cplusplus -size_t rtems_pty_maximum_ptys; +extern "C" { +int printk(char*,...); -/* This procedure returns the devname for a free pty slot. - * If no slot available (field socket>=0) - * then the socket argument is closed +#endif + +#if MAX_PTYS > 5 +#undef MAX_PTYS +#define MAX_PTYS 5 +#endif + + +static int telnet_pty_inited=FALSE; +static pty_t telnet_ptys[MAX_PTYS]; + +static rtems_device_major_number pty_major; + + +/* This procedure returns the devname for a pty slot free. + * If not slot availiable (field socket>=0) + * then the socket argument is closed */ -char * rtems_pty_get(int socket) { +char * telnet_get_pty(int socket) { int ndx; - if (!ptys_initted) return NULL; - for (ndx=0;ndxsb_buf[0]) { + case 31: /* NAWS */ + pty->width = (pty->sb_buf[1]<<8) + pty->sb_buf[2]; + pty->height = (pty->sb_buf[3]<<8) + pty->sb_buf[4]; +#if DEBUG & DEBUG_WH + fprintf(stderr, + "Setting width/height to %ix%i\n", + pty->width, + pty->height); +#endif + break; + default: + break; + } + return 0; } -int read_pty(int minor) { /* Characters writed in the client side*/ - unsigned char value; - int count; - int result; - count=read(ptys[minor].socket,&value,sizeof(value)); +static int read_pty(int minor) { /* Characters written to the client side*/ + unsigned char value; + unsigned int omod; + int count; + int result; + pty_t *pty=telnet_ptys+minor; + + count=read(pty->socket,&value,sizeof(value)); + if (count<0) + return -1; + if (count<1) { - fclose(stdin); - fclose(stdout); - fclose(stderr); - /* If you don't read from the socket the system ends the task */ - rtems_task_delete(RTEMS_SELF); + /* Unfortunately, there is no way of passing an EOF + * condition through the termios driver. Hence, we + * resort to an ugly hack. Setting cindex>ccount + * causes the termios driver to return a read count + * of '0' which is what we want here. We leave + * 'errno' untouched. + */ + pty->ttyp->cindex=pty->ttyp->ccount+1; + return pty->ttyp->termios.c_cc[VEOF]; }; - switch(ptys[minor].iac_mode) { - case IAC_ESC: - ptys[minor].iac_mode=0; - switch(value) { - case IAC_ESC : - return IAC_ESC; - case IAC_DONT: - case IAC_DO : - case IAC_WONT: - case IAC_WILL: - ptys[minor].iac_mode=value; - return -1; - case IAC_SB : - return -100; - case IAC_GA : - return -1; - case IAC_EL : - return 0x03; /* Ctrl-C*/ - case IAC_EC : - return '\b'; - case IAC_AYT : - write(ptys[minor].socket,IAC_AYT_RSP,strlen(IAC_AYT_RSP)); - return -1; - case IAC_AO : - return -1; - case IAC_IP : - write(ptys[minor].socket,IAC_IP_RSP,strlen(IAC_IP_RSP)); - return -1; - case IAC_BRK : - write(ptys[minor].socket,IAC_BRK_RSP,strlen(IAC_BRK_RSP)); - return -1; - case IAC_DMARK: - return -2; - case IAC_NOP : - return -1; - case IAC_SE : - return -101; - case IAC_EOR : - return -102; - default : - return -1; - }; - break; - case IAC_WILL: - ptys[minor].iac_mode=0; - if (value==34){send_iac(minor,IAC_DONT, 34); /*LINEMODE*/ - send_iac(minor,IAC_DO , 1);} else /*ECHO */ - {send_iac(minor,IAC_DONT,value);}; - return -1; - case IAC_DONT: - ptys[minor].iac_mode=0; - return -1; - case IAC_DO : - ptys[minor].iac_mode=0; - if (value==3) {send_iac(minor,IAC_WILL, 3);} else /* GO AHEAD*/ - if (value==1) { } else /* ECHO */ - {send_iac(minor,IAC_WONT,value);}; - return -1; - case IAC_WONT: - ptys[minor].iac_mode=0; - if (value==1) {send_iac(minor,IAC_WILL, 1);} else /* ECHO */ - {send_iac(minor,IAC_WONT,value);}; - return -1; - default: - ptys[minor].iac_mode=0; - if (value==IAC_ESC) { - ptys[minor].iac_mode=value; - return -1; - } else { - result=value; - if (ptys[minor].last_cr && ((value=='\n')||(value=='\0'))) result=-1; - ptys[minor].last_cr=(value=='\r'); - return result; - }; + + omod=pty->iac_mode; + pty->iac_mode=0; + switch(omod & 0xff) { + case IAC_ESC: + switch(value) { + case IAC_ESC : + /* in case this is an ESC ESC sequence in SB mode */ + pty->iac_mode = omod>>8; + return IAC_ESC; + case IAC_DONT: + case IAC_DO : + case IAC_WONT: + case IAC_WILL: + pty->iac_mode=value; + return -1; + case IAC_SB : +#if DEBUG & DEBUG_DETAIL + printk("SB\n"); +#endif + pty->iac_mode=value; + pty->sb_ind=0; + return -100; + case IAC_GA : + return -1; + case IAC_EL : + return 0x03; /* Ctrl-C*/ + case IAC_EC : + return '\b'; + case IAC_AYT : + write(pty->socket,IAC_AYT_RSP,strlen(IAC_AYT_RSP)); + return -1; + case IAC_AO : + return -1; + case IAC_IP : + write(pty->socket,IAC_IP_RSP,strlen(IAC_IP_RSP)); + return -1; + case IAC_BRK : + write(pty->socket,IAC_BRK_RSP,strlen(IAC_BRK_RSP)); + return -1; + case IAC_DMARK: + return -2; + case IAC_NOP : + return -1; + case IAC_SE : +#if DEBUG & DEBUG_DETAIL + { + int i; + printk("SE"); + for (i=0; isb_ind; i++) + printk(" %02x",pty->sb_buf[i]); + printk("\n"); + } +#endif + handleSB(pty); + return -101; + case IAC_EOR : + return -102; + default : + return -1; + }; + break; + + case IAC_SB: + pty->iac_mode=omod; + if (IAC_ESC==value) { + pty->iac_mode=(omod<<8)|value; + } else { + if (pty->sb_ind < SB_MAX) + pty->sb_buf[pty->sb_ind++]=value; + } + return -1; + + case IAC_WILL: + if (value==34){ + send_iac(minor,IAC_DONT, 34); /*LINEMODE*/ + send_iac(minor,IAC_DO , 1); /*ECHO */ + } else if (value==31) { + send_iac(minor,IAC_DO , 31); /*NAWS */ +#if DEBUG & DEBUG_DETAIL + printk("replied DO NAWS\n"); +#endif + } else { + send_iac(minor,IAC_DONT,value); + } + return -1; + case IAC_DONT: + return -1; + case IAC_DO : + if (value==3) { + send_iac(minor,IAC_WILL, 3); /* GO AHEAD*/ + } else if (value==1) { + /* ECHO */ + } else { + send_iac(minor,IAC_WONT,value); + }; + return -1; + case IAC_WONT: + if (value==1) {send_iac(minor,IAC_WILL, 1);} else /* ECHO */ + {send_iac(minor,IAC_WONT,value);}; + return -1; + default: + if (value==IAC_ESC) { + pty->iac_mode=value; + return -1; + } else { + result=value; + if ( 0 +#if 0 /* pass CRLF through - they should use termios to handle it */ + || ((value=='\n') && (pty->last_cr)) +#endif + /* but map telnet CRNUL to CR down here */ + || ((value==0) && pty->last_cr) + ) result=-1; + pty->last_cr=(value=='\r'); + return result; + }; }; - + /* should never get here but keep compiler happy */ + return -1; } /*-----------------------------------------------------------*/ @@ -204,8 +360,8 @@ static const rtems_termios_callbacks * pty_get_termios_handlers(int polled) ; /*-----------------------------------------------------------*/ static int ptySetAttributes(int minor,const struct termios *t) { - if (minorc_cflag; + if (minorc_cflag; } else { return -1; }; @@ -214,12 +370,16 @@ ptySetAttributes(int minor,const struct termios *t) { /*-----------------------------------------------------------*/ static int ptyPollInitialize(int major,int minor,void * arg) { - rtems_libio_open_close_args_t * args = arg; + rtems_libio_open_close_args_t * args = (rtems_libio_open_close_args_t*)arg; struct termios t; - if (minoriop->data1; + if (minoriop->data1; + telnet_ptys[minor].iac_mode=0; + telnet_ptys[minor].sb_ind=0; + telnet_ptys[minor].width=0; + telnet_ptys[minor].height=0; t.c_cflag=B9600|CS8;/* termios default */ return ptySetAttributes(minor,&t); } else { @@ -229,11 +389,11 @@ ptyPollInitialize(int major,int minor,void * arg) { /*-----------------------------------------------------------*/ static int ptyShutdown(int major,int minor,void * arg) { - if (minor=0) close(ptys[minor].socket); - ptys[minor].socket=-1; - chown(ptys[minor].devname,2,0); + if (minor=0) close(telnet_ptys[minor].socket); + telnet_ptys[minor].socket=-1; + chown(telnet_ptys[minor].devname,2,0); } else { return -1; }; @@ -245,9 +405,9 @@ ptyShutdown(int major,int minor,void * arg) { static int ptyPollWrite(int minor, const char * buf,int len) { int count; - if (minorbuffer; +pty_t *p=&telnet_ptys[minor]; + + switch (args->command) { + + case TIOCGWINSZ: + + wp->ws_row = p->height; + wp->ws_col = p->width; + args->ioctl_return=0; +#if DEBUG & DEBUG_WH + fprintf(stderr, + "ioctl(TIOCGWINSZ), returning %ix%i\n", + wp->ws_col, + wp->ws_row); +#endif + + return RTEMS_SUCCESSFUL; + + case TIOCSWINSZ: +#if DEBUG & DEBUG_WH + fprintf(stderr, + "ioctl(TIOCGWINSZ), setting %ix%i\n", + wp->ws_col, + wp->ws_row); +#endif + + p->height = wp->ws_row; + p->width = wp->ws_col; + args->ioctl_return=0; + + return RTEMS_SUCCESSFUL; + + default: + + break; + } + return rtems_termios_ioctl(arg); } + +static rtems_driver_address_table drvPty = { + my_pty_initialize, + my_pty_open, + my_pty_close, + my_pty_read, + my_pty_write, + my_pty_control +}; + +/*-----------------------------------------------------------*/ +static const rtems_termios_callbacks pty_poll_callbacks = { + ptyPollInitialize, /* FirstOpen*/ + ptyShutdown, /* LastClose*/ + ptyPollRead, /* PollRead */ + ptyPollWrite, /* Write */ + ptySetAttributes, /* setAttributes */ + NULL, /* stopRemoteTX */ + NULL, /* StartRemoteTX */ + 0 /* outputUsesInterrupts */ +}; +/*-----------------------------------------------------------*/ +static const rtems_termios_callbacks * pty_get_termios_handlers(int polled) { + return &pty_poll_callbacks; +} +/*-----------------------------------------------------------*/ + +static int pty_do_initialize() +{ + if ( !telnet_pty_inited ) { + if (RTEMS_SUCCESSFUL==rtems_io_register_driver(0, &drvPty, &pty_major)) + telnet_pty_inited=TRUE; + else + fprintf(stderr,"WARNING: registering the PTY driver FAILED\n"); + } + return telnet_pty_inited; +} + +#ifdef __cplusplus + +class TelnetPtyIni { +public: + TelnetPtyIni() { if (!nest++) { + pty_do_initialize(); + } + }; + ~TelnetPtyIni(){ if (!--nest) { + pty_do_finalize(); + } + }; +private: +static int nest; +}; + +static TelnetPtyIni onlyInst; +int TelnetPtyIni::nest=0; + +int telnet_pty_initialize() +{ + return telnet_pty_inited; +} + +int telnet_pty_finalize() +{ + return telnet_pty_inited; +} +}; +#else +int telnet_pty_initialize() +{ + return pty_do_initialize(); +} + +int telnet_pty_finalize() +{ + return pty_do_finalize(); +} +#endif diff --git a/cpukit/telnetd/telnetd.c b/cpukit/telnetd/telnetd.c index 5f856b005a..24282059aa 100644 --- a/cpukit/telnetd/telnetd.c +++ b/cpukit/telnetd/telnetd.c @@ -17,7 +17,36 @@ * this daemon interactively. (Login in /dev/console of course) * * Sorry but OOB is not still implemented. (This is the first version) + * + * Till Straumann + * - made the 'shell' interface more generic, i.e. it is now + * possible to have 'telnetd' run an arbitrary 'shell' + * program. */ + +/* + LICENSE INFORMATION + +RTEMS is free software; you can redistribute it and/or modify it under +terms of the GNU General Public License as published by the +Free Software Foundation; either version 2, or (at your option) any +later version. RTEMS 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 RTEMS; see +file COPYING. If not, write to the Free Software Foundation, 675 +Mass Ave, Cambridge, MA 02139, USA. + +As a special exception, including RTEMS header files in a file, +instantiating RTEMS generics or templates, or linking other files +with RTEMS objects to produce an executable application, does not +by itself cause the resulting executable application to be covered +by the GNU General Public License. This exception does not +however invalidate any other reasons why the executable file might be +covered by the GNU Public License. +*/ + #ifdef HAVE_CONFIG_H #include "config.h" #endif @@ -27,113 +56,422 @@ #include #include #include +#include #include #include #include #include #include -#include /* memset */ #include -#include +#include +#include +#include + +#include +#include +#include + +#define PARANOIA + +extern char *telnet_get_pty(int socket); +extern int telnet_pty_initialize(); +extern int telnet_pty_finalize(); + +struct shell_args { + char *devname; + void *arg; + char peername[16]; + char delete_myself; +}; + +typedef union uni_sa { + struct sockaddr_in sin; + struct sockaddr sa; +} uni_sa; + +static int sockpeername(int sock, char *buf, int bufsz); + +static int initialize_telnetd(); -/* FIXME: This should not be here */ -extern void _rtems_telnetd_register_icmds(void); +void * telnetd_dflt_spawn(const char *name, unsigned priority, unsigned stackSize, void (*fn)(void*), void *fnarg); /***********************************************************/ rtems_id telnetd_task_id =0; -uint32_t telnetd_stack_size =16384; -rtems_task_priority telnetd_task_priority=100; +uint32_t telnetd_stack_size =32000; +rtems_task_priority telnetd_task_priority=0; +int telnetd_dont_spawn =0; +void (*telnetd_shell)(char *, void*)=0; +void *telnetd_shell_arg =0; +void * (*telnetd_spawn_task)(const char *, unsigned, unsigned, void (*)(void*), void *) = telnetd_dflt_spawn; + +static char *grab_a_Connection(int des_socket, uni_sa *srv, char *peername, int sz) +{ +char *rval = 0; +#if 0 +socklen_t +#else +/* 4.6 doesn't have socklen_t */ +uint32_t +#endif + size_adr = sizeof(srv->sin); +int acp_sock; + + acp_sock = accept(des_socket,&srv->sa,&size_adr); + + if (acp_sock<0) { + perror("telnetd:accept"); + goto bailout; + }; + + if ( !(rval=telnet_get_pty(acp_sock)) ) { + syslog( LOG_DAEMON | LOG_ERR, "telnetd: unable to obtain PTY"); + /* NOTE: failing 'do_get_pty()' closed the socket */ + goto bailout; + } + + if (sockpeername(acp_sock, peername, sz)) + strncpy(peername, "", sz); + +#ifdef PARANOIA + syslog(LOG_DAEMON | LOG_INFO, + "telnetd: accepted connection from %s on %s", + peername, + rval); +#endif + +bailout: + + return rval; +} + + +static void release_a_Connection(char *devname, char *peername, FILE **pstd, int n) +{ + +#ifdef PARANOIA + syslog( LOG_DAEMON | LOG_INFO, + "telnetd: releasing connection from %s on %s", + peername, + devname ); +#endif + + while (--n>=0) + if (pstd[n]) fclose(pstd[n]); + +} + +static int sockpeername(int sock, char *buf, int bufsz) +{ +uni_sa peer; +#if 0 +socklen_t +#else +/* 4.6 doesn't have socklen_t */ +uint32_t +#endif + len = sizeof(peer.sin); + +int rval = sock < 0; + + if ( !rval) + rval = getpeername(sock, &peer.sa, &len); + + if ( !rval ) + rval = !inet_ntop( AF_INET, &peer.sin.sin_addr, buf, bufsz ); + + return rval; +} + +#if 1 +#define INSIDE_TELNETD +#include "check_passwd.c" +#else +#define check_passwd(arg) 0 +#endif + + +static void +spawned_shell(void *arg); + /***********************************************************/ -rtems_task rtems_task_telnetd(rtems_task_argument task_argument) { - int des_socket, - acp_socket; - struct sockaddr_in srv; - char * devname; - int i=1; - socklen_t size_adr; +static void +rtems_task_telnetd(void *task_argument) +{ +int des_socket; +uni_sa srv; +char *devname; +char peername[16]; +int i=1; +int size_adr; +struct shell_args *arg; + if ((des_socket=socket(PF_INET,SOCK_STREAM,0))<0) { perror("telnetd:socket"); + telnetd_task_id=0; rtems_task_delete(RTEMS_SELF); }; - setsockopt(des_socket,SOL_SOCKET,0,&i,sizeof(i)); + setsockopt(des_socket,SOL_SOCKET,SO_KEEPALIVE,&i,sizeof(i)); + memset(&srv,0,sizeof(srv)); - srv.sin_family=AF_INET; - srv.sin_port=htons(23); - size_adr=sizeof(srv); - if ((bind(des_socket,(struct sockaddr *)&srv,size_adr))<0) { + srv.sin.sin_family=AF_INET; + srv.sin.sin_port=htons(23); + size_adr=sizeof(srv.sin); + if ((bind(des_socket,&srv.sa,size_adr))<0) { perror("telnetd:bind"); close(des_socket); + telnetd_task_id=0; rtems_task_delete(RTEMS_SELF); }; if ((listen(des_socket,5))<0) { perror("telnetd:listen"); close(des_socket); + telnetd_task_id=0; rtems_task_delete(RTEMS_SELF); }; + + /* we don't redirect stdio as this probably + * was started from the console anyways.. + */ do { - acp_socket=accept(des_socket,(struct sockaddr*)&srv,&size_adr); - if (acp_socket<0) { - perror("telnetd:accept"); - break; - }; - if ((devname = rtems_pty_get(acp_socket)) ) { - shell_init(&devname[5], - telnetd_stack_size, - telnetd_task_priority, - devname,B9600|CS8,FALSE); + devname = grab_a_Connection(des_socket, &srv, peername, sizeof(peername)); + + if ( !devname ) { + /* if something went wrong, sleep for some time */ + sleep(10); + continue; + } + if ( telnetd_dont_spawn ) { + if ( 0 == check_passwd(peername) ) + telnetd_shell(devname, telnetd_shell_arg); } else { - close(acp_socket); - }; + arg = malloc( sizeof(*arg) ); + + arg->devname = devname; + arg->arg = telnetd_shell_arg; + strncpy(arg->peername, peername, sizeof(arg->peername)); + + if ( !telnetd_spawn_task(&devname[5], telnetd_task_priority, telnetd_stack_size, spawned_shell, arg) ) { + + FILE *dummy; + + if ( telnetd_spawn_task != telnetd_dflt_spawn ) { + fprintf(stderr,"Telnetd: Unable to spawn child task\n"); + } + + /* hmm - the pty driver slot can only be + * released by opening and subsequently + * closing the PTY - this also closes + * the underlying socket. So we mock up + * a stream... + */ + + if ( !(dummy=fopen(devname,"r+")) ) + perror("Unable to dummy open the pty, losing a slot :-("); + release_a_Connection(devname, peername, &dummy, 1); + free(arg); + sleep(2); /* don't accept connections too fast */ + } + } } while(1); + /* TODO: how to free the connection semaphore? But then - + * stopping the daemon is probably only needed during + * development/debugging. + * Finalizer code should collect all the connection semaphore + * counts and eventually clean up... + */ close(des_socket); - rtems_task_delete(RTEMS_SELF); + telnetd_task_id=0; } /***********************************************************/ -int rtems_telnetd_initialize(void) { - rtems_status_code sc; - - _rtems_telnetd_register_icmds(); /* stats for tcp/ip */ +static int initialize_telnetd(void) { if (telnetd_task_id ) return RTEMS_RESOURCE_IN_USE; - if (telnetd_stack_size<=0 ) telnetd_stack_size =16384; - if (telnetd_task_priority<=2) telnetd_task_priority=100; - sc=rtems_task_create(new_rtems_name("tlnd"), - 100,RTEMS_MINIMUM_STACK_SIZE, - RTEMS_DEFAULT_MODES, - RTEMS_DEFAULT_ATTRIBUTES, - &telnetd_task_id); - if (sc!=RTEMS_SUCCESSFUL) { - rtems_error(sc,"creating task telnetd"); - return (int)sc; - }; - sc=rtems_task_start(telnetd_task_id, - rtems_task_telnetd, - (rtems_task_argument)NULL); - if (sc!=RTEMS_SUCCESSFUL) { - rtems_error(sc,"starting task telnetd"); - }; - return (int)sc; + if (telnetd_stack_size<=0 ) telnetd_stack_size =32000; + + if ( !telnetd_spawn_task("TNTD", telnetd_task_priority, RTEMS_MINIMUM_STACK_SIZE, rtems_task_telnetd, 0) ) { + return -1; + } + return 0; } + /***********************************************************/ -int rtems_telnetd_main(int argc,char * argv[]) { - rtems_status_code sc; +int startTelnetd(void (*cmd)(char *, void *), void *arg, int dontSpawn, int stack, int priority) +{ + rtems_status_code sc; + + printf("This is rtems-telnetd (modified by Till Straumann)\n"); + printf("$Id$\n"); + printf("Release $Name$\n"); + + if ( !telnetd_shell && !cmd ) { + fprintf(stderr,"startTelnetd(): setup error - NO SHELL; bailing out\n"); + return 1; + } + if (telnetd_task_id) { - printf("ERROR:telnetd already started\n"); + fprintf(stderr,"ERROR:telnetd already started\n"); return 1; }; - if (argc>1) telnetd_stack_size =str2int(argv[1]); - if (argc>2) telnetd_task_priority=str2int(argv[2]); - sc=rtems_initialize_telnetd(); + + if ( !telnet_pty_initialize() ) { + fprintf(stderr,"PTY driver probably not properly registered\n"); + return 1; + } + + if (cmd) + telnetd_shell = cmd; + telnetd_shell_arg = arg; + telnetd_stack_size = stack; + if ( !priority ) { + priority = rtems_bsdnet_config.network_task_priority; + } + if ( priority < 2 ) + priority=100; + telnetd_task_priority = priority; + telnetd_dont_spawn = dontSpawn; + + sc=initialize_telnetd(); if (sc!=RTEMS_SUCCESSFUL) return sc; - printf( - "rtems_telnetd() started with stacksize=%" PRIu32 - ",priority=%" PRId32 "\n", - telnetd_stack_size,telnetd_task_priority); + printf("rtems_telnetd() started with stacksize=%u,priority=%d\n", + (unsigned)telnetd_stack_size,(int)telnetd_task_priority); return 0; } -/***********************************************************/ -int rtems_telnetd_register(void) { - shell_add_cmd("telnetd","telnet","telnetd [stacksize [tsk_priority]]",main_telnetd); - return 0; + +/* utility wrapper */ +static void +spawned_shell(void *targ) +{ +rtems_status_code sc; +FILE *nstd[3]={0}; +FILE *ostd[3]={ stdin, stdout, stderr }; +int i=0; +struct shell_args *arg = targ; + + sc=rtems_libio_set_private_env(); + + /* newlib hack/workaround. Before we change stdin/out/err we must make + * sure the internal data are initialized (fileno(stdout) has this sideeffect). + * This should probably be done from RTEMS' libc support layer... + * (T.S., newlibc-1.13; 2005/10) + */ + + fileno(stdout); + + if (RTEMS_SUCCESSFUL != sc) { + rtems_error(sc,"rtems_libio_set_private_env"); + goto cleanup; + } + + /* redirect stdio */ + for (i=0; i<3; i++) { + if ( !(nstd[i]=fopen(arg->devname,"r+")) ) { + perror("unable to open stdio"); + goto cleanup; + } + } + + stdin = nstd[0]; + stdout = nstd[1]; + stderr = nstd[2]; + + + +#if 0 +printk("STDOUT is now %x (%x) (FD %i/%i)\n",stdout,nstd[1],fileno(stdout),fileno(nstd[1])); +printf("hello\n"); +write(fileno(stdout),"hellofd\n",8); +#endif + + /* call their routine */ + if ( 0 == check_passwd(arg->peername) ) + telnetd_shell(arg->devname, arg->arg); + + stdin = ostd[0]; + stdout = ostd[1]; + stderr = ostd[2]; + +cleanup: + release_a_Connection(arg->devname, arg->peername, nstd, i); + free(arg); } -/***********************************************************/ + +struct wrap_delete_args { + void (*t)(void *); + void *a; +}; + +static rtems_task +wrap_delete(rtems_task_argument arg) +{ +struct wrap_delete_args *pwa = (struct wrap_delete_args *)arg; +register void (*t)(void *) = pwa->t; +register void *a = pwa->a; + + /* free argument before calling function (which may never return if + * they choose to delete themselves) + */ + free(pwa); + t(a); + rtems_task_delete(RTEMS_SELF); +} + +void * +telnetd_dflt_spawn(const char *name, unsigned int priority, unsigned int stackSize, void (*fn)(void *), void* fnarg) +{ +rtems_status_code sc; +rtems_id task_id; +char nm[4] = {'X','X','X','X' }; +struct wrap_delete_args *pwa = malloc(sizeof(*pwa)); + + strncpy(nm, name, 4); + + if ( !pwa ) { + perror("Telnetd: no memory\n"); + return 0; + } + + pwa->t = fn; + pwa->a = fnarg; + + if ((sc=rtems_task_create( + rtems_build_name(nm[0], nm[1], nm[2], nm[3]), + (rtems_task_priority)priority, + stackSize, + RTEMS_DEFAULT_MODES, + RTEMS_DEFAULT_ATTRIBUTES | RTEMS_FLOATING_POINT, + &task_id)) || + (sc=rtems_task_start( + task_id, + wrap_delete, + (rtems_task_argument)pwa))) { + free(pwa); + rtems_error(sc,"Telnetd: spawning task failed"); + return 0; + } + return (void*)task_id; +} + +/* convenience routines for CEXP (retrieve stdio descriptors + * from reent structure) + * + */ +#ifdef stdin +static __inline__ FILE * +_stdin(void) { return stdin; } +#undef stdin +FILE *stdin(void) { return _stdin(); } +#endif +#ifdef stdout +static __inline__ FILE * +_stdout(void) { return stdout; } +#undef stdout +FILE *stdout(void) { return _stdout(); } +#endif +#ifdef stderr +static __inline__ FILE * +_stderr(void) { return stderr; } +#undef stderr +FILE *stderr(void) { return _stderr(); } +#endif + +/* MUST NOT USE stdin & friends below here !!!!!!!!!!!!! */ -- cgit v1.2.3