Different access levels with PAM - c

Currently I have a graphical application that has two levels of access, operator and administrator. The login and authentication is all homebrewed and I'd like to switch the application to use PAM instead. I'm not sure what the right way to do that is.
Correct me if I'm wrong, but it seems that PAM boils down to a "yes" or "no" check--yes you can access this service, or no you can't. There's no provision for having various levels of access based on which user is logging in. I need to be able to tell who's an operator and who's an admin, though, and I want to be able to do it strictly through PAM if possible.
So my thought is that I'd set up two services with two different configurations, /etc/pam.d/pamdemo for operators and /etc/pam.d/pamdemo-admin for administrators. My application would then try to authenticate against pamdemo-admin first, and if that fails then pamdemo. If both fails, access is denied. Am I on the right track or am I completely off the rails?
Here's some sample C code I've written up as a proof of concept. When I do the login I don't want to prompt the user for his credentials twice. I've got it so it remembers the username across the two pam_start() calls but I can't access pam_get_item(PAM_AUTHTOK) from the application level to do the same caching for the password. And it was in trying to do so that I realized that there might be a totally different way to do this. I would like this application to work no matter what the authentication method, be it username/password or Kerberos tickets or fingerprints, whatever.
pam_handle_t *try_login(const char *service, int *retval)
{
static char * username = NULL;
struct pam_conv pam_conversation = { conv, NULL };
pam_handle_t * pamh;
*retval = pam_start(service, username, &pam_conversation, &pamh);
if (*retval == PAM_SUCCESS) *retval = pam_authenticate(pamh, 0);
if (*retval == PAM_SUCCESS) *retval = pam_acct_mgmt (pamh, 0);
if (*retval == PAM_SUCCESS) *retval = pam_open_session(pamh, 0);
if (username == NULL) {
if (pam_get_item(pamh, PAM_USER, (const void **) &username) == PAM_SUCCESS) {
username = strdup(username);
}
}
if (*retval != PAM_SUCCESS) {
fprintf(stderr, "%s: %s\n", service, pam_strerror(pamh, *retval));
pam_end(pamh, *retval);
pamh = NULL;
}
return pamh;
}
int main(void)
{
pam_handle_t *pamh = NULL;
int retval;
const char *service, *username;
if (!pamh) pamh = try_login("pamdemo-admin", &retval);
if (!pamh) pamh = try_login("pamdemo", &retval);
if (!pamh) {
fprintf(stderr, "Access denied.\n");
return 1;
}
pam_get_item(pamh, PAM_SERVICE, (const void **) &service);
pam_get_item(pamh, PAM_USER, (const void **) &username);
printf("Logged into %s as %s.\n", service, username);
pam_close_session(pamh, 0);
pam_end (pamh, retval);
return 0;
}
As written this demo program repeats the "password:" prompt. I don't want it to ask twice!

I believe one right way to do this might be:
Set up the "pamdemo" service to do account, authentication and session functions.
Set up the "pamdemo-admin" service to only do account (and possibly session) functions. No authentication.
When logging in, first make them pass "pamdemo" (to ensure they are who they say they are) - if this fails, kick them out.
Then, once authenticated, hand them to "pamdemo-admin". This just checks to see if they're allowed to be admin - if they are, this check succeeds, if they aren't, it doesn't. Because this check doesn't do auth modules, they aren't prompted for a password again.

Per caf's suggestion, here is my solution:
#define PAM_CALL(call) \
do { \
if ((retval = (call)) != PAM_SUCCESS) { \
goto pam_error; \
} \
} while (0)
int check_admin_login(const char *user)
{
pam_handle_t * pamh = NULL;
struct pam_conv pam_conversation = { conv, NULL };
int retval;
PAM_CALL(pam_start ("pamdemo-admin", user, &pam_conversation, &pamh));
PAM_CALL(pam_acct_mgmt(pamh, 0));
PAM_CALL(pam_end (pamh, retval));
return 1;
pam_error:
pam_end(pamh, retval);
return 0;
}
int main(void)
{
pam_handle_t * pamh = NULL;
struct pam_conv pam_conversation = { conv, NULL };
int retval;
const char * user;
int is_admin;
PAM_CALL(pam_start ("pamdemo", NULL, &pam_conversation, &pamh));
PAM_CALL(pam_authenticate (pamh, 0));
PAM_CALL(pam_acct_mgmt (pamh, 0));
PAM_CALL(pam_open_session (pamh, 0));
PAM_CALL(pam_get_item (pamh, PAM_USER, (const void **) &user));
is_admin = check_admin_login(user);
printf("Logged in as %s (%s).\n", user, is_admin ? "administrator" : "operator");
PAM_CALL(pam_close_session(pamh, 0));
pam_end (pamh, retval);
return 0;
pam_error:
fprintf(stderr, "%s\n", pam_strerror(pamh, retval));
pam_end(pamh, retval);
return 1;
}

you can just use the command "groups " or "id " and get the groups for the user, then grep the groups and if you hit admin first, then it's an admin user otherwise it's a demo user.
The groups / id commands (tested on Linux) will get the groups for non-local users as well (e.g PAM / LDAP)
So, instead of checking against a service, check the group in which the user belongs.

Related

Trouble with implementing a Direct Module Method in Azure IoT Edge Module using azure-iot-sdk-c SDK

Full disclosure, I have asked this question on Azure IoT SDK C github project, but since they recommend looking on StackOverflow, I decided to post here as well.
I am having trouble implementing a Direct Module Method handler in my azure-iot-sdk-c based IoT Edge Module. I could not find a documentation page with an example implementation, so I assembled my implementation from various SDK documentation pages and unit test "examples".
To test this, I have a dedicated Linux based PC (Ubuntu 18.04) running iotedge 1.0.8-2. I can see that my module is starting and printing its version and firing the connection status callback message. I even even see that the ModuleTwin callback is firing and printing the payload when I manually edit the module identity twin for my device in the portal.
However, when I try to manually invoke a Direct Method on my module within my device in the portal, I see nothing printed and I get the following error in the portal:
{"message":"GatewayTimeout:{\r\n \"Message\": \"{\\\"errorCode\\\":504101,\\\"trackingId\\\":\\\"8215e001484d41a19245639844f44f78-G:9-TimeStamp:01/14/2020 21:20:42-G:0-TimeStamp:01/14/2020 21:20:42\\\",\\\"message\\\":\\\"Timed out waiting for the response from device.\\\",\\\"info\\\":{},\\\"timestampUtc\\\":\\\"2020-01-14T21:20:42.0556758Z\\\"}\",\r\n \"ExceptionMessage\": \"\"\r\n}"}
The relevant code is below. I looked on StackOverflow but examples there are not C SDK based. Where am I going wrong with Direct Module Methods? Thank you!
Update: An interesting observation is that if I change this code to use MQTT from AMQP, then everything works. Is AMQP not supported for Direct Module Methods?
#include <iothub_module_client_ll.h>
#include <iothub_client_options.h>
#include <iothub_message.h>
#include <azure_c_shared_utility/threadapi.h>
#include <azure_c_shared_utility/crt_abstractions.h>
#include <azure_c_shared_utility/platform.h>
#include <azure_c_shared_utility/shared_util_options.h>
#include <iothubtransportamqp.h>
#include <iothub.h>
#include <time.h>
#include <stdio.h>
#include <stdlib.h>
// Linker defined build information (see Makefile)
extern char __BUILD_DATE;
extern char __BUILD_NUMBER;
// Set the default value for module communication (e.g. AMQP) log tracing, yet
// allow compile time overrides.
#ifndef LOG_TRACE_ENABLED
#define LOG_TRACE_ENABLED 0
#endif
static void moduleTwinCallback(DEVICE_TWIN_UPDATE_STATE update_state, const unsigned char* payLoad, size_t size, void* /*userContextCallback*/)
{
EPRINT("DEBUG: Module Twin callback called with (state=%s)", MU_ENUM_TO_STRING(DEVICE_TWIN_UPDATE_STATE, update_state));
EPRINT("DEBUG: payload=%.*s", (int)size, (const char *)payLoad);
fflush(NULL);
//JSON_Value *root_value = json_parse_string(payLoad);
//JSON_Object *root_object = json_value_get_object(root_value);
//if (json_object_dotget_value(root_object, "desired.TemperatureThreshold") != NULL) {
// temperatureThreshold = json_object_dotget_number(root_object, "desired.TemperatureThreshold");
//}
//if (json_object_get_value(root_object, "TemperatureThreshold") != NULL) {
// temperatureThreshold = json_object_get_number(root_object, "TemperatureThreshold");
//}
}
static int DirectMethodCb(const char* method_name, const unsigned char* payload, size_t size, unsigned char** response, size_t* resp_size, void* /*userContextCallback*/)
{
const char *METHOD_NAME = "TestMethod";
const int METHOD_RESPONSE_SUCCESS = 200;
const int METHOD_RESPONSE_ERROR = 401;
int responseCode;
EPRINT("DEBUG: Method name: %s", method_name);
EPRINT("DEBUG: Method payload: %.*s", (int)size, (const char*)payload);
if (strcmp(METHOD_NAME, method_name))
{
EPRINT("Method name incorrect - expected %s but got %s", METHOD_NAME, method_name);
responseCode = METHOD_RESPONSE_ERROR;
}
/*
else if (size != strlen(expectedMethodPayload))
{
LogError("payload size incorect - expected %zu but got %zu", strlen(expectedMethodPayload), size);
responseCode = METHOD_RESPONSE_ERROR;
}
else if (memcmp(payload, expectedMethodPayload, size))
{
LogError("Payload strings do not match");
responseCode = METHOD_RESPONSE_ERROR;
}
*/
else
{
*resp_size = size;
if (size == 0)
{
*response = NULL;
EPRINT("DEBUG: Empty, but good response");
responseCode = METHOD_RESPONSE_SUCCESS;
}
else
{
if ((*response = (unsigned char*)malloc(*resp_size)) == NULL)
{
EPRINT("allocation failure");
responseCode = METHOD_RESPONSE_ERROR;
}
else
{
(void)memcpy(*response, payload, *resp_size);
EPRINT("DEBUG: All good - echoing back the payload");
responseCode = METHOD_RESPONSE_SUCCESS;
}
}
}
EPRINT("DEBUG: completing with return code %d", responseCode);
fflush(NULL);
return responseCode;
}
static void ConnectionStatusCb(IOTHUB_CLIENT_CONNECTION_STATUS result, IOTHUB_CLIENT_CONNECTION_STATUS_REASON reason, void* /*userContextCallback*/)
{
EPRINT("DEBUG: ConnectionStatusCb(status=%d %s, reason=%d %s",
result, MU_ENUM_TO_STRING(IOTHUB_CLIENT_CONNECTION_STATUS, result),
reason, MU_ENUM_TO_STRING(IOTHUB_CLIENT_CONNECTION_STATUS_REASON, reason)
);
fflush(NULL);
}
int main(void)
{
IOTHUB_MODULE_CLIENT_LL_HANDLE iotHubModuleClientHandle = nullptr;
int retval = 1;
do
{
printf("\n\n=======================\n");
printf("Build date : %lu\n", (unsigned long) &__BUILD_DATE);
printf("Build number: %lu\n", (unsigned long) &__BUILD_NUMBER);
fflush(NULL);
srand((unsigned int)time(NULL));
if (0 != IoTHub_Init())
{
EPRINT("Failed to initialize the platform.");
break;
}
iotHubModuleClientHandle = IoTHubModuleClient_LL_CreateFromEnvironment(AMQP_Protocol);
if (nullptr == iotHubModuleClientHandle)
{
EPRINT("IoTHubModuleClient_LL_CreateFromEnvironment failed");
break;
}
IOTHUB_CLIENT_RESULT result = IoTHubModuleClient_LL_SetModuleMethodCallback(iotHubModuleClientHandle, DirectMethodCb, iotHubModuleClientHandle);
if (IOTHUB_CLIENT_OK != result)
{
EPRINT("IoTHubModuleClient_SetModuleMethodCallback failed: %d", result);
break;
}
result = IoTHubModuleClient_LL_SetConnectionStatusCallback(iotHubModuleClientHandle, ConnectionStatusCb, iotHubModuleClientHandle);
if (IOTHUB_CLIENT_OK != result)
{
EPRINT("IoTHubDeviceClient_SetConnectionStatusCallback failed: %d", result);
break;
}
#if LOG_TRACE_ENABLED
bool traceOn = true;
IoTHubModuleClient_LL_SetOption(iotHubModuleClientHandle, OPTION_LOG_TRACE, &traceOn);
#endif // LOG_TRACE_ENABLED
result = IoTHubModuleClient_LL_SetModuleTwinCallback(iotHubModuleClientHandle, moduleTwinCallback, iotHubModuleClientHandle);
if (IOTHUB_CLIENT_OK != result)
{
EPRINT("IoTHubModuleClient_LL_SetModuleTwinCallback failed: %d", result);
break;
}
while (true)
{
IoTHubModuleClient_LL_DoWork(iotHubModuleClientHandle);
ThreadAPI_Sleep(100);
}
} while(false);
if (nullptr != iotHubModuleClientHandle)
{
IoTHubModuleClient_LL_Destroy(iotHubModuleClientHandle);
}
IoTHub_Deinit();
return retval;
}

SIGABRT when returning from function

my problem is that, just as I mentioned on the title, i have a function that, upon return, causes a SIGABRT to be raised. I ran valgrind on my program and I got this at that exact point.
==5807== Process terminating with default action of signal 6 (SIGABRT)
==5807==    at 0x52F5428: raise (raise.c:54)
==5807==    by 0x52F7029: abort (abort.c:89)
==5807==    by 0x53377E9: __libc_message (libc_fatal.c:175)
==5807==    by 0x53D911B: __fortify_fail (fortify_fail.c:37)
==5807==    by 0x53D90BF: __stack_chk_fail (stack_chk_fail.c:28)
==5807==    by 0x402E8B: foo (file.c:43)
==5807==    by 0x202C27323939312C: ???
==5807==    by 0x592D4D4D2D444426: ???
==5807==    by 0x66202C2927595958: ???
==5807==    by 0x2965736C60: ???
==5807==    by 0x505770F: ??? (in /usr/lib/x86_64-linux-gnu/libodbc.so.2.0.0)
==5807==
This is the code i'm executing, the stack error causes the abort at the return ret statement from add_user:
int add_user(SQLHDBC dbc, char * mail, char * password, char * name, char * date) {
char query[TAM];
SQLHSTMT stmt;
SQLRETURN ret;
if (mail == NULL || password == NULL || name == NULL || date == NULL)
return ERR;
sprintf(query, "INSERT INTO mms_user values (default,'%s',encrypt_password('%s'),set_type(),'%s',to_date('%s', 'DD-MM-YYYY'), false)", mail, password, name,date);
ret= DBExecuteQuery(dbc, query, &stmt);
DBFreeHandle(&stmt);
return ret;
}
int DBExecuteQuery(SQLHDBC dbc, char * query, SQLHSTMT *stmt) {
SQLRETURN ret;
/*Error control*/
if (query == NULL || stmt == NULL)
return ERR;
/*Allocates memory for a new statement*/
ret = SQLAllocHandle(SQL_HANDLE_STMT, dbc, stmt);
if (!SQL_SUCCEEDED(ret)) {
return ERR;
}
/*Executes query and stores result in stmt*/
ret = SQLExecDirect(*stmt, (SQLCHAR *)query, SQL_NTS);
if (!SQL_SUCCEEDED(ret))
return ERR;
return OK;
}
int DBFreeHandle(SQLHSTMT * stmt){
int ret;
/*Frees allocated memory*/
ret = SQLFreeHandle(SQL_HANDLE_STMT, *stmt);
if (!SQL_SUCCEEDED(ret)) {
return ERR;
}
}
The function executes normally as far as I have checked. I don't know what could be happening, if any of you could provide any possible explanation, I would be really thankful. Also feel free to ask for further information (this is the only useful info I've been taught to look for, so I might need some guidance in that case).
Thank you in advance
This code is dangerous:
char query[TAM];
.
.
.
sprintf(query, "INSERT INTO mms_user values"
" (default,'%s',encrypt_password('%s'),set_type(),"
"'%s',to_date('%s', 'DD-MM-YYYY'), false)",
mail, password, name,date);
You do absolutely no bounds checking so it will overflow your stack easily.

C Programming Writing to registry isn't working? Error at RegSetValueEX

Trying to write my programme into the windows registry but my code tells me there is an error when doing the RegSetValueEX(). I have administrative access. I can't see whats wrong at all and I've been staring at MSDN pages on REG all day.
int StartupKey()
{
int StartupKey;
long RegOpenResult, result_write;
const char *FilePath[]= "C:\\Windows\\security\\BensKlog.exe";
LPCSTR Klog = "BensKLOG";
HKEY hkey;
printf("Opening Key...\n");
RegOpenResult = RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", 0, KEY_ALL_ACCESS, &hkey);
if(RegOpenResult != ERROR_SUCCESS) {
if(RegOpenResult == ERROR_FILE_NOT_FOUND) {
printf("Not found\n");
} else {
printf("Error Opening Key\n");
}
} else {
printf("SUCCESS!!!\n");
}
StartupKey=RegCreateKey(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run",&hkey);
printf("Writing Value named Klog\n");
result_write = RegSetValueEx((HKEY)hkey,Klog,0,REG_SZ,(BYTE *)FilePath,strlen(FilePath));
if(result_write != ERROR_SUCCESS) {
printf("Error Writing Value\n");
} else {
printf("SUCCESS!!!\n");
}
RegCloseKey(hkey);
}
const char *FilePath[]= "C:\\Windows\\security\\BensKlog.exe";
Use either:
const char FilePath[] = "C:\\Windows\\security\\BensKlog.exe";
or
const char *FilePath = "C:\\Windows\\security\\BensKlog.exe";
but don't mix them (your code defines an array of const char pointers instead of one pointer).
(Might not be the only error though)
Spoke to a lecturer at my university, showed him
My code he claims I need to run on an Administrator account, turns out my
User on my laptop wasn't the admin(which I thought it was) will check if it works when I'm home and update

How to retrieve issuer alternative name for ssl certificate by openssl

I can get the subject alternative name like
X509_NAME_get_text_by_NID(X509_get_subject_name(x), NID_subject_alt_name, hc->https_domain_name, 256)
With same method by changing 2. parameter to NID_issuer_alt_name I am expecting to get issuer name like;
X509_NAME_get_text_by_NID(X509_get_subject_name(x), NID_issuer_alt_name, hc->https_ca_name, 256);
But instead I am getting a empty string . How can I retrieve issuer alternative name correctly?
You could try the following solution, as recommended in https://github.com/iSECPartners/ssl-conservatory :
static HostnameValidationResult matches_subject_alternative_name (const char *hostname, const X509 *server_cert) {
HostnameValidationResult result = MatchNotFound;
int i;
int san_names_nb = -1;
STACK_OF(GENERAL_NAME) *san_names = NULL;
// Try to extract the names within the SAN extension from the certificate
san_names = X509_get_ext_d2i((X509 *) server_cert, NID_subject_alt_name, NULL, NULL);
if (san_names == NULL) {
return NoSANPresent;
}
san_names_nb = sk_GENERAL_NAME_num(san_names);
// Check each name within the extension
for (i=0; i<san_names_nb; i++) {
const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i);
if (current_name->type == GEN_DNS) {
// Current name is a DNS name, let's check it
char *dns_name = (char *) ASN1_STRING_data(current_name->d.dNSName);
// Make sure there isn't an embedded NUL character in the DNS name
if (ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) {
result = MalformedCertificate;
break;
}
else { // Compare expected hostname with the DNS name
if (strcasecmp(hostname, dns_name) == 0) {
result = MatchFound;
break;
}
}
}
}
sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free);
return result;
}
Hope it helps !
In your call to X509_NAME_get_text_by_NID with the NID_issuer_alt_name constant, I would have replaced X509_get_subject_name(x) by X509_get_issuer_name(x). I think this should do the trick you are after.

How to check if pid belongs to current user session?

I can get the list of running process from the this source code on mac.
Now, I want to filter these processes for different users or at least for current user session.
You can just extend your code like this..
kinfo_proc *mylist;
size_t mycount = 0;
mylist = (kinfo_proc *)malloc(sizeof(kinfo_proc));
GetBSDProcessList(&mylist, &mycount);
char *user = getenv("USER");
for (int i = 0; i < mycount; i++)
{
uid_t uid = mylist[i].kp_eproc.e_pcred.p_ruid;
struct passwd * pwd = getpwuid(uid);
char * username = pwd->pw_name;
if(strcmp(username, user) == 0)
{
printf(" %d - %s \n", mylist[i].kp_proc.p_pid, mylist[i].kp_proc.p_comm);
}
}
To be more precise you can get username buy this technique
SCDynamicStoreRef store;
store = SCDynamicStoreCreate(NULL, CFSTR("com.apple.dts.ConsoleUser"), NULL, NULL);
CFStringRef currentConsoleUser = CopyCurrentConsoleUsername(store);
const int kBufferSize = 256;
char logedinusername[kBufferSize];
CFStringGetCString(currentConsoleUser,logedinusername,kBufferSize,kCFStringEncodingMacRoman);
as getenv("USER"); may not work if you are running as root user and want logged in user.

Resources