/*
* hostname - get or set the node name of the current workstation.
* Copyright (c) 2022 A. Wilcox. All rights reserved.
* Licensed under the NCSA open source license.
* See LICENSE file included with this source for more information.
*/
#include <arpa/inet.h> /* inet_ntop */
#include <errno.h> /* errno */
#include <stdbool.h> /* bool type and true/false */
#include <stdio.h> /* stderr, stdout, fprintf, fflush */
#include <stdlib.h> /* EXIT_FAILURE, EXIT_SUCCESS */
#include <string.h> /* strchr */
#include <unistd.h> /* getopt, sysconf */
/* gai and friends */
#include <sys/socket.h>
#include <netdb.h>
/* open, read, close */
#include <sys/stat.h>
#include <fcntl.h>
int sethostname(const char *value,
#ifdef __linux__
size_t
#else
int
#endif
namelen);
int do_print_nodename(bool verbose, bool domain, bool fqdn, bool address, bool shorten)
{
long hostname = sysconf(_SC_HOST_NAME_MAX);
char buf[hostname];
struct addrinfo *info;
if(verbose)
{
fprintf(stderr, "gethostname()=...");
fflush(stderr);
}
if(gethostname(buf, sizeof buf) != 0)
{
if(verbose) fprintf(stderr, "{error}\n");
perror("gethostname");
return EXIT_FAILURE;
}
if(verbose) fprintf(stderr, "\"%s\"\n", buf);
if(domain || fqdn || address)
{
struct addrinfo hints = {0};
char addr_buf[INET6_ADDRSTRLEN];
if(verbose)
{
fprintf(stderr, "getaddrinfo(\"%s\")=...", buf);
fflush(stderr);
}
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_CANONNAME | AI_ADDRCONFIG;
if(getaddrinfo(buf, NULL, &hints, &info) != 0)
{
if(verbose) fprintf(stderr, "{error}\n");
perror("getaddrinfo");
return EXIT_FAILURE;
}
if(info == NULL || info->ai_canonname == NULL)
{
if(verbose) fprintf(stderr, "{no information}\n");
fprintf(stderr, "No information available\n");
return EXIT_FAILURE;
}
if(getnameinfo(info->ai_addr, info->ai_addrlen, addr_buf, sizeof addr_buf,
NULL, 0, NI_NUMERICHOST) != 0 && address)
{
if(verbose) fprintf(stderr, "{getnameinfo returned error}\n");
perror("getnameinfo");
return EXIT_FAILURE;
}
if(verbose) fprintf(stderr, "resolved: %s at %s\n", info->ai_canonname, addr_buf);
if(domain)
{
char *domain = strchr(info->ai_canonname, '.');
if(domain == NULL)
{
fprintf(stdout, "(none)\n");
return EXIT_SUCCESS;
}
fprintf(stdout, "%s\n", domain + 1);
return EXIT_SUCCESS;
}
if(fqdn)
{
fprintf(stdout, "%s\n", info->ai_canonname);
return EXIT_SUCCESS;
}
if(address)
{
fprintf(stdout, "%s\n", addr_buf);
return EXIT_SUCCESS;
}
}
if(shorten)
{
char *domain = strchr(buf, '.');
if(domain != NULL) *domain = '\0';
}
fprintf(stdout, "%s\n", buf);
return EXIT_SUCCESS;
}
int do_set_nodename(bool file, const char *value)
{
size_t len;
if(file)
{
/* This is insane. There is no validation whatsoever.
* However, strace on net-tools hostname(1) shows this to be
* the exact algorithm used, so... */
char buf[1025];
int fildes = open(value, O_RDONLY);
ssize_t result = 0;
if(fildes == -1)
{
perror("open");
return EXIT_FAILURE;
}
while(true)
{
char *curr = buf;
memset(buf, 0, sizeof buf);
result = read(fildes, buf, sizeof buf - 1);
switch(result)
{
case -1:
perror("read");
close(fildes);
return EXIT_FAILURE;
case 0:
close(fildes);
return EXIT_SUCCESS;
}
do
{
len = strnlen(curr, result);
char *pos = strchr(curr, '\n');
if(pos)
{
len = pos - curr;
}
if(len == 0 || *curr == '#') continue;
while(len > 63)
{
sethostname(curr, 63);
len -= 63;
curr += 63;
}
sethostname(curr, len);
} while((curr = strchr(curr, '\n')) && curr++);
}
}
len = strlen(value);
sethostname(value, len);
return EXIT_SUCCESS;
}
void usage()
{
fprintf(stderr, "usage:\n");
fprintf(stderr, "hostname {hostname|-F filename}\n");
fprintf(stderr, "hostname [-v] [-d|-f|-s]\n");
fprintf(stderr, "\n\tRetrieve or set node name.\n");
}
int main(int argc, char *argv[])
{
/* Whether to use a file or argument */
bool is_file = false;
/* Enable verbose output */
bool verbose = false;
/* Intentionally shorten name to first part */
bool shorten = false;
/* Show DNS domain name only (if available) */
bool domain = false;
/* Go out of our way to show FQDN */
bool fqdn = false;
/* Print the IP address associated with this node */
bool address = false;
/* An error occurred during argument parsing */
bool arg_error = false;
/* The current argument being parsed */
int arg;
while((arg = getopt(argc, argv, ":FVvdfish")) != -1)
{
switch(arg)
{
case 'v':
verbose = true;
break;
case 'd':
domain = true;
break;
case 'F':
is_file = true;
break;
case 'f':
fqdn = true;
break;
case 'i':
address = true;
break;
case 's':
shorten = true;
break;
case 'h':
usage();
return EXIT_SUCCESS;
case 'V':
fprintf(stdout, "Shimmy " SHIMMY_VERSION "\n");
return EXIT_SUCCESS;
case ':':
fprintf(stderr, "required argument to '-%c' missing\n",
optopt);
arg_error = true;
break;
case '?':
default:
fprintf(stderr, "unrecognised option: '-%c'\n", optopt);
arg_error = true;
break;
}
}
switch(argc - optind)
{
case 0:
if(is_file || arg_error)
{
arg_error = true;
break;
}
return do_print_nodename(verbose, domain, fqdn, address, shorten);
case 1:
return do_set_nodename(is_file, argv[optind++]);
default:
/* just show usage. */
break;
}
usage();
return EXIT_FAILURE;
}