OpenSSH does work now, but required -0, -1 and -2 specification.
[retty.git] / retty.c
blob4e39b381e56a58d8fe8390fc8293570dd4bad83e
1 /* retty.c - attach process to current terminal
3 * Usage: retty PID
5 * PID is the pid of a running process.
7 * retty works on x86 Linux.
9 * Copyright (c) 2006 Petr Baudis, Jan Sembera
11 * ./-~~-\.
12 * | o o |
13 * | vv |
14 * \_. ._/
15 * \_> <_/
16 * |_/..\_|
17 * / \
20 #define _GNU_SOURCE // grantpt & family
21 #include <signal.h>
22 #include <sys/ioctl.h>
23 #include <termios.h>
24 #include <stdint.h>
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <fcntl.h>
29 #include <sys/ptrace.h>
30 #include <sys/user.h>
31 #include <sys/types.h>
32 #include <sys/wait.h>
33 #include <sys/select.h>
34 #include <unistd.h>
35 #include <errno.h>
36 #include <stdbool.h>
38 #define VERSION "1.0"
41 void sigwinch(int x);
43 static int oldin, oldout, olderr, die, intr;
44 int stin = 0, sout = 1, serr = 2;
45 pid_t pid = 0;
46 bool forking = 0;
47 struct termios t_orig;
50 /* Write NLONG 4 byte words from BUF into PID starting
51 at address POS. Calling process must be attached to PID. */
52 static int
53 write_mem(pid_t pid, unsigned long *buf, int nlong, unsigned long pos)
55 unsigned long *p;
56 int i;
58 for (p = buf, i = 0; i < nlong; p++, i++)
59 if (0 > ptrace(PTRACE_POKEDATA, pid, pos+(i*4), *p))
60 return -1;
61 return 0;
65 static void
66 poke_32(unsigned char *data, off_t offset, uint32_t val)
68 *((uint32_t *)(&data[offset])) = val;
71 #ifdef DEBUG
72 void
73 dump_code(unsigned char *code, size_t size)
75 size_t i;
76 for (i = 0; i < size; i++) {
77 if (i % 8 == 0) {
78 printf("\n");
80 printf("0x%02x, ", code[i]);
83 #endif
86 static void
87 inject_attach(pid_t pid, int n, char ptsname[])
89 struct user_regs_struct regs;
90 unsigned long codeaddr, ptsnameaddr;
91 int waitst;
93 int fd_cervena = stin, fd_zelena = sout, fd_modra = serr;
94 int fd_fialova = stin, fd_oranzova = sout, fd_bezova = serr;
95 int fd_zluta = stin, fd_bila = sout, fd_cerna = serr;
96 int fd_hnusna = stin, fd_cokoladova = sout, fd_vanilkova = serr;
98 static unsigned char attach_code[] = {
99 // this is not how it looks like *hint* *hint*
100 #include "bc-attach.i"
103 #ifdef DEBUG
104 dump_code(attach_code, sizeof(attach_code));
105 #endif
107 /* Attach */
108 if (0 > ptrace(PTRACE_ATTACH, pid, 0, 0)) {
109 fprintf(stderr, "cannot attach to %d\n", pid);
110 exit(1);
112 waitpid(pid, NULL, 0);
113 ptrace(PTRACE_GETREGS, pid, 0, &regs);
116 /* Code injecting */
118 /* push EIP */
119 regs.esp -= 4;
120 ptrace(PTRACE_POKEDATA, pid, regs.esp, regs.eip);
122 /* finish code and push it */
123 regs.esp -= sizeof(attach_code);
124 codeaddr = regs.esp;
125 printf("codesize: %x codeaddr: %lx\n", sizeof(attach_code), codeaddr);
126 *((int*)&attach_code[sizeof(attach_code)-5]) = sizeof(attach_code) + n*4 + 4;
127 if (0 > write_mem(pid, (unsigned long*)&attach_code, sizeof(attach_code)/sizeof(long), regs.esp)) {
128 fprintf(stderr, "cannot write attach_code\n");
129 exit(1);
132 /* push ptsname[] */
133 regs.esp -= n*4;
134 ptsnameaddr = regs.esp;
135 if (0 > write_mem(pid, (unsigned long*)ptsname, n, regs.esp)) {
136 fprintf(stderr, "cannot write bla argument (%s)\n",
137 strerror(errno));
138 exit(1);
141 /* push ptsname */
142 /* FIXME: This is superfluous now, change bytecode to use lea */
143 regs.esp -= 4;
144 ptrace(PTRACE_POKEDATA, pid, regs.esp, ptsnameaddr);
146 regs.eip = codeaddr+8;
147 printf("stack: %lx eip: %lx sub:%x\n", regs.esp, regs.eip, (int) attach_code[sizeof(attach_code)-5]);
150 /* Run the bytecode */
151 ptrace(PTRACE_SETREGS, pid, 0, &regs);
152 sigwinch(0); // bytecode will raise another SIGWINCH later so it will get sync'd thru
153 // interrupt any syscall with the WINCH (typically read() ;)
154 do {
155 ptrace(PTRACE_CONT, pid, 0, (void*) SIGWINCH);
156 wait(&waitst);
157 if (!WIFSTOPPED(waitst)) {
158 fprintf(stderr, "attached task not stopped\n");
159 exit(1);
161 } while (WSTOPSIG(waitst) != SIGWINCH);
163 /* Grab backed up fds from stack */
164 ptrace(PTRACE_GETREGS, pid, 0, &regs);
165 oldin = ptrace(PTRACE_PEEKDATA, pid, regs.esp + 0x8, NULL);
166 oldout = ptrace(PTRACE_PEEKDATA, pid, regs.esp + 0x4, NULL);
167 olderr = ptrace(PTRACE_PEEKDATA, pid, regs.esp + 0x0, NULL);
168 printf("oldfds (esp: %lx): %d, %d, %d\n", regs.esp, oldin, oldout, olderr);
170 /* Let go */
171 ptrace(PTRACE_DETACH, pid, 0, (void*) SIGWINCH);
175 try_detach() {
176 static int detached = 0;
177 if (detached > 0) return 0;
178 if (0 > ptrace(PTRACE_ATTACH, pid, 0, 0)) {
179 return -1;
181 detached++;
182 return 0;
185 static void
186 inject_detach(pid_t pid, int fd0, int fd1, int fd2)
188 struct user_regs_struct regs;
189 unsigned long codeaddr;
191 int fd_zelena = stin, fd_cervena = sout, fd_vyblita = serr;
192 int fd_modra = stin, fd_smoulova = sout, fd_hneda = serr;
194 static unsigned char detach_code[] = {
195 // this is not how it looks like either *hint* *hint*
196 #include "bc-detach.i"
199 /* Attach */
200 (void) try_detach();
201 waitpid(pid, NULL, 0);
202 ptrace(PTRACE_GETREGS, pid, 0, &regs);
205 /* Code injecting */
207 /* push EIP */
208 regs.esp -= 4;
209 ptrace(PTRACE_POKEDATA, pid, regs.esp, regs.eip);
211 /* finish code and push it */
212 regs.esp -= sizeof(detach_code);
213 codeaddr = regs.esp;
214 printf("codesize: %x codeaddr: %lx\n", sizeof(detach_code), codeaddr);
215 *((int*)&detach_code[sizeof(detach_code)-5]) = sizeof(detach_code) + 4 + 4 + 4;
216 if (0 > write_mem(pid, (unsigned long*)&detach_code, sizeof(detach_code)/sizeof(long), regs.esp)) {
217 fprintf(stderr, "cannot write detach_code\n");
218 exit(1);
221 /* push fds */
222 regs.esp -= 4;
223 ptrace(PTRACE_POKEDATA, pid, regs.esp, fd0);
224 regs.esp -= 4;
225 ptrace(PTRACE_POKEDATA, pid, regs.esp, fd1);
226 regs.esp -= 4;
227 ptrace(PTRACE_POKEDATA, pid, regs.esp, fd2);
229 regs.eip = codeaddr+8;
230 printf("stack: %lx eip: %lx sub:%x\n", regs.esp, regs.eip, (int) detach_code[sizeof(detach_code)-5]);
233 /* Detach and continue */
234 ptrace(PTRACE_SETREGS, pid, 0, &regs);
235 kill(pid, SIGWINCH); // interrupt any syscall (typically read() ;)
236 ptrace(PTRACE_DETACH, pid, 0, 0);
240 int ptm;
242 void
243 sigwinch(int x)
245 struct winsize w;
246 ioctl(1, TIOCGWINSZ, &w);
247 ioctl(ptm, TIOCSWINSZ, &w);
250 void
251 sigint(int x)
253 intr = 1;
256 void
257 cleanup(int x)
259 static int cleanups;
260 if ((x != 0) && try_detach()) return;
261 if (cleanups++ > 0) return;
262 if (!try_detach()) inject_detach(pid, oldin, oldout, olderr);
263 ioctl(0, TCSETS, &t_orig);
264 die = 1;
267 ssize_t
268 process_escapes(char *buf, ssize_t *len)
270 static enum { ST_NONE, ST_ENTER, ST_ESCAPE } state;
271 ssize_t i;
272 for (i = 0; i < *len; i++) {
273 //fprintf(stderr, "[state=%d %d/%d char=%x]\n", state, i, *len - 1, buf[i]);
274 switch (state) {
275 case ST_NONE:
276 if (buf[i] == '\n' || buf[i] == '\r')
277 state = ST_ENTER;
278 break;
279 case ST_ENTER:
280 if (buf[i] == '`') {
281 state = ST_ESCAPE;
282 memmove(buf + i, buf + i + 1, *len - i - 1);
283 (*len)--; i--;
284 } else {
285 state = ST_NONE;
287 break;
288 case ST_ESCAPE:
289 state = ST_NONE;
290 switch (buf[i]) {
291 case '.':
292 case 'd':
293 if (try_detach()) {
294 printf("Detach request aborted - ptrace unsuccessful\n");
295 memmove(buf + i, buf + i + 1, *len - i - 1);
296 (*len)--; i--;
297 break;
298 } else return i-2+1;
299 case '?':
300 printf("Supported escape sequences:\n");
301 printf("`. - return the process to its original terminal\n");
302 printf("`d - return the process to its original terminal\n");
303 printf("`? - this message\n");
304 printf("`` - send the escape character by typing it twice\n");
305 printf("(Note that escapes are only recognized immediately after newline.)\n");
306 memmove(buf + i, buf + i + 1, *len - i - 1);
307 (*len)--; i--;
308 break;
309 case '`':
310 break;
311 default:
312 memmove(buf + i + 1, buf + i, *len - i);
313 buf[i] = '`';
314 (*len)++; i++;
315 break;
317 break;
321 return 0;
324 void
325 version(void) {
326 printf("retty %s\n", VERSION);
327 printf("Copyright (c) 2006 Petr Baudis, Jan Sembera\n");
328 printf("This program is licensed under GNU GPL version 2 and no later.\n");
331 void
332 usage(char *pname) {
333 printf("Usage: \n");
334 printf(" %s [-h] [-v] [-0 fd] [-1 fd] [-2 fd] PID \n\n", pname);
336 printf(" -h This help\n");
337 printf(" -v Shows version of retty\n\n");
339 printf(" -0 fd Specify input file descriptor of target process (default 0)\n");
340 printf(" -1 fd Specify output file descriptor of target process (default 1)\n");
341 printf(" -2 fd Specify error file descriptor of target process (default 2)\n\n");
343 printf(" PID PID of process that will be attached (required)\n");
346 void
347 setpid(char *pidchar, char *argv) {
348 char *x;
350 pid = strtol(pidchar, &x, 0);
351 if ((!x) || (*x)) {
352 fprintf(stderr, "PID specified incorrectly. Aborting.\n");
353 usage(argv);
354 exit(EXIT_FAILURE);
359 main(int argc, char *argv[])
361 int n;
362 char *arg;
363 char *pts;
365 while (1) {
366 int res;
367 char *c;
369 res = getopt(argc, argv, "hv0:1:2:");
370 if (res == -1) break;
372 switch (res) {
373 case 'h':
374 usage(argv[0]);
375 exit(EXIT_SUCCESS);
376 break;
378 case 'v':
379 version();
380 exit(EXIT_SUCCESS);
381 break;
383 case '0':
384 stin = strtol(optarg, &c, 10);
385 if ((*optarg == '\0') || (*c != '\0')) {
386 fprintf(stderr, "Wrong stdin specification\n");
387 exit(EXIT_FAILURE);
389 break;
391 case '1':
392 sout = strtol(optarg, &c, 10);
393 if ((*optarg == '\0') || (*c != '\0')) {
394 fprintf(stderr, "Wrong stdout specification\n");
395 exit(EXIT_FAILURE);
397 break;
399 case '2':
400 serr = strtol(optarg, &c, 10);
401 if ((*optarg == '\0') || (*c != '\0')) {
402 fprintf(stderr, "Wrong stderr specification\n");
403 exit(EXIT_FAILURE);
405 break;
407 default:
408 usage(argv[0]);
409 exit(EXIT_FAILURE);
410 break;
415 if (optind < argc) {
416 char *x;
418 pid = strtol(argv[optind], &x, 0);
419 if ((!x) || (*x)) {
420 fprintf(stderr, "PID specified incorrectly. Aborting.\n");
421 usage(argv[0]);
422 exit(EXIT_FAILURE);
425 } else {
426 usage(argv[0]);
427 exit(EXIT_FAILURE);
430 /* Setup pty */
431 ptm = getpt();
432 grantpt(ptm);
433 unlockpt(ptm);
434 pts = ptsname(ptm);
435 tcflush(ptm, TCIOFLUSH);
436 //(void) ioctl(ptm, TIOCEXCL, (char *) 0);
438 n = strlen(pts)+1;
439 n = n/4 + (n%4 ? 1 : 0);
440 arg = malloc(n*sizeof(unsigned long));
441 memcpy(arg, pts, n*4);
443 signal(SIGWINCH, sigwinch);
444 signal(SIGINT, sigint); // breaks stuff
447 inject_attach(pid, n, arg);
449 ioctl(0, TCGETS, &t_orig);
451 signal(SIGTERM, cleanup);
452 //signal(SIGINT, cleanup);
453 signal(SIGQUIT, cleanup);
454 signal(SIGPIPE, cleanup);
456 while (!die) {
457 static struct termios t;
458 fd_set fds;
460 while (intr) {
461 char ibuf = t.c_cc[VINTR];
462 write(ptm, &ibuf, 1);
463 intr--;
466 FD_ZERO(&fds);
467 FD_SET(ptm, &fds);
468 FD_SET(0, &fds);
469 if (select(ptm+1, &fds, NULL, NULL, NULL) < 0) {
470 if (errno == EINTR || errno == EAGAIN)
471 continue;
472 perror("select()");
473 break;
476 ioctl(ptm, TCGETS, &t);
477 // we keep 0 raw and let the pts do the terminal work
478 t.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL|ICANON);
479 ioctl(0, TCSETS, &t);
481 if (FD_ISSET(ptm, &fds)) {
482 char buf[256];
483 ssize_t len = read(ptm, buf, 256);
484 if (len < 0 && errno != EINTR && errno != EAGAIN) {
485 break;
487 write(1, buf, len);
490 if (FD_ISSET(0, &fds)) {
491 char buf[2*256];
492 ssize_t len = read(0, buf, 256);
493 ssize_t stop;
494 stop = process_escapes(buf, &len);
495 if (stop) {
496 write(ptm, buf, stop-1);
497 break;
499 write(ptm, buf, len);
503 cleanup(0);
505 return 0;