From patchwork Mon Oct 3 15:27:19 2022 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Robin Roevens X-Patchwork-Id: 6037 Return-Path: Received: from mail01.ipfire.org (mail01.haj.ipfire.org [172.28.1.202]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) client-signature ECDSA (P-384)) (Client CN "mail01.haj.ipfire.org", Issuer "R3" (verified OK)) by web04.haj.ipfire.org (Postfix) with ESMTPS id 4Mh6BV2sj3z3wcJ for ; Mon, 3 Oct 2022 16:44:50 +0000 (UTC) Received: from mail02.haj.ipfire.org (mail02.haj.ipfire.org [172.28.1.201]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) client-signature ECDSA (P-384)) (Client CN "mail02.haj.ipfire.org", Issuer "R3" (verified OK)) by mail01.ipfire.org (Postfix) with ESMTPS id 4Mh6BS0WmCz2RG; Mon, 3 Oct 2022 16:44:48 +0000 (UTC) Received: from mail02.haj.ipfire.org (localhost [127.0.0.1]) by mail02.haj.ipfire.org (Postfix) with ESMTP id 4Mh6BR6Tbkz2yWd; Mon, 3 Oct 2022 16:44:47 +0000 (UTC) Received: from mail01.ipfire.org (mail01.haj.ipfire.org [172.28.1.202]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (P-384) client-signature ECDSA (P-384)) (Client CN "mail01.haj.ipfire.org", Issuer "R3" (verified OK)) by mail02.haj.ipfire.org (Postfix) with ESMTPS id 4Mh6BQ65J5z2xRw for ; Mon, 3 Oct 2022 16:44:46 +0000 (UTC) Received: from knopi.disroot.org (knopi.disroot.org [178.21.23.139]) (using TLSv1.2 with cipher ECDHE-ECDSA-AES256-GCM-SHA384 (256/256 bits)) (Client did not present a certificate) by mail01.ipfire.org (Postfix) with ESMTPS id 4Mh6BQ2fvMz1Pk for ; Mon, 3 Oct 2022 16:44:46 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by disroot.org (Postfix) with ESMTP id C5EFE4C702 for ; Mon, 3 Oct 2022 18:44:45 +0200 (CEST) X-Virus-Scanned: SPAM Filter at disroot.org Received: from knopi.disroot.org ([127.0.0.1]) by localhost (disroot.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id 3AjIhE7afHMc for ; Mon, 3 Oct 2022 18:44:43 +0200 (CEST) Received: from chojin.sicho.home (amaterasu.sicho.home [192.168.0.1]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (no client certificate requested) (Authenticated sender) by hachiman (MailScanner Milter) with SMTP id B4B0471E35; Mon, 3 Oct 2022 17:27:49 +0200 (CEST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=disroot.org; s=mail; t=1664815481; bh=A1R2rXf6ez0eUhiBiUj2pgv8xJ6MZwfdvfnvQXyfEfg=; h=From:To:Cc:Subject:Date:In-Reply-To:References; b=Dtcc99/09M4+Pi+v/LfoKG9EZdoI687bZ4UXHSNU87pQfHh2nPN0kS7KN/Gkwz3ji eVRrH1ctr6v0oEUViVVy4FihcEpYQKsVdOEXEPD6qEJl5dkKLzqy7YLLppX/yFFbFS Lkc3tCmIvxzKnVAVDogQGIe014uceT9DLBRIMmUXKFuOPqUtjAiIK4Hr1tZJjLIE2h nmMth9IZ9WDmRIjVeT1chKKTCaMIUdYi/Hz1GOw07z4QXlbupDlfoDdb6Jm+PwWQJz 4oYK6aZoRNC+1Mi2rcm9rhS0B0ZF9UoJvaZ3lQZE6lbKMWRrF04s7hd5kjA51EVTCb mxeDNEEPkt7tQ== From: Robin Roevens To: development@lists.ipfire.org Subject: [PATCH 1/2] misc-progs: addonctrl: Add support for 'Services' metadata Date: Mon, 3 Oct 2022 17:27:19 +0200 Message-Id: <20221003152720.13140-3-robin.roevens@disroot.org> In-Reply-To: <20221003152720.13140-1-robin.roevens@disroot.org> References: <20221003152720.13140-1-robin.roevens@disroot.org> Mime-Version: 1.0 X-sicho-MailScanner-ID: B4B0471E35.A8A80 X-sicho-MailScanner: Found to be clean X-sicho-MailScanner-From: robin.roevens@disroot.org X-sicho-MailScanner-Watermark: 1665415669.91726@t9pkzH/avvadaR2B9Ul3uw ARC-Seal: i=1; s=202003rsa; d=lists.ipfire.org; t=1664815486; a=rsa-sha256; cv=none; b=C8/jDM2clik2Pa9WmoG949zUAi6aVoir/HtKQ6QqKakhFRQdpXcPLvbYHkjyMU41XdAwjA jTaxhnnW424rYnYJ1Lv7tCPnE/3HV1EHYtFJ4u1kMW59Cj6dL7f5saKjXFUvmyc03RtftF +u7xZoqZ8wubcIAdnpAcOLwX6+rHZ0CGuBNkPNcPnwuLidUr5xcwQE1xZLl10DyJVoBz8p X1m8Ex4Vfw9GSrTNxTAKTN51aRdQl4rlcSVi9lWTUdBueq/0bM9MyNUtJMORSfxZgZ2UC+ drVAnu5PdNh2PQ69XZp+B3dxPi0wLZZjnjnHuqRAvzl0IzxNXeZ+PZ9lHZ+UfA== ARC-Authentication-Results: i=1; mail01.ipfire.org; dkim=pass header.d=disroot.org header.s=mail header.b="Dtcc99/0"; spf=pass (mail01.ipfire.org: domain of robin.roevens@disroot.org designates 178.21.23.139 as permitted sender) smtp.mailfrom=robin.roevens@disroot.org; dmarc=pass (policy=quarantine) header.from=disroot.org ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=lists.ipfire.org; s=202003rsa; t=1664815486; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references:dkim-signature; bh=eqG5zps9sMmlShxHNcMMbxRJi4Zia8QaGduT02G6TG0=; b=slwH+iZQeAWIg43UA8/2uG2UutWEc43wwhDvqxYw7mYlCK/WJObS5TC50QFUv3k1KqDIFG yii9xM/HxAy8BTBCzGwbvDgnAcmvHNbi+MJ+OCOzzbpbXpWT6Uc9ntuQkWXp/TJ6C/+iO2 5copIX9KRWrIPhzkgwfPFdQ3p6vcAPiI8KGOGQBydhI2zpFo95TacqV/oJdSKB54vWiQfy oeLRjZcBrBrvOrXPZjbzohmieK9UwaYuc14zW6330sV1ossF3DMmF7j6Q2o8hXv+rnN/jc nQktBo0lRAwOlvFnRR7Utn5s+bQ9iN0G4TB1uit6GUXsQ/3/+02kcz9ycWYBig== Authentication-Results: mail01.ipfire.org; dkim=pass header.d=disroot.org header.s=mail header.b="Dtcc99/0"; spf=pass (mail01.ipfire.org: domain of robin.roevens@disroot.org designates 178.21.23.139 as permitted sender) smtp.mailfrom=robin.roevens@disroot.org; dmarc=pass (policy=quarantine) header.from=disroot.org X-Rspamd-Server: mail01.haj.ipfire.org X-Spamd-Result: default: False [-5.05 / 11.00]; BAYES_HAM(-3.00)[99.99%]; IP_REPUTATION_HAM(-1.07)[asn: 50673(-0.30), country: NL(-0.01), ip: 178.21.23.139(-0.76)]; MID_CONTAINS_FROM(1.00)[]; NEURAL_HAM(-1.00)[-1.000]; DKIM_REPUTATION(-0.77)[-0.76738082578851]; SPF_REPUTATION_HAM(-0.67)[-0.66696041144247]; DMARC_POLICY_ALLOW(-0.50)[disroot.org,quarantine]; R_MISSING_CHARSET(0.50)[]; MV_CASE(0.50)[]; R_DKIM_ALLOW(0.26)[disroot.org:s=mail]; R_SPF_ALLOW(-0.20)[+a:c]; MIME_GOOD(-0.10)[text/plain]; MX_GOOD(-0.01)[]; ARC_NA(0.00)[]; PREVIOUSLY_DELIVERED(0.00)[development@lists.ipfire.org]; FROM_HAS_DN(0.00)[]; TO_MATCH_ENVRCPT_SOME(0.00)[]; FROM_EQ_ENVFROM(0.00)[]; RCVD_TLS_LAST(0.00)[]; RCVD_COUNT_THREE(0.00)[4]; ARC_SIGNED(0.00)[lists.ipfire.org:s=202003rsa:i=1]; ASN(0.00)[asn:50673, ipnet:178.21.23.0/24, country:NL]; DKIM_TRACE(0.00)[disroot.org:+]; MIME_TRACE(0.00)[0:+]; RCPT_COUNT_TWO(0.00)[2]; TO_DN_SOME(0.00)[] X-Rspamd-Queue-Id: 4Mh6BQ2fvMz1Pk X-BeenThere: development@lists.ipfire.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: IPFire development talk List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: development-bounces@lists.ipfire.org Sender: "Development" * Addonctrl will now check in addon metadata for the exact initscript names (Services). If more than one initscript is defined for an addon, the requested action will be performed on all listed initscripts. * Added posibility to perform action on a specific initscript of an addon instead of on all initscripts of the addon. * New action 'list-services' to display a list of services related to an addon. * New action 'boot-status' to display wether service(s) are enabled to start on boot or not. * More error checking and cleaner error reporting to user * General cleanup and code restructuring to avoid code duplication * Updated and made usage instructions more verbose. Fixes: Bug#12935 Signed-off-by: Robin Roevens Tested-by: Adolf Belka --- src/misc-progs/addonctrl.c | 384 +++++++++++++++++++++++++++++++------ 1 file changed, 323 insertions(+), 61 deletions(-) diff --git a/src/misc-progs/addonctrl.c b/src/misc-progs/addonctrl.c index 14b4b1325..277bd1809 100644 --- a/src/misc-progs/addonctrl.c +++ b/src/misc-progs/addonctrl.c @@ -10,71 +10,333 @@ #include #include #include +#include #include +#include +#include #include "setuid.h" #define BUFFER_SIZE 1024 +const char *enabled_path = "/etc/rc.d/rc3.d"; +const char *disabled_path = "/etc/rc.d/rc3.d/off"; + +char errormsg[BUFFER_SIZE] = ""; +char *usage = + "Usage\n" + " addonctrl (start|stop|restart|reload|enable|disable|status|boot-status|list-services) []\n" + "\n" + "Options:\n" + " \t\tName of the addon to control\n" + " \t\tSpecific service of the addon to control (optional)\n" + " \t\t\tBy default the requested action is performed on all related services. See also 'list-services'.\n" + " start\t\t\tStart service(s) of the addon\n" + " stop\t\t\tStop service(s) of the addon\n" + " restart\t\tRestart service(s) of the addon\n" + " enable\t\tEnable service(s) of the addon to start at boot\n" + " disable\t\tDisable service(s) of the addon to start at boot\n" + " status\t\tDisplay current state of addon service(s)\n" + " boot-status\t\tDisplay wether service(s) is enabled on boot or not\n" + " list-services\t\tDisplay a list of services related to the addon"; + +// Check if matches . Wildcard '?' matches any single character. +// Returns 1 when found, 0 when not found +int match(const char *pattern, const char *text) { + if (pattern[0] == '\0' && *text == '\0') + return 1; + if (*text != '\0' && (pattern[0]=='?' || pattern[0]==*text)) + return match(pattern+1, text+1); + return 0; +} + +// Find a file using allowing the '?' wildcard. +// Returns the found filename or NULL if not found +char* find_file_in_dir(const char *path, const char *filepattern) +{ + static struct dirent *entry; + DIR *dp; + int found = 0; + + if ((dp = opendir(path)) != NULL) { + while(found == 0 && (entry = readdir(dp)) != NULL) + found = match(filepattern, entry->d_name); + + closedir(dp); + } + + if (! found) { + return NULL; + } + + return entry->d_name; +} + +// Reads Services metadata for . +// Returns pointer to array of strings containing the services for +// and sets to the number of found services +char **get_addon_services(const char *addon, int *servicescnt) { + const char *metafile_prefix = "/opt/pakfire/db/installed/meta-"; + const char *metadata_key = "Services"; + const char *keyvalue_delim = ":"; + const char *service_delim = " "; + char *token; + char **services = NULL; + char *service; + char line[BUFFER_SIZE]; + int i = 0; + char *metafile = malloc((strlen(metafile_prefix) + strlen(addon) + 1) * sizeof(char)); + + sprintf(metafile, "%s%s", metafile_prefix, addon); + FILE *fp = fopen(metafile,"r"); + if ( fp ) { + // Get initscript(s) for addon from meta-file + while (!feof(fp) && services == NULL) { + if (fgets(line, BUFFER_SIZE - 1, fp) != NULL) { + // Strip newline + char *newline = strchr(line, '\n'); + if (newline) *newline = 0; + + // Parse key/value and look for required key. + token = strtok(line, keyvalue_delim); + if (token != NULL && strcmp(token, metadata_key) == 0) { + token = strtok(NULL, keyvalue_delim); + if (token != NULL) { + services = malloc((strlen(token) + 1) * sizeof (char *)); + + // Put each service in services array + service = strtok(token, service_delim); + while (service != NULL) { + services[i] = malloc((strlen(service) + 1) * sizeof (char)); + strcpy(services[i++], service); + + service = strtok(NULL, service_delim); + } + } + } + } else { + snprintf(errormsg, BUFFER_SIZE - 1, "Could not read '%s' metadata for addon '%s'.", metadata_key, addon); + } + } + fclose(fp); + } else { + snprintf(errormsg, BUFFER_SIZE - 1, "Addon '%s' not found.\n\n%s", addon, usage); + } + + free (metafile); + *servicescnt = i; + return services; +} + +// Calls initscript with parameter +int initscript_action(const char *service, const char *action) { + const char *initd_path = "/etc/rc.d/init.d"; + char *command = malloc((strlen(initd_path) + 1 + strlen(service) + 1) * sizeof(char)); + int r = 0; + + sprintf(command, "%s/%s %s", initd_path, service, action); + r = safe_system(command); + free(command); + + return r; +} + +// Move an initscript with filepattern from to +// Returns: +// -1: Error during move. Details in errno (returned by C rename function) +// 0: Success +// 1: file was not moved, but is already in +// 2: file does not exist in either in or +int move_initscript_by_pattern(const char *src_path, const char *dest_path, const char *filepattern) { + char *src, *dest; + int r = 1; + char *filename = find_file_in_dir(src_path, filepattern); + + if ( filename != NULL ) { + src = malloc((strlen(src_path) + 1 + strlen(filename) + 1) * sizeof(char)); + dest = malloc((strlen(dest_path) + 1 + strlen(filename) + 1) * sizeof(char)); + sprintf(src, "%s/%s", src_path, filename); + sprintf(dest, "%s/%s", dest_path, filename); + + r = rename(src, dest); + + free(src); + free(dest); + } else { + filename = find_file_in_dir(dest_path, filepattern); + if (filename == NULL) + r = 2; + } + + return r; +} + +// Enable/Disable addon service(s) by moving initscript symlink from/to disabled_path +int toggle_service(const char *service, const char *action) { + const char *src_path, *dest_path; + char *filepattern = malloc((3 + strlen(service) + 1) * sizeof(char)); + int r = 0; + + sprintf(filepattern, "S??%s", service); + + if (strcmp(action, "enable") == 0) { + src_path = disabled_path; + dest_path = enabled_path; + } else { + src_path = enabled_path; + dest_path = disabled_path; + } + + // Ensure disabled_path exists + if (mkdir(disabled_path, S_IRWXU + S_IRGRP + S_IXGRP + S_IROTH + S_IXOTH) == -1 && errno != EEXIST) { + r = 1; + snprintf(errormsg, BUFFER_SIZE -1, "Error creating %s. (Error: %d)", disabled_path, errno); + } else { + r = move_initscript_by_pattern(src_path, dest_path, filepattern); + if (r == -1 ) { + r = 1; + snprintf(errormsg, BUFFER_SIZE - 1, "Could not %s %s. (Error: %d)", action, service, errno); + } else if (r == 1) { + snprintf(errormsg, BUFFER_SIZE - 1, "Service %s is already %sd. Skipping...", service, action); + } else if (r == 2) { + snprintf(errormsg, BUFFER_SIZE - 1, "Unable to %s service %s. (Service has no valid symlink in %s).", action, service, src_path); + } + } + + free(filepattern); + + return r; +} + +// Print to stdout wether is enabled or disabled on boot +// Prints as Not available when initscript is not found +// in either enabled_path or disabled_path. +void print_boot_status(char *service) { + char *filepattern = malloc((3 + strlen(service) + 1) * sizeof(char)); + sprintf(filepattern, "S??%s", service); + char *enabled = find_file_in_dir(enabled_path, filepattern); + char *disabled = find_file_in_dir(disabled_path, filepattern); + + if (enabled != NULL) + fprintf(stdout, "%s is enabled on boot.\n", service); + else if (disabled != NULL) + fprintf(stdout, "%s is disabled on boot.\n", service); + else + fprintf(stdout, "%s is not available for boot. (Service has no valid symlink in either %s or %s).\n", service, enabled_path, disabled_path); + + free(filepattern); +} + int main(int argc, char *argv[]) { - char command[BUFFER_SIZE]; - - if (!(initsetuid())) - exit(1); - - if (argc < 3) { - fprintf(stderr, "\nMissing arguments.\n\naddonctrl addon (start|stop|restart|reload|enable|disable)\n\n"); - exit(1); - } - - const char* name = argv[1]; - - if (strlen(name) > 32) { - fprintf(stderr, "\nString to large.\n\naddonctrl addon (start|stop|restart|reload|enable|disable)\n\n"); - exit(1); - } - - // Check if the input argument is valid - if (!is_valid_argument_alnum(name)) { - fprintf(stderr, "Invalid add-on name: %s\n", name); - exit(2); - } - - sprintf(command, "/opt/pakfire/db/installed/meta-%s", name); - FILE *fp = fopen(command,"r"); - if ( fp ) { - fclose(fp); - } else { - fprintf(stderr, "\nAddon '%s' not found.\n\naddonctrl addon (start|stop|restart|reload|status|enable|disable)\n\n", name); - exit(1); - } - - if (strcmp(argv[2], "start") == 0) { - snprintf(command, BUFFER_SIZE - 1, "/etc/rc.d/init.d/%s start", name); - safe_system(command); - } else if (strcmp(argv[2], "stop") == 0) { - snprintf(command, BUFFER_SIZE - 1, "/etc/rc.d/init.d/%s stop", name); - safe_system(command); - } else if (strcmp(argv[2], "restart") == 0) { - snprintf(command, BUFFER_SIZE - 1, "/etc/rc.d/init.d/%s restart", name); - safe_system(command); - } else if (strcmp(argv[2], "reload") == 0) { - snprintf(command, BUFFER_SIZE - 1, "/etc/rc.d/init.d/%s reload", name); - safe_system(command); - } else if (strcmp(argv[2], "status") == 0) { - snprintf(command, BUFFER_SIZE - 1, "/etc/rc.d/init.d/%s status", name); - safe_system(command); - } else if (strcmp(argv[2], "enable") == 0) { - snprintf(command, BUFFER_SIZE - 1, "mv -f /etc/rc.d/rc3.d/off/S??%s /etc/rc.d/rc3.d" , name); - safe_system(command); - } else if (strcmp(argv[2], "disable") == 0) { - snprintf(command, BUFFER_SIZE - 1, "mkdir -p /etc/rc.d/rc3.d/off"); - safe_system(command); - snprintf(command, BUFFER_SIZE - 1, "mv -f /etc/rc.d/rc3.d/S??%s /etc/rc.d/rc3.d/off" , name); - safe_system(command); - } else { - fprintf(stderr, "\nBad argument given.\n\naddonctrl addon (start|stop|restart|reload|enable|disable)\n\n"); - exit(1); - } - - return 0; + char **services = NULL; + char **services_ptr = NULL; + int servicescnt = 0; + char *addon = argv[1]; + char *action = argv[2]; + char *service_filter = NULL; + int actioned = 0; + int r = 0; + + if (!(initsetuid())) + exit(1); + + if (argc < 3) { + fprintf(stderr, "\nMissing arguments.\n\n%s\n\n", usage); + exit(1); + } + + if (argc == 4) + service_filter = argv[3]; + + if (strlen(addon) > 32) { + fprintf(stderr, "\nString to large.\n\n%s\n\n", usage); + exit(1); + } + + // Check if the input argument is valid + if (!is_valid_argument_alnum(addon)) { + fprintf(stderr, "Invalid add-on name: %s.\n", addon); + exit(2); + } + + // Get initscript name(s) from addon metadata + services_ptr = get_addon_services(addon, &servicescnt); + services = services_ptr; + if (services == NULL || *services == 0) { + if (strcmp(errormsg, "") != 0) + fprintf(stderr, "\n%s\n\n", errormsg); + else + fprintf(stderr, "\nAddon '%s' has no services.\n\n", addon); + exit(1); + } + + if (strcmp(action, "start") == 0 || + strcmp(action, "stop") == 0 || + strcmp(action, "restart") == 0 || + strcmp(action, "reload") == 0 || + strcmp(action, "status") == 0) { + + while (*services != 0) { + if ((service_filter != NULL && strcmp(service_filter, *services) == 0) || + service_filter == NULL) { + if (initscript_action(*services, action) != 0) + r = 1; + ++actioned; + } + ++services; + } + + } else if (strcmp(action, "enable") == 0 || + strcmp(action, "disable") == 0) { + + while (*services != 0) { + if ((service_filter != NULL && strcmp(service_filter, *services) == 0) || + service_filter == NULL) { + if (toggle_service(*services, action) == 0) { + fprintf(stdout, "%sd service %s\n", action, *services); + } + else { + r = 1; + fprintf(stderr, "\n%s\n\n", errormsg); + } + + ++actioned; + } + ++services; + } + + } else if (strcmp(action, "boot-status") == 0) { + while(*services != 0) { + if ((service_filter != NULL && strcmp(service_filter, *services) == 0) || + service_filter == NULL) { + print_boot_status(*services); + ++actioned; + } + ++services; + } + + } else if (strcmp(action, "list-services") == 0) { + fprintf(stdout, "\nServices for addon %s:\n", addon); + while (*services != 0) { + fprintf(stdout, " %s\n", *services); + ++actioned; + ++services; + } + fprintf(stdout, "\n"); + + } else { + fprintf(stderr, "\nBad argument given.\n\n%s\n\n", usage); + r = 1; + } + + if (r == 0 && service_filter != NULL && actioned == 0) { + fprintf(stderr, "\nNo service %s found for addon %s. Use 'list-services' to get a list of available services\n\n%s\n\n", service_filter, addon, usage); + r = 1; + } + + // Cleanup + for(int i = 0; i < servicescnt; i++) + free(services_ptr[i]); + free(services_ptr); + + return r; }