/* * Sauerbraten coopedit helper bot * * Copyright (C) 2007 Michael Buesch * * Derived from * Sauerbraten game engine source code. * Copyright (C) 2001-2006 Wouter van Oortmerssen. * * This software is provided 'as-is', without any express or implied * warranty. In no event will the authors be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, subject to the following restrictions: * * 1. The origin of this software must not be misrepresented; you must not * claim that you wrote the original software. If you use this software * in a product, an acknowledgment in the product documentation would be * appreciated but is not required. * 2. Altered source versions must be plainly marked as such, and must not be * misrepresented as being the original software. * 3. This notice may not be removed or altered from any source distribution. */ #include "main.h" #include "auth.h" #include #include #include #include #include #include #include #include #include #include #define HEARTBEAT 1 /* secs */ #define MAPFILE_POSTFIX ".ogz" static struct cmdline_args { const char *servername; const char *masterpw; const char *nick; const char *team; const char *authmapfile; const char *maps_dir; int autoauth; } args = { .nick = "sauerbot", .team = "bot", .authmapfile = "./authmap", .maps_dir = "./maps", }; static FILE *rng_fd; static volatile sig_atomic_t heartbeat; static void strip_whitespace(char **str, size_t *len) { /* strip whitespace at the front */ while (*len && isspace((*str)[0])) { (*str)++; (*len)--; } if (!(*len)) return; /* strip whitespace at the end */ while (*len && isspace((*str)[*len - 1])) { (*len)--; (*str)[*len] = '\0'; } } static int isalnum_string(const char *str) { if (!str) return 0; while (*str) { if (!isalnum(*str)) return 0; str++; } return 1; } static void close_maps_directory(struct sbot *sb) { closedir(sb->maps_dir); } static int open_maps_directory(struct sbot *sb) { sb->maps_dirname = args.maps_dir; sb->maps_dir = opendir(sb->maps_dirname); if (!sb->maps_dir) { fprintf(stderr, "Could not open Maps directory \"%s\": %s\n", sb->maps_dirname, strerror(errno)); return -1; } return 0; } int random_int(void) { int ret; if (rng_fd) { if (fread(&ret, sizeof(int), 1, rng_fd) == 1) return ret; } ret = rand(); return ret; } static void rng_init(void) { rng_fd = fopen("/dev/urandom", "r"); if (!rng_fd) rng_fd = fopen("/dev/random", "r"); srand(time(0)); } struct bytestream { unsigned char *buf; size_t size; size_t pos; }; static signed char bytestream_get(struct bytestream *s, int *err) { if (s->pos >= s->size) { *err = 1; return 0; } return (signed char)(s->buf[s->pos++]); } static int bytestream_put(struct bytestream *s, unsigned char c) { if (s->pos >= s->size) return -1; s->buf[s->pos++] = c; return 0; } static void putint(struct bytestream *s, int n) { //TODO maybe check error from bytestream_put if (n < 128 && n > -127) { bytestream_put(s, n); } else if (n < 0x8000 && n >= -0x8000) { bytestream_put(s, 0x80); bytestream_put(s, n); bytestream_put(s, (n >> 8)); } else { bytestream_put(s, 0x81); bytestream_put(s, n); bytestream_put(s, (n >> 8)); bytestream_put(s, (n >> 16)); bytestream_put(s, (n >> 24)); } } static int getint(struct bytestream *s) { int c, n; int err; //TODO maybe check error from bytestream_get c = bytestream_get(s, &err); if (c == -128) { n = (bytestream_get(s, &err) & 0xFF); n |= ((bytestream_get(s, &err) & 0xFF) << 8); return n; } else if (c == -127) { n = (bytestream_get(s, &err) & 0xFF); n |= ((bytestream_get(s, &err) & 0xFF) << 8); n |= ((bytestream_get(s, &err) & 0xFF) << 16); n |= ((bytestream_get(s, &err) & 0xFF) << 24); return n; } return c; } #if 0 static void putuint(struct bytestream *s, int n) { //TODO maybe check error from bytestream_put if (n < 0 || n >= (1 << 21)) { bytestream_put(s, (0x80 | (n & 0x7F))); bytestream_put(s, (0x80 | ((n >> 7) & 0x7F))); bytestream_put(s, (0x80 | ((n >> 14) & 0x7F))); bytestream_put(s, (n >> 21)); } else if (n < (1<<7)) { bytestream_put(s, n); } else if (n < (1<<14)) { bytestream_put(s, (0x80 | (n & 0x7F))); bytestream_put(s, (n >> 7)); } else { bytestream_put(s, (0x80 | (n & 0x7F))); bytestream_put(s, (0x80 | ((n >> 7) & 0x7F))); bytestream_put(s, (n >> 14)); } } #endif static int getuint(struct bytestream *s) { int n; int err; //TODO maybe check error from bytestream_get n = (bytestream_get(s, &err) & 0xFF); if (n & 0x80) { n += ((bytestream_get(s, &err) & 0xFF) << 7) - 0x80; if (n & (1 << 14)) n += ((bytestream_get(s, &err) & 0xFF) << 14) - (1 << 14); if (n & (1 << 21)) n += ((bytestream_get(s, &err) & 0xFF) << 21) - (1 << 21); if (n & (1 << 28)) n |= 0xF0000000; } return n; } static void putstring(struct bytestream *s, const char *str) { while (*str) putint(s, *(str++)); putint(s, 0); } static void getstring(char *text, struct bytestream *s, size_t len) { size_t pos = 0; while (pos < len - 1) { if (s->size == s->pos) { /* stream-end */ break; } text[pos] = (char)getint(s); if (text[pos] == '\0') return; pos++; } if (s->size != s->pos) { /* Uh, our buffer was not big enough. Just eat * the rest of the string. */ while (getint(s)) ; } /* Always ensure proper termination */ text[pos] = '\0'; } static void remove_client(struct sclient *c) { list_del(&c->list); free(c); } static struct sclient * find_client(struct sbot *sb, int clientnum) { struct sclient *c; list_for_each_entry(c, &sb->clients, list) { if (c->clientnum == clientnum) return c; } return NULL; } static struct sclient * find_client_by_nick(struct sbot *sb, const char *nick) { struct sclient *c; list_for_each_entry(c, &sb->clients, list) { if (strcmp(c->nick, nick) == 0) return c; } return NULL; } static int del_client(struct sbot *sb, int clientnum) { struct sclient *c; c = find_client(sb, clientnum); if (c) { remove_client(c); return 0; } return -1; } static int add_client(struct sbot *sb, int clientnum, const char *nick, const char *team) { struct sclient *c; c = malloc(sizeof(struct sclient)); if (!c) { fprintf(stderr, "Could not allocate client memory\n"); return -1; } memset(c, 0, sizeof(*c)); INIT_LIST_HEAD(&c->list); c->clientnum = clientnum; strncpy(c->nick, nick, sizeof(c->nick) - 1); c->nick[sizeof(c->nick) - 1] = '\0'; strncpy(c->team, team, sizeof(c->team) - 1); c->team[sizeof(c->team) - 1] = '\0'; list_add(&c->list, &sb->clients); return 0; } static int send_stream(struct sbot_connection *conn, struct bytestream *s, int channel, int reliable) { ENetPacket *p; enet_uint32 flags = 0; int err; if (reliable) flags = ENET_PACKET_FLAG_RELIABLE; p = enet_packet_create(s->buf, s->pos, flags); if (!p) { fprintf(stderr, "Could not allocate send-packet\n"); return -1; } err = enet_peer_send(conn->peer, channel, p); if (err < 0) { fprintf(stderr, "enet_peer_send() failed with %d\n", err); return err; } enet_host_flush(conn->client); return 0; } static void disconnect(struct sbot *sb) { enet_peer_disconnect(sb->conn.peer, 0); enet_host_flush(sb->conn.client); enet_host_destroy(sb->conn.client); } static int connect_to_server(struct sbot *sb) { ENetEvent event; ENetAddress addr; int err; memset(&sb->conn, 0, sizeof(sb->conn)); addr.port = SBOT_SERVER_PORT; err = enet_address_set_host(&addr, args.servername); printf("Connecting to %s:%d ...\n", args.servername, addr.port); if (err < 0) { fprintf(stderr, "Could not resolve servername %s\n", args.servername); return err; } sb->conn.client = enet_host_create(NULL, 2, SBOT_RATE, SBOT_RATE); if (!sb->conn.client) { fprintf(stderr, "Could not connect to server %s (1)\n", args.servername); return -1; } sb->conn.peer = enet_host_connect(sb->conn.client, &addr, SBOT_NUM_CHANNELS); if (!sb->conn.peer) { fprintf(stderr, "Could not connect to server %s (2)\n", args.servername); return -1; } if ((enet_host_service(sb->conn.client, &event, 5000) > 0) && (event.type == ENET_EVENT_TYPE_CONNECT)) { printf("connected.\n"); } else { fprintf(stderr, "Connection to %s:%d failed\n", args.servername, addr.port); return -1; } enet_host_flush(sb->conn.client); return 0; } static int send_hello(struct sbot *sb) { struct bytestream s; unsigned char buf[512]; s.buf = buf; s.size = sizeof(buf); s.pos = 0; putint(&s, SBOT_MSG_INITC2S); putstring(&s, sb->nick); putstring(&s, sb->team); putint(&s, 0); /* lifesequence 0 */ return send_stream(&sb->conn, &s, 1, 1); } static int send_text(struct sbot *sb, const char *fmt, ...) { /* Buffers can be static, as there is no recursion or * threaded concurrency. */ static unsigned char buf[2048]; static char text[2048]; struct bytestream s; va_list va; int err; s.buf = buf; s.size = sizeof(buf); s.pos = 0; va_start(va, fmt); vsnprintf(text, sizeof(text), fmt, va); putint(&s, SBOT_MSG_TEXT); putstring(&s, text); err = send_stream(&sb->conn, &s, 1, 1); va_end(va); return err; } static int send_getmap(struct sbot *sb) { unsigned char buf[10]; struct bytestream s; s.buf = buf; s.size = sizeof(buf); s.pos = 0; putint(&s, SBOT_MSG_GETMAP); return send_stream(&sb->conn, &s, 1, 1); } static int send_setmaster(struct sbot *sb, int on, const char *passwd) { unsigned char buf[256]; struct bytestream s; s.buf = buf; s.size = sizeof(buf); s.pos = 0; if (!passwd) passwd = ""; putint(&s, SBOT_MSG_SETMASTER); putint(&s, !!on); putstring(&s, passwd); return send_stream(&sb->conn, &s, 1, 1); } static int send_mastermode(struct sbot *sb, int mm) { unsigned char buf[256]; struct bytestream s; s.buf = buf; s.size = sizeof(buf); s.pos = 0; putint(&s, SBOT_MSG_MASTERMODE); putint(&s, mm); return send_stream(&sb->conn, &s, 1, 1); } static int send_kick(struct sbot *sb, int clientnum) { unsigned char buf[10]; struct bytestream s; s.buf = buf; s.size = sizeof(buf); s.pos = 0; putint(&s, SBOT_MSG_KICK); putint(&s, clientnum); return send_stream(&sb->conn, &s, 1, 1); } static int send_togglespectator(struct sbot *sb, int clientnum, int on) { unsigned char buf[15]; struct bytestream s; s.buf = buf; s.size = sizeof(buf); s.pos = 0; putint(&s, SBOT_MSG_SPECTATOR); putint(&s, clientnum); putint(&s, !!on); return send_stream(&sb->conn, &s, 1, 1); } static void send_helptext(struct sbot *sb) { send_text(sb, "Hello, I am a Sauerbraten coopedit helper bot."); send_text(sb, "Supported commands are: !help, !auth, !getmap, !sendmap, " "!savemap, !loadmap, !listmaps, !kick"); } static void do_sendmap(struct sbot *sb) { ENetPacket *p; int err; if (!sb->map || !sb->maplen) { send_text(sb, "I don't have a map to send in my memory :("); return; } p = enet_packet_create(sb->map, sb->maplen, ENET_PACKET_FLAG_RELIABLE); if (!p) { send_text(sb, "Could not allocate packet"); return; } printf("Sending map (size %u bytes)\n", (unsigned int)(sb->maplen)); err = enet_peer_send(sb->conn.peer, 2, p); if (err < 0) { send_text(sb, "Lowlevel failure to send the map"); return; } enet_host_flush(sb->conn.client); } static void do_savemap(struct sbot *sb, struct sclient *c, char *mapname, size_t len) { FILE *fd; char path[PATH_MAX + 1]; if (!sb->map || sb->maplen == 0) { send_text(sb, "%s: There's no map in my memory to save.", c->nick); return; } strip_whitespace(&mapname, &len); if (len) { if (!isalnum_string(mapname)) goto invalid_name; memset(sb->mapname, 0, sizeof(sb->mapname)); strncpy(sb->mapname, mapname, sizeof(sb->mapname) - 1); } else { if (strlen(sb->mapname) == 0) { send_text(sb, "%s: The map doesn't have a name, yet, " "so you must pass a name to !savemap.", c->nick); return; } } snprintf(path, sizeof(path), "%s/%s%s", sb->maps_dirname, sb->mapname, MAPFILE_POSTFIX); fd = fopen(path, "w+"); if (!fd) { send_text(sb, "%s: Could not create the !savemap file.", c->nick); return; } if (fwrite(sb->map, sb->maplen, 1, fd) != 1) { send_text(sb, "%s: Could not write the !savemap file.", c->nick); return; } fclose(fd); send_text(sb, "%s: Map successfully saved to disk.", c->nick); return; invalid_name: send_text(sb, "%s: Invalid mapname for !savemap. The mapname must only " "contain alphanumeric characters.", c->nick); } static void do_loadmap(struct sbot *sb, struct sclient *c, char *mapname, size_t len) { FILE *fd; char path[PATH_MAX + 1]; size_t size = 0, pos = 0; size_t ret; strip_whitespace(&mapname, &len); if (!len) goto invalid_name; if (!isalnum_string(mapname)) goto invalid_name; snprintf(path, sizeof(path), "%s/%s%s", sb->maps_dirname, mapname, MAPFILE_POSTFIX); fd = fopen(path, "r"); if (!fd) { send_text(sb, "%s: Could not open !loadmap file.", c->nick); return; } free(sb->map); sb->map = NULL; sb->maplen = 0; while (1) { char ch; if (pos >= size) { size += 512; sb->map = realloc(sb->map, size); if (!sb->map) { send_text(sb, "%s: Could not allocate " "memory while !loadmap.", c->nick); sb->maplen = 0; goto out_close; } } ret = fread(&ch, 1, 1, fd); if (!ret) break; sb->map[pos] = ch; pos++; } if (pos) { send_text(sb, "%s: Map successfully read from disk.", c->nick); sb->maplen = pos; memset(sb->mapname, 0, sizeof(sb->mapname)); strncpy(sb->mapname, mapname, sizeof(sb->mapname) - 1); } else { send_text(sb, "%s: Could not read mapfile from disk.", c->nick); free(sb->map); sb->map = NULL; sb->maplen = 0; } out_close: fclose(fd); return; invalid_name: send_text(sb, "%s: Invalid mapname for !loadmap. The mapname must only " "contain alphanumeric characters.", c->nick); } static void do_listmaps(struct sbot *sb) { char buf[1024]; size_t pos = 0; struct dirent *de; const size_t pflen = strlen(MAPFILE_POSTFIX); size_t namelen; char *name; unsigned int cnt = 0; memset(buf, 0, sizeof(buf)); pos += snprintf(buf + pos, sizeof(buf) - pos, "My stored maps are:"); while ((de = readdir(sb->maps_dir)) != NULL) { name = de->d_name; if (!name) continue; namelen = strlen(name); if (namelen <= pflen) continue; if (strcmp(&name[namelen - pflen], MAPFILE_POSTFIX) != 0) continue; name[namelen - pflen] = '\0'; /* strip postfix */ pos += snprintf(buf + pos, sizeof(buf) - pos, " %s", name); cnt++; } if (!cnt) pos += snprintf(buf + pos, sizeof(buf) - pos, " none :("); send_text(sb, buf); } static void do_kick(struct sbot *sb, struct sclient *sender, char *victim_name, size_t len) { /* We don't strip victim_name here to allow several "nonstandard" * nicknames. So one must be careful to type exactly. */ struct sclient *c; victim_name++; /* remove space between command and nick */ len--; if (!len) { send_text(sb, "%s: No victim to kick.", sender->nick); return; } c = find_client_by_nick(sb, victim_name); if (!c) { send_text(sb, "%s: Could not find the victim.", sender->nick); return; } if (c == sender) { send_text(sb, "%s: You don't wanna kick yourself, do you?", sender->nick); return; } send_kick(sb, c->clientnum); } static void do_setmaster(struct sbot *sb, char *buf, size_t len) { int on; strip_whitespace(&buf, &len); if (!len) goto invalid_arg; if (len != 1) goto invalid_arg; if (buf[0] == '0') on = 0; else if (buf[0] == '1') on = 1; else goto invalid_arg; send_setmaster(sb, on, args.masterpw); return; invalid_arg: send_text(sb, "!setmaster requires either 1 or 0 as argument."); } static void do_mastermode(struct sbot *sb, char *buf, size_t len) { int mm; strip_whitespace(&buf, &len); if (!len) goto invalid_arg; if (sscanf(buf, "%d", &mm) != 1) goto invalid_arg; send_mastermode(sb, mm); return; invalid_arg: send_text(sb, "!mastermode requires a mode number argument."); } static void accept_auth(struct sbot *sb, struct sclient *c) { if (!c->authenticated) { c->authenticated = 1; send_text(sb, "%s: Authentication succeed", c->nick); } } static void handle_authentication(struct sbot *sb, struct sclient *c, char *buf, size_t len) { if (c->authenticated) return; strip_whitespace(&buf, &len); if (!len) goto fail; if (strcmp(buf, sb->authmap.keys[c->auth.key].key) != 0) goto fail; /* success */ send_togglespectator(sb, c->clientnum, 0); accept_auth(sb, c); return; fail: c->auth.tries++; if (c->auth.tries >= MAX_AUTH_TRIES) { send_kick(sb, c->clientnum); } else { send_text(sb, "%s: Authentication FAILED. Wrong magic (tries %u/%u)", c->nick, c->auth.tries, MAX_AUTH_TRIES); } } static void send_auth_message(struct sbot *sb, struct sclient *c) { send_text(sb, "(%u/%u) %s, please AUTHENTICATE with %02u from authmap %04X", c->auth.timeout, AUTH_TIMEOUT, c->nick, c->auth.key, sb->authmap.mapid); } static void request_authentication(struct sbot *sb, int clientnum) { struct sclient *c; if (sb->mode != GAMEMODE_COOPEDIT) return; c = find_client(sb, clientnum); if (!c) { fprintf(stderr, "Could not send authentication request. " "ClientID not found.\n"); return; } if (args.autoauth) { /* Don't toggle spectator status here */ accept_auth(sb, c); return; } c->auth.key = (((unsigned int)random_int()) % NR_AUTHKEYS); send_auth_message(sb, c); } static int is_ctl_message(const char *buf, size_t len, const char *msg) { size_t ml = strlen(msg); if (ml > len) return 0; if (memcmp(buf, msg, ml) != 0) return 0; if (ml < len) { if (buf[ml] != ' ') return 0; } return 1; } static void process_textmessage(struct sbot *sb, struct sclient *c, char *buf, size_t len) { if (len < 2) return; if (buf[0] != '!') return; if (sb->mode != GAMEMODE_COOPEDIT) return; /* This is a control message */ buf++; len--; /* strip whitespace at the end */ while (len && !isalnum(buf[len - 1])) len--; if (!len) return; buf[len] = '\0'; if (is_ctl_message(buf, len, "auth")) handle_authentication(sb, c, buf + 4, len - 4); else if (is_ctl_message(buf, len, "help")) send_helptext(sb); if (!c->authenticated) return; if (c->is_spectator) return; if (is_ctl_message(buf, len, "getmap")) send_getmap(sb); else if (is_ctl_message(buf, len, "sendmap")) do_sendmap(sb); else if (is_ctl_message(buf, len, "savemap")) do_savemap(sb, c, buf + 7, len - 7); else if (is_ctl_message(buf, len, "listmaps")) do_listmaps(sb); else if (is_ctl_message(buf, len, "loadmap")) do_loadmap(sb, c, buf + 7, len - 7); else if (is_ctl_message(buf, len, "kick")) do_kick(sb, c, buf + 4, len - 4); else if (is_ctl_message(buf, len, "setmaster")) do_setmaster(sb, buf + 9, len - 9); else if (is_ctl_message(buf, len, "mastermode")) do_mastermode(sb, buf + 10, len - 10); } static int process_rx_messages(struct sbot *sb, unsigned char *buf, size_t len, int clientnum) { struct bytestream s; int type; int err; s.buf = buf; s.size = len; s.pos = 0; #if 0 { int i; for (i = 0; i < len; i++) printf("%02X", buf[i]); printf("\n"); } #endif while (s.pos < s.size) { type = getint(&s); switch (type) { case SBOT_MSG_TEXT: { char t[1024]; struct sclient *c = NULL; if (clientnum != -1 && clientnum != sb->clientnum) c = find_client(sb, clientnum); getstring(t, &s, sizeof(t) - 1); if (c) process_textmessage(sb, c, t, strlen(t)); break; } case SBOT_MSG_INITS2C: { /* welcome message from the server */ int prot; sb->clientnum = getint(&s); /* clientnum */ prot = getint(&s); if (prot != SBOT_PROT_VER) { fprintf(stderr, "The server is using an incompatible game " "protocol (sauerbot: %d, server: %d)", SBOT_PROT_VER, prot); return -1; } if (getint(&s)) { /* hasmap */ } else { } printf("Welcome message received from server\n"); /* There's no need to be player for the bot. * So be spectator. */ send_togglespectator(sb, sb->clientnum, 1); break; } case SBOT_MSG_INITC2S: { /* Another client connected */ char nick[50]; char team[6]; if (clientnum == -1) fprintf(stderr, "INITC2S invalid clientnum\n"); getstring(nick, &s, sizeof(nick) - 1); getstring(team, &s, sizeof(team) - 1); getint(&s); /* Lifesequence */ if (!find_client(sb, clientnum)) { add_client(sb, clientnum, nick, team); request_authentication(sb, clientnum); } send_hello(sb); break; } case SBOT_MSG_TIMEUP: { getint(&s); /* dummy */ break; } case SBOT_MSG_RESUME: { getint(&s); /* clientnum */ getint(&s); /* maxhealth */ getint(&s); /* frags */ break; } case SBOT_MSG_MAPVOTE: { char map[512]; getstring(map, &s, sizeof(map) - 1); getint(&s); /* mode */ break; } case SBOT_MSG_MAPCHANGE: { char map[512]; int mode; getstring(map, &s, sizeof(map) - 1); mode = getint(&s); /* gamemode */ if (mode == 1) sb->mode = GAMEMODE_COOPEDIT; else sb->mode = GAMEMODE_UNKNOWN; printf("Mode %d on map \"%s\"\n", mode, map); send_setmaster(sb, (sb->mode == GAMEMODE_COOPEDIT), args.masterpw); break; } case SBOT_MSG_ITEMLIST: { int n; while ((n = getint(&s)) != -1) getint(&s); /* type */ break; } case SBOT_MSG_SPECTATOR: { int cl, on; struct sclient *c; cl = getint(&s); /* clientnum */ on = getint(&s); /* on/off */ c = find_client(sb, cl); if (c) c->is_spectator = !!on; break; } case SBOT_MSG_CURRENTMASTER: { getint(&s); /* master */ break; } case SBOT_MSG_CLIENT: { int cl; unsigned int len; cl = getint(&s); /* clientnum */ len = getuint(&s); /* msgs.len */ len = min(s.size - s.pos, len); err = process_rx_messages(sb, s.buf + s.pos, len, cl); if (err) return err; s.pos += len; break; } case SBOT_MSG_CLIENTPING: { getint(&s); /* ping */ break; } case SBOT_MSG_POS: { int phy; getint(&s); /* client */ getuint(&s); /* X */ getuint(&s); /* Y */ getuint(&s); /* Z */ getuint(&s); /* yaw */ getint(&s); /* pitch */ getint(&s); /* roll */ getint(&s); /* X */ getint(&s); /* Y */ getint(&s); /* Z */ phy = getint(&s); /* phy */ if (phy & 0x20) { getint(&s); getint(&s); } if (phy & 0x10) getint(&s); getint(&s); /* state */ break; } case SBOT_MSG_SOUND: { getint(&s); break; } case SBOT_MSG_CDIS: { int cl; cl = getint(&s); /* client */ err = del_client(sb, cl); if (err) { fprintf(stderr, "Client disconnected, but could not find " "its ID (%d)\n", cl); } break; } case SBOT_MSG_DIED: { getint(&s); /* actor */ getint(&s); /* damage */ getint(&s); /* superdamage */ break; } case SBOT_MSG_DAMAGE: { getint(&s); /* target */ getint(&s); /* damage */ getint(&s); /* ls */ getint(&s); /* x */ getint(&s); /* y */ getint(&s); /* z */ break; } case SBOT_MSG_SHOT: { getint(&s); /* gun */ getint(&s); /* x */ getint(&s); /* y */ getint(&s); /* z */ getint(&s); /* x */ getint(&s); /* y */ getint(&s); /* z */ break; } case SBOT_MSG_FRAGS: { getint(&s); /* frags */ break; } case SBOT_MSG_GUNSELECT: { getint(&s); /* gunselect */ break; } case SBOT_MSG_ITEMSPAWN: { getint(&s); /* item */ break; } case SBOT_MSG_ITEMPICKUP: { getint(&s); /* item */ getint(&s); break; } case SBOT_MSG_DENIED: { getint(&s); break; } case SBOT_MSG_PING: { getint(&s); break; } case SBOT_MSG_PONG: { getint(&s); break; } case SBOT_MSG_MAPRELOAD: { break; } case SBOT_MSG_ITEMACC: { getint(&s); break; } case SBOT_MSG_SERVMSG: { char msg[512]; getstring(msg, &s, sizeof(msg) - 1); printf("Servermessage: %s\n", msg); break; } case SBOT_MSG_EDITENT: { int i; for (i = 0; i < 9; i++) getint(&s); break; } case SBOT_MSG_EDITF: { int i; for (i = 0; i < 15; i++) getint(&s); break; } case SBOT_MSG_EDITT: { int i; for (i = 0; i < 15; i++) getint(&s); break; } case SBOT_MSG_EDITM: { int i; for (i = 0; i < 14; i++) getint(&s); break; } case SBOT_MSG_FLIP: { int i; for (i = 0; i < 13; i++) getint(&s); break; } case SBOT_MSG_COPY: { int i; for (i = 0; i < 13; i++) getint(&s); break; } case SBOT_MSG_PASTE: { int i; for (i = 0; i < 13; i++) getint(&s); break; } case SBOT_MSG_ROTATE: { int i; for (i = 0; i < 14; i++) getint(&s); break; } case SBOT_MSG_REPLACE: { int i; for (i = 0; i < 15; i++) getint(&s); break; } case SBOT_MSG_DELCUBE: { int i; for (i = 0; i < 13; i++) getint(&s); break; } case SBOT_MSG_NEWMAP: { getint(&s); /* size */ break; } case SBOT_MSG_GETMAP: { break; } case SBOT_MSG_MASTERMODE: { getint(&s); /* mm */ break; } case SBOT_MSG_KICK: { getint(&s); /* victim */ break; } case SBOT_MSG_SETMASTER: { char t[512]; getint(&s); getstring(t, &s, sizeof(t) - 1); break; } case SBOT_MSG_SETTEAM: { char t[512]; getint(&s); getstring(t, &s, sizeof(t) - 1); break; } case SBOT_MSG_BASES: { char text[512]; int ammotype; while ((ammotype = getint(&s)) >= 0) { getstring(text, &s, sizeof(text) - 1); /* owner */ getstring(text, &s, sizeof(text) - 1); /* enemy */ getint(&s); /* converted */ getint(&s); /* ammo */ } break; } case SBOT_MSG_BASEINFO: { char text[512]; getint(&s); /* base */ getstring(text, &s, sizeof(text) - 1); /* owner */ getstring(text, &s, sizeof(text) - 1); /* enemy */ getint(&s); /* converted */ getint(&s); /* ammo */ break; } case SBOT_MSG_TEAMSCORE: { char text[512]; getstring(text, &s, sizeof(text) - 1); getint(&s); break; } case SBOT_MSG_REPAMMO: { getint(&s); /* ammotype */ break; } case SBOT_MSG_FORCEINTERMISSION: { break; } case SBOT_MSG_ANNOUNCE: { getint(&s); /* announce type (quad, boost) */ break; } default: printf("Unhandled MSG %d\n", type); goto invalid_stream; } } return 0; invalid_stream: /* The received data was invalid. Ignore the rest of this packet. * Don't return an error here, as that would allow peers to shutdown * the bot by sending bogus messages. */ return 0; } static int process_receivefile(struct sbot *sb, unsigned char *data, size_t len) { if (!len) { fprintf(stderr, "Received zero length file\n"); return -1; } if (len > 1024*1024) { fprintf(stderr, "Ignoring too big RX map-file\n"); return -1; } free(sb->map); sb->map = malloc(len); if (!sb->map) { fprintf(stderr, "Could not allocate map memory\n"); return -1; } memcpy(sb->map, data, len); sb->maplen = len; printf("Received map (size %u bytes)\n", (unsigned int)len); return 0; } static int process_rx_events(struct sbot *sb, int channel, unsigned char *buf, size_t len) { int res, err = 0; switch (channel) { case 0: /* Positions */ break; case 1: /* Messages */ err = process_rx_messages(sb, buf, len, -1); break; case 2: /* Receive file */ res = process_receivefile(sb, buf, len); if (res) send_text(sb, "I FAILED to get the map."); else send_text(sb, "I received the map."); break; default: fprintf(stderr, "Invalid channel %d in RX event\n", channel); err = -1; } return err; } static int initialize_connection(struct sbot *sb) { int err; err = send_hello(sb); if (err) { fprintf(stderr, "Failed to send HELLO message.\n"); return err; } send_setmaster(sb, 1, args.masterpw); if (!args.autoauth) { /* If we use authentication, make sure new clients * connect as spectators. */ send_mastermode(sb, 2); } send_helptext(sb); return 0; } static void handle_heartbeat(struct sbot *sb) { struct sclient *c; if (sb->mode != GAMEMODE_COOPEDIT) return; list_for_each_entry(c, &sb->clients, list) { if (args.autoauth) { accept_auth(sb, c); continue; } if (!c->authenticated) { c->auth.timeout++; if (c->auth.timeout >= AUTH_TIMEOUT) { send_kick(sb, c->clientnum); } else { if (c->auth.timeout % 5 == 0) send_auth_message(sb, c); } } } } static int event_loop(struct sbot *sb) { ENetEvent event; int err; sigset_t alarm_sigset; int must_handle_heartbeat; sigemptyset(&alarm_sigset); sigaddset(&alarm_sigset, SIGALRM); while ((enet_host_service(sb->conn.client, &event, 100) >= 0) || (errno == EINTR)) { must_handle_heartbeat = 0; sigprocmask(SIG_BLOCK, &alarm_sigset, NULL); if (heartbeat) { heartbeat = 0; must_handle_heartbeat = 1; } sigprocmask(SIG_UNBLOCK, &alarm_sigset, NULL); if (must_handle_heartbeat) handle_heartbeat(sb); switch (event.type) { case ENET_EVENT_TYPE_CONNECT: printf("CONNECT event\n"); /* This should not happen, though, as the connect * event is not caught in the event loop. */ break; case ENET_EVENT_TYPE_RECEIVE: err = process_rx_events(sb, event.channelID, event.packet->data, event.packet->dataLength); if (err) return -1; enet_packet_destroy(event.packet); break; case ENET_EVENT_TYPE_DISCONNECT: fprintf(stderr, "DISCONNECT event from peer\n"); return 0; default: break; } } return -1; } static struct sbot *_sb; static void cleanup(struct sbot *sb) { struct sclient *c, *ctmp; if (rng_fd) fclose(rng_fd); enet_deinitialize(); free(sb->map); sb->map = NULL; sb->maplen = 0; close_maps_directory(sb); list_for_each_entry_safe(c, ctmp, &sb->clients, list) remove_client(c); } static void signal_handler(int signum) { switch (signum) { case SIGALRM: heartbeat = 1; alarm(HEARTBEAT); break; case SIGINT: case SIGTERM: fprintf(stderr, "Killed by signal %d\n", signum); disconnect(_sb); cleanup(_sb); exit(1); break; default: fprintf(stderr, "Received unhandled signal %d\n", signum); } } static void setup_sighandler(void) { struct sigaction act; memset(&act, 0, sizeof(act)); sigemptyset(&act.sa_mask); act.sa_flags = SA_RESTART; act.sa_handler = signal_handler; sigaction(SIGINT, &act, NULL); sigaction(SIGTERM, &act, NULL); sigaction(SIGALRM, &act, NULL); alarm(HEARTBEAT); } static void usage(int argc, char **argv) { printf("Sauerbraten coopedit helper bot version " VERSION "\n\n"); printf("Usage: %s ARGS\n", argv[0]); printf("\n"); printf(" Required arguments:\n"); printf(" --server|-s The server to connect to.\n"); printf("\n"); printf(" Optional arguments:\n"); printf(" --nick|-n NICK The nickname for the bot. Default \"sauerbot\"\n"); printf(" --team|-t TEAM The teamname for the bot. Default \"bot\"\n"); printf(" --masterpass|-m PW The masterpassword to use for the server. Default none.\n"); printf(" --authmap|-a FILE Path to the authmap file. Default \"./authmap\"\n"); printf(" --autoauth|-A Automatically authenticate everybody.\n"); printf(" --maps|-M Path to the maps storage directory. Default \"./maps\"\n"); printf("\n"); printf(" --help|-h Print this help.\n"); printf(" --gen-authmap|-G Generate an authentication map on stdout and exit.\n"); } static int parse_args(int argc, char **argv) { static struct option long_options[] = { { "server", required_argument, 0, 's', }, { "nick", required_argument, 0, 'n', }, { "team", required_argument, 0, 't', }, { "masterpass", required_argument, 0, 'm', }, { "authmap", required_argument, 0, 'a', }, { "autoauth", no_argument, 0, 'A', }, { "maps", required_argument, 0, 'M', }, { "gen-authmap", no_argument, 0, 'G', }, { "help", no_argument, 0, 'h', }, { 0, }, }; int c, idx, err; while (1) { c = getopt_long(argc, argv, "s:n:t:m:a:AM:Gh", long_options, &idx); if (c == -1) break; switch (c) { case 's': args.servername = optarg; break; case 'n': args.nick = optarg; break; case 't': args.team = optarg; break; case 'm': args.masterpw = optarg; break; case 'a': args.authmapfile = optarg; break; case 'A': args.autoauth = 1; break; case 'M': args.maps_dir = optarg; break; case 'G': { struct sbot_authmap m; gen_authmap(&m); err = do_export_authmap(&m, stdout); return (!err) ? 1 : -1; } case 'h': usage(argc, argv); return 1; default: return -1; } } if (!args.servername) { fprintf(stderr, "Error: No server name (--server argument) given.\n"); return -1; } return 0; } int main(int argc, char **argv) { int err; int ret = 1; struct sbot sb; memset(&sb, 0, sizeof(sb)); INIT_LIST_HEAD(&sb.clients); rng_init(); err = enet_initialize(); if (err) { fprintf(stderr, "Could not initialize libenet\n"); return 1; } err = parse_args(argc, argv); if (err > 0) { ret = 0; goto out; } sb.nick = args.nick; sb.team = args.team; if (err) goto out; err = import_authmap(&sb.authmap, args.authmapfile); if (err) goto out; err = open_maps_directory(&sb); if (err) goto out; _sb = &sb; setup_sighandler(); err = connect_to_server(&sb); if (err) goto out; err = initialize_connection(&sb); if (err) goto out_disconn; err = event_loop(&sb); if (err) goto out_disconn; ret = 0; out_disconn: disconnect(&sb); out: cleanup(&sb); return ret; }