| /* Minecraft Interprocess Management Client |
| * Copyright 2015-2020 Rivoreo |
| * |
| * This program 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 of the License, or (at your |
| * option) any later version. |
| * |
| * This program 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. |
| */ |
| |
| #define MCIPM_CLIENT_VERSION "1.0.1" |
| #define MCIPM_CLIENT_COPYRIGHT_LINE "Copyright 2015-2020 Rivoreo" |
| #define MCIPM_CLIENT_LICENSE_INFORMATION \ |
| "This is free software; you are free to change and redistribute it. See the\n" \ |
| "source for copying conditions.\n" \ |
| "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n" \ |
| "PARTICULAR PURPOSE." |
| |
| #include <sys/socket.h> |
| #include <sys/un.h> |
| #include <unistd.h> |
| #include "syncrw.h" |
| #include <getopt.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <errno.h> |
| //#include <sys/stat.h> |
| #include "packet.h" |
| |
| #define _(S) (S) |
| |
| static void print_usage(const char *); |
| |
| //static struct sockaddr_un sockaddr = { .sun_family = AF_UNIX, .sun_path = "mcipm" }; |
| static int fd = -1; |
| |
| extern int entitystat_set_output_format(const char *); |
| extern int entitystat_set_sort(const char *); |
| extern void entitystat_print(struct entity **, unsigned int, int); |
| |
| static void print_server_error(const struct mcipm_packet *packet) { |
| if(packet->length < 1 + 4 + 2) { |
| fprintf(stderr, "Invalid MCIPM_REPLY_ERROR packet: too short (%u<7)\n", packet->length); |
| return; |
| } |
| uint32_t error_code = *(const uint32_t *)packet->data; |
| uint16_t msg_len = *(const uint16_t *)(packet->data + 4); |
| if(packet->length < 1 + 4 + 2 + msg_len) { |
| fprintf(stderr, "Invalid MCIPM_REPLY_ERROR packet: message out of packet\n"); |
| return; |
| } |
| fprintf(stderr, "Error %u from server: ", error_code); |
| fwrite((const char *)(packet->data + 4 + 2), msg_len, 1, stderr); |
| fputc('\n', stderr); |
| } |
| |
| static int entitystat_command(int argc, char **argv) { |
| static const struct option long_options[] = { |
| [0] = { "all", 0, NULL, 'A' }, |
| [1] = { "entity-id", 1, NULL, 'i' }, |
| [2] = { "entity-type", 1, NULL, 't' }, |
| [3] = { "world", 1, NULL, 'W' }, |
| [4] = { "format", 1, NULL, 'o' }, |
| [5] = { "sort", 1, NULL, 0 }, |
| { NULL, 0, NULL, 0 } |
| }; |
| unsigned char *selectors = NULL, *selectors_end = NULL; |
| unsigned int selector_count = 0; |
| int select_all = 0; |
| int wide = 0; |
| optind = 0; |
| while(1) { |
| int option_index; |
| int c = getopt_long(argc, argv, "Ai:o:t:W:w", long_options, &option_index); |
| if(c == -1) break; |
| switch(c) { |
| case 0: |
| switch(option_index) { |
| case 5: |
| if(entitystat_set_sort(optarg) < 0) { |
| fprintf(stderr, "%s: Invalid sort specification '%s'\n", argv[0], optarg); |
| return -1; |
| } |
| break; |
| default: |
| fprintf(stderr, "%s: Surprising option index %d\n", argv[0], option_index); |
| abort(); |
| } |
| break; |
| case 'A': |
| select_all = 1; |
| break; |
| case 'i': |
| if(!select_all) { |
| char *ip = optarg; |
| do { |
| char *endp; |
| long int id = strtol(ip, &endp, 0); |
| if(*endp && *endp != ',') { |
| fprintf(stderr, "Cannot parse '%s' as integer\n", ip); |
| return 1; |
| } |
| ip = endp; |
| unsigned char *p = realloc(selectors, (selectors_end - selectors) + 1 + 4); |
| if(!p) { |
| fputs("Out of memory\n", stderr); |
| return 1; |
| } |
| selectors_end = p + (selectors_end - selectors); |
| selectors = p; |
| *selectors_end++ = MCIPM_GET_ENTITIES_SELECTOR_ENTITY_ID; |
| *(uint32_t *)selectors_end = id; |
| selectors_end += 4; |
| selector_count++; |
| } while(*ip++ == ','); |
| } |
| break; |
| case 't': |
| case 'W': |
| if(!select_all) { |
| size_t len = strlen(optarg); |
| unsigned char *p = realloc(selectors, (selectors_end - selectors) + 1 + 1 + len); |
| if(!p) { |
| fputs("Out of memory\n", stderr); |
| return 1; |
| } |
| selectors_end = p + (selectors_end - selectors); |
| selectors = p; |
| switch(c) { |
| case 't': |
| *selectors_end++ = MCIPM_GET_ENTITIES_SELECTOR_ENTITY_TYPE; |
| break; |
| case 'W': |
| *selectors_end++ = MCIPM_GET_ENTITIES_SELECTOR_WORLD; |
| break; |
| } |
| *selectors_end++ = len; |
| memcpy(selectors_end, optarg, len); |
| selectors_end += len; |
| selector_count++; |
| } |
| break; |
| case 'o': |
| if(entitystat_set_output_format(optarg) < 0) { |
| fprintf(stderr, "%s: Invalid argument for option '-o'\n", argv[0]); |
| return -1; |
| } |
| break; |
| case 'w': |
| wide = 1; |
| break; |
| case '?': |
| print_usage(argv[0]); |
| return -1; |
| } |
| } |
| if(!selectors && !select_all) { |
| fprintf(stderr, "%s: you must specify a selector\n", argv[0]); |
| print_usage(argv[0]); |
| return -1; |
| } |
| if(select_all) { |
| free(selectors); |
| selectors = malloc(1); |
| *selectors = MCIPM_GET_ENTITIES_SELECTOR_ALL; |
| selectors_end = selectors + 1; |
| selector_count = 1; |
| } |
| if(send_packet_get_entities(fd, selectors, selectors_end - selectors, selector_count) < 0) { |
| fputs("send_packet_get_entities failed\n", stderr); |
| return 1; |
| } |
| struct mcipm_packet *packet; |
| int e = receive_packet(fd, &packet, 1); |
| if(e) { |
| fprintf(stderr, "receive_packet error %d\n", e); |
| return 1; |
| } |
| if(packet->type == MCIPM_REPLY_ERROR) { |
| print_server_error(packet); |
| return 1; |
| } |
| if(packet->type != MCIPM_REPLY_ENTITIES) { |
| fprintf(stderr, "expecting reply packet type MCIPM_REPLY_ENTITIES (%hhu), got %hhu\n", |
| MCIPM_REPLY_ENTITIES, packet->type); |
| return 1; |
| } |
| uint32_t entity_count = *(uint32_t *)packet->data; |
| //fprintf(stderr, "entity_count = %u\n", (unsigned int)entity_count); |
| struct entity **entities = malloc(sizeof(struct entity *) * entity_count); |
| if(!entities) { |
| fputs("Out of memory\n", stderr); |
| return 1; |
| } |
| uint32_t i = 0; |
| uint32_t offset = 4; |
| while(i < entity_count) { |
| if(offset >= packet->length - 1) { |
| fprintf(stderr, "entity entry offset (%u) out of packet (length %u)\n", |
| (unsigned int)offset, (unsigned int)packet->length); |
| return 1; |
| } |
| struct entity *entity = (struct entity *)(packet->data + offset); |
| //fprintf(stderr, "entity->entry_length = %u\n", (unsigned int)entity->entry_length); |
| if(entity->entry_length < sizeof(struct entity)) { |
| fprintf(stderr, "entity entry dosen't have enough length (%u<%zu), server/client version mismatch?\n", |
| (unsigned int)entity->entry_length, sizeof(struct entity)); |
| return 1; |
| } |
| if(entity->world_name_offset < 0) { |
| fputs("world_name_offset is not available, however it is required\n", stderr); |
| return 1; |
| } |
| if(entity->entity_type_name_offset < 0) { |
| fputs("entity_type_name_offset is not available, however it is required\n", stderr); |
| return 1; |
| } |
| if(entity->display_name_offset < 0) { |
| fputs("display_name_offset is not available, however it is required\n", stderr); |
| return 1; |
| } |
| if(entity->world_name_offset + sizeof(struct entity) > entity->entry_length) { |
| fprintf(stderr, "world_name_offset %d points out of current entity entry\n", |
| (int)entity->world_name_offset); |
| return 1; |
| } |
| if(entity->entity_type_name_offset + sizeof(struct entity) > entity->entry_length) { |
| fprintf(stderr, "entity_type_name_offset %d points out of current entity entry\n", |
| (int)entity->world_name_offset); |
| return 1; |
| } |
| if(entity->display_name_offset + sizeof(struct entity) > entity->entry_length) { |
| fprintf(stderr, "display_name_offset %d points out of current entity entry\n", |
| (int)entity->world_name_offset); |
| return 1; |
| } |
| if(entity->is_item) { |
| if(entity->item.owner_offset >= 0 && entity->item.owner_offset + sizeof(struct entity) > entity->entry_length) { |
| fprintf(stderr, "item.owner_offset %d points out of current entity entry\n", |
| (int)entity->item.owner_offset); |
| return 1; |
| } |
| if(entity->item.thrower_offset >= 0 && entity->item.thrower_offset + sizeof(struct entity) > entity->entry_length) { |
| fprintf(stderr, "item.thrower_offset %d points out of current entity entry\n", |
| (int)entity->item.thrower_offset); |
| return 1; |
| } |
| } |
| offset += entity->entry_length; |
| entities[i++] = entity; |
| } |
| entitystat_print(entities, entity_count, wide); |
| return 0; |
| } |
| |
| static int kill_command(int argc, char **argv) { |
| if(argc < 3) { |
| print_usage(argv[0]); |
| return -1; |
| } |
| int r = 0; |
| char **p = argv + 2; |
| do { |
| char *endp; |
| long int id = strtol(*p, &endp, 0); |
| if(*endp) { |
| fprintf(stderr, "Cannot parse '%s' as integer\n", *p); |
| r = 1; |
| } else { |
| if(send_packet_kill_entity(fd, argv[1], id) < 0) { |
| fputs("send_packet_kill_entity failed\n", stderr); |
| return 1; |
| } |
| struct mcipm_packet *packet; |
| int e = receive_packet(fd, &packet, 1); |
| if(e) { |
| fprintf(stderr, "receive_packet error %d\n", e); |
| return 1; |
| } |
| switch(packet->type) { |
| case MCIPM_REPLY_ERROR: |
| print_server_error(packet); |
| r = 1; |
| break; |
| case MCIPM_REPLY_SUCCESS: |
| break; |
| default: |
| fprintf(stderr, "Unexpected packet type %hhu\n", packet->type); |
| r = 1; |
| break; |
| } |
| } |
| } while(*++p); |
| return r; |
| } |
| |
| static int version_command(int argc, char **argv) { |
| if(send_packet_get_version(fd) < 0) { |
| fputs("send_packet_get_version failed\n", stderr); |
| return 1; |
| } |
| struct mcipm_packet *packet; |
| int e = receive_packet(fd, &packet, 1); |
| if(e) { |
| fprintf(stderr, "receive_packet error %d\n", e); |
| return 1; |
| } |
| if(packet->type == MCIPM_REPLY_ERROR) { |
| print_server_error(packet); |
| return 1; |
| } |
| if(packet->type != MCIPM_REPLY_VERSION) { |
| fprintf(stderr, "expecting reply packet type MCIPM_REPLY_VERSION (%hhu), got %hhu\n", |
| MCIPM_REPLY_VERSION, packet->type); |
| return 1; |
| } |
| fwrite(packet->data, packet->length - 1, 1, stdout); |
| putchar('\n'); |
| return 0; |
| } |
| |
| static struct subcommand { |
| const char *name; |
| const char *usage; |
| int (*func)(int, char **); |
| } commands[] = { |
| #define SUBCOMMAND(N,U) { #N, U, N##_command } |
| SUBCOMMAND(entitystat, "[-o <spec>] [--sort <spec>] { -A | -W <world-type> | -i <entity-id> | -t <entity-type> }"), |
| SUBCOMMAND(kill, "<world> <entity-id> [<entity-id> ...]"), |
| SUBCOMMAND(version, ""), |
| #undef SUBCOMMAND |
| { NULL, NULL, NULL } |
| }; |
| |
| static void print_commands() { |
| struct subcommand *c = commands; |
| fputs(_("Following subcommands are available:\n"), stderr); |
| while(c->name) { |
| fprintf(stderr, " %s %s\n", c->name, c->usage); |
| c++; |
| } |
| } |
| |
| static void print_usage(const char *name) { |
| struct subcommand *c = commands; |
| while(c->name) { |
| if(strcmp(c->name, name) == 0) { |
| fprintf(stderr, _("Usage: %s %s\n"), name, c->usage); |
| return; |
| } |
| c++; |
| } |
| fprintf(stderr, _("Error: cannot find usage for command '%s'"), name); |
| } |
| |
| int main(int argc, char **argv) { |
| static const struct option long_options[] = { |
| [0] = { "socket-path", 1, NULL, 'S' }, |
| [1] = { "version", 0, NULL, 0 }, |
| { NULL, 0, NULL, 0 } |
| }; |
| struct sockaddr_un sockaddr = { .sun_family = AF_UNIX, .sun_path = "mcipm" }; |
| while(1) { |
| int c = getopt_long(argc, argv, "+S:", long_options, NULL); |
| if(c == -1) break; |
| switch(c) { |
| case 0: |
| puts("MCIPM client " MCIPM_CLIENT_VERSION); |
| puts(MCIPM_CLIENT_COPYRIGHT_LINE); |
| puts(MCIPM_CLIENT_LICENSE_INFORMATION); |
| return 0; |
| case 'S': |
| if(strlen(optarg) + 1 > sizeof sockaddr.sun_path) { |
| fprintf(stderr, "%s: Specified socket path too long\n", argv[0]); |
| return -1; |
| } |
| strcpy(sockaddr.sun_path, optarg); |
| break; |
| case '?': |
| fprintf(stderr, "Usage: %s [--socket-path <path>] <subcommand> [<options>]\n", argv[0]); |
| return -1; |
| } |
| } |
| |
| if(argc <= optind) { |
| print_commands(); |
| return -1; |
| } |
| |
| fd = socket(AF_UNIX, SOCK_STREAM, 0); |
| if(fd == -1) { |
| perror("socket: AF_UNIX"); |
| return 1; |
| } |
| while(connect(fd, (struct sockaddr *)&sockaddr, sizeof sockaddr) < 0) { |
| if(errno == EINTR) continue; |
| perror("connect"); |
| return 1; |
| } |
| |
| struct subcommand *c = commands; |
| while(c->name) { |
| if(strcmp(argv[optind], c->name) == 0) return c->func(argc - optind, argv + optind); |
| c++; |
| } |
| fprintf(stderr, _("Unknown command '%s'\n"), argv[optind]); |
| print_commands(); |
| return -1; |
| } |