blob: dc782545a83010ee3086dcbc28f0959342b31d54 [file] [log] [blame] [raw]
/* 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;
}