/* This file is part of Eclat. Copyright (C) 2012-2018 Sergey Poznyakoff. Eclat is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. Eclat is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Eclat. If not, see . */ #include "eclat.h" char *conffile = SYSCONFDIR "/eclat.conf" ; int lint_mode; int dry_run_mode; int match_command_mode; int preprocess_only = 0; char *endpoint = "ec2.amazonaws.com"; char *signature_version = "4"; enum authentication_provider authentication_provider = authp_undefined; char *access_key; char *secret_key; char *security_token; char *region_name; int use_ssl; int use_post; int ssl_verify_peer = 1; char *ssl_ca_file; char *ssl_ca_path; char *format_expr_option; char *format_file_option; char *format_name_option; int sort_option; char *format_file; char *test_map_name; enum eclat_confirm_mode confirm_mode; int translate_option = -1; struct grecs_list *extra_param; FILE *xml_dump_file; static char *categories[] = { [ECLAT_DEBCAT_MAIN] = "main", [ECLAT_DEBCAT_CFGRAM] = "cfgram", [ECLAT_DEBCAT_CFLEX] = "cflex", [ECLAT_DEBCAT_CONF] = "conf", [ECLAT_DEBCAT_CURL] = "curl", [ECLAT_DEBCAT_FORLAN] = "forlan", [ECLAT_DEBCAT_DUMP] = "dump" }; static void debug_init() { int i; for (i = 0; i < sizeof(categories)/sizeof(categories[0]); i++) debug_register(categories[i]); } static int node_ident_cmp(struct grecs_node const *a, struct grecs_node const *b) { return strcmp(a->ident, b->ident); } void listcmd(char *fmt); #include "cmdline.h" #define CMD_MOD 0x01 #define CMD_DESTR 0x02 #define CMD_NOQRY 0x04 struct eclat_command cmdtab[] = { { "start", "start-instances", "StartInstances", eclat_start_instance, CMD_MOD }, { "stop", "stop-instances", "StopInstances", eclat_stop_instance, CMD_MOD }, { "reboot", "reboot-instances", "RebootInstances", eclat_reboot_instance, CMD_MOD }, { "lsaddr", "describe-addresses", "DescribeAddresses", eclat_describe_addresses }, { "lstag", "describe-tags", "DescribeTags", eclat_describe_tags }, { "lsiattr", "describe-instance-attribute", "DescribeInstanceAttribute", eclat_describe_instance_attribute }, { "lsistat", "describe-instance-status", "DescribeInstanceStatus", eclat_describe_instance_status }, { "lsinst", "describe-instances", "DescribeInstances", eclat_describe_instances }, { "lsvol", "describe-volumes", "DescribeVolumes", eclat_describe_volumes }, { "mkaddr", "allocate-address", "AllocateAddress", eclat_allocate_address, CMD_MOD }, { "rmaddr", "release-address", "ReleaseAddress", eclat_release_address, CMD_MOD|CMD_DESTR }, { "assocaddr", "associate-address", "AssociateAddress", eclat_associate_address, CMD_MOD }, { "disasaddr", "disassociate-address", "DisassociateAddress", eclat_disassociate_address, CMD_MOD }, { "mktag", "create-tags", "CreateTags", eclat_create_tags, CMD_MOD }, { "rmtag", "delete-tags", "DeleteTags", eclat_delete_tags, CMD_MOD|CMD_DESTR }, { "dmesg", "get-console-output", "GetConsoleOutput", eclat_get_console_output }, { "lssg", "describe-security-groups", "DescribeSecurityGroups", eclat_describe_security_groups }, { "mksnap", "create-snapshot", "CreateSnapshot", eclat_create_snapshot }, { "lssnap", "describe-snapshots", "DescribeSnapshots", eclat_describe_snapshots }, { "lssattr", "describe-snapshot-attribute", "DescribeSnapshotAttribute", eclat_describe_snapshot_attribute }, { "setsattr", "modify-snapshot-attribute", "ModifySnapshotAttribute", eclat_modify_snapshot_attribute, CMD_MOD }, { "clrsattr", "reset-snapshot-attribute", "ResetSnapshotAttribute", eclat_reset_snapshot_attribute, CMD_MOD|CMD_DESTR }, { "rmsnap", "delete-snapshot", "DeleteSnapshot", eclat_delete_snapshot, CMD_MOD|CMD_DESTR }, { "lszon", "describe-availability-zones", "DescribeAvailabilityZones", eclat_describe_avaialbility_zones }, { "lsreg", "describe-regions", "DescribeRegions", eclat_describe_regions }, { "mkvol", "create-volume", "CreateVolume", eclat_create_volume, CMD_MOD }, { "rmvol", "delete-volume", "DeleteVolume", eclat_delete_volume, CMD_MOD|CMD_DESTR }, { "atvol", "attach-volume", "AttachVolume", eclat_attach_volume, CMD_MOD }, { "devol", "detach-volume", "DetachVolume", eclat_detach_volume, CMD_MOD|CMD_DESTR }, { "setiattr", "modify-instance-attribute", "ModifyInstanceAttribute", eclat_modify_instance_attribute, CMD_MOD }, { "lsimg", "describe-images", "DescribeImages", eclat_describe_images }, { "mkinst", "run-instances", "RunInstances", eclat_run_instances, CMD_MOD }, { "mkimg", "create-image", "CreateImage", eclat_create_image, CMD_MOD }, { "deimg", "deregister-image", "DeregisterImage", eclat_deregister_image, CMD_MOD|CMD_DESTR }, { "cpimg", "copy-image", "CopyImage", eclat_copy_image }, { "cpsnap", "copy-snapshot", "CopySnapshot", eclat_copy_snapshot }, { "lsattr", NULL, NULL, eclat_lsattr, CMD_NOQRY }, { "sg", NULL, NULL, eclat_sg }, { "mksg", "create-security-group", "CreateSecurityGroup", eclat_create_security_group }, { "rmsg", "delete-security-group", "DeleteSecurityGroup", eclat_delete_security_group, CMD_MOD|CMD_DESTR }, { "lsaattr", "describe-image-attribute", "DescribeImageAttribute", eclat_describe_image_attribute }, { "setaattr", "modify-image-attribute", "ModifyImageAttribute", eclat_modify_image_attribute, CMD_MOD }, { "lsvpc", "describe-vpcs", "DescribeVpcs", eclat_describe_vpcs }, { "lsvpcattr", "describe-vpc-attribute", "DescribeVpcAttribute", eclat_describe_vpc_attribute }, { "mkvpc", "create-vpc", "CreateVpc", eclat_create_vpc, CMD_MOD }, { "setvpcattr", "modify-vpc-attribute", "ModifyVpcAttribute", eclat_modify_vpc_attribute, CMD_MOD }, { "rmvpc", "delete-vpc", "DeleteVpc", eclat_delete_vpc, CMD_MOD|CMD_DESTR }, { "lsigw", "describe-internet-gateways", "DescribeInternetGateways", eclat_describe_internet_gateways }, { "mkigw", "create-internet-gateway", "CreateInternetGateway", eclat_create_internet_gateway, CMD_MOD }, { "rmigw", "delete-internet-gateway", "DeleteInternetGateway", eclat_delete_internet_gateway, CMD_MOD|CMD_DESTR }, { "atigw", "attach-internet-gateway", "AttachInternetGateway", eclat_attach_internet_gateway, CMD_MOD }, { "deigw", "detach-internet-gateway", "DetachInternetGateway", eclat_detach_internet_gateway, CMD_MOD|CMD_DESTR }, { "mksubnet", "create-subnet", "CreateSubnet", eclat_create_subnet, CMD_MOD }, { "lssubnet", "describe-subnets", "DescribeSubnets", eclat_describe_subnets }, { "setsubnetattr", "modify-subnet-attribute", "ModifySubnetAttribute", eclat_modify_subnet_attribute, CMD_MOD }, { "rmsubnet", "delete-subnet", "DeleteSubnet", eclat_delete_subnet, CMD_MOD|CMD_DESTR }, { "mkrtab", "create-route-table", "CreateRouteTable", eclat_create_route_table, CMD_MOD }, { "rmrtab", "delete-route-table", "DeleteRouteTable", eclat_delete_route_table, CMD_MOD|CMD_DESTR }, { "lsrtab", "describe-route-tables", "DescribeRouteTables", eclat_describe_route_tables }, { "assocrtab", "associate-route-table", "AssociateRouteTable", eclat_associate_route_table, CMD_MOD }, { "disasrtab", "disassociate-route-table", "DisassociateRouteTable", eclat_disassociate_route_table, CMD_MOD }, { "route", NULL, NULL, eclat_route }, }; size_t cmdcnt = sizeof(cmdtab) / sizeof(cmdtab[0]); static int cmdcmp(const void *a, const void *b) { struct eclat_command const *cmda = a; struct eclat_command const *cmdb = b; if (cmda->name && cmdb->name) return strcmp(cmda->name, cmdb->name); else if (cmda->ident && cmdb->ident) return strcmp(cmda->ident, cmdb->ident); else if (cmda->ident || cmda->name) return 127; else if (cmdb->ident || cmdb->name) return -127; return 0; } static void sortcmds() { qsort(cmdtab, cmdcnt, sizeof(cmdtab[0]), cmdcmp); } /* Format: %[pfx:][sfx][ni] For example, to format the table of available commands for doc/eclat.1: eclat -l '\t\\fB%n\\fR\t\\fB%i\\fR\n' To format a "see also" list of commands (ibidem): eclat -l '%.BR eclat-: (1)n,\n' */ int parsespec(char *start, char **endp, char *fchr, char *s[], int l[]) { char *p; int n = 0; ++start; s[0] = start; s[1] = NULL; l[0] = l[1] = 0; for (p = start; *p; p++) { if (*p == ':' || strchr(fchr, *p)) { if (n > 1) return -1; l[n++] = p - start; if (*p != ':') { *endp = p + 1; return *p; } if (n == 1) s[n] = start = p + 1; } } return -1; } void fmtstr(const char *str, int len) { while (len--) { int c = *str++; if (c == '\\') { if (len) { len--; c = wordsplit_c_unquote_char(*str++); } } fputc(c, stdout); } } void listcmd(char *fmt) { struct eclat_command *cp; for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) { char *p = fmt; char *start = fmt; char *end; char *s[2]; int l[2]; const char *str; while (*p) { if (*p == '%') { switch (parsespec(p, &end, "ni", s, l)) { case 'n': str = cp->name; break; case 'i': str = cp->ident; break; default: p++; continue; } if (str) { if (p != start) fmtstr(start, p - start); fmtstr(s[0], l[0]); printf("%s", str); fmtstr(s[1], l[1]); } start = p = end; } else { p++; } } if (p != start) fmtstr(start, p - start); } } void listcmdhook() { struct eclat_command *cp; printf("Available commands\n"); printf("Eclat name EC2 Name\n"); printf("-----------+---------\n"); for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) { printf(" %-10s", cp->name ? cp->name : ""); if (cp->ident) printf (" %s", cp->ident); putchar ('\n'); } printf("\nRun \"%s COMMAND --help\" to get help on a particular command.\n", program_name); putchar('\n'); } #define NO_MATCH 0 #define EXACT_MATCH 1 #define PARTIAL_MATCH 2 static int ident_matches(const char *p, const char *q) { int match = EXACT_MATCH; for (;; p++, q++) { if (*p == 0) return *q == 0 ? match : NO_MATCH; else if (*q == 0) return PARTIAL_MATCH; else if (*p == *q) continue; else if (*q == '-') { for (; *p != '-'; p++) if (!*p) return NO_MATCH; match = PARTIAL_MATCH; } else break; } return NO_MATCH; } struct eclat_command * find_command_name(const char *name) { struct eclat_command *cp, *match = NULL; for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) { if (cp->name && strcmp(cp->name, name) == 0) return cp; if (!cp->ident) continue; switch (ident_matches(cp->ident, name)) { case NO_MATCH: break; case EXACT_MATCH: return cp; case PARTIAL_MATCH: if (!match) match = cp; else { /* Second match */ err("ambiguous command %s:", name); err(" %s", match->ident); err(" %s", cp->ident); while (++cp < cmdtab + cmdcnt) if (ident_matches(cp->ident, name) != NO_MATCH) err(" %s", cp->ident); exit(EX_USAGE); } } } return match; } static void print_matching_commands(const char *pat) { struct eclat_command *cp; size_t patlen = strlen (pat); for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) { if (cp->name && strlen(cp->name) >= patlen && memcmp(cp->name, pat, patlen) == 0) printf("%s\n", cp->name); if (cp->ident && ident_matches(cp->ident, pat) != NO_MATCH) printf("%s\n", cp->ident); } } struct eclat_command * find_command_tag(const char *tag) { struct eclat_command *cp; for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) { if (eclat_actcmp(cp->tag, tag) == 0) return cp; } return NULL; } void set_command_confirmation(const char *name, enum eclat_confirm_mode cfmode, grecs_locus_t *locus) { struct eclat_command *cp; int flag = 0; if (strcmp(name, "all") == 0) flag = CMD_MOD; else if (strcmp(name, "destructive") == 0) flag = CMD_DESTR; else { cp = find_command_tag(name); if (!cp) grecs_error(locus, 0, "unknown command or class"); else cp->confirm = cfmode; return; } for (cp = cmdtab; cp < cmdtab + cmdcnt; cp++) { if (cp->flags & flag) cp->confirm = cfmode; } } void set_command_format(const char *name, const char *format, grecs_locus_t *locus) { struct eclat_command *cp = find_command_tag(name); if (!cp) { grecs_error(locus, 0, "unknown command"); return; } if (cp->fmt) { grecs_error(locus, 0, "format %s redefined", name); grecs_error(&cp->locus, 0, "this is the location of " "the previous definition"); free(cp->fmt); } cp->fmt = grecs_strdup(format); cp->locus = *locus; } forlan_eval_env_t compile_format_file(const char *fmtfile) { forlan_eval_env_t env; FILE *fp; struct grecs_locus_point pt; fp = fopen(fmtfile, "r"); if (!fp) die(EX_UNAVAILABLE, "cannot open format file \"%s\": %s", fmtfile, strerror(errno)); pt.file = (char*) fmtfile; pt.line = 1; pt.col = 0; env = forlan_parse_file(fp, &pt); fclose(fp); if (!env) exit(EX_UNAVAILABLE); return env; } struct filter_descr *available_filters; static char *aws_typestr[] = { "String", "dateType", "Boolean", "Integer" }; void list_filters(FILE *fp) { struct filter_descr *flt; size_t ncols = get_scr_cols(); size_t col, start; fprintf(fp, "Available filters are:\n"); for (flt = available_filters; flt->name; flt++) { col = fprintf(fp, " %-24s ", flt->name); if (col > 26) { col = start = 26; fprintf(fp, "\n%*.*s", (int)start, (int)start, ""); } if (flt->type != FILTER_ENUM) fprintf(fp, "%s", aws_typestr[flt->type]); else { int i; char *delim; size_t len; col += fprintf(fp, "One of:"); start = col + 1; delim = " "; for (i = 0; flt->avail[i]; i++) { len = strlen(delim) + strlen(flt->avail[i]); if (col + len >= ncols) { fprintf(fp, ",\n%*.*s", (int)start, (int)start, ""); col = start; delim = ""; } col += fprintf(fp, "%s%s", delim, flt->avail[i]); delim = ", "; } } fputc('\n', fp); } fputc('\n', fp); } struct format_defn { const char *name; char *text; grecs_locus_t locus; }; static struct grecs_symtab *format_table; void define_format(const char *name, const char *fmt, grecs_locus_t *loc) { struct format_defn key; struct format_defn *ent; int install = 1; if (!format_table) { format_table = grecs_symtab_create_default( sizeof(struct format_defn)); if (!format_table) grecs_alloc_die(); } key.name = (char*) name; ent = grecs_symtab_lookup_or_install(format_table, &key, &install); if (!ent) grecs_alloc_die(); if (!install) { grecs_error(loc, 0, "redefinition of format \"%s\"", name); grecs_error(&ent->locus, 0, "this is the place of the original definition"); free(ent->text); } ent->text = grecs_strdup(fmt); ent->locus = *loc; } forlan_eval_env_t find_format(const char *name) { struct format_defn key; struct format_defn *defn; forlan_eval_env_t env; if (!format_table) return NULL; key.name = (char*) name; defn = grecs_symtab_lookup_or_install(format_table, &key, NULL); if (!defn) { err("no such format: %s", name); return NULL; } env = forlan_parse_buffer(defn->text, strlen(defn->text), &defn->locus.beg); return env; } static forlan_eval_env_t read_format(struct eclat_command *cmd) { forlan_eval_env_t env = NULL; if (format_expr_option) { struct grecs_locus_point pt; pt.file = ""; pt.line = 1; pt.col = 0; env = forlan_parse_buffer(format_expr_option, strlen(format_expr_option), &pt); } else if (format_file_option) env = compile_format_file(format_file_option); else if (format_name_option) env = find_format(format_name_option); else if (!cmd) return NULL; else if (cmd->fmt) env = forlan_parse_buffer(cmd->fmt, strlen(cmd->fmt), &cmd->locus.beg); else if (format_file) { const char *kwe[5]; struct grecs_locus_point pt; FILE *fp; char *filename; kwe[0] = "command"; kwe[1] = cmd->ident ? cmd->ident : cmd->name; kwe[2] = "action"; kwe[3] = cmd->tag; kwe[4] = NULL; filename = eclat_expand_kw(format_file, kwe); fp = fopen(filename, "r"); if (!fp) { if (errno == ENOENT) { debug(ECLAT_DEBCAT_MAIN, 1, ("cannot open format source \"%s\": %s", filename, strerror(errno))); free(filename); return NULL; } die(EX_UNAVAILABLE, "cannot open format file %s: %s", filename, strerror(errno)); } pt.file = filename; pt.line = 1; pt.col = 0; env = forlan_parse_file(fp, &pt); fclose(fp); free(filename); } else return NULL; if (!env) exit(EX_UNAVAILABLE); return env; } int eclat_do_command(eclat_command_env_t *env, struct eclat_command *command, int argc, char **argv) { int rc; if (!(command->flags & CMD_NOQRY)) { int flags = 0; if (use_ssl) flags |= EC2_RF_HTTPS; if (use_post) flags |= EC2_RF_POST; env->request = eclat_request_create(flags, endpoint, "/", region_name, access_key, security_token); eclat_request_add_param_list(env->request, extra_param); } if (command->tag) eclat_request_add_param(env->request, "Action", command->tag); rc = command->handler(env, argc, argv); if (rc == 0 && !(command->flags & CMD_NOQRY)) { if (!eclat_confirm(confirm_mode, "Proceed with %s", command->ident)) die(EX_CANCELLED, "command not confirmed"); rc = eclat_send_request(env->request, &env->xmltree); } return rc; } int main(int argc, char **argv) { int rc; struct grecs_node *tree; forlan_eval_env_t env = NULL; struct eclat_command *command = NULL; eclat_command_env_t cmdenv; struct timeval tv; set_program_name(argv[0]); proginfo.print_help_hook = listcmdhook; debug_init(); forlan_init(); eclat_map_init(); eclat_map_drv_register(&eclat_map_drv_null); eclat_map_drv_register(&eclat_map_drv_file); eclat_map_drv_register(&eclat_map_drv_seq); eclat_map_drv_register(&eclat_map_drv_bidi); #ifdef WITH_GDBM eclat_map_drv_register(&eclat_map_drv_gdbm); #endif #ifdef WITH_LDAP eclat_map_drv_register(&eclat_map_drv_ldap); #endif eclat_map_drv_register(&eclat_map_drv_ec2); sortcmds(); config_init(); gettimeofday(&tv, NULL); srandom((unsigned int)tv.tv_usec); parse_options(&argc, &argv); if (match_command_mode) { if (argc > 1) die(EX_USAGE, "wrong number of arguments"); print_matching_commands(argc == 1 ? argv[0] : ""); return 0; } if (argc && !test_map_name) { command = find_command_name(argv[0]); if (!command) die(EX_USAGE, "unrecognized command"); } grecs_gram_trace(debug_level(ECLAT_DEBCAT_CFGRAM)); grecs_lex_trace(debug_level(ECLAT_DEBCAT_CFLEX)); if (preprocess_only) exit(grecs_preproc_run(conffile, grecs_preprocessor) ? EX_CONFIG : 0); if (access(conffile, R_OK) == 0) { tree = grecs_parse(conffile); if (!tree) exit(EX_CONFIG); config_finish(tree); /* Prevent texttab from being freed by grecs_tree_free. FIXME: A dirty kludge, needed to preserve file names in locus structures. A proper solution would be to have our own texttab for that purpose. */ tree->v.texttab = NULL; grecs_tree_free(tree); } else if (errno == ENOENT) { warn("no configuration file"); run_config_finish_hooks(); } else die(errno == EACCES ? EX_NOPERM : EX_OSFILE, "cannot access \"%s\": %s", conffile, strerror(errno)); if (translate_option != -1) translation_enabled = translate_option; if (test_map_name) { int i; if (argc < 1) die(EX_USAGE, "wrong number of arguments"); translate_ids(argc, argv, test_map_name); for (i = 0; i < argc; i++) { printf("%s\n", argv[i]); } return 0; } env = read_format(command); if (lint_mode) exit(0); if (!command) die(EX_USAGE, "no command given"); if (!region_name) { debug(ECLAT_DEBCAT_MAIN, 1, ("getting availability region")); region_name = eclat_get_instance_zone(); debug(ECLAT_DEBCAT_MAIN, 1, ("availability region %s", region_name)); } endpoint = region_to_endpoint(region_name); if (!endpoint) die(EX_USAGE, "cannot find endpoint for region %s", region_name); if (access_key && secret_key) authentication_provider = authp_immediate; switch (authentication_provider) { case authp_undefined: die(EX_USAGE, "cannot find authentication credentials"); case authp_immediate: break; case authp_file: if (get_access_creds(access_key, &access_key, &secret_key)) { die(EX_UNAVAILABLE, "cannot find authentication credentials"); } break; case authp_instance: eclat_get_instance_creds(access_key, &access_key, &secret_key, &security_token); } debug(ECLAT_DEBCAT_MAIN, 1, ("using access key %s", access_key)); if (confirm_mode == eclat_confirm_unspecified) confirm_mode = command->confirm; /* Prepare environment */ memset(&cmdenv, 0, sizeof(cmdenv)); cmdenv.cmd = command; rc = eclat_do_command(&cmdenv, command, argc, argv); if (rc) exit(rc); if (xml_dump_file) fclose(xml_dump_file); if (sort_option) grecs_tree_sort(cmdenv.xmltree, node_ident_cmp); if (dry_run_mode) /* nothing */; else if (env) { rc = forlan_run(env, cmdenv.xmltree); } else { grecs_print_node(cmdenv.xmltree, GRECS_NODE_FLAG_DEFAULT, stdout); fputc('\n', stdout); } eclat_map_free_all(); exit(rc); }