Subversion Repositories planix.SVN

Rev

Rev 2 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 - 1
#include <u.h>
2
#include <libc.h>
3
#include <bio.h>
4
#include "telnet.h"
5
 
6
int ctl = -1;		/* control fd (for break's) */
7
int consctl = -1;	/* consctl fd */
8
 
9
int ttypid;		/* pid's if the 2 processes (used to kill them) */
10
int netpid;
11
int interrupted;
12
int localecho;
13
int notkbd;
14
 
15
static char *srv;
16
 
17
typedef struct Comm Comm;
18
struct Comm {
19
	int returns;
20
	int stopped;
21
};
22
Comm *comm;
23
 
24
int	dodial(char*);
25
void	fromkbd(int);
26
void	fromnet(int);
27
int	menu(Biobuf*,  int);
28
void	notifyf(void*, char*);
29
void	rawoff(void);
30
void	rawon(void);
31
void	telnet(int);
32
char*	system(int, char*);
33
int	echochange(Biobuf*, int);
34
int	termsub(Biobuf*, uchar*, int);
35
int	xlocsub(Biobuf*, uchar*, int);
36
void*	share(ulong);
37
 
38
static int islikeatty(int);
39
 
40
void
41
usage(void)
42
{
43
	fatal("usage: telnet [-Cdnr] [-s srv] net!host[!service]", 0, 0);
44
}
45
 
46
void
47
main(int argc, char *argv[])
48
{
49
	int returns;
50
 
51
	returns = 1;
52
	ARGBEGIN{
53
	case 'C':
54
		opt[Echo].noway = 1;
55
		break;
56
	case 'd':
57
		debug = 1;
58
		break;
59
	case 'n':
60
		notkbd = 1;
61
		break; 
62
	case 'r':
63
		returns = 0;
64
		break;
65
	case 's':
66
		srv = EARGF(usage());
67
		break;
68
	default:
69
		usage();
70
	}ARGEND
71
 
72
	if(argc != 1)
73
		usage();
74
 
75
	/* options we need routines for */
76
	opt[Echo].change = echochange;
77
	opt[Term].sub = termsub;
78
	opt[Xloc].sub = xlocsub;
79
 
80
	comm = share(sizeof(comm));
81
	comm->returns = returns;
82
 
83
	telnet(dodial(argv[0]));
84
}
85
 
86
/*
87
 *  dial and return a data connection
88
 */
89
int
90
dodial(char *dest)
91
{
92
	char *name;
93
	int data;
94
	char devdir[NETPATHLEN];
95
 
96
	name = netmkaddr(dest, "tcp", "telnet");
97
	data = dial(name, 0, devdir, 0);
98
	if(data < 0)
99
		fatal("%s: %r", name, 0);
100
	fprint(2, "connected to %s on %s\n", name, devdir);
101
	return data;
102
}
103
 
104
void
105
post(char *srv, int fd)
106
{
107
	int f;
108
	char buf[32];
109
 
110
	f = create(srv, OWRITE, 0666);
111
	if(f < 0)
112
		sysfatal("create %s: %r", srv);
113
	snprint(buf, sizeof buf, "%d", fd);
114
	if(write(f, buf, strlen(buf)) != strlen(buf))
115
		sysfatal("write %s: %r", srv);
116
	close(f);
117
}
118
 
119
/*
120
 *  two processes pass bytes back and forth between the
121
 *  terminal and the network.
122
 */
123
void
124
telnet(int net)
125
{
126
	int pid;
127
	int p[2];
128
	char *svc;
129
 
130
	rawoff();
131
	svc = nil;
132
	if (srv) {
133
		if(pipe(p) < 0)
134
			sysfatal("pipe: %r");
135
		if (srv[0] != '/')
136
			svc = smprint("/srv/%s", srv);
137
		else
138
			svc = srv;
139
		post(svc, p[0]);
140
		close(p[0]);
141
		dup(p[1], 0);
142
		dup(p[1], 1);
143
		/* pipe is now std in & out */
144
	}
145
	ttypid = getpid();
146
	switch(pid = rfork(RFPROC|RFFDG|RFMEM)){
147
	case -1:
148
		perror("con");
149
		exits("fork");
150
	case 0:
151
		rawoff();
152
		notify(notifyf);
153
		fromnet(net);
154
		if (svc)
155
			remove(svc);
156
		sendnote(ttypid, "die");
157
		exits(0);
158
	default:
159
		netpid = pid;
160
		notify(notifyf);
161
		fromkbd(net);
162
		if(notkbd)
163
			for(;;)
164
				sleep(1000); // sleep(0) is a cpuhog
165
		if (svc)
166
			remove(svc);
167
		sendnote(netpid, "die");
168
		exits(0);
169
	}
170
}
171
 
172
/*
173
 *  Read the keyboard and write it to the network.  '^\' gets us into
174
 *  the menu.
175
 */
176
void
177
fromkbd(int net)
178
{
179
	Biobuf ib, ob;
180
	int c, likeatty;
181
	int eofs;
182
 
183
	Binit(&ib, 0, OREAD);
184
	Binit(&ob, net, OWRITE);
185
 
186
	likeatty = islikeatty(0);
187
	eofs = 0;
188
	for(;;){
189
		c = Bgetc(&ib);
190
 
191
		/*
192
		 *  with raw off, all ^D's get turned into Eof's.
193
		 *  change them back.
194
		 *  10 in a row implies that the terminal is really gone so
195
		 *  just hang up.
196
		 */
197
		if(c < 0){
198
			if(notkbd)
199
				return;
200
			if(eofs++ > 10)
201
				return;
202
			c = 004;
203
		} else
204
			eofs = 0;
205
 
206
		/*
207
		 *  if not in binary mode, look for the ^\ escape to menu.
208
		 *  also turn \n into \r\n
209
		 */
210
		if(likeatty || !opt[Binary].local){
211
			if(c == 0034){ /* CTRL \ */
212
				if(Bflush(&ob) < 0)
213
					return;
214
				if(menu(&ib, net) < 0)
215
					return;
216
				continue;
217
			}
218
		}
219
		if(!opt[Binary].local){
220
			if(c == '\n'){
221
				/*
222
				 *  This is a very strange use of the SGA option.
223
				 *  I did this because some systems that don't
224
				 *  announce a willingness to supress-go-ahead
225
				 *  need the \r\n sequence to recognize input.
226
				 *  If someone can explain this to me, please
227
				 *  send me mail. - presotto
228
				 */
229
				if(opt[SGA].remote){
230
					c = '\r';
231
				} else {
232
					if(Bputc(&ob, '\r') < 0)
233
						return;
234
				}
235
			}
236
		}
237
		if(Bputc(&ob, c) < 0)
238
			return;
239
		if(Bbuffered(&ib) == 0)
240
			if(Bflush(&ob) < 0)
241
				return;
242
	}
243
}
244
 
245
/*
246
 *  Read from the network and write to the screen.  If 'stopped' is set
247
 *  spin and don't read.  Filter out spurious carriage returns.
248
 */
249
void
250
fromnet(int net)
251
{
252
	int c;
253
	int crnls = 0, freenl = 0, eofs;
254
	Biobuf ib, ob;
255
 
256
	Binit(&ib, net, OREAD);
257
	Binit(&ob, 1, OWRITE);
258
	eofs = 0;
259
	for(;;){
260
		if(Bbuffered(&ib) == 0)
261
			Bflush(&ob);
262
		if(interrupted){
263
			interrupted = 0;
264
			send2(net, Iac, Interrupt);
265
		}
266
		c = Bgetc(&ib);
267
		if(c < 0){
268
			if(eofs++ >= 2)
269
				return;
270
			continue;
271
		}
272
		eofs = 0;
273
		switch(c){
274
		case '\n':	/* skip nl after string of cr's */
275
			if(!opt[Binary].local && !comm->returns){
276
				++crnls;
277
				if(freenl == 0)
278
					break;
279
				freenl = 0;
280
				continue;
281
			}
282
			break;
283
		case '\r':	/* first cr becomes nl, remainder dropped */
284
			if(!opt[Binary].local && !comm->returns){
285
				if(crnls++ == 0){
286
					freenl = 1;
287
					c = '\n';
288
					break;
289
				}
290
				continue;
291
			}
292
			break;
293
		case 0:		/* remove nulls from crnl string */
294
			if(crnls)
295
				continue;
296
			break;
297
 
298
		case Iac:
299
			crnls = 0;
300
			freenl = 0;
301
			c = Bgetc(&ib);
302
			if(c == Iac)
303
				break;
304
			if(Bflush(&ob) < 0)
305
				return;
306
			if(control(&ib, c) < 0)
307
				return;
308
			continue;
309
 
310
		default:
311
			crnls = 0;
312
			freenl = 0;
313
			break;
314
		}
315
		if(Bputc(&ob, c) < 0)
316
			return;
317
	}
318
}
319
 
320
/*
321
 *  turn keyboard raw mode on
322
 */
323
void
324
rawon(void)
325
{
326
	if(debug)
327
		fprint(2, "rawon\n");
328
	if(consctl < 0)
329
		consctl = open("/dev/consctl", OWRITE);
330
	if(consctl < 0){
331
		fprint(2, "%s: can't open consctl: %r\n", argv0);
332
		return;
333
	}
334
	write(consctl, "rawon", 5);
335
}
336
 
337
/*
338
 *  turn keyboard raw mode off
339
 */
340
void
341
rawoff(void)
342
{
343
	if(debug)
344
		fprint(2, "rawoff\n");
345
	if(consctl < 0)
346
		consctl = open("/dev/consctl", OWRITE);
347
	if(consctl < 0){
348
		fprint(2, "%s: can't open consctl: %r\n", argv0);
349
		return;
350
	}
351
	write(consctl, "rawoff", 6);
352
}
353
 
354
/*
355
 *  control menu
356
 */
357
#define STDHELP	"\t(b)reak, (i)nterrupt, (q)uit, (r)eturns, (!cmd), (.)continue\n"
358
 
359
int
360
menu(Biobuf *bp, int net)
361
{
362
	char *cp;
363
	int done;
364
 
365
	comm->stopped = 1;
366
 
367
	rawoff();
368
	fprint(2, ">>> ");
369
	for(done = 0; !done; ){
370
		cp = Brdline(bp, '\n');
371
		if(cp == 0){
372
			comm->stopped = 0;
373
			return -1;
374
		}
375
		cp[Blinelen(bp)-1] = 0;
376
		switch(*cp){
377
		case '!':
378
			system(Bfildes(bp), cp+1);
379
			done = 1;
380
			break;
381
		case '.':
382
			done = 1;
383
			break;
384
		case 'q':
385
			comm->stopped = 0;
386
			return -1;
387
		case 'o':
388
			switch(*(cp+1)){
389
			case 'd':
390
				send3(net, Iac, Do, atoi(cp+2));
391
				break;
392
			case 'w':
393
				send3(net, Iac, Will, atoi(cp+2));
394
				break;
395
			}
396
			break;
397
		case 'r':
398
			comm->returns = !comm->returns;
399
			done = 1;
400
			break;
401
		case 'i':
402
			send2(net, Iac, Interrupt);
403
			break;
404
		case 'b':
405
			send2(net, Iac, Break);
406
			break;
407
		default:
408
			fprint(2, STDHELP);
409
			break;
410
		}
411
		if(!done)
412
			fprint(2, ">>> ");
413
	}
414
 
415
	rawon();
416
	comm->stopped = 0;
417
	return 0;
418
}
419
 
420
/*
421
 *  ignore interrupts
422
 */
423
void
424
notifyf(void *a, char *msg)
425
{
426
	USED(a);
427
	if(strcmp(msg, "interrupt") == 0){
428
		interrupted = 1;
429
		noted(NCONT);
430
	}
431
	if(strcmp(msg, "hangup") == 0)
432
		noted(NCONT);
433
	noted(NDFLT);
434
}
435
 
436
/*
437
 *  run a command with the network connection as standard IO
438
 */
439
char *
440
system(int fd, char *cmd)
441
{
442
	int pid;
443
	int p;
444
	static Waitmsg msg;
445
 
446
	if((pid = fork()) == -1){
447
		perror("con");
448
		return "fork failed";
449
	}
450
	else if(pid == 0){
451
		dup(fd, 0);
452
		close(ctl);
453
		close(fd);
454
		if(*cmd)
455
			execl("/bin/rc", "rc", "-c", cmd, nil);
456
		else
457
			execl("/bin/rc", "rc", nil);
458
		perror("con");
459
		exits("exec");
460
	}
461
	for(p = waitpid(); p >= 0; p = waitpid()){
462
		if(p == pid)
463
			return msg.msg;	
464
	}
465
	return "lost child";
466
}
467
 
468
/*
469
 *  suppress local echo if the remote side is doing it
470
 */
471
int
472
echochange(Biobuf *bp, int cmd)
473
{
474
	USED(bp);
475
 
476
	switch(cmd){
477
	case Will:
478
		rawon();
479
		break;
480
	case Wont:
481
		rawoff();
482
		break;
483
	}
484
	return 0;
485
}
486
 
487
/*
488
 *  send terminal type to the other side
489
 */
490
int
491
termsub(Biobuf *bp, uchar *sub, int n)
492
{
493
	char buf[64];
494
	char *term;
495
	char *p = buf;
496
 
497
	if(n < 1)
498
		return 0;
499
	if(sub[0] == 1){
500
		*p++ = Iac;
501
		*p++ = Sb;
502
		*p++ = opt[Term].code;
503
		*p++ = 0;
504
		term = getenv("TERM");
505
		if(term == 0 || *term == 0)
506
			term = "p9win";
507
		strncpy(p, term, sizeof(buf) - (p - buf) - 2);
508
		buf[sizeof(buf)-2] = 0;
509
		p += strlen(p);
510
		*p++ = Iac;
511
		*p++ = Se;
512
		return iwrite(Bfildes(bp), buf, p-buf);
513
	}
514
	return 0;
515
}
516
 
517
/*
518
 *  send an x display location to the other side
519
 */
520
int
521
xlocsub(Biobuf *bp, uchar *sub, int n)
522
{
523
	char buf[64];
524
	char *term;
525
	char *p = buf;
526
 
527
	if(n < 1)
528
		return 0;
529
	if(sub[0] == 1){
530
		*p++ = Iac;
531
		*p++ = Sb;
532
		*p++ = opt[Xloc].code;
533
		*p++ = 0;
534
		term = getenv("XDISP");
535
		if(term == 0 || *term == 0)
536
			term = "unknown";
537
		strncpy(p, term, p - buf - 2);
538
		p += strlen(term);
539
		*p++ = Iac;
540
		*p++ = Se;
541
		return iwrite(Bfildes(bp), buf, p-buf);
542
	}
543
	return 0;
544
}
545
 
546
static int
547
islikeatty(int fd)
548
{
549
	char buf[64];
550
 
551
	if(fd2path(fd, buf, sizeof buf) != 0)
552
		return 0;
553
 
554
	/* might be /mnt/term/dev/cons */
555
	return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0;
556
}
557
 
558
/*
559
 *  create a shared segment.  Make is start 2 meg higher than the current
560
 *  end of process memory.
561
 */
562
void*
563
share(ulong len)
564
{
565
	uchar *vastart;
566
 
567
	vastart = sbrk(0);
568
	if(vastart == (void*)-1)
569
		return 0;
570
	vastart += 2*1024*1024;
571
 
572
	if(segattach(0, "shared", vastart, len) == (void*)-1)
573
		return 0;
574
 
575
	return vastart;
576
}