485 lines
9.4 KiB
C
485 lines
9.4 KiB
C
/*
|
|
* pam_url - authenticate against webservers
|
|
*
|
|
* This software is opensource software licensed under the GNU Public License version 2.
|
|
* The author of this software is Sascha Thomas Spreitzer <sspreitzer (at) fedoraproject.org>.
|
|
* Please take a look in the COPYING, INSTALL and README files.
|
|
*
|
|
* USE THIS SOFTWARE WITH ABSOLUTELY NO GUARANTEE AND WARRANTY
|
|
*
|
|
*
|
|
* /etc/pam.d/sshd or /etc/pam.d/system-auth:
|
|
*
|
|
* [...]
|
|
* auth sufficient pam_url.so https://www.example.org/ secret user passwd &do=login
|
|
* auth sufficient pam_url.so URL PSK USER PASSWD EXTRA
|
|
* [...]
|
|
* This module takes 4 arguments:
|
|
* - URL = HTTPS URL
|
|
* - PSK = Pre Shared Key
|
|
* - USER = The name of the user variable
|
|
* - PASSWD = The name of the password variable
|
|
* - EXTRA = additional url encoded data
|
|
*
|
|
* auth sufficient pam_url.so https://www.example.org/ secret user passwd &do=auth
|
|
* This line forms the following url encoded POST data:
|
|
* user=<username>&passwd=<pass>&mode=<PAM_AUTH|PAM_ACCT|PAM_SESS|PAM_PASS>&PSK=secret&do=auth
|
|
* It should return either 200 OK with PSK in the body or 403 Forbidden if unsuccessful.
|
|
*/
|
|
|
|
#ifndef NAME
|
|
#define NAME "pam_url"
|
|
#endif
|
|
|
|
#ifndef VERS
|
|
#define VERS "0.0"
|
|
#endif
|
|
|
|
#ifndef USER_AGENT
|
|
#define USER_AGENT NAME "/" VERS
|
|
#endif
|
|
|
|
#define PAM_SM_AUTH 1
|
|
#define PAM_SM_ACCOUNT 2
|
|
#define PAM_SM_SESSION 3
|
|
#define PAM_SM_PASSWORD 4
|
|
|
|
#include <security/pam_modules.h>
|
|
#include <security/pam_ext.h>
|
|
|
|
#ifndef _SECURITY_PAM_MODULES_H
|
|
#error PAM headers not found on this system. Giving up.
|
|
#endif
|
|
|
|
#include <curl/curl.h>
|
|
#ifndef __CURL_CURL_H
|
|
#error libcurl headers not found on this system. Giving up.
|
|
#endif
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <syslog.h>
|
|
#include <unistd.h>
|
|
|
|
#ifndef DEF_URL
|
|
#define DEF_URL "https://www.example.org/"
|
|
#endif
|
|
|
|
#ifndef DEF_PSK
|
|
#define DEF_PSK "presharedsecret"
|
|
#endif
|
|
|
|
#ifndef DEF_USER
|
|
#define DEF_USER "user"
|
|
#endif
|
|
|
|
#ifndef DEF_PASSWD
|
|
#define DEF_PASSWD "passwd"
|
|
#endif
|
|
|
|
#ifndef DEF_EXTRA
|
|
#define DEF_EXTRA "&do=pam_url"
|
|
#endif
|
|
|
|
typedef struct pam_url_opts_ {
|
|
char* url;
|
|
char* PSK;
|
|
char* userfield;
|
|
char* passwdfield;
|
|
char* extrafield;
|
|
char* mode;
|
|
|
|
const void* user;
|
|
const void* passwd;
|
|
} pam_url_opts;
|
|
|
|
char* recvbuf = NULL;
|
|
size_t recvbuf_size = 0;
|
|
|
|
void notice(pam_handle_t* pamh, const char *msg)
|
|
{
|
|
pam_syslog(pamh, LOG_NOTICE, "%s", msg);
|
|
}
|
|
|
|
void debug(pam_handle_t* pamh, const char *msg)
|
|
{
|
|
#ifdef DEBUG
|
|
pam_syslog(pamh, LOG_ERR, "%s", msg);
|
|
#endif
|
|
}
|
|
|
|
int get_password(pam_handle_t* pamh, pam_url_opts* opts)
|
|
{
|
|
char* p = NULL;
|
|
pam_prompt(pamh, PAM_PROMPT_ECHO_OFF, &p, "%s", "Password: ");
|
|
|
|
if( NULL != p )
|
|
{
|
|
opts->passwd = p;
|
|
return PAM_SUCCESS;
|
|
}
|
|
else
|
|
{
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
}
|
|
|
|
int parse_opts(pam_url_opts* opts, int argc, const char** argv, int mode)
|
|
{
|
|
opts->url = calloc(1, strlen(DEF_URL) + 1);
|
|
strcpy(opts->url, DEF_URL);
|
|
|
|
opts->PSK = calloc(1, strlen(DEF_PSK) + 1);
|
|
strcpy(opts->PSK, DEF_PSK);
|
|
|
|
opts->userfield = calloc(1, strlen(DEF_USER) + 1);
|
|
strcpy(opts->userfield, DEF_USER);
|
|
|
|
opts->passwdfield = calloc(1, strlen(DEF_PASSWD) + 1);
|
|
strcpy(opts->passwdfield, "passwd");
|
|
|
|
opts->extrafield = calloc(1, strlen(DEF_EXTRA) + 1);
|
|
strcpy(opts->extrafield, "&mode=login");
|
|
|
|
if( 0 == argc )
|
|
{
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
if( argc >= 1 )
|
|
{
|
|
opts->url = calloc(1, strlen(argv[0]) + 1);
|
|
strcpy(opts->url, argv[0]);
|
|
}
|
|
|
|
if( argc >= 2 )
|
|
{
|
|
opts->PSK = calloc(1, strlen(argv[1]) +1);
|
|
strcpy(opts->PSK, argv[1]);
|
|
}
|
|
|
|
if( argc >= 3 )
|
|
{
|
|
opts->userfield = calloc(1, strlen(argv[2]) + 1);
|
|
strcpy(opts->userfield, argv[2]);
|
|
}
|
|
|
|
if( argc >= 4 )
|
|
{
|
|
opts->passwdfield = calloc(1, strlen(argv[3]) + 1);
|
|
strcpy(opts->passwdfield, argv[3]);
|
|
}
|
|
|
|
if( argc >= 5 )
|
|
{
|
|
opts->extrafield = calloc(1, strlen(argv[4]) + 1);
|
|
strcpy(opts->extrafield, argv[4]);
|
|
}
|
|
|
|
switch(mode)
|
|
{
|
|
case PAM_SM_ACCOUNT:
|
|
opts->mode = calloc(1, strlen("PAM_SM_ACCOUNT") + 1);
|
|
strcpy(opts->mode, "PAM_SM_ACCOUNT");
|
|
break;
|
|
|
|
case PAM_SM_SESSION:
|
|
opts->mode = calloc(1, strlen("PAM_SM_SESSION") + 1);
|
|
strcpy(opts->mode, "PAM_SM_SESSION");
|
|
break;
|
|
|
|
case PAM_SM_PASSWORD:
|
|
opts->mode = calloc(1, strlen("PAM_SM_PASSWORD") + 1);
|
|
strcpy(opts->mode, "PAM_SM_PASSWORD");
|
|
break;
|
|
|
|
default: // PAM_SM_AUTH
|
|
opts->mode = calloc(1, strlen("PAM_SM_AUTH") + 1);
|
|
strcpy(opts->mode,"PAM_SM_AUTH");
|
|
}
|
|
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
size_t curl_wf(void *ptr, size_t size, size_t nmemb, void *stream)
|
|
{
|
|
size_t oldsize=0;
|
|
|
|
if( 0 == size * nmemb )
|
|
return 0;
|
|
|
|
if( NULL == recvbuf )
|
|
{
|
|
if( NULL == ( recvbuf = calloc(nmemb, size) ) )
|
|
{
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if( NULL == ( recvbuf = realloc(recvbuf, recvbuf_size + (nmemb * size)) ) )
|
|
{
|
|
return 0;
|
|
}
|
|
else
|
|
{
|
|
oldsize=recvbuf_size;
|
|
recvbuf_size += nmemb * size;
|
|
memcpy(recvbuf + oldsize, ptr, size * nmemb);
|
|
return(size*nmemb);
|
|
}
|
|
}
|
|
|
|
int fetch_url(pam_url_opts opts)
|
|
{
|
|
CURL* eh = NULL;
|
|
char* post = NULL;
|
|
|
|
if( NULL == opts.user )
|
|
opts.user = calloc(1,1);
|
|
|
|
if( NULL == opts.passwd )
|
|
opts.passwd = calloc(1,1);
|
|
|
|
post = calloc(1,strlen(opts.userfield) +
|
|
strlen("=") +
|
|
strlen(opts.user) +
|
|
strlen("&") +
|
|
strlen(opts.passwdfield) +
|
|
strlen("=") +
|
|
strlen(opts.passwd) +
|
|
strlen("&mode=") +
|
|
strlen(opts.mode) +
|
|
strlen(opts.extrafield) +
|
|
strlen("\0") );
|
|
|
|
sprintf(post, "%s=%s&%s=%s&mode=%s%s", opts.userfield,
|
|
(char*)opts.user,
|
|
opts.passwdfield,
|
|
(char*)opts.passwd,
|
|
opts.mode,
|
|
opts.extrafield);
|
|
|
|
if( 0 != curl_global_init(CURL_GLOBAL_ALL) )
|
|
return PAM_AUTH_ERR;
|
|
|
|
if( NULL == (eh = curl_easy_init() ) )
|
|
return PAM_AUTH_ERR;
|
|
|
|
#ifdef DEBUG
|
|
if( CURLE_OK != curl_easy_setopt(eh, CURLOPT_VERBOSE, 1) )
|
|
{
|
|
curl_easy_cleanup(eh);
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
#endif
|
|
|
|
if( CURLE_OK != curl_easy_setopt(eh, CURLOPT_POSTFIELDS, post) )
|
|
{
|
|
curl_easy_cleanup(eh);
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
if( CURLE_OK != curl_easy_setopt(eh, CURLOPT_USERAGENT, USER_AGENT) )
|
|
{
|
|
curl_easy_cleanup(eh);
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
if( CURLE_OK != curl_easy_setopt(eh, CURLOPT_WRITEFUNCTION, curl_wf) )
|
|
{
|
|
curl_easy_cleanup(eh);
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
if( CURLE_OK != curl_easy_setopt(eh, CURLOPT_URL, opts.url) )
|
|
{
|
|
curl_easy_cleanup(eh);
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
if( CURLE_OK != curl_easy_setopt(eh, CURLOPT_SSL_VERIFYHOST, 2) )
|
|
{
|
|
curl_easy_cleanup(eh);
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
if( CURLE_OK != curl_easy_setopt(eh, CURLOPT_SSL_VERIFYPEER, 1) )
|
|
{
|
|
curl_easy_cleanup(eh);
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
if( CURLE_OK != curl_easy_setopt(eh, CURLOPT_FAILONERROR, 1) )
|
|
{
|
|
curl_easy_cleanup(eh);
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
if( CURLE_OK != curl_easy_perform(eh) )
|
|
{
|
|
curl_easy_cleanup(eh);
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
else
|
|
{
|
|
curl_easy_cleanup(eh);
|
|
return PAM_SUCCESS;
|
|
}
|
|
}
|
|
|
|
int check_psk(pam_url_opts opts)
|
|
{
|
|
int ret=0;
|
|
|
|
if( NULL == recvbuf )
|
|
{
|
|
ret++;
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
if( 0 != memcmp(opts.PSK, recvbuf, strlen(opts.PSK)) )
|
|
ret++;
|
|
|
|
if( 0 != ret )
|
|
{
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
else
|
|
{
|
|
return PAM_SUCCESS;
|
|
}
|
|
}
|
|
|
|
void cleanup(pam_url_opts opts)
|
|
{
|
|
if( NULL != recvbuf )
|
|
free(recvbuf);
|
|
|
|
recvbuf_size=0;
|
|
}
|
|
|
|
PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
|
{ // by now, a dummy
|
|
return PAM_SUCCESS;
|
|
}
|
|
|
|
PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags,
|
|
int argc, const char **argv)
|
|
{
|
|
pam_url_opts opts;
|
|
|
|
int ret = 0;
|
|
|
|
if ( PAM_SUCCESS != pam_get_item(pamh, PAM_USER, &opts.user) )
|
|
{
|
|
ret++;
|
|
debug(pamh, "Could not get user item from pam.");
|
|
}
|
|
|
|
if( PAM_SUCCESS != pam_get_item(pamh, PAM_AUTHTOK, &opts.passwd) )
|
|
{
|
|
ret++;
|
|
debug(pamh, "Could not get password item from pam.");
|
|
}
|
|
|
|
if( NULL == opts.passwd )
|
|
{
|
|
debug(pamh, "No password. Will ask user for it.");
|
|
if( PAM_SUCCESS != get_password(pamh, &opts) )
|
|
{
|
|
debug(pamh, "Could not get password from user. No TTY?");
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
else
|
|
{
|
|
pam_set_item(pamh, PAM_AUTHTOK, opts.passwd);
|
|
}
|
|
}
|
|
|
|
if( PAM_SUCCESS != parse_opts(&opts, argc, argv, PAM_SM_AUTH) )
|
|
{
|
|
ret++;
|
|
debug(pamh, "Could not parse module options.");
|
|
}
|
|
|
|
if( PAM_SUCCESS != fetch_url(opts) )
|
|
{
|
|
ret++;
|
|
debug(pamh, "Could not fetch URL.");
|
|
}
|
|
|
|
if( PAM_SUCCESS != check_psk(opts) )
|
|
{
|
|
ret++;
|
|
debug(pamh, "Pre Shared Key differs from ours.");
|
|
}
|
|
|
|
if( 0 == ret )
|
|
return PAM_SUCCESS;
|
|
|
|
debug(pamh, "Authentication failed.");
|
|
cleanup(opts);
|
|
|
|
return PAM_AUTH_ERR;
|
|
}
|
|
|
|
PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
|
{
|
|
pam_url_opts opts;
|
|
int ret=0;
|
|
|
|
if ( PAM_SUCCESS != pam_get_item(pamh, PAM_USER, &opts.user) )
|
|
{
|
|
ret++;
|
|
debug(pamh, "Could not get user item from pam.");
|
|
}
|
|
|
|
if( PAM_SUCCESS != parse_opts(&opts, argc, argv, PAM_SM_ACCOUNT) )
|
|
{
|
|
ret++;
|
|
debug(pamh, "Could not parse module options.");
|
|
}
|
|
|
|
if( PAM_SUCCESS != fetch_url(opts) )
|
|
{
|
|
ret++;
|
|
debug(pamh, "Could not fetch URL.");
|
|
}
|
|
|
|
if( PAM_SUCCESS != check_psk(opts) )
|
|
{
|
|
ret++;
|
|
debug(pamh, "Pre Shared Key differs from ours.");
|
|
}
|
|
|
|
if( 0 == ret )
|
|
return PAM_SUCCESS;
|
|
|
|
debug(pamh, "Account aged out. Failing.");
|
|
|
|
cleanup(opts);
|
|
|
|
return PAM_PERM_DENIED;
|
|
}
|
|
|
|
PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
|
{
|
|
return PAM_SESSION_ERR;
|
|
}
|
|
|
|
PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
|
{
|
|
return PAM_SESSION_ERR;
|
|
}
|
|
|
|
PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv)
|
|
{
|
|
return PAM_AUTHTOK_ERR;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* vim: nu paste
|
|
*/
|
|
|