rublon-ssh-old/SSH/PAM/src/coreHandler.c
2019-10-30 08:39:19 +01:00

481 lines
19 KiB
C

#ifndef _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include "../lib/cJSON.h"
#include "../lib/qrcodegen.h"
#include "misc.h"
#include <unistd.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <curl/curl.h>
#include <security/pam_appl.h>
#include <security/pam_modules.h>
struct MemoryStruct {
char *memory;
size_t size;
};
static size_t WriteMemoryCallback(void *contents, size_t size, size_t nmemb, void *userp) {
size_t realsize = size * nmemb;
struct MemoryStruct *mem = (struct MemoryStruct *)userp;
mem->memory = realloc(mem->memory, mem->size + realsize + 1);
if(mem->memory == NULL)
return 0;
memcpy(&(mem->memory[mem->size]), contents, realsize);
mem->size += realsize;
mem->memory[mem->size] = 0;
return realsize;
}
char *parseNestedJson(char source[], char name[], char indexName[]) {
const cJSON *value = NULL;
const cJSON *nestedValue = NULL;
cJSON *monitor_json = cJSON_Parse(source);
value = cJSON_GetObjectItemCaseSensitive(monitor_json, name);
nestedValue = cJSON_GetObjectItemCaseSensitive(value,indexName);
if(nestedValue)
return nestedValue->valuestring;
else
return NULL;
}
int parseNestedJsonInt(char source[], char name[], char indexName[]) {
const cJSON *value = NULL;
const cJSON *nestedValue = NULL;
cJSON *monitor_json = cJSON_Parse(source);
value = cJSON_GetObjectItemCaseSensitive(monitor_json, name);
nestedValue = cJSON_GetObjectItemCaseSensitive(value,indexName);
if(nestedValue)
return nestedValue->valueint;
else
return 0;
}
char *parseJson(char source[], char name[] ) {
const cJSON *value = NULL;
cJSON *monitor_json = cJSON_Parse(source);
value = cJSON_GetObjectItemCaseSensitive(monitor_json, name);
if(value)
return value->valuestring;
else
return NULL;
}
void printQr(const uint8_t qrcode[],pam_handle_t *pamh) {
char *qrCodeString = "";
char *white = "\u2588\u2588";
char *black = " ";
int size = qrcodegen_getSize(qrcode);
int border = 1;
int y;
//additional line above
for(y = -border; y < size + border + 2; y++){
asprintf(&qrCodeString,"%s%s", qrCodeString, white);
}
asprintf(&qrCodeString,"%s%s", qrCodeString, "\n");
for(y = -border; y < size + border; y++) {
int x;
asprintf(&qrCodeString,"%s%s",qrCodeString,white);
for(x = -border; x < size + border; x++)
asprintf(&qrCodeString,"%s%s",qrCodeString,qrcodegen_getModule(qrcode, x, y) ? black : white);
asprintf(&qrCodeString,"%s%s",qrCodeString,white);
asprintf(&qrCodeString,"%s%s",qrCodeString,"\n");
}
//additional line below
for(y = -border; y < size + border + 2; y++){
asprintf(&qrCodeString,"%s%s", qrCodeString, white);
}
asprintf(&qrCodeString,"%s%s",qrCodeString,"\n");
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "%s",qrCodeString);
}
void displayQrCode(pam_handle_t *pamh, char *qrToken) {
enum qrcodegen_Ecc errCorLvl = qrcodegen_Ecc_LOW;
uint8_t qrcode[qrcodegen_BUFFER_LEN_MAX];
uint8_t tempBuffer[qrcodegen_BUFFER_LEN_MAX];
bool ok = qrcodegen_encodeText(qrToken, tempBuffer, qrcode, errCorLvl, qrcodegen_VERSION_MIN, qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true);
if(ok)
printQr(qrcode, pamh);
}
void displayAvailableAuthenticationMethods(cJSON *methods, pam_handle_t *pamh) {
if(cJSON_GetArraySize(methods)) {
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "");
int i;
for (i = 0; i < cJSON_GetArraySize(methods); i++) {
if (strcmp(cJSON_GetArrayItem(methods, i)->valuestring,"email") == 0)
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "%d: Email Link",i+1);
if (strcmp(cJSON_GetArrayItem(methods, i)->valuestring,"qrcode") == 0)
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "%d: QR Code",i+1);
if (strcmp(cJSON_GetArrayItem(methods, i)->valuestring,"totp") == 0)
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "%d: Mobile TOTP",i+1);
if (strcmp(cJSON_GetArrayItem(methods, i)->valuestring,"push") == 0)
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "%d: Mobile Push",i+1);
if (strcmp(cJSON_GetArrayItem(methods, i)->valuestring,"sms") == 0)
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "%d: SMS code",i+1);
}
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "");
}
}
void selectMethodModule(pam_handle_t *pamh, cJSON *methods, char **selectedMethod) {
char *authentication = 0;
int idx = 0;
if(cJSON_GetArraySize(methods)) {
displayAvailableAuthenticationMethods(methods, pamh);
do{
if(cJSON_GetArraySize(methods) == 1) {
idx=1;
break;
}
pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &authentication, "Select method [1-%d]: ",cJSON_GetArraySize(methods));
idx = atoi(authentication);
}while(idx < 1 || idx > cJSON_GetArraySize(methods));
*selectedMethod=cJSON_GetArrayItem(methods, idx-1)->valuestring;
}
}
cJSON *returnAvailableMethods(cJSON *methods, pam_handle_t *pamh) {
int methodsCount = 5, i, jsonIndex;
char *supportedMethods[] = {"email","qrcode","totp","push","sms"};
cJSON* result = cJSON_CreateArray();
for (i= 0 ; i < methodsCount ; i++){
cJSON* foundMethod = cJSON_GetObjectItem(methods, supportedMethods[i]);
for (jsonIndex = 0 ; jsonIndex < cJSON_GetArraySize(methods) ; jsonIndex ++ ){
if (strcmp(cJSON_GetArrayItem(methods, jsonIndex)->valuestring, supportedMethods[i]) == 0){
foundMethod = cJSON_GetArrayItem(methods,jsonIndex);
break;
}
}
if (foundMethod){
cJSON_AddItemReferenceToArray(result, foundMethod);
}
}
return result;
}
void displaySelectedMethodMsg(pam_handle_t *pamh, char *selectedMethod) {
if (strcmp(selectedMethod, "email") == 0) {
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "\nWe have sent a verification link.\nUse this link to sign in to your account.");
} else if (strcmp(selectedMethod, "qrcode") == 0) {
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "\nScan QR code using Rublon Authenticator:");
} else if (strcmp(selectedMethod, "push") == 0) {
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "\nLogin request via Mobile Push sent to phone ...");
}
}
void displayExceptionStatus(pam_handle_t *pamh, char *exception) {
if (strcmp(exception, "UserBypassedException") == 0) {
pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, NULL, "User bypassed.");
} else if (strcmp(exception, "TransactionAccessTokenExpiredException") == 0 || strcmp(exception, "TransactionExpiredException") == 0) {
pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, NULL, "Session expired. Please try again.");
} else if (strcmp(exception, "TransactionIdExpiredException") == 0) {
pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, NULL, "Session finally expired.");
} else if (strcmp(exception, "SendMessageException") == 0) {
pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, NULL, "Cannot send SMS message. Please check your phone number or try again later.");
} else {
pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, NULL, "%s", exception);
}
}
bool transactionErrorException(pam_handle_t *pamh, char *curlResponse) {
char *exception = NULL;
exception = parseNestedJson(curlResponse,"result","exception");
if(exception != NULL) {
displayExceptionStatus(pamh, exception);
return true;
}
return false;
}
char *curlHandler(pam_handle_t *pamh, char *jsonObj, char *url, char *secretKey) {
CURL *curl;
CURLcode res, headerLength;
struct MemoryStruct chunks;
chunks.memory = malloc(1);
chunks.size = 0;
curl = curl_easy_init();
char *curlResponse, *responseXRublon;
char *xRublon;
asprintf(&xRublon,"X-Rublon-Signature: %s",signData(pamh,jsonObj, secretKey));
if(curl) {
struct curl_slist *chunk = NULL;
curl_easy_setopt(curl, CURLOPT_URL, url);
chunk = curl_slist_append(chunk, "Content-Type: application/json");
chunk = curl_slist_append(chunk, "Accept: application/json");
chunk = curl_slist_append(chunk, xRublon);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, chunk);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, jsonObj);
curl_easy_setopt(curl, CURLOPT_HEADER, 1);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteMemoryCallback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&chunks);
res = curl_easy_perform(curl);
if(res != CURLE_OK)
return NULL;
long size;
curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &size);
curlResponse = curlResponseJsonParser(pamh, size, chunks.memory);
responseXRublon = curlResponseSignatureParser(pamh, size, chunks.memory);
if(!verifyData(pamh, curlResponse, secretKey, responseXRublon)) {
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "Invalid Signature!");
return NULL;
}
curl_easy_cleanup(curl);
if(chunks.memory)
free(chunks.memory);
return curlResponse;
}
else
return NULL;
}
bool checkResult(char *curlResponse) {
cJSON *json = cJSON_Parse(curlResponse);
cJSON *result_json = cJSON_GetObjectItemCaseSensitive(json, "result");
cJSON_Delete(json);
if (cJSON_IsTrue(result_json) == 1)
return true;
return false;
}
bool isDigit(char *code) {
int i;
for (i=0;i<strlen(code); i++)
if (!isdigit(code[i]))
return false;
return true;
}
bool isOneOfSelectedMethods(char *selectedMethod, char *methods[], int methodsSize) {
int i;
for (i = 0; i < methodsSize; i++)
if (strcmp(selectedMethod,methods[i]) == 0)
return true;
return false;
}
int postInit(pam_handle_t *pamh, cJSON **availableMethods, struct ApplicationInfoStruct* info, char **transactionId, char *systemToken, char *secretKey, const char *appUserId, char *userEmail, char *rublonApiServer) {
char *status = NULL;
char *transactionStatus = NULL;
char *exception = NULL;
int gdprAccepted = 0;
int tosAccepted = 0;
char *policy = NULL;
char *tid = NULL;
char *jsonObj;
char *url;
asprintf(&jsonObj,"{\"systemToken\":\"%s\",\"appUserId\":\"%s\",\"userEmail\":\"%s\"}",systemToken,appUserId,userEmail);
asprintf(&url,"%s/api/transaction/init",rublonApiServer);
char *curlResponse = curlHandler(pamh, jsonObj, url, secretKey);
if(curlResponse == NULL)
return CONNECTION_ERROR;
status = parseJson(curlResponse,"status");
exception = parseNestedJson(curlResponse,"result","exception");
transactionStatus = parseNestedJson(curlResponse,"result","status");
*transactionId = parseNestedJson(curlResponse,"result","tid");
cJSON *resp = cJSON_GetObjectItem(cJSON_Parse(curlResponse),"result");
cJSON *methods = cJSON_GetObjectItem(resp,"methods");
gdprAccepted = parseNestedJsonInt(curlResponse,"result","gdprAccepted");
tosAccepted = parseNestedJsonInt(curlResponse,"result","tosAccepted");
*availableMethods = methods;
info->companyName = cJSON_GetObjectItem(resp, "companyName")->valuestring;
info->applicationName = cJSON_GetObjectItem(resp, "applicationName")->valuestring;
if(gdprAccepted != 1 || tosAccepted != 1) {
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "\nYou have to read and accept our:\n- Terms of Use: https://core.rublon.net/terms_of_use\n- Privacy Policy: https://core.rublon.net/privacy_policy\n");
do{
pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &policy, "I have read and agree Terms of Use and Privacy Policy (press Y): ");
}while(strcmp(policy,"Y") != 0 && strcmp(policy,"y") != 0);
}
if(transactionStatus != NULL && strcmp(transactionStatus,"waiting") == 0) {
return STATUS_WAITING;
}
if(strcmp(status,"ERROR") == 0 && strcmp(exception,"UserBypassedException") == 0)
return STATUS_BYPASS;
if(strcmp(status,"ERROR") == 0) {
if(transactionErrorException(pamh,curlResponse))
return STATUS_DENIED;
}
if(transactionStatus == NULL || transactionId == NULL) {
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "\nRublon Transaction Error!");
return STATUS_BYPASS;
}
if(strcmp(transactionStatus,"denied") == 0)
return STATUS_DENIED;
if(strcmp(transactionStatus,"pending") == 0)
return STATUS_PENDING;
return STATUS_UNKNOWN;
}
int postMethod(pam_handle_t *pamh, char *secretKey, char *tId, char *selectedMethod, char *rublonApiServer, char *systemToken, bool onlyOneMethod) {
char *status = NULL;
char *qrToken = NULL;
char *transactionId = NULL;
char *jsonObj;
char *url;
char *changeMethod = NULL;
asprintf(&jsonObj,"{\"systemToken\":\"%s\",\"tid\":\"%s\",\"method\":\"%s\",\"GDPRAccepted\":\"true\",\"tosAccepted\":\"true\"}",systemToken,tId,selectedMethod);
asprintf(&url,"%s/api/transaction/methodSSH",rublonApiServer);
char *curlResponse = curlHandler(pamh, jsonObj, url, secretKey);
if(curlResponse == NULL)
return CONNECTION_ERROR;
status = parseJson(curlResponse,"status");
qrToken = parseNestedJson(curlResponse,"result","token");
transactionId = parseNestedJson(curlResponse,"result","tid");
displaySelectedMethodMsg(pamh, selectedMethod);
if(qrToken != NULL)
displayQrCode(pamh, qrToken);
char *methodsToCompare[] = {"email","push","qrcode"};
if(isOneOfSelectedMethods(selectedMethod, methodsToCompare, 3)) {
if(!onlyOneMethod) {
do {
pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &changeMethod, "Press [Enter] key to continue (or enter 0 to select different method): ");
if(strcmp(changeMethod,"0") == 0)
return CHANGE_METHOD;
}while(strlen(changeMethod) != 0);
}
else
pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, NULL, "Press enter to continue.");
}
if(strcmp(status,"ERROR") == 0) {
if(transactionErrorException(pamh,curlResponse))
return STATUS_DENIED;
}
if(transactionId == NULL) {
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "\nRublon Transaction Error!");
return STATUS_BYPASS;
}
if(strcmp(status,"OK") == 0) {
return STATUS_PENDING;
}
return STATUS_UNKNOWN;
}
int postConfirmCode(pam_handle_t *pamh, char *secretKey, char *systemToken, char *transactionId, char *selectedMethod, char *rublonApiServer, bool onlyOneMethod) {
char *status = NULL;
char *exception = NULL;
bool result = false;
char *code;
char *jsonObj;
char *url;
bool firstAttempt = true;
do {
if(!firstAttempt)
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "Invalid passcode. Try again!");
firstAttempt = false;
if(onlyOneMethod)
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "\nEnter passcode");
else
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "\nEnter passcode (or 0 to select different method):");
if(strcmp(selectedMethod,"totp") == 0)
pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &code, "Mobile TOTP from Rublon Authenticator:");
else if(strcmp(selectedMethod,"sms") == 0)
pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &code, "SMS passcode: ");
else
return CONNECTION_ERROR;
if(strcmp(code,"0") == 0 && !onlyOneMethod)
return CHANGE_METHOD;
while(strlen(code) != TOTP_SMS_INPUT_CODE_SIZE || !isDigit(code)){
pam_prompt(pamh, PAM_PROMPT_ECHO_ON, &code, "Mobile TOTP must contain 6 digits: ");
if(strcmp(code,"0") == 0)
return CHANGE_METHOD;
};
asprintf(&jsonObj,"{\"systemToken\":\"%s\",\"tid\":\"%s\",\"vericode\":\"%s\"}",systemToken,transactionId,code);
asprintf(&url,"%s/api/transaction/confirmCode",rublonApiServer);
char *curlResponse = curlHandler(pamh, jsonObj, url, secretKey);
if(curlResponse == NULL)
return CONNECTION_ERROR;
result = checkResult(curlResponse);
status = parseJson(curlResponse,"status");
exception = parseNestedJson(curlResponse,"result","exception");
if(strcmp(status,"ERROR") == 0) {
if(strcmp(exception, "TransactionAccessTokenExpiredException") == 0 || transactionErrorException(pamh,curlResponse))
return STATUS_DENIED;
}
if(status == NULL) {
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "\nRublon Transaction Error!");
return STATUS_BYPASS;
}
}while(result == false);
if(strcmp(status,"OK") == 0)
return STATUS_PENDING;
return STATUS_UNKNOWN;
}
int postVerifySSH(pam_handle_t *pamh, char *secretKey, char *transactionId, char *selectedMethod, char *rublonApiServer, char *systemToken, char **accessToken) {
char *jsonObj;
char *url;
char *status;
char *exception;
asprintf(&jsonObj,"{\"tid\":\"%s\",\"systemToken\":\"%s\"}",transactionId, systemToken);
asprintf(&url,"%s/api/transaction/verifySSH",rublonApiServer);
char *curlResponse = curlHandler(pamh, jsonObj, url, secretKey);
if(curlResponse == NULL)
return CONNECTION_ERROR;
status = parseJson(curlResponse,"status");
exception = parseNestedJson(curlResponse,"result","exception");
*accessToken = parseNestedJson(curlResponse,"result","token");
if(strcmp(status,"ERROR") == 0) {
if(transactionErrorException(pamh,curlResponse))
return STATUS_DENIED;
}
if(accessToken == NULL) {
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "\nRublon Transaction Error!");
return STATUS_BYPASS;
}
if(strcmp(status,"OK") == 0)
return STATUS_PENDING;
return STATUS_UNKNOWN;
}
int postCredentials(pam_handle_t *pamh, char *systemToken, char *accessToken, char *rublonApiServer, char *secretKey) {
char *status = NULL;
char *answer = NULL;
char *jsonObj;
char *url;
asprintf(&jsonObj,"{\"systemToken\":\"%s\",\"accessToken\":\"%s\"}",systemToken,accessToken);
asprintf(&url,"%s/api/transaction/credentials",rublonApiServer);
char *curlResponse = curlHandler(pamh, jsonObj, url, secretKey);
if(curlResponse == NULL)
return CONNECTION_ERROR;
status = parseJson(curlResponse,"status");
answer = parseNestedJson(curlResponse,"result","answer");
if(strcmp(status,"ERROR") == 0)
return STATUS_DENIED;
if(answer == NULL) {
pam_prompt(pamh, PAM_TEXT_INFO, NULL, "\nRublon Transaction Error!");
return STATUS_BYPASS;
}
if( (strcmp(answer,"true") == 0) && (strcmp(status,"OK") == 0))
return STATUS_CONFIRMED;
return STATUS_UNKNOWN;
}