summaryrefslogtreecommitdiff
path: root/sys/src/cmd/ip/telnetd.c
diff options
context:
space:
mode:
authorTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
committerTaru Karttunen <taruti@taruti.net>2011-03-30 15:46:40 +0300
commite5888a1ffdae813d7575f5fb02275c6bb07e5199 (patch)
treed8d51eac403f07814b9e936eed0c9a79195e2450 /sys/src/cmd/ip/telnetd.c
Import sources from 2011-03-30 iso image
Diffstat (limited to 'sys/src/cmd/ip/telnetd.c')
-rwxr-xr-xsys/src/cmd/ip/telnetd.c647
1 files changed, 647 insertions, 0 deletions
diff --git a/sys/src/cmd/ip/telnetd.c b/sys/src/cmd/ip/telnetd.c
new file mode 100755
index 000000000..395c3ee13
--- /dev/null
+++ b/sys/src/cmd/ip/telnetd.c
@@ -0,0 +1,647 @@
+#include <u.h>
+#include <libc.h>
+#include <bio.h>
+#include <auth.h>
+#include <libsec.h>
+
+#include "../ip/telnet.h"
+
+/* console state (for consctl) */
+typedef struct Consstate Consstate;
+struct Consstate{
+ int raw;
+ int hold;
+};
+Consstate *cons;
+
+int notefd; /* for sending notes to the child */
+int noproto; /* true if we shouldn't be using the telnet protocol */
+int trusted; /* true if we need not authenticate - current user
+ is ok */
+int nonone = 1; /* don't allow none logins */
+int noworldonly; /* only noworld accounts */
+
+enum
+{
+ Maxpath= 256,
+ Maxuser= 64,
+ Maxvar= 32,
+};
+
+/* input and output buffers for network connection */
+Biobuf netib;
+Biobuf childib;
+char remotesys[Maxpath]; /* name of remote system */
+
+int alnum(int);
+int conssim(void);
+int fromchild(char*, int);
+int fromnet(char*, int);
+int termchange(Biobuf*, int);
+int termsub(Biobuf*, uchar*, int);
+int xlocchange(Biobuf*, int);
+int xlocsub(Biobuf*, uchar*, int);
+int challuser(char*);
+int noworldlogin(char*);
+void* share(ulong);
+int doauth(char*);
+
+#define TELNETLOG "telnet"
+
+void
+logit(char *fmt, ...)
+{
+ va_list arg;
+ char buf[8192];
+
+ va_start(arg, fmt);
+ vseprint(buf, buf + sizeof(buf) / sizeof(*buf), fmt, arg);
+ va_end(arg);
+ syslog(0, TELNETLOG, "(%s) %s", remotesys, buf);
+}
+
+void
+getremote(char *dir)
+{
+ int fd, n;
+ char remfile[Maxpath];
+
+ sprint(remfile, "%s/remote", dir);
+ fd = open(remfile, OREAD);
+ if(fd < 0)
+ strcpy(remotesys, "unknown2");
+ n = read(fd, remotesys, sizeof(remotesys)-1);
+ if(n>0)
+ remotesys[n-1] = 0;
+ else
+ strcpy(remotesys, remfile);
+ close(fd);
+}
+
+void
+main(int argc, char *argv[])
+{
+ char buf[1024];
+ int fd;
+ char user[Maxuser];
+ int tries = 0;
+ int childpid;
+ int n, eofs;
+
+ memset(user, 0, sizeof(user));
+ ARGBEGIN {
+ case 'n':
+ opt[Echo].local = 1;
+ noproto = 1;
+ break;
+ case 'p':
+ noproto = 1;
+ break;
+ case 'a':
+ nonone = 0;
+ break;
+ case 't':
+ trusted = 1;
+ strncpy(user, getuser(), sizeof(user)-1);
+ break;
+ case 'u':
+ strncpy(user, ARGF(), sizeof(user)-1);
+ break;
+ case 'd':
+ debug = 1;
+ break;
+ case 'N':
+ noworldonly = 1;
+ break;
+ } ARGEND
+
+ if(argc)
+ getremote(argv[argc-1]);
+ else
+ strcpy(remotesys, "unknown");
+
+ /* options we need routines for */
+ opt[Term].change = termchange;
+ opt[Term].sub = termsub;
+ opt[Xloc].sub = xlocsub;
+
+ /* setup default telnet options */
+ if(!noproto){
+ send3(1, Iac, Will, opt[Echo].code);
+ send3(1, Iac, Do, opt[Term].code);
+ send3(1, Iac, Do, opt[Xloc].code);
+ }
+
+ /* shared data for console state */
+ cons = share(sizeof(Consstate));
+ if(cons == 0)
+ fatal("shared memory", 0, 0);
+
+ /* authenticate and create new name space */
+ Binit(&netib, 0, OREAD);
+ if (!trusted){
+ while(doauth(user) < 0)
+ if(++tries == 5){
+ logit("failed as %s: %r", user);
+ print("authentication failure:%r\r\n");
+ exits("authentication");
+ }
+ }
+ logit("logged in as %s", user);
+ putenv("service", "con");
+
+ /* simulate /dev/consctl and /dev/cons using pipes */
+ fd = conssim();
+ if(fd < 0)
+ fatal("simulating", 0, 0);
+ Binit(&childib, fd, OREAD);
+
+ /* start a shell in a different process group */
+ switch(childpid = rfork(RFPROC|RFNAMEG|RFFDG|RFNOTEG)){
+ case -1:
+ fatal("fork", 0, 0);
+ case 0:
+ close(fd);
+ fd = open("/dev/cons", OREAD);
+ dup(fd, 0);
+ close(fd);
+ fd = open("/dev/cons", OWRITE);
+ dup(fd, 1);
+ dup(fd, 2);
+ close(fd);
+ segdetach(cons);
+ execl("/bin/rc", "rc", "-il", nil);
+ fatal("/bin/rc", 0, 0);
+ default:
+ sprint(buf, "/proc/%d/notepg", childpid);
+ notefd = open(buf, OWRITE);
+ break;
+ }
+
+ /* two processes to shuttle bytes twixt children and network */
+ switch(fork()){
+ case -1:
+ fatal("fork", 0, 0);
+ case 0:
+ eofs = 0;
+ for(;;){
+ n = fromchild(buf, sizeof(buf));
+ if(n <= 0){
+ if(eofs++ > 2)
+ break;
+ continue;
+ }
+ eofs = 0;
+ if(write(1, buf, n) != n)
+ break;
+ }
+ break;
+ default:
+ while((n = fromnet(buf, sizeof(buf))) >= 0)
+ if(write(fd, buf, n) != n)
+ break;
+ break;
+ }
+
+ /* kill off all server processes */
+ sprint(buf, "/proc/%d/notepg", getpid());
+ fd = open(buf, OWRITE);
+ write(fd, "die", 3);
+ exits(0);
+}
+
+void
+prompt(char *p, char *b, int n, int raw)
+{
+ char *e;
+ int i;
+ int echo;
+
+ echo = opt[Echo].local;
+ if(raw)
+ opt[Echo].local = 0;
+ print("%s: ", p);
+ for(e = b+n; b < e;){
+ i = fromnet(b, e-b);
+ if(i <= 0)
+ exits("fromnet: hungup");
+ b += i;
+ if(*(b-1) == '\n' || *(b-1) == '\r'){
+ *(b-1) = 0;
+ break;
+ }
+ }
+ if(raw)
+ opt[Echo].local = echo;
+}
+
+/*
+ * challenge user
+ */
+int
+challuser(char *user)
+{
+ char nchall[64];
+ char response[64];
+ Chalstate *ch;
+ AuthInfo *ai;
+
+ if(strcmp(user, "none") == 0){
+ if(nonone)
+ return -1;
+ newns("none", nil);
+ return 0;
+ }
+ if((ch = auth_challenge("proto=p9cr role=server user=%q", user)) == nil)
+ return -1;
+ snprint(nchall, sizeof nchall, "challenge: %s\r\nresponse", ch->chal);
+ prompt(nchall, response, sizeof response, 0);
+ ch->resp = response;
+ ch->nresp = strlen(response);
+ ai = auth_response(ch);
+ auth_freechal(ch);
+ if(ai == nil){
+ rerrstr(response, sizeof response);
+ print("!%s\n", response);
+ return -1;
+ }
+ if(auth_chuid(ai, nil) < 0)
+ return -1;
+ return 0;
+}
+/*
+ * use the in the clear apop password to change user id
+ */
+int
+noworldlogin(char *user)
+{
+ char password[256];
+
+ prompt("password", password, sizeof(password), 1);
+ if(login(user, password, "/lib/namespace.noworld") < 0)
+ return -1;
+ rfork(RFNOMNT); /* sandbox */
+ return 0;
+}
+
+int
+doauth(char *user)
+{
+ if(*user == 0)
+ prompt("user", user, Maxuser, 0);
+ if(noworld(user))
+ return noworldlogin(user);
+ if(noworldonly)
+ return -1;
+ return challuser(user);
+
+}
+
+/*
+ * Process some input from the child, add protocol if needed. If
+ * the input buffer goes empty, return.
+ */
+int
+fromchild(char *bp, int len)
+{
+ int c;
+ char *start;
+
+ for(start = bp; bp-start < len-1; ){
+ c = Bgetc(&childib);
+ if(c < 0){
+ if(bp == start)
+ return -1;
+ else
+ break;
+ }
+ if(cons->raw == 0 && c == '\n')
+ *bp++ = '\r';
+ *bp++ = c;
+ if(Bbuffered(&childib) == 0)
+ break;
+ }
+ return bp-start;
+}
+
+/*
+ * Read from the network up to a '\n' or some other break.
+ *
+ * If in binary mode, buffer characters but don't
+ *
+ * The following characters are special:
+ * '\r\n's and '\r's get turned into '\n's.
+ * ^H erases the last character buffered.
+ * ^U kills the whole line buffered.
+ * ^W erases the last word
+ * ^D causes a 0-length line to be returned.
+ * Intr causes an "interrupt" note to be sent to the children.
+ */
+#define ECHO(c) { *ebp++ = (c); }
+int
+fromnet(char *bp, int len)
+{
+ int c;
+ char echobuf[1024];
+ char *ebp;
+ char *start;
+ static int crnl;
+ static int doeof;
+
+
+ /* simulate an EOF as a 0 length input */
+ if(doeof){
+ doeof = 0;
+ return 0;
+ }
+
+ for(ebp = echobuf,start = bp; bp-start < len && ebp-echobuf < sizeof(echobuf); ){
+ c = Bgetc(&netib);
+ if(c < 0){
+ if(bp == start)
+ return -1;
+ else
+ break;
+ }
+
+ /* telnet protocol only */
+ if(!noproto){
+ /* protocol messages */
+ switch(c){
+ case Iac:
+ crnl = 0;
+ c = Bgetc(&netib);
+ if(c == Iac)
+ break;
+ control(&netib, c);
+ continue;
+ }
+
+ }
+
+ /* \r\n or \n\r become \n */
+ if(c == '\r' || c == '\n'){
+ if(crnl && crnl != c){
+ crnl = 0;
+ continue;
+ }
+ if(cons->raw == 0 && opt[Echo].local){
+ ECHO('\r');
+ ECHO('\n');
+ }
+ crnl = c;
+ if(cons->raw == 0)
+ *bp++ = '\n';
+ else
+ *bp++ = c;
+ break;
+ } else
+ crnl = 0;
+
+ /* raw processing (each character terminates */
+ if(cons->raw){
+ *bp++ = c;
+ break;
+ }
+
+ /* in binary mode, there are no control characters */
+ if(opt[Binary].local){
+ if(opt[Echo].local)
+ ECHO(c);
+ *bp++ = c;
+ continue;
+ }
+
+ /* cooked processing */
+ switch(c){
+ case 0x00:
+ if(noproto) /* telnet ignores nulls */
+ *bp++ = c;
+ continue;
+ case 0x04:
+ if(bp != start)
+ doeof = 1;
+ goto out;
+
+ case 0x08: /* ^H */
+ if(start < bp)
+ bp--;
+ if(opt[Echo].local)
+ ECHO(c);
+ break;
+
+ case 0x15: /* ^U */
+ bp = start;
+ if(opt[Echo].local){
+ ECHO('^');
+ ECHO('U');
+ ECHO('\r');
+ ECHO('\n');
+ }
+ break;
+
+ case 0x17: /* ^W */
+ if (opt[Echo].local) {
+ while (--bp >= start && !alnum(*bp))
+ ECHO('\b');
+ while (bp >= start && alnum(*bp)) {
+ ECHO('\b');
+ bp--;
+ }
+ bp++;
+ }
+ break;
+
+ case 0x7f: /* Del */
+ write(notefd, "interrupt", 9);
+ bp = start;
+ break;
+
+ default:
+ if(opt[Echo].local)
+ ECHO(c);
+ *bp++ = c;
+ }
+ if(ebp != echobuf)
+ write(1, echobuf, ebp-echobuf);
+ ebp = echobuf;
+ }
+out:
+ if(ebp != echobuf)
+ write(1, echobuf, ebp-echobuf);
+ return bp - start;
+}
+
+int
+termchange(Biobuf *bp, int cmd)
+{
+ char buf[8];
+ char *p = buf;
+
+ if(cmd != Will)
+ return 0;
+
+ /* ask other side to send term type info */
+ *p++ = Iac;
+ *p++ = Sb;
+ *p++ = opt[Term].code;
+ *p++ = 1;
+ *p++ = Iac;
+ *p++ = Se;
+ return iwrite(Bfildes(bp), buf, p-buf);
+}
+
+int
+termsub(Biobuf *bp, uchar *sub, int n)
+{
+ char term[Maxvar];
+
+ USED(bp);
+ if(n-- < 1 || sub[0] != 0)
+ return 0;
+ if(n >= sizeof term)
+ n = sizeof term;
+ strncpy(term, (char*)sub, n);
+ putenv("TERM", term);
+ return 0;
+}
+
+int
+xlocchange(Biobuf *bp, int cmd)
+{
+ char buf[8];
+ char *p = buf;
+
+ if(cmd != Will)
+ return 0;
+
+ /* ask other side to send x display info */
+ *p++ = Iac;
+ *p++ = Sb;
+ *p++ = opt[Xloc].code;
+ *p++ = 1;
+ *p++ = Iac;
+ *p++ = Se;
+ return iwrite(Bfildes(bp), buf, p-buf);
+}
+
+int
+xlocsub(Biobuf *bp, uchar *sub, int n)
+{
+ char xloc[Maxvar];
+
+ USED(bp);
+ if(n-- < 1 || sub[0] != 0)
+ return 0;
+ if(n >= sizeof xloc)
+ n = sizeof xloc;
+ strncpy(xloc, (char*)sub, n);
+ putenv("DISPLAY", xloc);
+ return 0;
+}
+
+/*
+ * create a shared segment. Make is start 2 meg higher than the current
+ * end of process memory.
+ */
+void*
+share(ulong len)
+{
+ uchar *vastart;
+
+ vastart = sbrk(0);
+ if(vastart == (void*)-1)
+ return 0;
+ vastart += 2*1024*1024;
+
+ if(segattach(0, "shared", vastart, len) == (void*)-1)
+ return 0;
+
+ return vastart;
+}
+
+/*
+ * bind a pipe onto consctl and keep reading it to
+ * get changes to console state.
+ */
+int
+conssim(void)
+{
+ int i, n;
+ int fd;
+ int tries;
+ char buf[128];
+ char *field[10];
+
+ /* a pipe to simulate the /dev/cons */
+ if(bind("#|", "/mnt/cons/cons", MREPL) < 0)
+ fatal("/dev/cons1", 0, 0);
+ if(bind("/mnt/cons/cons/data1", "/dev/cons", MREPL) < 0)
+ fatal("/dev/cons2", 0, 0);
+
+ /* a pipe to simulate consctl */
+ if(bind("#|", "/mnt/cons/consctl", MBEFORE) < 0
+ || bind("/mnt/cons/consctl/data1", "/dev/consctl", MREPL) < 0)
+ fatal("/dev/consctl", 0, 0);
+
+ /* a process to read /dev/consctl and set the state in cons */
+ switch(fork()){
+ case -1:
+ fatal("forking", 0, 0);
+ case 0:
+ break;
+ default:
+ return open("/mnt/cons/cons/data", ORDWR);
+ }
+
+ for(tries = 0; tries < 100; tries++){
+ cons->raw = 0;
+ cons->hold = 0;
+ fd = open("/mnt/cons/consctl/data", OREAD);
+ if(fd < 0)
+ continue;
+ tries = 0;
+ for(;;){
+ n = read(fd, buf, sizeof(buf)-1);
+ if(n <= 0)
+ break;
+ buf[n] = 0;
+ n = getfields(buf, field, 10, 1, " ");
+ for(i = 0; i < n; i++){
+ if(strcmp(field[i], "rawon") == 0) {
+ if(debug) fprint(2, "raw = 1\n");
+ cons->raw = 1;
+ } else if(strcmp(field[i], "rawoff") == 0) {
+ if(debug) fprint(2, "raw = 0\n");
+ cons->raw = 0;
+ } else if(strcmp(field[i], "holdon") == 0) {
+ cons->hold = 1;
+ if(debug) fprint(2, "raw = 1\n");
+ } else if(strcmp(field[i], "holdoff") == 0) {
+ cons->hold = 0;
+ if(debug) fprint(2, "raw = 0\n");
+ }
+ }
+ }
+ close(fd);
+ }
+ exits(0);
+ return -1;
+}
+
+int
+alnum(int c)
+{
+ /*
+ * Hard to get absolutely right. Use what we know about ASCII
+ * and assume anything above the Latin control characters is
+ * potentially an alphanumeric.
+ */
+ if(c <= ' ')
+ return 0;
+ if(0x7F<=c && c<=0xA0)
+ return 0;
+ if(strchr("!\"#$%&'()*+,-./:;<=>?@`[\\]^{|}~", c))
+ return 0;
+ return 1;
+}