#include #define _POSIX_C_SOURCE 200112 #include #include #include #include #include #include #include #define RET_OK 0 #define RET_ERR 1 struct http_response { char* data; size_t length; }; static size_t write_data(void *data, size_t size, size_t nitems, void* user_data) { size_t total_size = size * nitems; struct http_response *response = (struct http_response*)user_data; response->data = (char*)realloc(response->data, response->length + total_size + 1); if (!response->data) { syslog(LOG_CRIT, "ran out of memory while reallocating http response structure!"); return 0; } memcpy(response->data + response->length, data, total_size); response->length += total_size; response->data[response->length] = 0; return total_size; } char* get_post_body(const char* user, const char* hostname) { const char* json_format = "{\"origin\": \"%s\", \"user\": \"%s\"}"; int sz = snprintf(NULL, 0, json_format, hostname, user) + 1; char* json = (char*)malloc(sz); int res = snprintf(json, sz, json_format, hostname, user); if (res < 0) { perror("snprintf"); free(json); return NULL; } return json; } int request_auth(pam_handle_t *pamh, char* post_body, struct http_response* resp) { const char url_request[] = "https://wnt.segfault.party/request_auth"; struct curl_slist* header_list = curl_slist_append(NULL, "Content-Type: application/json"); CURL* curl_handle = NULL; CURLcode res; int retval = RET_ERR; if (!(curl_handle = curl_easy_init())) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_URL, url_request); if (res != CURLE_OK) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); if (res != CURLE_OK) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "pam_telegram/0.1"); if (res != CURLE_OK) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data); if (res != CURLE_OK) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_POST, 1L); if (res != CURLE_OK) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_POSTFIELDS, post_body); if (res != CURLE_OK) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_HTTPHEADER, header_list); if (res != CURLE_OK) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void*)resp); if (res != CURLE_OK) goto cleanup; res = curl_easy_perform(curl_handle); if (res != CURLE_OK) { pam_syslog(pamh, LOG_ERR, "curl_easy_perform failed: %s", curl_easy_strerror(res)); goto cleanup; } retval = RET_OK; cleanup: if (curl_handle) curl_easy_cleanup(curl_handle); if (header_list) curl_slist_free_all(header_list); return retval; } int poll_authorization_status(pam_handle_t *pamh, char* poll_url, struct http_response *resp) { CURL* curl_handle = NULL; CURLcode res; int retval = RET_ERR; if (!(curl_handle = curl_easy_init())) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_URL, poll_url); if (res != CURLE_OK) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_FOLLOWLOCATION, 1); if (res != CURLE_OK) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_USERAGENT, "pam_telegram/0.1"); if (res != CURLE_OK) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_WRITEFUNCTION, write_data); if (res != CURLE_OK) goto cleanup; res = curl_easy_setopt(curl_handle, CURLOPT_WRITEDATA, (void*)resp); if (res != CURLE_OK) goto cleanup; res = curl_easy_perform(curl_handle); if (res != CURLE_OK) { pam_syslog(pamh, LOG_ERR, "curl_easy_perform failed: %s", curl_easy_strerror(res)); goto cleanup; } retval = RET_OK; cleanup: if (curl_handle) curl_easy_cleanup(curl_handle); return retval; } int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { int pam_retval = PAM_AUTH_ERR; int res; int sz; const char url_poll_base[] = "https://wnt.segfault.party/get_auth_status"; char* url_poll = NULL; struct http_response id_resp = {0}; struct http_response poll_resp = {0}; const char *user; char hostname[HOST_NAME_MAX] = ""; char* json = NULL; openlog(NULL, LOG_PID, LOG_AUTHPRIV); pam_info(pamh, "Waiting for Telegram authorization..."); gethostname(hostname, HOST_NAME_MAX); pam_get_user(pamh, &user, NULL); json = get_post_body(user, hostname); if (!json) goto cleanup; // if any of these fail, fail authentication. pam_retval = PAM_AUTH_ERR; res = request_auth(pamh, json, &id_resp); if (res != RET_OK) goto cleanup; // We've got a request id to poll. pam_syslog(pamh, LOG_INFO, "request_auth returned request id %s, starting polling...", id_resp.data); pam_info(pamh, "Please authorize the login request with id `%s'", id_resp.data); sz = snprintf(NULL, 0, "%s?request_id=%s", url_poll_base, id_resp.data) + 1; url_poll = (char*) malloc(sz); res = snprintf(url_poll, sz, "%s?request_id=%s", url_poll_base, id_resp.data); while (1) { res = poll_authorization_status(pamh, url_poll, &poll_resp); if (res != RET_OK) break; if (strcmp(poll_resp.data, "authorized") == 0) { pam_retval = PAM_SUCCESS; break; } else if (strcmp(poll_resp.data, "unauthorized") == 0) { pam_retval = PAM_AUTH_ERR; break; } // else continue polling free(poll_resp.data); poll_resp = (struct http_response){}; sleep(2); } pam_syslog(pamh, LOG_INFO, "polling returned result: %s", poll_resp.data); cleanup: if (json) free(json); if (url_poll) free(url_poll); if (poll_resp.data) free(poll_resp.data); if (id_resp.data) free(id_resp.data); return pam_retval; } int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) { // there are no credentials to speak of, per se pam_syslog(pamh, LOG_INFO, "pam_sm_setcred called."); return PAM_SUCCESS; }