0eaa9a5c3932b0782971236f4079074b2be730cc
[people/xl0/gpxe.git] / src / util / hijack.c
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <string.h>
5 #include <stdarg.h>
6 #include <errno.h>
7 #include <fcntl.h>
8 #include <libgen.h>
9 #include <signal.h>
10 #include <net/if.h>
11 #include <sys/select.h>
12 #include <sys/socket.h>
13 #include <sys/stat.h>
14 #include <sys/un.h>
15 #include <syslog.h>
16 #include <getopt.h>
17 #include <pcap.h>
18
19 #define SNAPLEN 1600
20
21 struct hijack {
22         pcap_t *pcap;
23         int fd;
24 };
25
26 struct hijack_listener {
27         struct sockaddr_un sun;
28         int fd;
29 };
30
31 struct hijack_options {
32         char interface[IF_NAMESIZE];
33         int daemonise;
34 };
35
36 static int daemonised = 0;
37
38 /**
39  * Log error message
40  *
41  */
42 static void logmsg ( int level, const char *format, ... ) {
43         va_list ap;
44
45         va_start ( ap, format );
46         if ( daemonised ) {
47                 vsyslog ( ( LOG_DAEMON | level ), format, ap );
48         } else {
49                 vfprintf ( stderr, format, ap );
50         }
51         va_end ( ap );
52 }
53
54 /**
55  * Open pcap device
56  *
57  */
58 static int hijack_open ( const char *interface, struct hijack *hijack ) {
59         char errbuf[PCAP_ERRBUF_SIZE];
60
61         /* Open interface via pcap */
62         errbuf[0] = '\0';
63         hijack->pcap = pcap_open_live ( interface, SNAPLEN, 1, 0, errbuf );
64         if ( ! hijack->pcap ) {
65                 logmsg ( LOG_ERR, "Failed to open %s: %s\n",
66                          interface, errbuf );
67                 goto err;
68         }
69         if ( errbuf[0] )
70                 logmsg ( LOG_WARNING, "Warning: %s\n", errbuf );
71
72         /* Set capture interface to non-blocking mode */
73         if ( pcap_setnonblock ( hijack->pcap, 1, errbuf ) < 0 ) {
74                 logmsg ( LOG_ERR, "Could not make %s non-blocking: %s\n",
75                          interface, errbuf );
76                 goto err;
77         }
78
79         /* Get file descriptor for select() */
80         hijack->fd = pcap_get_selectable_fd ( hijack->pcap );
81         if ( hijack->fd < 0 ) {
82                 logmsg ( LOG_ERR, "Cannot get selectable file descriptor "
83                          "for %s\n", interface );
84                 goto err;
85         }
86
87         return 0;
88
89  err:
90         if ( hijack->pcap )
91                 pcap_close ( hijack->pcap );
92         return -1;
93 }
94
95 /**
96  * Close pcap device
97  *
98  */
99 static void hijack_close ( struct hijack *hijack ) {
100         pcap_close ( hijack->pcap );
101 }
102
103 /**
104  * Forward data from hijacker
105  *
106  */
107 static ssize_t forward_from_hijacker ( struct hijack *hijack, int fd ) {
108         char buf[SNAPLEN];
109         ssize_t len;
110
111         len = read ( fd, buf, sizeof ( buf ) );
112         if ( len < 0 ) {
113                 logmsg ( LOG_ERR, "read from hijacker failed: %s\n",
114                          strerror ( errno ) );
115                 return -1;
116         }
117
118         if ( len == 0 )
119                 return 0;
120
121         if ( write ( hijack->fd, buf, len ) != len ) {
122                 logmsg ( LOG_ERR, "write to hijacked port failed: %s\n",
123                          strerror ( errno ) );
124                 return -1;
125         }
126
127         logmsg ( LOG_INFO, "forwarded %zd bytes from hijacker\n", len );
128
129         return len;
130 };
131
132 /**
133  * Forward data to hijacker
134  *
135  */
136 static ssize_t forward_to_hijacker ( int fd, struct hijack *hijack ) {
137         char buf[SNAPLEN];
138         ssize_t len;
139
140         len = read ( hijack->fd, buf, sizeof ( buf ) );
141         if ( len < 0 ) {
142                 logmsg ( LOG_ERR, "read from hijacked port failed: %s\n",
143                          strerror ( errno ) );
144                 return -1;
145         }
146
147         if ( len == 0 )
148                 return 0;
149
150         if ( write ( fd, buf, len ) != len ) {
151                 logmsg ( LOG_ERR, "write to hijacker failed: %s\n",
152                          strerror ( errno ) );
153                 return -1;
154         }
155
156         logmsg ( LOG_INFO, "forwarded %zd bytes to hijacker\n", len );
157
158         return len;
159 };
160
161
162 /**
163  * Run hijacker
164  *
165  */
166 static int run_hijacker ( const char *interface, int fd ) {
167         struct hijack hijack;
168         fd_set fdset;
169         int max_fd;
170         ssize_t len;
171
172         memset ( &hijack, 0, sizeof ( hijack ) );
173
174         logmsg ( LOG_INFO, "new connection for %s\n", interface );
175
176         if ( hijack_open ( interface, &hijack ) < 0 )
177                 goto err;
178         
179         /* Do the forwarding */
180         max_fd = ( ( fd > hijack.fd ) ? fd : hijack.fd );
181         while ( 1 ) {
182                 /* Wait for available data */
183                 FD_ZERO ( &fdset );
184                 FD_SET ( fd, &fdset );
185                 FD_SET ( hijack.fd, &fdset );
186                 if ( select ( ( max_fd + 1 ), &fdset, NULL, NULL, 0 ) < 0 ) {
187                         logmsg ( LOG_ERR, "select failed: %s\n",
188                                  strerror ( errno ) );
189                         goto err;
190                 }
191                 if ( FD_ISSET ( fd, &fdset ) ) {
192                         len = forward_from_hijacker ( &hijack, fd );
193                         if ( len < 0 )
194                                 goto err;
195                         if ( len == 0 )
196                                 break;
197                 }
198                 if ( FD_ISSET ( hijack.fd, &fdset ) ) {
199                         len = forward_to_hijacker ( fd, &hijack );
200                         if ( len < 0 )
201                                 goto err;
202                         if ( len == 0 )
203                                 break;
204                 }
205         }
206
207         hijack_close ( &hijack );
208         logmsg ( LOG_INFO, "closed connection for %s\n", interface );
209
210         return 0;
211
212  err:
213         if ( hijack.pcap )
214                 hijack_close ( &hijack );
215         return -1;
216 }
217
218 /**
219  * Open listener socket
220  *
221  */
222 static int open_listener ( const char *interface,
223                            struct hijack_listener *listener ) {
224         
225         /* Create socket */
226         listener->fd = socket ( PF_UNIX, SOCK_SEQPACKET, 0 );
227         if ( listener->fd < 0 ) {
228                 logmsg ( LOG_ERR, "Could not create socket: %s\n",
229                          strerror ( errno ) );
230                 goto err;
231         }
232
233         /* Bind to local filename */
234         listener->sun.sun_family = AF_UNIX,
235         snprintf ( listener->sun.sun_path, sizeof ( listener->sun.sun_path ),
236                    "/var/run/hijack-%s", interface );
237         if ( bind ( listener->fd, ( struct sockaddr * ) &listener->sun,
238                     sizeof ( listener->sun ) ) < 0 ) {
239                 logmsg ( LOG_ERR, "Could not bind socket to %s: %s\n",
240                          listener->sun.sun_path, strerror ( errno ) );
241                 goto err;
242         }
243
244         /* Set as a listening socket */
245         if ( listen ( listener->fd, 0 ) < 0 ) {
246                 logmsg ( LOG_ERR, "Could not listen to %s: %s\n",
247                          listener->sun.sun_path, strerror ( errno ) );
248                 goto err;
249         }
250
251         return 0;
252         
253  err:
254         if ( listener->fd >= 0 )
255                 close ( listener->fd );
256         return -1;
257 }
258
259 /**
260  * Listen on listener socket
261  *
262  */
263 static int listen_for_hijackers ( struct hijack_listener *listener,
264                                   const char *interface ) {
265         int fd;
266         pid_t child;
267         int rc;
268
269         logmsg ( LOG_INFO, "Listening on %s\n", listener->sun.sun_path );
270
271         while ( 1 ) {
272                 /* Accept new connection */
273                 fd = accept ( listener->fd, NULL, 0 );
274                 if ( fd < 0 ) {
275                         logmsg ( LOG_ERR, "accept failed: %s\n",
276                                  strerror ( errno ) );
277                         goto err;
278                 }
279
280                 /* Fork child process */
281                 child = fork();
282                 if ( child < 0 ) {
283                         logmsg ( LOG_ERR, "fork failed: %s\n",
284                                  strerror ( errno ) );
285                         goto err;
286                 }
287                 if ( child == 0 ) {
288                         /* I am the child; run the hijacker */
289                         rc = run_hijacker ( interface, fd );
290                         close ( fd );
291                         exit ( rc );
292                 }
293                 
294                 close ( fd );
295         }
296
297         return 0;
298
299  err:
300         return -1;
301 }
302
303 /**
304  * Close listener socket
305  *
306  */
307 static void close_listener ( struct hijack_listener *listener ) {
308         close ( listener->fd );
309         unlink ( listener->sun.sun_path );
310 }
311
312 /**
313  * Print usage
314  *
315  */
316 static void usage ( char **argv ) {
317         logmsg ( LOG_ERR,
318                  "Usage: %s [options]\n"
319                  "\n"
320                  "Options:\n"
321                  "  -h|--help               Print this help message\n"
322                  "  -i|--interface intf     Use specified network interface\n"
323                  "  -n|--nodaemon           Run in foreground\n",
324                  argv[0] );
325 }
326
327 /**
328  * Parse command-line options
329  *
330  */
331 static int parse_options ( int argc, char **argv,
332                            struct hijack_options *options ) {
333         static struct option long_options[] = {
334                 { "interface", 1, NULL, 'i' },
335                 { "nodaemon", 0, NULL, 'n' },
336                 { "help", 0, NULL, 'h' },
337                 { },
338         };
339         int c;
340
341         /* Set default options */
342         memset ( options, 0, sizeof ( *options ) );
343         strncpy ( options->interface, "eth0", sizeof ( options->interface ) );
344         options->daemonise = 1;
345
346         /* Parse command-line options */
347         while ( 1 ) {
348                 int option_index = 0;
349                 
350                 c = getopt_long ( argc, argv, "i:hn", long_options,
351                                   &option_index );
352                 if ( c < 0 )
353                         break;
354
355                 switch ( c ) {
356                 case 'i':
357                         strncpy ( options->interface, optarg,
358                                   sizeof ( options->interface ) );
359                         break;
360                 case 'n':
361                         options->daemonise = 0;
362                         break;
363                 case 'h':
364                         usage( argv );
365                         return -1;
366                 case '?':
367                         /* Unrecognised option */
368                         return -1;
369                 default:
370                         logmsg ( LOG_ERR, "Unrecognised option '-%c'\n", c );
371                         return -1;
372                 }
373         }
374
375         /* Check there's nothing left over on the command line */
376         if ( optind != argc ) {
377                 usage ( argv );
378                 return -1;
379         }
380
381         return 0;
382 }
383
384 /**
385  * Daemonise
386  *
387  */
388 static int daemonise ( const char *interface ) {
389         char pidfile[16 + IF_NAMESIZE + 4]; /* "/var/run/hijack-<intf>.pid" */
390         char pid[16];
391         int pidlen;
392         int fd = -1;
393
394         /* Daemonise */
395         if ( daemon ( 0, 0 ) < 0 ) {
396                 logmsg ( LOG_ERR, "Could not daemonise: %s\n",
397                          strerror ( errno ) );
398                 goto err;
399         }
400         daemonised = 1; /* Direct messages to syslog now */
401
402         /* Open pid file */
403         snprintf ( pidfile, sizeof ( pidfile ), "/var/run/hijack-%s.pid",
404                    interface );
405         fd = open ( pidfile, ( O_WRONLY | O_CREAT | O_TRUNC ),
406                     ( S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH ) );
407         if ( fd < 0 ) {
408                 logmsg ( LOG_ERR, "Could not open %s for writing: %s\n",
409                          pidfile, strerror ( errno ) );
410                 goto err;
411         }
412
413         /* Write pid to file */
414         pidlen = snprintf ( pid, sizeof ( pid ), "%d\n", getpid() );
415         if ( write ( fd, pid, pidlen ) != pidlen ) {
416                 logmsg ( LOG_ERR, "Could not write %s: %s\n",
417                          pidfile, strerror ( errno ) );
418                 goto err;
419         }
420
421         close ( fd );
422         return 0;
423
424  err:
425         if ( fd >= 0 )
426                 close ( fd );
427         return -1;
428 }
429
430 int main ( int argc, char **argv ) {
431         struct hijack_options options;
432         struct hijack_listener listener;
433         struct sigaction sigchld;
434
435         /* Parse command-line options */
436         if ( parse_options ( argc, argv, &options ) < 0 )
437                 exit ( 1 );
438
439         /* Set up syslog connection */
440         openlog ( basename ( argv[0] ), LOG_PID, LOG_DAEMON );
441
442         /* Set up listening socket */
443         if ( open_listener ( options.interface, &listener ) < 0 )
444                 exit ( 1 );
445
446         /* Daemonise on demand */
447         if ( options.daemonise ) {
448                 if ( daemonise ( options.interface ) < 0 )
449                         exit ( 1 );
450         }
451
452         /* Avoid creating zombies */
453         memset ( &sigchld, 0, sizeof ( sigchld ) );
454         sigchld.sa_handler = SIG_IGN;
455         sigchld.sa_flags = SA_NOCLDWAIT;
456         if ( sigaction ( SIGCHLD, &sigchld, NULL ) < 0 ) {
457                 logmsg ( LOG_ERR, "Could not set signal handler: %s",
458                          strerror ( errno ) );
459                 exit ( 1 );
460         }
461
462         /* Listen for hijackers */
463         if ( listen_for_hijackers ( &listener, options.interface ) < 0 )
464                 exit ( 1 );
465
466         close_listener ( &listener );
467         
468         return 0;
469 }