initial
This commit is contained in:
@@ -0,0 +1,20 @@
|
||||
CC=gcc
|
||||
CFLAGS=-Wall -Wextra -Werror -Wno-unused-parameter -O3
|
||||
OBJS = ttynvt.o telnet_basic.o telnet_rfc2217.o
|
||||
LIBS = -lpthread -lfuse
|
||||
|
||||
.PHONY: clean
|
||||
|
||||
all: ttynvt
|
||||
|
||||
-include $(OBJS:%.o=%.d)
|
||||
|
||||
%.o: %.c ./Makefile
|
||||
$(CC) -MMD -c -o $@ $< $(CFLAGS)
|
||||
|
||||
ttynvt: $(OBJS)
|
||||
$(CC) -o $@ $^ $(CFLAGS) $(LIBS)
|
||||
|
||||
clean:
|
||||
rm -f *.o *.d ttynvt
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
1 Build environment
|
||||
The gcc compiler is used by default (see Makefile).
|
||||
libfuse-dev - Filesystem in Userspace (development) must be installed
|
||||
at the build system.
|
||||
glibc must support pthreads.
|
||||
|
||||
2 Target
|
||||
The linux kernel at the target system must support FUSE (FUSE_FS=y) and
|
||||
libpthread and libfuse must be installed in the root filesystem.
|
||||
|
||||
3 Build
|
||||
The default target in the Makefile builds the Network Virtual Terminal.
|
||||
Output name is: ttynvt.
|
||||
|
||||
4 Run
|
||||
The application (ttynvt) installs a virtual serial device in the /dev
|
||||
directory (by default: /dev/ttyNVT0).
|
||||
The major/minor number of the device, the address (name/IP) and the port
|
||||
number of the serial server must be specified when the application
|
||||
is started (command-line options).
|
||||
|
||||
E.g.
|
||||
./ttynvt -M 199 -m 6 -n ttyNVT0 -p 5020 -s 169.254.1.1
|
||||
|
||||
To get a list of all command-line options:
|
||||
./ttynvt --help
|
||||
|
||||
5. Test
|
||||
The serial server: ser2net (http://ser2net.sourceforge.net/) might be used
|
||||
to test the virtual serial server on the target.
|
||||
Install the server (ser2net) and add folling line to the configuration
|
||||
file (/etc/ser2net.conf):
|
||||
5020:telnet:0:/dev/ttyS0:115200 remctl NONE 1STOPBIT 8DATABITS RTSCTS HANGUP_WHEN_DONE
|
||||
Plug-in a loopback connector in the serial port (ttyS0)
|
||||
Open the serial port, with a termial program, e.g minicom:
|
||||
minicom -D /dev/ttyNVT0
|
||||
|
||||
6 Diagnostic
|
||||
Add the command-line option '-l' to enable logging of diagnostic information.
|
||||
Wireshark is also very useful when debugging the interface between the client
|
||||
and the server.
|
||||
|
||||
Ref: RFC-854 - Telnet Protocol Specification
|
||||
RFC-2217 - Telnet Com Port Control Option
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Lars Thrane A/S
|
||||
* SPDX-License-Identifier: GPL-2.0
|
||||
*
|
||||
* Telnet interface
|
||||
*/
|
||||
#ifndef TELNET_H
|
||||
#define TELNET_H
|
||||
|
||||
typedef int (srv_tx_f) (void *cctx, const void *buf, int len);
|
||||
typedef int (srv_rx_f) (void *cctx, int timeout);
|
||||
typedef void (srv_ms_f) (void *cctx, int status);
|
||||
|
||||
/* Telnet context */
|
||||
typedef struct {
|
||||
void *cctx; /* Client context */
|
||||
|
||||
srv_tx_f *srv_tx; /* Write to server */
|
||||
srv_rx_f *srv_rx; /* Read from server */
|
||||
srv_ms_f *srv_ms; /* Modem status from server */
|
||||
|
||||
unsigned int bytes_rx; /* Rx byte count */
|
||||
unsigned char mode_telnet; /* Telnet mode */
|
||||
unsigned char mode_rfc2217; /* RFC-2217 mode */
|
||||
unsigned char telnet_ack[256]; /* telnet option ack counter */
|
||||
} tn_ctx_t;
|
||||
|
||||
extern tn_ctx_t *telnet_ctx_init(void *cctx, srv_tx_f * ftx, srv_rx_f * frx,
|
||||
srv_ms_f * fss);
|
||||
|
||||
extern int telnet_open(tn_ctx_t * tcc);
|
||||
|
||||
extern int telnet_rx(tn_ctx_t * tcc, char *buf, size_t *plen);
|
||||
extern int telnet_tx(tn_ctx_t * tcc, const char *buf, int len);
|
||||
|
||||
extern void telnet_set_opt(tn_ctx_t * tcc, int opt, int prm,
|
||||
const void *val, int len);
|
||||
|
||||
extern void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt,
|
||||
unsigned char *pu, int nd);
|
||||
|
||||
extern void telnet_rfc2217_cfg(tn_ctx_t * tcc, int cmd,
|
||||
const void *val, int nd);
|
||||
extern void telnet_rfc2217_ctl(tn_ctx_t * tcc, int cmd);
|
||||
|
||||
/*RFC2217 modem state*/
|
||||
#define TNS_STATE_CD 0x80
|
||||
#define TNS_STATE_RI 0x40
|
||||
#define TNS_STATE_DSR 0x20
|
||||
#define TNS_STATE_CTS 0x10
|
||||
|
||||
/*RFC2217 configuration commands*/
|
||||
#define TNS_SET_BAUDRATE 1
|
||||
#define TNS_SET_DATASIZE 2
|
||||
#define TNS_SET_PARITY 3
|
||||
#define TNS_SET_STOPSIZE 4
|
||||
#define TNS_SET_CONTROL 5
|
||||
#define TNS_SET_PURGE 12
|
||||
|
||||
/*RFC2217 control commands*/
|
||||
#define TNS_CTL_NOFLOW 1
|
||||
#define TNS_CTL_XONOFF 2
|
||||
#define TNS_CTL_CTSRTS 3
|
||||
#define TNS_CTL_DTR_ON 8
|
||||
#define TNS_CTL_DTR_OFF 9
|
||||
#define TNS_CTL_RTS_ON 11
|
||||
#define TNS_CTL_RTS_OFF 12
|
||||
|
||||
#endif /*TELNET_H */
|
||||
+290
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Lars Thrane A/S
|
||||
* SPDX-License-Identifier: GPL-2.0
|
||||
*
|
||||
* Telnet server - Telnet protocol handling
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "telnet.h"
|
||||
#include "telnet_param.h"
|
||||
|
||||
|
||||
/* Telnet commands */
|
||||
#if 0
|
||||
static const char *const tn_cmd[] = {
|
||||
"SE", "NOP", "MARK", "BRK", "IP", "AO", "AYT", "EC",
|
||||
"EL", "GA", "SB", "WILL", "WONT", "DO", "DONT", "IAC"
|
||||
};
|
||||
#endif
|
||||
|
||||
#define OPT_NACK 0 /* Reject (DONT/WONT) */
|
||||
#define OPT_IGNORE 1 /* Ignore the command (?) */
|
||||
#define OPT_ACK 2 /* Acknowledge (DO /WILL) */
|
||||
|
||||
/* Telnet option support */
|
||||
static const unsigned char tn_opt[256] = {
|
||||
/* *INDENT-OFF* */
|
||||
[TNO_BINARY] = OPT_ACK,
|
||||
[TNO_SUPP_GA] = OPT_ACK,
|
||||
// [TNO_TTYP] = OPT_NACK,
|
||||
// [TNO_NAWS] = OPT_NACK,
|
||||
// [TNO_NEO] = OPT_NACK,
|
||||
[TNO_CPCO] = OPT_ACK,
|
||||
// [TNO_KERMIT] = OPT_NACK,
|
||||
/* *INDENT-ON* */
|
||||
};
|
||||
|
||||
|
||||
static void _telnet_handle_req(tn_ctx_t * tcc, int cmd, int opt)
|
||||
{
|
||||
char buf[3];
|
||||
|
||||
if (tcc->telnet_ack[opt] > 0)
|
||||
return;
|
||||
|
||||
switch (tn_opt[opt])
|
||||
{
|
||||
default:
|
||||
case OPT_IGNORE:
|
||||
return;
|
||||
case OPT_ACK:
|
||||
cmd = TNC_FD_DO;
|
||||
break;
|
||||
case OPT_NACK:
|
||||
cmd = TNC_FE_DONT;
|
||||
break;
|
||||
}
|
||||
|
||||
buf[0] = TNC_FF_IAC;
|
||||
buf[1] = cmd;
|
||||
buf[2] = opt;
|
||||
|
||||
tcc->telnet_ack[opt]++;
|
||||
|
||||
tcc->srv_tx(tcc->cctx, buf, 3);
|
||||
}
|
||||
|
||||
static void _telnet_handle_resp(tn_ctx_t * tcc, int cmd, int opt)
|
||||
{
|
||||
if (opt == TNO_CPCO)
|
||||
{
|
||||
if (cmd == TNC_FD_DO)
|
||||
tcc->mode_rfc2217 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
void telnet_set_opt(tn_ctx_t * tcc, int opt, int prm, const void *val, int len)
|
||||
{
|
||||
unsigned char buf[128], *pu = buf;
|
||||
const unsigned char *pv;
|
||||
int i;
|
||||
|
||||
*pu++ = TNC_FF_IAC;
|
||||
*pu++ = TNC_FA_SB;
|
||||
|
||||
*pu++ = opt;
|
||||
*pu++ = prm;
|
||||
if (len > 0 && len <= 32)
|
||||
{
|
||||
pv = val;
|
||||
for (i = 0; i < len; i++)
|
||||
{
|
||||
*pu++ = *pv;
|
||||
if (*pv++ == TNC_FF_IAC)
|
||||
*pu++ = TNC_FF_IAC;
|
||||
}
|
||||
}
|
||||
|
||||
*pu++ = TNC_FF_IAC;
|
||||
*pu++ = TNC_F0_SE;
|
||||
|
||||
len = pu - buf;
|
||||
|
||||
tcc->srv_tx(tcc->cctx, buf, len);
|
||||
}
|
||||
|
||||
static void _telnet_handle_opt(tn_ctx_t * tcc, int opt,
|
||||
unsigned char *pu, int nd)
|
||||
{
|
||||
switch (opt)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
case TNO_CPCO: /* RFC 2217 */
|
||||
tcc->mode_rfc2217 = 1;
|
||||
telnet_rfc2217_handle_opt(tcc, opt, pu, nd);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int telnet_rx(tn_ctx_t * tcc, char *buf, size_t * plen)
|
||||
{
|
||||
char *s, *p;
|
||||
unsigned char *pu;
|
||||
size_t len, rem;
|
||||
int np, nd;
|
||||
int b1, b2;
|
||||
|
||||
len = *plen;
|
||||
tcc->bytes_rx += len;
|
||||
|
||||
for (s = buf, rem = len; rem > 0; s = p)
|
||||
{
|
||||
p = memchr(s, TNC_FF_IAC, rem);
|
||||
if (!p)
|
||||
break;
|
||||
|
||||
/* We have IAC (= 0xff) */
|
||||
np = rem - (p - s); /* N. chars available incl. IAC */
|
||||
if (np < 2)
|
||||
return 1;
|
||||
|
||||
pu = (unsigned char *)p;
|
||||
b1 = pu[1];
|
||||
switch (b1)
|
||||
{
|
||||
default:
|
||||
case TNC_F0_SE:
|
||||
case TNC_F1_NOP:
|
||||
case TNC_F2_MARK:
|
||||
case TNC_F3_BRK:
|
||||
case TNC_F4_IP:
|
||||
case TNC_F5_AO:
|
||||
case TNC_F6_AYT:
|
||||
case TNC_F7_EC:
|
||||
case TNC_F8_EL:
|
||||
case TNC_F9_GA:
|
||||
nd = 2; /* Drop 2 bytes */
|
||||
goto tn_drop;
|
||||
|
||||
case TNC_FB_WILL:
|
||||
case TNC_FC_WONT:
|
||||
case TNC_FD_DO:
|
||||
case TNC_FE_DONT:
|
||||
if (np < 3)
|
||||
return 1; /* Need more */
|
||||
tcc->mode_telnet = 1; /* Any such sequence activates TN */
|
||||
b2 = pu[2];
|
||||
if (b1 == TNC_FB_WILL)
|
||||
_telnet_handle_req(tcc, b1, b2);
|
||||
else if (b1 == TNC_FD_DO)
|
||||
_telnet_handle_resp(tcc, b1, b2);
|
||||
nd = 3; /* Drop 3 bytes */
|
||||
goto tn_drop;
|
||||
|
||||
case TNC_FA_SB:
|
||||
for (nd = 2; nd < np - 1; nd++)
|
||||
{
|
||||
if (pu[nd] == TNC_FF_IAC && pu[nd + 1] == TNC_F0_SE)
|
||||
{
|
||||
nd -= 2;
|
||||
if (nd <= 0)
|
||||
break;
|
||||
b2 = pu[2];
|
||||
tcc->mode_telnet = 1; /* Any such sequence activates TN */
|
||||
_telnet_handle_opt(tcc, b2, pu + 3, nd - 1);
|
||||
nd += 4;
|
||||
goto tn_drop;
|
||||
}
|
||||
}
|
||||
if (np < 16)
|
||||
return 1; /* Not done */
|
||||
/* This does not look right */
|
||||
rem = 0; /* Quit loop */
|
||||
break;
|
||||
|
||||
case TNC_FF_IAC:
|
||||
p++;
|
||||
nd = 1; /* Drop 1 bytes */
|
||||
goto tn_drop;
|
||||
|
||||
tn_drop:
|
||||
if (!tcc->mode_telnet)
|
||||
goto tn_break;
|
||||
rem -= nd + (p - s);
|
||||
len -= nd;
|
||||
memmove(p, p + nd, rem);
|
||||
break;
|
||||
|
||||
tn_break:
|
||||
rem = 0; /* Quit loop */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
*plen = len;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int telnet_tx(tn_ctx_t * tcc, const char *buf, int len)
|
||||
{
|
||||
const char *s, *p;
|
||||
int rem, nd;
|
||||
|
||||
for (s = buf, rem = len; rem > 0; s = p)
|
||||
{
|
||||
if (tcc->mode_telnet)
|
||||
p = memchr(s, TNC_FF_IAC, rem);
|
||||
else
|
||||
p = NULL;
|
||||
if (!p)
|
||||
{
|
||||
/* Send the rest */
|
||||
nd = rem;
|
||||
tcc->srv_tx(tcc->cctx, s, nd);
|
||||
}
|
||||
else
|
||||
{
|
||||
nd = p - s + 1;
|
||||
tcc->srv_tx(tcc->cctx, s, nd);
|
||||
tcc->srv_tx(tcc->cctx, p, 1); /* One TNC_FF_IAC */
|
||||
p++;
|
||||
}
|
||||
rem -= nd;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int telnet_open(tn_ctx_t * tcc)
|
||||
{
|
||||
time_t t = time(NULL);;
|
||||
char buf[3];
|
||||
int res;
|
||||
|
||||
buf[0] = TNC_FF_IAC;
|
||||
buf[1] = TNC_FB_WILL;
|
||||
buf[2] = TNO_CPCO;
|
||||
|
||||
tcc->srv_tx(tcc->cctx, buf, 3);
|
||||
|
||||
while (1)
|
||||
{
|
||||
res = tcc->srv_rx(tcc->cctx, 1000);
|
||||
if (res <= 0)
|
||||
return -1;
|
||||
else if (tcc->mode_rfc2217)
|
||||
return 0;
|
||||
else if (t - time(NULL) > 2)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
tn_ctx_t *telnet_ctx_init(void *cctx, srv_tx_f * ftx,
|
||||
srv_rx_f * frx, srv_ms_f * fms)
|
||||
{
|
||||
tn_ctx_t *tcc;
|
||||
|
||||
tcc = calloc(1, sizeof(tn_ctx_t));
|
||||
tcc->cctx = cctx;
|
||||
tcc->srv_tx = ftx; /* Write to server */
|
||||
tcc->srv_rx = frx; /* Read from server */
|
||||
tcc->srv_ms = fms; /* Set modem state */
|
||||
|
||||
return tcc;
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Telnet parameters
|
||||
*/
|
||||
#ifndef TELNET_PARAM_H
|
||||
#define TELNET_PARAM_H
|
||||
|
||||
|
||||
/*
|
||||
* Telnet commands
|
||||
*/
|
||||
|
||||
#define TNC_F0_SE (0xf0) /* End of subnegotiation parameters */
|
||||
#define TNC_F1_NOP (0xf1) /* No operation */
|
||||
#define TNC_F2_MARK (0xf2) /* The data stream portion of a Synch.
|
||||
This should always be accompanied
|
||||
by a TCP Urgent notification. */
|
||||
#define TNC_F3_BRK (0xf3) /* NVT character BRK. */
|
||||
#define TNC_F4_IP (0xf4) /* The function IP (interrupt process) */
|
||||
#define TNC_F5_AO (0xf5) /* The function AO (abort output) */
|
||||
#define TNC_F6_AYT (0xf6) /* The function AYT (are you there) */
|
||||
#define TNC_F7_EC (0xf7) /* The function EC (erase charceter) */
|
||||
#define TNC_F8_EL (0xf8) /* The function EL (erase line) */
|
||||
#define TNC_F9_GA (0xf9) /* The GA signal (go ahead) */
|
||||
#define TNC_FA_SB (0xfa) /* Subnegotiation begin */
|
||||
#define TNC_FB_WILL (0xfb) /* Indicates the desire to begin
|
||||
performing, or confirmation that
|
||||
you are now performing, the
|
||||
indicated option. */
|
||||
#define TNC_FC_WONT (0xfc) /* Indicates the refusal to perform,
|
||||
or continue performing, the
|
||||
indicated option. */
|
||||
#define TNC_FD_DO (0xfd) /* Indicates the request that the
|
||||
other party perform, or
|
||||
confirmation that you are expecting
|
||||
the other party to perform, the
|
||||
indicated option. */
|
||||
#define TNC_FE_DONT (0xfe) /* Indicates the demand that the
|
||||
other party stop performing,
|
||||
or confirmation that you are no
|
||||
longer expecting the other party
|
||||
to perform, the indicated option. */
|
||||
#define TNC_FF_IAC (0xff) /* Interpret as Command / data byte */
|
||||
|
||||
|
||||
/*
|
||||
* Telnet options
|
||||
*/
|
||||
|
||||
/*
|
||||
* https://www.iana.org/assignments/telnet-options/telnet-options.xhtml
|
||||
*
|
||||
* 0 Binary Transmission [RFC856]
|
||||
* 1 Echo [RFC857]
|
||||
* 2 Reconnection [DDN Protocol Handbook, ... ]
|
||||
* 3 Suppress Go Ahead [RFC858]
|
||||
* 4 Approx Message Size Negotiation ["The Ethernet, ... ]
|
||||
* 5 Status [RFC859]
|
||||
* 6 Timing Mark [RFC860]
|
||||
* 7 Remote Controlled Trans and Echo [RFC726]
|
||||
* 8 Output Line Width [DDN Protocol Handbook, ... ]
|
||||
* 9 Output Page Size [DDN Protocol Handbook, ... ]
|
||||
* 10 Output Carriage-Return Disposition [RFC652]
|
||||
* 11 Output Horizontal Tab Stops [RFC653]
|
||||
* 12 Output Horizontal Tab Disposition [RFC654]
|
||||
* 13 Output Formfeed Disposition [RFC655]
|
||||
* 14 Output Vertical Tabstops [RFC656]
|
||||
* 15 Output Vertical Tab Disposition [RFC657]
|
||||
* 16 Output Linefeed Disposition [RFC658]
|
||||
* 17 Extended ASCII [RFC698]
|
||||
* 18 Logout [RFC727]
|
||||
* 19 Byte Macro [RFC735]
|
||||
* 20 Data Entry Terminal [RFC1043][RFC732]
|
||||
* 21 SUPDUP [RFC736][RFC734]
|
||||
* 22 SUPDUP Output [RFC749]
|
||||
* 23 Send Location [RFC779]
|
||||
* 24 Terminal Type [RFC1091]
|
||||
* 25 End of Record [RFC885]
|
||||
* 26 TACACS User Identification [RFC927]
|
||||
* 27 Output Marking [RFC933]
|
||||
* 28 Terminal Location Number [RFC946]
|
||||
* 29 Telnet 3270 Regime [RFC1041]
|
||||
* 30 X.3 PAD [RFC1053]
|
||||
* 31 Negotiate About Window Size [RFC1073]
|
||||
* 32 Terminal Speed [RFC1079]
|
||||
* 33 Remote Flow Control [RFC1372]
|
||||
* 34 Linemode [RFC1184]
|
||||
* 35 X Display Location [RFC1096]
|
||||
* 36 Environment Option [RFC1408]
|
||||
* 37 Authentication Option [RFC2941]
|
||||
* 38 Encryption Option [RFC2946]
|
||||
* 39 New Environment Option [RFC1572]
|
||||
* 40 TN3270E [RFC2355]
|
||||
* 41 XAUTH [Rob_Earhart]
|
||||
* 42 CHARSET [RFC2066]
|
||||
* 43 Telnet Remote Serial Port (RSP) [Robert_Barnes]
|
||||
* 44 Com Port Control Option [RFC2217]
|
||||
* 45 Telnet Suppress Local Echo [Wirt_Atmar]
|
||||
* 46 Telnet Start TLS [Michael_Boe]
|
||||
* 47 KERMIT [RFC2840]
|
||||
* 48 SEND-URL [David_Croft]
|
||||
* 49 FORWARD_X [Jeffrey_Altman]
|
||||
* 50-137 Unassigned [IANA]
|
||||
* 138 TELOPT PRAGMA LOGON [Steve_McGregory]
|
||||
* 139 TELOPT SSPI LOGON [Steve_McGregory]
|
||||
* 140 TELOPT PRAGMA HEARTBEAT [Steve_McGregory]
|
||||
* 141-254 Unassigned
|
||||
* 255 Extended-Options-List [RFC861]
|
||||
*/
|
||||
|
||||
#define TNO_BINARY 0 /* Binary Transmission */
|
||||
#define TNO_ECHO 1 /* Echo */
|
||||
#define TNO_SUPP_GA 3 /* Suppress Go Ahead */
|
||||
#define TNO_LOGOUT 18 /* Logout */
|
||||
#define TNO_TTYP 24 /* Terminal Type */
|
||||
#define TNO_NAWS 31 /* Negotiate About Window Size */
|
||||
#define TNO_NEO 39 /* New Environment Option */
|
||||
#define TNO_CPCO 44 /* RFC 2217 */
|
||||
#define TNO_KERMIT 47 /* Kermit */
|
||||
|
||||
#endif /* TELNET_PARAM_H */
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Lars Thrane A/S
|
||||
* SPDX-License-Identifier: GPL-2.0
|
||||
*
|
||||
* Telnet server - RFC-2217 handling
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#include "telnet.h"
|
||||
#include "telnet_param.h"
|
||||
|
||||
void telnet_rfc2217_handle_opt(tn_ctx_t * tcc, int opt,
|
||||
unsigned char *pu, int nd)
|
||||
{
|
||||
unsigned int sub;
|
||||
|
||||
sub = pu[0];
|
||||
switch (sub)
|
||||
{
|
||||
default:
|
||||
break;
|
||||
case 100: /* SIGNATURE */
|
||||
telnet_set_opt(tcc, opt, 0, "LT-NVT", 6);
|
||||
break;
|
||||
case 106: /* NOTIFY-LINESTATE */
|
||||
break;
|
||||
case 107: /* NOTIFY-MODEMSTATE */
|
||||
if (tcc->srv_ms)
|
||||
tcc->srv_ms(tcc->cctx, pu[1]);
|
||||
break;
|
||||
case 108: /* FLOWCONTROL-SUSPEND */
|
||||
case 109: /* FLOWCONTROL-RESUME */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void telnet_rfc2217_cfg(tn_ctx_t * tcc, int cmd, const void *val, int nd)
|
||||
{
|
||||
if (!tcc->mode_rfc2217)
|
||||
return;
|
||||
|
||||
telnet_set_opt(tcc, TNO_CPCO, cmd, val, nd);
|
||||
}
|
||||
|
||||
void telnet_rfc2217_ctl(tn_ctx_t * tcc, int cmd)
|
||||
{
|
||||
if (!tcc->mode_rfc2217)
|
||||
return;
|
||||
|
||||
telnet_set_opt(tcc, TNO_CPCO, TNS_SET_CONTROL, &cmd, 1);
|
||||
}
|
||||
@@ -0,0 +1,857 @@
|
||||
/*
|
||||
* Copyright (c) 2018 Lars Thrane A/S
|
||||
* SPDX-License-Identifier: GPL-2.0
|
||||
*/
|
||||
|
||||
#define _FILE_OFFSET_BITS 64
|
||||
#include <fuse/cuse_lowlevel.h>
|
||||
#include <fuse/fuse_opt.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include <string.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <linux/sockios.h>
|
||||
#include <netinet/in.h>
|
||||
#include <netdb.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <errno.h>
|
||||
#include <time.h>
|
||||
#include <pthread.h>
|
||||
#include <sys/poll.h>
|
||||
#include <signal.h>
|
||||
#include <termios.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <time.h>
|
||||
|
||||
#include "telnet.h"
|
||||
|
||||
#define RX_BUF_SIZE 1024
|
||||
#define MAX_HOST_NAME 64
|
||||
|
||||
static char host_name[MAX_HOST_NAME];
|
||||
static int host_port;
|
||||
static int lfd;
|
||||
|
||||
typedef struct ttynvt {
|
||||
int nonblock;
|
||||
pthread_mutex_t tty_lock;
|
||||
pthread_mutex_t poll_lock;
|
||||
pthread_t thread_id;
|
||||
|
||||
pthread_mutex_t cond_lock;
|
||||
pthread_cond_t cond;
|
||||
|
||||
struct fuse_pollhandle *ph;
|
||||
|
||||
struct pollfd fds[1];
|
||||
|
||||
struct termios tio;
|
||||
unsigned char mcr;
|
||||
|
||||
char buf[RX_BUF_SIZE];
|
||||
size_t blen;
|
||||
size_t rxcnt;
|
||||
|
||||
int txbuf_size;
|
||||
volatile sig_atomic_t error;
|
||||
|
||||
tn_ctx_t *tn;
|
||||
} ttynvt_t;
|
||||
|
||||
void ttynvt_log(const char *fmt, ...)
|
||||
{
|
||||
struct tm *ts;
|
||||
time_t t;
|
||||
char s[32];
|
||||
va_list arg;
|
||||
|
||||
if (lfd < 0)
|
||||
return;
|
||||
t = time(NULL);
|
||||
ts = gmtime(&t);
|
||||
strftime(s, 32, "%b %a %T", ts);
|
||||
dprintf(lfd, "%s ", s);
|
||||
|
||||
va_start(arg, fmt);
|
||||
vdprintf(lfd, fmt, arg);
|
||||
va_end(arg);
|
||||
}
|
||||
|
||||
static void ttynvt_release(fuse_req_t req, struct fuse_file_info *info)
|
||||
{
|
||||
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
|
||||
|
||||
ttynvt_log("connection closed");
|
||||
|
||||
close(tty->fds[0].fd);
|
||||
|
||||
(void)pthread_cancel(tty->thread_id);
|
||||
(void)pthread_join(tty->thread_id, NULL);
|
||||
(void)pthread_mutex_destroy(&tty->tty_lock);
|
||||
(void)pthread_mutex_destroy(&tty->poll_lock);
|
||||
(void)pthread_mutex_destroy(&tty->cond_lock);
|
||||
(void)pthread_cond_destroy(&tty->cond);
|
||||
|
||||
if (tty->ph)
|
||||
fuse_pollhandle_destroy(tty->ph);
|
||||
|
||||
free(tty->tn);
|
||||
free(tty);
|
||||
fuse_reply_err(req, 0);
|
||||
}
|
||||
|
||||
static void ttynvt_notify(ttynvt_t * tty)
|
||||
{
|
||||
pthread_mutex_lock(&tty->poll_lock);
|
||||
if (tty->ph)
|
||||
{
|
||||
fuse_lowlevel_notify_poll(tty->ph);
|
||||
fuse_pollhandle_destroy(tty->ph);
|
||||
tty->ph = NULL;
|
||||
}
|
||||
pthread_mutex_unlock(&tty->poll_lock);
|
||||
pthread_cond_signal(&tty->cond);
|
||||
}
|
||||
|
||||
static void ttynvt_update_notify(ttynvt_t * tty, struct fuse_pollhandle *ph)
|
||||
{
|
||||
pthread_mutex_lock(&tty->poll_lock);
|
||||
struct fuse_pollhandle *tmp_ph = tty->ph;
|
||||
|
||||
tty->ph = ph;
|
||||
pthread_mutex_unlock(&tty->poll_lock);
|
||||
if (tmp_ph)
|
||||
fuse_pollhandle_destroy(tmp_ph);
|
||||
}
|
||||
|
||||
static unsigned ttynvt_write_avail(ttynvt_t * tty)
|
||||
{
|
||||
size_t write_avail;
|
||||
int used;
|
||||
|
||||
pthread_mutex_lock(&tty->tty_lock);
|
||||
if (ioctl(tty->fds[0].fd, SIOCOUTQ, &used) < 0)
|
||||
return 0;
|
||||
|
||||
write_avail = tty->txbuf_size - used;
|
||||
|
||||
pthread_mutex_unlock(&tty->tty_lock);
|
||||
return write_avail;
|
||||
}
|
||||
|
||||
static int ttynvt_read_avail(ttynvt_t * tty)
|
||||
{
|
||||
int read_avail;
|
||||
|
||||
pthread_mutex_lock(&tty->tty_lock);
|
||||
|
||||
read_avail = tty->rxcnt;
|
||||
|
||||
pthread_mutex_unlock(&tty->tty_lock);
|
||||
return read_avail;
|
||||
}
|
||||
|
||||
static void *ttynvt_read_net(void *arg)
|
||||
{
|
||||
ttynvt_t *tty = (ttynvt_t *) arg;
|
||||
ssize_t res;
|
||||
int tn_res = -1;
|
||||
|
||||
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
|
||||
|
||||
while (1)
|
||||
{
|
||||
res = poll(tty->fds, 1, -1);
|
||||
if (res < 0)
|
||||
{
|
||||
if (errno == EINTR)
|
||||
continue;
|
||||
else
|
||||
break;
|
||||
}
|
||||
else if (tty->fds[0].revents & (POLLERR | POLLHUP | POLLNVAL))
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (tty->fds[0].revents & POLLIN)
|
||||
{
|
||||
pthread_mutex_lock(&tty->tty_lock);
|
||||
int read_avail = RX_BUF_SIZE - tty->blen;
|
||||
|
||||
if (read_avail == 0)
|
||||
{
|
||||
/*buffer overflow */
|
||||
tty->blen = 0;
|
||||
read_avail = RX_BUF_SIZE;
|
||||
}
|
||||
res = read(tty->fds[0].fd, tty->buf + tty->blen, read_avail);
|
||||
|
||||
if (res <= 0)
|
||||
{
|
||||
pthread_mutex_unlock(&tty->tty_lock);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
tty->blen += res;
|
||||
|
||||
tn_res = telnet_rx(tty->tn, tty->buf, &tty->blen);
|
||||
if (tn_res == 0)
|
||||
{
|
||||
tty->rxcnt = tty->blen;
|
||||
pthread_mutex_unlock(&tty->tty_lock);
|
||||
ttynvt_notify(tty);
|
||||
}
|
||||
else
|
||||
{
|
||||
pthread_mutex_unlock(&tty->tty_lock);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ttynvt_log("RX read error:%ld\n", res);
|
||||
|
||||
tty->error = 1;
|
||||
close(tty->fds[0].fd);
|
||||
ttynvt_notify(tty);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static int ttynvt_connect(const char *host, unsigned int port)
|
||||
{
|
||||
|
||||
struct sockaddr_in serveraddr;
|
||||
struct hostent *server;
|
||||
|
||||
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
if (sockfd < 0)
|
||||
{
|
||||
perror("ERROR: opening socket\n");
|
||||
return sockfd;
|
||||
}
|
||||
server = gethostbyname(host);
|
||||
if (server == NULL)
|
||||
{
|
||||
perror("ERROR: can't resolve host name");
|
||||
return -1;
|
||||
}
|
||||
bzero((char *)&serveraddr, sizeof(serveraddr));
|
||||
serveraddr.sin_family = AF_INET;
|
||||
bcopy((char *)server->h_addr, (char *)&serveraddr.sin_addr.s_addr,
|
||||
server->h_length);
|
||||
serveraddr.sin_port = htons(port);
|
||||
|
||||
if (connect
|
||||
(sockfd, (const struct sockaddr *)&serveraddr, sizeof(serveraddr)) < 0)
|
||||
{
|
||||
perror("ERROR: connecting\n");
|
||||
return -1;
|
||||
}
|
||||
return sockfd;
|
||||
}
|
||||
|
||||
int srv_write(void *cctx, const void *buf, int len)
|
||||
{
|
||||
ttynvt_t *tty = cctx;
|
||||
|
||||
return write(tty->fds[0].fd, buf, len);
|
||||
}
|
||||
|
||||
int srv_read(void *cctx, int timeout)
|
||||
{
|
||||
ttynvt_t *tty = cctx;
|
||||
int res;
|
||||
|
||||
while (1)
|
||||
{
|
||||
res = poll(tty->fds, 1, timeout);
|
||||
if (res <= 0)
|
||||
return res;
|
||||
|
||||
if (tty->fds[0].revents & POLLIN)
|
||||
{
|
||||
res = read(tty->fds[0].fd, tty->buf + tty->blen,
|
||||
RX_BUF_SIZE - tty->blen);
|
||||
if (res <= 0)
|
||||
return res;
|
||||
tty->blen += (size_t) res;
|
||||
res = telnet_rx(tty->tn, tty->buf, &tty->blen);
|
||||
if (res == 0)
|
||||
{
|
||||
tty->rxcnt = tty->blen;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void modem_status_cb(void *cctx, int status)
|
||||
{
|
||||
ttynvt_t *tty = cctx;
|
||||
|
||||
tty->mcr &= ~(TIOCM_CD | TIOCM_RI | TIOCM_CTS | TIOCM_DSR);
|
||||
|
||||
ttynvt_log("modem status: 0x%02x\n", status);
|
||||
|
||||
if (status & TNS_STATE_CD)
|
||||
tty->mcr |= TIOCM_CD;
|
||||
if (status & TNS_STATE_RI)
|
||||
tty->mcr |= TIOCM_RI;
|
||||
if (status & TNS_STATE_CTS)
|
||||
tty->mcr |= TIOCM_CTS;
|
||||
if (status & TNS_STATE_DSR)
|
||||
tty->mcr |= TIOCM_DSR;
|
||||
}
|
||||
|
||||
static void ttynvt_open(fuse_req_t req, struct fuse_file_info *info)
|
||||
{
|
||||
ttynvt_t *tty;
|
||||
socklen_t len;
|
||||
int sockfd;
|
||||
int res;
|
||||
|
||||
tty = calloc(1, sizeof(*tty));
|
||||
if (!tty)
|
||||
{
|
||||
fuse_reply_err(req, ENOMEM);
|
||||
return;
|
||||
}
|
||||
|
||||
tty->tn = telnet_ctx_init(tty, srv_write, srv_read, modem_status_cb);
|
||||
if (!tty->tn)
|
||||
{
|
||||
free(tty);
|
||||
fuse_reply_err(req, ENOMEM);
|
||||
return;
|
||||
}
|
||||
|
||||
sockfd = ttynvt_connect(host_name, host_port);
|
||||
if (sockfd < 0)
|
||||
{
|
||||
free(tty->tn);
|
||||
free(tty);
|
||||
fuse_reply_err(req, EFAULT);
|
||||
return;
|
||||
}
|
||||
|
||||
tty->fds[0].fd = sockfd;
|
||||
tty->fds[0].events = POLLIN;
|
||||
tty->blen = 0;
|
||||
tty->rxcnt = 0;
|
||||
|
||||
res = telnet_open(tty->tn);
|
||||
if (res < 0)
|
||||
{
|
||||
free(tty->tn);
|
||||
free(tty);
|
||||
fuse_reply_err(req, EBADE);
|
||||
return;
|
||||
}
|
||||
|
||||
len = sizeof(tty->txbuf_size);
|
||||
if (getsockopt(sockfd, SOL_SOCKET, SO_SNDBUF, &tty->txbuf_size, &len) < 0)
|
||||
{
|
||||
free(tty->tn);
|
||||
free(tty);
|
||||
fuse_reply_err(req, EIO);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pthread_mutex_init(&tty->tty_lock, NULL) < 0 ||
|
||||
pthread_mutex_init(&tty->poll_lock, NULL) < 0 ||
|
||||
pthread_mutex_init(&tty->cond_lock, NULL) < 0 ||
|
||||
pthread_cond_init(&tty->cond, NULL) < 0 ||
|
||||
pthread_create(&tty->thread_id, NULL, &ttynvt_read_net, tty))
|
||||
{
|
||||
free(tty->tn);
|
||||
free(tty);
|
||||
fuse_reply_err(req, ENOMEM);
|
||||
return;
|
||||
}
|
||||
|
||||
info->fh = (uintptr_t) tty;
|
||||
info->nonseekable = 1;
|
||||
info->direct_io = 1;
|
||||
|
||||
ttynvt_log("open connection to server: %s:%d\n", host_name, host_port);
|
||||
|
||||
fuse_reply_open(req, info);
|
||||
}
|
||||
|
||||
static void
|
||||
ttynvt_read(fuse_req_t req, size_t size, off_t off,
|
||||
struct fuse_file_info *info)
|
||||
{
|
||||
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
|
||||
|
||||
if (tty->error)
|
||||
{
|
||||
fuse_reply_err(req, EPIPE);
|
||||
return;
|
||||
}
|
||||
|
||||
pthread_mutex_lock(&tty->tty_lock);
|
||||
if (size > tty->rxcnt)
|
||||
size = tty->rxcnt;
|
||||
fuse_reply_buf(req, tty->buf, size);
|
||||
memmove(tty->buf, tty->buf + size, tty->blen - size);
|
||||
tty->blen -= size;
|
||||
tty->rxcnt -= size;
|
||||
pthread_mutex_unlock(&tty->tty_lock);
|
||||
}
|
||||
|
||||
static void
|
||||
ttynvt_write(fuse_req_t req, const char *data, size_t size, off_t off,
|
||||
struct fuse_file_info *info)
|
||||
{
|
||||
int res;
|
||||
|
||||
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
fuse_reply_write(req, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tty->error)
|
||||
{
|
||||
fuse_reply_err(req, EPIPE);
|
||||
return;
|
||||
}
|
||||
|
||||
if (tty->nonblock || (info->flags & O_NONBLOCK))
|
||||
{
|
||||
size_t write_avail = ttynvt_write_avail(tty);
|
||||
|
||||
if (size > write_avail)
|
||||
size = write_avail;
|
||||
}
|
||||
|
||||
res = telnet_tx(tty->tn, data, size);
|
||||
if (res < 0)
|
||||
{
|
||||
tty->error = 1;
|
||||
fuse_reply_err(req, EPIPE);
|
||||
return;
|
||||
}
|
||||
else if (res == 0)
|
||||
{
|
||||
fuse_reply_err(req, EAGAIN);
|
||||
return;
|
||||
}
|
||||
|
||||
fuse_reply_write(req, res);
|
||||
}
|
||||
|
||||
static int baudrate(speed_t speed)
|
||||
{
|
||||
return speed == B50 ? 50 :
|
||||
speed == B75 ? 75 :
|
||||
speed == B110 ? 110 :
|
||||
speed == B300 ? 300 :
|
||||
speed == B600 ? 600 :
|
||||
speed == B1200 ? 1200 :
|
||||
speed == B1800 ? 1800 :
|
||||
speed == B2400 ? 2400 :
|
||||
speed == B4800 ? 4800 :
|
||||
speed == B9600 ? 9600 :
|
||||
speed == B19200 ? 19200 :
|
||||
speed == B38400 ? 38400 :
|
||||
speed == B57600 ? 57600 :
|
||||
speed == B115200 ? 115200 : speed == B230400 ? 230400 : 0;
|
||||
}
|
||||
|
||||
static const char pstr[] = { ' ', 'N', 'O', 'E', 'M', 'S' };
|
||||
|
||||
static void
|
||||
ttynvt_ioctl(fuse_req_t req, int cmd, void *arg,
|
||||
struct fuse_file_info *info, unsigned flags,
|
||||
const void *in_buf, size_t in_bufsz, size_t out_bufsz)
|
||||
{
|
||||
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
|
||||
unsigned int tiocm = 0;
|
||||
static unsigned int mcr = 0;
|
||||
static struct termios tio = {
|
||||
.c_cflag = B115200 | CS8 | PARODD | CREAD | CLOCAL,
|
||||
};
|
||||
int val;
|
||||
|
||||
switch (cmd)
|
||||
{
|
||||
case TCSETSF:
|
||||
case TCSETSW:
|
||||
case TCSETS:
|
||||
if (!in_bufsz)
|
||||
{
|
||||
struct iovec iov = { arg, sizeof(struct termios) };
|
||||
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
speed_t speed;
|
||||
int csize, par, sb;
|
||||
|
||||
memcpy(&tio, in_buf, in_bufsz);
|
||||
|
||||
speed = cfgetospeed(&tio);
|
||||
val = htonl(baudrate(speed));
|
||||
telnet_rfc2217_cfg(tty->tn, TNS_SET_BAUDRATE, &val, 4);
|
||||
|
||||
if ((tio.c_cflag & CSIZE) == CS5)
|
||||
csize = 5;
|
||||
else if ((tio.c_cflag & CSIZE) == CS6)
|
||||
csize = 6;
|
||||
else if ((tio.c_cflag & CSIZE) == CS7)
|
||||
csize = 7;
|
||||
else
|
||||
csize = 8;
|
||||
telnet_rfc2217_cfg(tty->tn, TNS_SET_DATASIZE, &csize, 1);
|
||||
|
||||
if (tio.c_cflag & PARENB && tio.c_iflag & IGNPAR)
|
||||
par = 1;
|
||||
else if (tio.c_cflag & PARENB && tio.c_iflag & CMSPAR &&
|
||||
tio.c_cflag & PARODD)
|
||||
par = 4;
|
||||
else if (tio.c_cflag & PARENB && tio.c_cflag & CMSPAR)
|
||||
par = 5;
|
||||
else if (tio.c_cflag & PARENB)
|
||||
par = 3;
|
||||
else
|
||||
par = 1;
|
||||
telnet_rfc2217_cfg(tty->tn, TNS_SET_PARITY, &par, 1);
|
||||
|
||||
if (tio.c_cflag & CSTOPB)
|
||||
sb = 2;
|
||||
else
|
||||
sb = 1;
|
||||
telnet_rfc2217_cfg(tty->tn, TNS_SET_STOPSIZE, &sb, 1);
|
||||
|
||||
if (tio.c_cflag & CRTSCTS)
|
||||
telnet_rfc2217_ctl(tty->tn, TNS_CTL_CTSRTS);
|
||||
|
||||
if (!(mcr & TIOCM_DTR) && speed != B0)
|
||||
{
|
||||
mcr |= TIOCM_DTR;
|
||||
telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_ON);
|
||||
}
|
||||
|
||||
ttynvt_log("CFG ioctl: %u%c%u%u %cCTSRTS %cCREAD %cHUPCL %cCLOCAL\n",
|
||||
baudrate(speed), pstr[par], csize, sb,
|
||||
tio.c_cflag & CRTSCTS ? '+' : '-',
|
||||
tio.c_cflag & CREAD ? '+' : '-',
|
||||
tio.c_cflag & HUPCL ? '+' : '-',
|
||||
tio.c_cflag & CLOCAL ? '+' : '-');
|
||||
|
||||
fuse_reply_ioctl(req, 0, 0, 0);
|
||||
}
|
||||
break;
|
||||
case TCGETS:
|
||||
if (out_bufsz)
|
||||
{
|
||||
fuse_reply_ioctl(req, 0, &tio, sizeof(struct termio));
|
||||
}
|
||||
else
|
||||
{
|
||||
struct iovec iov = { arg, sizeof(struct termio) };
|
||||
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
|
||||
}
|
||||
break;
|
||||
|
||||
case TIOCINQ: /* Get the number of bytes in the input
|
||||
* buffer */
|
||||
if (out_bufsz)
|
||||
{
|
||||
size_t rx_size = ttynvt_read_avail(tty);
|
||||
|
||||
fuse_reply_ioctl(req, 0, &rx_size, sizeof(size_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
struct iovec iov = { arg, sizeof(size_t) };
|
||||
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
|
||||
}
|
||||
|
||||
break;
|
||||
case TIOCOUTQ: /* Get the number of bytes in the output
|
||||
* buffer */
|
||||
if (out_bufsz)
|
||||
{
|
||||
size_t tx_size = ttynvt_write_avail(tty);
|
||||
|
||||
fuse_reply_ioctl(req, 0, &tx_size, sizeof(size_t));
|
||||
}
|
||||
else
|
||||
{
|
||||
struct iovec iov = { arg, sizeof(size_t) };
|
||||
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
|
||||
}
|
||||
break;
|
||||
case TCFLSH:
|
||||
if (arg == TCIFLUSH)
|
||||
{
|
||||
/*todo */
|
||||
}
|
||||
else if ((size_t) arg == TCOFLUSH)
|
||||
{
|
||||
/*todo */
|
||||
}
|
||||
else if ((size_t) arg == TCIOFLUSH)
|
||||
{
|
||||
/*todo */
|
||||
}
|
||||
fuse_reply_ioctl(req, 0, 0, 0);
|
||||
break;
|
||||
case TCIFLUSH:
|
||||
val = 1;
|
||||
telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &val, 1);
|
||||
pthread_mutex_lock(&tty->tty_lock);
|
||||
tty->blen = 0;
|
||||
pthread_mutex_unlock(&tty->tty_lock);
|
||||
|
||||
fuse_reply_ioctl(req, 0, 0, 0);
|
||||
break;
|
||||
case TCOFLUSH:
|
||||
val = 2;
|
||||
telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &val, 1);
|
||||
fuse_reply_ioctl(req, 0, 0, 0);
|
||||
break;
|
||||
case TCIOFLUSH:
|
||||
val = 3;
|
||||
telnet_rfc2217_cfg(tty->tn, TNS_SET_PURGE, &val, 1);
|
||||
fuse_reply_ioctl(req, 0, 0, 0);
|
||||
break;
|
||||
case TIOCMGET:
|
||||
if (out_bufsz)
|
||||
{
|
||||
fuse_reply_ioctl(req, 0, &mcr, sizeof(unsigned int));
|
||||
}
|
||||
else
|
||||
{
|
||||
struct iovec iov = { arg, sizeof(unsigned int) };
|
||||
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
|
||||
}
|
||||
break;
|
||||
case TIOCMBIS:
|
||||
if (!in_bufsz)
|
||||
{
|
||||
struct iovec iov = { arg, sizeof(unsigned int) };
|
||||
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tiocm = *(unsigned int *)in_buf;
|
||||
fuse_reply_ioctl(req, 0, 0, 0);
|
||||
}
|
||||
if (tiocm & TIOCM_RTS)
|
||||
{
|
||||
mcr |= TIOCM_RTS;
|
||||
telnet_rfc2217_ctl(tty->tn, TNS_CTL_RTS_ON);
|
||||
}
|
||||
if (tiocm & TIOCM_DTR)
|
||||
{
|
||||
mcr |= TIOCM_DTR;
|
||||
telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_ON);
|
||||
}
|
||||
break;
|
||||
case TIOCMBIC:
|
||||
if (!in_bufsz)
|
||||
{
|
||||
struct iovec iov = { arg, sizeof(unsigned int) };
|
||||
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tiocm = *(unsigned int *)in_buf;
|
||||
fuse_reply_ioctl(req, 0, 0, 0);
|
||||
}
|
||||
if (tiocm & TIOCM_RTS)
|
||||
{
|
||||
mcr &= ~TIOCM_RTS;
|
||||
telnet_rfc2217_ctl(tty->tn, TNS_CTL_RTS_OFF);
|
||||
}
|
||||
if (tiocm & TIOCM_DTR)
|
||||
{
|
||||
mcr &= ~TIOCM_DTR;
|
||||
telnet_rfc2217_ctl(tty->tn, TNS_CTL_DTR_OFF);
|
||||
}
|
||||
break;
|
||||
case TIOCMSET:
|
||||
if (!in_bufsz)
|
||||
{
|
||||
struct iovec iov = { arg, sizeof(unsigned int) };
|
||||
fuse_reply_ioctl_retry(req, &iov, 1, NULL, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
tiocm = *(unsigned int *)in_buf;
|
||||
fuse_reply_ioctl(req, 0, 0, 0);
|
||||
|
||||
ttynvt_log("CFG ioctl: DTR=%c RTS=%c\n",
|
||||
tiocm & TIOCM_DTR ? '1' : '0',
|
||||
tiocm & TIOCM_RTS ? '1' : '0');
|
||||
|
||||
mcr &= ~(TIOCM_RTS | TIOCM_DTR);
|
||||
mcr |= tiocm & (TIOCM_RTS | TIOCM_DTR);
|
||||
telnet_rfc2217_ctl(tty->tn, (tiocm & TIOCM_DTR) ?
|
||||
TNS_CTL_DTR_ON : TNS_CTL_DTR_OFF);
|
||||
telnet_rfc2217_ctl(tty->tn, (tiocm & TIOCM_RTS) ?
|
||||
TNS_CTL_RTS_ON : TNS_CTL_RTS_OFF);
|
||||
}
|
||||
break;
|
||||
case TCSBRK:
|
||||
fuse_reply_ioctl(req, 0, 0, 0);
|
||||
break;
|
||||
case TIOCGWINSZ:
|
||||
if (out_bufsz)
|
||||
{
|
||||
struct winsize ws = {.ws_row = 25,.ws_col = 80 };
|
||||
fuse_reply_ioctl(req, 0, &ws, sizeof(struct winsize));
|
||||
}
|
||||
else
|
||||
{
|
||||
struct iovec iov = { arg, sizeof(struct winsize) };
|
||||
fuse_reply_ioctl_retry(req, NULL, 0, &iov, 1);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fuse_reply_err(req, ENOSYS);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
ttynvt_poll(fuse_req_t req, struct fuse_file_info *info,
|
||||
struct fuse_pollhandle *ph)
|
||||
{
|
||||
ttynvt_t *tty = (ttynvt_t *) (uintptr_t) info->fh;
|
||||
int revents = 0;
|
||||
|
||||
ttynvt_update_notify(tty, ph);
|
||||
|
||||
if (ttynvt_write_avail(tty) > 0)
|
||||
revents |= POLLOUT;
|
||||
if (ttynvt_read_avail(tty) > 0)
|
||||
revents |= POLLIN;
|
||||
if (tty->error)
|
||||
revents = POLLERR;
|
||||
|
||||
fuse_reply_poll(req, revents);
|
||||
}
|
||||
|
||||
static const struct cuse_lowlevel_ops ttynvt_op = {
|
||||
.open = ttynvt_open,
|
||||
.read = ttynvt_read,
|
||||
.write = ttynvt_write,
|
||||
.ioctl = ttynvt_ioctl,
|
||||
.poll = ttynvt_poll,
|
||||
.release = ttynvt_release,
|
||||
};
|
||||
|
||||
#define TTYNET_OPT(t, p, v) { t, offsetof(struct ttynvt_param, p), v }
|
||||
struct ttynvt_param {
|
||||
unsigned major;
|
||||
unsigned minor;
|
||||
char *dev_name;
|
||||
char *logfile;
|
||||
char *host;
|
||||
unsigned port;
|
||||
};
|
||||
|
||||
static const struct fuse_opt ttynvt_opts[] = {
|
||||
TTYNET_OPT("-M %u", major, 1),
|
||||
TTYNET_OPT("--maj=%u", major, 1),
|
||||
TTYNET_OPT("-m %u", minor, 1),
|
||||
TTYNET_OPT("--min=%u", minor, 1),
|
||||
TTYNET_OPT("-n %s", dev_name, 0),
|
||||
TTYNET_OPT("--name=%s", dev_name, 0),
|
||||
TTYNET_OPT("-s %s", host, 0),
|
||||
TTYNET_OPT("--server=%s", host, 0),
|
||||
TTYNET_OPT("-p %u", port, 0),
|
||||
TTYNET_OPT("--port=%u", port, 0),
|
||||
TTYNET_OPT("-l %s", logfile, 0),
|
||||
TTYNET_OPT("--log=%s", logfile, 0),
|
||||
FUSE_OPT_KEY("-h", 0),
|
||||
FUSE_OPT_KEY("--help", 0),
|
||||
FUSE_OPT_END
|
||||
};
|
||||
|
||||
static void print_help(void)
|
||||
{
|
||||
fprintf(stderr, "ttynvt Usage:\n"
|
||||
"\t-M major, --maj=major\n"
|
||||
"\t-m minor, --min=minor\n"
|
||||
"\t-n name, --name=name (default: ttynvt)\n"
|
||||
"\t\tDevice will be created in /dev/$name.\n"
|
||||
"\t-l logfile, --log=file name\n"
|
||||
"\t-s server, --server=host name or IP address\n"
|
||||
"\t-p port, --port=host port number\n" "\n");
|
||||
}
|
||||
|
||||
static int
|
||||
process_arg(void *data, const char *arg, int key, struct fuse_args *outargs)
|
||||
{
|
||||
switch (key)
|
||||
{
|
||||
case 0:
|
||||
print_help();
|
||||
return fuse_opt_add_arg(outargs, "-ho");
|
||||
default:
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
struct fuse_args args = FUSE_ARGS_INIT(argc, argv);
|
||||
struct ttynvt_param param = { 0 };
|
||||
|
||||
char dev_name[128] = { 0 };
|
||||
const char *dev_info_argv[] = { dev_name };
|
||||
|
||||
if (fuse_opt_parse(&args, ¶m, ttynvt_opts, process_arg))
|
||||
{
|
||||
fprintf(stderr, "Failed to parse arguments\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
snprintf(dev_name, sizeof(dev_name), "DEVNAME=%s",
|
||||
param.dev_name ? param.dev_name : "ttyNVT0");
|
||||
|
||||
snprintf(host_name, sizeof(host_name), "%s", param.host);
|
||||
|
||||
host_port = param.port;
|
||||
|
||||
if (param.logfile)
|
||||
{
|
||||
lfd =
|
||||
open(param.logfile, O_CREAT | O_RDWR | O_APPEND,
|
||||
S_IWUSR | S_IRUSR);
|
||||
if (lfd < 0)
|
||||
{
|
||||
perror("Failed to open log file");
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
lfd = -1;
|
||||
|
||||
struct cuse_info ci = {
|
||||
.dev_major = param.major,
|
||||
.dev_minor = param.minor,
|
||||
.dev_info_argc = 1,
|
||||
.dev_info_argv = dev_info_argv,
|
||||
.flags = CUSE_UNRESTRICTED_IOCTL,
|
||||
};
|
||||
|
||||
return cuse_lowlevel_main(args.argc, args.argv, &ci, &ttynvt_op, NULL);
|
||||
}
|
||||
Reference in New Issue
Block a user