diff --git a/Dockerfile b/Dockerfile index 149cb40..06b4bc6 100644 --- a/Dockerfile +++ b/Dockerfile @@ -10,7 +10,8 @@ RUN apt update && apt install -y \ libpam0g-dev \ libssl-dev \ libtool \ - pkg-config + pkg-config \ + uuid-dev WORKDIR /tmp RUN git clone https://github.com/benmcollins/libjwt && \ diff --git a/Makefile.am b/Makefile.am index f0958ff..574b8c7 100644 --- a/Makefile.am +++ b/Makefile.am @@ -10,7 +10,7 @@ libdir = $(PAMDIR) lib_LTLIBRARIES= pam_aad.la pam_aad_la_SOURCES = src/pam_aad.c -pam_aad_la_LIBADD = -lcurl -ljansson -ljwt -lpam -lsds +pam_aad_la_LIBADD = -lcurl -ljansson -ljwt -lpam -lsds -luuid pam_aad_la_CFLAGS = $(AM_CFLAGS) pam_aad_la_LDFLAGS = $(AM_LDFLAGS) $(MODULES_LDFLAGS) -export-symbols-regex "^pam_sm_" diff --git a/README.md b/README.md index 7f97573..885687e 100755 --- a/README.md +++ b/README.md @@ -28,10 +28,14 @@ Create the file ```/etc/pam_aad.conf``` and fill it with: ```mustache { "client": { - "id": "{{client_id}}" - }, - "domain": "{{domain}}", - "tenant": "{{organization}}.onmicrosoft.com>" + "id": "{{client_id}}" + }, + "domain": "{{domain}}", + "tenant": { + "name": "{{organization}}.onmicrosoft.com", + "address": "{{organization_email_address}}" + }, + "smtp_server": "{{smtp_server}}" } ``` diff --git a/src/pam_aad.c b/src/pam_aad.c index 74a4fdb..aa99cb7 100644 --- a/src/pam_aad.c +++ b/src/pam_aad.c @@ -8,297 +8,413 @@ #include #include #include +#include +#include #define AUTH_ERROR "authorization_pending" -#define CODE_PROMPT "Enter the following code at https://aka.ms/devicelogin: " -#define CONFIG_FILE "/etc/pam-test.conf" +#define CODE_PROMPT "Enter the following code at https://aka.ms/devicelogin : " +#define CONFIG_FILE "/etc/pam_aad.conf" #define HOST "https://login.microsoftonline.com/" #define RESOURCE_ID "00000002-0000-0000-c000-000000000000" +#define SUBJECT "Your one-time passcode for signing in via Azure Active Directory" #define TTW 5 /* time to wait in seconds */ #define USER_AGENT "azure_authenticator_pam/1.0" +struct message { + size_t lines_read; + char *data[]; +}; + struct response { - char *data; - size_t size; + char *data; + size_t size; }; struct ret_data { - const char *u_code, *d_code, *auth_bearer; + const char *u_code, *d_code, *auth_bearer; }; -static size_t response_callback(void *contents, size_t size, size_t nmemb, - void *userp) +static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *userp) { - size_t realsize = size * nmemb; - struct response *resp = (struct response *) userp; - char *ptr = realloc(resp->data, resp->size + realsize + 1); - if (ptr == NULL) { - // Out of memory - printf("Not enough memory (realloc returned NULL)\n"); + struct message *msg = (struct message*) userp; + char *data; - return 0; - } + if ((size == 0) || (nmemb == 0) || ((size * nmemb) < 1)) { + return 0; + } + data = msg->data[msg->lines_read]; - resp->data = ptr; - memcpy(&(resp->data[resp->size]), contents, realsize); - resp->size += realsize; - resp->data[resp->size] = 0; + if(data) { + size_t len = strlen(data); + memcpy(ptr, data, len); + msg->lines_read++; + return len; + } - return realsize; + return 0; } -static json_t *curl(const char *endpoint, const char *post_body, - const char *headers) +static size_t response_callback(void *contents, size_t size, size_t nmemb, + void *userp) { - CURL *curl; - CURLcode res; - json_t *data; - json_error_t error; - - struct response resp; - - resp.data = malloc(1); - resp.size = 0; - - curl = curl_easy_init(); - curl_easy_setopt(curl, CURLOPT_URL, endpoint); - curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_body); - curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, response_callback); - curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &resp); - curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); - curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L); - if (headers) - curl_easy_setopt(curl, CURLOPT_HEADER, headers); - - //curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); - - res = curl_easy_perform(curl); - - if (res != CURLE_OK) { - fprintf(stderr, "curl_easy_perform() failed: %s\n", - curl_easy_strerror(res)); - } else { - data = json_loads(resp.data, 0, &error); - - if (!data) { - fprintf(stderr, "json_loads() failed: %s\n", error.text); - - return NULL; + size_t realsize = size * nmemb; + struct response *resp = (struct response *) userp; + char *ptr = realloc(resp->data, resp->size + realsize + 1); + if (ptr == NULL) { + // Out of memory + printf("Not enough memory (realloc returned NULL)\n"); + return 0; } - } - curl_easy_cleanup(curl); - free(resp.data); + resp->data = ptr; + memcpy(&(resp->data[resp->size]), contents, realsize); + resp->size += realsize; + resp->data[resp->size] = 0; - return (data) ? data : NULL; + return realsize; } -static char *auth_bearer_request(struct ret_data *data, - const char *client_id, const char *tenant, - const char *code, json_t * json_data) +static char *get_date(void) { - const char *auth_bearer; - - sds endpoint = sdsnew(HOST); - endpoint = sdscat(endpoint, "common/oauth2/token"); + char *date; + int len = 56; + struct tm *tm_info; + time_t timer; + + date = (char *) malloc(len * sizeof(char)); + time(&timer); + tm_info = localtime(&timer); + /* RFC 822 POSIX time stamp */ + strftime(date, len, "Date: %a, %d %b %Y %H:%M:%S %z\r\n", tm_info); + return date; +} - sds post_body = sdsnew("resource=" RESOURCE_ID); - post_body = sdscat(post_body, "&code="); - post_body = sdscat(post_body, code); - post_body = sdscat(post_body, "&client_id="); - post_body = sdscat(post_body, client_id); - post_body = sdscat(post_body, "&grant_type=device_code"); +static char *get_message_id(void) +{ + char domainname[64], hostname[64], msg_id[37], *message_id; + int len = 192; + size_t i; + uuid_t uuid; + + message_id = (char *) malloc(len * sizeof(char)); + uuid_generate(uuid); + + for (i = 0; i < sizeof(uuid); i++) { + if(i == 0) { + snprintf(msg_id, sizeof(msg_id) - strlen(msg_id), "%02x", uuid[i]); + } else if(i == 3 || i == 5 || i == 7 || i == 9) { + snprintf(msg_id + strlen(msg_id), sizeof(msg_id) - strlen(msg_id) + 1, "%02x-", uuid[i]); + } else { + snprintf(msg_id + strlen(msg_id), sizeof(msg_id) - strlen(msg_id), "%02x", uuid[i]); + } + } - for (;;) { - nanosleep((const struct timespec[]) { { - TTW, 0}}, NULL); - json_data = curl(endpoint, post_body, NULL); + getdomainname(domainname, sizeof(domainname)); + gethostname(hostname, sizeof(hostname)); + snprintf(message_id, len, "Message-ID: <%s@%s.%s>\r\n", msg_id, hostname, domainname); + return message_id; +} - if (json_object_get(json_data, "access_token")) { - auth_bearer = - json_string_value(json_object_get - (json_data, "access_token")); +static json_t *curl(const char *endpoint, const char *post_body, + const char *headers) +{ + CURL *curl; + CURLcode res; + json_t *data; + json_error_t error; + + struct response resp; + + resp.data = malloc(1); + resp.size = 0; + + curl = curl_easy_init(); + curl_easy_setopt(curl, CURLOPT_URL, endpoint); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_body); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, response_callback); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *) &resp); + curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 1L); + curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 1L); + if (headers) + curl_easy_setopt(curl, CURLOPT_HEADER, headers); + + /* curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); */ + + res = curl_easy_perform(curl); + + if (res != CURLE_OK) { + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); } else { - auth_bearer = - json_string_value(json_object_get(json_data, "error")); - } + data = json_loads(resp.data, 0, &error); - if (strcmp(auth_bearer, AUTH_ERROR) != 0) - break; + if (!data) { + fprintf(stderr, "json_loads() failed: %s\n", error.text); - } + return NULL; + } + } - sdsfree(endpoint); - sdsfree(post_body); + curl_easy_cleanup(curl); + free(resp.data); - data->auth_bearer = auth_bearer; + return (data) ? data : NULL; } -static char *oauth_request(struct ret_data *data, const char *client_id, - const char *tenant, json_t * json_data) +static void auth_bearer_request(struct ret_data *data, + const char *client_id, const char *tenant, + const char *code, json_t * json_data) { - const char *d_code, *u_code; + const char *auth_bearer; + + sds endpoint = sdsnew(HOST); + endpoint = sdscat(endpoint, "common/oauth2/token"); + + sds post_body = sdsnew("resource=" RESOURCE_ID); + post_body = sdscat(post_body, "&code="); + post_body = sdscat(post_body, code); + post_body = sdscat(post_body, "&client_id="); + post_body = sdscat(post_body, client_id); + post_body = sdscat(post_body, "&grant_type=device_code"); + + for (;;) { + nanosleep((const struct timespec[]) { { + TTW, 0}}, NULL); + json_data = curl(endpoint, post_body, NULL); + + if (json_object_get(json_data, "access_token")) { + auth_bearer = + json_string_value(json_object_get + (json_data, "access_token")); + } else { + auth_bearer = + json_string_value(json_object_get(json_data, "error")); + } + + if (strcmp(auth_bearer, AUTH_ERROR) != 0) + break; - sds endpoint = sdsnew(HOST); - endpoint = sdscat(endpoint, tenant); - endpoint = sdscat(endpoint, "/oauth2/devicecode/"); + } - sds post_body = sdsnew("resource=" RESOURCE_ID); - post_body = sdscat(post_body, "&client_id="); - post_body = sdscat(post_body, client_id); - post_body = sdscat(post_body, "&scope=profile"); + sdsfree(endpoint); + sdsfree(post_body); - json_data = curl(endpoint, post_body, NULL); + data->auth_bearer = auth_bearer; +} - if (json_object_get(json_data, "device_code") - && json_object_get(json_data, "user_code")) { - d_code = - json_string_value(json_object_get(json_data, "device_code")); - u_code = - json_string_value(json_object_get(json_data, "user_code")); - } else { - fprintf(stderr, - "json_object_get() failed: device_code & user_code NULL\n"); +static void oauth_request(struct ret_data *data, const char *client_id, + const char *tenant, json_t * json_data) +{ + const char *d_code, *u_code; - exit(1); - } + sds endpoint = sdsnew(HOST); + endpoint = sdscat(endpoint, tenant); + endpoint = sdscat(endpoint, "/oauth2/devicecode/"); - data->d_code = d_code; - data->u_code = u_code; + sds post_body = sdsnew("resource=" RESOURCE_ID); + post_body = sdscat(post_body, "&client_id="); + post_body = sdscat(post_body, client_id); + post_body = sdscat(post_body, "&scope=profile"); - sdsfree(endpoint); - sdsfree(post_body); -} + json_data = curl(endpoint, post_body, NULL); -static int *verify_user(jwt_t * jwt, const char *user, const char *domain) -{ - const char *upn = jwt_get_grant(jwt, "upn"); + if (json_object_get(json_data, "device_code") + && json_object_get(json_data, "user_code")) { + d_code = + json_string_value(json_object_get(json_data, "device_code")); + u_code = + json_string_value(json_object_get(json_data, "user_code")); + } else { + fprintf(stderr, + "json_object_get() failed: device_code & user_code NULL\n"); - sds username = sdsnew(user); - username = sdscat(username, domain); + exit(1); + } - if (strcmp(upn, username) == 0) { - return 0; - } else { - return (int *) 1; - } + data->d_code = d_code; + data->u_code = u_code; + + sdsfree(endpoint); + sdsfree(post_body); } -static int converse(pam_handle_t * pamh, int nargs, - const struct pam_message **message, - struct pam_response **response) +static int verify_user(jwt_t * jwt, const char *username) { - struct pam_conv *conv; - int retval = pam_get_item(pamh, PAM_CONV, (void *) &conv); - if (retval != PAM_SUCCESS) - return retval; - return conv->conv(nargs, message, response, conv->appdata_ptr); + const char *upn = jwt_get_grant(jwt, "upn"); + return (strcmp(upn, username) == 0) ? EXIT_SUCCESS : EXIT_FAILURE; } -PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, - int argc, const char **argv) +static int notify_user(const char *to_addr, const char *from_addr, const char *message, const char *smtp_server) { - jwt_t *jwt; - - const char *username, *client_id, *tenant, - *domain, *u_code, *d_code, *ab_token; - - struct ret_data data; - json_t *json_data, *config; - json_error_t error; + CURL *curl; + CURLcode res = CURLE_OK; + struct curl_slist *recipients = NULL; + int msg_len = 9; + struct message *msg; + + sds to_str = sdsnew("To: "); + to_str = sdscat(to_str, to_addr); + to_str = sdscat(to_str, "\r\n"); + + sds from_str = sdsnew("From: "); + from_str = sdscat(from_str, from_addr); + from_str = sdscat(from_str, "\r\n"); + + sds msg_str = sdsempty(); + msg_str = sdscat(msg_str, message); + msg_str = sdscat(msg_str, "\r\n"); + + sds smtp_url = sdsempty(); + smtp_url = sdscat(smtp_url, "smtp://"); + smtp_url = sdscat(smtp_url, smtp_server); + + msg = malloc(sizeof(struct message) + (msg_len * sizeof(char*))); + msg->lines_read = 0; + + msg->data[0] = get_date(); + msg->data[1] = to_str; + msg->data[2] = from_str; + msg->data[3] = get_message_id(); + msg->data[4] = "Subject: " SUBJECT "\r\n"; + msg->data[5] = "\r\n"; + msg->data[6] = msg_str; + msg->data[7] = "\r\n"; + msg->data[8] = NULL; + + curl = curl_easy_init(); + if(curl) { + curl_easy_setopt(curl, CURLOPT_USE_SSL, (long)CURLUSESSL_ALL); + curl_easy_setopt(curl, CURLOPT_URL, smtp_url); + curl_easy_setopt(curl, CURLOPT_MAIL_FROM, from_addr); + recipients = curl_slist_append(recipients, to_addr); + curl_easy_setopt(curl, CURLOPT_MAIL_RCPT, recipients); + curl_easy_setopt(curl, CURLOPT_READFUNCTION, read_callback); + curl_easy_setopt(curl, CURLOPT_READDATA, msg); + curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); + + /* curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L); */ + + res = curl_easy_perform(curl); + + if(res != CURLE_OK) + fprintf(stderr, "curl_easy_perform() failed: %s\n", + curl_easy_strerror(res)); + + curl_slist_free_all(recipients); + curl_easy_cleanup(curl); + } - config = json_load_file(CONFIG_FILE, 0, &error); - if (!config) { - fprintf(stderr, "error: on line %d: %s\n", error.line, error.text); + free(msg->data[0]); + free(msg->data[3]); + return (int)res; +} - return PAM_AUTH_ERR; - } +PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags, + int argc, const char **argv) +{ + jwt_t *jwt; - if (json_object_get(json_object_get(config, "client"), "id")) { - client_id = - json_string_value(json_object_get - (json_object_get(config, "client"), "id")); - } else { - printf("Error with ID in JSON\n"); + const char *user, *client_id, *tenant, + *domain, *u_code, *d_code, *ab_token, *tenant_addr, *smtp_server; - return PAM_AUTH_ERR; - } + struct ret_data data; + json_t *json_data, *config; + json_error_t error; - if (json_object_get(config, "domain")) { - domain = json_string_value(json_object_get(config, "domain")); - } else { - printf("Error with Domain in JSON\n"); + config = json_load_file(CONFIG_FILE, 0, &error); + if (!config) { + fprintf(stderr, "error in config on line %d: %s\n", error.line, error.text); + return PAM_AUTH_ERR; + } - return PAM_AUTH_ERR; - } + if (json_object_get(json_object_get(config, "client"), "id")) { + client_id = + json_string_value(json_object_get + (json_object_get(config, "client"), "id")); + } else { + fprintf(stderr, "error with ID in JSON\n"); + return PAM_AUTH_ERR; + } - if (json_object_get(config, "tenant")) { - tenant = json_string_value(json_object_get(config, "tenant")); - } else { - printf("Error with tenant in JSON\n"); + if (json_object_get(config, "domain")) { + domain = json_string_value(json_object_get(config, "domain")); + } else { + fprintf(stderr, "error with Domain in JSON\n"); + return PAM_AUTH_ERR; + } - return PAM_AUTH_ERR; - } + if (json_object_get(config, "tenant")) { + tenant = json_string_value(json_object_get(json_object_get(config, "tenant"), "name")); + if(json_object_get(json_object_get(config, "tenant"), "address")) { + tenant_addr = json_string_value(json_object_get(json_object_get(config, "tenant"), "address")); + } else { + fprintf(stderr, "error with tenant address in JSON\n"); + return PAM_AUTH_ERR; + } + } else { + fprintf(stderr, "error with tenant in JSON\n"); + return PAM_AUTH_ERR; + } - if (pam_get_user(pamh, &username, NULL) != PAM_SUCCESS) { - printf("pam_get_user(): failed to get a username"); + if (json_object_get(config, "smtp_server")) { + smtp_server = json_string_value(json_object_get(config, "smtp_server")); + } else { + fprintf(stderr, "error with Domain in JSON\n"); + return PAM_AUTH_ERR; + } - return PAM_AUTH_ERR; - } + if (pam_get_user(pamh, &user, NULL) != PAM_SUCCESS) { + fprintf(stderr, "pam_get_user(): failed to get a username\n"); + return PAM_AUTH_ERR; + } - oauth_request(&data, client_id, tenant, json_data); + sds user_addr = sdsnew(user); + user_addr = sdscat(user_addr, "@"); + user_addr = sdscat(user_addr, domain); - u_code = data.u_code; - d_code = data.d_code; + curl_global_init(CURL_GLOBAL_ALL); - sds prompt = sdsnew(CODE_PROMPT); - prompt = sdscat(prompt, u_code); - prompt = sdscat(prompt, "\nPress enter to begin polling...\n"); + oauth_request(&data, client_id, tenant, json_data); - const struct pam_message msg = { - .msg_style = PAM_PROMPT_ECHO_OFF, - .msg = prompt - }; - const struct pam_message *msgs = &msg; - struct pam_response *resp = NULL; + u_code = data.u_code; + d_code = data.d_code; - converse(pamh, 1, &msgs, &resp); + sds prompt = sdsnew(CODE_PROMPT); + prompt = sdscat(prompt, u_code); + notify_user(user_addr, tenant_addr, prompt, smtp_server); - sdsfree(prompt); + auth_bearer_request(&data, client_id, tenant, d_code, json_data); - auth_bearer_request(&data, client_id, tenant, d_code, json_data); - ab_token = data.auth_bearer; - jwt_decode(&jwt, ab_token, NULL, 0); + curl_global_cleanup(); - if (verify_user(jwt, username, domain) == 0) { - printf("Username supplied matches UPN! Success!\n"); + ab_token = data.auth_bearer; + jwt_decode(&jwt, ab_token, NULL, 0); - json_decref(json_data); - json_decref(config); - jwt_free(jwt); - return PAM_SUCCESS; - } else { - printf("Imposter detected! Failure!\n"); + if (verify_user(jwt, user_addr) == 0) { + json_decref(json_data); + json_decref(config); + jwt_free(jwt); + return PAM_SUCCESS; + } else { + json_decref(json_data); + json_decref(config); + jwt_free(jwt); + return PAM_AUTH_ERR; + } - json_decref(json_data); - json_decref(config); - jwt_free(jwt); return PAM_AUTH_ERR; - } - - return PAM_AUTH_ERR; } PAM_EXTERN int pam_sm_setcred(pam_handle_t * pamh, int flags, - int argc, const char **argv) + int argc, const char **argv) { - return PAM_SUCCESS; + return PAM_SUCCESS; } PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, - int argc, const char **argv) + int argc, const char **argv) { - return PAM_SUCCESS; + return PAM_SUCCESS; } diff --git a/test/dlopentest.c b/test/dlopentest.c index 383dbdf..4e5c1eb 100644 --- a/test/dlopentest.c +++ b/test/dlopentest.c @@ -16,17 +16,17 @@ int main(int argc, const char *argv[]) void *libhandle = dlopen(argv[1], RTLD_NOW); if (libhandle == NULL) { - printf("%s", dlerror()); + printf("%s\n", dlerror()); exit(1); } void *sym = dlsym(libhandle, "pam_sm_authenticate"); if (sym == NULL) { - printf("%s", dlerror()); + printf("%s\n", dlerror()); exit(1); } sym = dlsym(libhandle, "pam_sm_setcred"); if (sym == NULL) { - printf("%s", dlerror()); + printf("%s\n", dlerror()); exit(1); } dlclose(libhandle);