459 lines
9.5 KiB
C
459 lines
9.5 KiB
C
/*
|
|
dtach - A simple program that emulates the detach feature of screen.
|
|
Copyright (C) 2001 Ned T. Crigler
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program; if not, write to the Free Software
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
#include "detach.h"
|
|
|
|
/* The pty struct - The pty information is stored here. */
|
|
struct pty
|
|
{
|
|
/* File descriptor of the pty */
|
|
int fd;
|
|
#ifdef BROKEN_MASTER
|
|
/* File descriptor of the slave side of the pty. For broken systems. */
|
|
int slave;
|
|
#endif
|
|
/* The terminal parameters of the pty. Old and new for comparision
|
|
** purposes. */
|
|
struct termios term;
|
|
/* The current window size of the pty. */
|
|
struct winsize ws;
|
|
};
|
|
|
|
/* The poll structures */
|
|
static struct pollfd *polls;
|
|
/* The number of active poll slots */
|
|
static int num_polls;
|
|
/* Boolean array for whether a particular connection is attached. */
|
|
static int *attached;
|
|
/* The highest file descriptor possible, as returned by getrlimit. */
|
|
static int highest_fd;
|
|
|
|
/* The number of fixed slots in the poll structures */
|
|
#define FIXED_SLOTS 2
|
|
|
|
#ifndef HAVE_FORKPTY
|
|
pid_t forkpty(int *amaster, char *name, struct termios *termp,
|
|
struct winsize *winp);
|
|
#endif
|
|
|
|
/* Unlink the socket */
|
|
static void
|
|
unlink_socket(void)
|
|
{
|
|
unlink(sockname);
|
|
}
|
|
|
|
/* Signal */
|
|
static RETSIGTYPE
|
|
die(int sig)
|
|
{
|
|
/* Well, the child died. */
|
|
if (sig == SIGCHLD)
|
|
{
|
|
#ifdef BROKEN_MASTER
|
|
/* Damn you Solaris! */
|
|
close(polls[1].fd);
|
|
#endif
|
|
return;
|
|
}
|
|
exit(1);
|
|
}
|
|
|
|
/* Initialize the pty structure. */
|
|
static int
|
|
init_pty(struct pty *pty, char **argv)
|
|
{
|
|
pid_t pid;
|
|
|
|
/* Use the original terminal's settings. We don't have to set the
|
|
** window size here, because the attacher will send it in a packet. */
|
|
pty->term = orig_term;
|
|
|
|
/* Create the pty process */
|
|
pid = forkpty(&pty->fd, NULL, &pty->term, NULL);
|
|
if (pid < 0)
|
|
return -1;
|
|
else if (pid == 0)
|
|
{
|
|
int i;
|
|
|
|
/* Child.. Close some file descriptors and execute the
|
|
** program. */
|
|
for (i = highest_fd; i > 2; --i)
|
|
close(i);
|
|
|
|
execvp(*argv, argv);
|
|
exit(127);
|
|
}
|
|
/* Parent.. Finish up and return */
|
|
#ifdef BROKEN_MASTER
|
|
{
|
|
char *buf;
|
|
|
|
buf = ptsname(pty->fd);
|
|
pty->slave = open(buf, O_RDWR|O_NOCTTY);
|
|
}
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
/* Creates a new unix domain socket. */
|
|
static int
|
|
create_socket(char *name)
|
|
{
|
|
int s;
|
|
struct sockaddr_un sockun;
|
|
|
|
s = socket(PF_UNIX, SOCK_STREAM, 0);
|
|
if (s < 0)
|
|
return -1;
|
|
sockun.sun_family = AF_UNIX;
|
|
strcpy(sockun.sun_path, name);
|
|
if (bind(s, (struct sockaddr*)&sockun, sizeof(sockun)) < 0)
|
|
return -1;
|
|
if (listen(s, 128) < 0)
|
|
return -1;
|
|
/* chmod it to prevent any suprises */
|
|
if (chmod(name, 0600) < 0)
|
|
return -1;
|
|
return s;
|
|
}
|
|
|
|
/* Process activity on a pty - Input and terminal changes are sent out to
|
|
** the attached clients. If the pty goes away, we die. */
|
|
static void
|
|
pty_activity(struct pty *pty)
|
|
{
|
|
int i, len;
|
|
unsigned char buf[BUFSIZE];
|
|
|
|
/* Read the pty activity */
|
|
len = read(pty->fd, buf, sizeof(buf));
|
|
|
|
/* Error -> die */
|
|
if (len <= 0)
|
|
exit(1);
|
|
|
|
#ifdef BROKEN_MASTER
|
|
/* Get the current terminal settings. */
|
|
if (tcgetattr(pty->slave, &pty->term) < 0)
|
|
exit(1);
|
|
#else
|
|
/* Get the current terminal settings. */
|
|
if (tcgetattr(pty->fd, &pty->term) < 0)
|
|
exit(1);
|
|
#endif
|
|
|
|
/* Send it out to the clients. */
|
|
for (i = FIXED_SLOTS; i < num_polls; ++i)
|
|
{
|
|
if (attached[polls[i].fd])
|
|
write(polls[i].fd, buf, len);
|
|
}
|
|
}
|
|
|
|
/* Process activity on the control socket */
|
|
static void
|
|
control_activity(int s)
|
|
{
|
|
int fd;
|
|
|
|
/* Accept the new client and link it in. */
|
|
fd = accept(s, 0, 0);
|
|
if (fd < 0)
|
|
return;
|
|
|
|
/* Link it in. */
|
|
polls[num_polls].fd = fd;
|
|
polls[num_polls].events = POLLIN;
|
|
polls[num_polls].revents = 0;
|
|
attached[fd] = 1;
|
|
++num_polls;
|
|
}
|
|
|
|
/* Process activity from a client. */
|
|
static void
|
|
client_activity(int i, struct pty *pty)
|
|
{
|
|
int len;
|
|
struct packet pkt;
|
|
|
|
/* Read the activity. */
|
|
len = read(polls[i].fd, &pkt, sizeof(pkt));
|
|
if (len <= 0)
|
|
{
|
|
/* Close the socket and go bye bye */
|
|
attached[polls[i].fd]=0;
|
|
close(polls[i].fd);
|
|
memcpy(polls + i, polls + i + 1, num_polls - i);
|
|
--num_polls;
|
|
return;
|
|
}
|
|
|
|
/* Okay, check the command byte. Push out data if we need to. */
|
|
if (pkt.type == MSG_PUSH)
|
|
write(pty->fd, pkt.u.buf, pkt.len);
|
|
/* Window size change. */
|
|
else if (pkt.type == MSG_WINCH)
|
|
{
|
|
pty->ws = pkt.u.ws;
|
|
ioctl(pty->fd, TIOCSWINSZ, &pty->ws);
|
|
}
|
|
/* Redraw request? */
|
|
else if (pkt.type == MSG_REDRAW)
|
|
{
|
|
char c = '\f';
|
|
|
|
/* Guess that ^L might work under certain conditions. */
|
|
if (((pty->term.c_lflag & (ECHO|ICANON)) == 0) &&
|
|
(pty->term.c_cc[VMIN] == 1))
|
|
{
|
|
write(pty->fd, &c, sizeof(c));
|
|
}
|
|
}
|
|
/* Attach request? */
|
|
else if (pkt.type == MSG_ATTACH)
|
|
attached[polls[i].fd] = 1;
|
|
else if (pkt.type == MSG_DETACH)
|
|
attached[polls[i].fd] = 0;
|
|
}
|
|
|
|
/* The master process - It watches over the pty process and the attached */
|
|
/* clients. */
|
|
static void
|
|
master_process(int s, char **argv)
|
|
{
|
|
struct pty pty;
|
|
int i;
|
|
|
|
#ifdef HAVE_GETRLIMIT
|
|
struct rlimit rlim;
|
|
|
|
/* Dynamically allocate structures based on the number of file
|
|
** descriptors. */
|
|
|
|
if (getrlimit(RLIMIT_NOFILE, &rlim) < 0)
|
|
{
|
|
printf("%s: getrlimit: %s\n", progname, strerror(errno));
|
|
exit(1);
|
|
}
|
|
highest_fd = rlim.rlim_cur;
|
|
#else
|
|
/* We can't query the OS for the number of file descriptors, so
|
|
** we pull a number out of the air. */
|
|
highest_fd = 1024;
|
|
#endif
|
|
polls = (struct pollfd*)malloc(highest_fd * sizeof(struct pollfd));
|
|
attached = (int*)malloc(highest_fd * sizeof(int));
|
|
|
|
/* Okay, disassociate ourselves from the original terminal, as we
|
|
** don't care what happens to it. */
|
|
setsid();
|
|
|
|
/* Create a pty in which the process is running. */
|
|
if (init_pty(&pty, argv) < 0)
|
|
{
|
|
printf("%s: init_pty: %s\n", progname, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
/* Set up some signals. */
|
|
signal(SIGPIPE, SIG_IGN);
|
|
signal(SIGXFSZ, SIG_IGN);
|
|
signal(SIGHUP, SIG_IGN);
|
|
signal(SIGTTIN, SIG_IGN);
|
|
signal(SIGTTOU, SIG_IGN);
|
|
signal(SIGINT, die);
|
|
signal(SIGTERM, die);
|
|
signal(SIGCHLD, die);
|
|
|
|
/* Close the original terminal. We are now a daemon. */
|
|
fclose(stdin);
|
|
fclose(stdout);
|
|
fclose(stderr);
|
|
|
|
/* Set a trap to unlink the socket when we die */
|
|
atexit(unlink_socket);
|
|
|
|
/* Set up the poll structures. Slot 0 is the control socket, slot 1
|
|
** is the pty, and slot 2 .. n is the connected clients. */
|
|
polls[0].fd = s;
|
|
polls[0].events = POLLIN;
|
|
polls[0].revents = 0;
|
|
polls[1].fd = pty.fd;
|
|
polls[1].events = POLLIN;
|
|
polls[1].revents = 0;
|
|
num_polls = FIXED_SLOTS;
|
|
|
|
/* Loop forever. */
|
|
while (1)
|
|
{
|
|
/* Wait for something to happen. */
|
|
if (poll(polls, num_polls, -1) < 0)
|
|
{
|
|
if (errno == EINTR || errno == EAGAIN)
|
|
continue;
|
|
exit(1);
|
|
}
|
|
/* pty activity? */
|
|
if (polls[1].revents != 0)
|
|
pty_activity(&pty);
|
|
/* New client? */
|
|
if (polls[0].revents != 0)
|
|
control_activity(s);
|
|
/* Activity on a client? */
|
|
for (i = 2; i < num_polls; ++i)
|
|
{
|
|
if (polls[i].revents != 0)
|
|
client_activity(i, &pty);
|
|
}
|
|
}
|
|
}
|
|
|
|
int
|
|
master_main(char **argv)
|
|
{
|
|
int s;
|
|
pid_t pid;
|
|
|
|
/* Create the unix domain socket. */
|
|
s = create_socket(sockname);
|
|
if (s < 0)
|
|
{
|
|
printf("%s: %s: %s\n", progname, sockname, strerror(errno));
|
|
return 1;
|
|
}
|
|
|
|
/* Fork off so we can daemonize and such */
|
|
pid = fork();
|
|
if (pid < 0)
|
|
{
|
|
printf("%s: fork: %s\n", progname, strerror(errno));
|
|
return 1;
|
|
}
|
|
else if (pid == 0)
|
|
{
|
|
/* Child - this becomes the master */
|
|
master_process(s, argv);
|
|
return 0;
|
|
}
|
|
/* Parent - just return. */
|
|
return 0;
|
|
}
|
|
|
|
/* BSDish functions for systems that don't have them. */
|
|
#ifndef HAVE_OPENPTY
|
|
#define HAVE_OPENPTY
|
|
/* openpty: Use /dev/ptmx and Unix98 if we have it. */
|
|
#if defined(HAVE_PTSNAME) && defined(HAVE_GRANTPT) && defined(HAVE_UNLOCKPT)
|
|
int
|
|
openpty(int *amaster, int *aslave, char *name, struct termios *termp,
|
|
struct winsize *winp)
|
|
{
|
|
int master, slave;
|
|
char *buf;
|
|
|
|
master = open("/dev/ptmx", O_RDWR);
|
|
if (master < 0)
|
|
return -1;
|
|
if (grantpt(master) < 0)
|
|
return -1;
|
|
if (unlockpt(master) < 0)
|
|
return -1;
|
|
buf = ptsname(master);
|
|
if (!buf)
|
|
return -1;
|
|
|
|
slave = open(buf, O_RDWR|O_NOCTTY);
|
|
if (slave < 0)
|
|
return -1;
|
|
|
|
#ifdef I_PUSH
|
|
if (ioctl(slave, I_PUSH, "ptem") < 0)
|
|
return -1;
|
|
if (ioctl(slave, I_PUSH, "ldterm") < 0)
|
|
return -1;
|
|
#endif
|
|
|
|
*amaster = master;
|
|
*aslave = slave;
|
|
if (name)
|
|
strcpy(name, buf);
|
|
if (termp)
|
|
tcsetattr(slave, TCSAFLUSH, termp);
|
|
if (winp)
|
|
ioctl(slave, TIOCSWINSZ, winp);
|
|
return 0;
|
|
}
|
|
#else
|
|
#error Do not know how to define openpty.
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef HAVE_FORKPTY
|
|
#if defined(HAVE_OPENPTY)
|
|
pid_t
|
|
forkpty(int *amaster, char *name, struct termios *termp,
|
|
struct winsize *winp)
|
|
{
|
|
pid_t pid;
|
|
int master, slave;
|
|
|
|
if (openpty(&master, &slave, name, termp, winp) < 0)
|
|
return -1;
|
|
*amaster = master;
|
|
|
|
/* Fork off... */
|
|
pid = fork();
|
|
if (pid < 0)
|
|
return -1;
|
|
else if (pid == 0)
|
|
{
|
|
char *buf;
|
|
int fd;
|
|
|
|
setsid();
|
|
#ifdef TIOCSCTTY
|
|
buf = NULL;
|
|
if (ioctl(slave, TIOCSCTTY, NULL) < 0)
|
|
_exit(1);
|
|
#else
|
|
buf = ptsname(master);
|
|
fd = open(buf, O_RDWR);
|
|
close(fd);
|
|
#endif
|
|
dup2(slave, 0);
|
|
dup2(slave, 1);
|
|
dup2(slave, 2);
|
|
|
|
if (slave > 2)
|
|
close(slave);
|
|
close(master);
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
close(slave);
|
|
return pid;
|
|
}
|
|
}
|
|
#else
|
|
#error Do not know how to define forkpty.
|
|
#endif
|
|
#endif
|