/*

* PAM module for MySQL

*

* Copyright (C) 1998-2005 Gunay Arslan and the contributors.

* All rights reserved.

*

* This program is free software; you can redistribute it and/or

* modify it under the terms of the GNU General Public License

* as published by the Free Software Foundation; either version 2

* of the License, or (at your option) any later version.

*

* This program is distributed in the hope that it will be useful,

* but WITHOUT ANY WARRANTY; without even the implied warranty of

* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the

* GNU General Public License for more details.

*

* You should have received a copy of the GNU General Public License

* along with this program; if not, write to the Free Software

* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

*

* Original Version written by: Gunay ARSLAN

* This version by: James O'Kane

* Modifications by Steve Brown,

* B.J. Black,

* Kyle Smith,

* Patch integration by Moriyoshi Koizumi, ,

* based on the patches by the following contributors:

* Paul Bryan (check_acct_mgmt service support)

* Kev Green (Documentation, UNIX socket option)

* Kees Cook (Misc. clean-ups and more)

* Fredrik Rambris (RPM spec file)

* Peter E. Stokke (chauthtok service support)

* Sergey Matveychuk (OpenPAM support)

*

* $Id: pam_mysql.c,v 1.10.2.15 2006/01/09 10:35:59 moriyoshi Exp $

*/

#define _GNU_SOURCE

#define PAM_SM_AUTH

#define PAM_SM_ACCOUNT

#define PAM_SM_SESSION

#define PAM_SM_PASSWORD

#ifdef HAVE_CONFIG_H

#include "config.h"

#endif

/* {{{ includes */

#include

#include

#ifdef HAVE_UNISTD_H

#include

#endif

#ifdef HAVE_SYSLOG_H

#include

#endif

#ifdef HAVE_STDARG_H

#include

#endif

#ifdef HAVE_ALLOCA_H

#include

#endif

#ifdef HAVE_STRING_H

#include

#endif

#ifdef HAVE_STRINGS_H

#include

#endif

#ifdef HAVE_SYS_SOCKET_H

#include

#endif

#ifdef HAVE_SYS_TIME_H

#include

#endif

#ifdef HAVE_ERRNO_H

#include

#endif

#ifdef HAVE_FCNTL_H

#include

#endif

#ifdef HAVE_SYS_STAT_H

#include

#endif

#ifdef HAVE_SYS_TYPES_H

#include

#endif

#ifdef HAVE_SYS_PARAM_H

#include

#endif

#ifdef HAVE_NETINET_IN_H

#include

#endif

#ifdef HAVE_ARPA_INET_H

#include

#endif

#ifdef HAVE_NETDB_H

#include

#endif

#ifdef HAVE_CRYPT_H

#include

#endif

#ifndef HAVE_OPENSSL

#ifdef HAVE_MD5_H

#include

#endif

#if defined(HAVE_SASL_MD5_H) && (defined(HAVE_CYRUS_SASL_V1) || defined(HAVE_CYRUS_SASL_V2))

#define USE_SASL_MD5

#include

#include

#endif

#endif

#ifdef HAVE_OPENSSL

#include

#include

#endif

#ifdef HAVE_MYSQL_H

#include

#endif

/*

* here, we make definitions for the externally accessible functions

* in this file (these definitions are required for static modules

* but strongly encouraged generally) they are used to instruct the

* modules include file to define their prototypes.

*/

#define PAM_SM_AUTH

#define PAM_SM_ACCOUNT

#define PAM_SM_SESSION

#define PAM_SM_PASSWORD

#include

#include

/* }}} */

#ifndef PAM_EXTERN

#define PAM_EXTERN

#endif

#ifndef LOG_AUTHPRIV

#define LOG_AUTHPRIV LOG_AUTH

#endif

#ifdef LINUX_PAM_CONST_BUG

#define PAM_AUTHTOK_RECOVERY_ERR PAM_AUTHTOK_RECOVER_ERR

#endif

#define PAM_MODULE_NAME "pam_mysql"

#define PAM_MYSQL_LOG_PREFIX PAM_MODULE_NAME " - "

#define PLEASE_ENTER_PASSWORD "Password:"

#define PLEASE_ENTER_OLD_PASSWORD "(Current) Password:"

#define PLEASE_ENTER_NEW_PASSWORD "(New) Password:"

#define PLEASE_REENTER_NEW_PASSWORD "Retype (New) Password:"

/* {{{ consts */

enum _pam_mysql_err_t {

PAM_MYSQL_ERR_SUCCESS = 0,

PAM_MYSQL_ERR_UNKNOWN = -1,

PAM_MYSQL_ERR_NO_ENTRY = 1,

PAM_MYSQL_ERR_ALLOC = 2,

PAM_MYSQL_ERR_INVAL = 3,

PAM_MYSQL_ERR_BUSY = 4,

PAM_MYSQL_ERR_DB = 5,

PAM_MYSQL_ERR_MISMATCH = 6,

PAM_MYSQL_ERR_IO = 7,

PAM_MYSQL_ERR_SYNTAX = 8,

PAM_MYSQL_ERR_EOF = 9,

PAM_MYSQL_ERR_NOTIMPL = 10

};

enum _pam_mysql_config_token_t {

PAM_MYSQL_CONFIG_TOKEN_EQUAL = 0,

PAM_MYSQL_CONFIG_TOKEN_NEWLINE,

PAM_MYSQL_CONFIG_TOKEN_STRING,

PAM_MYSQL_CONFIG_TOKEN_SEMICOLON,

PAM_MYSQL_CONFIG_TOKEN_COMMENT,

PAM_MYSQL_CONFIG_TOKEN__LAST

};

#define PAM_MYSQL_USER_STAT_EXPIRED 0x0001

#define PAM_MYSQL_USER_STAT_AUTHTOK_EXPIRED 0x0002

#define PAM_MYSQL_USER_STAT_NULL_PASSWD 0x0004

#define PAM_MYSQL_CAP_CHAUTHTOK_SELF0x0001

#define PAM_MYSQL_CAP_CHAUTHTOK_OTHERS0x0002

/* }}} */

/* {{{ typedefs */

/* {{{ typedef struct pam_mysql_ctx_t */

typedef struct _pam_mysql_ctx_t {

MYSQL *mysql_hdl;

char *host;

char *where;

char *db;

char *user;

char *passwd;

char *table;

char *update_table;

char *usercolumn;

char *passwdcolumn;

char *statcolumn;

int crypt_type;

int use_323_passwd;

int md5;

int sqllog;

int verbose;

int use_first_pass;

int try_first_pass;

int disconnect_every_op;

char *logtable;

char *logmsgcolumn;

char *logpidcolumn;

char *logusercolumn;

char *loghostcolumn;

char *logrhostcolumn;

char *logtimecolumn;

char *config_file;

char *my_host_info;

} pam_mysql_ctx_t; /*Max length for most MySQL fields is 16 */

/* }}} */

/* {{{ typedef enum pam_mysql_err_t */

typedef enum _pam_mysql_err_t pam_mysql_err_t;

/* }}} */

/* {{{ typedef enum pam_mysql_config_token_t */

typedef enum _pam_mysql_config_token_t pam_mysql_config_token_t;

/* }}} */

/* {{{ typedef (func) pam_mysql_option_getter_t */

typedef int(*pam_mysql_option_getter_t)(void *val, const char **pretval, int *to_release);

/* }}} */

/* {{{ typedef (func) pam_mysql_option_setter_t */

typedef int(*pam_mysql_option_setter_t)(void *val, const char *newval_str);

/* }}} */

/* {{{ typedef struct pam_mysql_option_accessor_t */

typedef struct _pam_mysql_option_accessor_t {

pam_mysql_option_getter_t get_op;

pam_mysql_option_setter_t set_op;

} pam_mysql_option_accessor_t;

/* }}} */

/* {{{ typedef struct pam_mysql_option_t */

typedef struct _pam_mysql_option_t {

const char *name;

size_t name_len;

size_t offset;

pam_mysql_option_accessor_t *accessor;

} pam_mysql_option_t;

/* }}} */

/* {{{ typedef struct pam_mysql_str_t */

typedef struct _pam_mysql_str_t {

char *p;

size_t len;

size_t alloc_size;

int mangle;

} pam_mysql_str_t;

/* }}} */

/* {{{ typedef (func) pam_mysql_handle_entry_fn_t */

struct _pam_mysql_entry_handler_t;

typedef pam_mysql_err_t (*pam_mysql_handle_entry_fn_t)(

struct _pam_mysql_entry_handler_t *, int, const char *, size_t,

const char *, size_t);

/* }}} */

/* {{{ typedef struct pam_mysql_entry_handler_t */

typedef struct _pam_mysql_entry_handler_t {

pam_mysql_ctx_t *ctx;

pam_mysql_handle_entry_fn_t handle_entry_fn;

pam_mysql_option_t *options;

} pam_mysql_entry_handler_t;

/* }}} */

/* {{{ typedef struct pam_mysql_config_parser_t */

typedef struct _pam_mysql_config_parser_t {

pam_mysql_ctx_t *ctx;

pam_mysql_entry_handler_t *hdlr;

} pam_mysql_config_parser_t;

/* }}} */

/* {{{ typedef struct pam_mysql_stream_t */

typedef struct _pam_mysql_stream_t {

int fd;

unsigned char buf[2][2048];

unsigned char *buf_start;

unsigned char *buf_ptr;

unsigned char *buf_end;

unsigned char *pushback;

size_t buf_in_use;

int eof;

} pam_mysql_stream_t;

/* }}} */

/* {{{ typedef struct _pam_mysql_config_scanner_t */

typedef struct _pam_mysql_config_scanner_t {

pam_mysql_ctx_t *ctx;

pam_mysql_str_t image;

pam_mysql_config_token_t token;

pam_mysql_stream_t *stream;

int state;

} pam_mysql_config_scanner_t;

/* }}} */

/* }}} */

/* {{{ prototypes */

/* {{{ General PAM Prototypes */

PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh,

int flags, int argc, const char **argv);

PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc,

const char **argv);

PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc,

const char **argv);

PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc,

const char **argv);

PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,

const char **argv);

PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,

const char **argv);

/* }}} */

/* {{{ static prototypes */

static void pam_mysql_cleanup_hdlr(pam_handle_t *pamh, void * voiddata, int status);

/* {{{ pam_mysql methods */

static pam_mysql_err_t pam_mysql_retrieve_ctx(pam_mysql_ctx_t **pretval, pam_handle_t *pamh);

static pam_mysql_err_t pam_mysql_init_ctx(pam_mysql_ctx_t *);

static void pam_mysql_destroy_ctx(pam_mysql_ctx_t *);

static void pam_mysql_saltify(pam_mysql_ctx_t *, char *salt, const char *salter );

static pam_mysql_err_t pam_mysql_parse_args(pam_mysql_ctx_t *, int argc, const char **argv);

static pam_mysql_err_t pam_mysql_open_db(pam_mysql_ctx_t *);

static void pam_mysql_close_db(pam_mysql_ctx_t *);

static pam_mysql_err_t pam_mysql_check_passwd(pam_mysql_ctx_t *ctx,

const char *user, const char *passwd, int null_inhibited);

static pam_mysql_err_t pam_mysql_update_passwd(pam_mysql_ctx_t *,

const char *user, const char *new_passwd);

static pam_mysql_err_t pam_mysql_query_user_stat(pam_mysql_ctx_t *,

int *pretval, const char *user);

static pam_mysql_err_t pam_mysql_query_user_caps(pam_mysql_ctx_t *,

int *pretval, const char *user);

static pam_mysql_err_t pam_mysql_sql_log(pam_mysql_ctx_t *, const char *msg,

const char *user, const char *host);

static pam_mysql_err_t pam_mysql_get_host_info(pam_mysql_ctx_t *,

const char **pretval);

/* }}} */

static size_t strnncpy(char *dest, size_t dest_size, const char *src, size_t src_len);

static void *xcalloc(size_t nmemb, size_t size);

static char *xstrdup(const char *ptr);

static void xfree(void *ptr);

static void xfree_overwrite(char *ptr);

/* }}} */

/* }}} */

/* {{{ strnncpy */

static size_t strnncpy(char *dest, size_t dest_size, const char *src, size_t src_len)

{

size_t cpy_len;

dest_size--;

cpy_len = (dest_size < src_len ? dest_size: src_len);

memcpy(dest, src, cpy_len);

dest[cpy_len] = '\0';

return cpy_len;

}

/* }}} */

/* {{{ xcalloc */

static void *xcalloc(size_t nmemb, size_t size)

{

void *retval;

double v = ((double)size) * (int)(nmemb & (((size_t)-1) >> 1));

if (v != nmemb * size) {

return NULL;

}

retval = calloc(nmemb, size);

return retval;

}

/* }}} */

/* {{{ xrealloc */

static void *xrealloc(void *ptr, size_t nmemb, size_t size)

{

void *retval;

size_t total = nmemb * size;

if (((double)size) * (int)(nmemb & (((size_t)-1) >> 1)) != total) {

return NULL;

}

retval = realloc(ptr, total);

return retval;

}

/* }}} */

/* {{{ xstrdup */

static char *xstrdup(const char *ptr)

{

size_t len = strlen(ptr) + sizeof(char);

char *retval = xcalloc(sizeof(char), len);

if (retval == NULL) {

return NULL;

}

memcpy(retval, ptr, len);

return retval;

}

/* }}} */

/* {{{ xfree */

static void xfree(void *ptr)

{

if (ptr != NULL) {

free(ptr);

}

}

/* }}} */

/* {{{ xfree_overwrite */

static void xfree_overwrite(char *ptr)

{

if (ptr != NULL) {

char *p;

for (p = ptr; *p != '\0'; p++) {

*p = '\0';

}

free(ptr);

}

}

/* }}} */

/* {{{ memspn */

static void *memspn(void *buf, size_t buf_len, const unsigned char *delims,

size_t ndelims)

{

unsigned char *buf_end = ((unsigned char *)buf) + buf_len;

unsigned char *p;

switch (ndelims) {

case 0:

return buf_end;

case 1: {

unsigned char c = delims[0];

for (p = (unsigned char *)buf; p < buf_end; p++) {

if (*p != c) {

return (void *)p;

}

}

} break;

case 2: {

unsigned char c1 = delims[0], c2 = delims[1];

for (p = (unsigned char *)buf; p < buf_end; p++) {

if (*p != c1 && *p != c2) {

return (void *)p;

}

}

} break;

default: {

const unsigned char *delims_end = delims + ndelims;

unsigned char and_mask = ~0, or_mask = 0;

const unsigned char *q;

for (q = delims; q < delims_end; q++) {

and_mask &= *q;

or_mask |= *q;

}

for (p = (unsigned char *)buf; p < buf_end; p++) {

if ((*p & and_mask) == and_mask && (*p & or_mask) != 0) {

for (q = delims; *p != *q; q++) {

if (q >= delims_end) {

return (void *)p;

}

}

}

}

} break;

}

return NULL;

}

/* }}} */

/* {{{ memcspn */

static void *memcspn(void *buf, size_t buf_len, const unsigned char *delims,

size_t ndelims)

{

if (ndelims == 1) {

return memchr(buf, delims[0], buf_len);

} else {

unsigned char *buf_end = ((unsigned char *)buf) + buf_len;

const unsigned char *delims_end = delims + ndelims;

unsigned char *p;

for (p = (unsigned char *)buf; p < buf_end; p++) {

const unsigned char *q;

for (q = delims; q < delims_end; q++) {

if (*p == *q) {

return (void *)p;

}

}

}

return NULL;

}

}

/* }}} */

/* {{{ pam_mysql_md5_data

*

* AFAIK, only FreeBSD has MD5Data() defined in md5.h

* better MD5 support will appear in 0.5

*/

#ifdef HAVE_MD5DATA

#define HAVE_PAM_MYSQL_MD5_DATA

#define pam_mysql_md5_data MD5Data

#elif defined(HAVE_OPENSSL) || (defined(HAVE_SASL_MD5_H) && defined(USE_SASL_MD5)) || (!defined(HAVE_OPENSSL) && defined(HAVE_SOLARIS_MD5))

#if defined(USE_SASL_MD5)

static unsigned char *MD5(const unsigned char *d, unsigned int n,

unsigned char *md)

{

MD5_CTX ctx;

_sasl_MD5Init(&ctx);

_sasl_MD5Update(&ctx, (unsigned char *)d, n);

_sasl_MD5Final(md, &ctx);

return md;

}

#elif defined(USE_SOLARIS_MD5)

#define MD5(d, n, md) md5_calc(d, md, n)

#endif

#define HAVE_PAM_MYSQL_MD5_DATA

static char *pam_mysql_md5_data(const unsigned char *d, unsigned int sz, char *md)

{

size_t i, j;

unsigned char buf[16];

if (md == NULL) {

if ((md = xcalloc(32 + 1, sizeof(char))) == NULL) {

return NULL;

}

}

MD5(d, (unsigned long)sz, buf);

for (i = 0, j = 0; i < 16; i++, j += 2) {

md[j + 0] = "0123456789abcdef"[(int)(buf[i] >> 4)];

md[j + 1] = "0123456789abcdef"[(int)(buf[i] & 0x0f)];

}

md[j] = '\0';

return md;

}

#endif

/* }}} */

/* {{{ pam_mysql_sha1_data */

#if defined(HAVE_OPENSSL)

#define HAVE_PAM_MYSQL_SHA1_DATA

static char *pam_mysql_sha1_data(const unsigned char *d, unsigned int sz, char *md)

{

size_t i, j;

unsigned char buf[20];

if (md == NULL) {

if ((md = xcalloc(40 + 1, sizeof(char))) == NULL) {

return NULL;

}

}

SHA1(d, (unsigned long)sz, buf);

for (i = 0, j = 0; i < 20; i++, j += 2) {

md[j + 0] = "0123456789abcdef"[(int)(buf[i] >> 4)];

md[j + 1] = "0123456789abcdef"[(int)(buf[i] & 0x0f)];

}

md[j] = '\0';

return md;

}

#endif

/* }}} */

/* {{{ option handlers */

/* {{{ pam_mysql_string_opt_getter */

static pam_mysql_err_t pam_mysql_string_opt_getter(void *val, const char **pretval, int *to_release)

{

*pretval = *(char **)val;

*to_release = 0;

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_string_opt_setter */

static pam_mysql_err_t pam_mysql_string_opt_setter(void *val, const char *newval_str)

{

if (*(char **)val != NULL) {

xfree(*(char **)val);

}

if (NULL == (*(char **)val = xstrdup(newval_str))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

return PAM_MYSQL_ERR_ALLOC;

}

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_boolean_opt_getter

*/

static pam_mysql_err_t pam_mysql_boolean_opt_getter(void *val, const char **pretval, int *to_release)

{

*pretval = (*(int *)val ? "true": "false");

*to_release = 0;

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_boolean_opt_setter */

static pam_mysql_err_t pam_mysql_boolean_opt_setter(void *val, const char *newval_str)

{

*(int *)val = (strcmp(newval_str, "0") != 0 &&

strcasecmp(newval_str, "N") != 0 &&

strcasecmp(newval_str, "false") != 0 &&

strcasecmp(newval_str, "no") != 0);

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_crypt_opt_getter */

static pam_mysql_err_t pam_mysql_crypt_opt_getter(void *val, const char **pretval, int *to_release)

{

switch (*(int *)val) {

case 0:

*pretval = "plain";

break;

case 1:

*pretval = "Y";

break;

case 2:

*pretval = "mysql";

break;

case 3:

*pretval = "md5";

break;

case 4:

*pretval = "sha1";

break;

default:

*pretval = NULL;

}

*to_release = 0;

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_crypt_opt_setter */

static pam_mysql_err_t pam_mysql_crypt_opt_setter(void *val, const char *newval_str)

{

if (strcmp(newval_str, "0") == 0 || strcasecmp(newval_str, "plain") == 0) {

*(int *)val = 0;

return PAM_MYSQL_ERR_SUCCESS;

}

if (strcmp(newval_str, "1") == 0 || strcasecmp(newval_str, "Y") == 0) {

*(int *)val = 1;

return PAM_MYSQL_ERR_SUCCESS;

}

if (strcmp(newval_str, "2") == 0 || strcasecmp(newval_str, "mysql") == 0) {

*(int *)val = 2;

return PAM_MYSQL_ERR_SUCCESS;

}

if (strcmp(newval_str, "3") == 0 || strcasecmp(newval_str, "md5") == 0) {

*(int *)val = 3;

return PAM_MYSQL_ERR_SUCCESS;

}

if (strcmp(newval_str, "4") == 0 || strcasecmp(newval_str, "sha1") == 0) {

*(int *)val = 4;

return PAM_MYSQL_ERR_SUCCESS;

}

*(int *)val = 0;

return PAM_MYSQL_ERR_INVAL;

}

/* }}} */

/* }}} */

/* {{{ option definitions */

#define PAM_MYSQL_OFFSETOF(type, x) ((size_t)&((type *)0)->x)

#define PAM_MYSQL_DEF_OPTION(name, accr) PAM_MYSQL_DEF_OPTION2(name, name, accr)

#define PAM_MYSQL_DEF_OPTION2(name, sname, accr) \

{ #name, sizeof(#name) - 1, PAM_MYSQL_OFFSETOF(pam_mysql_ctx_t, sname), accr }

static pam_mysql_option_accessor_t pam_mysql_string_opt_accr = {

pam_mysql_string_opt_getter,

pam_mysql_string_opt_setter

};

static pam_mysql_option_accessor_t pam_mysql_boolean_opt_accr = {

pam_mysql_boolean_opt_getter,

pam_mysql_boolean_opt_setter

};

static pam_mysql_option_accessor_t pam_mysql_crypt_opt_accr = {

pam_mysql_crypt_opt_getter,

pam_mysql_crypt_opt_setter

};

static pam_mysql_option_t options[] = {

PAM_MYSQL_DEF_OPTION(host, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(where, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(db, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(user, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(passwd, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(table, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(update_table, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(usercolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(passwdcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(statcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(crypt, crypt_type, &pam_mysql_crypt_opt_accr),

PAM_MYSQL_DEF_OPTION(md5, &pam_mysql_boolean_opt_accr),

PAM_MYSQL_DEF_OPTION(sqllog, &pam_mysql_boolean_opt_accr),

PAM_MYSQL_DEF_OPTION(verbose, &pam_mysql_boolean_opt_accr),

PAM_MYSQL_DEF_OPTION(logtable, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(logmsgcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(logpidcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(logusercolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(loghostcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(logrhostcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(logtimecolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(config_file, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION(use_323_passwd, &pam_mysql_boolean_opt_accr),

PAM_MYSQL_DEF_OPTION(use_first_pass, &pam_mysql_boolean_opt_accr),

PAM_MYSQL_DEF_OPTION(try_first_pass, &pam_mysql_boolean_opt_accr),

PAM_MYSQL_DEF_OPTION(disconnect_every_op, &pam_mysql_boolean_opt_accr),

PAM_MYSQL_DEF_OPTION2(debug, verbose, &pam_mysql_boolean_opt_accr),

{ NULL, 0, 0, NULL }

};

/* }}} */

/* {{{ string functions */

/* {{{ pam_mysql_str_init() */

static pam_mysql_err_t pam_mysql_str_init(pam_mysql_str_t *str, int mangle)

{

str->p = "";

str->len = 0;

str->alloc_size = 0;

str->mangle = mangle;

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_str_destroy() */

static void pam_mysql_str_destroy(pam_mysql_str_t *str)

{

if (str->alloc_size > 0) {

if (str->mangle) {

memset(str->p, 0, str->len);

}

xfree(str->p);

}

}

/* }}} */

/* {{{ pam_mysql_str_reserve() */

static pam_mysql_err_t pam_mysql_str_reserve(pam_mysql_str_t *str, size_t len)

{

size_t len_req;

{

len_req = str->len + len;

if (len_req < str->len) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "integer overflow at " __FILE__ ":%d", __LINE__);

return PAM_MYSQL_ERR_INVAL;

}

len_req += sizeof(char); // space for a string terminator

}

if (len_req >= str->alloc_size) {

size_t cv = 0;

size_t new_size = (str->alloc_size == 0 ? 1: str->alloc_size);

char *new_buf;

do {

new_size *= 2;

if (cv > new_size) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

return PAM_MYSQL_ERR_ALLOC;

}

cv = new_size;

} while (new_size < len_req);

if (str->mangle) {

if (NULL == (new_buf = xcalloc(new_size, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

return PAM_MYSQL_ERR_ALLOC;

}

memcpy(new_buf, str->p, str->len);

memset(str->p, 0, str->len);

if (str->alloc_size > 0) {

xfree(str->p);

}

} else {

if (str->alloc_size == 0) {

if (NULL == (new_buf = xcalloc(new_size, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

return PAM_MYSQL_ERR_ALLOC;

}

} else {

if (NULL == (new_buf = xrealloc(str->p, new_size, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

return PAM_MYSQL_ERR_ALLOC;

}

}

}

str->p = new_buf;

str->alloc_size = new_size;

}

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_str_append() */

static pam_mysql_err_t pam_mysql_str_append(pam_mysql_str_t *str,

const char *s, size_t len)

{

pam_mysql_err_t err;

if ((err = pam_mysql_str_reserve(str, len))) {

return err;

}

memcpy(str->p + str->len, s, len);

str->len += len;

str->p[str->len] = '\0';

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_str_append_char() */

static pam_mysql_err_t pam_mysql_str_append_char(pam_mysql_str_t *str, char c)

{

return pam_mysql_str_append(str, &c, sizeof(c));

}

/* }}} */

/* {{{ pam_mysql_str_truncate() */

static pam_mysql_err_t pam_mysql_str_truncate(pam_mysql_str_t *str, size_t len)

{

if (len > str->len) {

return PAM_MYSQL_ERR_INVAL;

}

str->len = len;

if (str->alloc_size != 0) {

str->p[len] = '\0';

}

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* }}} */

/* {{{ stream functions */

/* {{{ pam_mysql_stream_open */

static pam_mysql_err_t pam_mysql_stream_open(pam_mysql_stream_t *stream,

pam_mysql_ctx_t *ctx, const char *file)

{

stream->buf_end = stream->buf_start = stream->buf_ptr = stream->buf[0];

stream->pushback = NULL;

stream->buf_in_use = 0;

stream->eof = 0;

if ((stream->fd = open(file, O_RDONLY)) == -1) {

if (ctx->verbose) {

switch (errno) {

case EACCES:

case EPERM:

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "access to %s not permitted", file);

break;

case EISDIR:

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "%s is directory", file);

break;

#ifdef HAVE_ELOOP

case ELOOP:

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "%s refers to an inresolvable symbolic link", file);

break;

#endif

case EMFILE:

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "too many opened files");

break;

case ENFILE:

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "too many opened files within this system");

break;

case ENOENT:

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "%s does not exist", file);

break;

case ENOMEM:

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "kernel resource exhausted");

break;

#ifdef HAVE_EOVERFLOW

case EOVERFLOW:

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "%s is too big", file);

break;

#endif

default:

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "unknown error while opening %s", file);

break;

}

}

return PAM_MYSQL_ERR_IO;

}

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_stream_close */

static void pam_mysql_stream_close(pam_mysql_stream_t *stream)

{

if (stream->fd != -1) {

close(stream->fd);

}

}

/* }}} */

/* {{{ pam_mysql_stream_getc */

static pam_mysql_err_t pam_mysql_stream_getc(pam_mysql_stream_t *stream,

int *retval)

{

if (stream->buf_ptr >= stream->buf_end) {

ssize_t new_buf_len;

unsigned char *new_buf = stream->buf[1 - stream->buf_in_use];

if (stream->pushback != NULL) {

stream->buf_end = stream->pushback;

stream->pushback = NULL;

} else {

if (stream->eof) {

return PAM_MYSQL_ERR_EOF;

}

if ((new_buf_len = read(stream->fd, new_buf, sizeof(stream->buf[0]))) == -1) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "I/O error");

return PAM_MYSQL_ERR_IO;

}

if (new_buf_len == 0) {

stream->eof = 1;

return PAM_MYSQL_ERR_EOF;

}

stream->buf_end = new_buf + new_buf_len;

}

stream->buf_start = stream->buf_ptr = new_buf;

stream->buf_in_use = 1 - stream->buf_in_use;

}

*retval = *(stream->buf_ptr++);

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_stream_ungetc */

static pam_mysql_err_t pam_mysql_stream_ungetc(pam_mysql_stream_t *stream, int c)

{

if (stream->buf_ptr == stream->buf_start) {

if (stream->pushback != NULL) {

return PAM_MYSQL_ERR_IO;

}

stream->pushback = stream->buf_end;

stream->buf_in_use = 1 - stream->buf_in_use;

stream->buf_start = stream->buf[stream->buf_in_use];

stream->buf_ptr = stream->buf_start + sizeof(stream->buf[0]);

}

*(--stream->buf_ptr) = c;

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_stream_skip_spn */

static pam_mysql_err_t pam_mysql_stream_skip_spn(pam_mysql_stream_t *stream,

const char *accept_cset, size_t naccepts)

{

unsigned char *p;

if (stream->eof) {

return PAM_MYSQL_ERR_EOF;

}

if ((p = (unsigned char *)memspn(stream->buf_ptr,

stream->buf_end - stream->buf_ptr, (const unsigned char *)accept_cset,

naccepts)) != NULL) {

stream->buf_ptr = p;

return PAM_MYSQL_ERR_SUCCESS;

}

if (stream->pushback != NULL) {

stream->buf_in_use = 1 - stream->buf_in_use;

stream->buf_start = stream->buf_ptr = stream->buf[stream->buf_in_use];

stream->buf_end = stream->pushback;

stream->pushback = NULL;

if ((p = (unsigned char *)memspn(stream->buf_ptr,

stream->buf_end - stream->buf_ptr, (const unsigned char *)accept_cset,

naccepts)) != NULL) {

stream->buf_ptr = p;

return PAM_MYSQL_ERR_SUCCESS;

}

}

for (;;) {

ssize_t new_buf_len;

if ((new_buf_len = read(stream->fd, stream->buf_start, sizeof(stream->buf[0]))) == -1) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "I/O error");

return PAM_MYSQL_ERR_IO;

}

if (new_buf_len == 0) {

stream->eof = 1;

return PAM_MYSQL_ERR_EOF;

}

stream->buf_end = stream->buf_start + new_buf_len;

if ((p = (unsigned char *)memspn(stream->buf_start, new_buf_len,

(const unsigned char *)accept_cset, naccepts)) != NULL) {

stream->buf_ptr = p;

break;

}

}

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_stream_read_cspn */

static pam_mysql_err_t pam_mysql_stream_read_cspn(pam_mysql_stream_t *stream,

pam_mysql_str_t *append_to, int *found_delim, const char *delims,

size_t ndelims)

{

pam_mysql_err_t err;

unsigned char *p;

ssize_t len;

size_t rem_len;

if (stream->eof) {

return PAM_MYSQL_ERR_EOF;

}

if ((p = (unsigned char *)memcspn(stream->buf_ptr,

stream->buf_end - stream->buf_ptr, (const unsigned char *)delims,

ndelims)) != NULL) {

if ((err = pam_mysql_str_append(append_to, (char *)stream->buf_ptr,

p - stream->buf_ptr))) {

return err;

}

*found_delim = *p;

stream->buf_ptr = p;

return PAM_MYSQL_ERR_SUCCESS;

}

if ((err = pam_mysql_str_append(append_to, (char *)stream->buf_ptr,

stream->buf_end - stream->buf_ptr))) {

return err;

}

if (stream->pushback != NULL) {

stream->buf_in_use = 1 - stream->buf_in_use;

stream->buf_start = stream->buf_ptr = stream->buf[stream->buf_in_use];

stream->buf_end = stream->pushback;

stream->pushback = NULL;

if ((p = (unsigned char *)memcspn(stream->buf_ptr,

stream->buf_end - stream->buf_ptr,

(const unsigned char *)delims, ndelims)) != NULL) {

if ((err = pam_mysql_str_append(append_to, (char *)stream->buf_ptr,

p - stream->buf_ptr))) {

return err;

}

*found_delim = *p;

stream->buf_ptr = p;

return PAM_MYSQL_ERR_SUCCESS;

}

if ((err = pam_mysql_str_append(append_to, (char *)stream->buf_ptr,

stream->buf_end - stream->buf_ptr))) {

return err;

}

}

rem_len = 0;

for (;;) {

unsigned char *block;

if ((err = pam_mysql_str_reserve(append_to,

sizeof(stream->buf[0]) - rem_len))) {

return err;

}

block = (unsigned char*)append_to->p + append_to->len;

if ((len = read(stream->fd, block, sizeof(stream->buf[0]))) == -1) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "I/O error");

return PAM_MYSQL_ERR_IO;

}

if (len == 0) {

stream->eof = 1;

return PAM_MYSQL_ERR_EOF;

}

if ((p = (unsigned char *)memcspn(block, len,

(const unsigned char *)delims, ndelims)) != NULL) {

size_t new_buf_len;

append_to->len += p - block;

new_buf_len = len - (p - block);

memcpy(stream->buf_start, p, new_buf_len);

stream->buf_ptr = stream->buf_start;

stream->buf_end = stream->buf_start + new_buf_len;

break;

}

append_to->len += len;

rem_len = sizeof(stream->buf[0]) - len;

}

*found_delim = *p;

append_to->p[append_to->len] = '\0';

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* }}} */

/* {{{ config file scanner / parser */

static const char * pam_mysql_config_token_name[] = {

"=",

"",

"",

";",

"",

NULL

};

/* {{{ pam_mysql_config_scanner_init */

static pam_mysql_err_t pam_mysql_config_scanner_init(

pam_mysql_config_scanner_t *scanner, pam_mysql_ctx_t *ctx,

pam_mysql_stream_t *stream)

{

pam_mysql_err_t err;

if ((err = pam_mysql_str_init(&scanner->image, 1))) {

return err;

}

scanner->ctx = ctx;

scanner->stream = stream;

scanner->state = 0;

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_config_scanner_destroy */

static void pam_mysql_config_scanner_destroy(

pam_mysql_config_scanner_t *scanner)

{

pam_mysql_str_destroy(&scanner->image);

}

/* }}} */

/* {{{ pam_mysql_config_scanner_next_token */

static pam_mysql_err_t pam_mysql_config_scanner_next_token(

pam_mysql_config_scanner_t *scanner)

{

pam_mysql_err_t err;

int c;

switch (scanner->state) {

case 0:

if ((err = pam_mysql_str_truncate(&scanner->image, 0))) {

return err;

}

if ((err = pam_mysql_stream_getc(scanner->stream, &c))) {

return err;

}

if (c == '#') {

if ((err = pam_mysql_str_append_char(&scanner->image, c))) {

return err;

}

if ((err = pam_mysql_stream_read_cspn(scanner->stream,

&scanner->image, &c, "\n\r", sizeof("\n\r") - 1))) {

return err;

}

scanner->token = PAM_MYSQL_CONFIG_TOKEN_COMMENT;

if ((err = pam_mysql_stream_getc(scanner->stream, &c))) {

return err;

}

if (c == '\r') {

if ((err = pam_mysql_stream_getc(scanner->stream, &c))) {

return err;

}

if (c != '\n') {

if ((err = pam_mysql_stream_ungetc(scanner->stream, c))) {

return err;

}

}

}

break;

} else {

if ((err = pam_mysql_stream_ungetc(scanner->stream, c))) {

return err;

}

}

case 1:

if ((err = pam_mysql_stream_skip_spn(scanner->stream,

" \t", sizeof(" \t") - 1))) {

return err;

}

if ((err = pam_mysql_str_truncate(&scanner->image, 0))) {

return err;

}

if ((err = pam_mysql_stream_getc(scanner->stream, &c))) {

return err;

}

if ((err = pam_mysql_str_append_char(&scanner->image, c))) {

return err;

}

switch (scanner->image.p[0]) {

case '=':

scanner->token = PAM_MYSQL_CONFIG_TOKEN_EQUAL;

scanner->state = 2;

break;

case ';':

scanner->token = PAM_MYSQL_CONFIG_TOKEN_SEMICOLON;

scanner->state = 1;

break;

default:

if ((err = pam_mysql_stream_read_cspn(scanner->stream,

&scanner->image, &c, "=; \t\n\r", sizeof("=; \t\n\r") - 1))) {

return err;

}

scanner->token = PAM_MYSQL_CONFIG_TOKEN_STRING;

scanner->state = 1;

break;

case '\r':

if ((err = pam_mysql_stream_getc(scanner->stream, &c))) {

return err;

}

if (c != '\n') {

if ((err = pam_mysql_stream_ungetc(scanner->stream, c))) {

return err;

}

} else {

if ((err = pam_mysql_str_append_char(&scanner->image, c))) {

return err;

}

}

scanner->token = PAM_MYSQL_CONFIG_TOKEN_NEWLINE;

scanner->state = 0;

break;

case '\n':

scanner->token = PAM_MYSQL_CONFIG_TOKEN_NEWLINE;

scanner->state = 0;

break;

}

break;

case 2:

if ((err = pam_mysql_stream_skip_spn(scanner->stream,

" \t", sizeof(" \t") - 1))) {

return err;

}

if ((err = pam_mysql_str_truncate(&scanner->image, 0))) {

return err;

}

if ((err = pam_mysql_stream_getc(scanner->stream, &c))) {

return err;

}

if ((err = pam_mysql_str_append_char(&scanner->image, c))) {

return err;

}

switch (scanner->image.p[0]) {

case ';':

scanner->token = PAM_MYSQL_CONFIG_TOKEN_SEMICOLON;

scanner->state = 1;

break;

default:

if ((err = pam_mysql_stream_read_cspn(scanner->stream,

&scanner->image, &c, ";\n\r", sizeof(";\n\r") - 1))) {

return err;

}

scanner->token = PAM_MYSQL_CONFIG_TOKEN_STRING;

break;

case '\r':

if ((err = pam_mysql_stream_getc(scanner->stream, &c))) {

return err;

}

if (c != '\n') {

if ((err = pam_mysql_stream_ungetc(scanner->stream, c))) {

return err;

}

} else {

if ((err = pam_mysql_str_append_char(&scanner->image, c))) {

return err;

}

}

scanner->token = PAM_MYSQL_CONFIG_TOKEN_NEWLINE;

scanner->state = 0;

break;

case '\n':

scanner->token = PAM_MYSQL_CONFIG_TOKEN_NEWLINE;

scanner->state = 0;

break;

}

break;

}

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_config_parser_init */

static pam_mysql_err_t pam_mysql_config_parser_init(

pam_mysql_config_parser_t *parser, pam_mysql_ctx_t *ctx,

pam_mysql_entry_handler_t *hdlr)

{

parser->ctx = ctx;

parser->hdlr = hdlr;

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_config_parser_destroy */

static void pam_mysql_config_parser_destroy(pam_mysql_config_parser_t *parser)

{

/* do nothing */

}

/* }}} */

/* {{{ pam_mysql_config_parser_parse */

static pam_mysql_err_t pam_mysql_config_parser_parse(

pam_mysql_config_parser_t *parser, pam_mysql_stream_t *stream)

{

pam_mysql_err_t err;

pam_mysql_config_scanner_t scanner;

char *name = NULL;

size_t name_len = 0;

char *value = NULL;

size_t value_len = 0;

int state = 0;

int line_num = 1;

if ((err = pam_mysql_config_scanner_init(&scanner, parser->ctx, stream))) {

return err;

}

while (!(err = pam_mysql_config_scanner_next_token(&scanner))) {

switch (state) {

case 0:

switch (scanner.token) {

case PAM_MYSQL_CONFIG_TOKEN_STRING:

if (name == NULL || name_len < scanner.image.len) {

char *new_buf;

if (NULL == (new_buf = xrealloc(name,

scanner.image.len + 1, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

name = new_buf;

}

memcpy(name, scanner.image.p, scanner.image.len + 1);

name_len = scanner.image.len;

state = 1;

break;

case PAM_MYSQL_CONFIG_TOKEN_NEWLINE:

line_num++;

break;

case PAM_MYSQL_CONFIG_TOKEN_COMMENT:

line_num++;

break;

default:

err = PAM_MYSQL_ERR_SYNTAX;

goto out;

}

break;

case 1:

switch (scanner.token) {

case PAM_MYSQL_CONFIG_TOKEN_EQUAL:

state = 2;

break;

default:

err = PAM_MYSQL_ERR_SYNTAX;

goto out;

}

break;

case 2:

switch (scanner.token) {

case PAM_MYSQL_CONFIG_TOKEN_SEMICOLON:

if (parser->hdlr &&

(err = parser->hdlr->handle_entry_fn(

parser->hdlr, line_num, name, name_len,

"", 0))) {

goto out;

}

state = 4;

break;

case PAM_MYSQL_CONFIG_TOKEN_NEWLINE:

if (parser->hdlr &&

(err = parser->hdlr->handle_entry_fn(

parser->hdlr, line_num, name, name_len,

"", 0))) {

goto out;

}

line_num++;

state = 0;

break;

case PAM_MYSQL_CONFIG_TOKEN_STRING:

if (value == NULL || value_len < scanner.image.len) {

char *new_buf;

if (NULL == (new_buf = xrealloc(value,

scanner.image.len + 1, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

value = new_buf;

}

memcpy(value, scanner.image.p, scanner.image.len + 1);

value_len = scanner.image.len;

if (parser->hdlr &&

(err = parser->hdlr->handle_entry_fn(

parser->hdlr, line_num, name, name_len,

value, value_len))) {

goto out;

}

state = 3;

break;

default:

err = PAM_MYSQL_ERR_SYNTAX;

goto out;

}

break;

case 3:

switch (scanner.token) {

case PAM_MYSQL_CONFIG_TOKEN_SEMICOLON:

state = 4;

break;

case PAM_MYSQL_CONFIG_TOKEN_NEWLINE:

line_num++;

state = 0;

break;

default:

err = PAM_MYSQL_ERR_SYNTAX;

goto out;

}

break;

case 4:

switch (scanner.token) {

case PAM_MYSQL_CONFIG_TOKEN_NEWLINE:

line_num++;

state = 0;

break;

default:

err = PAM_MYSQL_ERR_SYNTAX;

goto out;

}

break;

}

}

if (err == PAM_MYSQL_ERR_EOF) {

err = PAM_MYSQL_ERR_SUCCESS;

}

out:

if (err == PAM_MYSQL_ERR_SYNTAX) {

if (parser->ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "unexpected token %s on line %d\n",

pam_mysql_config_token_name[scanner.token],

line_num);

}

}

if (name != NULL) {

xfree(name);

}

if (value != NULL) {

xfree(value);

}

pam_mysql_config_scanner_destroy(&scanner);

return err;

}

/* }}} */

/* }}} */

/* {{{ pam_mysql_find_option()

*/

pam_mysql_option_t *pam_mysql_find_option(pam_mysql_option_t *options,

const char *name, size_t name_len)

{

/* set the various ctx */

pam_mysql_option_t *retval;

for (retval = options; retval->name != NULL; retval++) {

if (retval->name_len == name_len &&

memcmp(retval->name, name, name_len) == 0) {

return retval;

}

}

return NULL;

}

/* }}} */

/* {{{ entry handler */

static pam_mysql_option_t pam_mysql_entry_handler_options[] = {

PAM_MYSQL_DEF_OPTION2(users.host, host, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.database, db, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.db_user, user, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.db_passwd, passwd, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.where_clause, where, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.table, table, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.update_table, update_table, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.user_column, usercolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.password_column, passwdcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.status_column, statcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.password_crypt, crypt_type, &pam_mysql_crypt_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.use_md5, md5, &pam_mysql_boolean_opt_accr),

PAM_MYSQL_DEF_OPTION2(verbose, verbose, &pam_mysql_boolean_opt_accr),

PAM_MYSQL_DEF_OPTION2(log.enabled, sqllog, &pam_mysql_boolean_opt_accr),

PAM_MYSQL_DEF_OPTION2(log.table, logtable, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(log.message_column, logmsgcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(log.pid_column, logpidcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(log.user_column, logusercolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(log.host_column, loghostcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(log.rhost_column, logrhostcolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(log.time_column, logtimecolumn, &pam_mysql_string_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.use_323_password, use_323_passwd, &pam_mysql_boolean_opt_accr),

PAM_MYSQL_DEF_OPTION2(users.disconnect_every_operation, disconnect_every_op, &pam_mysql_boolean_opt_accr),

{ NULL, 0, 0, NULL }

};

/* {{{ pam_mysql_handle_entry */

static pam_mysql_err_t pam_mysql_handle_entry(

pam_mysql_entry_handler_t *hdlr, int line_num, const char *name,

size_t name_len, const char *value, size_t value_len)

{

pam_mysql_err_t err;

pam_mysql_option_t *opt = pam_mysql_find_option(hdlr->options, name,

name_len);

if (opt == NULL) {

if (hdlr->ctx->verbose) {

char buf[1024];

strnncpy(buf, sizeof(buf), name, name_len);

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "unknown option %s on line %d", buf, line_num);

}

return PAM_MYSQL_ERR_SUCCESS;

}

err = opt->accessor->set_op((void*)((char *)hdlr->ctx + opt->offset), value);

if (!err && hdlr->ctx->verbose) {

char buf[1024];

strnncpy(buf, sizeof(buf), name, name_len);

syslog(LOG_AUTHPRIV | LOG_INFO, PAM_MYSQL_LOG_PREFIX "option %s is set to \"%s\"", buf, value);

}

return err;

}

/* }}} */

/* {{{ pam_mysql_entry_handler_init */

static pam_mysql_err_t pam_mysql_entry_handler_init(

pam_mysql_entry_handler_t *hdlr, pam_mysql_ctx_t *ctx)

{

hdlr->handle_entry_fn = pam_mysql_handle_entry;

hdlr->ctx = ctx;

hdlr->options = pam_mysql_entry_handler_options;

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_entry_handler_destroy */

static void pam_mysql_entry_handler_destroy(

pam_mysql_entry_handler_t *hdlr)

{

/* do nothing */

}

/* }}} */

/* }}} */

// {{{ pam_mysql_get_host_info

static pam_mysql_err_t pam_mysql_get_host_info(pam_mysql_ctx_t *ctx,

const char **pretval)

{

char hostname[MAXHOSTNAMELEN + 1];

char *retval;

if (ctx->my_host_info) {

*pretval = ctx->my_host_info;

return PAM_MYSQL_ERR_SUCCESS;

}

if (gethostname(hostname, sizeof(hostname))) {

return PAM_MYSQL_ERR_UNKNOWN;

}

#ifdef HAVE_GETADDRINFO

{

struct addrinfo *ainfo = NULL;

static const struct addrinfo hint = {

AI_CANONNAME, PF_INET, 0, 0, 0, NULL, NULL, NULL

};

switch (getaddrinfo(hostname, NULL, &hint, &ainfo)) {

case 0:

break;

case EAI_MEMORY:

return PAM_MYSQL_ERR_ALLOC;

default:

return PAM_MYSQL_ERR_UNKNOWN;

}

switch (ainfo->ai_family) {

case AF_INET:

if (NULL == (retval = xcalloc(INET_ADDRSTRLEN, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

freeaddrinfo(ainfo);

return PAM_MYSQL_ERR_ALLOC;

}

if (!inet_ntop(AF_INET,

&((struct sockaddr_in *)ainfo->ai_addr)->sin_addr,

retval, INET_ADDRSTRLEN)) {

xfree(retval);

freeaddrinfo(ainfo);

return PAM_MYSQL_ERR_UNKNOWN;

}

break;

#ifdef HAVE_IPV6

case AF_INET6:

if (NULL == (retval = xcalloc(INET6_ADDRSTRLEN,sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

freeaddrinfo(ainfo);

return PAM_MYSQL_ERR_ALLOC;

}

if (!inet_ntop(AF_INET6,

&((struct sockaddr_in6 *)ainfo->ai_addr)->sin6_addr,

retval, INET6_ADDRSTRLEN)) {

xfree(retval);

freeaddrinfo(ainfo);

return PAM_MYSQL_ERR_UNKNOWN;

}

break;

#endif /* HAVE_IPV6 */

default:

freeaddrinfo(ainfo);

return PAM_MYSQL_ERR_NOTIMPL;

}

freeaddrinfo(ainfo);

}

#else

{

struct hostent *hent = NULL;

struct hostent *tmp;

size_t hent_size = sizeof(struct hostent) + 64;

int herr = 0;

#if defined(HAVE_SUNOS_GETHOSTBYNAME_R)

for (;;) {

if (NULL == (tmp = xrealloc(hent, hent_size, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

if (hent != NULL) {

xfree(hent);

}

return PAM_MYSQL_ERR_ALLOC;

}

hent = tmp;

if (gethostbyname_r(hostname, hent,

(char *)hent + sizeof(struct hostent),

hent_size - sizeof(struct hostent), &herr) != NULL) {

break;

}

if (errno != ERANGE) {

xfree(hent);

return PAM_MYSQL_ERR_UNKNOWN;

}

if (hent_size + 32 < hent_size) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

xfree(hent);

return PAM_MYSQL_ERR_ALLOC;

}

hent_size += 32;

}

#elif defined(HAVE_GNU_GETHOSTBYNAME_R)

for (;;) {

if (NULL == (tmp = xrealloc(hent, hent_size, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

if (hent != NULL) {

xfree(hent);

}

return PAM_MYSQL_ERR_ALLOC;

}

hent = tmp;

if (!gethostbyname_r(hostname, hent,

(char *)hent + sizeof(struct hostent),

hent_size - sizeof(struct hostent), &tmp, &herr)) {

break;

}

if (errno != ERANGE) {

xfree(hent);

return PAM_MYSQL_ERR_UNKNOWN;

}

if (hent_size + 32 < hent_size) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

xfree(hent);

return PAM_MYSQL_ERR_ALLOC;

}

hent_size += 32;

}

#else

return PAM_MYSQL_ERR_NOTIMPL;

#endif

switch (hent->h_addrtype) {

case AF_INET:

if (NULL == (retval = xcalloc(INET_ADDRSTRLEN, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

xfree(hent);

return PAM_MYSQL_ERR_ALLOC;

}

if (!inet_ntop(AF_INET, hent->h_addr_list[0], retval,

INET_ADDRSTRLEN)) {

xfree(retval);

xfree(hent);

return PAM_MYSQL_ERR_UNKNOWN;

}

break;

#ifdef HAVE_IPV6

case AF_INET6:

if (NULL == (retval = xcalloc(INET6_ADDRSTRLEN,sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

xfree(hent);

return PAM_MYSQL_ERR_ALLOC;

}

if (!inet_ntop(AF_INET6, hent->h_addr_list[0], retval,

INET6_ADDRSTRLEN)) {

xfree(retval);

xfree(hent);

return PAM_MYSQL_ERR_UNKNOWN;

}

break;

#endif /* HAVE_IPV6 */

default:

xfree(hent);

return PAM_MYSQL_ERR_NOTIMPL;

}

xfree(hent);

}

#endif /* HAVE_GETADDRINFO */

*pretval = ctx->my_host_info = retval;

return PAM_MYSQL_ERR_SUCCESS;

}

/* {{{ pam_mysql_init_ctx()

*/

static pam_mysql_err_t pam_mysql_init_ctx(pam_mysql_ctx_t *ctx)

{

ctx->mysql_hdl = NULL;

ctx->host = NULL;

ctx->where = NULL;

ctx->db = NULL;

ctx->user = NULL;

ctx->passwd = NULL;

ctx->table = NULL;

ctx->update_table =NULL;

ctx->usercolumn = NULL;

ctx->passwdcolumn = NULL;

ctx->statcolumn = xstrdup("0");

ctx->crypt_type = 0;

ctx->use_323_passwd = 0;

ctx->md5 = -1;

ctx->sqllog = 0;

ctx->verbose = 0;

ctx->use_first_pass = 0;

ctx->try_first_pass = 1;

ctx->disconnect_every_op = 0;

ctx->logtable = NULL;

ctx->logmsgcolumn = NULL;

ctx->logpidcolumn = NULL;

ctx->logusercolumn = NULL;

ctx->loghostcolumn = NULL;

ctx->logrhostcolumn = NULL;

ctx->logtimecolumn = NULL;

ctx->config_file = NULL;

ctx->my_host_info = NULL;

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_destroy_ctx() */

static void pam_mysql_destroy_ctx(pam_mysql_ctx_t *ctx)

{

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_destroy_ctx() called.");

}

pam_mysql_close_db(ctx);

xfree(ctx->host);

ctx->host = NULL;

xfree(ctx->where);

ctx->where = NULL;

xfree(ctx->db);

ctx->db = NULL;

xfree(ctx->user);

ctx->user = NULL;

xfree(ctx->passwd);

ctx->passwd = NULL;

xfree(ctx->table);

ctx->table = NULL;

xfree(ctx->update_table);

ctx->update_table = NULL;

xfree(ctx->usercolumn);

ctx->usercolumn = NULL;

xfree(ctx->passwdcolumn);

ctx->passwdcolumn = NULL;

xfree(ctx->statcolumn);

ctx->statcolumn = NULL;

xfree(ctx->logtable);

ctx->logtable = NULL;

xfree(ctx->logmsgcolumn);

ctx->logmsgcolumn = NULL;

xfree(ctx->logpidcolumn);

ctx->logpidcolumn = NULL;

xfree(ctx->logusercolumn);

ctx->logusercolumn = NULL;

xfree(ctx->loghostcolumn);

ctx->loghostcolumn = NULL;

xfree(ctx->logrhostcolumn);

ctx->logrhostcolumn = NULL;

xfree(ctx->logtimecolumn);

ctx->logtimecolumn = NULL;

xfree(ctx->config_file);

ctx->config_file = NULL;

xfree(ctx->my_host_info);

ctx->my_host_info = NULL;

}

/* }}} */

/* {{{ pam_mysql_release_ctx() */

static void pam_mysql_release_ctx(pam_mysql_ctx_t *ctx)

{

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_release_ctx() called.");

}

if (ctx != NULL) {

pam_mysql_destroy_ctx(ctx);

xfree(ctx);

}

}

/* }}} */

/* {{{ pam_mysql_cleanup_hdlr() */

static void pam_mysql_cleanup_hdlr(pam_handle_t *pamh, void * voiddata, int status)

{

pam_mysql_release_ctx((pam_mysql_ctx_t*)voiddata);

}

/* }}} */

/* {{{ pam_mysql_retrieve_ctx()

*/

pam_mysql_err_t pam_mysql_retrieve_ctx(pam_mysql_ctx_t **pretval, pam_handle_t *pamh)

{

pam_mysql_err_t err;

switch (pam_get_data(pamh, PAM_MODULE_NAME,

(PAM_GET_DATA_CONST void**)pretval)) {

case PAM_NO_MODULE_DATA:

*pretval = NULL;

break;

case PAM_SUCCESS:

break;

default:

return PAM_MYSQL_ERR_UNKNOWN;

}

if (*pretval == NULL) {

/* allocate global data space */

if (NULL == (*pretval = (pam_mysql_ctx_t*)xcalloc(1, sizeof(pam_mysql_ctx_t)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

return PAM_MYSQL_ERR_ALLOC;

}

/* give the data back to PAM for management */

if (pam_set_data(pamh, PAM_MODULE_NAME, (void*)*pretval, pam_mysql_cleanup_hdlr)) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "failed to set context to PAM at " __FILE__ ":%d", __LINE__);

xfree(*pretval);

*pretval = NULL;

return PAM_MYSQL_ERR_UNKNOWN;

}

if ((err = pam_mysql_init_ctx(*pretval))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "cannot initialize context at " __FILE__ ":%d", __LINE__);

pam_mysql_destroy_ctx(*pretval);

xfree(*pretval);

*pretval = NULL;

return err;

}

}

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_set_option()

*/

pam_mysql_err_t pam_mysql_set_option(pam_mysql_ctx_t *ctx, const char *name, size_t name_len, const char *val)

{

pam_mysql_option_t *opt = pam_mysql_find_option(options, name, name_len);

if (opt == NULL) {

if (ctx->verbose) {

char buf[1024];

strnncpy(buf, sizeof(buf), name, name_len);

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "unknown option: %s", buf);

}

return PAM_MYSQL_ERR_NO_ENTRY;

}

return opt->accessor->set_op((void *)((char *)ctx + opt->offset), val);

}

/* }}} */

/* {{{ pam_mysql_get_option()

*/

pam_mysql_err_t pam_mysql_get_option(pam_mysql_ctx_t *ctx, const char **pretval, int *to_release, const char *name, size_t name_len)

{

pam_mysql_option_t *opt = pam_mysql_find_option(options, name, name_len);

if (opt == NULL) {

if (ctx->verbose) {

char buf[1024];

strnncpy(buf, sizeof(buf), name, name_len);

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "unknown option: %s", buf);

}

return PAM_MYSQL_ERR_NO_ENTRY;

}

return opt->accessor->get_op((void *)((char *)ctx + opt->offset), pretval, to_release);

}

/* }}} */

/* {{{ pam_mysql_parse_args()

*/

pam_mysql_err_t pam_mysql_parse_args(pam_mysql_ctx_t *ctx, int argc, const char **argv)

{

pam_mysql_err_t err;

int param_changed = 0;

char *value = NULL;

int i;

/* process all the arguments */

for (i = 0; i < argc; i++) {

const char *name = argv[i];

size_t name_len;

if ((value = strchr(name, '=')) != NULL) {

name_len = (size_t)(value - name);

value++; /* get past the '=' */

} else {

name_len = strlen(name);

value = "";

}

err = pam_mysql_set_option(ctx, name, name_len, value);

if (err == PAM_MYSQL_ERR_NO_ENTRY) {

continue;

} else if (err) {

return err;

}

param_changed = 1;

if (ctx->verbose) {

char buf[1024];

strnncpy(buf, sizeof(buf), name, name_len);

syslog(LOG_AUTHPRIV | LOG_INFO, PAM_MYSQL_LOG_PREFIX "option %s is set to \"%s\"", buf, value);

}

}

/* close the database in case we get new args */

if (param_changed) {

pam_mysql_close_db(ctx);

}

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_read_config_file() */

static pam_mysql_err_t pam_mysql_read_config_file(pam_mysql_ctx_t *ctx,

const char *path)

{

pam_mysql_err_t err;

pam_mysql_entry_handler_t handler;

pam_mysql_config_parser_t parser;

pam_mysql_stream_t stream;

if ((err = pam_mysql_entry_handler_init(&handler, ctx))) {

return err;

}

if ((err = pam_mysql_stream_open(&stream, ctx, path))) {

pam_mysql_entry_handler_destroy(&handler);

return err;

}

if ((err = pam_mysql_config_parser_init(&parser, ctx, &handler))) {

pam_mysql_stream_close(&stream);

pam_mysql_entry_handler_destroy(&handler);

return err;

}

err = pam_mysql_config_parser_parse(&parser, &stream);

pam_mysql_config_parser_destroy(&parser);

pam_mysql_stream_close(&stream);

pam_mysql_entry_handler_destroy(&handler);

return err;

}

/* }}} */

/* {{{ pam_mysql_open_db()

*/

static pam_mysql_err_t pam_mysql_open_db(pam_mysql_ctx_t *ctx)

{

pam_mysql_err_t err;

char *host = NULL;

char *socket = NULL;

int port = 0;

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_open_db() called.");

}

if (ctx->mysql_hdl != NULL) {

return PAM_MYSQL_ERR_BUSY;

}

if (NULL == (ctx->mysql_hdl = xcalloc(1, sizeof(MYSQL)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

return PAM_MYSQL_ERR_ALLOC;

}

if (ctx->user == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "required option \"user\" is not set");

return PAM_MYSQL_ERR_INVAL;

}

if (ctx->db == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "required option \"db\" is not set");

return PAM_MYSQL_ERR_INVAL;

}

if (ctx->host != NULL) {

if (ctx->host[0] == '/') {

host = NULL;

socket = ctx->host;

} else {

char *p;

if ((p = strchr(ctx->host, ':')) != NULL) {

size_t len = (size_t)(p - ctx->host);

if (NULL == (host = xcalloc(len + 1, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

return PAM_MYSQL_ERR_ALLOC;

}

memcpy(host, ctx->host, len);

host[len] = '\0';

port = strtol(p + 1, NULL, 10);

} else {

host = ctx->host;

}

socket = NULL;

}

}

if (NULL == mysql_init(ctx->mysql_hdl)) {

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

if (NULL == mysql_real_connect(ctx->mysql_hdl, host,

ctx->user, (ctx->passwd == NULL ? "": ctx->passwd),

ctx->db, port, socket, 0)) {

err = PAM_MYSQL_ERR_DB;

goto out;

}

if (mysql_select_db(ctx->mysql_hdl, ctx->db)) {

err = PAM_MYSQL_ERR_DB;

goto out;

}

err = PAM_MYSQL_ERR_SUCCESS;

out:

if (err == PAM_MYSQL_ERR_DB) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "MySQL error (%s)\n", mysql_error(ctx->mysql_hdl));

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_open_db() returning %d.", err);

}

if (host != ctx->host) {

xfree(host);

}

return err;

}

/* }}} */

/* {{{ pam_mysql_close_db()

*/

static void pam_mysql_close_db(pam_mysql_ctx_t *ctx)

{

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_close_db() called.");

}

if (ctx->mysql_hdl == NULL) {

return; /* closed already */

}

/* Memory leak fix */

mysql_library_end();

/* if mysql server version < 4.1.10 */

/* mysql_server_end() */

mysql_close(ctx->mysql_hdl);

xfree(ctx->mysql_hdl);

ctx->mysql_hdl = NULL;

}

/* }}} */

/* {{{ pam_mysql_quick_escape()

*/

static pam_mysql_err_t pam_mysql_quick_escape(pam_mysql_ctx_t *ctx, pam_mysql_str_t *append_to, const char *val, size_t val_len)

{

size_t len;

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_quick_escape() called.");

}

if (val_len >= (((size_t)-1)>>1) || pam_mysql_str_reserve(append_to, val_len * 2)) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

return PAM_MYSQL_ERR_ALLOC;

}

#ifdef HAVE_MYSQL_REAL_ESCAPE_STRING

len = mysql_real_escape_string(ctx->mysql_hdl, &append_to->p[append_to->len], val, val_len);

#else

len = mysql_escape_string(&append_to->p[append_to->len], val, val_len);

#endif

append_to->p[append_to->len += len] = '\0';

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ pam_mysql_format_string() */

static pam_mysql_err_t pam_mysql_format_string(pam_mysql_ctx_t *ctx,

pam_mysql_str_t *pretval, const char *template, int mangle, ...)

{

pam_mysql_err_t err = PAM_MYSQL_ERR_SUCCESS;

const char *p;

const char *name = NULL;

const char *commit_ptr;

int state;

va_list ap;

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_format_string() called");

}

va_start(ap, mangle);

state = 0;

for (commit_ptr = p = template; *p != '\0'; p++) {

switch (state) {

case 0:

if (*p == '%') {

if ((err = pam_mysql_str_append(pretval, commit_ptr, (size_t)(p - commit_ptr)))) {

goto out;

}

commit_ptr = p;

state = 1;

}

break;

case 1:

switch (*p) {

case '{':

state = 2;

break;

case '[':

state = 4;

break;

case 's': {

const char *val = va_arg(ap, char *);

if ((err = pam_mysql_quick_escape(ctx, pretval, val, strlen(val)))) {

goto out;

}

state = 0;

commit_ptr = p + 1;

} break;

case 'S': {

const char *val = va_arg(ap, char *);

if ((err = pam_mysql_str_append(pretval, val, strlen(val)))) {

goto out;

}

state = 0;

commit_ptr = p + 1;

} break;

case 'u': {

char buf[128];

unsigned int val = va_arg(ap, unsigned int);

char *q = buf + sizeof(buf);

while (--q >= buf) {

*q = "0123456789"[val % 10];

val /= 10;

if (val == 0) break;

}

if ((err = pam_mysql_str_append(pretval, q, sizeof(buf) - (size_t)(q - buf)))) {

goto out;

}

state = 0;

commit_ptr = p + 1;

} break;

default:

if ((err = pam_mysql_str_append_char(pretval, '%'))) {

goto out;

}

if ((err = pam_mysql_str_append_char(pretval, *p))) {

goto out;

}

state = 0;

commit_ptr = p + 1;

break;

}

break;

case 2:

name = p;

state = 3;

break;

case 3:

if (*p == '}') {

const char *val;

int to_release;

if ((err = pam_mysql_get_option(ctx, &val, &to_release, name, (size_t)(p - name)))) {

goto out;

}

if (val == NULL) {

val = xstrdup("");

}

if ((err = pam_mysql_quick_escape(ctx, pretval, val, strlen(val)))) {

if (to_release) {

xfree((char *)val);

}

goto out;

}

if (to_release) {

xfree((char *)val);

}

state = 0;

commit_ptr = p + 1;

}

break;

case 4:

name = p;

state = 5;

break;

case 5:

if (*p == ']') {

const char *val;

int to_release;

if ((err = pam_mysql_get_option(ctx, &val, &to_release, name, (size_t)(p - name)))) {

goto out;

}

if (val == NULL) {

val = xstrdup("");

}

if ((err = pam_mysql_str_append(pretval, val, strlen(val)))) {

if (to_release) {

xfree((char *)val);

}

goto out;

}

if (to_release) {

xfree((char *)val);

}

state = 0;

commit_ptr = p + 1;

}

break;

}

}

if (commit_ptr < p) {

if ((err = pam_mysql_str_append(pretval, commit_ptr, (size_t)(p - commit_ptr)))) {

goto out;

}

}

out:

if (err) {

pam_mysql_str_destroy(pretval);

}

va_end(ap);

return err;

}

/* }}} */

/* {{{ pam_mysql_check_passwd

*/

static pam_mysql_err_t pam_mysql_check_passwd(pam_mysql_ctx_t *ctx,

const char *user, const char *passwd, int null_inhibited)

{

pam_mysql_err_t err;

pam_mysql_str_t query;

MYSQL_RES *result = NULL;

MYSQL_ROW row;

int vresult;

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_check_passwd() called.");

}

/* To avoid putting a plain password in the MySQL log file and on

* the wire more than needed we will request the encrypted password

* from MySQL. We will check encrypt the passed password against the

* one returned from MySQL.

*/

if ((err = pam_mysql_str_init(&query, 1))) {

return err;

}

err = pam_mysql_format_string(ctx, &query,

(ctx->where == NULL ?

"SELECT %[passwdcolumn] FROM %[table] WHERE %[usercolumn] = '%s'":

"SELECT %[passwdcolumn] FROM %[table] WHERE %[usercolumn] = '%s' AND (%S)"),

1, user, ctx->where);

if (err) {

goto out;

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "%s", query.p);

}

#ifdef HAVE_MYSQL_REAL_QUERY

if (mysql_real_query(ctx->mysql_hdl, query.p, query.len)) {

#else

if (mysql_query(ctx->mysql_hdl, query.p)) {

#endif

err = PAM_MYSQL_ERR_DB;

goto out;

}

if (NULL == (result = mysql_store_result(ctx->mysql_hdl))) {

err = PAM_MYSQL_ERR_DB;

goto out;

}

switch (mysql_num_rows(result)) {

case 0:

syslog(LOG_AUTHPRIV | LOG_ERR, "%s", PAM_MYSQL_LOG_PREFIX "SELECT returned no result.");

err = PAM_MYSQL_ERR_NO_ENTRY;

goto out;

case 1:

break;

case 2:

syslog(LOG_AUTHPRIV | LOG_ERR, "%s", PAM_MYSQL_LOG_PREFIX "SELECT returned an indetermined result.");

err = PAM_MYSQL_ERR_UNKNOWN;

goto out;

}

/* Grab the password from RESULT_SET. */

if (NULL == (row = mysql_fetch_row(result))) {

err = PAM_MYSQL_ERR_DB;

goto out;

}

vresult = -1;

if (row[0] != NULL) {

if (passwd != NULL) {

switch (ctx->crypt_type) {

/* PLAIN */

case 0:

vresult = strcmp(row[0], passwd);

break;

/* ENCRYPT */

case 1:

vresult = strcmp(row[0], crypt(passwd, row[0]));

if (errno) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "something went wrong when invoking crypt() - %s", strerror(errno));

}

break;

/* PASSWORD */

case 2: {

char buf[42];

#ifdef HAVE_MAKE_SCRAMBLED_PASSWORD_323

if (ctx->use_323_passwd) {

make_scrambled_password_323(buf, passwd);

} else {

make_scrambled_password(buf, passwd);

}

#else

make_scrambled_password(buf, passwd);

#endif

vresult = strcmp(row[0], buf);

{

char *p = buf - 1;

while (*(++p)) *p = '\0';

}

} break;

/* MD5 hash (not MD5 crypt()) */

case 3: {

#ifdef HAVE_PAM_MYSQL_MD5_DATA

char buf[33];

pam_mysql_md5_data((unsigned char*)passwd, strlen(passwd),

buf);

vresult = strcmp(row[0], buf);

{

char *p = buf - 1;

while (*(++p)) *p = '\0';

}

#else

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "non-crypt()ish MD5 hash is not supported in this build.");

#endif

} break;

case 4: {

#ifdef HAVE_PAM_MYSQL_SHA1_DATA

char buf[41];

pam_mysql_sha1_data((unsigned char*)passwd, strlen(passwd),

buf);

vresult = strcmp(row[0], buf);

{

char *p = buf - 1;

while (*(++p)) *p = '\0';

}

#else

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "non-crypt()ish SHA1 hash is not supported in this build.");

#endif

} break;

default: {

}

}

}

} else {

vresult = null_inhibited;

}

if (vresult == 0) {

err = PAM_MYSQL_ERR_SUCCESS;

} else {

err = PAM_MYSQL_ERR_MISMATCH;

}

out:

if (err == PAM_MYSQL_ERR_DB) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "MySQL error(%s)", mysql_error(ctx->mysql_hdl));

}

if (result != NULL) {

mysql_free_result(result);

}

pam_mysql_str_destroy(&query);

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_check_passwd() returning %i.", err);

}

return err;

}

/* }}} */

/* {{{ pam_mysql_saltify()

* Create a random salt for use with CRYPT() when changing passwords */

static void pam_mysql_saltify(pam_mysql_ctx_t *ctx, char *salt, const char *salter)

{

unsigned int i = 0;

char *q;

unsigned int seed = 0;

static const char saltstr[] =

"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789./";

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "saltify called.");

}

if (salter != NULL) {

const char *p;

for (p = salter; *p != '\0'; p++) {

seed += salter[i];

}

}

#ifdef HAVE_GETTIMEOFDAY

{

struct timeval *tv;

if (gettimeofday(&tv, NULL)) {

tv.tv_usec = 0;

}

seed = (unsigned int)((tv.tv_usec + j) % 65535);

}

#endif

q = salt;

if (ctx->md5) {

strcpy(salt, "$1$");

q += sizeof("$1$") - 1;

i = 8;

} else {

i = 2;

}

while (i-- > 0) {

*(q++) = saltstr[seed % 64];

seed = (((seed ^ 0x967e3c5a) << 3) ^ (~(seed >> 2) + i));

}

if (ctx->md5) {

*(q++) = '$';

}

*q = '\0';

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_saltify() returning salt = %s.", salt);

}

}

/* }}} */

/* {{{ pam_mysql_update_passwd

* Update the password in MySQL

* To reduce the number of calls to the DB, I'm now assuming that the old

* password has been verified elsewhere, so I only check for null/not null

* and is_root. */

static pam_mysql_err_t pam_mysql_update_passwd(pam_mysql_ctx_t *ctx, const char *user, const char *new_passwd)

{

pam_mysql_err_t err = PAM_MYSQL_ERR_SUCCESS;

pam_mysql_str_t query;

char *encrypted_passwd = NULL;

if ((err = pam_mysql_str_init(&query, 1))) {

return err;

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_update_passwd() called.");

}

if (user == NULL) {

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "user is NULL.");

}

syslog(LOG_NOTICE, PAM_MYSQL_LOG_PREFIX "unable to change password");

return PAM_MYSQL_ERR_INVAL;

}

if (new_passwd != NULL) {

switch (ctx->crypt_type) {

case 0:

if (NULL == (encrypted_passwd = xstrdup(new_passwd))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

break;

case 1: {

char salt[18];

pam_mysql_saltify(ctx, salt, new_passwd);

if (NULL == (encrypted_passwd = xstrdup(crypt(new_passwd, salt)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

} break;

case 2:

if (NULL == (encrypted_passwd = xcalloc(41 + 1, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

#ifdef HAVE_MAKE_SCRAMBLED_PASSWORD_323

if (ctx->use_323_passwd) {

make_scrambled_password_323(encrypted_passwd, new_passwd);

} else {

make_scrambled_password(encrypted_passwd, new_passwd);

}

#else

make_scrambled_password(encrypted_passwd, new_passwd);

#endif

break;

case 3:

#ifdef HAVE_PAM_MYSQL_MD5_DATA

if (NULL == (encrypted_passwd = xcalloc(32 + 1, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

pam_mysql_md5_data((unsigned char*)new_passwd,

strlen(new_passwd), encrypted_passwd);

#else

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "non-crypt()ish MD5 hash is not supported in this build.");

err = PAM_MYSQL_ERR_NOTIMPL;

goto out;

#endif

break;

case 4:

#ifdef HAVE_PAM_MYSQL_SHA1_DATA

if (NULL == (encrypted_passwd = xcalloc(40 + 1, sizeof(char)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

pam_mysql_sha1_data((unsigned char*)new_passwd,

strlen(new_passwd), encrypted_passwd);

#else

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "non-crypt()ish SHA1 hash is not supported in this build.");

err = PAM_MYSQL_ERR_NOTIMPL;

goto out;

#endif

break;

default:

encrypted_passwd = NULL;

break;

}

}

err = pam_mysql_format_string(ctx, &query,

(ctx->where == NULL ?

"UPDATE %[table] SET %[passwdcolumn] = '%s' WHERE %[usercolumn] = '%s'":

"UPDATE %[table] SET %[passwdcolumn] = '%s' WHERE %[usercolumn] = '%s' AND (%S)"),

1, (encrypted_passwd == NULL ? "": encrypted_passwd), user, ctx->where);

if (err) {

goto out;

}

#ifdef HAVE_MYSQL_REAL_QUERY

if (mysql_real_query(ctx->mysql_hdl, query.p, query.len)) {

#else

if (mysql_query(ctx->mysql_hdl, query.p)) {

#endif

err = PAM_MYSQL_ERR_DB;

goto out;

}

out:

if (err == PAM_MYSQL_ERR_DB) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "MySQL error (%s)", mysql_error(ctx->mysql_hdl));

}

if (encrypted_passwd != NULL) {

char *p;

for (p = encrypted_passwd; *p != '\0'; p++) {

*p = '\0';

}

xfree(encrypted_passwd);

}

pam_mysql_str_destroy(&query);

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_update_passwd() returning %i.", err);

}

return err;

}

/* }}} */

/* {{{ pam_mysql_query_user_stat */

static pam_mysql_err_t pam_mysql_query_user_stat(pam_mysql_ctx_t *ctx,

int *pretval, const char *user)

{

pam_mysql_err_t err = PAM_MYSQL_ERR_SUCCESS;

pam_mysql_str_t query;

MYSQL_RES *result = NULL;

MYSQL_ROW row;

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_query_user_stat() called.");

}

if ((err = pam_mysql_str_init(&query, 0))) {

return err;

}

err = pam_mysql_format_string(ctx, &query,

(ctx->where == NULL ?

"SELECT %[statcolumn], %[passwdcolumn] FROM %[table] WHERE %[usercolumn] = '%s'":

"SELECT %[statcolumn], %[passwdcolumn] FROM %[table] WHERE %[usercolumn] = '%s' AND (%S)"),

1, user, ctx->where);

if (err) {

goto out;

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "%s", query.p);

}

#ifdef HAVE_MYSQL_REAL_QUERY

if (mysql_real_query(ctx->mysql_hdl, query.p, query.len)) {

#else

if (mysql_query(ctx->mysql_hdl, query.p)) {

#endif

err = PAM_MYSQL_ERR_DB;

goto out;

}

if (NULL == (result = mysql_store_result(ctx->mysql_hdl))) {

err = PAM_MYSQL_ERR_DB;

goto out;

}

switch (mysql_num_rows(result)) {

case 0:

syslog(LOG_AUTHPRIV | LOG_ERR, "%s", PAM_MYSQL_LOG_PREFIX "SELECT returned no result.");

err = PAM_MYSQL_ERR_NO_ENTRY;

goto out;

case 1:

break;

case 2:

syslog(LOG_AUTHPRIV | LOG_ERR, "%s", PAM_MYSQL_LOG_PREFIX "SELECT returned an indetermined result.");

err = PAM_MYSQL_ERR_UNKNOWN;

goto out;

}

if (NULL == (row = mysql_fetch_row(result))) {

err = PAM_MYSQL_ERR_DB;

goto out;

}

if (row[0] == NULL) {

*pretval = PAM_MYSQL_USER_STAT_EXPIRED;

} else {

*pretval = strtol(row[0], NULL, 10) & ~PAM_MYSQL_USER_STAT_NULL_PASSWD;

}

if (row[1] == NULL) {

*pretval |= PAM_MYSQL_USER_STAT_NULL_PASSWD;

}

out:

if (err == PAM_MYSQL_ERR_DB) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "MySQL error (%s)", mysql_error(ctx->mysql_hdl));

}

if (result != NULL) {

mysql_free_result(result);

}

pam_mysql_str_destroy(&query);

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_query_user_stat() returning %i.", err);

}

return err;

}

/* }}} */

/* {{{ pam_mysql_sql_log()

*/

static pam_mysql_err_t pam_mysql_sql_log(pam_mysql_ctx_t *ctx, const char *msg, const char *user, const char *rhost)

{

pam_mysql_err_t err;

pam_mysql_str_t query;

const char *host;

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_sql_log() called.");

}

if ((err = pam_mysql_str_init(&query, 1))) {

return err;

}

if (!ctx->sqllog) {

err = PAM_MYSQL_ERR_SUCCESS;

goto out;

}

if (pam_mysql_get_host_info(ctx, &host)) {

host = "(unknown)";

}

if (ctx->logtable == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, "%s",

PAM_MYSQL_LOG_PREFIX "sqllog set but logtable not set");

return PAM_MYSQL_ERR_INVAL;

}

if (ctx->logmsgcolumn == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, "%s",

PAM_MYSQL_LOG_PREFIX "sqllog set but logmsgcolumn not set");

return PAM_MYSQL_ERR_INVAL;

}

if (ctx->logusercolumn == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, "%s",

PAM_MYSQL_LOG_PREFIX "sqllog set but logusercolumn not set");

return PAM_MYSQL_ERR_INVAL;

}

if (ctx->loghostcolumn == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, "%s",

PAM_MYSQL_LOG_PREFIX "sqllog set but loghostcolumn not set");

return PAM_MYSQL_ERR_INVAL;

}

if (ctx->logtimecolumn == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, "%s",

PAM_MYSQL_LOG_PREFIX "sqllog set but logtimecolumn not set");

return PAM_MYSQL_ERR_INVAL;

}

if (ctx->logrhostcolumn) {

err = pam_mysql_format_string(ctx, &query,

"INSERT INTO %[logtable] (%[logmsgcolumn], %[logusercolumn], %[loghostcolumn], %[logrhostcolumn], %[logpidcolumn], %[logtimecolumn]) VALUES ('%s', '%s', '%s', '%s', '%u', NOW())", 1,

msg, user, host, rhost == NULL ? "(unknown)": rhost, getpid());

} else {

err = pam_mysql_format_string(ctx, &query,

"INSERT INTO %[logtable] (%[logmsgcolumn], %[logusercolumn], %[loghostcolumn], %[logpidcolumn], %[logtimecolumn]) VALUES ('%s', '%s', '%s', '%u', NOW())", 1,

msg, user, host, getpid());

}

if (err) {

goto out;

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "%s", query.p);

}

#ifdef HAVE_MYSQL_REAL_QUERY

if (mysql_real_query(ctx->mysql_hdl, query.p, query.len)) {

#else

if (mysql_query(ctx->mysql_hdl, query.p)) {

#endif

err = PAM_MYSQL_ERR_DB;

goto out;

}

err = PAM_MYSQL_ERR_SUCCESS;

out:

if (err == PAM_MYSQL_ERR_DB) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "MySQL error (%s)", mysql_error(ctx->mysql_hdl));

}

pam_mysql_str_destroy(&query);

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_sql_log() returning %d.", err);

}

return err;

}

/* }}} */

/* {{{ pam_mysql_converse()

*/

static pam_mysql_err_t pam_mysql_converse(pam_mysql_ctx_t *ctx, char ***pretval,

pam_handle_t *pamh, size_t nargs, ...)

{

pam_mysql_err_t err = PAM_MYSQL_ERR_SUCCESS;

int perr;

struct pam_message **msgs = NULL;

struct pam_message *bulk_msg_buf = NULL;

struct pam_response *resps = NULL;

struct pam_conv *conv = NULL;

va_list ap;

size_t i;

char **retval = NULL;

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_mysql_converse() called.");

}

va_start(ap, nargs);

/* obtain conversation interface */

if ((perr = pam_get_item(pamh, PAM_CONV,

(PAM_GET_ITEM_CONST void **)&conv))) {

syslog(LOG_AUTHPRIV | LOG_ERR,

PAM_MYSQL_LOG_PREFIX "could not obtain coversation interface (reason: %s)", pam_strerror(pamh, perr));

err = PAM_MYSQL_ERR_UNKNOWN;

goto out;

}

/* build message array */

if (NULL == (msgs = xcalloc(nargs, sizeof(struct pam_message *)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

for (i = 0; i < nargs; i++) {

msgs[i] = NULL;

}

if (NULL == (bulk_msg_buf = xcalloc(nargs, sizeof(struct pam_message)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

for (i = 0; i < nargs; i++) {

msgs[i] = &bulk_msg_buf[i];

msgs[i]->msg_style = va_arg(ap, int);

msgs[i]->msg = va_arg(ap, char *);

}

if (NULL == (retval = xcalloc(nargs + 1, sizeof(char **)))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

for (i = 0; i < nargs; i++) {

retval[i] = NULL;

}

switch ((perr = conv->conv(nargs,

(PAM_CONV_CONST struct pam_message **)msgs, &resps,

conv->appdata_ptr))) {

case PAM_SUCCESS:

break;

#ifdef HAVE_PAM_CONV_AGAIN

case PAM_CONV_AGAIN:

break;

#endif

default:

syslog(LOG_DEBUG, PAM_MYSQL_LOG_PREFIX "conversation failure (reason: %s)",

pam_strerror(pamh, perr));

err = PAM_MYSQL_ERR_UNKNOWN;

goto out;

}

for (i = 0; i < nargs; i++) {

if (resps[i].resp != NULL &&

NULL == (retval[i] = xstrdup(resps[i].resp))) {

syslog(LOG_AUTHPRIV | LOG_CRIT, PAM_MYSQL_LOG_PREFIX "allocation failure at " __FILE__ ":%d", __LINE__);

err = PAM_MYSQL_ERR_ALLOC;

goto out;

}

}

retval[i] = NULL;

out:

if (resps != NULL) {

size_t i;

for (i = 0; i < nargs; i++) {

xfree_overwrite(resps[i].resp);

}

xfree(resps);

}

if (bulk_msg_buf != NULL) {

memset(bulk_msg_buf, 0, sizeof(*bulk_msg_buf) * nargs);

xfree(bulk_msg_buf);

}

xfree(msgs);

if (err) {

if (retval != NULL) {

for (i = 0; i < nargs; i++) {

xfree_overwrite(retval[i]);

retval[i] = NULL;

}

xfree(retval);

}

} else {

*pretval = retval;

}

va_end(ap);

return err;

}

/* }}} */

/* }}} */

/* {{{ pam_mysql_query_user_caps */

static pam_mysql_err_t pam_mysql_query_user_caps(pam_mysql_ctx_t *ctx,

int *pretval, const char *user)

{

*pretval = 0;

if (geteuid() == 0) {

*pretval |= PAM_MYSQL_CAP_CHAUTHTOK_SELF;

if (getuid() == 0) {

*pretval |= PAM_MYSQL_CAP_CHAUTHTOK_OTHERS;

}

}

return PAM_MYSQL_ERR_SUCCESS;

}

/* }}} */

/* {{{ PAM Authentication services */

/* {{{ pam_sm_authenticate

*/

PAM_EXTERN int pam_sm_authenticate(pam_handle_t * pamh, int flags,

int argc, const char **argv)

{

int retval;

int err;

const char *user;

const char *rhost;

char *passwd = NULL;

pam_mysql_ctx_t *ctx = NULL;

char **resps = NULL;

int passwd_is_local = 0;

switch (pam_mysql_retrieve_ctx(&ctx, pamh)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

return PAM_BUF_ERR;

default:

return PAM_SERVICE_ERR;

}

switch (pam_mysql_parse_args(ctx, argc, argv)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

if (ctx->config_file != NULL) {

switch (pam_mysql_read_config_file(ctx, ctx->config_file)) {

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

break;

}

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_sm_authenticate() called.");

}

/* Get User */

if ((retval = pam_get_user(pamh, (PAM_GET_USER_CONST char **)&user,

NULL))) {

goto out;

}

if (user == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "no user specified.");

retval = PAM_USER_UNKNOWN;

goto out;

}

switch (pam_get_item(pamh, PAM_RHOST,

(PAM_GET_ITEM_CONST void **)&rhost)) {

case PAM_SUCCESS:

break;

default:

rhost = NULL;

}

if (ctx->use_first_pass || ctx->try_first_pass) {

retval = pam_get_item(pamh, PAM_AUTHTOK,

(PAM_GET_ITEM_CONST void **)&passwd);

switch (retval) {

case PAM_SUCCESS:

break;

case PAM_NO_MODULE_DATA:

passwd = NULL;

break;

default:

retval = PAM_AUTH_ERR;

goto out;

}

switch (pam_mysql_open_db(ctx)) {

case PAM_MYSQL_ERR_BUSY:

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

case PAM_MYSQL_ERR_DB:

retval = PAM_AUTHINFO_UNAVAIL;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

err = pam_mysql_check_passwd(ctx, user, passwd,

!(flags & PAM_DISALLOW_NULL_AUTHTOK));

if (err == PAM_MYSQL_ERR_SUCCESS) {

pam_mysql_sql_log(ctx, "AUTHENTICATION SUCCESS (FIRST_PASS)", user, rhost);

} else {

pam_mysql_sql_log(ctx, "AUTHENTICATION FALURE (FIRST_PASS)", user, rhost);

}

switch (err) {

case PAM_MYSQL_ERR_SUCCESS:

if (ctx->use_first_pass || ctx->try_first_pass) {

retval = PAM_SUCCESS;

goto out;

}

break;

case PAM_MYSQL_ERR_NO_ENTRY:

if (ctx->use_first_pass) {

retval = PAM_USER_UNKNOWN;

goto out;

}

break;

case PAM_MYSQL_ERR_MISMATCH:

if (ctx->use_first_pass) {

retval = PAM_AUTH_ERR;

goto out;

}

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

}

switch (pam_mysql_converse(ctx, &resps, pamh, 1,

PAM_PROMPT_ECHO_OFF, PLEASE_ENTER_PASSWORD)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

passwd = resps[0];

passwd_is_local = 1;

resps[0] = NULL;

xfree(resps);

if (passwd == NULL) {

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "failed to retrieve authentication token.");

}

retval = PAM_AUTH_ERR;

goto out;

}

switch (pam_mysql_open_db(ctx)) {

case PAM_MYSQL_ERR_BUSY:

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

case PAM_MYSQL_ERR_DB:

retval = PAM_AUTHINFO_UNAVAIL;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

if (passwd_is_local) {

(void) pam_set_item(pamh, PAM_AUTHTOK, passwd);

}

err = pam_mysql_check_passwd(ctx, user, passwd,

!(flags & PAM_DISALLOW_NULL_AUTHTOK));

if (err == PAM_MYSQL_ERR_SUCCESS) {

pam_mysql_sql_log(ctx, "AUTHENTICATION SUCCESS", user, rhost);

} else {

pam_mysql_sql_log(ctx, "AUTHENTICATION FAILURE", user, rhost);

}

switch (err) {

case PAM_MYSQL_ERR_SUCCESS:

retval = PAM_SUCCESS;

break;

case PAM_MYSQL_ERR_NO_ENTRY:

retval = PAM_USER_UNKNOWN;

goto out;

case PAM_MYSQL_ERR_MISMATCH:

retval = PAM_AUTH_ERR;

goto out;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

out:

if (ctx->disconnect_every_op) {

pam_mysql_close_db(ctx);

}

if (passwd != NULL && passwd_is_local) {

xfree_overwrite(passwd);

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_sm_authenticate() returning %d.", retval);

}

return retval;

}

/* }}} */

/* {{{ pam_sm_acct_mgmt

*/

PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t * pamh, int flags, int argc,

const char **argv)

{

int retval;

int err;

int stat;

const char *user;

const char *rhost;

pam_mysql_ctx_t *ctx = NULL;

switch (pam_mysql_retrieve_ctx(&ctx, pamh)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

return PAM_BUF_ERR;

default:

return PAM_SERVICE_ERR;

}

switch (pam_mysql_parse_args(ctx, argc, argv)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

if (ctx->config_file != NULL) {

switch (pam_mysql_read_config_file(ctx, ctx->config_file)) {

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

break;

}

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_sm_acct_mgmt() called.");

}

/* Get User */

if ((retval = pam_get_user(pamh, (PAM_GET_USER_CONST char **)&user,

NULL))) {

goto out;

}

if (user == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "no user specified.");

retval = PAM_USER_UNKNOWN;

goto out;

}

switch (pam_get_item(pamh, PAM_RHOST,

(PAM_GET_ITEM_CONST void **)&rhost)) {

case PAM_SUCCESS:

break;

default:

rhost = NULL;

}

switch (pam_mysql_open_db(ctx)) {

case PAM_MYSQL_ERR_BUSY:

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

case PAM_MYSQL_ERR_DB:

retval = PAM_AUTHINFO_UNAVAIL;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

err = pam_mysql_query_user_stat(ctx, &stat, user);

if (err == PAM_MYSQL_ERR_SUCCESS) {

pam_mysql_sql_log(ctx, "QUERYING SUCCESS", user, rhost);

} else {

pam_mysql_sql_log(ctx, "QUERYING FAILURE", user, rhost);

}

switch (err) {

case PAM_MYSQL_ERR_SUCCESS:

retval = PAM_SUCCESS;

break;

case PAM_MYSQL_ERR_NO_ENTRY:

retval = PAM_USER_UNKNOWN;

goto out;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

if (stat & PAM_MYSQL_USER_STAT_EXPIRED) {

retval = PAM_ACCT_EXPIRED;

} else if (stat & PAM_MYSQL_USER_STAT_AUTHTOK_EXPIRED) {

if (stat & PAM_MYSQL_USER_STAT_NULL_PASSWD) {

#if defined(HAVE_PAM_NEW_AUTHTOK_REQD)

retval = PAM_NEW_AUTHTOK_REQD;

#else

retval = PAM_AUTHTOK_EXPIRED;

#endif

} else {

retval = PAM_AUTHTOK_EXPIRED;

}

}

out:

if (ctx->disconnect_every_op) {

pam_mysql_close_db(ctx);

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_sm_acct_mgmt() returning %i.",retval);

}

return retval;

}

/* }}} */

/* {{{ pam_sm_setcred

*/

PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh,int flags,int argc,

const char **argv)

{

#ifdef DEBUG

syslog(LOG_INFO, "%s", PAM_MYSQL_LOG_PREFIX "setcred called but not implemented.");

#endif

return PAM_SUCCESS;

}

/* }}} */

/* {{{ pam_sm_chauthtok

*/

PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh,int flags,int argc,

const char **argv)

{

int retval;

int err;

const char *user;

const char *rhost;

char *old_passwd = NULL;

char *first_enter = NULL;

char *new_passwd = NULL;

int old_passwd_should_be_freed = 0;

int new_passwd_is_local = 0;

int caps = 0;

int stat = 0;

pam_mysql_ctx_t *ctx = NULL;

switch (pam_mysql_retrieve_ctx(&ctx, pamh)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

return PAM_BUF_ERR;

default:

return PAM_SERVICE_ERR;

}

switch (pam_mysql_parse_args(ctx, argc, argv)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

if (ctx->config_file != NULL) {

switch (pam_mysql_read_config_file(ctx, ctx->config_file)) {

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

break;

}

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_sm_chauthtok() called.");

}

/* Get User */

if ((retval = pam_get_user(pamh, (PAM_GET_USER_CONST char **)&user,

NULL))) {

goto out;

}

if (user == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "no user specified.");

retval = PAM_USER_UNKNOWN;

goto out;

}

switch (pam_get_item(pamh, PAM_RHOST,

(PAM_GET_ITEM_CONST void **)&rhost)) {

case PAM_SUCCESS:

break;

default:

rhost = NULL;

}

err = pam_mysql_open_db(ctx);

if (flags & PAM_PRELIM_CHECK) {

switch (err) {

case PAM_MYSQL_ERR_BUSY:

case PAM_MYSQL_ERR_SUCCESS:

break;

default:

retval = PAM_TRY_AGAIN;

goto out;

}

} else {

switch (err) {

case PAM_MYSQL_ERR_BUSY:

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

case PAM_MYSQL_ERR_DB:

retval = PAM_PERM_DENIED;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

}

if (!(flags & PAM_UPDATE_AUTHTOK)) {

goto out;

}

err = pam_mysql_query_user_caps(ctx, &caps, user);

switch (err) {

case PAM_MYSQL_ERR_SUCCESS:

retval = PAM_SUCCESS;

break;

case PAM_MYSQL_ERR_NO_ENTRY:

retval = PAM_SUCCESS;

caps = 0;

break;

default:

retval = PAM_PERM_DENIED;

goto out;

}

if (!(caps & (PAM_MYSQL_CAP_CHAUTHTOK_SELF

| PAM_MYSQL_CAP_CHAUTHTOK_OTHERS))) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "User is not allowed to change the authentication token.");

retval = PAM_PERM_DENIED;

goto out;

}

err = pam_mysql_query_user_stat(ctx, &stat, user);

if (err == PAM_MYSQL_ERR_SUCCESS) {

pam_mysql_sql_log(ctx, "QUERYING SUCCESS", user, rhost);

} else {

pam_mysql_sql_log(ctx, "QUERYING FAILURE", user, rhost);

}

switch (err) {

case PAM_MYSQL_ERR_SUCCESS:

retval = PAM_SUCCESS;

break;

default:

retval = PAM_PERM_DENIED;

goto out;

}

if (!(flags & PAM_CHANGE_EXPIRED_AUTHTOK) &&

(stat & PAM_MYSQL_USER_STAT_EXPIRED)) {

retval = PAM_AUTHTOK_LOCK_BUSY;

goto out;

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "update authentication token");

}

if (!(caps & PAM_MYSQL_CAP_CHAUTHTOK_OTHERS) &&

!(stat & PAM_MYSQL_USER_STAT_NULL_PASSWD)) {

if (ctx->use_first_pass || ctx->try_first_pass) {

retval = pam_get_item(pamh, PAM_OLDAUTHTOK,

(PAM_GET_ITEM_CONST void **)&old_passwd);

switch (retval) {

case PAM_SUCCESS:

break;

case PAM_NO_MODULE_DATA:

old_passwd = NULL;

break;

default:

retval = PAM_AUTHTOK_ERR;

goto out;

}

if (old_passwd != NULL) {

switch (pam_mysql_check_passwd(ctx, user, old_passwd, 0)) {

case PAM_MYSQL_ERR_SUCCESS:

retval = PAM_SUCCESS;

break;

case PAM_MYSQL_ERR_NO_ENTRY:

retval = PAM_USER_UNKNOWN;

goto out;

case PAM_MYSQL_ERR_MISMATCH:

if (ctx->use_first_pass) {

retval = PAM_AUTH_ERR;

goto out;

}

retval = PAM_SUCCESS;

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

}

}

if (!ctx->use_first_pass) {

char **resps;

if (flags & PAM_SILENT) {

retval = PAM_AUTHTOK_RECOVERY_ERR;

goto out;

}

switch (pam_mysql_converse(ctx, &resps, pamh, 1,

PAM_PROMPT_ECHO_OFF, PLEASE_ENTER_OLD_PASSWORD)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

old_passwd = resps[0];

old_passwd_should_be_freed = 1;

resps[0] = NULL;

xfree(resps);

if (old_passwd == NULL) {

retval = PAM_AUTHTOK_RECOVERY_ERR;

goto out;

}

switch (pam_mysql_check_passwd(ctx, user, old_passwd, 0)) {

case PAM_MYSQL_ERR_SUCCESS:

retval = PAM_SUCCESS;

break;

case PAM_MYSQL_ERR_NO_ENTRY:

retval = PAM_USER_UNKNOWN;

goto out;

case PAM_MYSQL_ERR_MISMATCH:

retval = PAM_AUTH_ERR;

goto out;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

if ((retval = pam_set_item(pamh, PAM_OLDAUTHTOK,

old_passwd)) != PAM_SUCCESS) {

goto out;

}

}

}

retval = pam_get_item(pamh, PAM_AUTHTOK,

(PAM_GET_ITEM_CONST void **)&new_passwd);

switch (retval) {

case PAM_SUCCESS:

break;

case PAM_NO_MODULE_DATA:

new_passwd = NULL;

break;

default:

retval = PAM_AUTHTOK_ERR;

goto out;

}

if (new_passwd == NULL) {

char **resps;

if (ctx->use_first_pass) {

retval = PAM_AUTHTOK_RECOVERY_ERR;

goto out;

}

if (flags & PAM_SILENT) {

retval = PAM_AUTHTOK_RECOVERY_ERR;

goto out;

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, "Asking for new password (1)");

}

switch (pam_mysql_converse(ctx, &resps, pamh, 1,

PAM_PROMPT_ECHO_OFF, PLEASE_ENTER_NEW_PASSWORD)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

first_enter = resps[0];

resps[0] = NULL;

xfree(resps);

switch (pam_mysql_converse(ctx, &resps, pamh, 1,

PAM_PROMPT_ECHO_OFF, PLEASE_REENTER_NEW_PASSWORD)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

new_passwd = resps[0];

new_passwd_is_local = 1;

resps[0] = NULL;

xfree(resps);

if (new_passwd == NULL || strcmp(first_enter, new_passwd) != 0) {

retval = PAM_AUTHTOK_RECOVERY_ERR;

goto out;

}

}

switch (pam_mysql_update_passwd(ctx, user, new_passwd)) {

case PAM_MYSQL_ERR_SUCCESS:

if (new_passwd_is_local) {

(void) pam_set_item(pamh, PAM_AUTHTOK, new_passwd);

}

retval = PAM_SUCCESS;

break;

default:

retval = PAM_AUTHTOK_ERR;

break;

}

if (retval == PAM_SUCCESS) {

pam_mysql_sql_log(ctx, "ALTERATION SUCCESS", user, rhost);

} else {

pam_mysql_sql_log(ctx, "ALTERATION FAILURE", user, rhost);

}

out:

if (ctx->disconnect_every_op) {

pam_mysql_close_db(ctx);

}

if (new_passwd != NULL && new_passwd_is_local) {

xfree_overwrite(new_passwd);

}

if (first_enter != NULL) {

xfree_overwrite(first_enter);

}

if (old_passwd != NULL && old_passwd_should_be_freed) {

xfree_overwrite(old_passwd);

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_sm_chauthtok() returning %d.", retval);

}

return retval;

}

/* }}} */

/* {{{ pam_sm_open_session

*/

PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc,

const char **argv)

{

int retval;

pam_mysql_ctx_t *ctx = NULL;

const char *user;

const char *rhost;

switch (pam_mysql_retrieve_ctx(&ctx, pamh)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

return PAM_BUF_ERR;

default:

return PAM_SERVICE_ERR;

}

switch (pam_mysql_parse_args(ctx, argc, argv)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

if (ctx->config_file != NULL) {

switch (pam_mysql_read_config_file(ctx, ctx->config_file)) {

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

break;

}

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_sm_open_session() called.");

}

/* Get User */

if ((retval = pam_get_user(pamh, (PAM_GET_USER_CONST char **)&user,

NULL))) {

goto out;

}

if (user == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "no user specified.");

retval = PAM_USER_UNKNOWN;

goto out;

}

switch (pam_get_item(pamh, PAM_RHOST,

(PAM_GET_ITEM_CONST void **)&rhost)) {

case PAM_SUCCESS:

break;

default:

rhost = NULL;

}

switch (pam_mysql_open_db(ctx)) {

case PAM_MYSQL_ERR_BUSY:

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

case PAM_MYSQL_ERR_DB:

retval = PAM_AUTHINFO_UNAVAIL;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

pam_mysql_sql_log(ctx, "OPEN SESSION", user, rhost);

out:

if (ctx->disconnect_every_op) {

pam_mysql_close_db(ctx);

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_sm_open_session() returning %i.", retval);

}

return retval;

}

/* }}} */

/* {{{ pam_sm_close_session

*/

PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc,

const char **argv)

{

int retval;

pam_mysql_ctx_t *ctx = NULL;

const char *user;

const char *rhost;

switch (pam_mysql_retrieve_ctx(&ctx, pamh)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

return PAM_BUF_ERR;

default:

return PAM_SERVICE_ERR;

}

switch (pam_mysql_parse_args(ctx, argc, argv)) {

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

if (ctx->config_file != NULL) {

switch (pam_mysql_read_config_file(ctx, ctx->config_file)) {

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

default:

break;

}

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_sm_close_session() called.");

}

/* Get User */

if ((retval = pam_get_user(pamh, (PAM_GET_USER_CONST char **)&user,

NULL))) {

goto out;

}

if (user == NULL) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "no user specified.");

retval = PAM_USER_UNKNOWN;

goto out;

}

switch (pam_get_item(pamh, PAM_RHOST,

(PAM_GET_ITEM_CONST void **)&rhost)) {

case PAM_SUCCESS:

break;

default:

rhost = NULL;

}

switch (pam_mysql_open_db(ctx)) {

case PAM_MYSQL_ERR_BUSY:

case PAM_MYSQL_ERR_SUCCESS:

break;

case PAM_MYSQL_ERR_ALLOC:

retval = PAM_BUF_ERR;

goto out;

case PAM_MYSQL_ERR_DB:

retval = PAM_AUTHINFO_UNAVAIL;

goto out;

default:

retval = PAM_SERVICE_ERR;

goto out;

}

pam_mysql_sql_log(ctx, "CLOSE SESSION", user, rhost);

out:

if (ctx->disconnect_every_op) {

pam_mysql_close_db(ctx);

}

if (ctx->verbose) {

syslog(LOG_AUTHPRIV | LOG_ERR, PAM_MYSQL_LOG_PREFIX "pam_sm_close_session() returning %i.", retval);

}

return retval;

}

/* }}} */

/* }}} */

/* end of module definition */

#ifdef PAM_STATIC

/* static module data */

struct pam_module _pam_mysql_modstruct = {

PAM_MODULE_NAME,

pam_sm_authenticate,

pam_sm_setcred,

pam_sm_acct_mgmt,

pam_sm_open_session,

pam_sm_close_session,

pam_sm_chauthtok

};

#endif

/*

* vim600: fdm=marker sw=4 ts=4 sts=4

*/

一键复制

编辑

Web IDE

原始数据

按行查看

历史

Logo

瓜分20万奖金 获得内推名额 丰厚实物奖励 易参与易上手

更多推荐