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