diff --git a/configure.ac b/configure.ac index c9bfec2..0173fb0 100644 --- a/configure.ac +++ b/configure.ac @@ -2,7 +2,7 @@ # Process this file with autoconf to produce a configure script. AC_PREREQ([2.69]) -AC_INIT([sendwol], [0.02], [https://github.com/KusaReMKN/sendwol/issue]) +AC_INIT([sendwol], [0.10], [https://github.com/KusaReMKN/sendwol/issue]) AM_INIT_AUTOMAKE([foreign]) AC_CONFIG_SRCDIR([sendwol.c]) AC_CONFIG_HEADERS([config.h]) @@ -13,7 +13,7 @@ AC_PROG_CC # Checks for libraries. # Checks for header files. -AC_CHECK_HEADERS([arpa/inet.h locale.h netdb.h netinet/in.h stdlib.h string.h sys/ioctl.h sys/socket.h unistd.h]) +AC_CHECK_HEADERS([arpa/inet.h locale.h netdb.h netinet/in.h stdlib.h string.h sys/ioctl.h sys/param.h sys/socket.h unistd.h]) # Checks for typedefs, structures, and compiler characteristics. AC_C_RESTRICT @@ -22,6 +22,7 @@ AC_TYPE_UINT16_T AC_TYPE_UINT8_T # Checks for library functions. +AC_FUNC_MALLOC AC_CHECK_FUNCS([memset setlocale socket strtol]) AC_CONFIG_FILES([Makefile]) diff --git a/sendwol.1 b/sendwol.1 index 51c86bc..ae62fb3 100644 --- a/sendwol.1 +++ b/sendwol.1 @@ -2,7 +2,7 @@ .\" Copyright (c) 2022 KusaReMKN .\" Available under the 2-Clause BSD License .\" -.Dd 28th March 2022 +.Dd 30th March 2022 .Dt SENDWOL 1 .Os .Sh NAME @@ -12,6 +12,7 @@ .Nm .Op Fl 4 | Fl 6 .Op Fl b | Fl a Ar address | Fl h Ar hostname +.Op Fl f Ar file .Op Fl i Ar interface .Op Fl p Ar port | Fl s Ar service .Ar target ... @@ -36,6 +37,26 @@ Use the broadcast address of the interface as destination. This is the default. .It Fl a Ar address , Fl h Ar hostname Specify the destination address or host name. +.It Fl f Ar file +Specify the database file containing the MAC addresses of the network hosts. +The data is stored in the following format: +.Bd -literal -offset indent +.Ar MAC-address host-name +.Ed +.Pp +Each element is separated by any number of blank spaces and/or tab characters. +If there is a hash character +.Pq Ql # +at the beginning of a line, the line is ignored as a comment. +.Pp +By default, +.Pa $HOME/.sendwol +and +.Pa /etc/ethers +are looked up in this order. +The specified file is looked up before them. +If this option is specified more than once, +those specified later will be looked up first. .It Fl i Ar interface Specify the name of the interface from which packets are sent. This option applies only if the UDP destination is a multicast address @@ -47,6 +68,18 @@ Specify the destination port number or service name. The default value is .Ql 9 . .El +.Sh ENVIRONMENT +.Bl -tag -compact +.It Ev HOME +Pathname of the user's home directory. +.El +.Sh FILES +.Bl -tag -compact +.It Pa $HOME/.sendwol +User's MAC address database file. +.It Pa /etc/ethers +System Ethernet address database file. +.El .Sh EXIT STATUS .Ex -std .Sh EXAMPLES @@ -76,6 +109,7 @@ and the packets would reach every node on the network link. .Xr ip 4 , .Xr ip6 4 , .Xr udp 4 , +.Xr ethers 5 , .Xr ifconfig 8 .Sh COPYRIGHT Copyright \(co 2022 diff --git a/sendwol.c b/sendwol.c index ce30252..09330a2 100644 --- a/sendwol.c +++ b/sendwol.c @@ -28,6 +28,7 @@ #include #include +#include #include #include @@ -61,12 +62,25 @@ struct magic_packet { struct macaddr mp_target[MP_TARGET_REP]; } __attribute__((__packed__)); +/* + * database list + */ +struct dblist { + struct dblist *dbl_next; + const char *dbl_path; +}; static int brd_connect(int domain, const char *restrict servname, const char *restrict interface); static struct magic_packet *build_magicpacket(const struct macaddr *target); +static void free_dblist(struct dblist *head); +static char *home_sendwol(void); +static struct macaddr *lookup_dblist(const struct dblist *restrict head, + const char *restrict hostname); static struct macaddr *mac_aton(const char *a); static const char *multicast_if(int fd, int domain, const char *interface); +static int push_dblist(struct dblist **restrict headp, + const char *restrict path); static int udp_connect(int domain, const char *restrict nodename, const char *restrict servname, const char *restrict interface); static void usage(void); @@ -76,20 +90,38 @@ main(int argc, char *argv[]) { int c, fd, yes; int broadcast, domain, forcev4, forcev6; - char *interface, *nodename, *servname; + char *homesendwol, *interface, *nodename, *servname; + struct dblist *list; struct macaddr *target; struct magic_packet *packet; ssize_t written; (void)setlocale(LC_ALL, ""); + list = NULL; + if (push_dblist(&list, "/etc/ethers") == -1) + goto dberror; + + homesendwol = home_sendwol(); + if (homesendwol != NULL) { + if (push_dblist(&list, homesendwol) == -1) { +dberror: warn(NULL); + warnx("database files are not available"); + free_dblist(list); + list = NULL; + } + } else { + warn(NULL); + warnx("~/.sendwol is not available"); + } + forcev4 = 0; forcev6 = 0; broadcast = 0; interface = NULL; nodename = NULL; servname = "9"; - while ((c = getopt(argc, argv, "46ba:h:i:p:s:")) != -1) + while ((c = getopt(argc, argv, "46ba:h:f:i:p:s:")) != -1) switch (c) { case '4': forcev4 = 1; @@ -104,6 +136,16 @@ main(int argc, char *argv[]) case 'h': nodename = optarg; break; + case 'f': + if (list == NULL) + break; + if (push_dblist(&list, optarg) == -1) { + warn(NULL); + warnx("database files are not available"); + free_dblist(list); + list = NULL; + } + break; case 'i': interface = optarg; break; @@ -141,8 +183,12 @@ main(int argc, char *argv[]) for (; *argv != NULL; argv++) { target = mac_aton(*argv); if (target == NULL) { - warnx("%s: invalid MAC address", *argv); - continue; + target = lookup_dblist(list, *argv); + if (target == NULL) { + warnx("%s: invalid MAC address" + " or unknown host", *argv); + continue; + } } packet = build_magicpacket(target); written = write(fd, packet, sizeof(*packet)); @@ -150,6 +196,8 @@ main(int argc, char *argv[]) err(1, "write"); } + free_dblist(list); + if (close(fd) == -1) err(1, "close"); @@ -236,6 +284,70 @@ build_magicpacket(const struct macaddr *target) return ∓ } +static void +free_dblist(struct dblist *head) +{ + struct dblist *tmp; + + while ((tmp = head) != NULL) { + head = head->dbl_next; + free(tmp); + } +} + +static char * +home_sendwol(void) +{ +#define HOME_SENDWOL "%s/.sendwol" + int length; + char *buffer, *home; + + home = getenv("HOME"); + if (home == NULL) + return NULL; + + length = snprintf(NULL, 0, HOME_SENDWOL, home) + 1; + buffer = (char *)malloc(length); + if (buffer == NULL) + return NULL; + + snprintf(buffer, length, HOME_SENDWOL, home); + + return buffer; +} + +static struct macaddr * +lookup_dblist(const struct dblist *restrict head, + const char *restrict hostname) +{ + int elem; + FILE *fp; + struct macaddr *res; + char line[BUFSIZ]; /* I have no idea how much buffer is needed */ + char host[MAXHOSTNAMELEN]; /* This is also */ + char addrstr[18]; + + for (; head != NULL; head = head->dbl_next) { + fp = fopen(head->dbl_path, "r"); + if (fp == NULL) + continue; + while (fgets(line, sizeof(line), fp) != NULL) { + if (line[0] == '#') + continue; + elem = sscanf(line, "%s%s", addrstr, host); + if (elem < 2) + continue; + if (strcmp(hostname, host) == 0) { + (void)fclose(fp); + return mac_aton(addrstr); /* found */ + } + } + (void)fclose(fp); + } + + return NULL; +} + static struct macaddr * mac_aton(const char *a) { @@ -285,6 +397,22 @@ multicast_if(int fd, int domain, const char *interface) return NULL; } +static int +push_dblist(struct dblist **restrict headp, const char *restrict path) +{ + struct dblist *temp; + + temp = (struct dblist *)malloc(sizeof(*temp)); + if (temp == NULL) + return -1; + + temp->dbl_next = *headp; + temp->dbl_path = path; + *headp = temp; + + return 0; +} + static int udp_connect(int domain, const char *restrict nodename, const char *restrict servname, const char *restrict interface) @@ -328,13 +456,13 @@ udp_connect(int domain, const char *restrict nodename, return fd; } - static void usage(void) { (void)fprintf(stderr, "usage: sendwol [-4 | -6]" - " [-b | -a address | -h hostname] [-i interface]\n" - " [-p port | -s service] target ...\n"); + " [-b | -a address | -h hostname] [-f file]\n" + " [-i interface] [-p port | -s service]" + " target ...\n"); exit(1); }