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 22 : 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 22 : tl_writer_init(&w);
26 :
27 : /* auth.sendCode#a677244f phone_number:string api_id:int api_hash:string
28 : * settings:CodeSettings */
29 22 : tl_write_uint32(&w, CRC_auth_sendCode);
30 22 : tl_write_string(&w, phone);
31 22 : tl_write_int32(&w, cfg->api_id);
32 22 : tl_write_string(&w, cfg->api_hash ? cfg->api_hash : "");
33 :
34 : /* codeSettings#ad253d78 flags:# (flags=0 → no special options) */
35 22 : tl_write_uint32(&w, CRC_codeSettings);
36 22 : tl_write_uint32(&w, 0); /* flags = 0 */
37 :
38 22 : if (w.len > max_len) {
39 0 : tl_writer_free(&w);
40 0 : return -1;
41 : }
42 22 : memcpy(out, w.data, w.len);
43 22 : *out_len = w.len;
44 22 : tl_writer_free(&w);
45 22 : return 0;
46 : }
47 :
48 : /* ---- Internal: read a sentCodeType sub-object, return its CRC ---- */
49 :
50 8 : static uint32_t read_sent_code_type(TlReader *r) {
51 8 : if (!tl_reader_ok(r)) return 0;
52 8 : uint32_t type_crc = tl_read_uint32(r);
53 :
54 8 : logger_log(LOG_INFO, "auth_send_code: sentCodeType=0x%08x", type_crc);
55 :
56 8 : switch (type_crc) {
57 6 : case CRC_auth_sentCodeTypeApp:
58 : case CRC_auth_sentCodeTypeSms:
59 : case CRC_auth_sentCodeTypeCall: {
60 6 : int32_t code_len = tl_read_int32(r);
61 6 : logger_log(LOG_INFO, "auth_send_code: sentCodeType code_length=%d", code_len);
62 6 : return type_crc;
63 : }
64 :
65 1 : case CRC_auth_sentCodeTypeFlashCall: {
66 1 : RAII_STRING char *pattern = tl_read_string(r);
67 : (void)pattern;
68 1 : return type_crc;
69 : }
70 :
71 1 : default:
72 1 : logger_log(LOG_WARN, "auth_send_code: unknown sentCodeType 0x%08x", type_crc);
73 1 : return 0;
74 : }
75 : }
76 :
77 : /* ---- auth_send_code ---- */
78 :
79 22 : int auth_send_code(const ApiConfig *cfg,
80 : MtProtoSession *s, Transport *t,
81 : const char *phone,
82 : AuthSentCode *out,
83 : RpcError *err) {
84 22 : if (!cfg || !s || !t || !phone || !out) return -1;
85 22 : if (err) { err->error_code = 0; err->error_msg[0] = '\0';
86 22 : err->migrate_dc = -1; err->flood_wait_secs = 0; }
87 :
88 : uint8_t query[4096];
89 22 : size_t qlen = 0;
90 22 : 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 22 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(65536);
96 22 : if (!resp) return -1;
97 22 : size_t resp_len = 0;
98 :
99 22 : if (api_call(cfg, s, t, query, qlen, resp, 65536, &resp_len) != 0) {
100 0 : logger_log(LOG_ERROR, "auth_send_code: api_call failed");
101 0 : return -1;
102 : }
103 :
104 22 : 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 22 : memcpy(&constructor, resp, 4);
112 22 : if (constructor == TL_rpc_error) {
113 : RpcError perr;
114 13 : rpc_parse_error(resp, resp_len, &perr);
115 13 : logger_log(LOG_ERROR, "auth_send_code: RPC error %d: %s",
116 : perr.error_code, perr.error_msg);
117 13 : if (err) *err = perr;
118 13 : return -1;
119 : }
120 :
121 9 : if (constructor != CRC_auth_sentCode) {
122 1 : logger_log(LOG_ERROR, "auth_send_code: unexpected constructor 0x%08x",
123 : constructor);
124 1 : 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 8 : TlReader r = tl_reader_init(resp, resp_len);
131 8 : tl_read_uint32(&r); /* skip constructor */
132 :
133 8 : uint32_t flags = tl_read_uint32(&r);
134 :
135 8 : out->code_type = read_sent_code_type(&r);
136 8 : if (out->code_type == 0) {
137 1 : logger_log(LOG_ERROR, "auth_send_code: failed to parse sentCodeType");
138 1 : return -1;
139 : }
140 :
141 14 : RAII_STRING char *hash = tl_read_string(&r);
142 7 : if (!hash) {
143 0 : logger_log(LOG_ERROR, "auth_send_code: failed to read phone_code_hash");
144 0 : return -1;
145 : }
146 :
147 7 : size_t hash_len = strlen(hash);
148 7 : 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 7 : memcpy(out->phone_code_hash, hash, hash_len + 1);
153 :
154 : /* next_type: flags.1 */
155 7 : out->next_type = 0;
156 7 : if (flags & (1u << 1)) out->next_type = tl_read_uint32(&r);
157 :
158 : /* timeout: flags.2 */
159 7 : out->timeout = 0;
160 7 : if (flags & (1u << 2)) out->timeout = tl_read_int32(&r);
161 :
162 7 : 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 7 : strlen(out->phone_code_hash), out->code_type,
166 : out->next_type, out->timeout);
167 7 : return 0;
168 : }
169 :
170 : /* ---- Internal: build auth.signIn request ---- */
171 :
172 14 : 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 14 : tl_writer_init(&w);
176 :
177 : /* auth.signIn#8d52a951 flags:# phone_number:string phone_code_hash:string
178 : * phone_code:flags.0?string */
179 14 : tl_write_uint32(&w, CRC_auth_signIn);
180 14 : tl_write_uint32(&w, 1u); /* flags = 0x1 → phone_code present */
181 14 : tl_write_string(&w, phone);
182 14 : tl_write_string(&w, hash);
183 14 : tl_write_string(&w, code); /* flags.0 is set */
184 :
185 14 : if (w.len > max_len) {
186 0 : tl_writer_free(&w);
187 0 : return -1;
188 : }
189 14 : memcpy(out, w.data, w.len);
190 14 : *out_len = w.len;
191 14 : tl_writer_free(&w);
192 14 : return 0;
193 : }
194 :
195 : /* ---- Internal: parse auth.authorization and extract user id ---- */
196 :
197 3 : static int parse_authorization(const uint8_t *resp, size_t resp_len,
198 : int64_t *user_id_out, const char *caller) {
199 3 : TlReader r = tl_reader_init(resp, resp_len);
200 3 : tl_read_uint32(&r); /* skip constructor */
201 3 : uint32_t flags = tl_read_uint32(&r);
202 :
203 3 : if (flags & (1u << 1)) tl_read_int32(&r); /* otherwise_relogin_days */
204 :
205 3 : uint32_t user_crc = tl_read_uint32(&r);
206 3 : if (user_crc != TL_user && user_crc != TL_user2 && user_crc != TL_userFull) {
207 1 : logger_log(LOG_WARN, "%s: unexpected user constructor 0x%08x",
208 : caller, user_crc);
209 1 : if (user_id_out) *user_id_out = 0;
210 1 : return 0;
211 : }
212 :
213 2 : tl_read_uint32(&r); /* user flags */
214 2 : int64_t uid = tl_read_int64(&r);
215 2 : if (user_id_out) *user_id_out = uid;
216 2 : logger_log(LOG_INFO, "%s: authenticated as user_id=%lld",
217 : caller, (long long)uid);
218 2 : return 0;
219 : }
220 :
221 : /* ---- auth_sign_in ---- */
222 :
223 14 : 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 14 : if (!cfg || !s || !t || !phone || !phone_code_hash || !code) return -1;
232 14 : if (signup_required) *signup_required = 0;
233 14 : if (err) { err->error_code = 0; err->error_msg[0] = '\0';
234 14 : err->migrate_dc = -1; err->flood_wait_secs = 0; }
235 :
236 : uint8_t query[4096];
237 14 : size_t qlen = 0;
238 14 : 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 14 : logger_log(LOG_INFO, "auth_sign_in: sending request");
245 :
246 14 : RAII_STRING uint8_t *resp = (uint8_t *)malloc(65536);
247 14 : if (!resp) return -1;
248 14 : size_t resp_len = 0;
249 :
250 14 : if (api_call(cfg, s, t, query, qlen, resp, 65536, &resp_len) != 0) {
251 0 : logger_log(LOG_ERROR, "auth_sign_in: api_call failed");
252 0 : return -1;
253 : }
254 :
255 14 : 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 14 : memcpy(&constructor, resp, 4);
262 :
263 14 : if (constructor == TL_rpc_error) {
264 : RpcError perr;
265 9 : rpc_parse_error(resp, resp_len, &perr);
266 9 : logger_log(LOG_ERROR, "auth_sign_in: RPC error %d: %s",
267 : perr.error_code, perr.error_msg);
268 9 : if (err) *err = perr;
269 9 : return -1;
270 : }
271 :
272 5 : if (constructor == CRC_auth_authorizationSignUpRequired) {
273 1 : logger_log(LOG_INFO, "auth_sign_in: sign-up required for this phone");
274 1 : if (signup_required) *signup_required = 1;
275 1 : return -1;
276 : }
277 :
278 4 : if (constructor != CRC_auth_authorization) {
279 1 : logger_log(LOG_ERROR, "auth_sign_in: unexpected constructor 0x%08x",
280 : constructor);
281 1 : return -1;
282 : }
283 :
284 3 : 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 : }
|