source: trunk/instant_messenger/socket/BKP_20071105/BKP_20071026/BKP_20071019/proxy.c @ 151

Revision 151, 14.6 KB checked in by niltonneto, 16 years ago (diff)

Commit da nova versão do módulo, usando agente em C.
Vide Página do módulo do Trac:
http://www.expressolivre.org/dev/wiki/messenger

A versão anterior encontra-se na subpasta bkp (32/64).

Line 
1#include <stdio.h>
2#include <stdlib.h>
3#include <string.h>
4#include <sys/socket.h>
5#include <arpa/inet.h>
6#include <netdb.h>
7#include <unistd.h>
8#include <signal.h>
9#include <errno.h>
10#include <sys/wait.h>
11#include <sys/stat.h>
12#include <stdarg.h>
13#include <syslog.h>
14
15/* Buffer size, in bytes, used for copying in the Cat() function. */
16const size_t BUF_SIZE = 10240;
17
18/* Typedef for a signal handler function. */
19typedef void (*signal_handler_t) (int);
20
21/* Prototypes for functions in this file. */
22void ParseArgs (int argc, char *argv[], struct in_addr *ra,
23                unsigned short *rp, struct in_addr *la, unsigned short *lp);
24void Initialise (void);
25void sig_child (int signo);
26int CreateServerSocket (struct in_addr addr, unsigned short port);
27void Daemonise (void);
28void MainLoop (int listen_fd, struct in_addr rem_addr,
29                unsigned short rem_port);
30void Proxy (int server_fd, int client_fd);
31int AcceptClientConnection (int listen_fd);
32int ConnectToServer (struct in_addr addr, unsigned short port);
33void Cat (int in_fd, int out_fd);
34int NameToAddr (const char *name, struct in_addr *p_inaddr);
35int NameToPort (const char *name, unsigned short *port, const char *proto);
36#ifdef __GNUC__
37void quit (const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
38void pbomb (const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
39void hbomb (const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
40#else
41void quit (const char *fmt, ...);
42void pbomb (const char *fmt, ...);
43void hbomb (const char *fmt, ...);
44#endif
45void set_signal_handler (int signum, signal_handler_t sig_handler_func);
46
47/* Global variables */
48char g_program_name[100];       /* Initialised from argv[0] in ParseArgs() */
49
50        int
51main (int argc, char *argv[])
52{
53        struct in_addr remote_addr, local_addr;
54        unsigned short remote_port, local_port;
55        int listen_fd;
56
57        ParseArgs (argc, argv, &remote_addr, &remote_port, &local_addr,
58                        &local_port);
59
60        Initialise ();
61
62        /* Create server socket before becoming a daemon so there is still a
63         *      chance to print an error message. */
64        listen_fd = CreateServerSocket (local_addr, local_port);
65        if (listen_fd < 0)
66                pbomb ("Unable to create server socket");
67
68        Daemonise ();
69
70        MainLoop (listen_fd, remote_addr, remote_port);       /* never returns */
71
72        exit (EXIT_SUCCESS);
73}
74
75/* ParseArgs(): Parse the command line arguments to extract the remote and
76 *  * local adresses and port numbers, ra, rp, la & lp.  Exit the program
77 *   * gracefully upon error.
78 *    */
79        void
80ParseArgs (int argc, char *argv[], struct in_addr *ra, unsigned short *rp,
81                struct in_addr *la, unsigned short *lp)
82{
83        /*
84         *    * argv[0] = program name
85         *       * argv[1] = remote_addr
86         *          * argv[2] = remote_port
87         *             * argv[3] = local_addr (optional)
88         *                * argv[4] = local_port (optional)
89         *                   */
90
91        char *p = strrchr (argv[0], '/');
92
93        strncpy (g_program_name, (p == NULL) ? argv[0] : p + 1,
94                        sizeof (g_program_name) - 1);
95
96        if ((argc < 3) || (argc > 5))
97        {
98                fprintf (stderr,
99                                "usage: %s remote_addr remote_port [local_addr] [local_port]\n",
100                                argv[0]);
101                exit (EXIT_FAILURE);
102        }
103
104        if (NameToAddr (argv[1], ra))
105                hbomb ("Unable to resolve \"%s\" to an ip address", argv[1]);
106
107        if (NameToPort (argv[2], rp, "tcp"))
108                quit ("Unable to resolve \"%s\" to a port number", argv[2]);
109
110        if (argc < 4)
111                la->s_addr = htonl (INADDR_ANY);
112        else if (NameToAddr (argv[3], la))
113                hbomb ("Unable to resolve \"%s\" to an ip address", argv[3]);
114
115        if (argc < 5)
116                memcpy (lp, rp, sizeof (*lp));
117        else if (NameToPort (argv[4], lp, "tcp"))
118                quit ("Unable to resolve \"%s\" to a port number", argv[4]);
119}
120
121/* Initialise(): Setup syslog, signal handlers, and other intialisation.
122 *  */
123        void
124Initialise (void)
125{
126        openlog (g_program_name, LOG_PID, LOG_USER);
127        syslog (LOG_INFO, "%s started", g_program_name);
128
129        chdir ("/");                  /* Change working directory to the root. */
130
131        umask (0);                    /* Clear our file mode creation mask */
132
133        set_signal_handler (SIGCHLD, sig_child);
134
135        signal (SIGPIPE, SIG_IGN);
136}
137
138/* sig_child(): Handles SIGCHLD from exiting child processes.
139 *  */
140        void
141sig_child (int signo)
142{
143        pid_t pid;
144
145        (void) signo;                 /* suppress compiler warning */
146
147        for (;;)
148        {
149                pid = waitpid (WAIT_ANY, NULL, WNOHANG);
150
151                if (pid > 0)
152                        syslog (LOG_INFO, "Caught SIGCHLD from pid %d", pid);
153                else
154                        break;
155        }
156
157        if ((pid < 0) && (errno != ECHILD))
158                syslog (LOG_ERR, "waitpid(): %m"), exit (EXIT_FAILURE);
159
160        return;
161}
162
163/* CreateServerSocket(): Create a socket, bind it to the specified address
164 *  * and port, and set it to listen for client connections.  Returns <0 on
165 *   * failure to bind, bombs on error otherwise, returns the fd of the new
166 *    * socket on success.
167 *     */
168        int
169CreateServerSocket (struct in_addr addr, unsigned short port)
170{
171        int err, fd;
172        const int on = 1;
173        struct sockaddr_in sa;
174
175        /* Create a socket and get its descriptor. */
176        fd = socket (AF_INET, SOCK_STREAM, 0);
177        if (fd < 0)
178                syslog (LOG_ERR, "socket(): %m"), exit (EXIT_FAILURE);
179
180        /* Set SO_REUSEADDR socket option */
181        if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) < 0)
182                syslog (LOG_ERR, "setsockopt(fd%d, SO_REUSEADDR): %m", fd);
183
184        /* Load a sa structure with the specified address and port */
185        sa.sin_family = AF_INET;
186        sa.sin_port = htons (port);
187        sa.sin_addr = addr;
188        memset (sa.sin_zero, 0, sizeof (sa.sin_zero));
189
190        /* Bind our socket to the address and port specified */
191        err = bind (fd, (struct sockaddr *) &sa, sizeof (sa));
192        if (err < 0)
193        {
194                syslog (LOG_ERR, "bind(): %m");
195                return err;
196        }
197
198        /* Tell socket to listen and queue up to 5 incoming connections. */
199        if (listen (fd, 5) < 0)
200                syslog (LOG_ERR, "listen(): %m"), exit (EXIT_FAILURE);
201
202        return fd;
203}
204
205/* Daemonise(): Put the program in the background, set PPID=1, create a new
206 *  * session and process group, without a controlling tty.
207 *   */
208        void
209Daemonise (void)
210{
211        pid_t pid;
212
213        /* Close stdin, stdout & stderr */
214        /* TODO: open /dev/null and dup the fd to stdin, stdout & stderr
215         *      close(STDIN_FILENO);
216         *           close(STDOUT_FILENO);
217         *                close(STDERR_FILENO);
218         *                   */
219
220        syslog (LOG_INFO, "%s daemonising", g_program_name);
221
222        /* Fork the process to put it in the background. */
223
224        pid = fork ();
225        if (pid == -1)
226                syslog (LOG_ERR, "fork(): %m"), exit (EXIT_FAILURE);
227
228        /* parent terminates here, so shell thinks the command is done. */
229        if (pid)
230                exit (0);
231
232        syslog (LOG_INFO, "%s in background", g_program_name);
233
234        /* 1st child continues to run in the background with PPID=1 */
235
236        /* Become leader of a new session and a new process group,
237         *      with no controlling tty.  */
238        setsid ();
239
240        /* Fork again to guarantee the process will not be able to aquire a
241         *      controlling tty. */
242
243        /*signal (SIGHUP, SIG_IGN); *//* required according to Stevens' UNP2 p333 */
244
245        pid = fork ();
246        if (pid == -1)
247                syslog (LOG_ERR, "fork(): %m"), exit (EXIT_FAILURE);
248
249        if (pid)                      /* 1st child terminates */
250                exit (0);
251
252        /* 2nd child continues, no longer a session or group leader */
253
254        syslog (LOG_INFO, "%s daemonised", g_program_name);
255}
256
257/* MainLoop(): Classic concurrent server model. Wait for a client to connect,
258 *  * fork a child process to do the business with the client, parent process
259 *   * continues to wait for the next connection.  This function does not return.
260 *    */
261        void
262MainLoop (int listen_fd, struct in_addr rem_addr, unsigned short rem_port)
263{
264        int server_fd, client_fd;
265        pid_t child_pid;
266
267        for (;;)
268        {
269                client_fd = AcceptClientConnection (listen_fd);
270
271                child_pid = fork ();
272                if (child_pid == -1)
273                        syslog (LOG_ERR, "fork(): %m"), exit (EXIT_FAILURE);
274
275                if (child_pid)            /* parent */
276                {
277                        close (client_fd);
278
279                        syslog (LOG_INFO, "Forked child pid %d to deal with client",
280                                        child_pid);
281
282                        continue;
283                }
284                else                      /* child */
285                {
286                        close (listen_fd);
287
288                        server_fd = ConnectToServer (rem_addr, rem_port);
289
290                        Proxy (server_fd, client_fd);
291
292                        syslog (LOG_INFO, "exiting");
293                        _exit (0);
294                }
295        }
296}
297
298/* Proxy(): Copies data between server_fd and client_fd in both directions
299 *  * until one or other peer closes their connection.
300 *   */
301        void
302Proxy (int server_fd, int client_fd)
303{
304        pid_t helper_pid;
305
306        syslog (LOG_INFO, "Proxy(fd%d, fd%d)", server_fd, client_fd);
307
308        signal (SIGCHLD, SIG_IGN);
309
310        helper_pid = fork ();
311        if (helper_pid == -1)
312                syslog (LOG_ERR, "fork(): %m"), exit (EXIT_FAILURE);
313
314        if (helper_pid)               /* parent */
315        {
316                syslog (LOG_INFO, "Forked child pid %d to help with proxying",
317                                helper_pid);
318
319                Cat (server_fd, client_fd);
320
321                shutdown (client_fd, 1);
322                close (server_fd);
323                close (client_fd);
324
325                wait (0);                 /* wait for helper to exit */
326        }
327        else                          /* child (helper) */
328        {
329                Cat (client_fd, server_fd);
330
331                shutdown (server_fd, 1);
332
333                syslog (LOG_INFO, "exiting");
334                exit (0);                 /* helper exits here */
335        }
336}
337
338/* AcceptClientConnection(): waits for a tcp connect to the socket listen_fd,
339 *  * which must already be bound and set to listen on a local port.  Bombs on
340 *   * error, returns the fd of the new socket on success.
341 *    */
342        int
343AcceptClientConnection (int listen_fd)
344{
345        int newfd;
346        struct sockaddr_in sa;
347        socklen_t socklen;
348
349        syslog (LOG_INFO, "AcceptClientConnection(fd%d)", listen_fd);
350
351        /* Accept the connection and create a new socket for it. */
352        socklen = sizeof (sa);
353        memset (&sa, 0, socklen);
354        do
355        {
356                newfd = accept (listen_fd, (struct sockaddr *) &sa, &socklen);
357        }
358        while ((newfd < 0) && (errno == EINTR));
359
360        syslog (LOG_INFO, "Accepted client connection on new socket fd%d", newfd);
361
362        if (newfd < 0)
363                syslog (LOG_ERR, "accept(): %m"), exit (EXIT_FAILURE);
364
365        if (socklen != sizeof (sa))
366                syslog (LOG_ERR, "accept() screwed up!"), exit (EXIT_FAILURE);
367
368        return (newfd);
369}
370
371/* ConnectToServer(): attempts a tcp connect to the server specified by addr
372 *  * and port.  Bombs on failure to connect, returns the fd of the new socket
373 *   * on success.
374 *    */
375        int
376ConnectToServer (struct in_addr addr, unsigned short port)
377{
378        /* TODO: have a timeout for connect() - see Unix socket FAQ 6.2 */
379
380        int fd, err;
381        struct sockaddr_in sa;
382
383        /* Create a socket and get its descriptor. */
384        fd = socket (AF_INET, SOCK_STREAM, 0);
385        if (fd < 0)
386                syslog (LOG_ERR, "socket(): %m"), exit (EXIT_FAILURE);
387
388        sa.sin_family = AF_INET;
389        sa.sin_port = htons (port);
390        sa.sin_addr = addr;
391        memset (sa.sin_zero, 0, sizeof (sa.sin_zero));
392
393        err = connect (fd, (struct sockaddr *) &sa, sizeof (sa));
394        if (err < 0)
395        {
396                syslog (LOG_ERR, "Unable to connect socket fd%d to server: %m", fd);
397                exit (EXIT_FAILURE);
398        }
399
400        syslog (LOG_INFO, "Connected socket fd%d to server", fd);
401
402        return fd;
403}
404
405/* Cat(): read data from in_fd and write it to out_fd until the connection is
406 *  * closed by one of the peers.  Data is copied using a dynamically allocated
407 *   * buffer.
408 *    */
409        void
410Cat (int in_fd, int out_fd)
411{
412        unsigned char *const buf = malloc (BUF_SIZE);
413        int bytes_rcvd, bytes_sent = 0, i;
414
415        syslog (LOG_INFO, "Cat(fd%d, fd%d)", in_fd, out_fd);
416
417        if (buf == NULL)
418                syslog (LOG_ERR, "malloc(): %m"), exit (EXIT_FAILURE);
419
420        do
421        {
422                bytes_rcvd = recv (in_fd, buf, BUF_SIZE, 0);
423
424                for (i = 0; i < bytes_rcvd; i += bytes_sent)
425                {
426                        bytes_sent = send (out_fd, buf + i, bytes_rcvd - i, 0);
427
428                        if (bytes_sent < 0)
429                                break;
430                }
431
432        }
433        while ((bytes_rcvd > 0) && (bytes_sent > 0));
434
435        if ((bytes_rcvd < 0) && (errno != ECONNRESET))
436                syslog (LOG_ERR, "recv(): %m"), exit (EXIT_FAILURE);
437
438        if ((bytes_sent < 0) && (errno != EPIPE))
439                syslog (LOG_ERR, "send(): %m"), exit (EXIT_FAILURE);
440
441        free (buf);
442}
443
444/* NameToAddress(): Convert name to an ip address.  Returns 0 on success,
445 *  * -1 on failure.
446 *   */
447        int
448NameToAddr (const char *name, struct in_addr *p_inaddr)
449{
450        struct hostent *he;
451
452        /* First, attempt to convert from string ip format */
453        /* TODO: use inet_aton() instead */
454        p_inaddr->s_addr = inet_addr (name);
455        if (p_inaddr->s_addr != -1U)  /* Success */
456                return 0;
457
458        /* Next, attempt to read from /etc/hosts or do a DNS lookup */
459        he = gethostbyname (name);
460        if (he != NULL)               /* Success */
461        {
462                memcpy (p_inaddr, he->h_addr, sizeof (struct in_addr));
463                return 0;
464        }
465
466        return -1;                    /* Failed to resolve name to an ip address */
467}
468
469/* NameToPort(): Convert name to a port number. Name can either be a port
470 *  * name (in which case proto must also be set to either "tcp" or "udp") or
471 *   * name can be the ascii representation of the port number.  Returns 0 on
472 *    * success, -1 on failure.
473 *     */
474        int
475NameToPort (const char *name, unsigned short *port, const char *proto)
476{
477        unsigned long lport;
478        char *errpos;
479        struct servent *se;
480
481        /* First, attempt to convert string to integer */
482        lport = strtoul (name, &errpos, 0);
483        if ((*errpos == 0) && (lport <= 65535))       /* Success */
484        {
485                *port = lport;
486                return 0;
487        }
488
489        /* Next, attempt to read the string from /etc/services */
490        se = getservbyname (name, proto);
491        if (se != NULL)               /* Success */
492        {
493                *port = ntohs (se->s_port);
494                return 0;
495        }
496
497        return -1;                    /* Failed to resolve port name to a number */
498}
499
500/* quit(), pbomb(), hbomb(): Print an error message to stderr and syslog, then
501 *  * exit the program.  pbomb() and hbomb() additionally include the string
502 *   * representation of errno and h_errno respectively.
503 *    */
504        void
505quit (const char *fmt, ...)     /* quit with msg */
506{
507        va_list ap;
508
509        fflush (stdout);
510
511        fprintf (stderr, "%s: ", g_program_name);
512
513        va_start (ap, fmt);
514        vfprintf (stderr, fmt, ap);
515        va_end (ap);
516
517        fputc ('\n', stderr);
518
519        syslog (LOG_ERR, "I quit!");
520
521        exit (EXIT_FAILURE);
522}
523
524        void
525pbomb (const char *fmt, ...)    /* bomb with perror */
526{
527        va_list ap;
528        int errno_save = errno;
529        char buf[100];
530
531        fflush (stdout);
532
533        fprintf (stderr, "%s: ", g_program_name);
534
535        va_start (ap, fmt);
536        vsnprintf (buf, sizeof (buf), fmt, ap);
537        va_end (ap);
538
539        errno = errno_save;
540        perror (buf);
541
542        syslog (LOG_ERR, "Bang!: %s: %m", buf);
543
544        exit (EXIT_FAILURE);
545}
546
547        void
548hbomb (const char *fmt, ...)    /* bomb with herror */
549{
550        va_list ap;
551        int h_errno_save = h_errno;
552        char buf[100];
553
554        fflush (stdout);
555
556        fprintf (stderr, "%s: ", g_program_name);
557
558        va_start (ap, fmt);
559        vsnprintf (buf, sizeof (buf), fmt, ap);
560        va_end (ap);
561
562        h_errno = h_errno_save;
563        herror (buf);
564
565        syslog (LOG_ERR, "Bang!: %s: %s", buf, hstrerror (h_errno));
566
567        exit (EXIT_FAILURE);
568}
569
570/* set_signal_handler(): Sets a signal handler function.  Similar to signal()
571 *  * but this method is more portable between platforms.
572 *   */
573        void
574set_signal_handler (int signum, signal_handler_t sa_handler_func)
575{
576        struct sigaction act;
577
578        act.sa_handler = sa_handler_func;
579        sigemptyset (&(act.sa_mask));
580        act.sa_flags = 0;
581
582        if (sigaction (signum, &act, NULL) < 0)
583        {
584                syslog (LOG_ERR, "Error setting handler for signal %d: %m", signum);
585                exit (EXIT_FAILURE);
586        }
587}
Note: See TracBrowser for help on using the repository browser.