From e1887b523a1401c357ae2440bbb5eb96037a5c98 Mon Sep 17 00:00:00 2001 From: Trumeet Date: Fri, 7 Jan 2022 15:00:40 -0800 Subject: First Commit, add Monitor --- Monitor/CMakeLists.txt | 20 ++ Monitor/README.md | 1 + Monitor/main.c | 491 +++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 3 + 4 files changed, 515 insertions(+) create mode 100644 Monitor/CMakeLists.txt create mode 100644 Monitor/README.md create mode 100644 Monitor/main.c create mode 100644 README.md diff --git a/Monitor/CMakeLists.txt b/Monitor/CMakeLists.txt new file mode 100644 index 0000000..ed5004a --- /dev/null +++ b/Monitor/CMakeLists.txt @@ -0,0 +1,20 @@ +cmake_minimum_required(VERSION 3.21) +project(Monitor C CXX) + +set(CMAKE_C_STANDARD 99) +IF (CMAKE_BUILD_TYPE MATCHES Debug) + add_compile_definitions(TD_USE_ASAN) +ENDIF (CMAKE_BUILD_TYPE MATCHES Debug) +add_subdirectory(td) + +set(CMAKE_C_FLAGS_DEBUG + "${CMAKE_C_FLAGS_DEBUG} -g3 -O0 -fsanitize=address") +set(CMAKE_EXE_LINKER_FLAGS_DEBUG + "${CMAKE_EXE_LINKER_FLAGS_DEBUG} -fsanitize=address") +add_definitions(-D_POSIX_C_SOURCE=200809L) + +add_executable(Monitor + main.c + ) +target_include_directories(Monitor PUBLIC "${PROJECT_BINARY_DIR}") +target_link_libraries(Monitor PRIVATE tdc) diff --git a/Monitor/README.md b/Monitor/README.md new file mode 100644 index 0000000..d0167df --- /dev/null +++ b/Monitor/README.md @@ -0,0 +1 @@ +Monitor online / offline of a specific Telegram user. Used to help test one of my friend's userbot. diff --git a/Monitor/main.c b/Monitor/main.c new file mode 100644 index 0000000..a9036e2 --- /dev/null +++ b/Monitor/main.c @@ -0,0 +1,491 @@ +#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; +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..2549393 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# Gists + +Small temp projects -- cgit v1.2.3