summaryrefslogtreecommitdiff
path: root/src/login.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/login.c')
-rw-r--r--src/login.c362
1 files changed, 362 insertions, 0 deletions
diff --git a/src/login.c b/src/login.c
new file mode 100644
index 0000000..a804d33
--- /dev/null
+++ b/src/login.c
@@ -0,0 +1,362 @@
+#include "db.h"
+#include "packet.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <err.h>
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0]))
+
+static struct {
+ struct { struct db login; } db;
+ uint32_t session;
+} server;
+
+static void
+flush_packet(union packet *packet)
+{
+ packet_crypt(packet);
+ fwrite(packet->buf, 1, packet->hdr.size, stdout);
+}
+
+static void
+handle_select_character_req(union packet *packet, bool prologue)
+{
+ uint32_t char_id;
+ {
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_read(&pbuf, &char_id, sizeof(char_id));
+ }
+
+ // TODO: check that account actually has the character
+
+ db_query_single(&server.db.login, "update sessions set char_id = ? where id = ?",
+ (struct db_query_arg[]){
+ { .type = DB_ARG_I32, .u.i32 = char_id },
+ { .type = DB_ARG_I32, .u.i32 = server.session }
+ }, 2, NULL, 0);
+
+ struct db_query_arg args[] = {
+ { .type = DB_ARG_I32, .u.i32 = server.session }
+ }, cols[2];
+
+ const int results = db_query_single(&server.db.login,
+ "select host, port from servers where name = (select server from sessions where id = ?) limit 1",
+ args, ARRAY_SIZE(args), cols, ARRAY_SIZE(cols));
+
+ assert(results == ARRAY_SIZE(cols));
+ assert(cols[0].type == DB_ARG_UTF8 || cols[0].type == DB_ARG_NULL);
+ assert(cols[1].type == DB_ARG_I32);
+
+ const char *host = getenv("TCPREMOTEIP");
+ if (!host || strcmp(host, "127.0.0.1")) {
+ const char *host = getenv("GAME_SERVER");
+ host = (host ? host : cols[0].u.blob.data);
+ }
+
+ warnx("connecting to game server: %s:%u", (host ? host : (char*)cols[0].u.blob.data), cols[1].u.i32);
+
+ packet->hdr.type = (prologue ? PACKET_CHARACTER_ENTER_PROLOGUE : PACKET_SELECT_CHARACTER_RES);
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, &char_id, sizeof(char_id));
+ for (size_t i = 0; i < 32; ++i) pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown
+ pbuf_write_str(&pbuf, (host ? host : cols[0].u.blob.data)); // server ip
+ pbuf_write(&pbuf, (uint16_t[]){cols[1].u.i32}, sizeof(uint16_t)); // port
+ for (size_t i = 0; i < 38; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown
+ pbuf_flush(&pbuf);
+ flush_packet(packet);
+}
+
+static void
+handle_create_character_req(union packet *packet)
+{
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf.cursor += 4; // unknown;
+
+ struct pbuf_string name;
+ uint8_t model;
+ pbuf_read_str_utf16(&pbuf, &name);
+ pbuf_read(&pbuf, &model, 1);
+
+ db_query_single(&server.db.login,
+ "insert into characters (account_id, model, name) values ((select account_id from sessions where id = ? limit 1), ?, ?)",
+ (struct db_query_arg[]){
+ { .type = DB_ARG_I32, .u.i32 = server.session },
+ { .type = DB_ARG_I32, .u.i32 = model },
+ { .type = DB_ARG_UTF8, .u.blob = { .data = name.data, .sz = name.len } }
+ }, 3, NULL, 0);
+}
+
+static void
+handle_delete_character_req(union packet *packet)
+{
+ packet->hdr.type = PACKET_DELETE_CHARACTER_RES;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_flush(&pbuf);
+ flush_packet(packet);
+}
+
+static void
+handle_charlist(union packet *packet)
+{
+ packet->hdr.type = PACKET_CHARACTER_LIST_RES;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+
+ struct db_query_arg args[] = {{ .type = DB_ARG_I32, .u.i32 = server.session }}, cols[5];
+ struct db_query query = db_query_begin(&server.db.login,
+ "select id, model, name, evolution, lvl from characters where account_id = (select account_id from sessions where id = ? limit 1) order by id",
+ args, ARRAY_SIZE(args));
+
+ pbuf_write(&pbuf, (uint8_t[]){0}, 1); // num characters (placeholder)
+ for (uint8_t i = 0; db_query_fetch(&query, cols, ARRAY_SIZE(cols)) == ARRAY_SIZE(cols); ++i) {
+ assert(cols[0].type == DB_ARG_I32 && cols[1].type == DB_ARG_I32 && cols[3].type == DB_ARG_I32 && cols[4].type == DB_ARG_I32);
+ assert(cols[2].type == DB_ARG_UTF8);
+ pbuf_write(&pbuf, (uint32_t[]){cols[0].u.i32}, sizeof(uint32_t)); // charid
+ pbuf_write_str_len_utf16(&pbuf, cols[2].u.blob.data, cols[2].u.blob.sz); // char name UTF16
+ pbuf_write(&pbuf, (uint8_t[]){cols[1].u.i32}, 1); // char model (lily, haru, ...), 0 for no char
+ pbuf_write(&pbuf, (uint8_t[]){cols[3].u.i32}, 1); // evolution lvl
+ for (size_t i = 0; i < 20; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // skin/eye/body/etc
+ pbuf_write(&pbuf, (uint8_t[]){cols[4].u.i32}, 1); // char lvl
+ for (size_t i = 0; i < 526; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // fashion?
+ pbuf_write(&pbuf, (uint8_t[]){i + 1}, 1); // char slot
+ packet->buf[sizeof(packet->hdr)] = i + 1;
+ }
+ pbuf_flush(&pbuf);
+ flush_packet(packet);
+}
+
+static void
+handle_enter_server_req(union packet *packet)
+{
+ {
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_read(&pbuf, &server.session, sizeof(server.session));
+ }
+
+ packet->hdr.type = PACKET_ENTER_SERVER_RES;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // error code maybe?
+ pbuf_write(&pbuf, &server.session, sizeof(server.session));
+ pbuf_write(&pbuf, (uint8_t[]){0xff, 0xff}, 2); // unknown
+ pbuf_flush(&pbuf);
+ flush_packet(packet);
+
+ packet->hdr.type = PACKET_SYSTEM_SERVER_OPTION_UPDATE;
+ pbuf = (struct pbuf){ .packet = packet, .cursor = sizeof(packet->hdr) };
+ // TODO: figure out what these options are, some of them disable second password
+ pbuf_write(&pbuf, (uint8_t[]){0x01, 0x00, 0x01, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01}, 14);
+ pbuf_flush(&pbuf);
+ flush_packet(packet);
+}
+
+static void
+handle_server_connect_req(union packet *packet)
+{
+ {
+ uint8_t selected;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_read(&pbuf, &selected, sizeof(selected));
+ db_query_single(&server.db.login, "update sessions set server = (select name from servers order by id limit 1 offset ?) where id = ?",
+ (struct db_query_arg[]){
+ { .type = DB_ARG_I32, .u.i32 = (int)selected + 1 },
+ { .type = DB_ARG_I32, .u.i32 = server.session }
+ }, 2, NULL, 0);
+
+ warnx("moving to the authentication part: %u", selected);
+ }
+
+ packet->hdr.type = PACKET_ENTER_SERVER;
+ packet->hdr.size = sizeof(packet->hdr);
+ flush_packet(packet);
+}
+
+static void
+handle_server_list_req(union packet *packet)
+{
+ uint8_t num_chars;
+ {
+ struct db_query_arg col = {0};
+ db_query_single(&server.db.login,
+ "select count(id) from characters where account_id = (select account_id from sessions where id = ? limit 1)",
+ (struct db_query_arg[]){{ .type = DB_ARG_I32, .u.i32 = server.session }}, 1, &col, 1);
+ num_chars = col.u.i32;
+ }
+
+ uint16_t port;
+ const char *host = getenv("TCPREMOTEIP");
+ {
+ struct db_query_arg cols[2];
+ const int results = db_query_single(&server.db.login,
+ "select host, port from servers limit 1",
+ NULL, 0, cols, ARRAY_SIZE(cols));
+
+ assert(results == ARRAY_SIZE(cols));
+ assert(cols[0].type == DB_ARG_UTF8 || cols[0].type == DB_ARG_NULL);
+ assert(cols[1].type == DB_ARG_I32);
+ port = cols[1].u.i32;
+
+ if (!host || strcmp(host, "127.0.0.1"))
+ host = (cols[0].u.blob.data ? cols[0].u.blob.data : "127.0.0.1");
+ }
+
+ packet->hdr.type = PACKET_SERVER_LIST;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown
+ pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // num servers (placeholder)
+
+ struct db_query_arg cols[2];
+ struct db_query query = db_query_begin(&server.db.login, "select name, status from servers order by id limit 255 offset 1", NULL, 0);
+ for (uint8_t i = 0; db_query_fetch(&query, cols, ARRAY_SIZE(cols)) == ARRAY_SIZE(cols); ++i) {
+ assert(cols[0].type == DB_ARG_UTF8);
+ assert(cols[1].type == DB_ARG_I32);
+ warnx("%s: %u", (char*)cols[0].u.blob.data, cols[1].u.i32);
+ pbuf_write(&pbuf, (uint8_t[]){i}, 1); // server index
+ pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown
+ pbuf_write(&pbuf, &port, sizeof(port)); // port
+ pbuf_write_str_len(&pbuf, cols[0].u.blob.data, cols[0].u.blob.sz); // server name
+ pbuf_write_str(&pbuf, host); // server ip
+ pbuf_write(&pbuf, (uint32_t[]){cols[1].u.i32}, sizeof(uint32_t)); // server status
+ for (size_t i = 0; i < 4; ++i) pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown
+ pbuf_write(&pbuf, &num_chars, 1); // num characters
+ packet->buf[sizeof(packet->hdr) + 1] = i + 1;
+ }
+ db_query_end(&query);
+ pbuf_flush(&pbuf);
+ flush_packet(packet);
+}
+
+static void
+handle_login_req(union packet *packet)
+{
+ struct pbuf_string username, password, mac = {0};
+ {
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_read_str_utf16(&pbuf, &username);
+ pbuf_read_str_utf16(&pbuf, &password);
+ pbuf_read_str_utf16(&pbuf, &mac);
+ }
+
+ struct db_query_arg args[] = {
+ { .type = DB_ARG_UTF8, .u.blob = { .data = username.data, .sz = username.len } },
+ { .type = DB_ARG_UTF8, .u.blob = { .data = password.data, .sz = password.len } },
+ }, cols[1];
+
+ const int results = db_query_single(&server.db.login,
+ "select id from accounts where username = ? and password = ? limit 1",
+ args, ARRAY_SIZE(args), cols, ARRAY_SIZE(cols));
+
+ warnx("%s login: %d", username.data, results);
+
+ enum {
+ OK = 0x00,
+ INVALID_CREDENTIALS = 0x01,
+ ALREADY_CONNECTED = 0x02,
+ ACCOUNT_BLOCKED = 0x03,
+ IP_BLOCKED = 0x04,
+ MAC_BLOCKED = 0x05,
+ MAC_INVALID = 0x06,
+ } status = OK;
+
+ if (mac.len != 17) {
+ status = MAC_INVALID;
+ } else if (!results) {
+ status = INVALID_CREDENTIALS;
+ } else {
+ const uint32_t account_id = cols[0].u.i32;
+
+ db_query_single(&server.db.login,
+ "update accounts set last_login = current_timestamp where username = ? and password = ?",
+ args, ARRAY_SIZE(args), NULL, 0);
+
+ do {
+ server.session = rand();
+ } while (db_query_single(&server.db.login, "select id from sessions where id = ?",
+ (struct db_query_arg[]){{ .type = DB_ARG_I32, .u.i32 = server.session }}, 1, NULL, 0));
+
+ db_query_single(&server.db.login,
+ "insert into sessions (id, account_id, server) values (?, ?, (select name from servers order by id limit 1))",
+ (struct db_query_arg[]){
+ { .type = DB_ARG_I32, .u.i32 = server.session },
+ { .type = DB_ARG_I32, .u.i32 = account_id }
+ }, 2, NULL, 0);
+ }
+
+ packet->hdr.type = PACKET_LOGIN_RESULT;
+ struct pbuf pbuf = { .packet = packet, .cursor = sizeof(packet->hdr) };
+ pbuf_write(&pbuf, &server.session, sizeof(uint32_t)); // session token
+ pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown
+ pbuf_write(&pbuf, mac.data, 17); // mac
+ for (size_t i = 0; i < 3; ++i) pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown
+ pbuf_write(&pbuf, (uint8_t[]){status}, 1); // login status
+ for (size_t i = 0; i < 4; ++i) pbuf_write(&pbuf, (uint8_t[]){0x00}, 1); // unknown (but needs to be 0)
+ pbuf_write_str_len_utf16(&pbuf, username.data, username.len); // username
+ for (size_t i = 0; i < 13; ++i) pbuf_write(&pbuf, (uint8_t[]){0xff}, 1); // unknown
+ pbuf_flush(&pbuf);
+ flush_packet(packet);
+}
+
+int
+main(void)
+{
+ setvbuf(stdout, NULL, _IONBF, 0);
+ db_init(&server.db.login, "login.db");
+ server.session = ~(uint32_t)0;
+
+ union packet packet;
+ for (size_t psz; (psz = fread(packet.buf, 1, sizeof(packet.hdr), stdin)) > 0; ) {
+ if (!packet_verify(&packet))
+ errx(EXIT_FAILURE, "invalid packet");
+
+ psz += fread(packet.buf + sizeof(packet.hdr), 1, packet.hdr.size - sizeof(packet.hdr), stdin);
+ if (psz != packet.hdr.size)
+ errx(EXIT_FAILURE, "packet size doesn't match, got %zu, expected %u", psz, packet.hdr.size);
+
+ packet_crypt(&packet);
+ warnx("got packet of type 0x%.4x", packet.hdr.type);
+
+ switch (packet.hdr.type) {
+ /** LOGIN */
+ case PACKET_LOGIN_REQ:
+ handle_login_req(&packet);
+ break;
+ case PACKET_SERVER_LIST_REQ:
+ handle_server_list_req(&packet);
+ break;
+ case PACKET_SERVER_CONNECT_REQ:
+ handle_server_connect_req(&packet);
+ break;
+ /** AUTH */
+ case PACKET_ENTER_SERVER_REQ:
+ handle_enter_server_req(&packet);
+ break;
+ case PACKET_CHARACTER_LIST_REQ:
+ handle_charlist(&packet);
+ break;
+ case PACKET_CHARACTER_UPDATE_SPECIAL_OPTION_LIST: // ???
+ break;
+ case PACKET_SECOND_PASSWORD:
+ warnx("we shouldn't get this packet as we told client to not send it");
+ break;
+ case PACKET_CREATE_CHARACTER_REQ:
+ handle_create_character_req(&packet);
+ handle_charlist(&packet);
+ break;
+ case PACKET_DELETE_CHARACTER_REQ:
+ handle_delete_character_req(&packet);
+ handle_charlist(&packet);
+ break;
+ case PACKET_SELECT_CHARACTER_REQ:
+ handle_select_character_req(&packet, false);
+ break;
+ default:
+ warnx("unknown packet");
+ break;
+ }
+
+ db_gc(&server.db.login);
+ }
+
+ db_release(&server.db.login);
+ return EXIT_SUCCESS;
+}