#include #include #include #include #include #include #include #include #include #include #include #include struct td_callback { struct td_callback *next; long long request_id; void (*cb)(bool, struct TdObject *, struct TdError *); }; int td = -1; long long my_id = -1; static struct td_callback *cbs; /** * Last request_id. Increased whenever sending a query. * * Write: any * Read: any */ static atomic_llong last_req_id = 0; /** * Set to true by main thread when received authorizationStateClosed or authorizationStateClosing. * Stop accepting new queries. * * Write: main * Read: any */ bool closing = false; static bool sighandler_setup = false; static pthread_t thread_sighandler; static void tg_close(); static int td_send(void *func, void (*cb)(bool, struct TdObject *, struct TdError *)); static void fetal_cb(bool successful, struct TdObject *result, struct TdError *error); /** * Used for sigwait(2). */ sigset_t set; static int read_input(const char *prompt, char **buf, bool secure) { printf("%s", prompt); struct termios oldt, newt; if (secure) { tcgetattr(0, &oldt); newt = oldt; newt.c_lflag &= ~(ECHO); tcsetattr(0, TCSANOW, &newt); } size_t len = 0; ssize_t read = getline(buf, &len, stdin); if (secure) { printf("\n"); /* Manually insert a new line */ tcsetattr(0, TCSANOW, &oldt); } if (read == -1) { int r = errno; fprintf(stderr, "Cannot read input: %s\n", strerror(r)); return r; } (*buf)[read - 1] = '\0'; /* Remove \n */ return 0; } static void *main_sighandler(void *arg) { pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL); int r; int sig; while (true) { r = sigwait(&set, &sig); if (r) { fprintf(stderr, "Cannot call sigwait(): %d.\n", r); goto cleanup; } switch (sig) { case SIGINT: case SIGTERM: if (td == -1) goto cleanup; tg_close(); goto cleanup; default: break; } } cleanup: pthread_exit(NULL); } static int tdcb_push(long long request_id, void (*cb)(bool, struct TdObject *, struct TdError *)) { struct td_callback *current_ptr = malloc(sizeof(struct td_callback)); if (current_ptr == NULL) { int r = errno; fprintf(stderr, "Cannot allocate memory: %s\n", strerror(r)); return r; } current_ptr->next = NULL; current_ptr->request_id = request_id; current_ptr->cb = cb; if (cbs == NULL) { cbs = current_ptr; } else { current_ptr->next = cbs; cbs = current_ptr; } return 0; } static int td_send(void *func, void (*cb)(bool, struct TdObject *, struct TdError *)) { if (closing) { TdDestroyObjectFunction((struct TdFunction *) func); return 0; } if (last_req_id == LLONG_MAX) last_req_id = 0; last_req_id++; int r; if (cb != NULL && (r = tdcb_push(last_req_id, cb))) { return r; } TdCClientSend(td, (struct TdRequest) { last_req_id, (struct TdFunction *) func }); return 0; } static void tdcb_call(long long request_id, bool successful, struct TdObject *result, struct TdError *error) { if (cbs == NULL) return; struct td_callback *current_ptr = cbs; bool node_found = false; while (current_ptr != NULL) { if (current_ptr->request_id == request_id) { node_found = true; current_ptr->cb(successful, result, error); if (error != NULL && current_ptr->cb == &fetal_cb) { /* The fetal_cb callback does not print anything */ /* because it does not know the request_id. */ fprintf(stderr, "Error: Request %lld: %s (%d)\n", request_id, error->message_, error->code_); } break; } current_ptr = current_ptr->next; } if (node_found) { /* * The callback function may insert nodes to the link. * Therefore, we do the iteration again after calling the function to * delete the current one. * Need a better implementation. */ current_ptr = cbs; struct td_callback *prev_ptr = NULL; while (current_ptr != NULL) { if (current_ptr->request_id == request_id) { if (prev_ptr == NULL) cbs = current_ptr->next; else prev_ptr->next = current_ptr->next; free(current_ptr); return; } prev_ptr = current_ptr; current_ptr = current_ptr->next; } } if (error != NULL) { /* No callback found, Display default error message. */ fprintf(stderr, "Error: Request %lld: %s (%d)\n", request_id, error->message_, error->code_); } } static void tdcb_free() { struct td_callback *current_ptr = cbs; while (current_ptr != NULL) { struct td_callback *bak = current_ptr; current_ptr = current_ptr->next; free(bak); } cbs = NULL; } static void tg_close() { if (td == -1) return; td_send(TdCreateObjectClose(), NULL); } static void fetal_cb(bool successful, struct TdObject *result, struct TdError *error) { if (!successful) { tg_close(); } } static void auth_phone(bool successful, struct TdObject *result, struct TdError *error) { if (result) TdDestroyObjectObject(result); if (closing || successful) return; if (error != NULL) { /* error == NULL when caused from update handler */ fprintf(stderr, "Invalid phone number: %s (%d)\n", error->message_, error->code_); } char *phone = NULL; if (read_input("Phone number: ", &phone, false)) { tg_close(); return; } td_send(TdCreateObjectSetAuthenticationPhoneNumber(phone, TdCreateObjectPhoneNumberAuthenticationSettings( false, false, false, false, (struct TdVectorString *) TdCreateObjectVectorObject(0, NULL))), &auth_phone); free(phone); } static void auth_code(bool successful, struct TdObject *result, struct TdError *error) { if (result) TdDestroyObjectObject(result); if (closing || successful) return; if (error != NULL) { /* error == NULL when caused from update handler */ fprintf(stderr, "Invalid verification code: %s (%d)\n", error->message_, error->code_); } char *code = NULL; if (read_input("Verification code: ", &code, false)) { tg_close(); return; } td_send(TdCreateObjectCheckAuthenticationCode(code), &auth_code); free(code); } static void auth_pass(bool successful, struct TdObject *result, struct TdError *error) { if (closing || successful) return; if (error != NULL) { /* error == NULL when caused from update handler */ fprintf(stderr, "Invalid password: %s (%d)\n", error->message_, error->code_); } printf("Password "); if (result != NULL && result->ID == CODE_AuthorizationStateWaitPassword) { struct TdAuthorizationStateWaitPassword *waitPassword = (struct TdAuthorizationStateWaitPassword *) result; printf("(Hint: %s)", waitPassword->password_hint_); } /* The result is owned by the main loop -- It will be freed there. */ printf(": "); char *password = NULL; if (read_input("", &password, true)) { tg_close(); return; } td_send(TdCreateObjectCheckAuthenticationPassword(password), &auth_pass); free(password); } static void handle_auth(const struct TdUpdateAuthorizationState *update) { switch (update->authorization_state_->ID) { case CODE_AuthorizationStateWaitTdlibParameters: td_send(TdCreateObjectSetTdlibParameters(TdCreateObjectTdlibParameters( false, "./td/", NULL, false, false, false, false, 72527, "ba974de6a156b1bf310286ceff85b3e6", "en", "Desktop", "0.0", "Test", false, true )), &fetal_cb); break; case CODE_AuthorizationStateWaitPhoneNumber: { auth_phone(false, NULL, NULL); break; } case CODE_AuthorizationStateWaitCode: { auth_code(false, NULL, NULL); break; } case CODE_AuthorizationStateWaitPassword: { auth_pass(false, (struct TdObject *) update->authorization_state_, NULL); break; } case CODE_AuthorizationStateWaitEncryptionKey: { td_send(TdCreateObjectCheckDatabaseEncryptionKey(TdCreateObjectBytes((unsigned char *) {0x0}, 0)), &fetal_cb); break; } case CODE_AuthorizationStateReady: { printf("System operational.\n"); break; } /* Closed state is handled in the main loop. */ case CODE_AuthorizationStateLoggingOut: case CODE_AuthorizationStateClosing: { closing = true; break; } case CODE_AuthorizationStateWaitOtherDeviceConfirmation: { struct TdAuthorizationStateWaitOtherDeviceConfirmation *waitOtherDeviceConfirmation = (struct TdAuthorizationStateWaitOtherDeviceConfirmation *) update->authorization_state_; printf("Please scan the QR code of the following link using another Telegram seession:\n%s\n", waitOtherDeviceConfirmation->link_); break; } default: { fprintf(stderr, "Unsupported authorization state: %d. Aborted.\n", update->authorization_state_->ID); tg_close(); break; } } } static void handle_update(const struct TdUpdate *update) { switch (update->ID) { case CODE_UpdateAuthorizationState: handle_auth((struct TdUpdateAuthorizationState *) update); break; case CODE_UpdateUserStatus: { struct TdUpdateUserStatus *u = (struct TdUpdateUserStatus *) update; if (u->user_id_ != TARGET_UID) { break; } char buff[20]; time_t now = time(NULL); strftime(buff, 20, "%Y-%m-%d %H:%M:%S", localtime(&now)); char *type = NULL; char t1[40]; switch (u->status_->ID) { case CODE_UserStatusEmpty: type = "empty"; break; case CODE_UserStatusOnline: { struct TdUserStatusOnline *s = (struct TdUserStatusOnline *) u->status_; sprintf(t1, "online (expires %lu seconds later)", s->expires_ - ((unsigned long) time(NULL))); type = t1; break; } case CODE_UserStatusLastMonth: type = "offline (seen last month)"; break; case CODE_UserStatusLastWeek: type = "offline (seen last week)"; break; case CODE_UserStatusOffline: { struct TdUserStatusOffline *s = (struct TdUserStatusOffline *) u->status_; sprintf(t1, "offline (last seen %d)", s->was_online_); type = t1; break; } default: { type = "unknown"; break; } } assert(type); printf("%s %lld is %s.\n", buff, u->user_id_, type); break; } case CODE_UpdateConnectionState: { struct TdUpdateConnectionState *u = (struct TdUpdateConnectionState *) update; switch (u->state_->ID) { case CODE_ConnectionStateConnecting: printf("Connecting ...\n"); break; case CODE_ConnectionStateConnectingToProxy: printf("Connecting to proxy ...\n"); break; case CODE_ConnectionStateReady: printf("Connected.\n"); break; case CODE_ConnectionStateUpdating: printf("Updating ...\n"); break; case CODE_ConnectionStateWaitingForNetwork: printf("Waiting for network ...\n"); break; default: printf("Unknown connection state.\n"); break; } break; } } } int main(void) { int r; sigemptyset(&set); sigaddset(&set, SIGTERM); sigaddset(&set, SIGINT); sigaddset(&set, SIGUSR1); r = pthread_sigmask(SIG_BLOCK, &set, NULL); if (r) { fprintf(stderr, "Cannot call pthread_sigmask(): %d\n", r); return r; } r = pthread_create(&thread_sighandler, NULL, &main_sighandler, NULL); if (r) { fprintf(stderr, "Cannot call pthread_create(): %d\n", r); return r; } sighandler_setup = true; td = TdCClientCreateId(); TdDestroyObjectObject(TdCClientExecute((struct TdFunction *) TdCreateObjectSetLogVerbosityLevel(0))); td_send(TdCreateObjectGetOption("version"), &fetal_cb); struct TdResponse response; while (1) { response = TdCClientReceive(5); struct TdObject *obj = response.object; if (obj == NULL) continue; const bool is_update = response.request_id == 0; if (is_update && obj->ID == CODE_UpdateAuthorizationState && ((struct TdUpdate *) obj)->ID == CODE_UpdateAuthorizationState && ((struct TdUpdateAuthorizationState *) obj)->authorization_state_->ID == CODE_AuthorizationStateClosed) { closing = true; TdDestroyObjectObject(obj); goto cleanup; } if (is_update) { handle_update((struct TdUpdate *) obj); TdDestroyObjectObject(obj); continue; } switch (obj->ID) { case CODE_Ok: tdcb_call(response.request_id, true, NULL, NULL); break; case CODE_Error: { struct TdError *error = (struct TdError *) obj; tdcb_call(response.request_id, false, NULL, error); break; } default: tdcb_call(response.request_id, true, obj, NULL); break; } TdDestroyObjectObject(obj); } cleanup: if (sighandler_setup) { pthread_cancel(thread_sighandler); r = pthread_join(thread_sighandler, NULL); if (!r) sighandler_setup = false; } tdcb_free(); return 0; }