summaryrefslogtreecommitdiff
path: root/master.c
blob: 0aad0cc868bd4a41971c07d0cdaeaa3acdae547c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
/*
    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)
	{
		close(s);
		return -1;
	}
	if (listen(s, 128) < 0)
	{
		close(s);
		return -1;
	}
	/* chmod it to prevent any suprises */
	if (chmod(name, 0600) < 0)
	{
		close(s);
		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. */
	close(s);
	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