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