From 662d78b9a5bebdb98dc178ebd1f8e3384793c104 Mon Sep 17 00:00:00 2001 From: Jari Vetoniemi Date: Sun, 17 Mar 2019 15:30:43 +0200 Subject: Initial commit --- src/login.c | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 362 insertions(+) create mode 100644 src/login.c (limited to 'src/login.c') 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 +#include +#include +#include +#include + +#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; +} -- cgit v1.2.3