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