Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : /**
5 : * @file mtproto_session.c
6 : * @brief MTProto session state management.
7 : */
8 :
9 : #include "mtproto_session.h"
10 : #include "crypto.h"
11 : #include "raii.h"
12 : #include "fs_util.h"
13 :
14 : #include <stdio.h>
15 : #include <stdlib.h>
16 : #include <string.h>
17 : #include <time.h>
18 :
19 : /**
20 : * @brief Initialize a new MTProto session with a random session_id.
21 : * @param s Session to initialize.
22 : */
23 552 : void mtproto_session_init(MtProtoSession *s) {
24 552 : if (!s) return;
25 552 : memset(s, 0, sizeof(*s));
26 :
27 : /* Random session_id */
28 552 : crypto_rand_bytes((unsigned char *)&s->session_id, sizeof(s->session_id));
29 : /* Ensure non-zero */
30 552 : if (s->session_id == 0) s->session_id = 1;
31 : }
32 :
33 1 : void mtproto_session_renew_id(MtProtoSession *s) {
34 1 : if (!s) return;
35 1 : crypto_rand_bytes((unsigned char *)&s->session_id, sizeof(s->session_id));
36 1 : if (s->session_id == 0) s->session_id = 1;
37 1 : s->seq_no = 0;
38 1 : s->last_msg_id = 0;
39 : }
40 :
41 : /**
42 : * @brief Generate the next monotonically increasing message ID.
43 : *
44 : * Message IDs are approximately unix_time * 2^32, must be divisible by 4
45 : * (client→server), and strictly increasing.
46 : *
47 : * @param s Session.
48 : * @return Next message ID, or 0 if s is NULL.
49 : */
50 328 : uint64_t mtproto_session_next_msg_id(MtProtoSession *s) {
51 328 : if (!s) return 0;
52 :
53 : /* msg_id ≈ unix_time * 2^32 with cryptographic randomness in the
54 : * lower 32 bits. Must be monotonically increasing and divisible by
55 : * 4 for client→server. */
56 328 : uint64_t now = (uint64_t)time(NULL);
57 328 : uint32_t low = 0;
58 328 : crypto_rand_bytes((unsigned char *)&low, sizeof(low));
59 328 : uint64_t msg_id = (now << 32) | ((uint64_t)low & 0xFFFFFFFC);
60 :
61 : /* Ensure monotonic increase */
62 328 : if (msg_id <= s->last_msg_id) {
63 77 : msg_id = s->last_msg_id + 4;
64 : }
65 :
66 : /* Client→server: msg_id mod 4 == 0 */
67 328 : msg_id &= ~(uint64_t)3;
68 :
69 328 : s->last_msg_id = msg_id;
70 328 : return msg_id;
71 : }
72 :
73 : /**
74 : * @brief Get the next sequence number.
75 : *
76 : * For content-related messages, increments the internal counter.
77 : *
78 : * @param s Session.
79 : * @param content_related 1 for RPC calls, 0 for acks/pings.
80 : * @return Next sequence number, or 0 if s is NULL.
81 : */
82 313 : uint32_t mtproto_session_next_seq_no(MtProtoSession *s, int content_related) {
83 313 : if (!s) return 0;
84 313 : uint32_t result = s->seq_no * 2 + (content_related ? 1 : 0);
85 313 : if (content_related) s->seq_no++;
86 313 : return result;
87 : }
88 :
89 : /**
90 : * @brief Set the 256-byte authorization key on the session.
91 : * @param s Session.
92 : * @param key 256-byte key to copy.
93 : */
94 242 : void mtproto_session_set_auth_key(MtProtoSession *s, const uint8_t key[256]) {
95 242 : if (!s || !key) return;
96 242 : memcpy(s->auth_key, key, 256);
97 242 : s->has_auth_key = 1;
98 : }
99 :
100 : /**
101 : * @brief Set the server salt.
102 : * @param s Session.
103 : * @param salt Server salt value.
104 : */
105 242 : void mtproto_session_set_salt(MtProtoSession *s, uint64_t salt) {
106 242 : if (!s) return;
107 242 : s->server_salt = salt;
108 : }
109 :
110 : /**
111 : * @brief Save the auth key to a file with mode 0600.
112 : *
113 : * @param s Session (must have auth_key).
114 : * @param path File path to write.
115 : * @return 0 on success, -1 on error.
116 : */
117 0 : int mtproto_session_save_auth_key(const MtProtoSession *s, const char *path) {
118 0 : if (!s || !path || !s->has_auth_key) return -1;
119 0 : RAII_FILE FILE *f = fopen(path, "wb");
120 0 : if (!f) return -1;
121 :
122 : /* Set restrictive permissions (auth key is sensitive) */
123 0 : if (fs_ensure_permissions(path, 0600) != 0) {
124 : /* Non-fatal but log-worthy — file was already created */
125 : }
126 :
127 0 : size_t written = fwrite(s->auth_key, 1, 256, f);
128 0 : return (written == 256) ? 0 : -1;
129 : }
130 :
131 : /**
132 : * @brief Load an auth key from a file.
133 : *
134 : * @param s Session (auth_key and has_auth_key will be set on success).
135 : * @param path File path to read.
136 : * @return 0 on success, -1 on error (file not found, too short, etc.).
137 : */
138 0 : int mtproto_session_load_auth_key(MtProtoSession *s, const char *path) {
139 0 : if (!s || !path) return -1;
140 0 : RAII_FILE FILE *f = fopen(path, "rb");
141 0 : if (!f) return -1;
142 0 : size_t nread = fread(s->auth_key, 1, 256, f);
143 0 : if (nread != 256) return -1;
144 0 : s->has_auth_key = 1;
145 0 : return 0;
146 : }
|