summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorerdgeist <>2013-03-01 23:00:32 +0000
committererdgeist <>2013-03-01 23:00:32 +0000
commit1581102195ece168cc6b3c862ec3cc19fbb8f906 (patch)
treec2e3dba05e5ca8e8431bb5f550ac0d4917fc99ae
Kickoff
-rw-r--r--Makefile11
-rw-r--r--jaildaemon.c569
-rwxr-xr-xjaildaemon.sh36
3 files changed, 616 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4db0245
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,11 @@
1CFLAGS+=-Wall -Wextra -pedantic -Os
2
3jaildaemon: jaildaemon.c
4 $(CC) -o $@ $< $(CFLAGS) $(LDFLAGS)
5
6.PHONY: clean test
7clean:
8 rm -f jaildaemon
9
10test: jaildaemon
11 sudo sh ./jaildaemon.sh
diff --git a/jaildaemon.c b/jaildaemon.c
new file mode 100644
index 0000000..ed36531
--- /dev/null
+++ b/jaildaemon.c
@@ -0,0 +1,569 @@
1#include <sys/socket.h>
2#include <sys/un.h>
3#include <sys/types.h>
4#include <sys/event.h>
5#include <sys/param.h>
6#include <sys/jail.h>
7#include <sys/wait.h>
8#include <errno.h>
9#include <stdio.h>
10#include <stdlib.h>
11#include <string.h>
12#include <signal.h>
13#include <unistd.h>
14#include <syslog.h>
15
16#define IPC_PACKETSIZE 4096
17#define MAGIC_EXIT_CODE 42
18enum { IAM_DAEMON, IAM_CLIENT, IAM_FORKSLAVE };
19static int g_uds;
20static int g_whoami = IAM_CLIENT;
21static int g_fork_slave_fd;
22static char g_ipc_packet[IPC_PACKETSIZE];
23static int * const g_ipc_packet_int = (int*)g_ipc_packet;
24
25typedef struct {
26 int m_jid;
27 int m_flags;
28 char *m_commandline;
29 char *m_proctitle;
30} daemon_task;
31
32/* Forward declarations */
33static void signal_handler( int signal );
34static int check_for_jail( int jid );
35static int copy_daemontask( daemon_task ** out, daemon_task * const in );
36static int add_task_to_kqueue( int kq, daemon_task * task_in );
37static pid_t fork_and_jail( int jid, char * proctitle );
38static void fork_and_execve( int kq, daemon_task * task );
39static int fork_fork_slave( );
40static void exerr( char * message );
41static void warn( char * message );
42static void usage( char * command );
43
44/* This is the handler installed in the jailed process.
45 It will exit with the proper exit code to make the
46 host system daemon recognize the process has
47 deliberately killed itself and was not just shutdown
48 with the jail */
49static void signal_handler( int signal ) {
50 if( signal == SIGHUP )
51 _exit( MAGIC_EXIT_CODE );
52}
53
54/* Report error through the appropriate notification channel.
55 Currently this just writes to stderr, which hopefully still is there. */
56static void exerr( char * message ) {
57 switch( g_whoami ) {
58 case IAM_DAEMON:
59 syslog( LOG_ERR, "Error %s\n", message );
60 break;
61 case IAM_CLIENT:
62 fprintf( stderr, "Error %s\n", message );
63 break;
64 case IAM_FORKSLAVE:
65 /* TODO */
66 (void)message;
67 break;
68 }
69 exit( 11 );
70}
71
72/* Report a non-fatal situation */
73static void warn( char * message ) {
74 syslog( LOG_WARNING, "%s\n", message );
75}
76
77/* Report syntax of command line arguments to the user */
78static void usage( char * cmd ) {
79 fprintf( stderr,
80 "%s -D [-ppidfile] [-fipcsockpath]\n"
81 "%s -c command -j jid [-t proctitle] [-r]\n", cmd, cmd );
82 exit( 1 );
83}
84
85/* This fork slave is an extra process that is spawned very early so that we do
86 not leak information into the jail via copied memory. For communication the
87 fork slave keeps a bi-directional pipe open to the daemon. */
88static void fork_slave( int master_fd ) {
89 struct sigaction sa;
90 /* explain why the user sees two processes in ps */
91 setproctitle( "fork slave" );
92
93 /* We do not care for the spawned process -- it is checked for in the
94 daemons kqueue filter. So just ignore SIGCHLD */
95 memset( &sa, 0, sizeof( sa ) );
96 sigemptyset(&sa.sa_mask);
97 sa.sa_flags = SA_NOCLDWAIT;
98 if( sigaction(SIGCHLD, &sa, NULL) == -1 )
99 exerr( "when trying to enable auto reap" );
100
101 /* Wait for command from master */
102 while(1) {
103 switch( read( master_fd, g_ipc_packet, sizeof(g_ipc_packet) ) ) {
104 case -1:
105 exerr( "reading commands from master's socket" );
106 case IPC_PACKETSIZE:
107 /* Decode packet and throw a forked child */
108 *(pid_t*)g_ipc_packet = fork_and_jail(
109 g_ipc_packet_int[0], g_ipc_packet + sizeof(int) );
110 if( write( master_fd, g_ipc_packet, sizeof(pid_t) ) !=
111 sizeof(pid_t) )
112 exerr( "replying to master" );
113 break;
114 case 0:
115 /* Remote end closed, bye */
116 exit(0);
117 default:
118 exerr( "ignoring corrupt command packet" );
119 break;
120 }
121 }
122}
123
124static int fork_fork_slave( ) {
125 int sockets[2];
126
127 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sockets) < 0)
128 exerr("opening stream socket pair");
129
130 switch( fork() ) {
131 case -1:
132 exerr("forking fork slave");
133 break;
134 case 0:
135 /* I am child, close master's socket fd */
136 close( sockets[0] );
137 g_whoami = IAM_FORKSLAVE;
138 fork_slave( sockets[1] ); /* Never returns */
139 exit(0);
140 default:
141 /* I am master, close child's socket fd */
142 close( sockets[1] );
143 return sockets[0];
144 }
145
146 /* Should not happen*/
147 return -1;
148}
149
150/* Helper function to check if a jail id is valid */
151static int check_for_jail( int jid ) {
152 struct iovec iov[2];
153
154 iov[0].iov_base = "jid\0";
155 iov[0].iov_len = 4;
156 iov[1].iov_base = &jid;
157 iov[1].iov_len = sizeof(jid);
158
159 if( jail_get( iov, 2, 0 ) != -1 )
160 return 0;
161
162 return -1;
163}
164
165static pid_t fork_and_jail( int jid, char * proctitle ) {
166 pid_t pid = fork();
167 if( !pid ) {
168 struct sigaction sa;
169
170 /* Set proctitle so that jail's pgrep -f can identify the process */
171 if( proctitle && *proctitle )
172 setproctitle( "%s", proctitle );
173
174 /* Setup signal handler for SIGHUP */
175 sa.sa_handler = signal_handler;
176 sigemptyset(&sa.sa_mask);
177 sa.sa_flags = SA_RESTART;
178 if( sigaction(SIGHUP, &sa, NULL) == -1 )
179 exerr( "when install signal handler" );
180
181 /* Throw ourself into the jail */
182 if( jail_attach( jid ) )
183 exerr( "when attaching to jail" );
184
185 /* Spin and wait for SIGHUP */
186 while( 1 )
187 sleep(32);
188 }
189 return pid;
190}
191
192static int copy_daemontask( daemon_task ** out, daemon_task * const in ) {
193 daemon_task * t = (daemon_task *)malloc( sizeof( daemon_task ) );
194 *out = t;
195 if( !t ) return -1;
196
197 t->m_jid = in->m_jid;
198 t->m_flags = in->m_flags;
199 t->m_commandline = in->m_commandline ? strdup( in->m_commandline ): 0;
200 t->m_proctitle = in->m_proctitle ? strdup( in->m_proctitle ) : 0;
201
202 /* If all strings could be copied, return array */
203 if( ( !in->m_commandline || t->m_commandline ) &&
204 ( !in->m_proctitle || t->m_proctitle ) )
205 return 0;
206
207 free( t->m_commandline );
208 free( t->m_proctitle );
209 free( t );
210
211 *out = 0;
212
213 return -1;
214}
215
216static void fork_and_execve( int kq, daemon_task * t_in ) {
217 char * shell = "/bin/sh";
218 char * envp[] = { "PATH=/bin:/sbin:/usr/bin:/usr/sbin", NULL };
219 pid_t pid;
220
221 pid = fork();
222
223 switch( pid ) {
224 case -1:
225 warn("Failed forking command line process" );
226 break;
227 case 0:
228 /* start a new process group */
229 (void) setsid();
230
231 /* Execute command line provided by user */
232 if( execle(shell, shell, "-c", t_in->m_commandline, (char *)0, envp)
233 == -1 )
234 _exit(0);
235 /* Never reached */
236 break;
237 default:
238 /* If no respawn requested, just let the command finish */
239 if( !(t_in->m_flags & 0x01) )
240 return;
241
242 /* else add process to our process watch list, so we get notified,
243 once it finishes to be able to respawn. ("else" to open block) */
244 else {
245 struct kevent ke;
246 daemon_task * t;
247
248 /* Try to take a copy of task struct. If this fails, then only
249 respawn fails. */
250 if( copy_daemontask( &t, t_in ) )
251 return;
252
253 /* Signal that this is a process that shall respawn the task
254 in jail */
255 t->m_flags |= 0x02;
256
257 memset( &ke, 0, sizeof ke );
258 EV_SET( &ke, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, t );
259 if( kevent( kq, &ke, 1, NULL, 0, NULL ) == -1 ) {
260 /* If adding the event fails, get rid of struct */
261 warn( "Can not put respawn watcher pid on the kqueue" );
262 free( t->m_commandline );
263 free( t->m_proctitle );
264 free( t );
265 }
266 }
267 break;
268 }
269}
270
271static int add_task_to_kqueue( int kq, daemon_task * t_in ) {
272 struct kevent ke;
273 daemon_task * t;
274 pid_t pid;
275
276 if( check_for_jail( t_in->m_jid ) ) {
277 syslog( LOG_ERR, "Invalid jail id: %d", t_in->m_jid );
278 return -1;
279 }
280
281 /* Take a copy of the task structure */
282 if( copy_daemontask( &t, t_in ) )
283 return -1;
284
285 /* Forge a command packet for fork slave and send it via control socket */
286 memset( g_ipc_packet, 0, IPC_PACKETSIZE );
287 g_ipc_packet_int[0] = t->m_jid;
288 if( t->m_proctitle )
289 strncpy( g_ipc_packet + sizeof(int), t->m_proctitle,
290 IPC_PACKETSIZE - sizeof(int) );
291 if( write( g_fork_slave_fd, g_ipc_packet, IPC_PACKETSIZE ) !=
292 IPC_PACKETSIZE )
293 exerr( "sending task to fork slave" );
294
295 if( read( g_fork_slave_fd, g_ipc_packet, sizeof(pid_t) ) <
296 (ssize_t)sizeof(pid_t) )
297 exerr( "receiving pid from fork slave" );
298
299 /* Expect reply from fork slave */
300 pid = *(pid_t*)g_ipc_packet;
301
302 /* Associate pid with command line to execute and add to our kqueue */
303 memset( &ke, 0, sizeof ke );
304 EV_SET( &ke, pid, EVFILT_PROC, EV_ADD, NOTE_EXIT, 0, t );
305 if( kevent( kq, &ke, 1, NULL, 0, NULL ) == 0 )
306 return 0;
307
308 /* Avoid an unused task in the jail. Kill it. */
309 warn( "Can not put pid on the kqueue. Killing task." );
310 kill( pid, SIGKILL );
311
312 free( t->m_commandline );
313 free( t->m_proctitle );
314 free( t );
315 return -1;
316}
317
318/* jaildaemon -D <-ppidfile> <-fipcsockpath> -c command -j jid -t proctitle <-r>
319 */
320int main( int argc, char **argv ) {
321 int kq, i;
322 int o_force_daemon = 0;
323 int o_daemonize = 0, o_jid = -1, o_respawn = 0;
324 char *o_command = NULL, *o_pidfile = NULL, *o_proctitle = NULL;
325 char *o_uds_path = "/var/run/jaildaemon";
326 struct kevent ke;
327 struct sockaddr_un addr;
328 struct sigaction sa;
329 size_t ipc_bytes = IPC_PACKETSIZE;
330
331 /* If we are not started from root, there is not much we can do,
332 neither access the unix domain socket.*/
333 if( getuid() != 0 )
334 exerr( "when starting. Need to run as root." );
335
336 i=1;
337 while(i) {
338 switch( getopt( argc, argv, "Drt:c:j:p:f:" ) ) {
339 case -1: i=0; break;
340 case 'D': o_daemonize = 1; break;
341 case 'r': o_respawn = 1; break;
342 case 't': o_proctitle = optarg; break;
343 case 'c': o_command = optarg; break;
344 case 'j': o_jid = strtol( optarg, 0, 0 ); break;
345 case 'p': o_pidfile = optarg; break;
346 case 'f': o_uds_path = optarg; break;
347 case '?': usage( argv[0]); exit(0); break;
348 }
349 }
350
351 /* Start a fork slave while there is no file descriptors or initialized
352 memory yet. Communicate with this slave via socketpair */
353 if( o_daemonize ) {
354 if( daemon(0,0) == -1 )
355 exerr( "daemonzing" );
356 g_fork_slave_fd = fork_fork_slave( );
357
358 openlog( "jaildaemon", 0, LOG_DAEMON );
359 setlogmask(LOG_UPTO(LOG_INFO));
360 g_whoami = IAM_DAEMON;
361
362 } else {
363 /* Need a command line, and jid if not a daemon */
364 if( !o_command || o_jid <= 0 )
365 usage( argv[0] );
366 }
367
368 /* Setup unix domain socket descriptors */
369 g_uds = socket(AF_UNIX, SOCK_DGRAM, 0);
370 if( g_uds < 0 )
371 exerr( "Can not create control channel." );
372
373 if(1) {
374 size_t packet_size = 2 * IPC_PACKETSIZE;
375 socklen_t pss = sizeof(packet_size);
376 /* Allow huge packets on our unix domain socket */
377 setsockopt( g_uds, SOL_SOCKET, SO_SNDBUF, &packet_size, pss );
378 setsockopt( g_uds, SOL_SOCKET, SO_RCVBUF, &packet_size, pss );
379 }
380 memset(&addr, 0, sizeof(addr));
381 addr.sun_family = AF_UNIX;
382 strncpy(addr.sun_path, o_uds_path, sizeof(addr.sun_path)-1);
383
384 if( !o_daemonize ) {
385 /* If we're not supposed to daemonize, just try to pipe the
386 request to the daemon already running and exit
387
388 Packed packet format:
389 int m_flags ( 0x01 respawn, 0x02 executing, to be respawned )
390 int m_jid
391 int m_commandline_length
392 int m_proctitle_length
393 char[] command_line \0
394 char[] proctitle \0
395 */
396 size_t o_command_len = strlen(o_command);
397 size_t o_proctitle_len = o_proctitle ? strlen( o_proctitle ) : 0;
398 char *text_off = (char*)(g_ipc_packet_int + 4);
399
400 if( text_off + 2 + o_command_len + o_proctitle_len >
401 g_ipc_packet + IPC_PACKETSIZE )
402 exerr( "Command line and proc title too long" );
403
404 g_ipc_packet_int[0] = o_respawn;
405 g_ipc_packet_int[1] = o_jid;
406 g_ipc_packet_int[2] = o_command_len;
407 g_ipc_packet_int[3] = o_proctitle_len;
408 memcpy( text_off, o_command, o_command_len + 1 );
409 if( o_proctitle_len ) {
410 text_off += o_command_len + 1;
411 strncpy( text_off, o_proctitle, o_proctitle_len + 1 );
412 }
413
414 ipc_bytes = sendto( g_uds, g_ipc_packet, IPC_PACKETSIZE, 0,
415 (struct sockaddr*)&addr, sizeof(addr) );
416 if( ipc_bytes != IPC_PACKETSIZE )
417 exerr( "sending command to daemon. Maybe it is not running?" );
418
419 exit(0);
420 }
421
422 /* Send test DGRAM through the unix domain socket. If this succeeds, there
423 likely is another daemon already listening. You have to force the daemon
424 to start in this case */
425 if( sendto( g_uds, g_ipc_packet, IPC_PACKETSIZE, 0,
426 (struct sockaddr*)&addr, sizeof(addr) ) == 0 ) {
427 /* TODO: Force not implemented yet */
428 if( !o_force_daemon )
429 exerr( "Found command channel. Refusing to overwrite a working one."
430 " Another server may be running. Force with -f.");
431 else
432 warn( "Forcing start of daemon despite working command channel." );
433 }
434
435 /* Create the unix domain socket to receive commands on */
436 unlink(o_uds_path);
437 if (bind(g_uds, (struct sockaddr*)&addr, sizeof(addr)) == -1)
438 exerr( "binding to command channel. Maybe another daemon is running?" );
439
440 /* We do not care for the spawned process -- it is checked for in our
441 kqueue filter. So just ignore SIGCHLD */
442 memset( &sa, 0, sizeof( sa ) );
443 sigemptyset(&sa.sa_mask);
444 sa.sa_flags = SA_NOCLDWAIT;
445 if( sigaction(SIGCHLD, &sa, NULL) == -1 )
446 exerr( "when trying to enable auto reap" );
447
448 /* Create our kqueue */
449 if( ( kq = kqueue( ) ) == -1 )
450 exerr( "when create kqueue" );
451
452 /* Add our command uds to our kevent list */
453 memset( &ke, 0, sizeof(ke) );
454 EV_SET( &ke, g_uds, EVFILT_READ, EV_ADD, 0, 0, 0);
455 kevent( kq, &ke, 1, NULL, 0, NULL );
456
457 /* We want to be notified if our command uds is removed, so we can quit */
458 EV_SET( &ke, g_uds, EVFILT_VNODE, EV_ADD, NOTE_DELETE | NOTE_LINK, 0, 0);
459 kevent( kq, &ke, 1, NULL, 0, NULL );
460
461 /* If daemon was started with some initial script, fire it now
462 -- this leaks some information in the command line to all jails an
463 thus is disabled
464 if( o_command ) {
465 daemon_task task;
466 task.m_jid = o_jid;
467 task.m_flags = o_respawn ? 0x01 : 0x00;
468 task.m_commandline = o_command;
469 task.m_proctitle = o_proctitle;
470 add_task_to_kqueue( kq, &task );
471 }
472 */
473
474 /* Main loop */
475 while( 1 ) {
476 memset( &ke, 0, sizeof(ke) );
477 switch( kevent( kq, NULL, 0, &ke, 1, NULL ) ) {
478 case -1:
479 if( errno == EINTR )
480 continue;
481 exerr( "when reading from kqueue" );
482 case 0:
483 continue;
484 default:
485 /* We should only see one event, because we asked for 1 */
486 break;
487 }
488
489 switch( ke.filter ) {
490 case EVFILT_PROC:
491 if( ke.fflags & NOTE_EXIT ) {
492 daemon_task * task = (daemon_task *)ke.udata;
493 if( !task )
494 continue;
495
496 /* If this task was watched to respawn a daemon in the jail,
497 do it now */
498 if( task->m_flags & 0x02 ) {
499 task->m_flags &= ~0x02;
500 add_task_to_kqueue( kq, task );
501
502 /* If the process exited with the correct magic code,
503 execute the associated command */
504 } else if( WEXITSTATUS(ke.data) == MAGIC_EXIT_CODE )
505 fork_and_execve( kq, task );
506
507 free( task->m_commandline );
508 free( task->m_proctitle );
509 free( task );
510
511 /* Remove process filter from kqueue */
512 EV_SET( &ke, ke.ident, EVFILT_PROC, EV_DELETE, NOTE_EXIT,
513 0, NULL );
514 kevent( kq, &ke, 1, NULL, 0, NULL );
515 }
516 break;
517 case EVFILT_READ:
518 if( (int)ke.ident == g_uds ) {
519 char *text_off = (char*)(g_ipc_packet_int + 4);
520 socklen_t fromlen;
521 daemon_task task;
522
523 /* Some data arrived at our admin pipe, parse the request.
524 If the format is not recognized, throw away the complete
525 request */
526 ipc_bytes =
527 recvfrom(g_uds, g_ipc_packet, sizeof g_ipc_packet, 0,
528 (struct sockaddr*)&addr, &fromlen);
529
530 /* parse request, fail immediately for any packet not of
531 size IPC_PACKETSIZE */
532 if( ipc_bytes != IPC_PACKETSIZE )
533 continue;
534
535 task.m_flags = g_ipc_packet_int[0];
536 task.m_jid = g_ipc_packet_int[1];
537 task.m_commandline = text_off;
538 text_off += g_ipc_packet_int[2];
539
540 /* Sanity check on string length, expect terminator */
541 if( text_off > (char *)( g_ipc_packet + IPC_PACKETSIZE ) ||
542 *text_off ) {
543 warn( "Received invalid command packet" );
544 continue;
545 }
546
547 task.m_proctitle = g_ipc_packet_int[3] ? ++text_off : 0;
548 text_off += g_ipc_packet_int[3];
549
550 /* Sanity check on string length, expect terminator */
551 if( text_off > (char *)(g_ipc_packet + IPC_PACKETSIZE) ||
552 *text_off ) {
553 warn( "Received invalid command packet" );
554 continue;
555 }
556
557 /* Takes a copy of our task and all string members */
558 add_task_to_kqueue( kq, &task );
559 }
560 break;
561 case EVFILT_VNODE:
562 if( (int)ke.ident == g_uds && ke.fflags == NOTE_DELETE )
563 exerr( "Control channel was deleted. Quitting." );
564 break;
565 default:
566 break;
567 }
568 }
569}
diff --git a/jaildaemon.sh b/jaildaemon.sh
new file mode 100755
index 0000000..0741c23
--- /dev/null
+++ b/jaildaemon.sh
@@ -0,0 +1,36 @@
1#!/bin/sh
2
3echo Need a running jail with jid 1 for this test. Enter to proceed
4read a
5
6killall jaildaemon
7./jaildaemon -D
8
9# daemon comes back too fast, so uds is not yet open
10sleep 1
11./jaildaemon -j 1 -c '/usr/bin/touch /foo' -t Schnupsi -r
12
13echo -n "Killing (respawnin) task: "
14pgrep -f Schnupsi
15
16pkill -HUP -f Schnupsi
17
18echo "Yielding file, touched by task:"
19ls -al /foo
20
21echo "Deleting file"
22rm -r /foo
23
24echo
25echo -n "New task id: "
26pgrep -f Schnupsi
27
28echo "Killing with normal signal:"
29
30pkill -f Schnupsi
31
32echo "Expecting no file here:"
33ls -al /foo
34
35echo "Also expecting no command to match:"
36pgrep -f Schnupsi