1/*
2 * Dropbear - a SSH2 server
3 *
4 * Copyright (c) 2002,2003 Matt Johnston
5 * All rights reserved.
6 *
7 * Permission is hereby granted, free of charge, to any person obtaining a copy
8 * of this software and associated documentation files (the "Software"), to deal
9 * in the Software without restriction, including without limitation the rights
10 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11 * copies of the Software, and to permit persons to whom the Software is
12 * furnished to do so, subject to the following conditions:
13 *
14 * The above copyright notice and this permission notice shall be included in
15 * all copies or substantial portions of the Software.
16 *
17 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23 * SOFTWARE. */
24
25#include "includes.h"
26#include "packet.h"
27#include "buffer.h"
28#include "session.h"
29#include "dbutil.h"
30#include "channel.h"
31#include "chansession.h"
32#include "sshpty.h"
33#include "termcodes.h"
34#include "ssh.h"
35#include "random.h"
36#include "utmp.h"
37#include "x11fwd.h"
38#include "agentfwd.h"
39#include "runopts.h"
40
41/* Handles sessions (either shells or programs) requested by the client */
42
43static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
44		int iscmd, int issubsys);
45static int sessionpty(struct ChanSess * chansess);
46static int sessionsignal(struct ChanSess *chansess);
47static int noptycommand(struct Channel *channel, struct ChanSess *chansess);
48static int ptycommand(struct Channel *channel, struct ChanSess *chansess);
49static int sessionwinchange(struct ChanSess *chansess);
50static void execchild(struct ChanSess *chansess);
51static void addchildpid(struct ChanSess *chansess, pid_t pid);
52static void sesssigchild_handler(int val);
53static void closechansess(struct Channel *channel);
54static int newchansess(struct Channel *channel);
55static void chansessionrequest(struct Channel *channel);
56
57static void send_exitsignalstatus(struct Channel *channel);
58static void send_msg_chansess_exitstatus(struct Channel * channel,
59		struct ChanSess * chansess);
60static void send_msg_chansess_exitsignal(struct Channel * channel,
61		struct ChanSess * chansess);
62static void get_termmodes(struct ChanSess *chansess);
63
64
65/* required to clear environment */
66extern char** environ;
67
68static int sesscheckclose(struct Channel *channel) {
69	struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
70	TRACE(("sesscheckclose, pid is %d", chansess->exit.exitpid))
71	return chansess->exit.exitpid != -1;
72}
73
74/* Handler for childs exiting, store the state for return to the client */
75
76/* There's a particular race we have to watch out for: if the forked child
77 * executes, exits, and this signal-handler is called, all before the parent
78 * gets to run, then the childpids[] array won't have the pid in it. Hence we
79 * use the svr_ses.lastexit struct to hold the exit, which is then compared by
80 * the parent when it runs. This work correctly at least in the case of a
81 * single shell spawned (ie the usual case) */
82static void sesssigchild_handler(int UNUSED(dummy)) {
83
84	int status;
85	pid_t pid;
86	unsigned int i;
87	struct sigaction sa_chld;
88	struct exitinfo *exit = NULL;
89
90	TRACE(("enter sigchld handler"))
91	while ((pid = waitpid(-1, &status, WNOHANG)) > 0) {
92		TRACE(("sigchld handler: pid %d", pid))
93
94		exit = NULL;
95		/* find the corresponding chansess */
96		for (i = 0; i < svr_ses.childpidsize; i++) {
97			if (svr_ses.childpids[i].pid == pid) {
98				TRACE(("found match session"));
99				exit = &svr_ses.childpids[i].chansess->exit;
100				break;
101			}
102		}
103
104		/* If the pid wasn't matched, then we might have hit the race mentioned
105		 * above. So we just store the info for the parent to deal with */
106		if (exit == NULL) {
107			TRACE(("using lastexit"));
108			exit = &svr_ses.lastexit;
109		}
110
111		exit->exitpid = pid;
112		if (WIFEXITED(status)) {
113			exit->exitstatus = WEXITSTATUS(status);
114		}
115		if (WIFSIGNALED(status)) {
116			exit->exitsignal = WTERMSIG(status);
117#if !defined(AIX) && defined(WCOREDUMP)
118			exit->exitcore = WCOREDUMP(status);
119#else
120			exit->exitcore = 0;
121#endif
122		} else {
123			/* we use this to determine how pid exited */
124			exit->exitsignal = -1;
125		}
126
127		/* Make sure that the main select() loop wakes up */
128		while (1) {
129			/* isserver is just a random byte to write. We can't do anything
130			about an error so should just ignore it */
131			if (write(ses.signal_pipe[1], &ses.isserver, 1) == 1
132					|| errno != EINTR) {
133				break;
134			}
135		}
136	}
137
138	sa_chld.sa_handler = sesssigchild_handler;
139	sa_chld.sa_flags = SA_NOCLDSTOP;
140	sigaction(SIGCHLD, &sa_chld, NULL);
141	TRACE(("leave sigchld handler"))
142}
143
144/* send the exit status or the signal causing termination for a session */
145static void send_exitsignalstatus(struct Channel *channel) {
146
147	struct ChanSess *chansess = (struct ChanSess*)channel->typedata;
148
149	if (chansess->exit.exitpid >= 0) {
150		if (chansess->exit.exitsignal > 0) {
151			send_msg_chansess_exitsignal(channel, chansess);
152		} else {
153			send_msg_chansess_exitstatus(channel, chansess);
154		}
155	}
156}
157
158/* send the exitstatus to the client */
159static void send_msg_chansess_exitstatus(struct Channel * channel,
160		struct ChanSess * chansess) {
161
162	dropbear_assert(chansess->exit.exitpid != -1);
163	dropbear_assert(chansess->exit.exitsignal == -1);
164
165	CHECKCLEARTOWRITE();
166
167	buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
168	buf_putint(ses.writepayload, channel->remotechan);
169	buf_putstring(ses.writepayload, "exit-status", 11);
170	buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
171	buf_putint(ses.writepayload, chansess->exit.exitstatus);
172
173	encrypt_packet();
174
175}
176
177/* send the signal causing the exit to the client */
178static void send_msg_chansess_exitsignal(struct Channel * channel,
179		struct ChanSess * chansess) {
180
181	int i;
182	char* signame = NULL;
183	dropbear_assert(chansess->exit.exitpid != -1);
184	dropbear_assert(chansess->exit.exitsignal > 0);
185
186	TRACE(("send_msg_chansess_exitsignal %d", chansess->exit.exitsignal))
187
188	CHECKCLEARTOWRITE();
189
190	/* we check that we can match a signal name, otherwise
191	 * don't send anything */
192	for (i = 0; signames[i].name != NULL; i++) {
193		if (signames[i].signal == chansess->exit.exitsignal) {
194			signame = signames[i].name;
195			break;
196		}
197	}
198
199	if (signame == NULL) {
200		return;
201	}
202
203	buf_putbyte(ses.writepayload, SSH_MSG_CHANNEL_REQUEST);
204	buf_putint(ses.writepayload, channel->remotechan);
205	buf_putstring(ses.writepayload, "exit-signal", 11);
206	buf_putbyte(ses.writepayload, 0); /* boolean FALSE */
207	buf_putstring(ses.writepayload, signame, strlen(signame));
208	buf_putbyte(ses.writepayload, chansess->exit.exitcore);
209	buf_putstring(ses.writepayload, "", 0); /* error msg */
210	buf_putstring(ses.writepayload, "", 0); /* lang */
211
212	encrypt_packet();
213}
214
215/* set up a session channel */
216static int newchansess(struct Channel *channel) {
217
218	struct ChanSess *chansess;
219
220	dropbear_assert(channel->typedata == NULL);
221
222	chansess = (struct ChanSess*)m_malloc(sizeof(struct ChanSess));
223	chansess->cmd = NULL;
224	chansess->pid = 0;
225
226	/* pty details */
227	chansess->master = -1;
228	chansess->slave = -1;
229	chansess->tty = NULL;
230	chansess->term = NULL;
231
232	chansess->exit.exitpid = -1;
233
234	channel->typedata = chansess;
235
236#ifndef DISABLE_X11FWD
237	chansess->x11listener = NULL;
238	chansess->x11authprot = NULL;
239	chansess->x11authcookie = NULL;
240#endif
241
242#ifndef DISABLE_AGENTFWD
243	chansess->agentlistener = NULL;
244	chansess->agentfile = NULL;
245	chansess->agentdir = NULL;
246#endif
247
248	return 0;
249
250}
251
252/* clean a session channel */
253static void closechansess(struct Channel *channel) {
254
255	struct ChanSess *chansess;
256	unsigned int i;
257	struct logininfo *li;
258
259	TRACE(("enter closechansess"))
260
261	chansess = (struct ChanSess*)channel->typedata;
262
263	if (chansess == NULL) {
264		TRACE(("leave closechansess: chansess == NULL"))
265		return;
266	}
267
268	send_exitsignalstatus(channel);
269
270	m_free(chansess->cmd);
271	m_free(chansess->term);
272
273	if (chansess->tty) {
274		/* write the utmp/wtmp login record */
275		li = login_alloc_entry(chansess->pid, ses.authstate.username,
276				ses.remotehost, chansess->tty);
277		login_logout(li);
278		login_free_entry(li);
279
280		pty_release(chansess->tty);
281		m_free(chansess->tty);
282	}
283
284#ifndef DISABLE_X11FWD
285	x11cleanup(chansess);
286#endif
287
288#ifndef DISABLE_AGENTFWD
289	agentcleanup(chansess);
290#endif
291
292	/* clear child pid entries */
293	for (i = 0; i < svr_ses.childpidsize; i++) {
294		if (svr_ses.childpids[i].chansess == chansess) {
295			dropbear_assert(svr_ses.childpids[i].pid > 0);
296			TRACE(("closing pid %d", svr_ses.childpids[i].pid))
297			TRACE(("exitpid is %d", chansess->exit.exitpid))
298			svr_ses.childpids[i].pid = -1;
299			svr_ses.childpids[i].chansess = NULL;
300		}
301	}
302
303	m_free(chansess);
304
305	TRACE(("leave closechansess"))
306}
307
308/* Handle requests for a channel. These can be execution requests,
309 * or x11/authagent forwarding. These are passed to appropriate handlers */
310static void chansessionrequest(struct Channel *channel) {
311
312	unsigned char * type = NULL;
313	unsigned int typelen;
314	unsigned char wantreply;
315	int ret = 1;
316	struct ChanSess *chansess;
317
318	TRACE(("enter chansessionrequest"))
319
320	type = buf_getstring(ses.payload, &typelen);
321	wantreply = buf_getbool(ses.payload);
322
323	if (typelen > MAX_NAME_LEN) {
324		TRACE(("leave chansessionrequest: type too long")) /* XXX send error?*/
325		goto out;
326	}
327
328	chansess = (struct ChanSess*)channel->typedata;
329	dropbear_assert(chansess != NULL);
330	TRACE(("type is %s", type))
331
332	if (strcmp(type, "window-change") == 0) {
333		ret = sessionwinchange(chansess);
334	} else if (strcmp(type, "shell") == 0) {
335		ret = sessioncommand(channel, chansess, 0, 0);
336	} else if (strcmp(type, "pty-req") == 0) {
337		ret = sessionpty(chansess);
338	} else if (strcmp(type, "exec") == 0) {
339		ret = sessioncommand(channel, chansess, 1, 0);
340	} else if (strcmp(type, "subsystem") == 0) {
341		ret = sessioncommand(channel, chansess, 1, 1);
342#ifndef DISABLE_X11FWD
343	} else if (strcmp(type, "x11-req") == 0) {
344		ret = x11req(chansess);
345#endif
346#ifndef DISABLE_AGENTFWD
347	} else if (strcmp(type, "auth-agent-req@openssh.com") == 0) {
348		ret = agentreq(chansess);
349#endif
350	} else if (strcmp(type, "signal") == 0) {
351		ret = sessionsignal(chansess);
352	} else {
353		/* etc, todo "env", "subsystem" */
354	}
355
356out:
357
358	if (wantreply) {
359		if (ret == DROPBEAR_SUCCESS) {
360			send_msg_channel_success(channel);
361		} else {
362			send_msg_channel_failure(channel);
363		}
364	}
365
366	m_free(type);
367	TRACE(("leave chansessionrequest"))
368}
369
370
371/* Send a signal to a session's process as requested by the client*/
372static int sessionsignal(struct ChanSess *chansess) {
373
374	int sig = 0;
375	unsigned char* signame = NULL;
376	int i;
377
378	if (chansess->pid == 0) {
379		/* haven't got a process pid yet */
380		return DROPBEAR_FAILURE;
381	}
382
383	signame = buf_getstring(ses.payload, NULL);
384
385	i = 0;
386	while (signames[i].name != 0) {
387		if (strcmp(signames[i].name, signame) == 0) {
388			sig = signames[i].signal;
389			break;
390		}
391		i++;
392	}
393
394	m_free(signame);
395
396	if (sig == 0) {
397		/* failed */
398		return DROPBEAR_FAILURE;
399	}
400
401	if (kill(chansess->pid, sig) < 0) {
402		return DROPBEAR_FAILURE;
403	}
404
405	return DROPBEAR_SUCCESS;
406}
407
408/* Let the process know that the window size has changed, as notified from the
409 * client. Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
410static int sessionwinchange(struct ChanSess *chansess) {
411
412	int termc, termr, termw, termh;
413
414	if (chansess->master < 0) {
415		/* haven't got a pty yet */
416		return DROPBEAR_FAILURE;
417	}
418
419	termc = buf_getint(ses.payload);
420	termr = buf_getint(ses.payload);
421	termw = buf_getint(ses.payload);
422	termh = buf_getint(ses.payload);
423
424	pty_change_window_size(chansess->master, termr, termc, termw, termh);
425
426	return DROPBEAR_SUCCESS;
427}
428
429static void get_termmodes(struct ChanSess *chansess) {
430
431	struct termios termio;
432	unsigned char opcode;
433	unsigned int value;
434	const struct TermCode * termcode;
435	unsigned int len;
436
437	TRACE(("enter get_termmodes"))
438
439	/* Term modes */
440	/* We'll ignore errors and continue if we can't set modes.
441	 * We're ignoring baud rates since they seem evil */
442	if (tcgetattr(chansess->master, &termio) == -1) {
443		return;
444	}
445
446	len = buf_getint(ses.payload);
447	TRACE(("term mode str %d p->l %d p->p %d",
448				len, ses.payload->len , ses.payload->pos));
449	if (len != ses.payload->len - ses.payload->pos) {
450		dropbear_exit("bad term mode string");
451	}
452
453	if (len == 0) {
454		TRACE(("leave get_termmodes: empty terminal modes string"))
455		return;
456	}
457
458	while (((opcode = buf_getbyte(ses.payload)) != 0x00) && opcode <= 159) {
459
460		/* must be before checking type, so that value is consumed even if
461		 * we don't use it */
462		value = buf_getint(ses.payload);
463
464		/* handle types of code */
465		if (opcode > MAX_TERMCODE) {
466			continue;
467		}
468		termcode = &termcodes[(unsigned int)opcode];
469
470
471		switch (termcode->type) {
472
473			case TERMCODE_NONE:
474				break;
475
476			case TERMCODE_CONTROLCHAR:
477				termio.c_cc[termcode->mapcode] = value;
478				break;
479
480			case TERMCODE_INPUT:
481				if (value) {
482					termio.c_iflag |= termcode->mapcode;
483				} else {
484					termio.c_iflag &= ~(termcode->mapcode);
485				}
486				break;
487
488			case TERMCODE_OUTPUT:
489				if (value) {
490					termio.c_oflag |= termcode->mapcode;
491				} else {
492					termio.c_oflag &= ~(termcode->mapcode);
493				}
494				break;
495
496			case TERMCODE_LOCAL:
497				if (value) {
498					termio.c_lflag |= termcode->mapcode;
499				} else {
500					termio.c_lflag &= ~(termcode->mapcode);
501				}
502				break;
503
504			case TERMCODE_CONTROL:
505				if (value) {
506					termio.c_cflag |= termcode->mapcode;
507				} else {
508					termio.c_cflag &= ~(termcode->mapcode);
509				}
510				break;
511
512		}
513	}
514	if (tcsetattr(chansess->master, TCSANOW, &termio) < 0) {
515		dropbear_log(LOG_INFO, "error setting terminal attributes");
516	}
517	TRACE(("leave get_termmodes"))
518}
519
520/* Set up a session pty which will be used to execute the shell or program.
521 * The pty is allocated now, and kept for when the shell/program executes.
522 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
523static int sessionpty(struct ChanSess * chansess) {
524
525	unsigned int termlen;
526	unsigned char namebuf[65];
527
528	TRACE(("enter sessionpty"))
529	chansess->term = buf_getstring(ses.payload, &termlen);
530	if (termlen > MAX_TERM_LEN) {
531		/* TODO send disconnect ? */
532		TRACE(("leave sessionpty: term len too long"))
533		return DROPBEAR_FAILURE;
534	}
535
536	/* allocate the pty */
537	if (chansess->master != -1) {
538		dropbear_exit("multiple pty requests");
539	}
540	if (pty_allocate(&chansess->master, &chansess->slave, namebuf, 64) == 0) {
541		TRACE(("leave sessionpty: failed to allocate pty"))
542		return DROPBEAR_FAILURE;
543	}
544
545	chansess->tty = (char*)m_strdup(namebuf);
546	if (!chansess->tty) {
547		dropbear_exit("out of memory"); /* TODO disconnect */
548	}
549
550	pty_setowner(ses.authstate.pw, chansess->tty);
551
552	/* Set up the rows/col counts */
553	sessionwinchange(chansess);
554
555	/* Read the terminal modes */
556	get_termmodes(chansess);
557
558	TRACE(("leave sessionpty"))
559	return DROPBEAR_SUCCESS;
560}
561
562/* Handle a command request from the client. This is used for both shell
563 * and command-execution requests, and passes the command to
564 * noptycommand or ptycommand as appropriate.
565 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
566static int sessioncommand(struct Channel *channel, struct ChanSess *chansess,
567		int iscmd, int issubsys) {
568
569	unsigned int cmdlen;
570	int ret;
571
572	TRACE(("enter sessioncommand"))
573
574	if (chansess->cmd != NULL) {
575		/* Note that only one command can _succeed_. The client might try
576		 * one command (which fails), then try another. Ie fallback
577		 * from sftp to scp */
578		return DROPBEAR_FAILURE;
579	}
580
581	if (iscmd) {
582		/* "exec" */
583		chansess->cmd = buf_getstring(ses.payload, &cmdlen);
584
585		if (cmdlen > MAX_CMD_LEN) {
586			m_free(chansess->cmd);
587			/* TODO - send error - too long ? */
588			return DROPBEAR_FAILURE;
589		}
590		if (issubsys) {
591#ifdef SFTPSERVER_PATH
592			if ((cmdlen == 4) && strncmp(chansess->cmd, "sftp", 4) == 0) {
593				m_free(chansess->cmd);
594				chansess->cmd = m_strdup(SFTPSERVER_PATH);
595			} else
596#endif
597			{
598				m_free(chansess->cmd);
599				return DROPBEAR_FAILURE;
600			}
601		}
602	}
603
604#ifdef LOG_COMMANDS
605	if (chansess->cmd) {
606		dropbear_log(LOG_INFO, "user %s executing '%s'",
607						ses.authstate.printableuser, chansess->cmd);
608	} else {
609		dropbear_log(LOG_INFO, "user %s executing login shell",
610						ses.authstate.printableuser);
611	}
612#endif
613
614	if (chansess->term == NULL) {
615		/* no pty */
616		ret = noptycommand(channel, chansess);
617	} else {
618		/* want pty */
619		ret = ptycommand(channel, chansess);
620	}
621
622	if (ret == DROPBEAR_FAILURE) {
623		m_free(chansess->cmd);
624	}
625	return ret;
626}
627
628/* Execute a command and set up redirection of stdin/stdout/stderr without a
629 * pty.
630 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
631static int noptycommand(struct Channel *channel, struct ChanSess *chansess) {
632
633	int infds[2];
634	int outfds[2];
635	int errfds[2];
636	pid_t pid;
637	unsigned int i;
638
639	TRACE(("enter noptycommand"))
640
641	/* redirect stdin/stdout/stderr */
642	if (pipe(infds) != 0)
643		return DROPBEAR_FAILURE;
644	if (pipe(outfds) != 0)
645		return DROPBEAR_FAILURE;
646	if (pipe(errfds) != 0)
647		return DROPBEAR_FAILURE;
648
649#ifdef __uClinux__
650	pid = vfork();
651#else
652	pid = fork();
653#endif
654
655	if (pid < 0)
656		return DROPBEAR_FAILURE;
657
658	if (!pid) {
659		/* child */
660
661		TRACE(("back to normal sigchld"))
662		/* Revert to normal sigchld handling */
663		if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) {
664			dropbear_exit("signal() error");
665		}
666
667		/* redirect stdin/stdout */
668#define FDIN 0
669#define FDOUT 1
670		if ((dup2(infds[FDIN], STDIN_FILENO) < 0) ||
671			(dup2(outfds[FDOUT], STDOUT_FILENO) < 0) ||
672			(dup2(errfds[FDOUT], STDERR_FILENO) < 0)) {
673			TRACE(("leave noptycommand: error redirecting FDs"))
674			return DROPBEAR_FAILURE;
675		}
676
677		close(infds[FDOUT]);
678		close(infds[FDIN]);
679		close(outfds[FDIN]);
680		close(outfds[FDOUT]);
681		close(errfds[FDIN]);
682		close(errfds[FDOUT]);
683
684		execchild(chansess);
685		/* not reached */
686
687	} else {
688		/* parent */
689		TRACE(("continue noptycommand: parent"))
690		chansess->pid = pid;
691		TRACE(("child pid is %d", pid))
692
693		addchildpid(chansess, pid);
694
695		if (svr_ses.lastexit.exitpid != -1) {
696			TRACE(("parent side: lastexitpid is %d", svr_ses.lastexit.exitpid))
697			/* The child probably exited and the signal handler triggered
698			 * possibly before we got around to adding the childpid. So we fill
699			 * out its data manually */
700			for (i = 0; i < svr_ses.childpidsize; i++) {
701				if (svr_ses.childpids[i].pid == svr_ses.lastexit.exitpid) {
702					TRACE(("found match for lastexitpid"))
703					svr_ses.childpids[i].chansess->exit = svr_ses.lastexit;
704					svr_ses.lastexit.exitpid = -1;
705				}
706			}
707		}
708
709		close(infds[FDIN]);
710		close(outfds[FDOUT]);
711		close(errfds[FDOUT]);
712		channel->writefd = infds[FDOUT];
713		channel->readfd = outfds[FDIN];
714		channel->errfd = errfds[FDIN];
715		ses.maxfd = MAX(ses.maxfd, channel->writefd);
716		ses.maxfd = MAX(ses.maxfd, channel->readfd);
717		ses.maxfd = MAX(ses.maxfd, channel->errfd);
718
719		setnonblocking(channel->readfd);
720		setnonblocking(channel->writefd);
721		setnonblocking(channel->errfd);
722
723	}
724#undef FDIN
725#undef FDOUT
726
727	TRACE(("leave noptycommand"))
728	return DROPBEAR_SUCCESS;
729}
730
731/* Execute a command or shell within a pty environment, and set up
732 * redirection as appropriate.
733 * Returns DROPBEAR_SUCCESS or DROPBEAR_FAILURE */
734static int ptycommand(struct Channel *channel, struct ChanSess *chansess) {
735
736	pid_t pid;
737	struct logininfo *li = NULL;
738#ifdef DO_MOTD
739	buffer * motdbuf = NULL;
740	int len;
741	struct stat sb;
742	char *hushpath = NULL;
743#endif
744
745	TRACE(("enter ptycommand"))
746
747	/* we need to have a pty allocated */
748	if (chansess->master == -1 || chansess->tty == NULL) {
749		dropbear_log(LOG_WARNING, "no pty was allocated, couldn't execute");
750		return DROPBEAR_FAILURE;
751	}
752
753#ifdef __uClinux__
754	pid = vfork();
755#else
756	pid = fork();
757#endif
758	if (pid < 0)
759		return DROPBEAR_FAILURE;
760
761	if (pid == 0) {
762		/* child */
763
764		TRACE(("back to normal sigchld"))
765		/* Revert to normal sigchld handling */
766		if (signal(SIGCHLD, SIG_DFL) == SIG_ERR) {
767			dropbear_exit("signal() error");
768		}
769
770		/* redirect stdin/stdout/stderr */
771		close(chansess->master);
772
773		pty_make_controlling_tty(&chansess->slave, chansess->tty);
774
775		if ((dup2(chansess->slave, STDIN_FILENO) < 0) ||
776			(dup2(chansess->slave, STDERR_FILENO) < 0) ||
777			(dup2(chansess->slave, STDOUT_FILENO) < 0)) {
778			TRACE(("leave ptycommand: error redirecting filedesc"))
779			return DROPBEAR_FAILURE;
780		}
781
782		close(chansess->slave);
783
784		/* write the utmp/wtmp login record - must be after changing the
785		 * terminal used for stdout with the dup2 above */
786		li= login_alloc_entry(getpid(), ses.authstate.username,
787				ses.remotehost, chansess->tty);
788		login_login(li);
789		login_free_entry(li);
790
791		m_free(chansess->tty);
792
793#ifdef DO_MOTD
794		if (svr_opts.domotd) {
795			/* don't show the motd if ~/.hushlogin exists */
796
797			/* 11 == strlen("/hushlogin\0") */
798			len = strlen(ses.authstate.pw->pw_dir) + 11;
799
800			hushpath = m_malloc(len);
801			snprintf(hushpath, len, "%s/hushlogin", ses.authstate.pw->pw_dir);
802
803			if (stat(hushpath, &sb) < 0) {
804				/* more than a screenful is stupid IMHO */
805				motdbuf = buf_new(80 * 25);
806				if (buf_readfile(motdbuf, MOTD_FILENAME) == DROPBEAR_SUCCESS) {
807					buf_setpos(motdbuf, 0);
808					while (motdbuf->pos != motdbuf->len) {
809						len = motdbuf->len - motdbuf->pos;
810						len = write(STDOUT_FILENO,
811								buf_getptr(motdbuf, len), len);
812						buf_incrpos(motdbuf, len);
813					}
814				}
815				buf_free(motdbuf);
816			}
817			m_free(hushpath);
818		}
819#endif /* DO_MOTD */
820
821		execchild(chansess);
822		/* not reached */
823
824	} else {
825		/* parent */
826		TRACE(("continue ptycommand: parent"))
827		chansess->pid = pid;
828
829		/* add a child pid */
830		addchildpid(chansess, pid);
831
832		close(chansess->slave);
833		channel->writefd = chansess->master;
834		channel->readfd = chansess->master;
835		/* don't need to set stderr here */
836		ses.maxfd = MAX(ses.maxfd, chansess->master);
837
838		setnonblocking(chansess->master);
839
840	}
841
842	TRACE(("leave ptycommand"))
843	return DROPBEAR_SUCCESS;
844}
845
846/* Add the pid of a child to the list for exit-handling */
847static void addchildpid(struct ChanSess *chansess, pid_t pid) {
848
849	unsigned int i;
850	for (i = 0; i < svr_ses.childpidsize; i++) {
851		if (svr_ses.childpids[i].pid == -1) {
852			break;
853		}
854	}
855
856	/* need to increase size */
857	if (i == svr_ses.childpidsize) {
858		svr_ses.childpids = (struct ChildPid*)m_realloc(svr_ses.childpids,
859				sizeof(struct ChildPid) * (svr_ses.childpidsize+1));
860		svr_ses.childpidsize++;
861	}
862
863	svr_ses.childpids[i].pid = pid;
864	svr_ses.childpids[i].chansess = chansess;
865
866}
867
868/* Clean up, drop to user privileges, set up the environment and execute
869 * the command/shell. This function does not return. */
870static void execchild(struct ChanSess *chansess) {
871
872	char *argv[4];
873	char * usershell = NULL;
874	char * baseshell = NULL;
875	unsigned int i;
876
877    /* with uClinux we'll have vfork()ed, so don't want to overwrite the
878     * hostkey. can't think of a workaround to clear it */
879#ifndef __uClinux__
880	/* wipe the hostkey */
881	sign_key_free(svr_opts.hostkey);
882	svr_opts.hostkey = NULL;
883
884	/* overwrite the prng state */
885	reseedrandom();
886#endif
887
888	/* close file descriptors except stdin/stdout/stderr
889	 * Need to be sure FDs are closed here to avoid reading files as root */
890	for (i = 3; i <= (unsigned int)ses.maxfd; i++) {
891		m_close(i);
892	}
893
894	/* clear environment */
895	/* if we're debugging using valgrind etc, we need to keep the LD_PRELOAD
896	 * etc. This is hazardous, so should only be used for debugging. */
897#ifndef DEBUG_VALGRIND
898#ifdef HAVE_CLEARENV
899	clearenv();
900#else /* don't HAVE_CLEARENV */
901	/* Yay for posix. */
902	if (environ) {
903		environ[0] = NULL;
904	}
905#endif /* HAVE_CLEARENV */
906#endif /* DEBUG_VALGRIND */
907
908	/* We can only change uid/gid as root ... */
909	if (getuid() == 0) {
910
911		if ((setgid(ses.authstate.pw->pw_gid) < 0) ||
912			(initgroups(ses.authstate.pw->pw_name,
913						ses.authstate.pw->pw_gid) < 0)) {
914			dropbear_exit("error changing user group");
915		}
916		if (setuid(ses.authstate.pw->pw_uid) < 0) {
917			dropbear_exit("error changing user");
918		}
919	} else {
920		/* ... but if the daemon is the same uid as the requested uid, we don't
921		 * need to */
922
923		/* XXX - there is a minor issue here, in that if there are multiple
924		 * usernames with the same uid, but differing groups, then the
925		 * differing groups won't be set (as with initgroups()). The solution
926		 * is for the sysadmin not to give out the UID twice */
927		if (getuid() != ses.authstate.pw->pw_uid) {
928			dropbear_exit("couldn't	change user as non-root");
929		}
930	}
931
932	/* an empty shell should be interpreted as "/bin/sh" */
933	if (ses.authstate.pw->pw_shell[0] == '\0') {
934		usershell = "/bin/sh";
935	} else {
936		usershell = ses.authstate.pw->pw_shell;
937	}
938
939	/* set env vars */
940	addnewvar("USER", ses.authstate.pw->pw_name);
941	addnewvar("LOGNAME", ses.authstate.pw->pw_name);
942	addnewvar("HOME", ses.authstate.pw->pw_dir);
943	addnewvar("SHELL", usershell);
944	if (chansess->term != NULL) {
945		addnewvar("TERM", chansess->term);
946	}
947
948	/* change directory */
949	if (chdir(ses.authstate.pw->pw_dir) < 0) {
950		dropbear_exit("error changing directory");
951	}
952
953#ifndef DISABLE_X11FWD
954	/* set up X11 forwarding if enabled */
955	x11setauth(chansess);
956#endif
957#ifndef DISABLE_AGENTFWD
958	/* set up agent env variable */
959	agentset(chansess);
960#endif
961
962	/* Re-enable SIGPIPE for the executed process */
963	if (signal(SIGPIPE, SIG_DFL) == SIG_ERR) {
964		dropbear_exit("signal() error");
965	}
966
967	baseshell = basename(usershell);
968
969	if (chansess->cmd != NULL) {
970		argv[0] = baseshell;
971	} else {
972		/* a login shell should be "-bash" for "/bin/bash" etc */
973		int len = strlen(baseshell) + 2; /* 2 for "-" */
974		argv[0] = (char*)m_malloc(len);
975		snprintf(argv[0], len, "-%s", baseshell);
976	}
977
978	if (chansess->cmd != NULL) {
979		argv[1] = "-c";
980		argv[2] = chansess->cmd;
981		argv[3] = NULL;
982	} else {
983		/* construct a shell of the form "-bash" etc */
984		argv[1] = NULL;
985	}
986
987	execv(usershell, argv);
988
989	/* only reached on error */
990	dropbear_exit("child failed");
991}
992
993const struct ChanType svrchansess = {
994	0, /* sepfds */
995	"session", /* name */
996	newchansess, /* inithandler */
997	sesscheckclose, /* checkclosehandler */
998	chansessionrequest, /* reqhandler */
999	closechansess, /* closehandler */
1000};
1001
1002
1003/* Set up the general chansession environment, in particular child-exit
1004 * handling */
1005void svr_chansessinitialise() {
1006
1007	struct sigaction sa_chld;
1008
1009	/* single child process intially */
1010	svr_ses.childpids = (struct ChildPid*)m_malloc(sizeof(struct ChildPid));
1011	svr_ses.childpids[0].pid = -1; /* unused */
1012	svr_ses.childpids[0].chansess = NULL;
1013	svr_ses.childpidsize = 1;
1014	svr_ses.lastexit.exitpid = -1; /* Nothing has exited yet */
1015	sa_chld.sa_handler = sesssigchild_handler;
1016	sa_chld.sa_flags = SA_NOCLDSTOP;
1017	if (sigaction(SIGCHLD, &sa_chld, NULL) < 0) {
1018		dropbear_exit("signal() error");
1019	}
1020
1021}
1022
1023/* add a new environment variable, allocating space for the entry */
1024void addnewvar(const char* param, const char* var) {
1025
1026	char* newvar = NULL;
1027	int plen, vlen;
1028
1029	plen = strlen(param);
1030	vlen = strlen(var);
1031
1032	newvar = m_malloc(plen + vlen + 2); /* 2 is for '=' and '\0' */
1033	memcpy(newvar, param, plen);
1034	newvar[plen] = '=';
1035	memcpy(&newvar[plen+1], var, vlen);
1036	newvar[plen+vlen+1] = '\0';
1037	/* newvar is leaked here, but that's part of putenv()'s semantics */
1038	if (putenv(newvar) < 0) {
1039		dropbear_exit("environ error");
1040	}
1041}
1042