home *** CD-ROM | disk | FTP | other *** search
/ OS/2 Shareware BBS: 35 Internet / 35-Internet.zip / bootpd-2.zip / BOOTPGW.C < prev    next >
C/C++ Source or Header  |  1995-09-04  |  17KB  |  696 lines

  1. /*
  2.  * bootpgw.c - BOOTP GateWay
  3.  * This program forwards BOOTP Request packets to a BOOTP server.
  4.  */
  5.  
  6. /************************************************************************
  7.           Copyright 1988, 1991 by Carnegie Mellon University
  8.  
  9.                           All Rights Reserved
  10.  
  11. Permission to use, copy, modify, and distribute this software and its
  12. documentation for any purpose and without fee is hereby granted, provided
  13. that the above copyright notice appear in all copies and that both that
  14. copyright notice and this permission notice appear in supporting
  15. documentation, and that the name of Carnegie Mellon University not be used
  16. in advertising or publicity pertaining to distribution of the software
  17. without specific, written prior permission.
  18.  
  19. CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
  20. SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
  21. IN NO EVENT SHALL CMU BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL
  22. DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
  23. PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
  24. ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
  25. SOFTWARE.
  26. ************************************************************************/
  27.  
  28. /*
  29.  * BOOTPGW is typically used to forward BOOTP client requests from
  30.  * one subnet to a BOOTP server on a different subnet.
  31.  */
  32.  
  33. #include <sys/types.h>
  34. #include <sys/param.h>
  35. #include <sys/socket.h>
  36. #include <sys/ioctl.h>
  37. #include <sys/file.h>
  38. #include <sys/time.h>
  39. #include <sys/stat.h>
  40. #include <sys/utsname.h>
  41.  
  42. #include <net/if.h>
  43. #include <netinet/in.h>
  44. #include <arpa/inet.h>    /* inet_ntoa */
  45.  
  46. #ifndef    NO_UNISTD
  47. #include <unistd.h>
  48. #endif
  49.  
  50. #include <stdlib.h>
  51. #include <signal.h>
  52. #include <stdio.h>
  53. #include <string.h>
  54. #include <errno.h>
  55. #include <ctype.h>
  56. #include <netdb.h>
  57. #include <syslog.h>
  58. #include <assert.h>
  59.  
  60. #ifdef    NO_SETSID
  61. # include <fcntl.h>        /* for O_RDONLY, etc */
  62. #endif
  63.  
  64. #ifndef    USE_BFUNCS
  65. # include <memory.h>
  66. /* Yes, memcpy is OK here (no overlapped copies). */
  67. # define bcopy(a,b,c)    memcpy(b,a,c)
  68. # define bzero(p,l)      memset(p,0,l)
  69. # define bcmp(a,b,c)     memcmp(a,b,c)
  70. #endif
  71.  
  72. #include "bootp.h"
  73. #include "getif.h"
  74. #include "hwaddr.h"
  75. #include "report.h"
  76. #include "patchlevel.h"
  77.  
  78. /* Local definitions: */
  79. #define MAX_MSG_SIZE            (3*512)    /* Maximum packet size */
  80. #define TRUE 1
  81. #define FALSE 0
  82. #define get_network_errmsg get_errmsg
  83.  
  84.  
  85.  
  86. /*
  87.  * Externals, forward declarations, and global variables
  88.  */
  89.  
  90. #ifdef    __STDC__
  91. #define P(args) args
  92. #else
  93. #define P(args) ()
  94. #endif
  95.  
  96. static void usage P((void));
  97. static void handle_reply P((void));
  98. static void handle_request P((void));
  99.  
  100. #undef    P
  101.  
  102. /*
  103.  * IP port numbers for client and server obtained from /etc/services
  104.  */
  105.  
  106. u_short bootps_port, bootpc_port;
  107.  
  108.  
  109. /*
  110.  * Internet socket and interface config structures
  111.  */
  112.  
  113. struct sockaddr_in bind_addr;    /* Listening */
  114. struct sockaddr_in recv_addr;    /* Packet source */
  115. struct sockaddr_in send_addr;    /*  destination */
  116.  
  117.  
  118. /*
  119.  * option defaults
  120.  */
  121. int debug = 0;                    /* Debugging flag (level) */
  122. struct timeval actualtimeout =
  123. {                                /* fifteen minutes */
  124.     15 * 60L,                    /* tv_sec */
  125.     0                            /* tv_usec */
  126. };
  127. u_int maxhops = 4;                /* Number of hops allowed for requests. */
  128. u_int minwait = 3;                /* Number of seconds client must wait before
  129.                            its bootrequest packets are forwarded. */
  130.  
  131. /*
  132.  * General
  133.  */
  134.  
  135. int s;                            /* Socket file descriptor */
  136. char *pktbuf;                    /* Receive packet buffer */
  137. int pktlen;
  138. char *progname;
  139. char *servername;
  140. int32 server_ipa;                /* Real server IP address, network order. */
  141.  
  142. struct in_addr my_ip_addr;
  143.  
  144. struct utsname my_uname;
  145. char *hostname;
  146.  
  147.  
  148.  
  149.  
  150.  
  151. /*
  152.  * Initialization such as command-line processing is done and then the
  153.  * main server loop is started.
  154.  */
  155.  
  156. void
  157. main(argc, argv)
  158.     int argc;
  159.     char **argv;
  160. {
  161.     struct timeval *timeout;
  162.     struct bootp *bp;
  163.     struct servent *servp;
  164.     struct hostent *hep;
  165.     char *stmp;
  166.     int n, ba_len, ra_len;
  167.     int nfound, readfds;
  168.     int standalone;
  169.  
  170.     progname = strrchr(argv[0], '/');
  171.     if (progname) progname++;
  172.     else progname = argv[0];
  173.  
  174.     /*
  175.      * Initialize logging.
  176.      */
  177.     report_init(0);                /* uses progname */
  178.  
  179.     /*
  180.      * Log startup
  181.      */
  182.     report(LOG_INFO, "version %s.%d", VERSION, PATCHLEVEL);
  183.  
  184.     /* Debugging for compilers with struct padding. */
  185.     assert(sizeof(struct bootp) == BP_MINPKTSZ);
  186.  
  187.     /* Get space for receiving packets and composing replies. */
  188.     pktbuf = malloc(MAX_MSG_SIZE);
  189.     if (!pktbuf) {
  190.         report(LOG_ERR, "malloc failed");
  191.         exit(1);
  192.     }
  193.     bp = (struct bootp *) pktbuf;
  194.  
  195.     /*
  196.      * Check to see if a socket was passed to us from inetd.
  197.      *
  198.      * Use getsockname() to determine if descriptor 0 is indeed a socket
  199.      * (and thus we are probably a child of inetd) or if it is instead
  200.      * something else and we are running standalone.
  201.      */
  202.     s = 0;
  203.     ba_len = sizeof(bind_addr);
  204.     bzero((char *) &bind_addr, ba_len);
  205.     errno = 0;
  206.     standalone = TRUE;
  207. #ifndef __EMX__
  208.     if (getsockname(s, (struct sockaddr *) &bind_addr, &ba_len) == 0) {
  209.         /*
  210.          * Descriptor 0 is a socket.  Assume we are a child of inetd.
  211.          */
  212.         if (bind_addr.sin_family == AF_INET) {
  213.             standalone = FALSE;
  214.             bootps_port = ntohs(bind_addr.sin_port);
  215.         } else {
  216.             /* Some other type of socket? */
  217.             report(LOG_INFO, "getsockname: not an INET socket");
  218.         }
  219.     }
  220. #endif
  221.     /*
  222.      * Set defaults that might be changed by option switches.
  223.      */
  224.     stmp = NULL;
  225.     timeout = &actualtimeout;
  226.  
  227.     if (uname(&my_uname) < 0) {
  228.         fprintf(stderr, "bootpgw: can't get hostname\n");
  229.         exit(1);
  230.     }
  231.     hostname = my_uname.nodename;
  232.  
  233.     hep = gethostbyname(hostname);
  234.     if (!hep) {
  235.         printf("Can not get my IP address\n");
  236.         exit(1);
  237.     }
  238.     bcopy(hep->h_addr, (char *)&my_ip_addr, sizeof(my_ip_addr));
  239.  
  240.     /*
  241.      * Read switches.
  242.      */
  243.     for (argc--, argv++; argc > 0; argc--, argv++) {
  244.         if (argv[0][0] != '-')
  245.             break;
  246.         switch (argv[0][1]) {
  247.  
  248.         case 'd':                /* debug level */
  249.             if (argv[0][2]) {
  250.                 stmp = &(argv[0][2]);
  251.             } else if (argv[1] && argv[1][0] == '-') {
  252.                 /*
  253.                  * Backwards-compatible behavior:
  254.                  * no parameter, so just increment the debug flag.
  255.                  */
  256.                 debug++;
  257.                 break;
  258.             } else {
  259.                 argc--;
  260.                 argv++;
  261.                 stmp = argv[0];
  262.             }
  263.             if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
  264.                 fprintf(stderr,
  265.                         "%s: invalid debug level\n", progname);
  266.                 break;
  267.             }
  268.             debug = n;
  269.             break;
  270.  
  271.         case 'h':                /* hop count limit */
  272.             if (argv[0][2]) {
  273.                 stmp = &(argv[0][2]);
  274.             } else {
  275.                 argc--;
  276.                 argv++;
  277.                 stmp = argv[0];
  278.             }
  279.             if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
  280.                 (n < 0) || (n > 16))
  281.             {
  282.                 fprintf(stderr,
  283.                         "bootpgw: invalid hop count limit\n");
  284.                 break;
  285.             }
  286.             maxhops = (u_int)n;
  287.             break;
  288.  
  289. #ifndef __EMX__
  290.         case 'i':                /* inetd mode */
  291.             standalone = FALSE;
  292.             break;
  293.  
  294.         case 's':                /* standalone mode */
  295.             standalone = TRUE;
  296.             break;
  297. #endif
  298.  
  299.         case 't':                /* timeout */
  300.             if (argv[0][2]) {
  301.                 stmp = &(argv[0][2]);
  302.             } else {
  303.                 argc--;
  304.                 argv++;
  305.                 stmp = argv[0];
  306.             }
  307.             if (!stmp || (sscanf(stmp, "%d", &n) != 1) || (n < 0)) {
  308.                 fprintf(stderr,
  309.                         "%s: invalid timeout specification\n", progname);
  310.                 break;
  311.             }
  312.             actualtimeout.tv_sec = (int32) (60 * n);
  313.             /*
  314.              * If the actual timeout is zero, pass a NULL pointer
  315.              * to select so it blocks indefinitely, otherwise,
  316.              * point to the actual timeout value.
  317.              */
  318.             timeout = (n > 0) ? &actualtimeout : NULL;
  319.             break;
  320.  
  321.         case 'w':                /* wait time */
  322.             if (argv[0][2]) {
  323.                 stmp = &(argv[0][2]);
  324.             } else {
  325.                 argc--;
  326.                 argv++;
  327.                 stmp = argv[0];
  328.             }
  329.             if (!stmp || (sscanf(stmp, "%d", &n) != 1) ||
  330.                 (n < 0) || (n > 60))
  331.             {
  332.                 fprintf(stderr,
  333.                         "bootpgw: invalid wait time\n");
  334.                 break;
  335.             }
  336.             minwait = (u_int)n;
  337.             break;
  338.  
  339.         default:
  340.             fprintf(stderr, "%s: unknown switch: -%c\n",
  341.                     progname, argv[0][1]);
  342.             usage();
  343.             break;
  344.  
  345.         } /* switch */
  346.     } /* for args */
  347.  
  348.     /* Make sure server name argument is suplied. */
  349.     servername = argv[0];
  350.     if (!servername) {
  351.         fprintf(stderr, "bootpgw: missing server name\n");
  352.         usage();
  353.     }
  354.     /*
  355.      * Get address of real bootp server.
  356.      */
  357.     if (isdigit(servername[0]))
  358.         server_ipa = inet_addr(servername);
  359.     else {
  360.         hep = gethostbyname(servername);
  361.         if (!hep) {
  362.             fprintf(stderr, "bootpgw: can't get addr for %s\n", servername);
  363.             exit(1);
  364.         }
  365.         bcopy(hep->h_addr, (char *)&server_ipa, sizeof(server_ipa));
  366.     }
  367.  
  368.     if (standalone) {
  369.         /*
  370.          * Go into background and disassociate from controlling terminal.
  371.          * XXX - This is not the POSIX way (Should use setsid). -gwr
  372.          */
  373. #ifndef __EMX__
  374.         if (debug < 3) {
  375.             if (fork())
  376.                 exit(0);
  377. #ifdef    NO_SETSID
  378.             setpgrp(0,0);
  379. #ifdef TIOCNOTTY
  380.             n = open("/dev/tty", O_RDWR);
  381.             if (n >= 0) {
  382.                 ioctl(n, TIOCNOTTY, (char *) 0);
  383.                 (void) close(n);
  384.             }
  385. #endif    /* TIOCNOTTY */
  386. #else    /* SETSID */
  387.             if (setsid() < 0)
  388.                 perror("setsid");
  389. #endif    /* SETSID */
  390.         } /* if debug < 3 */
  391. #endif
  392.         /*
  393.          * Nuke any timeout value
  394.          */
  395.         timeout = NULL;
  396.  
  397.         /*
  398.          * Here, bootpd would do:
  399.          *    chdir
  400.          *    tzone_init
  401.          *    rdtab_init
  402.          *    readtab
  403.          */
  404.  
  405.         /*
  406.          * Create a socket.
  407.          */
  408.         if ((s = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
  409.             report(LOG_ERR, "socket: %s", get_network_errmsg());
  410.             exit(1);
  411.         }
  412.         /*
  413.          * Get server's listening port number
  414.          */
  415.         servp = getservbyname("sbootp", "udp");
  416.         if (servp) {
  417.             bootps_port = ntohs((u_short) servp->s_port);
  418.         } else {
  419.             bootps_port = (u_short) IPPORT_BOOTPS;
  420.             report(LOG_ERR,
  421.                    "udp/bootps: unknown service -- assuming port %d",
  422.                    bootps_port);
  423.         }
  424.  
  425.         /*
  426.          * Bind socket to BOOTPS port.
  427.          */
  428.         bind_addr.sin_family = AF_INET;
  429.         bind_addr.sin_port = htons(bootps_port);
  430.         bind_addr.sin_addr.s_addr = INADDR_ANY;
  431.         if (bind(s, (struct sockaddr *) &bind_addr,
  432.                  sizeof(bind_addr)) < 0)
  433.         {
  434.             report(LOG_ERR, "bind: %s", get_network_errmsg());
  435.             exit(1);
  436.         }
  437.     } /* if standalone */
  438.     /*
  439.      * Get destination port number so we can reply to client
  440.      */
  441.     servp = getservbyname("cbootp", "udp");
  442.     if (servp) {
  443.         bootpc_port = ntohs(servp->s_port);
  444.     } else {
  445.         report(LOG_ERR,
  446.                "udp/bootpc: unknown service -- assuming port %d",
  447.                IPPORT_BOOTPC);
  448.         bootpc_port = (u_short) IPPORT_BOOTPC;
  449.     }
  450.  
  451.     /* no signal catchers */
  452.  
  453.     /*
  454.      * Process incoming requests.
  455.      */
  456.     for (;;) {
  457.         struct timeval tv;
  458.  
  459.         readfds = 1 << s;
  460.         if (timeout)
  461.             tv = *timeout;
  462.  
  463.         nfound = select(s + 1, (fd_set *)&readfds, NULL, NULL,
  464.                         (timeout) ? &tv : NULL);
  465.         if (nfound < 0) {
  466.             if (errno != EINTR) {
  467.                 report(LOG_ERR, "select: %s", get_errmsg());
  468.             }
  469.             continue;
  470.         }
  471.         if (!(readfds & (1 << s))) {
  472.             report(LOG_INFO, "exiting after %ld minutes of inactivity",
  473.                    actualtimeout.tv_sec / 60);
  474.             exit(0);
  475.         }
  476.         ra_len = sizeof(recv_addr);
  477.         n = recvfrom(s, pktbuf, MAX_MSG_SIZE, 0,
  478.                      (struct sockaddr *) &recv_addr, &ra_len);
  479.         if (n <= 0) {
  480.             continue;
  481.         }
  482.         if (debug > 3) {
  483.             report(LOG_INFO, "recvd pkt from IP addr %s",
  484.                    inet_ntoa(recv_addr.sin_addr));
  485.         }
  486.         if (n < sizeof(struct bootp)) {
  487.             if (debug) {
  488.                 report(LOG_INFO, "received short packet");
  489.             }
  490.             continue;
  491.         }
  492.         pktlen = n;
  493.  
  494.         switch (bp->bp_op) {
  495.         case BOOTREQUEST:
  496.             handle_request();
  497.             break;
  498.         case BOOTREPLY:
  499.             handle_reply();
  500.             break;
  501.         }
  502.     }
  503. }
  504.  
  505.  
  506.  
  507.  
  508. /*
  509.  * Print "usage" message and exit
  510.  */
  511.  
  512. static void
  513. usage()
  514. {
  515.     fprintf(stderr,
  516.             "usage:  bootpgw [-d level] [-i] [-s] [-t timeout] server\n");
  517.     fprintf(stderr, "\t -d n\tset debug level\n");
  518.     fprintf(stderr, "\t -h n\tset max hop count\n");
  519. #ifndef __EMX__
  520.     fprintf(stderr, "\t -i\tforce inetd mode (run as child of inetd)\n");
  521.     fprintf(stderr, "\t -s\tforce standalone mode (run without inetd)\n");
  522. #endif
  523.     fprintf(stderr, "\t -t n\tset inetd exit timeout to n minutes\n");
  524.     fprintf(stderr, "\t -w n\tset min wait time (secs)\n");
  525.     exit(1);
  526. }
  527.  
  528.  
  529.  
  530. /*
  531.  * Process BOOTREQUEST packet.
  532.  *
  533.  * Note, this just forwards the request to a real server.
  534.  */
  535. static void
  536. handle_request()
  537. {
  538.     struct bootp *bp = (struct bootp *) pktbuf;
  539.     u_short secs, hops;
  540.  
  541.     /* XXX - SLIP init: Set bp_ciaddr = recv_addr here? */
  542.  
  543.     if (debug) {
  544.         report(LOG_INFO, "request from %s",
  545.                inet_ntoa(recv_addr.sin_addr));
  546.     }
  547.     /* Has the client been waiting long enough? */
  548.     secs = ntohs(bp->bp_secs);
  549.     if (secs < minwait)
  550.         return;
  551.  
  552.     /* Has this packet hopped too many times? */
  553.     hops = ntohs(bp->bp_hops);
  554.     if (++hops > maxhops) {
  555.         report(LOG_NOTICE, "reqest from %s reached hop limit",
  556.                inet_ntoa(recv_addr.sin_addr));
  557.         return;
  558.     }
  559.     bp->bp_hops = htons(hops);
  560.  
  561.     /*
  562.      * Here one might discard a request from the same subnet as the
  563.      * real server, but we can assume that the real server will send
  564.      * a reply to the client before it waits for minwait seconds.
  565.      */
  566.  
  567.     /* If gateway address is not set, put in local interface addr. */
  568.     if (bp->bp_giaddr.s_addr == 0) {
  569. #if 0    /* BUG */
  570.         struct sockaddr_in *sip;
  571.         struct ifreq *ifr;
  572.         /*
  573.          * XXX - This picks the wrong interface when the receive addr
  574.          * is the broadcast address.  There is no  portable way to
  575.          * find out which interface a broadcast was received on. -gwr
  576.          * (Thanks to <walker@zk3.dec.com> for finding this bug!)
  577.          */
  578.         ifr = getif(s, &recv_addr.sin_addr);
  579.         if (!ifr) {
  580.             report(LOG_NOTICE, "no interface for request from %s",
  581.                    inet_ntoa(recv_addr.sin_addr));
  582.             return;
  583.         }
  584.         sip = (struct sockaddr_in *) &(ifr->ifr_addr);
  585.         bp->bp_giaddr = sip->sin_addr;
  586. #else    /* BUG */
  587.         /*
  588.          * XXX - Just set "giaddr" to our "official" IP address.
  589.          * RFC 1532 says giaddr MUST be set to the address of the
  590.          * interface on which the request was received.  Setting
  591.          * it to our "default" IP address is not strictly correct,
  592.          * but is good enough to allow the real BOOTP server to
  593.          * get the reply back here.  Then, before we forward the
  594.          * reply to the client, the giaddr field is corrected.
  595.          * (In case the client uses giaddr, which it should not.)
  596.          * See handle_reply()
  597.          */
  598.         bp->bp_giaddr = my_ip_addr;
  599. #endif    /* BUG */
  600.  
  601.         /*
  602.          * XXX - DHCP says to insert a subnet mask option into the
  603.          * options area of the request (if vendor magic == std).
  604.          */
  605.     }
  606.     /* Set up socket address for send. */
  607.     send_addr.sin_family = AF_INET;
  608.     send_addr.sin_port = htons(bootps_port);
  609.     send_addr.sin_addr.s_addr = server_ipa;
  610.  
  611.     /* Send reply with same size packet as request used. */
  612.     if (sendto(s, pktbuf, pktlen, 0,
  613.                (struct sockaddr *) &send_addr,
  614.                sizeof(send_addr)) < 0)
  615.     {
  616.         report(LOG_ERR, "sendto: %s", get_network_errmsg());
  617.     }
  618. }
  619.  
  620.  
  621.  
  622. /*
  623.  * Process BOOTREPLY packet.
  624.  */
  625. static void
  626. handle_reply()
  627. {
  628.     struct bootp *bp = (struct bootp *) pktbuf;
  629.     struct ifreq *ifr;
  630.     struct sockaddr_in *sip;
  631.     unsigned char *ha;
  632.     int len, haf;
  633.  
  634.     if (debug) {
  635.         report(LOG_INFO, "   reply for %s",
  636.                inet_ntoa(bp->bp_yiaddr));
  637.     }
  638.     /* Make sure client is directly accessible. */
  639.     ifr = getif(s, &(bp->bp_yiaddr));
  640.     if (!ifr) {
  641.         report(LOG_NOTICE, "no interface for reply to %s",
  642.                inet_ntoa(bp->bp_yiaddr));
  643.         return;
  644.     }
  645. #if 1    /* Experimental (see BUG above) */
  646. /* #ifdef CATER_TO_OLD_CLIENTS ? */
  647.     /*
  648.      * The giaddr field has been set to our "default" IP address
  649.      * which might not be on the same interface as the client.
  650.      * In case the client looks at giaddr, (which it should not)
  651.      * giaddr is now set to the address of the correct interface.
  652.      */
  653.     sip = (struct sockaddr_in *) &(ifr->ifr_addr);
  654.     bp->bp_giaddr = sip->sin_addr;
  655. #endif
  656.  
  657.     /* Set up socket address for send to client. */
  658.     send_addr.sin_family = AF_INET;
  659.     send_addr.sin_addr = bp->bp_yiaddr;
  660.     send_addr.sin_port = htons(bootpc_port);
  661.  
  662.     /* Create an ARP cache entry for the client. */
  663.     ha = bp->bp_chaddr;
  664.     len = bp->bp_hlen;
  665.     if (len > MAXHADDRLEN)
  666.         len = MAXHADDRLEN;
  667.     haf = (int) bp->bp_htype;
  668.     if (haf == 0)
  669.         haf = HTYPE_ETHERNET;
  670.  
  671.     if (debug > 1)
  672.         report(LOG_INFO, "setarp %s - %s",
  673.                inet_ntoa(bp->bp_yiaddr), haddrtoa(ha, len));
  674.     setarp(s, &bp->bp_yiaddr, haf, ha, len);
  675.  
  676.     /* Send reply with same size packet as request used. */
  677.     if (sendto(s, pktbuf, pktlen, 0,
  678.                (struct sockaddr *) &send_addr,
  679.                sizeof(send_addr)) < 0)
  680.     {
  681.         report(LOG_ERR, "sendto: %s", get_network_errmsg());
  682.     }
  683. }
  684.  
  685. /*
  686.  * Local Variables:
  687.  * tab-width: 4
  688.  * c-indent-level: 4
  689.  * c-argdecl-indent: 4
  690.  * c-continued-statement-offset: 4
  691.  * c-continued-brace-offset: -4
  692.  * c-label-offset: -4
  693.  * c-brace-offset: 0
  694.  * End:
  695.  */
  696.