diff options
Diffstat (limited to 'minecctl')
| -rw-r--r-- | minecctl/meson.build | 1 | ||||
| -rw-r--r-- | minecctl/minecctl.c | 322 | 
2 files changed, 304 insertions, 19 deletions
diff --git a/minecctl/meson.build b/minecctl/meson.build index 3490338..ac8210d 100644 --- a/minecctl/meson.build +++ b/minecctl/meson.build @@ -9,6 +9,7 @@ minecctl_deps = [  executable(  	'minecctl',  	minecctl_sources, +	link_args: [ '-lanl' ],  	dependencies: minecctl_deps,  ) diff --git a/minecctl/minecctl.c b/minecctl/minecctl.c index e29dcef..d601980 100644 --- a/minecctl/minecctl.c +++ b/minecctl/minecctl.c @@ -1,39 +1,323 @@ +#define _GNU_SOURCE  #include <stdio.h>  #include <stdlib.h>  #include <stdint.h>  #include <inttypes.h> +#include <stdarg.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h>  #include "rcon-protocol.h" +#include "utils.h" +#include "config-parser.h" +#include "server-config-options.h" -int -main(int argc, char **argv) +void +__debug(enum debug_lvl lvl, const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	vfprintf(stderr, fmt, ap); +	va_end(ap); +} + +__attribute__((noreturn)) void +__die(const char *fmt, ...) +{ +	va_list ap; + +	va_start(ap, fmt); +	vfprintf(stderr, fmt, ap); +	va_end(ap); + +	exit(EXIT_FAILURE); +}; + +void * +__zmalloc(const char *fn, int line, size_t size) +{ +	void *ptr; + +	assert_die(!empty_str(fn) && line > 0 && size > 0, "invalid arguments"); + +	ptr = calloc(1, size); +	if (!ptr) +		die("malloc: %m"); +	return ptr; +} + +char * +__xstrdup(const char *fn, int line, const char *s) +{ +	char *ptr; + +	assert_die(!empty_str(fn) && line > 0 && !empty_str(s), "invalid arguments"); + +	ptr = strdup(s); +	if (!ptr) +		die("strdup: %m"); +	return ptr; +} + +char * +__xstrndup(const char *fn, int line, const char *s, size_t n) +{ +	char *ptr; + +	assert_die(!empty_str(fn) && line > 0 && !empty_str(s) && n > 0, "invalid arguments"); + +	ptr = strndup(s, n); +	if (ptr) +		die("strdup: %m"); +	return ptr; +} + +void +__xfree(const char *fn, int line, void *ptr) +{ +	assert_die(!empty_str(fn) && line > 0, "invalid arguments"); + +	free(ptr); +} + +static void +send_packet(int sfd, const char *buf, size_t len) +{ +	size_t off = 0; +	ssize_t r; + +	while (true) { +		r = write(sfd, buf + off, len - off); +		if (r < 0) { +			if (errno == EINTR) +				continue; +			else +				die("Failed to write packet: %m"); +		} + +		off += r; +		if (off == len) +			break; +	} +} + +static void +read_packet(int sfd, char *buf, size_t len, int32_t *id, int32_t *type, const char **msg) +{ +	size_t off = 0; +	size_t r; +	const char *error; + +	while (true) { +		r = read(sfd, buf + off, len - off); +		if (r < 0) { +			if (errno == EINTR) +				continue; +			else +				die("Failed to read reply: %m"); +		} + +		if (r == 0) +			die("Failed, connection closed"); + +		off += r; +		if (rcon_protocol_packet_complete(buf, off)) +			break; + +		if (off >= len) +			die("Reply too large %zu and %zu", off, len); +	} + +	if (!rcon_protocol_read_packet(buf, off, id, type, msg, &error)) +		die("Failed to parse response: %s", error); +} + +static void +send_msg(int sfd, char *buf, size_t len, enum rcon_packet_type type, +	 const char *msg, enum rcon_packet_type *rtype, const char **reply) +{ +	static unsigned rcon_packet_id = 1; +	size_t plen; +	int32_t id; + +	if (!rcon_protocol_create_packet(buf, len, &plen, +					 rcon_packet_id, type, msg)) +		die("Failed to create rcon packet"); + +	send_packet(sfd, buf, plen); +	read_packet(sfd, buf, len, &id, rtype, reply); + +	if (id != rcon_packet_id) +		die("Invalid reply id"); + +	rcon_packet_id++; +} + +static void +send_login(int sfd, const char *password) +{ +	char buf[4096]; +	int32_t rtype; +	const char *reply; + +	send_msg(sfd, buf, sizeof(buf), RCON_PACKET_LOGIN, password, &rtype, &reply); + +	if (rtype == RCON_PACKET_LOGIN_OK) +		info("Login ok"); +	else if (rtype == RCON_PACKET_LOGIN_FAIL) +		die("Login failure, invalid password?"); +	else +		die("Invalid return code: %" PRIi32, rtype); +} + +static void +send_cmd(int sfd, const char *cmd)  {  	char buf[4096]; -	size_t len; -	int32_t id, type; -	const char *msg, *error; +	int32_t rtype; +	const char *reply; + +	send_msg(sfd, buf, sizeof(buf), RCON_PACKET_COMMAND, "stop", &rtype, &reply); + +	if (rtype == RCON_PACKET_RESPONSE) +		info("Command (%s) sent, reply: %s", cmd, reply); +	else +		die("Invalid return code: %" PRIi32, rtype); +} -	fprintf(stderr, "Started\n"); +static int +connect_any(struct list_head *addrs) +{ +	struct saddr *saddr; +	bool connected = false; +	int sfd; + +	list_for_each_entry(saddr, addrs, list) { +		sfd = socket(saddr->storage.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0); +		if (sfd < 0) +			die("socket: %m"); + +		socket_set_low_latency(sfd, true, true, true); -	if (!rcon_protocol_create_packet(buf, sizeof(buf), &len, -					 1, RCON_PACKET_LOGIN, -					 "test")) { -		fprintf(stderr, "Failed to create packet\n"); -		exit(EXIT_FAILURE); +		if (connect(sfd, (struct sockaddr *)&saddr->storage, saddr->addrlen) < 0) { +			close(sfd); +			continue; +		} + +		connected = true; +		break;  	} -	if (!rcon_protocol_packet_complete(buf, len)) { -		fprintf(stderr, "Packet not complete\n"); -		exit(EXIT_FAILURE); +	if (!connected) +		die("Failed to connect to remote host"); + +	return sfd; +} + +static void +parse_config(char *buf, const char *filename, +	     const char **password, struct list_head *addrs) +{ +	*password = NULL; +	list_init(addrs); + +	/* FIXME: filename argument is superfluous */ +	if (!config_parse_header(filename, SERVER_CFG_HEADER, &buf)) +		die("Unable to parse %s: invalid header", filename); + +	/* FIXME: this will cause superflous DNS lookups of other cfg entries */ +	while (true) { +		int key; +		const char *keyname; +		struct cfg_value value; + +		if (!config_parse_line(filename, &buf, scfg_key_map, +				       &key, &keyname, &value)) +			break; + +		switch (key) { +		case SCFG_KEY_RCON: +			list_replace(&value.saddrs, addrs); +			break; +		case SCFG_KEY_RCON_PASSWORD: +			*password = value.str; +			break; +		default: +			continue; +		} + +		if (*password && !list_empty(addrs)) +			break;  	} -	if (!rcon_protocol_read_packet(buf, len, &id, &type, &msg, &error)) { -		fprintf(stderr, "Packet parsing failed: %s\n", error); -		exit(EXIT_FAILURE); +	if (!*password) +		die("rcon password not found in %s", filename); + +	if (list_empty(addrs)) +		die("rcon address not found in %s", filename); +} + +static void +read_file(const char *filename, char *buf, size_t len) +{ +	int fd; +	size_t off = 0; +	ssize_t r; + +	fd = open(filename, O_RDONLY | O_CLOEXEC); +	if (fd < 0) +		die("Failed to open %s: %m", filename); + +	while (true) { +		r = read(fd, buf + off, len - off - 1); +		if (r < 0) +			die("Failed to read %s: %m", filename); +		else if (r == 0) +			break; + +		off += r; +		if (off == len) +			die("Failed to read %s: file too large", filename);  	} -	fprintf(stderr, "Packet - id: %" PRIi32 ", type: %" PRIi32 ", msg: %s\n", -		id, type, msg); +	buf[off] = '\0'; +	close(fd); +} + +int +main(int argc, char **argv) +{ +	char buf[4096]; +	const char *password; +	const char *filename; +	struct list_head addrs; +	struct saddr *saddr; +	int fd; + +	debug_mask = DBG_ERROR | DBG_INFO; + +	if (argc != 2) +		die("Usage: minecctl CFGFILE"); + +	filename = argv[1]; + +	read_file(filename, buf, sizeof(buf)); + +	parse_config(buf, filename, &password, &addrs); + +	info("Password: %s", password); +	list_for_each_entry(saddr, &addrs, list) +		info("Address: %s", saddr->addrstr); + +	fd = connect_any(&addrs); + +	send_login(fd, password); + +	send_cmd(fd, "stop");  	exit(EXIT_SUCCESS);  }  | 
