Skip to content

Instantly share code, notes, and snippets.

@k0u5uk3
Created June 18, 2015 03:56
Show Gist options
  • Save k0u5uk3/c2ba825ace6783cc2647 to your computer and use it in GitHub Desktop.
Save k0u5uk3/c2ba825ace6783cc2647 to your computer and use it in GitHub Desktop.
telnetd.c
//
// telnetd.c
//
// Telnet daemon
//
// Copyright (C) 2002 Michael Ringgaard. 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 project 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 COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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.
//
#include <os.h>
#include <inifile.h>
#include <stdio.h>
#include <string.h>
//
// Events
//
#define APPDON 0 // Application terminated
#define USRATT 10 // User needs attention for reading input
#define USRRDY 11 // User is ready to receive output
#define APPATT 12 // Application needs attention for writing output
#define APPRDY 13 // Application is ready to receive input
//
// States
//
#define STATE_NORMAL 0
#define STATE_IAC 1
#define STATE_OPT 2
#define STATE_SB 3
#define STATE_OPTDAT 4
#define STATE_SE 5
//
// Special telnet characters
//
#define TELNET_SE 240 // End of subnegotiation parameters
#define TELNET_NOP 241 // No operation
#define TELNET_MARK 242 // Data mark
#define TELNET_BRK 243 // Break
#define TELNET_IP 244 // Interrupt process
#define TELNET_AO 245 // Abort output
#define TELNET_AYT 246 // Are you there
#define TELNET_EC 247 // Erase character
#define TELNET_EL 248 // Erase line
#define TELNET_GA 249 // Go ahead
#define TELNET_SB 250 // Start of subnegotiation parameters
#define TELNET_WILL 251 // Will option code
#define TELNET_WONT 252 // Won't option code
#define TELNET_DO 253 // Do option code
#define TELNET_DONT 254 // Don't option code
#define TELNET_IAC 255 // Interpret as command
//
// Telnet options
//
#define TELOPT_TRANSMIT_BINARY 0 // Binary Transmission (RFC856)
#define TELOPT_ECHO 1 // Echo (RFC857)
#define TELOPT_SUPPRESS_GO_AHEAD 3 // Suppress Go Ahead (RFC858)
#define TELOPT_STATUS 5 // Status (RFC859)
#define TELOPT_TIMING_MARK 6 // Timing Mark (RFC860)
#define TELOPT_NAOCRD 10 // Output Carriage-Return Disposition (RFC652)
#define TELOPT_NAOHTS 11 // Output Horizontal Tab Stops (RFC653)
#define TELOPT_NAOHTD 12 // Output Horizontal Tab Stop Disposition (RFC654)
#define TELOPT_NAOFFD 13 // Output Formfeed Disposition (RFC655)
#define TELOPT_NAOVTS 14 // Output Vertical Tabstops (RFC656)
#define TELOPT_NAOVTD 15 // Output Vertical Tab Disposition (RFC657)
#define TELOPT_NAOLFD 16 // Output Linefeed Disposition (RFC658)
#define TELOPT_EXTEND_ASCII 17 // Extended ASCII (RFC698)
#define TELOPT_TERMINAL_TYPE 24 // Terminal Type (RFC1091)
#define TELOPT_NAWS 31 // Negotiate About Window Size (RFC1073)
#define TELOPT_TERMINAL_SPEED 32 // Terminal Speed (RFC1079)
#define TELOPT_TOGGLE_FLOW_CONTROL 33 // Remote Flow Control (RFC1372)
#define TELOPT_LINEMODE 34 // Linemode (RFC1184)
#define TELOPT_AUTHENTICATION 37 // Authentication (RFC1416)
//
// Globals
//
#define STOPPED 0
#define RUNNING 1
int port;
char *pgm;
int sock = NOHANDLE;
int state = STOPPED;
int off = 0;
int on = 1;
struct buffer {
unsigned char data[4096];
unsigned char *start;
unsigned char *end;
};
struct termstate {
int sock;
int state;
int code;
unsigned char optdata[256];
int optlen;
struct term term;
struct buffer bi;
struct buffer bo;
};
void sendopt(struct termstate *ts, int code, int option) {
unsigned char buf[3];
buf[0] = TELNET_IAC;
buf[1] = (unsigned char) code;
buf[2] = (unsigned char) option;
write(ts->sock, buf, 3);
}
void parseopt(struct termstate *ts, int code, int option)
{
//static char *codename[4] = {"WILL", "WONT", "DO", "DONT"};
//syslog(LOG_DEBUG, "%s %d", codename[code - 251], option);
switch (option) {
case TELOPT_ECHO:
case TELOPT_SUPPRESS_GO_AHEAD:
case TELOPT_NAWS:
break;
case TELOPT_TERMINAL_TYPE:
case TELOPT_TERMINAL_SPEED:
sendopt(ts, TELNET_DO, option);
break;
default:
if (code == TELNET_WILL || code == TELNET_WONT) {
sendopt(ts, TELNET_DONT, option);
} else {
sendopt(ts, TELNET_WONT, option);
}
}
}
void parseoptdat(struct termstate *ts, int option, unsigned char *data, int len) {
//syslog(LOG_DEBUG, "OPTION %d data (%d bytes)", option, len);
switch (option) {
case TELOPT_NAWS:
if (len == 4) {
int cols = ntohs(*(unsigned short *) data);
int lines = ntohs(*(unsigned short *) (data + 2));
//syslog(LOG_DEBUG, "WINDOW %d cols %d lines", cols, lines);
if (cols != 0) ts->term.cols = cols;
if (lines != 0) ts->term.lines = lines;
}
break;
case TELOPT_TERMINAL_SPEED:
//syslog(LOG_DEBUG, "TERMINAL SPEED %*s", len, data);
break;
case TELOPT_TERMINAL_TYPE:
//syslog(LOG_DEBUG, "TERMINAL TYPE %*s", len, data);
break;
}
}
void parse(struct termstate *ts) {
unsigned char *p = ts->bi.start;
unsigned char *q = p;
while (p < ts->bi.end) {
int c = *p++;
switch (ts->state) {
case STATE_NORMAL:
if (c == TELNET_IAC) {
ts->state = STATE_IAC;
} else {
*q++ = c;
}
break;
case STATE_IAC:
switch (c) {
case TELNET_IAC:
*q++ = c;
ts->state = STATE_NORMAL;
break;
case TELNET_WILL:
case TELNET_WONT:
case TELNET_DO:
case TELNET_DONT:
ts->code = c;
ts->state = STATE_OPT;
break;
case TELNET_SB:
ts->state = STATE_SB;
break;
default:
ts->state = STATE_NORMAL;
}
break;
case STATE_OPT:
parseopt(ts, ts->code, c);
ts->state = STATE_NORMAL;
break;
case STATE_SB:
ts->code = c;
ts->optlen = 0;
ts->state = STATE_OPTDAT;
break;
case STATE_OPTDAT:
if (c == TELNET_IAC) {
ts->state = STATE_SE;
} else if (ts->optlen < sizeof(ts->optdata)) {
ts->optdata[ts->optlen++] = c;
}
break;
case STATE_SE:
if (c == TELNET_SE) parseoptdat(ts, ts->code, ts->optdata, ts->optlen);
ts->state = STATE_NORMAL;
break;
}
}
ts->bi.end = q;
}
//
// +-----------+ +-----------+ pin[1] pin[0]=in +-----------+
// | | | |--------------------->| |
// | user |<---------->| telnetd | pipes | app |
// | | s | |<---------------------| |
// +-----------+ +-----------+ pout[0] pout[1]=out +-----------+
//
void __stdcall telnet_task(void *arg) {
struct process *proc = gettib()->proc;
struct tib *apptib;
int s = (int) arg;
int pin[2];
int pout[2];
int events[2];
int app;
int mux;
int n;
int last_was_cr;
struct termstate ts;
// Set process identifer
if (!proc->ident && !proc->cmdline) {
struct sockaddr_in sin;
int sinlen = sizeof sin;
getpeername(s, (struct sockaddr *) &sin, &sinlen);
proc->ident = strdup("user");
proc->cmdline = strdup(inet_ntoa(sin.sin_addr));
}
// Initialize terminal state
memset(&ts, 0, sizeof(struct termstate));
ts.sock = s;
ts.state = STATE_NORMAL;
ts.term.type = TERM_VT100;
ts.term.cols = 80;
ts.term.lines = 25;
// Allocate pipes
if (pipe(pin) < 0) return;
if (pipe(pout) < 0) return;
// Set non-blocking writes
ioctl(s, FIONBIO, &on, sizeof(on));
ioctl(pin[1], FIONBIO, &on, sizeof(on));
// Initialize multiplexer
mux = mkiomux(0);
if (mux < 0) return;
dispatch(mux, s, IOEVT_READ | IOEVT_ERROR | IOEVT_CLOSE, USRATT);
dispatch(mux, pout[0], IOEVT_READ | IOEVT_ERROR | IOEVT_CLOSE, APPATT);
// Spawn initial telnet application
app = spawn(P_NOWAIT | P_SUSPEND | P_DETACH, NULL, pgm, NULL, &apptib);
if (app < 0) return;
// Setup standard input, output, and error
apptib->proc->iob[0] = pin[0];
apptib->proc->iob[1] = pout[1];
apptib->proc->iob[2] = dup(pout[1]);
apptib->proc->term = &ts.term;
ts.term.ttyin = pin[0];
ts.term.ttyout = pout[1];
ioctl(pin[0], IOCTL_SET_TTY, &on, sizeof(on));
ioctl(pout[1], IOCTL_SET_TTY, &on, sizeof(on));
resume(app);
// Initialize event handle array
events[0] = app;
events[1] = mux;
// Send initial options
sendopt(&ts, TELNET_WILL, TELOPT_ECHO);
sendopt(&ts, TELNET_WILL, TELOPT_SUPPRESS_GO_AHEAD);
sendopt(&ts, TELNET_WONT, TELOPT_LINEMODE);
sendopt(&ts, TELNET_DO, TELOPT_NAWS);
last_was_cr = 0;
for (;;) {
// Wait for application termination or multiplexer event
int rc = waitany(events, 2, INFINITE);
switch (rc) {
case APPDON:
// Application terminated
goto done;
case USRATT:
// Data arrived from user
if (ts.bi.start == ts.bi.end) {
int i;
// Read data from user
n = read(s, ts.bi.data, sizeof ts.bi.data);
if (n < 0) goto done;
// Convert cr nul to cr lf.
for (i = 0; i < n; ++i) {
unsigned char ch = ts.bi.data[i];
if (ch == 0 && last_was_cr) ts.bi.data[i] = '\n';
last_was_cr = (ch == '\r');
}
ts.bi.start = ts.bi.data;
ts.bi.end = ts.bi.data + n;
}
// Parse user input for telnet options
parse(&ts);
// Fall through
case APPRDY:
// Application ready to receive
if (ts.bi.start != ts.bi.end) {
// Write data to application
n = write(pin[1], ts.bi.start, ts.bi.end - ts.bi.start);
if (n < 0) goto done;
ts.bi.start += n;
}
if (ts.bi.start == ts.bi.end) {
dispatch(mux, s, IOEVT_READ | IOEVT_ERROR | IOEVT_CLOSE, USRATT);
} else {
dispatch(mux, pin[1], IOEVT_WRITE | IOEVT_ERROR | IOEVT_CLOSE, APPRDY);
}
break;
case APPATT:
// Data arrived from application
if (ts.bo.start == ts.bo.end) {
// Read data from application
n = read(pout[0], ts.bo.data, sizeof ts.bo.data);
if (n < 0) goto done;
ts.bo.start = ts.bo.data;
ts.bo.end = ts.bo.data + n;
}
// Fall through
case USRRDY:
// User ready to receive
if (ts.bo.start != ts.bo.end) {
// Write data to user
n = write(s, ts.bo.start, ts.bo.end - ts.bo.start);
if (n < 0) goto done;
ts.bo.start += n;
}
if (ts.bo.start == ts.bo.end) {
dispatch(mux, pout[0], IOEVT_READ | IOEVT_ERROR | IOEVT_CLOSE, APPATT);
} else {
dispatch(mux, s, IOEVT_WRITE | IOEVT_ERROR | IOEVT_CLOSE, USRRDY);
}
break;
default:
// Error occured
goto done;
}
}
done:
syslog(LOG_DEBUG, "telnet client disconnected");
close(pin[1]);
close(pout[0]);
close(s);
close(app);
close(mux);
}
int main(int argc, char *argv[]) {
int s;
int rc;
struct sockaddr_in sin;
int hthread;
if (argc == 2) {
if (strcmp(argv[1], "start") == 0) {
char path[MAXPATH];
getmodpath(NULL, path, MAXPATH);
hthread = spawn(P_NOWAIT | P_DETACH, path, "", NULL, NULL);
if (hthread < 0) syslog(LOG_ERR, "error %d (%s) in spawn", errno, strerror(errno));
close(hthread);
return 0;
} else if (strcmp(argv[1], "stop") == 0) {
state = STOPPED;
close(sock);
return 0;
}
}
port = get_numeric_property(osconfig(), "telnetd", "port", 23);
pgm = get_property(osconfig(), "telnetd", "pgm", "login");
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
syslog(LOG_ERR, "error %d (%s) in socket", errno, strerror(errno));
return 1;
}
sin.sin_family = AF_INET;
sin.sin_addr.s_addr = INADDR_ANY;
sin.sin_port = htons(port);
rc = bind(sock, (struct sockaddr *) &sin, sizeof sin);
if (rc < 0) {
syslog(LOG_ERR, "error %d (%s) in bind", errno, strerror(errno));
return 1;
}
rc = listen(sock, 5);
if (rc < 0) {
syslog(LOG_ERR, "error %d (%s) in listen", errno, strerror(errno));
return 1;
}
syslog(LOG_INFO, "telnetd started on port %d", port);
state = RUNNING;
while (1) {
struct sockaddr_in sin;
s = accept(sock, (struct sockaddr *) &sin, NULL);
if (state == STOPPED) break;
if (s < 0) {
syslog(LOG_ERR, "error %d (%s) in accept", errno, strerror(errno));
return 1;
}
syslog(LOG_INFO, "client connected from %a", &sin.sin_addr);
setsockopt(s, IPPROTO_TCP, TCP_NODELAY, &off, sizeof(off));
hthread = beginthread(telnet_task, 0, (void *) s, CREATE_NEW_PROCESS | CREATE_DETACHED, "telnet", NULL);
close(hthread);
}
syslog(LOG_INFO, "telnetd stopped");
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment