Subversion Repositories planix.SVN

Rev

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

Rev Author Line No. Line
2 - 1
/*
2
 * ssh - remote login via SSH v2
3
 *	/net/ssh does most of the work; we copy bytes back and forth
4
 */
5
#include <u.h>
6
#include <libc.h>
7
#include <auth.h>
8
#include "ssh2.h"
9
 
10
int doauth(int, char *);
11
int isatty(int);
12
 
13
char *user, *remote;
14
char *netdir = "/net";
15
int debug = 0;
16
 
17
static int stripcr = 0;
18
static int mflag = 0;
19
static int iflag = -1;
20
static int nopw = 0, nopka = 0;
21
static int chpid;
22
static int reqfd, dfd1, cfd1, dfd2, cfd2, consfd, kconsfd, cctlfd, notefd, keyfd;
23
 
24
void
25
usage(void)
26
{
27
	fprint(2, "usage: %s [-dkKmr] [-l user] [-n dir] [-z attr=val] addr "
28
		"[cmd [args]]\n", argv0);
29
	exits("usage");
30
}
31
 
32
/*
33
 * this is probably overkill except writing "kill" to notefd;
34
 * file descriptors are closed by the kernel upon exit.
35
 */
36
static void
37
shutdown(void)
38
{
39
	if (cctlfd > 0) {
40
		fprint(cctlfd, "rawoff");
41
		close(cctlfd);
42
	}
43
	if (consfd > 0)
44
		close(consfd);
45
	if (reqfd > 0) {
46
		fprint(reqfd, "close");
47
		close(reqfd);
48
	}
49
	close(dfd2);
50
	close(dfd1);
51
	close(cfd2);
52
	close(cfd1);
53
 
54
	fprint(notefd, "kill");
55
	close(notefd);
56
}
57
 
58
static void
59
bail(char *sts)
60
{
61
	shutdown();
62
	exits(sts);
63
}
64
 
65
int
66
handler(void *, char *s)
67
{
68
	char *nf;
69
	int fd;
70
 
71
	if (strstr(s, "alarm") != nil)
72
		return 0;
73
	if (chpid) {
74
		nf = esmprint("/proc/%d/note", chpid);
75
		fd = open(nf, OWRITE);
76
		fprint(fd, "interrupt");
77
		close(fd);
78
		free(nf);
79
	}
80
	shutdown();
81
	return 1;
82
}
83
 
84
static void
85
parseargs(void)
86
{
87
	int n;
88
	char *p, *q;
89
 
90
	q = strchr(remote, '@');
91
	if (q != nil) {
92
		user = remote;
93
		*q++ = 0;
94
		remote = q;
95
	}
96
 
97
	q = strchr(remote, '!');
98
	if (q) {
99
		n = q - remote;
100
		netdir = malloc(n+1);
101
		if (netdir == nil)
102
			sysfatal("out of memory");
103
		strncpy(netdir, remote, n+1);
104
		netdir[n] = '\0';
105
 
106
		p = strrchr(netdir, '/');
107
		if (p == nil) {
108
			free(netdir);
109
			netdir = "/net";
110
		} else if (strcmp(p+1, "ssh") == 0)
111
			*p = '\0';
112
		else
113
			remote = esmprint("%s/ssh", netdir);
114
	}
115
 
116
}
117
 
118
static int
119
catcher(void *, char *s)
120
{
121
	return strstr(s, "alarm") != nil;
122
}
123
 
124
static int
125
timedmount(int fd, int afd, char *mntpt, int flag, char *aname)
126
{
127
	int oalarm, ret;
128
 
129
	atnotify(catcher, 1);
130
	oalarm = alarm(5*1000);		/* don't get stuck here */
131
	ret = mount(fd, afd, mntpt, flag, aname);
132
	alarm(oalarm);
133
	atnotify(catcher, 0);
134
	return ret;
135
}
136
 
137
static void
138
mounttunnel(char *srv)
139
{
140
	int fd;
141
 
142
	if (debug)
143
		fprint(2, "%s: mounting %s on /net\n", argv0, srv);
144
	fd = open(srv, OREAD);
145
	if (fd < 0) {
146
		if (debug)
147
			fprint(2, "%s: can't open %s: %r\n", argv0, srv);
148
	} else if (timedmount(fd, -1, netdir, MBEFORE, "") < 0) {
149
		fprint(2, "can't mount %s on %s: %r\n", srv, netdir);
150
		close(fd);
151
	}
152
}
153
 
154
static void
155
newtunnel(char *myname)
156
{
157
	int kid, pid;
158
 
159
	if(debug)
160
		fprint(2, "%s: starting new netssh for key access\n", argv0);
161
	kid = rfork(RFPROC|RFNOTEG|RFENVG /* |RFFDG */);
162
	if (kid == 0) {
163
//		for (fd = 3; fd < 40; fd++)
164
//			close(fd);
165
		execl("/bin/netssh", "netssh", "-m", netdir, "-s", myname, nil);
166
		sysfatal("no /bin/netssh: %r");
167
	} else if (kid < 0)
168
		sysfatal("fork failed: %r");
169
	while ((pid = waitpid()) != kid && pid >= 0)
170
		;
171
}
172
 
173
static void
174
starttunnel(void)
175
{
176
	char *keys, *mysrv, *myname;
177
 
178
	keys = esmprint("%s/ssh/keys", netdir);
179
	myname = esmprint("ssh.%s", getuser());
180
	mysrv = esmprint("/srv/%s", myname);
181
 
182
	if (access(keys, ORDWR) < 0)
183
		mounttunnel("/srv/netssh");		/* old name */
184
	if (access(keys, ORDWR) < 0)
185
		mounttunnel("/srv/ssh");
186
	if (access(keys, ORDWR) < 0)
187
		mounttunnel(mysrv);
188
	if (access(keys, ORDWR) < 0)
189
		newtunnel(myname);
190
	if (access(keys, ORDWR) < 0)
191
		mounttunnel(mysrv);
192
 
193
	/* if we *still* can't see our own tunnel, throw a tantrum. */
194
	if (access(keys, ORDWR) < 0)
195
		sysfatal("%s inaccessible: %r", keys);		/* WTF? */
196
 
197
	free(myname);
198
	free(mysrv);
199
	free(keys);
200
}
201
 
202
int
203
cmdmode(void)
204
{
205
	int n, m;
206
	char buf[Arbbufsz];
207
 
208
	for(;;) {
209
reprompt:
210
		print("\n>>> ");
211
		n = 0;
212
		do {
213
			m = read(0, buf + n, sizeof buf - n - 1);
214
			if (m <= 0)
215
				return 1;
216
			write(1, buf + n, m);
217
			n += m;
218
			buf[n] = '\0';
219
			if (buf[n-1] == ('u' & 037))
220
				goto reprompt;
221
		} while (buf[n-1] != '\n' && buf[n-1] != '\r');
222
		switch (buf[0]) {
223
		case '\n':
224
		case '\r':
225
			break;
226
		case 'q':
227
			return 1;
228
		case 'c':
229
			return 0;
230
		case 'r':
231
			stripcr = !stripcr;
232
			return 0;
233
		case 'h':
234
			print("c - continue\n");
235
			print("h - help\n");
236
			print("q - quit\n");
237
			print("r - toggle carriage return stripping\n");
238
			break;
239
		default:
240
			print("unknown command\n");
241
			break;
242
		}
243
	}
244
}
245
 
246
static void
247
keyprompt(char *buf, int size, int n)
248
{
249
	if (*buf == 'c') {
250
		fprint(kconsfd, "The following key has been offered by the server:\n");
251
		write(kconsfd, buf+5, n);
252
		fprint(kconsfd, "\n\n");
253
		fprint(kconsfd, "Add this key? (yes, no, session) ");
254
	} else {
255
		fprint(kconsfd, "The following key does NOT match the known "
256
			"key(s) for the server:\n");
257
		write(kconsfd, buf+5, n);
258
		fprint(kconsfd, "\n\n");
259
		fprint(kconsfd, "Add this key? (yes, no, session, replace) ");
260
	}
261
	n = read(kconsfd, buf, size - 1);
262
	if (n <= 0)
263
		return;
264
	write(keyfd, buf, n);		/* user's response -> /net/ssh/keys */
265
	seek(keyfd, 0, 2);
266
	if (readn(keyfd, buf, 5) <= 0)
267
		return;
268
	buf[5] = 0;
269
	n = strtol(buf+1, nil, 10);
270
	n = readn(keyfd, buf+5, n);
271
	if (n <= 0)
272
		return;
273
	buf[n+5] = 0;
274
 
275
	switch (*buf) {
276
	case 'b':
277
	case 'f':
278
		fprint(kconsfd, "%s\n", buf+5);
279
	case 'o':
280
		close(keyfd);
281
		close(kconsfd);
282
	}
283
}
284
 
285
/* talk the undocumented /net/ssh/keys protocol */
286
static void
287
keyproc(char *buf, int size)
288
{
289
	int n;
290
	char *p;
291
 
292
	if (size < 6)
293
		exits("keyproc buffer too small");
294
	p = esmprint("%s/ssh/keys", netdir);
295
	keyfd = open(p, ORDWR);
296
	if (keyfd < 0) {
297
		chpid = 0;
298
		sysfatal("failed to open ssh keys in %s: %r", p);
299
	}
300
 
301
	kconsfd = open("/dev/cons", ORDWR);
302
	if (kconsfd < 0)
303
		nopw = 1;
304
 
305
	buf[0] = 0;
306
	n = read(keyfd, buf, 5);		/* reading /net/ssh/keys */
307
	if (n < 0)
308
		sysfatal("%s read: %r", p);
309
	buf[5] = 0;
310
	n = strtol(buf+1, nil, 10);
311
	n = readn(keyfd, buf+5, n);
312
	buf[n < 0? 5: n+5] = 0;
313
	free(p);
314
 
315
	switch (*buf) {
316
	case 'f':
317
		if (kconsfd >= 0)
318
			fprint(kconsfd, "%s\n", buf+5);
319
		/* fall through */
320
	case 'o':
321
		close(keyfd);
322
		if (kconsfd >= 0)
323
			close(kconsfd);
324
		break;
325
	default:
326
		if (kconsfd >= 0)
327
			keyprompt(buf, size, n);
328
		else {
329
			fprint(keyfd, "n");
330
			close(keyfd);
331
		}
332
		break;
333
	}
334
	chpid = 0;
335
	exits(nil);
336
}
337
 
338
/*
339
 * start a subproc to copy from network to stdout
340
 * while we copy from stdin to network.
341
 */
342
static void
343
bidircopy(char *buf, int size)
344
{
345
	int i, n, lstart;
346
	char *path, *p, *q;
347
 
348
	rfork(RFNOTEG);
349
	path = esmprint("/proc/%d/notepg", getpid());
350
	notefd = open(path, OWRITE);
351
 
352
	switch (rfork(RFPROC|RFMEM|RFNOWAIT)) {
353
	case 0:
354
		while ((n = read(dfd2, buf, size - 1)) > 0) {
355
			if (!stripcr)
356
				p = buf + n;
357
			else
358
				for (i = 0, p = buf, q = buf; i < n; ++i, ++q)
359
					if (*q != '\r')
360
						*p++ = *q;
361
			if (p != buf)
362
				write(1, buf, p-buf);
363
		}
364
		/*
365
		 * don't bother; it will be obvious when the user's prompt
366
		 * changes.
367
		 *
368
		 * fprint(2, "%s: Connection closed by server\n", argv0);
369
		 */
370
		break;
371
	default:
372
		lstart = 1;
373
		while ((n = read(0, buf, size - 1)) > 0) {
374
			if (!mflag && lstart && buf[0] == 0x1c)
375
				if (cmdmode())
376
					break;
377
				else
378
					continue;
379
			lstart = (buf[n-1] == '\n' || buf[n-1] == '\r');
380
			write(dfd2, buf, n);
381
		}
382
		/*
383
		 * don't bother; it will be obvious when the user's prompt
384
		 * changes.
385
		 *
386
		 * fprint(2, "%s: EOF on client side\n", argv0);
387
		 */
388
		break;
389
	case -1:
390
		fprint(2, "%s: fork error: %r\n", argv0);
391
		break;
392
	}
393
 
394
	bail(nil);
395
}
396
 
397
static int
398
connect(char *buf, int size)
399
{
400
	int nfd, n;
401
	char *dir, *ds, *nf;
402
 
403
	dir = esmprint("%s/ssh", netdir);
404
	ds = netmkaddr(remote, dir, "22");		/* tcp port 22 is ssh */
405
	free(dir);
406
 
407
	dfd1 = dial(ds, nil, nil, &cfd1);
408
	if (dfd1 < 0) {
409
		fprint(2, "%s: dial conn %s: %r\n", argv0, ds);
410
		if (chpid) {
411
			nf = esmprint("/proc/%d/note", chpid);
412
			nfd = open(nf, OWRITE);
413
			fprint(nfd, "interrupt");
414
			close(nfd);
415
		}
416
		exits("can't dial");
417
	}
418
 
419
	seek(cfd1, 0, 0);
420
	n = read(cfd1, buf, size - 1);
421
	buf[n >= 0? n: 0] = 0;
422
	return atoi(buf);
423
}
424
 
425
static int
426
chanconnect(int conn, char *buf, int size)
427
{
428
	int n;
429
	char *path;
430
 
431
	path = esmprint("%s/ssh/%d!session", netdir, conn);
432
	dfd2 = dial(path, nil, nil, &cfd2);
433
	if (dfd2 < 0) {
434
		fprint(2, "%s: dial chan %s: %r\n", argv0, path);
435
		bail("dial");
436
	}
437
	free(path);
438
 
439
	n = read(cfd2, buf, size - 1);
440
	buf[n >= 0? n: 0] = 0;
441
	return atoi(buf);
442
}
443
 
444
static void
445
remotecmd(int argc, char *argv[], int conn, int chan, char *buf, int size)
446
{
447
	int i;
448
	char *path, *q, *ep;
449
 
450
	path = esmprint("%s/ssh/%d/%d/request", netdir, conn, chan);
451
	reqfd = open(path, OWRITE);
452
	if (reqfd < 0)
453
		bail("can't open request chan");
454
	if (argc == 0)
455
		if (readfile("/env/TERM", buf, size) < 0)
456
			fprint(reqfd, "shell");
457
		else
458
			fprint(reqfd, "shell %s", buf);
459
	else {
460
		assert(size >= Bigbufsz);
461
		ep = buf + Bigbufsz;
462
		q = seprint(buf, ep, "exec");
463
		for (i = 0; i < argc; ++i)
464
			q = seprint(q, ep, " %q", argv[i]);
465
		if (q >= ep) {
466
			fprint(2, "%s: command too long\n", argv0);
467
			fprint(reqfd, "close");
468
			bail("cmd too long");
469
		}
470
		write(reqfd, buf, q - buf);
471
	}
472
}
473
 
474
void
475
main(int argc, char *argv[])
476
{
477
	char *whichkey;
478
	int conn, chan, n;
479
	char buf[Copybufsz];
480
 
481
	quotefmtinstall();
482
	reqfd = dfd1 = cfd1 = dfd2 = cfd2 = consfd = kconsfd = cctlfd =
483
		notefd = keyfd = -1;
484
	whichkey = nil;
485
	ARGBEGIN {
486
	case 'A':			/* auth protos */
487
	case 'c':			/* ciphers */
488
		fprint(2, "%s: sorry, -%c is not supported\n", argv0, ARGC());
489
		break;
490
	case 'a':			/* compat? */
491
	case 'C':			/* cooked mode */
492
	case 'f':			/* agent forwarding */
493
	case 'p':			/* force pty */
494
	case 'P':			/* force no pty */
495
	case 'R':			/* force raw mode on pty */
496
	case 'v':			/* scp compat */
497
	case 'w':			/* send window-size changes */
498
	case 'x':			/* unix compat: no x11 forwarding */
499
		break;
500
	case 'd':
501
		debug++;
502
		break;
503
	case 'I':			/* non-interactive */
504
		iflag = 0;
505
		break;
506
	case 'i':			/* interactive: scp & rx do it */
507
		iflag = 1;
508
		break;
509
	case 'l':
510
	case 'u':
511
		user = EARGF(usage());
512
		break;
513
	case 'k':
514
		nopka = 1;
515
		break;
516
	case 'K':
517
		nopw = 1;
518
		break;
519
	case 'm':
520
		mflag = 1;
521
		break;
522
	case 'n':
523
		netdir = EARGF(usage());
524
		break;
525
	case 'r':
526
		stripcr = 1;
527
		break;
528
	case 'z':
529
		whichkey = EARGF(usage());
530
		break;
531
	default:
532
		usage();
533
	} ARGEND;
534
	if (argc == 0)
535
		usage();
536
 
537
	if (iflag == -1)
538
		iflag = isatty(0);
539
	remote = *argv++;
540
	--argc;
541
 
542
	parseargs();
543
 
544
	if (!user)
545
		user = getuser();
546
	if (user == nil || remote == nil)
547
		sysfatal("out of memory");
548
 
549
	starttunnel();
550
 
551
	/* fork subproc to handle keys; don't wait for it */
552
	if ((n = rfork(RFPROC|RFMEM|RFFDG|RFNOWAIT)) == 0)
553
		keyproc(buf, sizeof buf);
554
	chpid = n;
555
	atnotify(handler, 1);
556
 
557
	/* connect and learn connection number */
558
	conn = connect(buf, sizeof buf);
559
 
560
	consfd = open("/dev/cons", ORDWR);
561
	cctlfd = open("/dev/consctl", OWRITE);
562
	fprint(cctlfd, "rawon");
563
	if (doauth(cfd1, whichkey) < 0)
564
		bail("doauth");
565
 
566
	/* connect a channel of conn and learn channel number */
567
	chan = chanconnect(conn, buf, sizeof buf);
568
 
569
	/* open request channel, request shell or command execution */
570
	remotecmd(argc, argv, conn, chan, buf, sizeof buf);
571
 
572
	bidircopy(buf, sizeof buf);
573
}
574
 
575
int
576
isatty(int fd)
577
{
578
	char buf[64];
579
 
580
	buf[0] = '\0';
581
	fd2path(fd, buf, sizeof buf);
582
	return strlen(buf) >= 9 && strcmp(buf+strlen(buf)-9, "/dev/cons") == 0;
583
}
584
 
585
int
586
doauth(int cfd1, char *whichkey)
587
{
588
	UserPasswd *up;
589
	int n;
590
	char path[Arbpathlen];
591
 
592
 	if (!nopka) {
593
		if (whichkey)
594
			n = fprint(cfd1, "ssh-userauth K %q %q", user, whichkey);
595
		else
596
			n = fprint(cfd1, "ssh-userauth K %q", user);
597
		if (n >= 0)
598
			return 0;
599
	}
600
	if (nopw)
601
		return -1;
602
	up = auth_getuserpasswd(iflag? auth_getkey: nil,
603
		"proto=pass service=ssh server=%q user=%q", remote, user);
604
	if (up == nil) {
605
		fprint(2, "%s: didn't get password: %r\n", argv0);
606
		return -1;
607
	}
608
	n = fprint(cfd1, "ssh-userauth k %q %q", user, up->passwd);
609
	if (n >= 0)
610
		return 0;
611
 
612
	path[0] = '\0';
613
	fd2path(cfd1, path, sizeof path);
614
	fprint(2, "%s: auth ctl msg `ssh-userauth k %q <password>' for %q: %r\n",
615
		argv0, user, path);
616
	return -1;
617
}