/* wol.c - Simple Wake-On-LAN utility to wake a networked PC. */

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <errno.h>

int send_wol(unsigned char *mac_addr, unsigned port, unsigned long bcast);
  /* Sends Wake-On-LAN packet to given address with the given options
   * Returns 0 on success, 1 on error. */

int get_ether(char *mac_address_string, unsigned char *mac_addr);
  /* Extract inet address from hardware address. */

void print_usage(char *arg);
  /* print help information */

int get_hex(char buf);
  /* Attempts to extract hexadecimal from ASCII string.
   * Returns byte value read on success, or -1 on error. */

int verbose = 0;                      /* verbosity */
int debug = 0;                        /* debuging output */
unsigned port = 9;                    /* portnumber,  7 or 9 */
unsigned long bcast = 0xFFFFFFFF;     /* broadcast address (IP) */

int main(int argc, char * argv[])
  {

  int c;
  unsigned char mac_addr[8];

  /* parse command line */
  while ((c = getopt(argc, argv, "hvdb:p:")) != -1)
    {
    switch (c)
      {
      case 'd': /* debug */
        debug = 1;
        verbose = 1;
        break;
      case 'v': /* verbose */
        verbose = 1;
        break;
      case 'b': /* broadcast */
        bcast = inet_addr(optarg);
        if (bcast == INADDR_NONE)
          {
          perror("-b: Broadcast Address required");
          return 1;
          }
        break;
      case 'p': /* port */
        port = strtol(optarg, NULL, 0);
        if (port == 0 && errno != 0)
          {
          perror("-p: Portnumber (int) requirend!");
          return 1;
          }
      case 'h': /* help */
      case '?': /* unrecognized option */
      default:
        print_usage(argv[0]);
        return 1;
        break;
      }
    }

  /* parse any remaining arguments (not options) */
  if (optind != (argc - 1))
    {
    print_usage(argv[0]);
    return 1;
    }

  /* fetch the hardware address */
  if (get_ether(argv[optind], mac_addr) < 0)
    {
    fprintf(stderr,"\"%s\" is not a valid ether address!\n", mac_addr);
    return 1;
    }

  /* send magic packet */
  if (send_wol(mac_addr, port, bcast) < 0)
    {
    fprintf(stderr,"Error sending packet %s.\n", strerror(errno));
    return 1;
    }
  if (verbose)
    {
    printf("Packet sent to %08X - %s on port %d\n",
          htonl(bcast), argv[optind], port);
    }
  return 0;
  }

void print_usage(char *arg)
  /* print help information */
  {
  fprintf(stderr, "Usage: %s [-v] [-b <bcast>] [-p <port>] <dest>\n\n", arg);
  fprintf(stderr, "The single required parameter is the Ethernet MAC address\n");
  fprintf(stderr, "of the machine to wake.\n\n");

  fprintf(stderr, "Options:\n");
  fprintf(stderr, "    -b       Send wake-up packet to the broadcast address.\n");
  fprintf(stderr, "    -v       Increase the verbosity level.\n");
  fprintf(stderr, "    -d       Increase the debug level.\n");
  fprintf(stderr, "-p <port>    Set the TCP-Port (default 9)\n\n");
  }


int send_wol(unsigned char *mac_addr, unsigned port, unsigned long bcast)
  /* Sends Wake-On-LAN packet to given address with the given options
   * Returns 0 on success, -1 on error. */
  {
  unsigned char message[102];
  unsigned char *message_ptr = message;
  int client, i;
  struct sockaddr_in addr;
  int optval = 1;     /* setsockopt needs a variable, not a constant */


  /* Build the message: 6 * 0xFF followed by 16 * destination address */
  memset(message_ptr, 0xFF, 6);
  message_ptr = message_ptr + 6;
  for (i = 0; i < 16; ++i)
    {
    memcpy(message_ptr, mac_addr, 6);
    message_ptr = message_ptr + 6;
    }
  if (debug)
    {
    fprintf(stderr,"The final packet is: ");
    for (i = 0; i < sizeof(message); i++)
    fprintf(stderr," %02x", message[i]);
    fprintf(stderr,"\n");
    }

  if ((client = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0)
    {
    fprintf(stderr,"Cannot create socket%s\n", strerror(errno));
    return -1;
    }
  if (debug)
    { fprintf(stderr,"Socket created\n"); }
  /* Set socket options */
  if (setsockopt(client, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval)) < 0)
    {
    fprintf(stderr,"Cannot set socket options %s\n", strerror(errno));
    close(client);
    return -1;
    }
  if (debug)
    { fprintf(stderr,"Options changed\n"); }

  /* Set up broadcast address */
  memset( &addr, 0, sizeof (addr));
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = bcast;
  addr.sin_port = htons(port);

  /* Send the packet out */
  if (sendto(client, (char *)message, sizeof message, 0,
               (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
    fprintf(stderr,"sendto failed %s\n", strerror(errno));
    close(client);
    return -1;
    }
  if (debug)
    { fprintf(stderr,"Sendto successful\n"); }
  close(client);
  return 0;
  }

int get_hex(char buf)
  /* Attempts to extract hexadecimal from ASCII character.
   * Returns value read on success, or -1 on error. */
  {
  int hex;

  hex = 0;
  if (buf >= '0' && buf <= '9')
    { hex |= buf - '0'; }
  else if (buf >= 'a' && buf <= 'f')
    { hex |= buf - 'a' + 10; }
  else if (buf >= 'A' && buf <= 'F')
    { hex |= buf - 'A' + 10; }
  else
    { return -1; /* Error */ }
  return hex;
  }

int get_ether(char *mac_address_string, unsigned char *mac_addr)
  /* Extract MAC address from ASCII string.
   * Returns 0 on success, -1 on error. */
  {
  char *orig = mac_address_string;
  int i;
  int hex;

  for (i = 0; *mac_address_string != '\0' && i < 6; ++i)
    {
    /* Parse two characters at a time. */
    hex = get_hex(*mac_address_string);
    if (hex == -1) { return -1; }
    mac_address_string++;
    hex = (hex << 4) | get_hex(*mac_address_string);
    if (hex == -1) { return -1; }
    mac_address_string++;
    mac_addr[i] = (char) (hex & 0xFF);
    /* We might get a ' ', ':', '.' or '-' here */
    if ((*mac_address_string == ':') ||
        (*mac_address_string == ' ') ||
        (*mac_address_string == '.') ||
        (*mac_address_string == '-'))   ++mac_address_string;
    }
  if (debug)
    {
    fprintf(stderr,"The extracted MAC address is: ");
    for (i = 0; i < 6; i++)
    fprintf(stderr,"%02x", mac_addr[i]);
    fprintf(stderr,"\n");
    }
  return (mac_address_string - orig == 17) ? 0 : -1;
  }
