Skip to content

Instantly share code, notes, and snippets.

@kapil1024
Created September 21, 2021 18:14
Show Gist options
  • Select an option

  • Save kapil1024/2e87186416e562587ea2e1faad3c3f29 to your computer and use it in GitHub Desktop.

Select an option

Save kapil1024/2e87186416e562587ea2e1faad3c3f29 to your computer and use it in GitHub Desktop.
Using Linux PAM to authenticate
/*
* This program is inspired from following articles -
* http://www.linux-pam.org/Linux-PAM-html/adg-example.html
* https://docs.oracle.com/cd/E23824_01/html/819-2145/pam-1.html#pam-20
* https://man7.org/tlpi/code/online/book/tty/no_echo.c.html
*
* Install required packages:
* $ sudo apt-get install libpam-dev -y
*
* Compile using gcc:
* $ gcc testpam.c -lpam -o testpam
*
* Run it then type your password to exit:
* $ ./testpam
*
*/
#define __EXTENSIONS__ /* to expose flockfile and friends in stdio.h */
#include <errno.h>
#include <libgen.h>
#include <malloc.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <strings.h>
#include <unistd.h>
#include <termio.h>
#include <security/pam_appl.h>
#include <sys/types.h>
#include <pwd.h>
#include <string.h>
static int ctl_c; /* was the conversation interrupted? */
/* ARGSUSED 1 */
static void
interrupt(int x) /* This handler is not useful right now */
{
ctl_c = 1;
}
/* getinput -- read user input from stdin abort on ^C
* Entry noecho == TRUE, don't echo input.
* Exit User's input.
* If interrupted, send SIGINT to caller for processing.
*/
static char *
getinput(int noecho)
{
struct termios tty;
tcflag_t tty_flags;
char input[PAM_MAX_RESP_SIZE];
int c;
int i = 0;
void (*sig)(int);
ctl_c = 0;
sig = signal(SIGINT, interrupt);
if (noecho) {
if (tcgetattr(STDIN_FILENO, &tty) == -1)
perror("tcgetattr");
else {
tty_flags = tty.c_lflag;
tty.c_lflag &= ~(ECHO | ECHOE | ECHOK | ECHONL);
if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &tty) == -1)
perror("tcsetattr");
}
}
/* go to end, but don't overflow PAM_MAX_RESP_SIZE */
flockfile(stdin);
while (ctl_c == 0 &&
(c = getchar_unlocked()) != '\n' &&
c != '\r' &&
c != EOF) {
if (i < PAM_MAX_RESP_SIZE) {
input[i++] = (char)c;
}
}
funlockfile(stdin);
input[i] = '\0';
if (noecho) {
tty.c_lflag = tty_flags;
if (tcsetattr(STDIN_FILENO, TCSANOW, &tty) == -1)
perror("tcsetattr");
(void) fputc('\n', stdout);
}
(void) signal(SIGINT, sig);
if (ctl_c == 1)
(void) kill(getpid(), SIGINT);
return (strdup(input));
}
/* Service modules do not clean up responses if an error is returned.
* Free responses here.
*/
static void
free_resp(int num_msg, struct pam_response *pr)
{
int i;
struct pam_response *r = pr;
if (pr == NULL)
return;
for (i = 0; i < num_msg; i++, r++) {
if (r->resp) {
/* clear before freeing -- may be a password */
bzero(r->resp, strlen(r->resp));
free(r->resp);
r->resp = NULL;
}
}
free(pr);
}
/* ARGSUSED */
int
pam_tty_conv(int num_msg, const struct pam_message **mess,
struct pam_response **resp, void *my_data)
{
struct pam_message *m = (struct pam_message *)*mess;
struct pam_response *r;
int i;
if (num_msg <= 0 || num_msg >= PAM_MAX_NUM_MSG) {
(void) fprintf(stderr, "bad number of messages %d "
"<= 0 || >= %d\n",
num_msg, PAM_MAX_NUM_MSG);
*resp = NULL;
return (PAM_CONV_ERR);
}
if ((*resp = r = calloc(num_msg,
sizeof (struct pam_response))) == NULL)
return (PAM_BUF_ERR);
errno = 0; /* don't propogate possible EINTR */
/* Loop through messages */
for (i = 0; i < num_msg; i++) {
int echo_off;
/* bad message from service module */
if (m->msg == NULL) {
(void) fprintf(stderr, "message[%d]: %d/NULL\n",
i, m->msg_style);
goto err;
}
/*
* fix up final newline:
* removed for prompts
* added back for messages
*/
//if (m->msg[strlen(m->msg)] == '\n')
// m->msg[strlen(m->msg)] = '\0';
r->resp = NULL;
r->resp_retcode = 0;
echo_off = 0;
switch (m->msg_style) {
case PAM_PROMPT_ECHO_OFF:
echo_off = 1;
/*FALLTHROUGH*/
case PAM_PROMPT_ECHO_ON:
(void) fputs(m->msg, stdout);
r->resp = getinput(echo_off);
break;
case PAM_ERROR_MSG:
(void) fputs(m->msg, stderr);
(void) fputc('\n', stderr);
break;
case PAM_TEXT_INFO:
(void) fputs(m->msg, stdout);
(void) fputc('\n', stdout);
break;
default:
(void) fprintf(stderr, "message[%d]: unknown type "
"%d/val=\"%s\"\n",
i, m->msg_style, m->msg);
/* error, service module won't clean up */
goto err;
}
if (errno == EINTR)
goto err;
/* next message/response */
m++;
r++;
}
return (PAM_SUCCESS);
err:
free_resp(i, r);
*resp = NULL;
return (PAM_CONV_ERR);
}
/* Disable keyboard interrupts (Ctrl-C, Ctrl-Z, Ctrl-\) */
static void
disable_kbd_signals(void)
{
(void) signal(SIGINT, SIG_IGN);
(void) signal(SIGTSTP, SIG_IGN);
(void) signal(SIGQUIT, SIG_IGN);
}
/* Terminate current user session, i.e., logout */
static void
logout()
{
pid_t pgroup = getpgrp();
(void) signal(SIGTERM, SIG_IGN);
(void) fprintf(stderr, "Sorry, your session can't be restored.\n");
(void) fprintf(stderr, "Press return to terminate this session.\n");
(void) getchar();
(void) kill(-pgroup, SIGTERM);
(void) sleep(2);
(void) kill(-pgroup, SIGKILL);
exit(-1);
}
int
/*ARGSUSED*/
main(int argc, char *argv)
{
struct pam_conv conv = { pam_tty_conv, NULL };
pam_handle_t *pamh;
struct passwd *pw;
int err;
disable_kbd_signals();
if ((pw = getpwuid(getuid())) == NULL) {
(void) fprintf(stderr, "login : Can't get username: %s\n",
strerror(errno));
exit(1);
}
/* Initialize PAM framework */
err = pam_start("login", pw->pw_name, &conv, &pamh);
if (err != PAM_SUCCESS) {
(void) fprintf(stderr, "login : pam_start failed: %s\n",
pam_strerror(pamh, err));
exit(1);
}
/* Authenticate user in order to unlock screen */
do {
(void) fprintf(stderr, "Terminal locked for %s. ", pw->pw_name);
err = pam_authenticate(pamh, 0);
if (err == PAM_USER_UNKNOWN) {
logout();
} else if (err != PAM_SUCCESS) {
(void) fprintf(stderr, "Invalid password.\n");
}
} while (err != PAM_SUCCESS);
/* Make sure account and password are still valid */
switch (err = pam_acct_mgmt(pamh, 0)) {
case PAM_SUCCESS:
break;
case PAM_USER_UNKNOWN:
case PAM_ACCT_EXPIRED:
/* User not allowed in anymore */
logout();
break;
case PAM_NEW_AUTHTOK_REQD:
/* The user's password has expired. Get a new one */
do {
err = pam_chauthtok(pamh, 0);
} while (err == PAM_AUTHTOK_ERR);
if (err != PAM_SUCCESS)
logout();
break;
default:
logout();
}
if (pam_setcred(pamh, PAM_REFRESH_CRED) != PAM_SUCCESS){
logout();
}
(void) pam_end(pamh, 0);
return(0);
/*NOTREACHED*/
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment