/* * 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 /* inet_ntop */ #include /* errno */ #include /* bool type and true/false */ #include /* stderr, stdout, fprintf, fflush */ #include /* EXIT_FAILURE, EXIT_SUCCESS */ #include /* strchr */ #include /* getopt, sysconf */ /* gai and friends */ #include #include /* open, read, close */ #include #include 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') + 1, curr != 0x1); } } 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; }