/*
 * DHCP toolkit
 *
 * Complicate frontend for crafting dhcp traffic
 *
 * Jon Hart <warchild@spoofed.org>
 *
 *  Redistribution and use in source and binary forms, with or without modification, 
 *  are permitted provided that the following conditions are met:
 *
 *  * Redistributions of source code must retain the above copyright notice, 
 *    this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 *  * Neither the name of the organization nor the names of its contributors may
 *    be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 *
 *
 *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 *  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 *  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 *  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
 *  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
 *  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
 *  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
 *  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 
 *  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
 *  USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <libnet.h>

#define OPT_HELP        0
#define OPT_VERBOSE     1
#define OPT_INTERFACE   2
#define OPT_DHCPOP      3
#define OPT_SRCETHER    4
#define OPT_DSTETHER    5
#define OPT_HWADDR      6 
#define OPT_DHCPMSG     7
#define OPT_SRCIP       8
#define OPT_DSTIP       9
#define OPT_OPTIONS     10 

struct opt {
    char *name; /* name of this option */
    int req;    /* required? */
    int set;    /* Does it have a value? */
};

struct opt opts[] = {
    {"help", 0, 0},
    {"verbose", 0, 0},
    {"interface", 1, 0},
    {"DHCP operation", 1, 0},
    {"source ethernet address", 1, 0},
    {"destination ethernet address", 1, 0},
    {"hardware address", 1, 0},
    {"DHCP message type", 1, 0},
    {"source IP", 1, 0},
    {"destination IP", 1, 0},
    {"options", 1, 0}
};

struct libnet_ether_addr *make_ether(char *in) {

    int i;
    u_int enet_val[6];
    struct libnet_ether_addr *ether = (struct libnet_ether_addr *)malloc(sizeof(struct libnet_ether_addr *));
    sscanf(in, "%2x:%2x:%2x:%2x:%2x:%2x", &enet_val[0], &enet_val[1], &enet_val[2], &enet_val[3], &enet_val[4], &enet_val[5]);

    for (i = 0; i < 6; i++) { 
        ether->ether_addr_octet[i] = (u_char)enet_val[i]; 
    }

    return ether;
}



int main(int argc, char *argv[]) {

    int c, i;

    libnet_t *libnet;
    libnet_ptag_t dhcp, udp, ipv4, ether;

    u_char *packet;
    u_long packet_s;

    u_short dhcp_op;
    u_char hw_type, hw_length, hop_count, dhcp_msg;
    u_short secs, flags;
    u_long client_ip, src_ip, dst_ip, your_ip, server_ip, gateway_ip, options_len;
    u_char trans_id, *boot_file, *server_name;
    u_char *options;


     /* The source and destination MAC addresses
     * for the ethernet level
     */ 
    struct libnet_ether_addr *src_ether = (struct libnet_ether_addr *) malloc(sizeof(struct libnet_ether_addr *));
    struct libnet_ether_addr *dst_ether = (struct libnet_ether_addr *) malloc(sizeof(struct libnet_ether_addr *));

    struct libnet_ether_addr *hw_addr = (struct libnet_ether_addr *) malloc(sizeof(struct libnet_ether_addr *));

    u_char broadcast[6] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};

    char *interface;
    char errbuf[LIBNET_ERRBUF_SIZE];

    while ((c = getopt(argc, argv, "i:o:p:P:s:S:d:m:O:vh")) != EOF) {
        switch (c) {
            case 'h':
                exit(EXIT_SUCCESS);
            case 'v':
                opts[OPT_VERBOSE].set = 1;
                break;
            case 'O':
                options = optarg;
                options_len = strlen(optarg);
                opts[OPT_OPTIONS].set = 1;
                break;
            case 'i':
                interface = optarg;
                opts[OPT_INTERFACE].set = 1;
                break;
            case 'p':
                src_ip = libnet_name2addr4(libnet, optarg, LIBNET_DONT_RESOLVE);
                opts[OPT_SRCIP].set = 1;
                break;
            case 'P':
                dst_ip = libnet_name2addr4(libnet, optarg, LIBNET_DONT_RESOLVE);
                opts[OPT_DSTIP].set = 1;
                break;
            case 's':
                src_ether = make_ether(optarg);
                opts[OPT_SRCETHER].set = 1;
                break;
            case 'S':
                hw_addr = make_ether(optarg);
                opts[OPT_HWADDR].set = 1;
                break;
            case 'd':
                dst_ether = make_ether(optarg);
                opts[OPT_DSTETHER].set = 1;
                break;
            case 'o':
                if (strcmp(optarg, "req") == 0) {
                    dhcp_op = LIBNET_DHCP_REQUEST;
                    opts[OPT_DHCPOP].set = 1;
                    break;
                } else if (strcmp(optarg, "rep") == 0) {
                    dhcp_op = LIBNET_DHCP_REPLY;
                    opts[OPT_DHCPOP].set = 1;
                    break;
                } else {
                    fprintf(stderr, "Invalid dhcp operation %s.  Must be one of req, rep\n", optarg);
                    opts[OPT_DHCPOP].set = 0;
                    exit(EXIT_FAILURE);
                }
            case 'm':
                if (strcmp(optarg, "disc") == 0) {
                    dhcp_msg = LIBNET_DHCP_MSGDISCOVER;
                    opts[OPT_DHCPMSG].set = 1;
                    break;
                } else if (strcmp(optarg, "off") == 0) {
                    dhcp_msg = LIBNET_DHCP_MSGOFFER;
                    opts[OPT_DHCPMSG].set = 1;
                    break;
                } else if (strcmp(optarg, "req") == 0) {
                    dhcp_msg = LIBNET_DHCP_MSGREQUEST;
                    opts[OPT_DHCPMSG].set = 1;
                    break;
                } else if (strcmp(optarg, "dec") == 0) {
                    dhcp_msg = LIBNET_DHCP_MSGDECLINE;
                    opts[OPT_DHCPMSG].set = 1;
                    break;
                } else if (strcmp(optarg, "ack") == 0) {
                    dhcp_msg = LIBNET_DHCP_MSGACK;
                    opts[OPT_DHCPMSG].set = 1;
                    break;
                } else if (strcmp(optarg, "nack") == 0) {
                    dhcp_msg = LIBNET_DHCP_MSGNACK;
                    opts[OPT_DHCPMSG].set = 1;
                    break;
                } else if (strcmp(optarg, "rel") == 0) {
                    dhcp_msg = LIBNET_DHCP_MSGRELEASE;
                    opts[OPT_DHCPMSG].set = 1;
                    break;
                } else if (strcmp(optarg, "inf") == 0) {
                    dhcp_msg = LIBNET_DHCP_MSGINFORM;
                    opts[OPT_DHCPMSG].set = 1;
                    break;
                } else {
                    fprintf(stderr, "Invalid DHCP message type %s.  Must be one of off, req, dec, ack, nack, rel, or inf\n", optarg);
                    exit(EXIT_FAILURE);
                }
            default:
                break;
        }
    }

    if (!opts[OPT_INTERFACE].set) {
        fprintf(stderr, "Please specify an interface\n");
        exit(EXIT_FAILURE);
    }

    libnet = libnet_init(LIBNET_LINK, interface, errbuf);

    if (libnet == NULL) {
        fprintf(stderr, "libnet_init() failed: %s", errbuf);
        exit(EXIT_FAILURE);
    }   

    if (!opts[OPT_SRCETHER].set) {
        if (opts[OPT_VERBOSE].set) { fprintf(stderr, "No source ethernet address specified.  Attempting to use %s's\n", interface); }
        src_ether = libnet_get_hwaddr(libnet);
        if (src_ether == NULL) {
            fprintf(stderr, "No source ethernet address specified, and I couldn't guess it: %s\n", libnet_geterror(libnet));
            exit(EXIT_FAILURE);
        }
        opts[OPT_SRCETHER].set = 1;
    }

    if (!opts[OPT_DSTETHER].set) {
        if (opts[OPT_VERBOSE].set) { fprintf(stderr, "No destination ethernet address specified.  Using broadcast\n"); }
        memcpy(dst_ether->ether_addr_octet, broadcast, sizeof(broadcast));
        opts[OPT_DSTETHER].set = 1; 
    }

    if (!opts[OPT_HWADDR].set) {
        hw_addr = libnet_get_hwaddr(libnet);
        if (hw_addr == NULL) {
            fprintf(stderr, "No hardware address specified, and I couldn't guess it: %s\n", libnet_geterror(libnet));
            exit(EXIT_FAILURE); 
        } 
    }

    if (!opts[OPT_DSTIP].set) {
        dst_ip = inet_addr("255.255.255.255");
        opts[OPT_DSTIP].set = 1;
    }

    if (!opts[OPT_SRCIP].set) {
        src_ip = inet_addr("0.0.0.0");
        opts[OPT_SRCIP].set = 1;
    }

    if (!opts[OPT_OPTIONS].set) {
        i = 0;
        options_len = 3;
        options = malloc(3);
        options[i++] = LIBNET_DHCP_MESSAGETYPE;     // type
        options[i++] = 1;                           // len
        options[i++] = dhcp_msg;                    // message type
        options[i] = LIBNET_DHCP_END;
    }


    dhcp = libnet_build_dhcpv4(
            dhcp_op,            /* dhcp operation */
            1,            /* hardware type */
            6,          /* hardware address length */
            0,
            0xdeadbeef,           /* transaction id */
            0,               /* seconds since bootstrap */
            0x8000,              /* flags */
            client_ip,
            your_ip,
            server_ip,
            gateway_ip,
            hw_addr->ether_addr_octet,
            NULL,
            NULL,
            options,
            options_len,
            libnet,
            0);

    udp = libnet_build_udp(
            68,
            67,
            LIBNET_UDP_H + LIBNET_DHCPV4_H + options_len,
            0,
            NULL,
            0,
            libnet,
            0);

    ipv4 = libnet_build_ipv4(
            LIBNET_IPV4_H + LIBNET_UDP_H + LIBNET_DHCPV4_H + options_len,
            0x10,
            0,
            0,
            16,
            IPPROTO_UDP,
            0,
            0,
            dst_ip,
            NULL,
            0,
            libnet,
            0);

    ether = libnet_build_ethernet(
                dst_ether->ether_addr_octet,
                src_ether->ether_addr_octet,
                ETHERTYPE_IP,
                NULL,
                0,
                libnet,
                0);

    c = libnet_adv_cull_packet(libnet, &packet, &packet_s);

    if ( (c = libnet_write(libnet)) == -1) {
        fprintf(stderr, "Couldn't write packet to the wire: %s\n", libnet_geterror(libnet));
    } else {
        if (opts[OPT_VERBOSE].set) { fprintf(stderr, "Wrote %i bytes to %s\n", c, interface); }
    }

    libnet_destroy(libnet);
    return (EXIT_SUCCESS);

            
       








}

