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 1301 : void mtproto_session_init(MtProtoSession *s) {
24 1301 : if (!s) return;
25 1301 : memset(s, 0, sizeof(*s));
26 :
27 : /* Random session_id */
28 1301 : crypto_rand_bytes((unsigned char *)&s->session_id, sizeof(s->session_id));
29 : /* Ensure non-zero */
30 1301 : if (s->session_id == 0) s->session_id = 1;
31 : }
32 :
33 2 : void mtproto_session_renew_id(MtProtoSession *s) {
34 2 : if (!s) return;
35 2 : crypto_rand_bytes((unsigned char *)&s->session_id, sizeof(s->session_id));
36 2 : if (s->session_id == 0) s->session_id = 1;
37 2 : s->seq_no = 0;
38 2 : 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 807 : uint64_t mtproto_session_next_msg_id(MtProtoSession *s) {
51 807 : 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 807 : uint64_t now = (uint64_t)time(NULL);
57 807 : uint32_t low = 0;
58 807 : crypto_rand_bytes((unsigned char *)&low, sizeof(low));
59 807 : uint64_t msg_id = (now << 32) | ((uint64_t)low & 0xFFFFFFFC);
60 :
61 : /* Ensure monotonic increase */
62 807 : if (msg_id <= s->last_msg_id) {
63 185 : msg_id = s->last_msg_id + 4;
64 : }
65 :
66 : /* Client→server: msg_id mod 4 == 0 */
67 807 : msg_id &= ~(uint64_t)3;
68 :
69 807 : s->last_msg_id = msg_id;
70 807 : 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 759 : uint32_t mtproto_session_next_seq_no(MtProtoSession *s, int content_related) {
83 759 : if (!s) return 0;
84 759 : uint32_t result = s->seq_no * 2 + (content_related ? 1 : 0);
85 759 : if (content_related) s->seq_no++;
86 759 : 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 634 : void mtproto_session_set_auth_key(MtProtoSession *s, const uint8_t key[256]) {
95 634 : if (!s || !key) return;
96 634 : memcpy(s->auth_key, key, 256);
97 634 : 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 629 : void mtproto_session_set_salt(MtProtoSession *s, uint64_t salt) {
106 629 : if (!s) return;
107 629 : 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 5 : int mtproto_session_save_auth_key(const MtProtoSession *s, const char *path) {
118 5 : if (!s || !path || !s->has_auth_key) return -1;
119 4 : RAII_FILE FILE *f = fopen(path, "wb");
120 2 : if (!f) return -1;
121 :
122 : /* Set restrictive permissions (auth key is sensitive) */
123 1 : if (fs_ensure_permissions(path, 0600) != 0) {
124 : /* Non-fatal but log-worthy — file was already created */
125 : }
126 :
127 1 : size_t written = fwrite(s->auth_key, 1, 256, f);
128 1 : 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 5 : int mtproto_session_load_auth_key(MtProtoSession *s, const char *path) {
139 5 : if (!s || !path) return -1;
140 6 : RAII_FILE FILE *f = fopen(path, "rb");
141 3 : if (!f) return -1;
142 2 : size_t nread = fread(s->auth_key, 1, 256, f);
143 2 : if (nread != 256) return -1;
144 1 : s->has_auth_key = 1;
145 1 : return 0;
146 : }
|