255 lines
6.8 KiB
C
255 lines
6.8 KiB
C
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <arpa/inet.h>
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
#include <sys/time.h>
|
|
|
|
const char *LOCALHOST = "127.0.0.1";
|
|
|
|
#define SERVERDATA_AUTH 3
|
|
#define SERVERDATA_AUTH_RESPONSE 2
|
|
#define SERVERDATA_EXECCOMMAND 2
|
|
#define SERVERDATA_RESPONSE_VALUE 0
|
|
|
|
typedef struct {
|
|
int32_t size;
|
|
int32_t id;
|
|
int32_t type;
|
|
char body[4096];
|
|
} rcon_packet_t;
|
|
|
|
int read_env_int(const char *var) {
|
|
const char* port_str = getenv(var);
|
|
if (port_str == NULL) {
|
|
fprintf(stderr, "error: %s environment variable not set.\n", var);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
char* endptr;
|
|
long val = strtol(port_str, &endptr, 10);
|
|
if (endptr == port_str || *endptr != '\0') {
|
|
fprintf(stderr, "error: invalid number format for %s.\n", var);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
return (int)val;
|
|
}
|
|
|
|
int rcon_connect(int port) {
|
|
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
|
|
if (sockfd < 0) {
|
|
perror("socket creation failed");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
struct timeval tv;
|
|
tv.tv_sec = 2;
|
|
tv.tv_usec = 0;
|
|
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
|
|
|
|
struct sockaddr_in server_addr;
|
|
memset(&server_addr, 0, sizeof(server_addr));
|
|
server_addr.sin_family = AF_INET;
|
|
server_addr.sin_port = htons(port);
|
|
|
|
if (inet_pton(AF_INET, LOCALHOST, &server_addr.sin_addr) <= 0) {
|
|
perror("invalid address");
|
|
close(sockfd);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
|
|
perror("connection failed");
|
|
close(sockfd);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
printf("connected to rcon server\n");
|
|
return sockfd;
|
|
}
|
|
|
|
void send_packet(int sockfd, int32_t id, int32_t type, const char *body) {
|
|
int body_len = strlen(body);
|
|
int32_t size = 10 + body_len;
|
|
|
|
size_t packet_size = sizeof(int32_t) + size;
|
|
unsigned char *packet = malloc(packet_size);
|
|
if (!packet) {
|
|
perror("malloc failed");
|
|
return;
|
|
}
|
|
|
|
packet[0] = size & 0xFF;
|
|
packet[1] = (size >> 8) & 0xFF;
|
|
packet[2] = (size >> 16) & 0xFF;
|
|
packet[3] = (size >> 24) & 0xFF;
|
|
|
|
packet[4] = id & 0xFF;
|
|
packet[5] = (id >> 8) & 0xFF;
|
|
packet[6] = (id >> 16) & 0xFF;
|
|
packet[7] = (id >> 24) & 0xFF;
|
|
|
|
packet[8] = type & 0xFF;
|
|
packet[9] = (type >> 8) & 0xFF;
|
|
packet[10] = (type >> 16) & 0xFF;
|
|
packet[11] = (type >> 24) & 0xFF;
|
|
|
|
memcpy(packet + 12, body, body_len);
|
|
packet[12 + body_len] = 0;
|
|
packet[12 + body_len + 1] = 0;
|
|
|
|
ssize_t sent = send(sockfd, packet, packet_size, 0);
|
|
if (sent < 0) {
|
|
perror("send failed");
|
|
}
|
|
|
|
free(packet);
|
|
}
|
|
|
|
int recv_packet(int sockfd, rcon_packet_t *packet) {
|
|
ssize_t bytes;
|
|
|
|
bytes = recv(sockfd, &packet->size, sizeof(int32_t), MSG_WAITALL);
|
|
if (bytes <= 0) {
|
|
if (bytes == 0) {
|
|
fprintf(stderr, "connection closed by server\n");
|
|
} else if (errno == EAGAIN || errno == EWOULDBLOCK) {
|
|
return -2; // Timeout
|
|
} else {
|
|
perror("recv size failed");
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
if (packet->size < 10 || packet->size > 4110) {
|
|
fprintf(stderr, "invalid packet size: %d\n", packet->size);
|
|
return -1;
|
|
}
|
|
|
|
bytes = recv(sockfd, &packet->id, sizeof(int32_t), MSG_WAITALL);
|
|
if (bytes <= 0) {
|
|
perror("recv id failed");
|
|
return -1;
|
|
}
|
|
|
|
bytes = recv(sockfd, &packet->type, sizeof(int32_t), MSG_WAITALL);
|
|
if (bytes <= 0) {
|
|
perror("recv type failed");
|
|
return -1;
|
|
}
|
|
|
|
int body_size = packet->size - 10;
|
|
if (body_size > 0 && body_size < (int)sizeof(packet->body)) {
|
|
bytes = recv(sockfd, packet->body, body_size + 2, MSG_WAITALL);
|
|
if (bytes <= 0) {
|
|
perror("recv body failed");
|
|
return -1;
|
|
}
|
|
packet->body[body_size] = '\0';
|
|
} else {
|
|
packet->body[0] = '\0';
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int rcon_authenticate(int sockfd, const char *password) {
|
|
printf("authenticating with password: '%s'\n", password);
|
|
|
|
send_packet(sockfd, 1, SERVERDATA_AUTH, password);
|
|
|
|
printf("waiting for auth response...\n");
|
|
|
|
rcon_packet_t response;
|
|
if (recv_packet(sockfd, &response) < 0) {
|
|
fprintf(stderr, "failed to receive auth response\n");
|
|
return -1;
|
|
}
|
|
|
|
printf("received auth packet: id=%d, type=%d\n", response.id, response.type);
|
|
|
|
// Check if authentication failed (id == -1)
|
|
if (response.id == -1) {
|
|
fprintf(stderr, "authentication failed - invalid password\n");
|
|
return -1;
|
|
}
|
|
|
|
// Try to read second packet (may timeout, which is OK)
|
|
rcon_packet_t response2;
|
|
int result = recv_packet(sockfd, &response2);
|
|
if (result == 0) {
|
|
printf("received second auth packet: id=%d, type=%d\n", response2.id, response2.type);
|
|
} else if (result == -2) {
|
|
printf("no second packet (timeout - this is normal)\n");
|
|
}
|
|
|
|
printf("authenticated successfully\n");
|
|
return 0;
|
|
}
|
|
|
|
char* rcon_command_multipacket(int sockfd, const char *command) {
|
|
static char result[65536];
|
|
result[0] = '\0';
|
|
|
|
int cmd_id = 100;
|
|
int dummy_id = 101;
|
|
|
|
send_packet(sockfd, cmd_id, SERVERDATA_EXECCOMMAND, command);
|
|
send_packet(sockfd, dummy_id, SERVERDATA_EXECCOMMAND, "");
|
|
|
|
while (1) {
|
|
rcon_packet_t response;
|
|
if (recv_packet(sockfd, &response) < 0) {
|
|
fprintf(stderr, "failed to receive command response\n");
|
|
return NULL;
|
|
}
|
|
if (response.id == dummy_id) {
|
|
break;
|
|
}
|
|
if (response.type == SERVERDATA_RESPONSE_VALUE && response.id == cmd_id) {
|
|
strncat(result, response.body, sizeof(result) - strlen(result) - 1);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
const int PORT_NUM = read_env_int("RCON_PORT_NUMBER");
|
|
const char *password = getenv("RCON_PASSWORD");
|
|
|
|
if (password == NULL) {
|
|
fprintf(stderr, "error: RCON_PASSWORD environment variable not set.\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
printf("=== RCON Configuration ===\n");
|
|
printf("port: %d\n", PORT_NUM);
|
|
printf("password: '%s'\n", password);
|
|
printf("==========================\n\n");
|
|
|
|
int sockfd = rcon_connect(PORT_NUM);
|
|
|
|
if (rcon_authenticate(sockfd, password) < 0) {
|
|
close(sockfd);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
const char *get_player_positions_cmd = "execute as @a run data get entity @s Pos";
|
|
printf("\nexecuting command: %s\n", get_player_positions_cmd);
|
|
char *response = rcon_command_multipacket(sockfd, get_player_positions_cmd);
|
|
|
|
if (response) {
|
|
printf("\n=== player positions ===\n");
|
|
printf("%s\n", response);
|
|
} else {
|
|
fprintf(stderr, "failed to get player positions\n");
|
|
}
|
|
|
|
close(sockfd);
|
|
return 0;
|
|
}
|