summaryrefslogblamecommitdiff
path: root/hostname/hostname.c
blob: 7dcef71c30f4e8d95afaf5a5edc6409bf07e3858 (plain) (tree)



















                                                                   











                                  






























































































                                                                                                  






                                                                            
                               











                                                   

                                                                   











                                                    
                                                            




                                                               







                                                                      
                                                                       





                                
























































































                                                                                  
/*
 * 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;
}