diff options
| author | erdgeist <> | 2013-03-01 23:00:32 +0000 |
|---|---|---|
| committer | erdgeist <> | 2013-03-01 23:00:32 +0000 |
| commit | 1581102195ece168cc6b3c862ec3cc19fbb8f906 (patch) | |
| tree | c2e3dba05e5ca8e8431bb5f550ac0d4917fc99ae | |
Kickoff
| -rw-r--r-- | Makefile | 11 | ||||
| -rw-r--r-- | jaildaemon.c | 569 | ||||
| -rwxr-xr-x | jaildaemon.sh | 36 |
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 @@ | |||
| 1 | CFLAGS+=-Wall -Wextra -pedantic -Os | ||
| 2 | |||
| 3 | jaildaemon: jaildaemon.c | ||
| 4 | $(CC) -o $@ $< $(CFLAGS) $(LDFLAGS) | ||
| 5 | |||
| 6 | .PHONY: clean test | ||
| 7 | clean: | ||
| 8 | rm -f jaildaemon | ||
| 9 | |||
| 10 | test: 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 | ||
| 18 | enum { IAM_DAEMON, IAM_CLIENT, IAM_FORKSLAVE }; | ||
| 19 | static int g_uds; | ||
| 20 | static int g_whoami = IAM_CLIENT; | ||
| 21 | static int g_fork_slave_fd; | ||
| 22 | static char g_ipc_packet[IPC_PACKETSIZE]; | ||
| 23 | static int * const g_ipc_packet_int = (int*)g_ipc_packet; | ||
| 24 | |||
| 25 | typedef struct { | ||
| 26 | int m_jid; | ||
| 27 | int m_flags; | ||
| 28 | char *m_commandline; | ||
| 29 | char *m_proctitle; | ||
| 30 | } daemon_task; | ||
| 31 | |||
| 32 | /* Forward declarations */ | ||
| 33 | static void signal_handler( int signal ); | ||
| 34 | static int check_for_jail( int jid ); | ||
| 35 | static int copy_daemontask( daemon_task ** out, daemon_task * const in ); | ||
| 36 | static int add_task_to_kqueue( int kq, daemon_task * task_in ); | ||
| 37 | static pid_t fork_and_jail( int jid, char * proctitle ); | ||
| 38 | static void fork_and_execve( int kq, daemon_task * task ); | ||
| 39 | static int fork_fork_slave( ); | ||
| 40 | static void exerr( char * message ); | ||
| 41 | static void warn( char * message ); | ||
| 42 | static 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 */ | ||
| 49 | static 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. */ | ||
| 56 | static 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 */ | ||
| 73 | static void warn( char * message ) { | ||
| 74 | syslog( LOG_WARNING, "%s\n", message ); | ||
| 75 | } | ||
| 76 | |||
| 77 | /* Report syntax of command line arguments to the user */ | ||
| 78 | static 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. */ | ||
| 88 | static 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 | |||
| 124 | static 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 */ | ||
| 151 | static 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 | |||
| 165 | static 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 | |||
| 192 | static 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 | |||
| 216 | static 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 | |||
| 271 | static 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 | */ | ||
| 320 | int 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 | |||
| 3 | echo Need a running jail with jid 1 for this test. Enter to proceed | ||
| 4 | read a | ||
| 5 | |||
| 6 | killall jaildaemon | ||
| 7 | ./jaildaemon -D | ||
| 8 | |||
| 9 | # daemon comes back too fast, so uds is not yet open | ||
| 10 | sleep 1 | ||
| 11 | ./jaildaemon -j 1 -c '/usr/bin/touch /foo' -t Schnupsi -r | ||
| 12 | |||
| 13 | echo -n "Killing (respawnin) task: " | ||
| 14 | pgrep -f Schnupsi | ||
| 15 | |||
| 16 | pkill -HUP -f Schnupsi | ||
| 17 | |||
| 18 | echo "Yielding file, touched by task:" | ||
| 19 | ls -al /foo | ||
| 20 | |||
| 21 | echo "Deleting file" | ||
| 22 | rm -r /foo | ||
| 23 | |||
| 24 | echo | ||
| 25 | echo -n "New task id: " | ||
| 26 | pgrep -f Schnupsi | ||
| 27 | |||
| 28 | echo "Killing with normal signal:" | ||
| 29 | |||
| 30 | pkill -f Schnupsi | ||
| 31 | |||
| 32 | echo "Expecting no file here:" | ||
| 33 | ls -al /foo | ||
| 34 | |||
| 35 | echo "Also expecting no command to match:" | ||
| 36 | pgrep -f Schnupsi | ||
