svr-chansession.c 27.2 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/*
 * Dropbear - a SSH2 server
 * 
 * Copyright (c) 2002,2003 Matt Johnston
 * All rights reserved.
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE. */

#include "includes.h"
#include "packet.h"
#include "buffer.h"
#include "session.h"
#include "dbutil.h"
#include "channel.h"
#include "chansession.h"
#include "sshpty.h"
#include "termcodes.h"
#include "ssh.h"
35
#include "dbrandom.h"
36
37
#include "x11fwd.h"
#include "agentfwd.h"
Matt Johnston's avatar
Matt Johnston committed
38
#include "runopts.h"
39
#include "auth.h"
40
41
42
43
44
45
46
47
48
49

/* Handles sessions (either shells or programs) requested by the client */

static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
		int iscmd, int issubsys);
static int sessionpty(struct ChanSess * chansess);
static int sessionsignal(struct ChanSess *chansess);
static int noptycommand(struct Channel *channel, struct ChanSess *chansess);
static int ptycommand(struct Channel *channel, struct ChanSess *chansess);
static int sessionwinchange(struct ChanSess *chansess);
50
static void execchild(void *user_data_chansess);
51
52
53
static void addchildpid(struct ChanSess *chansess, pid_t pid);
static void sesssigchild_handler(int val);
static void closechansess(struct Channel *channel);
54
static int newchansess(struct Channel *channel);
55
static void chansessionrequest(struct Channel *channel);
56
static int sesscheckclose(struct Channel *channel);
57
58

static void send_exitsignalstatus(struct Channel *channel);
59
60
61
62
static void send_msg_chansess_exitstatus(struct Channel * channel,
		struct ChanSess * chansess);
static void send_msg_chansess_exitsignal(struct Channel * channel,
		struct ChanSess * chansess);
63
static void get_termmodes(struct ChanSess *chansess);
64

65
66
67
68
69
70
71
72
const struct ChanType svrchansess = {
	0, /* sepfds */
	"session", /* name */
	newchansess, /* inithandler */
	sesscheckclose, /* checkclosehandler */
	chansessionrequest, /* reqhandler */
	closechansess, /* closehandler */
};
73
74
75
76
77

/* required to clear environment */
extern char** environ;

static int sesscheckclose(struct Channel *channel) {
78
	struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
79
	TRACE(("sesscheckclose, pid is %d", chansess->exit.exitpid))
Matt Johnston's avatar
Matt Johnston committed
80
	return chansess->exit.exitpid != -1;
81
82
}

83
84
85
86
87
88
89
90
/* Handler for childs exiting, store the state for return to the client */

/* There's a particular race we have to watch out for: if the forked child
 * executes, exits, and this signal-handler is called, all before the parent
 * gets to run, then the childpids[] array won't have the pid in it. Hence we
 * use the svr_ses.lastexit struct to hold the exit, which is then compared by
 * the parent when it runs. This work correctly at least in the case of a
 * single shell spawned (ie the usual case) */
91
static void sesssigchild_handler(int UNUSED(dummy)) {
92
93
94
95
96

	int status;
	pid_t pid;
	unsigned int i;
	struct sigaction sa_chld;
97
	struct exitinfo *exit = NULL;
98

99
100
	const int saved_errno = errno;

101
102
103
	/* Make channel handling code look for closed channels */
	ses.channel_signal_pending = 1;

104
	TRACE(("enter sigchld handler"))
105
	while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
106
		TRACE(("sigchld handler: pid %d", pid))
107
108

		exit = NULL;
109
110
111
		/* find the corresponding chansess */
		for (i = 0; i < svr_ses.childpidsize; i++) {
			if (svr_ses.childpids[i].pid == pid) {
112
				TRACE(("found match session"));
113
114
115
116
117
118
119
				exit = &svr_ses.childpids[i].chansess->exit;
				break;
			}
		}

		/* If the pid wasn't matched, then we might have hit the race mentioned
		 * above. So we just store the info for the parent to deal with */
120
		if (exit == NULL) {
121
			TRACE(("using lastexit"));
122
123
124
125
126
127
128
129
130
			exit = &svr_ses.lastexit;
		}

		exit->exitpid = pid;
		if (WIFEXITED(status)) {
			exit->exitstatus = WEXITSTATUS(status);
		}
		if (WIFSIGNALED(status)) {
			exit->exitsignal = WTERMSIG(status);
Matt Johnston's avatar
Matt Johnston committed
131
#if !defined(AIX) && defined(WCOREDUMP)
132
			exit->exitcore = WCOREDUMP(status);
133
#else
134
			exit->exitcore = 0;
135
#endif
136
137
138
		} else {
			/* we use this to determine how pid exited */
			exit->exitsignal = -1;
139
		}
140
141
142
		
		/* Make sure that the main select() loop wakes up */
		while (1) {
143
144
145
146
			/* isserver is just a random byte to write. We can't do anything
			about an error so should just ignore it */
			if (write(ses.signal_pipe[1], &ses.isserver, 1) == 1
					|| errno != EINTR) {
147
148
149
				break;
			}
		}
150
	}
151

152
153
	sa_chld.sa_handler = sesssigchild_handler;
	sa_chld.sa_flags = SA_NOCLDSTOP;
Matt Johnston's avatar
Matt Johnston committed
154
	sigemptyset(&sa_chld.sa_mask);
155
	sigaction(SIGCHLD, &sa_chld, NULL);
156
	TRACE(("leave sigchld handler"))
157
158

	errno = saved_errno;
159
160
161
162
163
164
165
}

/* send the exit status or the signal causing termination for a session */
static void send_exitsignalstatus(struct Channel *channel) {

	struct ChanSess *chansess = (struct ChanSess*)channel->typedata;

166
167
	if (chansess->exit.exitpid >= 0) {
		if (chansess->exit.exitsignal > 0) {
168
169
170
171
172
173
174
175
176
177
178
			send_msg_chansess_exitsignal(channel, chansess);
		} else {
			send_msg_chansess_exitstatus(channel, chansess);
		}
	}
}

/* send the exitstatus to the client */
static void send_msg_chansess_exitstatus(struct Channel * channel,
		struct ChanSess * chansess) {

179
180
	dropbear_assert(chansess->exit.exitpid != -1);
	dropbear_assert(chansess->exit.exitsignal == -1);
181
182
183
184
185

	CHECKCLEARTOWRITE();

	buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
	buf_putint(ses.writepayload, channel->remotechan);
186
	buf_putstring(ses.writepayload, "exit-status", 11);
187
	buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
188
	buf_putint(ses.writepayload, chansess->exit.exitstatus);
189
190
191
192
193
194
195
196
197
198
199

	encrypt_packet();

}

/* send the signal causing the exit to the client */
static void send_msg_chansess_exitsignal(struct Channel * channel,
		struct ChanSess * chansess) {

	int i;
	char* signame = NULL;
200
201
	dropbear_assert(chansess->exit.exitpid != -1);
	dropbear_assert(chansess->exit.exitsignal > 0);
202

203
204
	TRACE(("send_msg_chansess_exitsignal %d", chansess->exit.exitsignal))

205
206
207
208
209
	CHECKCLEARTOWRITE();

	/* we check that we can match a signal name, otherwise
	 * don't send anything */
	for (i = 0; signames[i].name != NULL; i++) {
210
		if (signames[i].signal == chansess->exit.exitsignal) {
211
212
213
214
215
216
217
218
219
220
221
			signame = signames[i].name;
			break;
		}
	}

	if (signame == NULL) {
		return;
	}

	buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
	buf_putint(ses.writepayload, channel->remotechan);
222
	buf_putstring(ses.writepayload, "exit-signal", 11);
223
	buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
224
	buf_putstring(ses.writepayload, signame, strlen(signame));
225
	buf_putbyte(ses.writepayload, chansess->exit.exitcore);
226
227
	buf_putstring(ses.writepayload, "", 0); /* error msg */
	buf_putstring(ses.writepayload, "", 0); /* lang */
228
229
230
231
232

	encrypt_packet();
}

/* set up a session channel */
233
static int newchansess(struct Channel *channel) {
234
235
236

	struct ChanSess *chansess;

237
	TRACE(("new chansess %p", (void*)channel))
238

239
	dropbear_assert(channel->typedata == NULL);
240
241
242

	chansess = (struct ChanSess*)m_malloc(sizeof(struct ChanSess));
	chansess->cmd = NULL;
Matt Johnston's avatar
Matt Johnston committed
243
	chansess->connection_string = NULL;
244
	chansess->client_string = NULL;
245
246
247
248
249
250
251
252
	chansess->pid = 0;

	/* pty details */
	chansess->master = -1;
	chansess->slave = -1;
	chansess->tty = NULL;
	chansess->term = NULL;

253
	chansess->exit.exitpid = -1;
254
255
256

	channel->typedata = chansess;

257
#if DROPBEAR_X11FWD
258
	chansess->x11listener = NULL;
259
260
261
262
	chansess->x11authprot = NULL;
	chansess->x11authcookie = NULL;
#endif

263
#if DROPBEAR_SVR_AGENTFWD
264
	chansess->agentlistener = NULL;
265
266
267
268
	chansess->agentfile = NULL;
	chansess->agentdir = NULL;
#endif

Matt Johnston's avatar
Matt Johnston committed
269
270
	channel->prio = DROPBEAR_CHANNEL_PRIO_INTERACTIVE;

271
272
	return 0;

273
274
}

Matt Johnston's avatar
Matt Johnston committed
275
276
277
278
static struct logininfo* 
chansess_login_alloc(struct ChanSess *chansess) {
	struct logininfo * li;
	li = login_alloc_entry(chansess->pid, ses.authstate.username,
279
			svr_ses.remotehost, chansess->tty);
Matt Johnston's avatar
Matt Johnston committed
280
281
282
	return li;
}

283
284
285
286
287
288
289
/* clean a session channel */
static void closechansess(struct Channel *channel) {

	struct ChanSess *chansess;
	unsigned int i;
	struct logininfo *li;

290
	TRACE(("enter closechansess"))
291

292
	chansess = (struct ChanSess*)channel->typedata;
293
294

	if (chansess == NULL) {
295
		TRACE(("leave closechansess: chansess == NULL"))
296
297
298
		return;
	}

299
300
	send_exitsignalstatus(channel);

301
302
303
	m_free(chansess->cmd);
	m_free(chansess->term);

304
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
305
306
307
	m_free(chansess->original_command);
#endif

308
309
	if (chansess->tty) {
		/* write the utmp/wtmp login record */
Matt Johnston's avatar
Matt Johnston committed
310
		li = chansess_login_alloc(chansess);
311
312
313
314
315
316
317
		login_logout(li);
		login_free_entry(li);

		pty_release(chansess->tty);
		m_free(chansess->tty);
	}

318
#if DROPBEAR_X11FWD
319
320
321
	x11cleanup(chansess);
#endif

322
#if DROPBEAR_SVR_AGENTFWD
Matt Johnston's avatar
Matt Johnston committed
323
	svr_agentcleanup(chansess);
324
325
326
327
328
#endif

	/* clear child pid entries */
	for (i = 0; i < svr_ses.childpidsize; i++) {
		if (svr_ses.childpids[i].chansess == chansess) {
329
			dropbear_assert(svr_ses.childpids[i].pid > 0);
330
			TRACE(("closing pid %d", svr_ses.childpids[i].pid))
331
			TRACE(("exitpid is %d", chansess->exit.exitpid))
332
333
334
335
336
337
338
			svr_ses.childpids[i].pid = -1;
			svr_ses.childpids[i].chansess = NULL;
		}
	}
				
	m_free(chansess);

339
	TRACE(("leave closechansess"))
340
341
342
343
344
345
}

/* Handle requests for a channel. These can be execution requests,
 * or x11/authagent forwarding. These are passed to appropriate handlers */
static void chansessionrequest(struct Channel *channel) {

346
	char * type = NULL;
347
348
349
350
351
	unsigned int typelen;
	unsigned char wantreply;
	int ret = 1;
	struct ChanSess *chansess;

352
	TRACE(("enter chansessionrequest"))
353

354
	type = buf_getstring(ses.payload, &typelen);
355
	wantreply = buf_getbool(ses.payload);
356
357

	if (typelen > MAX_NAME_LEN) {
358
		TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/
359
360
361
362
		goto out;
	}

	chansess = (struct ChanSess*)channel->typedata;
363
	dropbear_assert(chansess != NULL);
364
	TRACE(("type is %s", type))
365
366
367
368
369
370
371
372
373
374
375

	if (strcmp(type, "window-change") == 0) {
		ret = sessionwinchange(chansess);
	} else if (strcmp(type, "shell") == 0) {
		ret = sessioncommand(channel, chansess, 0, 0);
	} else if (strcmp(type, "pty-req") == 0) {
		ret = sessionpty(chansess);
	} else if (strcmp(type, "exec") == 0) {
		ret = sessioncommand(channel, chansess, 1, 0);
	} else if (strcmp(type, "subsystem") == 0) {
		ret = sessioncommand(channel, chansess, 1, 1);
376
#if DROPBEAR_X11FWD
377
378
379
	} else if (strcmp(type, "x11-req") == 0) {
		ret = x11req(chansess);
#endif
380
#if DROPBEAR_SVR_AGENTFWD
381
	} else if (strcmp(type, "[email protected]") == 0) {
Matt Johnston's avatar
Matt Johnston committed
382
		ret = svr_agentreq(chansess);
383
384
385
386
387
388
389
390
391
392
#endif
	} else if (strcmp(type, "signal") == 0) {
		ret = sessionsignal(chansess);
	} else {
		/* etc, todo "env", "subsystem" */
	}

out:

	if (wantreply) {
393
		if (ret == DROPBEAR_SUCCESS) {
394
395
396
397
398
399
400
			send_msg_channel_success(channel);
		} else {
			send_msg_channel_failure(channel);
		}
	}

	m_free(type);
401
	TRACE(("leave chansessionrequest"))
402
403
404
405
406
407
408
}


/* Send a signal to a session's process as requested by the client*/
static int sessionsignal(struct ChanSess *chansess) {

	int sig = 0;
409
	char* signame = NULL;
410
411
412
413
414
415
416
	int i;

	if (chansess->pid == 0) {
		/* haven't got a process pid yet */
		return DROPBEAR_FAILURE;
	}

417
	signame = buf_getstring(ses.payload, NULL);
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445

	i = 0;
	while (signames[i].name != 0) {
		if (strcmp(signames[i].name, signame) == 0) {
			sig = signames[i].signal;
			break;
		}
		i++;
	}

	m_free(signame);

	if (sig == 0) {
		/* failed */
		return DROPBEAR_FAILURE;
	}
			
	if (kill(chansess->pid, sig) < 0) {
		return DROPBEAR_FAILURE;
	} 

	return DROPBEAR_SUCCESS;
}

/* Let the process know that the window size has changed, as notified from the
 * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
static int sessionwinchange(struct ChanSess *chansess) {

446
447
	int termc, termr, termw, termh;

448
449
450
451
452
	if (chansess->master < 0) {
		/* haven't got a pty yet */
		return DROPBEAR_FAILURE;
	}
			
453
454
455
456
	termc = buf_getint(ses.payload);
	termr = buf_getint(ses.payload);
	termw = buf_getint(ses.payload);
	termh = buf_getint(ses.payload);
457
	
458
	pty_change_window_size(chansess->master, termr, termc, termw, termh);
459

460
	return DROPBEAR_SUCCESS;
461
462
}

463
464
465
466
467
468
469
470
static void get_termmodes(struct ChanSess *chansess) {

	struct termios termio;
	unsigned char opcode;
	unsigned int value;
	const struct TermCode * termcode;
	unsigned int len;

471
	TRACE(("enter get_termmodes"))
472
473
474
475
476
477
478
479
480

	/* Term modes */
	/* We'll ignore errors and continue if we can't set modes.
	 * We're ignoring baud rates since they seem evil */
	if (tcgetattr(chansess->master, &termio) == -1) {
		return;
	}

	len = buf_getint(ses.payload);
Matt Johnston's avatar
Matt Johnston committed
481
482
	TRACE(("term mode str %d p->l %d p->p %d", 
				len, ses.payload->len , ses.payload->pos));
483
	if (len != ses.payload->len - ses.payload->pos) {
484
		dropbear_exit("Bad term mode string");
485
486
487
	}

	if (len == 0) {
488
		TRACE(("leave get_termmodes: empty terminal modes string"))
489
		return;
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
	}

	while (((opcode = buf_getbyte(ses.payload)) != 0x00) && opcode <= 159) {

		/* must be before checking type, so that value is consumed even if
		 * we don't use it */
		value = buf_getint(ses.payload);

		/* handle types of code */
		if (opcode > MAX_TERMCODE) {
			continue;
		}
		termcode = &termcodes[(unsigned int)opcode];
		

		switch (termcode->type) {

			case TERMCODE_NONE:
				break;

			case TERMCODE_CONTROLCHAR:
				termio.c_cc[termcode->mapcode] = value;
				break;

			case TERMCODE_INPUT:
				if (value) {
					termio.c_iflag |= termcode->mapcode;
				} else {
					termio.c_iflag &= ~(termcode->mapcode);
				}
				break;

			case TERMCODE_OUTPUT:
				if (value) {
					termio.c_oflag |= termcode->mapcode;
				} else {
					termio.c_oflag &= ~(termcode->mapcode);
				}
				break;

			case TERMCODE_LOCAL:
				if (value) {
					termio.c_lflag |= termcode->mapcode;
				} else {
					termio.c_lflag &= ~(termcode->mapcode);
				}
				break;

			case TERMCODE_CONTROL:
				if (value) {
					termio.c_cflag |= termcode->mapcode;
				} else {
					termio.c_cflag &= ~(termcode->mapcode);
				}
				break;
				
		}
	}
	if (tcsetattr(chansess->master, TCSANOW, &termio) < 0) {
549
		dropbear_log(LOG_INFO, "Error setting terminal attributes");
550
	}
551
	TRACE(("leave get_termmodes"))
552
553
}

554
555
556
557
558
559
/* Set up a session pty which will be used to execute the shell or program.
 * The pty is allocated now, and kept for when the shell/program executes.
 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
static int sessionpty(struct ChanSess * chansess) {

	unsigned int termlen;
560
	char namebuf[65];
561
	struct passwd * pw = NULL;
562

563
	TRACE(("enter sessionpty"))
564
565
566
567
568
569

	if (!svr_pubkey_allows_pty()) {
		TRACE(("leave sessionpty : pty forbidden by public key option"))
		return DROPBEAR_FAILURE;
	}

570
	chansess->term = buf_getstring(ses.payload, &termlen);
571
572
	if (termlen > MAX_TERM_LEN) {
		/* TODO send disconnect ? */
573
		TRACE(("leave sessionpty: term len too long"))
574
575
576
577
		return DROPBEAR_FAILURE;
	}

	/* allocate the pty */
578
	if (chansess->master != -1) {
579
		dropbear_exit("Multiple pty requests");
580
	}
581
	if (pty_allocate(&chansess->master, &chansess->slave, namebuf, 64) == 0) {
582
		TRACE(("leave sessionpty: failed to allocate pty"))
583
584
585
		return DROPBEAR_FAILURE;
	}
	
586
	chansess->tty = m_strdup(namebuf);
587
	if (!chansess->tty) {
588
		dropbear_exit("Out of memory"); /* TODO disconnect */
589
590
	}

591
592
593
594
	pw = getpwnam(ses.authstate.pw_name);
	if (!pw)
		dropbear_exit("getpwnam failed after succeeding previously");
	pty_setowner(pw, chansess->tty);
595

596
597
	/* Set up the rows/col counts */
	sessionwinchange(chansess);
598

599
600
	/* Read the terminal modes */
	get_termmodes(chansess);
601

602
	TRACE(("leave sessionpty"))
603
604
605
	return DROPBEAR_SUCCESS;
}

606
#if !DROPBEAR_VFORK
607
static void make_connection_string(struct ChanSess *chansess) {
Matt Johnston's avatar
Matt Johnston committed
608
609
610
611
	char *local_ip, *local_port, *remote_ip, *remote_port;
	size_t len;
	get_socket_address(ses.sock_in, &local_ip, &local_port, &remote_ip, &remote_port, 0);

612
613
614
615
616
617
618
619
620
621
622
	/* "remoteip remoteport localip localport" */
	len = strlen(local_ip) + strlen(remote_ip) + 20;
	chansess->connection_string = m_malloc(len);
	snprintf(chansess->connection_string, len, "%s %s %s %s", remote_ip, remote_port, local_ip, local_port);

	/* deprecated but bash only loads .bashrc if SSH_CLIENT is set */ 
	/* "remoteip remoteport localport" */
	len = strlen(remote_ip) + 20;
	chansess->client_string = m_malloc(len);
	snprintf(chansess->client_string, len, "%s %s %s", remote_ip, remote_port, local_port);

623
624
625
626
627
	m_free(local_ip);
	m_free(local_port);
	m_free(remote_ip);
	m_free(remote_port);
}
628
#endif
629

630
631
632
633
634
635
636
/* Handle a command request from the client. This is used for both shell
 * and command-execution requests, and passes the command to
 * noptycommand or ptycommand as appropriate.
 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
		int iscmd, int issubsys) {

Francois Perrad's avatar
Francois Perrad committed
637
	unsigned int cmdlen = 0;
638
	int ret;
639

640
	TRACE(("enter sessioncommand"))
641
642

	if (chansess->cmd != NULL) {
643
644
645
		/* Note that only one command can _succeed_. The client might try
		 * one command (which fails), then try another. Ie fallback
		 * from sftp to scp */
646
647
648
649
650
		return DROPBEAR_FAILURE;
	}

	if (iscmd) {
		/* "exec" */
651
		if (chansess->cmd == NULL) {
652
			chansess->cmd = buf_getstring(ses.payload, &cmdlen);
653

654
655
656
657
658
			if (cmdlen > MAX_CMD_LEN) {
				m_free(chansess->cmd);
				/* TODO - send error - too long ? */
				return DROPBEAR_FAILURE;
			}
659
660
661
662
663
		}
		if (issubsys) {
#ifdef SFTPSERVER_PATH
			if ((cmdlen == 4) && strncmp(chansess->cmd, "sftp", 4) == 0) {
				m_free(chansess->cmd);
664
				chansess->cmd = m_strdup(SFTPSERVER_PATH);
665
666
667
			} else 
#endif
			{
668
				m_free(chansess->cmd);
669
670
671
672
				return DROPBEAR_FAILURE;
			}
		}
	}
673
	
674
675
676
677
678
679
680
681
682
683

	/* take global command into account */
	if (svr_opts.forced_command) {
		chansess->original_command = chansess->cmd ? : m_strdup("");
		chansess->cmd = m_strdup(svr_opts.forced_command);
	} else {
		/* take public key option 'command' into account */
		svr_pubkey_set_forced_command(chansess);
	}

684

685
686
#ifdef LOG_COMMANDS
	if (chansess->cmd) {
687
		dropbear_log(LOG_INFO, "User %s executing '%s'", 
688
						ses.authstate.pw_name, chansess->cmd);
689
	} else {
690
		dropbear_log(LOG_INFO, "User %s executing login shell", 
691
						ses.authstate.pw_name);
692
693
694
	}
#endif

Matt Johnston's avatar
Matt Johnston committed
695
696
	/* uClinux will vfork(), so there'll be a race as 
	connection_string is freed below. */
697
#if !DROPBEAR_VFORK
698
	make_connection_string(chansess);
Matt Johnston's avatar
Matt Johnston committed
699
#endif
Matt Johnston's avatar
Matt Johnston committed
700

701
702
	if (chansess->term == NULL) {
		/* no pty */
703
		ret = noptycommand(channel, chansess);
Matt Johnston's avatar
Matt Johnston committed
704
705
706
707
		if (ret == DROPBEAR_SUCCESS) {
			channel->prio = DROPBEAR_CHANNEL_PRIO_BULK;
			update_channel_prio();
		}
708
709
	} else {
		/* want pty */
710
711
712
		ret = ptycommand(channel, chansess);
	}

713
#if !DROPBEAR_VFORK
Matt Johnston's avatar
Matt Johnston committed
714
	m_free(chansess->connection_string);
715
	m_free(chansess->client_string);
Matt Johnston's avatar
Matt Johnston committed
716
717
#endif

718
719
	if (ret == DROPBEAR_FAILURE) {
		m_free(chansess->cmd);
720
	}
721
	return ret;
722
723
724
725
726
727
}

/* Execute a command and set up redirection of stdin/stdout/stderr without a
 * pty.
 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
static int noptycommand(struct Channel *channel, struct ChanSess *chansess) {
728
	int ret;
729

730
	TRACE(("enter noptycommand"))
731
732
733
	ret = spawn_command(execchild, chansess, 
			&channel->writefd, &channel->readfd, &channel->errfd,
			&chansess->pid);
734

735
736
737
	if (ret == DROPBEAR_FAILURE) {
		return ret;
	}
738

739
740
741
	ses.maxfd = MAX(ses.maxfd, channel->writefd);
	ses.maxfd = MAX(ses.maxfd, channel->readfd);
	ses.maxfd = MAX(ses.maxfd, channel->errfd);
742

743
	addchildpid(chansess, chansess->pid);
744

745
	if (svr_ses.lastexit.exitpid != -1) {
Matt Johnston's avatar
Matt Johnston committed
746
		unsigned int i;
747
748
749
750
751
752
753
754
755
		TRACE(("parent side: lastexitpid is %d", svr_ses.lastexit.exitpid))
		/* The child probably exited and the signal handler triggered
		 * possibly before we got around to adding the childpid. So we fill
		 * out its data manually */
		for (i = 0; i < svr_ses.childpidsize; i++) {
			if (svr_ses.childpids[i].pid == svr_ses.lastexit.exitpid) {
				TRACE(("found match for lastexitpid"))
				svr_ses.childpids[i].chansess->exit = svr_ses.lastexit;
				svr_ses.lastexit.exitpid = -1;
756
				break;
757
758
			}
		}
759
760
	}

761
	TRACE(("leave noptycommand"))
762
763
764
765
766
767
768
769
770
	return DROPBEAR_SUCCESS;
}

/* Execute a command or shell within a pty environment, and set up
 * redirection as appropriate.
 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
static int ptycommand(struct Channel *channel, struct ChanSess *chansess) {

	pid_t pid;
771
	struct logininfo *li = NULL;
772
773
774
775
776
777
778
#ifdef DO_MOTD
	buffer * motdbuf = NULL;
	int len;
	struct stat sb;
	char *hushpath = NULL;
#endif

779
	TRACE(("enter ptycommand"))
780
781
782

	/* we need to have a pty allocated */
	if (chansess->master == -1 || chansess->tty == NULL) {
783
		dropbear_log(LOG_WARNING, "No pty was allocated, couldn't execute");
784
785
786
		return DROPBEAR_FAILURE;
	}
	
787
#if DROPBEAR_VFORK
788
789
	pid = vfork();
#else
790
	pid = fork();
791
#endif
792
793
794
795
796
797
	if (pid < 0)
		return DROPBEAR_FAILURE;

	if (pid == 0) {
		/* child */
		
798
799
800
801
802
803
		TRACE(("back to normal sigchld"))
		/* Revert to normal sigchld handling */
		if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) {
			dropbear_exit("signal() error");
		}
		
804
805
806
807
808
809
810
811
		/* redirect stdin/stdout/stderr */
		close(chansess->master);

		pty_make_controlling_tty(&chansess->slave, chansess->tty);
		
		if ((dup2(chansess->slave, STDIN_FILENO) < 0) ||
			(dup2(chansess->slave, STDERR_FILENO) < 0) ||
			(dup2(chansess->slave, STDOUT_FILENO) < 0)) {
812
			TRACE(("leave ptycommand: error redirecting filedesc"))
813
814
815
816
817
818
819
			return DROPBEAR_FAILURE;
		}

		close(chansess->slave);

		/* write the utmp/wtmp login record - must be after changing the
		 * terminal used for stdout with the dup2 above */
Matt Johnston's avatar
Matt Johnston committed
820
		li = chansess_login_alloc(chansess);
821
822
823
824
		login_login(li);
		login_free_entry(li);

#ifdef DO_MOTD
825
		if (svr_opts.domotd && !chansess->cmd) {
826
827
			/* don't show the motd if ~/.hushlogin exists */

828
			/* 12 == strlen("/.hushlogin\0") */
Matt Johnston's avatar
Matt Johnston committed
829
			len = strlen(ses.authstate.pw_dir) + 12; 
830
831

			hushpath = m_malloc(len);
Matt Johnston's avatar
Matt Johnston committed
832
			snprintf(hushpath, len, "%s/.hushlogin", ses.authstate.pw_dir);
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856

			if (stat(hushpath, &sb) < 0) {
				/* more than a screenful is stupid IMHO */
				motdbuf = buf_new(80 * 25);
				if (buf_readfile(motdbuf, MOTD_FILENAME) == DROPBEAR_SUCCESS) {
					buf_setpos(motdbuf, 0);
					while (motdbuf->pos != motdbuf->len) {
						len = motdbuf->len - motdbuf->pos;
						len = write(STDOUT_FILENO, 
								buf_getptr(motdbuf, len), len);
						buf_incrpos(motdbuf, len);
					}
				}
				buf_free(motdbuf);
			}
			m_free(hushpath);
		}
#endif /* DO_MOTD */

		execchild(chansess);
		/* not reached */

	} else {
		/* parent */
857
		TRACE(("continue ptycommand: parent"))
858
859
860
861
862
863
		chansess->pid = pid;

		/* add a child pid */
		addchildpid(chansess, pid);

		close(chansess->slave);
864
865
		channel->writefd = chansess->master;
		channel->readfd = chansess->master;
866
867
868
		/* don't need to set stderr here */
		ses.maxfd = MAX(ses.maxfd, chansess->master);

869
		setnonblocking(chansess->master);
870
871
872

	}

873
	TRACE(("leave ptycommand"))
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
	return DROPBEAR_SUCCESS;
}

/* Add the pid of a child to the list for exit-handling */
static void addchildpid(struct ChanSess *chansess, pid_t pid) {

	unsigned int i;
	for (i = 0; i < svr_ses.childpidsize; i++) {
		if (svr_ses.childpids[i].pid == -1) {
			break;
		}
	}

	/* need to increase size */
	if (i == svr_ses.childpidsize) {
		svr_ses.childpids = (struct ChildPid*)m_realloc(svr_ses.childpids,
890
				sizeof(struct ChildPid) * (svr_ses.childpidsize+1));
891
892
893
894
895
896
897
898
899
900
		svr_ses.childpidsize++;
	}
	
	svr_ses.childpids[i].pid = pid;
	svr_ses.childpids[i].chansess = chansess;

}

/* Clean up, drop to user privileges, set up the environment and execute
 * the command/shell. This function does not return. */
901
902
static void execchild(void *user_data) {
	struct ChanSess *chansess = user_data;
903
	char *usershell = NULL;
904

905
906
	/* with uClinux we'll have vfork()ed, so don't want to overwrite the
	 * hostkey. can't think of a workaround to clear it */
907
#if !DROPBEAR_VFORK
908
	/* wipe the hostkey */
Matt Johnston's avatar
Matt Johnston committed
909
910
	sign_key_free(svr_opts.hostkey);
	svr_opts.hostkey = NULL;
911
912

	/* overwrite the prng state */
Matt Johnston's avatar
Matt Johnston committed
913
	seedrandom();
914
#endif
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932

	/* clear environment */
	/* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD
	 * etc. This is hazardous, so should only be used for debugging. */
#ifndef DEBUG_VALGRIND
#ifdef HAVE_CLEARENV
	clearenv();
#else /* don't HAVE_CLEARENV */
	/* Yay for posix. */
	if (environ) {
		environ[0] = NULL;
	}
#endif /* HAVE_CLEARENV */
#endif /* DEBUG_VALGRIND */

	/* We can only change uid/gid as root ... */
	if (getuid() == 0) {

933
934
935
		if ((setgid(ses.authstate.pw_gid) < 0) ||
			(initgroups(ses.authstate.pw_name, 
						ses.authstate.pw_gid) < 0)) {
936
			dropbear_exit("Error changing user group");
937
		}
938
		if (setuid(ses.authstate.pw_uid) < 0) {
939
			dropbear_exit("Error changing user");
940
941
942
943
944
945
946
947
948
		}
	} else {
		/* ... but if the daemon is the same uid as the requested uid, we don't
		 * need to */

		/* XXX - there is a minor issue here, in that if there are multiple
		 * usernames with the same uid, but differing groups, then the
		 * differing groups won't be set (as with initgroups()). The solution
		 * is for the sysadmin not to give out the UID twice */
949
		if (getuid() != ses.authstate.pw_uid) {
950
			dropbear_exit("Couldn't	change user as non-root");
951
952
953
954
		}
	}

	/* set env vars */
955
956
957
	addnewvar("USER", ses.authstate.pw_name);
	addnewvar("LOGNAME", ses.authstate.pw_name);
	addnewvar("HOME", ses.authstate.pw_dir);
958
	addnewvar("SHELL", get_user_shell());
959
	addnewvar("PATH", DEFAULT_PATH);
960
961
962
963
	if (chansess->term != NULL) {
		addnewvar("TERM", chansess->term);
	}

964
965
966
967
	if (chansess->tty) {
		addnewvar("SSH_TTY", chansess->tty);
	}
	
Matt Johnston's avatar
Matt Johnston committed
968
969
970
	if (chansess->connection_string) {
		addnewvar("SSH_CONNECTION", chansess->connection_string);
	}
971
972
973
974

	if (chansess->client_string) {
		addnewvar("SSH_CLIENT", chansess->client_string);
	}
Matt Johnston's avatar
Matt Johnston committed
975
	
976
#if DROPBEAR_SVR_PUBKEY_OPTIONS_BUILT
977
978
	if (chansess->original_command) {
		addnewvar("SSH_ORIGINAL_COMMAND", chansess->original_command);
979
980
981
	}
#endif

982
	/* change directory */
983
	if (chdir(ses.authstate.pw_dir) < 0) {
984
		dropbear_exit("Error changing directory");
985
986
	}

987
#if DROPBEAR_X11FWD
988
989
990
	/* set up X11 forwarding if enabled */
	x11setauth(chansess);
#endif
991
#if DROPBEAR_SVR_AGENTFWD
992
	/* set up agent env variable */
Matt Johnston's avatar
Matt Johnston committed
993
	svr_agentset(chansess);
994
995
#endif

996
997
	usershell = m_strdup(get_user_shell());
	run_shell_command(chansess->cmd, ses.maxfd, usershell);
998
999

	/* only reached on error */
1000
	dropbear_exit("Child failed");
1001
}
1002

Matt Johnston's avatar
Matt Johnston committed
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
/* Set up the general chansession environment, in particular child-exit
 * handling */
void svr_chansessinitialise() {

	struct sigaction sa_chld;

	/* single child process intially */
	svr_ses.childpids = (struct ChildPid*)m_malloc(sizeof(struct ChildPid));
	svr_ses.childpids[0].pid = -1; /* unused */
	svr_ses.childpids[0].chansess = NULL;
	svr_ses.childpidsize = 1;
1014
	svr_ses.lastexit.exitpid = -1; /* Nothing has exited yet */
Matt Johnston's avatar
Matt Johnston committed
1015
1016
	sa_chld.sa_handler = sesssigchild_handler;
	sa_chld.sa_flags = SA_NOCLDSTOP;
Matt Johnston's avatar
Matt Johnston committed
1017
	sigemptyset(&sa_chld.sa_mask);
Matt Johnston's avatar
Matt Johnston committed
1018
1019
1020
1021
1022
1023
	if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) {
		dropbear_exit("signal() error");
	}
	
}

1024
1025
1026
/* add a new environment variable, allocating space for the entry */
void addnewvar(const char* param, const char* var) {

1027
	char* newvar = NULL;
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
	int plen, vlen;

	plen = strlen(param);
	vlen = strlen(var);

	newvar = m_malloc(plen + vlen + 2); /* 2 is for '=' and '\0' */
	memcpy(newvar, param, plen);
	newvar[plen] = '=';
	memcpy(&newvar[plen+1], var, vlen);
	newvar[plen+vlen+1] = '\0';
1038
	/* newvar is leaked here, but that's part of putenv()'s semantics */
1039
1040
1041
1042
	if (putenv(newvar) < 0) {
		dropbear_exit("environ error");
	}
}