Skip to content

Instantly share code, notes, and snippets.

@rmohr
Last active April 22, 2024 08:03
Show Gist options
  • Save rmohr/a131ae3b095095aff134 to your computer and use it in GitHub Desktop.
Save rmohr/a131ae3b095095aff134 to your computer and use it in GitHub Desktop.
Absolutely minimalistic pam authentication example
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include <unistd.h> /* getpass */
#include <security/pam_appl.h> /* pam_start, pam_conv, pam_end, ... */
#define TRY(x) ret = (x); printf("PAM: %s\n", pam_strerror(handle, ret)); if (ret != PAM_SUCCESS) goto finally
int test_conv(int num_msg, const struct pam_message **msg,
struct pam_response **resp, void *appdata_ptr){
const struct pam_message* msg_ptr = *msg;
struct pam_response * resp_ptr = NULL;
int x = 0;
*resp = calloc(sizeof(struct pam_response), num_msg);
for (x = 0; x < num_msg; x++, msg_ptr++){
char* resp_str;
switch (msg_ptr->msg_style){
case PAM_PROMPT_ECHO_OFF:
case PAM_PROMPT_ECHO_ON:
resp_str = getpass(msg_ptr->msg);
resp[x]->resp= strdup(resp_str);
break;
case PAM_ERROR_MSG:
case PAM_TEXT_INFO:
printf("PAM: %s\n", msg_ptr->msg);
break;
default:
assert(0);
}
}
return PAM_SUCCESS;
}
int main(int argc, char** argv){
int len = 1000;
const char service[] = "passwd";
char user[len];
const char *changed_username = NULL;
struct pam_conv conv;
char resp_str[len];
conv.conv=test_conv;
pam_handle_t* handle;
int ret;
printf("Username: ");
fgets(user,len, stdin);
strtok(user, "\n");
TRY( pam_start(service, user, &conv, &handle ));
TRY( pam_authenticate(handle, 0));
TRY( pam_acct_mgmt(handle, 0));
TRY( pam_get_item(handle, PAM_USER,(const void**) &changed_username ));
if (changed_username != NULL) {
printf("PAM: %s\n", changed_username);
}
finally:
pam_end(handle, ret);
return ret;
}
@rmohr
Copy link
Author

rmohr commented Jun 25, 2014

Dependencies

Fedora:

yum install pam-devel

Debian/Ubuntu:

apt-get install pam-devel

Compilation

gcc -o pam simple_pam_auth.c -lpam

@dbaarda
Copy link

dbaarda commented Mar 15, 2019

I believe the following line is wrong;

resp[x]->resp= strdup(resp_str);

The problem is resp is a pointer to a pointer to an array of pam_response structs, not a pointer to an array of pointers to pam_response structs. The difference is subtle, and the result is exactly the same if the array contains only one entry, but will break if num_msg is ever greater than 1.

Fortunately there is confusion between the Sun and Linux PAM implementations around this interpretation for the **msg argument. This has resulted in nearly everyone always using num_msg=1 to avoid compatibility errors. However, some people work around this confusion by making sure that **msg works for both interpretations by setting it to be a pointer to an array of pointers to pam_response structs, where the pam_response structs are themselves in an array. For things that use this approach, num_msg can be greater than 1, which will break this.

@wecsam
Copy link

wecsam commented May 10, 2019

On Debian/Ubuntu, the package pam-devel was not found, but the package libpam0g-dev worked for me.

@fridaythe14th
Copy link

Doesn't the pam_conv man page say to release the resources pam_message and pam_response structures, where does that fit in this code ?

@Demiu
Copy link

Demiu commented Nov 24, 2022

It is the caller's responsibility to release both, this array and the responses themselves

"both" here means the pam_message's and pam_response's

"caller" is the pam module, this is the application code. So for example if you were implementing your own module side pam_sm_authenticate and you'd call pam_get_item(handle, PAM_CONV, &conv) and then conv->conv( ... )

@dimaweber
Copy link

dimaweber commented Apr 22, 2024

should't it be

(*resp)[idx].resp=strdup(...)

?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment