Line data Source code
1 : /* SPDX-License-Identifier: GPL-3.0-or-later */
2 : /* Copyright 2026 Peter Csaszar */
3 :
4 : /**
5 : * @file auth_session.c
6 : * @brief Telegram phone-number login: auth.sendCode + auth.signIn.
7 : */
8 :
9 : #include "auth_session.h"
10 : #include "mtproto_rpc.h"
11 : #include "tl_serial.h"
12 : #include "tl_registry.h"
13 : #include "logger.h"
14 : #include "pii_redact.h"
15 : #include "raii.h"
16 :
17 : #include <stdlib.h>
18 : #include <string.h>
19 :
20 : /* ---- Internal: build auth.sendCode request ---- */
21 :
22 51 : static int build_send_code(const ApiConfig *cfg, const char *phone,
23 : uint8_t *out, size_t max_len, size_t *out_len) {
24 : TlWriter w;
25 51 : tl_writer_init(&w);
26 :
27 : /* auth.sendCode#a677244f phone_number:string api_id:int api_hash:string
28 : * settings:CodeSettings */
29 51 : tl_write_uint32(&w, CRC_auth_sendCode);
30 51 : tl_write_string(&w, phone);
31 51 : tl_write_int32(&w, cfg->api_id);
32 51 : tl_write_string(&w, cfg->api_hash ? cfg->api_hash : "");
33 :
34 : /* codeSettings#ad253d78 flags:# (flags=0 → no special options) */
35 51 : tl_write_uint32(&w, CRC_codeSettings);
36 51 : tl_write_uint32(&w, 0); /* flags = 0 */
37 :
38 51 : if (w.len > max_len) {
39 0 : tl_writer_free(&w);
40 0 : return -1;
41 : }
42 51 : memcpy(out, w.data, w.len);
43 51 : *out_len = w.len;
44 51 : tl_writer_free(&w);
45 51 : return 0;
46 : }
47 :
48 : /* ---- Internal: read a sentCodeType sub-object, return its CRC ---- */
49 :
50 20 : static uint32_t read_sent_code_type(TlReader *r) {
51 20 : if (!tl_reader_ok(r)) return 0;
52 20 : uint32_t type_crc = tl_read_uint32(r);
53 :
54 20 : logger_log(LOG_INFO, "auth_send_code: sentCodeType=0x%08x", type_crc);
55 :
56 20 : switch (type_crc) {
57 14 : case CRC_auth_sentCodeTypeApp:
58 : case CRC_auth_sentCodeTypeSms:
59 : case CRC_auth_sentCodeTypeCall: {
60 14 : int32_t code_len = tl_read_int32(r);
61 14 : logger_log(LOG_INFO, "auth_send_code: sentCodeType code_length=%d", code_len);
62 14 : return type_crc;
63 : }
64 :
65 3 : case CRC_auth_sentCodeTypeFlashCall: {
66 3 : RAII_STRING char *pattern = tl_read_string(r);
67 : (void)pattern;
68 3 : return type_crc;
69 : }
70 :
71 3 : default:
72 3 : logger_log(LOG_WARN, "auth_send_code: unknown sentCodeType 0x%08x", type_crc);
73 3 : return 0;
74 : }
75 : }
76 :
77 : /* ---- auth_send_code ---- */
78 :
79 52 : int auth_send_code(const ApiConfig *cfg,
80 : MtProtoSession *s, Transport *t,
81 : const char *phone,
82 : AuthSentCode *out,
83 : RpcError *err) {
84 52 : if (!cfg || !s || !t || !phone || !out) return -1;
85 51 : if (err) { err->error_code = 0; err->error_msg[0] = '\0';
86 44 : err->migrate_dc = -1; err->flood_wait_secs = 0; }
87 :
88 : uint8_t query[4096];
89 51 : size_t qlen = 0;
90 51 : if (build_send_code(cfg, phone, query, sizeof(query), &qlen) != 0) {
91 0 : logger_log(LOG_ERROR, "auth_send_code: failed to build request");
92 0 : return -1;
93 : }
94 :
95 51 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(65536);
96 51 : if (!resp) return -1;
97 51 : size_t resp_len = 0;
98 :
99 51 : if (api_call(cfg, s, t, query, qlen, resp, 65536, &resp_len) != 0) {
100 1 : logger_log(LOG_ERROR, "auth_send_code: api_call failed");
101 1 : return -1;
102 : }
103 :
104 50 : if (resp_len < 4) {
105 0 : logger_log(LOG_ERROR, "auth_send_code: response too short");
106 0 : return -1;
107 : }
108 :
109 : /* Check for rpc_error */
110 : uint32_t constructor;
111 50 : memcpy(&constructor, resp, 4);
112 50 : if (constructor == TL_rpc_error) {
113 : RpcError perr;
114 27 : rpc_parse_error(resp, resp_len, &perr);
115 27 : logger_log(LOG_ERROR, "auth_send_code: RPC error %d: %s",
116 : perr.error_code, perr.error_msg);
117 27 : if (err) *err = perr;
118 27 : return -1;
119 : }
120 :
121 23 : if (constructor != CRC_auth_sentCode) {
122 3 : logger_log(LOG_ERROR, "auth_send_code: unexpected constructor 0x%08x",
123 : constructor);
124 3 : return -1;
125 : }
126 :
127 : /* Parse auth.sentCode:
128 : * flags:# type:auth.SentCodeType phone_code_hash:string
129 : * next_type:flags.1?auth.CodeType timeout:flags.2?int */
130 20 : TlReader r = tl_reader_init(resp, resp_len);
131 20 : tl_read_uint32(&r); /* skip constructor */
132 :
133 20 : uint32_t flags = tl_read_uint32(&r);
134 :
135 20 : out->code_type = read_sent_code_type(&r);
136 20 : if (out->code_type == 0) {
137 3 : logger_log(LOG_ERROR, "auth_send_code: failed to parse sentCodeType");
138 3 : return -1;
139 : }
140 :
141 34 : RAII_STRING char *hash = tl_read_string(&r);
142 17 : if (!hash) {
143 0 : logger_log(LOG_ERROR, "auth_send_code: failed to read phone_code_hash");
144 0 : return -1;
145 : }
146 :
147 17 : size_t hash_len = strlen(hash);
148 17 : if (hash_len >= AUTH_CODE_HASH_MAX) {
149 0 : logger_log(LOG_ERROR, "auth_send_code: phone_code_hash too long");
150 0 : return -1;
151 : }
152 17 : memcpy(out->phone_code_hash, hash, hash_len + 1);
153 :
154 : /* next_type: flags.1 */
155 17 : out->next_type = 0;
156 17 : if (flags & (1u << 1)) out->next_type = tl_read_uint32(&r);
157 :
158 : /* timeout: flags.2 */
159 17 : out->timeout = 0;
160 17 : if (flags & (1u << 2)) out->timeout = tl_read_int32(&r);
161 :
162 17 : logger_log(LOG_INFO,
163 : "auth_send_code: code sent (hash_len=%zu, code_type=0x%08x, "
164 : "next_type=0x%08x, timeout=%d)",
165 17 : strlen(out->phone_code_hash), out->code_type,
166 : out->next_type, out->timeout);
167 17 : return 0;
168 : }
169 :
170 : /* ---- Internal: build auth.signIn request ---- */
171 :
172 33 : static int build_sign_in(const char *phone, const char *hash, const char *code,
173 : uint8_t *out, size_t max_len, size_t *out_len) {
174 : TlWriter w;
175 33 : tl_writer_init(&w);
176 :
177 : /* auth.signIn#8d52a951 flags:# phone_number:string phone_code_hash:string
178 : * phone_code:flags.0?string */
179 33 : tl_write_uint32(&w, CRC_auth_signIn);
180 33 : tl_write_uint32(&w, 1u); /* flags = 0x1 → phone_code present */
181 33 : tl_write_string(&w, phone);
182 33 : tl_write_string(&w, hash);
183 33 : tl_write_string(&w, code); /* flags.0 is set */
184 :
185 33 : if (w.len > max_len) {
186 0 : tl_writer_free(&w);
187 0 : return -1;
188 : }
189 33 : memcpy(out, w.data, w.len);
190 33 : *out_len = w.len;
191 33 : tl_writer_free(&w);
192 33 : return 0;
193 : }
194 :
195 : /* ---- Internal: parse auth.authorization and extract user id ---- */
196 :
197 8 : static int parse_authorization(const uint8_t *resp, size_t resp_len,
198 : int64_t *user_id_out, const char *caller) {
199 8 : TlReader r = tl_reader_init(resp, resp_len);
200 8 : tl_read_uint32(&r); /* skip constructor */
201 8 : uint32_t flags = tl_read_uint32(&r);
202 :
203 8 : if (flags & (1u << 1)) tl_read_int32(&r); /* otherwise_relogin_days */
204 :
205 8 : uint32_t user_crc = tl_read_uint32(&r);
206 8 : if (user_crc != TL_user && user_crc != TL_user2 && user_crc != TL_userFull) {
207 3 : logger_log(LOG_WARN, "%s: unexpected user constructor 0x%08x",
208 : caller, user_crc);
209 3 : if (user_id_out) *user_id_out = 0;
210 3 : return 0;
211 : }
212 :
213 5 : tl_read_uint32(&r); /* user flags */
214 5 : int64_t uid = tl_read_int64(&r);
215 5 : if (user_id_out) *user_id_out = uid;
216 5 : logger_log(LOG_INFO, "%s: authenticated as user_id=%lld",
217 : caller, (long long)uid);
218 5 : return 0;
219 : }
220 :
221 : /* ---- auth_sign_in ---- */
222 :
223 34 : int auth_sign_in(const ApiConfig *cfg,
224 : MtProtoSession *s, Transport *t,
225 : const char *phone,
226 : const char *phone_code_hash,
227 : const char *code,
228 : int64_t *user_id_out,
229 : int *signup_required,
230 : RpcError *err) {
231 34 : if (!cfg || !s || !t || !phone || !phone_code_hash || !code) return -1;
232 33 : if (signup_required) *signup_required = 0;
233 33 : if (err) { err->error_code = 0; err->error_msg[0] = '\0';
234 28 : err->migrate_dc = -1; err->flood_wait_secs = 0; }
235 :
236 : uint8_t query[4096];
237 33 : size_t qlen = 0;
238 33 : if (build_sign_in(phone, phone_code_hash, code,
239 : query, sizeof(query), &qlen) != 0) {
240 0 : logger_log(LOG_ERROR, "auth_sign_in: failed to build request");
241 0 : return -1;
242 : }
243 :
244 33 : logger_log(LOG_INFO, "auth_sign_in: sending request");
245 :
246 33 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(65536);
247 33 : if (!resp) return -1;
248 33 : size_t resp_len = 0;
249 :
250 33 : if (api_call(cfg, s, t, query, qlen, resp, 65536, &resp_len) != 0) {
251 1 : logger_log(LOG_ERROR, "auth_sign_in: api_call failed");
252 1 : return -1;
253 : }
254 :
255 32 : if (resp_len < 4) {
256 0 : logger_log(LOG_ERROR, "auth_sign_in: response too short");
257 0 : return -1;
258 : }
259 :
260 : uint32_t constructor;
261 32 : memcpy(&constructor, resp, 4);
262 :
263 32 : if (constructor == TL_rpc_error) {
264 : RpcError perr;
265 19 : rpc_parse_error(resp, resp_len, &perr);
266 19 : logger_log(LOG_ERROR, "auth_sign_in: RPC error %d: %s",
267 : perr.error_code, perr.error_msg);
268 19 : if (err) *err = perr;
269 19 : return -1;
270 : }
271 :
272 13 : if (constructor == CRC_auth_authorizationSignUpRequired) {
273 2 : logger_log(LOG_INFO, "auth_sign_in: sign-up required for this phone");
274 2 : if (signup_required) *signup_required = 1;
275 2 : return -1;
276 : }
277 :
278 11 : if (constructor != CRC_auth_authorization) {
279 3 : logger_log(LOG_ERROR, "auth_sign_in: unexpected constructor 0x%08x",
280 : constructor);
281 3 : return -1;
282 : }
283 :
284 8 : return parse_authorization(resp, resp_len, user_id_out, "auth_sign_in");
285 : }
286 :
287 : /* ---- Internal: build auth.signUp request ---- */
288 :
289 0 : static int build_sign_up(const char *phone, const char *hash,
290 : const char *first_name, const char *last_name,
291 : uint8_t *out, size_t max_len, size_t *out_len) {
292 : TlWriter w;
293 0 : tl_writer_init(&w);
294 :
295 : /* auth.signUp#80eee427 phone_number:string phone_code_hash:string
296 : * first_name:string last_name:string */
297 0 : tl_write_uint32(&w, CRC_auth_signUp);
298 0 : tl_write_string(&w, phone);
299 0 : tl_write_string(&w, hash);
300 0 : tl_write_string(&w, first_name);
301 0 : tl_write_string(&w, last_name ? last_name : "");
302 :
303 0 : if (w.len > max_len) {
304 0 : tl_writer_free(&w);
305 0 : return -1;
306 : }
307 0 : memcpy(out, w.data, w.len);
308 0 : *out_len = w.len;
309 0 : tl_writer_free(&w);
310 0 : return 0;
311 : }
312 :
313 : /* ---- auth_sign_up ---- */
314 :
315 0 : int auth_sign_up(const ApiConfig *cfg,
316 : MtProtoSession *s, Transport *t,
317 : const char *phone,
318 : const char *phone_code_hash,
319 : const char *first_name,
320 : const char *last_name,
321 : int64_t *user_id_out,
322 : RpcError *err) {
323 0 : if (!cfg || !s || !t || !phone || !phone_code_hash || !first_name) return -1;
324 0 : if (err) { err->error_code = 0; err->error_msg[0] = '\0';
325 0 : err->migrate_dc = -1; err->flood_wait_secs = 0; }
326 :
327 : uint8_t query[4096];
328 0 : size_t qlen = 0;
329 0 : if (build_sign_up(phone, phone_code_hash, first_name, last_name,
330 : query, sizeof(query), &qlen) != 0) {
331 0 : logger_log(LOG_ERROR, "auth_sign_up: failed to build request");
332 0 : return -1;
333 : }
334 :
335 0 : logger_log(LOG_INFO, "auth_sign_up: phone='%s' first_name='%s'",
336 : phone, first_name);
337 :
338 0 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(65536);
339 0 : if (!resp) return -1;
340 0 : size_t resp_len = 0;
341 :
342 0 : if (api_call(cfg, s, t, query, qlen, resp, 65536, &resp_len) != 0) {
343 0 : logger_log(LOG_ERROR, "auth_sign_up: api_call failed");
344 0 : return -1;
345 : }
346 :
347 0 : if (resp_len < 4) {
348 0 : logger_log(LOG_ERROR, "auth_sign_up: response too short");
349 0 : return -1;
350 : }
351 :
352 : uint32_t constructor;
353 0 : memcpy(&constructor, resp, 4);
354 :
355 0 : if (constructor == TL_rpc_error) {
356 : RpcError perr;
357 0 : rpc_parse_error(resp, resp_len, &perr);
358 0 : logger_log(LOG_ERROR, "auth_sign_up: RPC error %d: %s",
359 : perr.error_code, perr.error_msg);
360 0 : if (err) *err = perr;
361 0 : return -1;
362 : }
363 :
364 0 : if (constructor != CRC_auth_authorization) {
365 0 : logger_log(LOG_ERROR, "auth_sign_up: unexpected constructor 0x%08x",
366 : constructor);
367 0 : return -1;
368 : }
369 :
370 0 : return parse_authorization(resp, resp_len, user_id_out, "auth_sign_up");
371 : }
372 :
373 : /* ---- auth_resend_code ---- */
374 :
375 0 : int auth_resend_code(const ApiConfig *cfg,
376 : MtProtoSession *s, Transport *t,
377 : const char *phone,
378 : AuthSentCode *sent,
379 : RpcError *err) {
380 0 : if (!cfg || !s || !t || !phone || !sent) return -1;
381 0 : if (err) { err->error_code = 0; err->error_msg[0] = '\0';
382 0 : err->migrate_dc = -1; err->flood_wait_secs = 0; }
383 :
384 : TlWriter w;
385 0 : tl_writer_init(&w);
386 :
387 : /* auth.resendCode#3ef1a9bf flags:# phone_number:string phone_code_hash:string
388 : * reason:flags.0?string */
389 0 : tl_write_uint32(&w, CRC_auth_resendCode);
390 0 : tl_write_uint32(&w, 0); /* flags = 0 (no reason) */
391 0 : tl_write_string(&w, phone);
392 0 : tl_write_string(&w, sent->phone_code_hash);
393 :
394 : uint8_t query[1024];
395 0 : size_t qlen = 0;
396 0 : if (w.len > sizeof(query)) { tl_writer_free(&w); return -1; }
397 0 : memcpy(query, w.data, w.len);
398 0 : qlen = w.len;
399 0 : tl_writer_free(&w);
400 :
401 0 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(65536);
402 0 : if (!resp) return -1;
403 0 : size_t resp_len = 0;
404 :
405 0 : if (api_call(cfg, s, t, query, qlen, resp, 65536, &resp_len) != 0) {
406 0 : logger_log(LOG_ERROR, "auth_resend_code: api_call failed");
407 0 : return -1;
408 : }
409 :
410 0 : if (resp_len < 4) {
411 0 : logger_log(LOG_ERROR, "auth_resend_code: response too short");
412 0 : return -1;
413 : }
414 :
415 : uint32_t constructor;
416 0 : memcpy(&constructor, resp, 4);
417 :
418 0 : if (constructor == TL_rpc_error) {
419 : RpcError perr;
420 0 : rpc_parse_error(resp, resp_len, &perr);
421 0 : logger_log(LOG_ERROR, "auth_resend_code: RPC error %d: %s",
422 : perr.error_code, perr.error_msg);
423 0 : if (err) *err = perr;
424 0 : return -1;
425 : }
426 :
427 0 : if (constructor != CRC_auth_sentCode) {
428 0 : logger_log(LOG_ERROR, "auth_resend_code: unexpected constructor 0x%08x",
429 : constructor);
430 0 : return -1;
431 : }
432 :
433 : /* Parse the new sentCode to update code_type. */
434 0 : TlReader r = tl_reader_init(resp, resp_len);
435 0 : tl_read_uint32(&r); /* skip constructor */
436 0 : uint32_t flags = tl_read_uint32(&r);
437 :
438 0 : sent->code_type = read_sent_code_type(&r);
439 :
440 : /* skip phone_code_hash — must not change */
441 0 : RAII_STRING char *new_hash = tl_read_string(&r);
442 : (void)new_hash;
443 :
444 0 : sent->next_type = 0;
445 0 : if (flags & (1u << 1)) sent->next_type = tl_read_uint32(&r);
446 0 : if (flags & (1u << 2)) sent->timeout = tl_read_int32(&r);
447 :
448 0 : logger_log(LOG_INFO,
449 : "auth_resend_code: new code_type=0x%08x, next_type=0x%08x",
450 : sent->code_type, sent->next_type);
451 0 : return 0;
452 : }
|