| Line | Branch | Exec | Source |
|---|---|---|---|
| 1 | #include <glib.h> | ||
| 2 | #include <stdbool.h> | ||
| 3 | #include <stdio.h> | ||
| 4 | #include <string.h> | ||
| 5 | #include <time.h> | ||
| 6 | #include "api/internal.h" | ||
| 7 | #include "config/config.h" | ||
| 8 | #include "login/login.h" | ||
| 9 | |||
| 10 | static error_t parse_opt(int key, char* arg, struct argp_state* state); | ||
| 11 | |||
| 12 | struct login_arguments { | ||
| 13 | struct global_arguments* global; | ||
| 14 | }; | ||
| 15 | |||
| 16 | static struct argp_option options[] = {{NULL}}; | ||
| 17 | |||
| 18 | static char doc[] = | ||
| 19 | "Login to GitTor. Prompts for email/username and password, then saves the " | ||
| 20 | "returned authentication token in the global config."; | ||
| 21 | |||
| 22 | static struct argp argp = {options, parse_opt, "", doc, NULL, NULL, NULL}; | ||
| 23 | |||
| 24 | ✗ | static error_t parse_opt(int key, | |
| 25 | __attribute__((__unused__)) char* arg, | ||
| 26 | __attribute__((__unused__)) struct argp_state* state) { | ||
| 27 | switch (key) { | ||
| 28 | default: | ||
| 29 | ✗ | return ARGP_ERR_UNKNOWN; | |
| 30 | } | ||
| 31 | |||
| 32 | return 0; | ||
| 33 | } | ||
| 34 | |||
| 35 | ✗ | extern int gittor_login(struct argp_state* state) { | |
| 36 | // Set defaults arguments for login argumnets | ||
| 37 | ✗ | struct login_arguments args = {0}; | |
| 38 | |||
| 39 | // Prepare arguments array for just login | ||
| 40 | ✗ | int argc = state->argc - state->next + 1; | |
| 41 | ✗ | char** argv = &state->argv[state->next - 1]; | |
| 42 | ✗ | args.global = state->input; | |
| 43 | |||
| 44 | // Change the command name to gittor login | ||
| 45 | ✗ | const char name[] = "login"; | |
| 46 | ✗ | size_t argv0len = strlen(state->name) + sizeof(name) + 1; | |
| 47 | ✗ | char* argv0 = argv[0]; | |
| 48 | ✗ | argv[0] = malloc(argv0len); | |
| 49 | ✗ | g_snprintf(argv[0], argv0len, "%s %s", state->name, name); | |
| 50 | |||
| 51 | // Parse arguments | ||
| 52 | ✗ | int err = argp_parse(&argp, argc, argv, ARGP_NO_EXIT, 0, &args); | |
| 53 | ✗ | if (err) { | |
| 54 | ✗ | free(argv[0]); | |
| 55 | ✗ | argv[0] = argv0; | |
| 56 | ✗ | state->next += argc - 1; | |
| 57 | |||
| 58 | ✗ | return err; | |
| 59 | } | ||
| 60 | |||
| 61 | // Prompt for email/username | ||
| 62 | ✗ | char identifier[256] = {0}; | |
| 63 | ✗ | char password[73] = {0}; | |
| 64 | |||
| 65 | err = | ||
| 66 | ✗ | prompt_line("Email or username", false, identifier, sizeof(identifier)); | |
| 67 | ✗ | if (err) { | |
| 68 | ✗ | g_printerr("Email or username is required.\n"); | |
| 69 | |||
| 70 | ✗ | free(argv[0]); | |
| 71 | ✗ | argv[0] = argv0; | |
| 72 | ✗ | state->next += argc - 1; | |
| 73 | |||
| 74 | ✗ | return err; | |
| 75 | } | ||
| 76 | |||
| 77 | // Prompt for password | ||
| 78 | ✗ | err = prompt_line("Password", true, password, sizeof(password)); | |
| 79 | ✗ | if (err) { | |
| 80 | ✗ | g_printerr("Password is required.\n"); | |
| 81 | |||
| 82 | ✗ | secure_zero(password, sizeof(password)); | |
| 83 | ✗ | free(argv[0]); | |
| 84 | ✗ | argv[0] = argv0; | |
| 85 | ✗ | state->next += argc - 1; | |
| 86 | |||
| 87 | ✗ | return err; | |
| 88 | } | ||
| 89 | |||
| 90 | // Build the login DTO and clear the password | ||
| 91 | ✗ | login_dto_t login_dto = {0}; | |
| 92 | ✗ | if (strchr(identifier, '@')) { // check if it is an email or username | |
| 93 | ✗ | login_dto.email = g_strdup(identifier); | |
| 94 | ✗ | login_dto.username = NULL; | |
| 95 | } else { | ||
| 96 | ✗ | login_dto.username = g_strdup(identifier); | |
| 97 | ✗ | login_dto.email = NULL; | |
| 98 | } | ||
| 99 | ✗ | login_dto.password = g_strdup(password); | |
| 100 | ✗ | secure_zero(password, sizeof(password)); | |
| 101 | |||
| 102 | ✗ | CURL* curl = api_curl_handle_new(); | |
| 103 | ✗ | if (!curl) { | |
| 104 | ✗ | login_dto_free(&login_dto); | |
| 105 | |||
| 106 | ✗ | free(argv[0]); | |
| 107 | ✗ | argv[0] = argv0; | |
| 108 | ✗ | state->next += argc - 1; | |
| 109 | |||
| 110 | ✗ | return EIO; | |
| 111 | } | ||
| 112 | |||
| 113 | // Build the URL: /authenticate/login | ||
| 114 | ✗ | char url[512] = {0}; | |
| 115 | ✗ | if (api_build_url(url, sizeof(url), "/authenticate/login")) { | |
| 116 | ✗ | login_dto_free(&login_dto); | |
| 117 | ✗ | curl_easy_cleanup(curl); | |
| 118 | |||
| 119 | ✗ | free(argv[0]); | |
| 120 | ✗ | argv[0] = argv0; | |
| 121 | ✗ | state->next += argc - 1; | |
| 122 | |||
| 123 | ✗ | return EINVAL; | |
| 124 | } | ||
| 125 | |||
| 126 | // Serialize the login struct to JSON | ||
| 127 | ✗ | char* json_str = build_login_json(&login_dto); | |
| 128 | ✗ | if (!json_str) { | |
| 129 | ✗ | login_dto_free(&login_dto); | |
| 130 | ✗ | curl_easy_cleanup(curl); | |
| 131 | |||
| 132 | ✗ | free(argv[0]); | |
| 133 | ✗ | argv[0] = argv0; | |
| 134 | ✗ | state->next += argc - 1; | |
| 135 | |||
| 136 | ✗ | return EINVAL; | |
| 137 | } | ||
| 138 | |||
| 139 | // Set up headers and response buffer | ||
| 140 | ✗ | struct curl_slist* headers = NULL; | |
| 141 | ✗ | headers = curl_slist_append(headers, "Content-Type: application/json"); | |
| 142 | ✗ | headers = curl_slist_append(headers, "Accept: application/json"); | |
| 143 | |||
| 144 | ✗ | response_buf_t response = response_buf_init(); | |
| 145 | |||
| 146 | // Perform request | ||
| 147 | ✗ | curl_easy_setopt(curl, CURLOPT_URL, url); | |
| 148 | ✗ | curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); | |
| 149 | ✗ | curl_easy_setopt(curl, CURLOPT_POST, 1L); | |
| 150 | ✗ | curl_easy_setopt(curl, CURLOPT_POSTFIELDS, json_str); | |
| 151 | ✗ | curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_cb); | |
| 152 | ✗ | curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); | |
| 153 | |||
| 154 | ✗ | CURLcode res = curl_easy_perform(curl); | |
| 155 | ✗ | api_result_e check = api_check_response(curl, res); | |
| 156 | |||
| 157 | ✗ | login_dto_free(&login_dto); | |
| 158 | ✗ | curl_slist_free_all(headers); | |
| 159 | ✗ | curl_easy_cleanup(curl); | |
| 160 | ✗ | g_free(json_str); | |
| 161 | |||
| 162 | ✗ | if (check != API_OK) { | |
| 163 | ✗ | if (check == API_BAD_REQUEST) { | |
| 164 | ✗ | g_printerr("Invalid request.\n"); | |
| 165 | ✗ | } else if (check == API_FORBIDDEN) { | |
| 166 | ✗ | g_printerr("Invalid email/username or password.\n"); | |
| 167 | ✗ | } else if (check == API_SERVER_ERR) { | |
| 168 | ✗ | g_printerr("Server error occurred. Please try again later.\n"); | |
| 169 | ✗ | } else if (check == API_CURL_ERR) { | |
| 170 | ✗ | g_printerr("Network error occurred: %s\n", curl_easy_strerror(res)); | |
| 171 | } else { | ||
| 172 | ✗ | g_printerr("An unknown error occurred.\n"); | |
| 173 | } | ||
| 174 | |||
| 175 | ✗ | g_free(response.data); | |
| 176 | |||
| 177 | ✗ | free(argv[0]); | |
| 178 | ✗ | argv[0] = argv0; | |
| 179 | ✗ | state->next += argc - 1; | |
| 180 | |||
| 181 | ✗ | return EIO; | |
| 182 | } | ||
| 183 | |||
| 184 | // Parse the returned AuthenticationDto JSON | ||
| 185 | ✗ | authentication_dto_t* auth_dto = parse_authentication_json(response.data); | |
| 186 | ✗ | g_free(response.data); | |
| 187 | |||
| 188 | // Persist the authentication data in the global config | ||
| 189 | ✗ | config_id_t key_access_token = {.group = "auth", .key = "access_token"}; | |
| 190 | ✗ | config_id_t key_token_type = {.group = "auth", .key = "token_type"}; | |
| 191 | ✗ | config_id_t key_expires = {.group = "auth", .key = "expires"}; | |
| 192 | |||
| 193 | ✗ | if (config_set(CONFIG_SCOPE_GLOBAL, &key_access_token, | |
| 194 | ✗ | auth_dto->access_token) || | |
| 195 | ✗ | config_set(CONFIG_SCOPE_GLOBAL, &key_token_type, | |
| 196 | ✗ | auth_dto->token_type) || | |
| 197 | ✗ | config_set(CONFIG_SCOPE_GLOBAL, &key_expires, auth_dto->expires)) { | |
| 198 | ✗ | authentication_dto_free(auth_dto); | |
| 199 | |||
| 200 | ✗ | free(argv[0]); | |
| 201 | ✗ | argv[0] = argv0; | |
| 202 | ✗ | state->next += argc - 1; | |
| 203 | |||
| 204 | ✗ | return EIO; | |
| 205 | } | ||
| 206 | |||
| 207 | // Lock the config file permissions to be only accessible by the owner | ||
| 208 | ✗ | char* config_path = global_config_path(); | |
| 209 | ✗ | if (lock_file_permissions(config_path)) { | |
| 210 | ✗ | g_printerr( | |
| 211 | "Warning: Failed to set config file permissions. Please ensure " | ||
| 212 | "that %s is only accessible by the owner.\n", | ||
| 213 | config_path); | ||
| 214 | } | ||
| 215 | ✗ | g_free(config_path); | |
| 216 | |||
| 217 | // Check if the token is already expired | ||
| 218 | ✗ | time_t now = time(NULL); | |
| 219 | ✗ | time_t exp = 0; | |
| 220 | ✗ | if (parse_expiry_epoch(auth_dto->expires, &exp) == 0 && exp <= now) { | |
| 221 | ✗ | g_printerr( | |
| 222 | "Warning: Received an already expired token. Please log in " | ||
| 223 | "again.\n"); | ||
| 224 | ✗ | err = EAGAIN; | |
| 225 | } else { | ||
| 226 | ✗ | g_printerr("Login successful.\n"); | |
| 227 | } | ||
| 228 | |||
| 229 | ✗ | authentication_dto_free(auth_dto); | |
| 230 | |||
| 231 | // Reset back to global | ||
| 232 | ✗ | free(argv[0]); | |
| 233 | ✗ | argv[0] = argv0; | |
| 234 | ✗ | state->next += argc - 1; | |
| 235 | |||
| 236 | ✗ | return err; | |
| 237 | } | ||
| 238 |