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