cyrusimap / cyrus-sasl

Other
129 stars 149 forks source link

postgresql auth mechanism #286

Open brong opened 17 years ago

brong commented 17 years ago

From: Kwiatek Bugzilla-Id: 2900 Version: 2.1.x Owner: Ken Murchison

brong commented 17 years ago

From: Kwiatek

This two files provides pgsql auth mechanism for saslauthd: They need to be putted in saslauthd directory, and require manual changes in Makefile, saslauthd.c files.

auth_pgsql.h

ifndef __AUTH_PGSQL_H

define __AUTH_PGSQL_H

char auth_pgsql(const char , const char , const char , const char *); int auth_pgsql_init(void);

include <libpq-fe.h>

define PGSQL_OK 0

define PGSQL_FAIL -1

define PGSQL_NOMEM -2

define PGSQL_RETRY -3

define PGSQL_NOT_GROUP_MEMBER -4

define PGSQL_INVALID_PASSWORD -5

define PGSQL_USER_NOT_FOUND -6

define PGSQL_BIND_FAIL -7

define PGSQL_CONNECT_FAIL -8

define PGSQL_NOT_CONNECT 1

define PGSQL_CONNECT 2

define PGSQL_BUF_LEN 256

typedef struct pgsql_conf { char dbhost[PGSQL_BUF_LEN]; char dbport[PGSQL_BUF_LEN]; char dbuser[PGSQL_BUF_LEN]; char dbpass[PGSQL_BUF_LEN]; char database[PGSQL_BUF_LEN]; char select[PGSQL_BUF_LEN]; int verbose; } PGSQL_CONF;

typedef struct pgsql { PGconn ld; char status; PGSQL_CONF conf; } PGSQL;

endif

auth_pgsql.c:

ifdef GNUC

ident "$Id: auth_pgsql.c "

endif

include <stdlib.h>

include <string.h>

include <syslog.h>

include <ctype.h>

include "mechanisms.h"

ifdef HAVE_UNISTD_H

include <unistd.h>

endif

ifdef HAVE_CRYPT_H

include <crypt.h>

endif

ifdef HAVE_OPENSSL

include <openssl/evp.h>

include <openssl/des.h>

endif

ifdef HAVE_PGSQL

include "auth_pgsql.h"

endif

typedef struct pgsql_hash_rock { const char *mda; int salted; } PGSQL_HASH_ROCK;

typedef struct pgsql_password_scheme { char hash; int (check) (const char cred, const char passwd, void rock); void rock; } PGSQL_PASSWORD_SCHEME;

static int pgsql_check_hashed(const char hash, const char passwd, void rock); static int pgsql_check_crypt(const char hash,const char passwd,void rock attribute((unused))); int pgsql_init(const char *configfile, PGSQL *ret); static int pgsql_config(const char configfile,PGSQL_CONF *ret); static int pgsql_config_read(PGSQL_CONF conf, const char configfile); static int pgsql_check_password( const char hash,const char passwd,void rock attribute((unused))); static int pgsql_verify_password(PGSQL pgsql, const char user,const char service,const char realm,const char *password);

static PGSQL_HASH_ROCK hash_rock[] = {

ifdef HAVE_OPENSSL

    { &quot;md5&quot;, 0 },
    { &quot;md5&quot;, 1 },
    { &quot;sha&quot;, 0 },
    { &quot;sha&quot;, 1 },
    { &quot;sha1&quot;, 0 },
    { &quot;sha1&quot;, 1 }

endif

};

static PGSQL_PASSWORD_SCHEME password_scheme[] = { { "{CRYPT}", pgsql_check_crypt, NULL }, { "{UNIX}", pgsql_check_crypt, NULL },

ifdef HAVE_OPENSSL

    { &quot;{MD5}&quot;, pgsql_check_hashed, &amp;hash_rock[0] },
    { &quot;{SMD5}&quot;, pgsql_check_hashed, &amp;hash_rock[1] },
    { &quot;{SHA}&quot;, pgsql_check_hashed, &amp;hash_rock[2] },
    { &quot;{SSHA}&quot;, pgsql_check_hashed, &amp;hash_rock[3] },
    { &quot;{SHA1}&quot;, pgsql_check_hashed, &amp;hash_rock[4] },
    { &quot;{SSH1A}&quot;, pgsql_check_hashed, &amp;hash_rock[5] },

endif

    { NULL, NULL, NULL }

};

/ FUNCTION: auth_ldap /

ifdef AUTH_PGSQL

define ISSET(x) ((x != NULL) && (*(x) != '\0'))

define EMPTY(x) ((x == NULL) || (*(x) == '\0'))

include "globals.h"

const char *SASLAUTHD_CONF_FILE = SASLAUTHD_CONF_FILE_DEFAULT;

int pgsql_init(const char *configfile, PGSQL *ret) { PGSQL pgsql; int rc;

pgsql=*ret;

if (pgsql!=NULL) { return PGSQL_OK; };

pgsql=(PGSQL *)malloc(sizeof(PGSQL)); if (pgsql==NULL) return PGSQL_NOMEM;

pgsql->status=PGSQL_NOT_CONNECT; pgsql->ld=NULL; rc = pgsql_config(configfile,&pgsql->conf); if (rc!=0) { free(pgsql); return rc; };

ifdef HAVE_OPENSSL

    OpenSSL_add_all_digests();

endif

*ret=pgsql; return PGSQL_OK; };

static int pgsql_config(const char *configfile,PGSQL_CONF *ret) { PGSQL_CONF conf; int rc=0;

conf=malloc(sizeof(PGSQL_CONF)); if (conf==NULL) { return PGSQL_NOMEM; };

memset(conf,0,sizeof(PGSQL_CONF)); strlcpy(conf->dbhost,"localhost",PGSQL_BUF_LEN); strlcpy(conf->select,"select password from users where uid='%s'",PGSQL_BUF_LEN); conf->verbose=0;

rc = pgsql_config_read(conf, configfile); if (rc!=0) { if (conf!=NULL) { memset(conf,0,sizeof(PGSQL_CONF)); free(conf); } return rc; }; *ret=conf; return PGSQL_OK; };

static int pgsql_config_read(PGSQL_CONF conf, const char configfile) { FILE in; int lineno=0; char buf[2048]; char p,*key;

static int pgsql_config_int(const char val) { if (!val) return 0; if (!isdigit((int) val) && (*val != '-' || !isdigit((int) val[1]))) return 0; return atoi(val); }

in = fopen(configfile, "r"); if (!in) { syslog(LOG_ERR|LOG_LOCAL5,"Could not open saslauthd config file: %s (%m)",configfile); return -1; };

while (fgets(buf, sizeof(buf), in)) { lineno++;

    if (buf[strlen(buf)-1] == &#39;\n&#39;) buf[strlen(buf)-1] = &#39;\0&#39;;
    for (p = buf; *p &amp;&amp; isspace((int) *p); p++);
            if (!*p || *p == &#39;#&#39;)  continue;

    key=p;
    while (*p &amp;&amp; (isalnum((int) *p) || *p == &#39;-&#39; || *p == &#39;_&#39;)) {
            if (isupper((int) *p))
                    *p = tolower(*p);
            p++; };

    if (*p!=&#39;:&#39;) return -1;
    *p++ = &#39;\0&#39;;

    while (*p &amp;&amp; isspace((int) *p)) p++;
    if (!*p) return -1;

    if (!strcasecmp(key, &quot;pgsql_server&quot;))
            strlcpy(conf-&gt;dbhost, p, PGSQL_BUF_LEN);
    else if (!strcasecmp(key, &quot;pgsql_port&quot;))
            strlcpy(conf-&gt;dbport, p, PGSQL_BUF_LEN);
    else if (!strcasecmp(key, &quot;pgsql_username&quot;))
            strlcpy(conf-&gt;dbuser, p, PGSQL_BUF_LEN);
    else if (!strcasecmp(key, &quot;pgsql_password&quot;))
            strlcpy(conf-&gt;dbpass, p, PGSQL_BUF_LEN);
    else if (!strcasecmp(key, &quot;pgsql_database&quot;))
            strlcpy(conf-&gt;database, p, PGSQL_BUF_LEN);
    else if (!strcasecmp(key, &quot;pgsql_select&quot;))
            strlcpy(conf-&gt;select, p, PGSQL_BUF_LEN);
    else if (!strcasecmp(key, &quot;pgsql_verbose&quot;))
            conf-&gt;verbose=pgsql_config_int(p);
    };

fclose(in);

return PGSQL_OK; };

char *pgsql_error( const int errno) {

switch (errno) {
    case PGSQL_OK:
        return &quot;Success&quot;;
    case PGSQL_FAIL:
        return &quot;Generic error&quot;;
    case PGSQL_NOMEM:
        return &quot;Out of memory&quot;;
    case PGSQL_RETRY:
        return &quot;Retry condition (pgsql server connection reset or broken)&quot;;
    case PGSQL_NOT_GROUP_MEMBER:
        return &quot;Group member check failed&quot;;
    case PGSQL_INVALID_PASSWORD:
        return &quot;Invalid password&quot;;
    case PGSQL_USER_NOT_FOUND:
        return &quot;User not found&quot;;
    case PGSQL_CONNECT_FAIL:
        return &quot;Cannot connect to pgsql server (configuration error)&quot;;
    default:
        return &quot;Unknow error&quot;;
}

}

static int pgsql_check_crypt(const char hash,const char passwd,void rock attribute((unused))) { char cred;

if (strlen(hash) < 2 ) return PGSQL_INVALID_PASSWORD;

cred = crypt(passwd, hash); if (EMPTY(cred)) return PGSQL_INVALID_PASSWORD; return strcmp(hash, cred) ? PGSQL_INVALID_PASSWORD : PGSQL_OK; }

ifdef HAVE_OPENSSL

static int pgsql_check_hashed(const char hash, const char passwd, void rock) { int rc, dlen; PGSQL_HASH_ROCK hrock = (PGSQL_HASH_ROCK ) rock; EVP_MD_CTX mdctx; const EVP_MD md; unsigned char digest[EVP_MAX_MD_SIZE]; char dg[256]; char
ch[16]={'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f' }; int i;

md = EVP_get_digestbyname(hrock->mda); if (!md) { syslog(LOG_ERR|LOG_LOCAL5,"unknown digest type[%s]", hrock->mda); return PGSQL_FAIL; };

EVP_MD_CTX_init(&mdctx); EVP_DigestInit_ex(&mdctx, md, NULL); EVP_DigestUpdate(&mdctx,passwd,strlen(passwd)); EVP_DigestFinal_ex(&mdctx, digest, &dlen); EVP_MD_CTX_cleanup(&mdctx);

rc=0; for(i = 0; i < dlen; i++) { dg[rc]=ch[(digest[i] & 0xf0)>>4]; rc ++; dg[rc]=ch[(digest[i] & 0xf)]; rc ++; }; dg[rc]='\0';

rc = strncmp(dg, (char *)hash, rc); return rc ? PGSQL_INVALID_PASSWORD : PGSQL_OK; }

endif

static int pgsql_verify_password(PGSQL pgsql, const char user,const char service,const char realm,const char password) { int i, hlen; PGresult res; char select[2048]; char *getp;

if (EMPTY(password)) return PGSQL_INVALID_PASSWORD;

snprintf(select,2048,pgsql->conf->select,user);

res=PQexec(pgsql->ld,select); if (!res || PQresultStatus(res)!=PGRES_TUPLES_OK) { syslog(LOG_ERR|LOG_LOCAL5,"cant exec pgsql select: %s",PQerrorMessage(pgsql->ld)); PQclear(res); return PGSQL_FAIL; };

if (PQntuples(res)!=1) { syslog(LOG_ERR|LOG_LOCAL5,"no single entry for user [%s]",user); PQclear(res); return PGSQL_USER_NOT_FOUND; }; getp=PQgetvalue(res,0,0);

i=pgsql_check_password(getp,password,NULL); PQclear(res); return i; }

static int pgsql_check_password( const char hash,const char passwd,void *rock attribute((unused))) { int i,hlen;

if (EMPTY(hash)) return PGSQL_INVALID_PASSWORD;

if (EMPTY(passwd)) return PGSQL_INVALID_PASSWORD;

for (i = 0; password_scheme[i].hash != NULL; i++) { hlen = strlen(password_scheme[i].hash); if (!strncasecmp(password_scheme[i].hash, hash, hlen)) { if (password_scheme[i].check) { return (password_scheme[i].check)(hash+hlen, passwd, password_scheme[i].rock); } return PGSQL_FAIL; } } return strcmp(hash, passwd) ? PGSQL_INVALID_PASSWORD : PGSQL_OK; };

PGconn pgsql_connect(PGSQL pgsql) { PGconn *cn;

cn=PQsetdbLogin( pgsql->conf->dbhost!=NULL?pgsql->conf->dbhost:NULL, pgsql->conf->dbport!=NULL?pgsql->conf->dbport:NULL, NULL,NULL, pgsql->conf->database!=NULL?pgsql->conf->database:NULL, pgsql->conf->dbuser!=NULL?pgsql->conf->dbuser:NULL, pgsql->conf->dbpass!=NULL?pgsql->conf->dbpass:NULL );

if (PQstatus(cn)==CONNECTION_BAD) { PQfinish(cn); return NULL; }; return cn; };

int pgsql_authenticate(PGSQL pgsql,const char user,const char service,const char realm,const char *password) { int i,rc; int retry=2;

if (pgsql==NULL) { syslog(LOG_ERR|LOG_LOCAL5,"pgsql_init did not run."); return PGSQL_FAIL; };

if (pgsql->ld==NULL) { PGconn *conn; conn=pgsql_connect(pgsql); pgsql->ld=conn; pgsql->status=PGSQL_CONNECT; };

if (pgsql->ld==NULL) { syslog(LOG_ERR|LOG_LOCAL5,"Cant connect to psql server"); return PGSQL_CONNECT_FAIL; };

for (;retry > 0; retry--) { rc=pgsql_verify_password(pgsql,user,service,realm,password); switch(rc) { case 0: return PGSQL_OK; case 1: if (retry>1) { syslog(LOG_INFO|LOG_LOCAL5,"Retrying authentication"); break; } default: syslog(LOG_DEBUG|LOG_LOCAL5, "Authentication failed for %s%s%s: %s(%d)", user, (ISSET(realm) ? "/" : ""), (ISSET(realm) ? realm : ""), pgsql_error(rc), rc); return PGSQL_FAIL; }; };

};

// end of authentication

char auth_pgsql( const char login, const char password, const char service,const char *realm ) {

static PGSQL *pgsql = NULL; int rc=0;

if (pgsql==NULL) { rc=pgsql_init(SASLAUTHD_CONF_FILE, &pgsql); if (rc!=0) { pgsql=NULL; RETURN("NO"); }; };

rc=pgsql_authenticate(pgsql,login,service,realm,password); if (rc==0) { RETURN("OK"); } else { RETURN("NO"); }

};

int auth_pgsql_init () { struct addrinfo hints; int err; char *c; if (mech_option != NULL) { SASLAUTHD_CONF_FILE = mech_option; } return 0; }

for proper compile in saslauthd.h need to put:

define HAVE_PGSQL

define AUTH_PGSQL

For more questions please ask.

brong commented 16 years ago

From: Ken Murchison

Why not just use the existing SQL auxprop plugin? It supports ALL SASL mechanisms, not just plaintext authentication